(회고) 우아한 프리코스 4주차
Woowahan tech course, First step to mobile developer
우아한 프리코스 7기 4주차 회고
4주차 미션 내용을 보고 싶으신 분들은 가볍게 4주차 미션 챕터로 넘어가시면 되겠습니다. ㅎ
회고하기 앞서서,,,
약 3주 전에 4주차 프리코스가 이미 끝났다,,
이번 미션은 자바 및 코틀린 언어에 대한 베이스가 없었기 때문에 무척이나 어려웠다.
따라서, 구현하는데 굉장히 많은 시간이 걸리기도 했지만 완벽히 구현했다고도 하기 어려운 결과가 나왔다.
그래서 혹, 이 글을 읽는 프리코스를 준비하는 예비 개발자들에게 남기고 싶은 말은
꼭, 자바와 코틀린 기본기를 한번 알아보고 시작하면 더 좋겠다는 점을 알리고 싶다.
그런데, 같이 참가한 사람 중에 나만큼 코틀린을 모르고 시작하는 사람은 없는 것 같기는 했다,,
이제 4주차 미션 내용을 알아보자!
학습 목표
학습 목표는 지금까지의 목표와 거의 동일했다.
학습 목표를 보고싶다면, 2주차 회고에서 확인하면 된다.
그리고 이어서, 3주차 미션 공통 피드백은 아래와 같다.
3주차 공통 피드백
아래 작성한 공통 피드백은 전체 내용에서 내가 중요하다고 생각하고, 이번 미션에서 염두해 두면서 구현을 해야겠다고 생각한 내용 위주로 정리했다.
함수(메서드) 라인에 대한 기준도 적용하기.
프로그래밍 요구사항(링크 추가하기)에는 함수 길이를 15줄으로 제한하는 규칙이 포함되어 있다.
그리고 이 규칙은 main() 함수도 동일하게 적용되며, 공백도 한 줄로 간주한다.
코드의 가독성과 유지보수성을 높이기 위해 함수의 길이가 15줄을 넘어가면 함수 및 클래스 분리를 고려해야한다.- 예외 상황에 대해 고민한다.
정상적인 상황을 구현하는 것보다 예외 상황을 모두 고려하여 프로그래밍 하는 것이 더욱 어렵다.
하지만, 이러한 상황을 염두하고 처리할 수 있는 습관을 들이는 것이 중요하다.이 부분은 3주차 미션에서의
테스트를 작성하는 이유
와 동일하다고 생각한다. 비즈니스 로직과 UI 로직을 분리한다.
비즈니스 로직과 UI 로직을 한 클래스에서 처리하는 것은 단일 책임 원칙(SRP, Single Responsibility Principle)에 위배 된다.
비즈니스 로직은 데이터 처리 및 도메인 규칙을 담당하고, UI 로직은 화면에 데이터를 표시하거나 입력을 받는 역할로 분리한다.
UI 관련 코드는 별도View
클래스로 분리해야한다.객체의 상태 접근을 제한한다.
객체의 상태 접근을 제한하는 것은 캡슐화(Encapsulation)의 중요한 원칙 중 하나이다.
인스턴스 변수의 접근 제어자를private
으로 설정하면 외부에서 직접 해당 변수에 접근하거나 수정하는 것을 방지하여,
객체의 상태는, 객체 내에서만 관리 될 수 있다.- 객체는 객체답게 사용한다. 객체에서 데이터를 꺼내지(get) 않고, 메시지를 던지도록 구조를 바꾸어 데이터를 가지는 객체가 일하도록 해야한다.
다음 링크, getter를 사용하는 대신 객체에 메시지를 보내자 내용 참고.
테스트를 위한 코드는 구현 코드에서 분리되어야 한다.
테스트를 위해 구현 코드를 변경하는 것은 좋지 않은 습관이다.
테스트 코드를 작성하다 보면, 테스트를 더 쉽게 하기 위해 접근 제어자를 변경하거나, 테스트에서만 사용되는 메서드를 구현 코드에 추가하는 경우가 생긴다.
그러나, 이렇게 하면 구현 코드가 테스트에 종속되며, 캡슐화가 깨져 코드의 일관성이 저해된다.- 단위 테스트하기 어려운 코드를 단위 테스트하기
테스트하기 어려운 요소(ex. Random)를 외부에서 주입하도록 하여 테스트를 용이하게 할 수 있다.다음 링크, 메서드 시그니처를 수정하여 테스트하기 좋은 메서드로 만들기 내용 참고.
4 주차 미션
4주차 미션은 편의점을 구현하는 것이었다.
기능 요구 사항
구매자의 할인 혜택과 재고 상황을 고려하여 최종 결제 금액을 계산하고 안내하는 결제 시스템을 구현한다.
- 사용자가 입력한 상품의 가격과 수량을 기반으로 최종 결제 금액을 계산한다.
- 총구매액은 상품별 가격과 수량을 곱하여 계산하며, 프로모션 및 멤버십 할인 정책을 반영하여 최종 결제 금액을 산출한다.
- 구매 내역과 산출한 금액 정보를 영수증으로 출력한다.
- 영수증 출력 후 추가 구매를 진행할지 또는 종료할지를 선택할 수 있다.
- 사용자가 잘못된 값을 입력할 경우
IllegalArgumentException
를 발생시키고, “[ERROR]”로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.Exception
이 아닌IllegalArgumentException
,IllegalStateException
등과 같은 명확한 유형을 처리한다.
재고 관리
- 각 상품의 재고 수량을 고려하여 결제 가능 여부를 확인한다.
- 고객이 상품을 구매할 때마다, 결제된 수량만큼 해당 상품의 재고에서 차감하여 수량을 관리한다.
- 재고를 차감함으로써 시스템은 최신 재고 상태를 유지하며, 다음 고객이 구매할 때 정확한 재고 정보를 제공한다.
프로모션 할인
- 오늘 날짜가 프로모션 기간 내에 포함된 경우에만 할인을 적용한다.
- 프로모션은 N개 구매 시 1개 무료 증정(Buy N Get 1 Free)의 형태로 진행된다.
- 1+1 또는 2+1 프로모션이 각각 지정된 상품에 적용되며, 동일 상품에 여러 프로모션이 적용되지 않는다.
- 프로모션 혜택은 프로모션 재고 내에서만 적용할 수 있다.
- 프로모션 기간 중이라면 프로모션 재고를 우선적으로 차감하며, 프로모션 재고가 부족할 경우에는 일반 재고를 사용한다.
- 프로모션 적용이 가능한 상품에 대해 고객이 해당 수량보다 적게 가져온 경우, 필요한 수량을 추가로 가져오면 혜택을 받을 수 있음을 안내한다.
- 프로모션 재고가 부족하여 일부 수량을 프로모션 혜택 없이 결제해야 하는 경우, 일부 수량에 대해 정가로 결제하게 됨을 안내한다.
멤버십 할인
- 멤버십 회원은 프로모션 미적용 금액의 30%를 할인받는다.
- 프로모션 적용 후 남은 금액에 대해 멤버십 할인을 적용한다.
- 멤버십 할인의 최대 한도는 8,000원이다.
영수증 출력
- 영수증은 고객의 구매 내역과 할인을 요약하여 출력한다.
- 영수증 항목은 아래와 같다.
- 구매 상품 내역: 구매한 상품명, 수량, 가격
- 증정 상품 내역: 프로모션에 따라 무료로 제공된 증정 상품의 목록
- 금액 정보
- 총구매액: 구매한 상품의 총 수량과 총 금액
- 행사할인: 프로모션에 의해 할인된 금액
- 멤버십할인: 멤버십에 의해 추가로 할인된 금액
- 내실돈: 최종 결제 금액
- 영수증의 구성 요소를 보기 좋게 정렬하여 고객이 쉽게 금액과 수량을 확인할 수 있게 한다.
입출력 요구 사항
입력
- 구현에 필요한 상품 목록과 행사 목록을 파일 입출력을 통해 불러온다.
src/main/resources/products.md
과src/main/resources/promotions.md
파일을 이용한다.- 두 파일 모두 내용의 형식을 유지한다면 값은 수정할 수 있다.
- 구매할 상품과 수량을 입력 받는다. 상품명, 수량은 하이픈(-)으로, 개별 상품은 대괄호([])로 묶어 쉼표(,)로 구분한다.
1
[콜라-10],[사이다-3]
- 프로모션 적용이 가능한 상품에 대해 고객이 해당 수량보다 적게 가져온 경우, 그 수량만큼 추가 여부를 입력받는다.
- Y: 증정 받을 수 있는 상품을 추가한다.
- N: 증정 받을 수 있는 상품을 추가하지 않는다.
1
Y
- 프로모션 재고가 부족하여 일부 수량을 프로모션 혜택 없이 결제해야 하는 경우, 일부 수량에 대해 정가로 결제할지 여부를 입력받는다.
- Y: 일부 수량에 대해 정가로 결제한다.
- N: 정가로 결제해야하는 수량만큼 제외한 후 결제를 진행한다.
1
Y
- 멤버십 할인 적용 여부를 입력 받는다.
- Y: 멤버십 할인을 적용한다.
- N: 멤버십 할인을 적용하지 않는다.
1
Y
- 추가 구매 여부를 입력 받는다.
- Y: 재고가 업데이트된 상품 목록을 확인 후 추가로 구매를 진행한다.
- N: 구매를 종료한다.
1
Y
출력
- 환영 인사와 함께 상품명, 가격, 프로모션 이름, 재고를 안내한다. 만약 재고가 0개라면
재고 없음
을 출력한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
안녕하세요. W편의점입니다.
현재 보유하고 있는 상품입니다.
- 콜라 1,000원 10개 탄산2+1
- 콜라 1,000원 10개
- 사이다 1,000원 8개 탄산2+1
- 사이다 1,000원 7개
- 오렌지주스 1,800원 9개 MD추천상품
- 오렌지주스 1,800원 재고 없음
- 탄산수 1,200원 5개 탄산2+1
- 탄산수 1,200원 재고 없음
- 물 500원 10개
- 비타민워터 1,500원 6개
- 감자칩 1,500원 5개 반짝할인
- 감자칩 1,500원 5개
- 초코바 1,200원 5개 MD추천상품
- 초코바 1,200원 5개
- 에너지바 2,000원 5개
- 정식도시락 6,400원 8개
- 컵라면 1,700원 1개 MD추천상품
- 컵라면 1,700원 10개
구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])
- 프로모션 적용이 가능한 상품에 대해 고객이 해당 수량만큼 가져오지 않았을 경우, 혜택에 대한 안내 메시지를 출력한다.
현재 {상품명}은(는) 1개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (Y/N)
- 프로모션 재고가 부족하여 일부 수량을 프로모션 혜택 없이 결제해야 하는 경우, 일부 수량에 대해 정가로 결제할지 여부에 대한 안내 메시지를 출력한다.
현재 {상품명} {수량}개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까? (Y/N)
- 멤버십 할인 적용 여부를 확인하기 위해 안내 문구를 출력한다.
1
멤버십 할인을 받으시겠습니까? (Y/N)
- 구매 상품 내역, 증정 상품 내역, 금액 정보를 출력한다.
1
2
3
4
5
6
7
8
9
10
11
==============W 편의점================
상품명 수량 금액
콜라 3 3,000
에너지바 5 10,000
=============증 정===============
콜라 1
====================================
총구매액 8 13,000
행사할인 -1,000
멤버십할인 -3,000
내실돈 9,000
- 추가 구매 여부를 확인하기 위해 안내 문구를 출력한다.
1
감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)
- 사용자가 잘못된 값을 입력했을 때, “[ERROR]”로 시작하는 오류 메시지와 함께 상황에 맞는 안내를 출력한다.
- 구매할 상품과 수량 형식이 올바르지 않은 경우:
[ERROR] 올바르지 않은 형식으로 입력했습니다. 다시 입력해 주세요.
- 존재하지 않는 상품을 입력한 경우:
[ERROR] 존재하지 않는 상품입니다. 다시 입력해 주세요.
- 구매 수량이 재고 수량을 초과한 경우:
[ERROR] 재고 수량을 초과하여 구매할 수 없습니다. 다시 입력해 주세요.
- 기타 잘못된 입력의 경우:
[ERROR] 잘못된 입력입니다. 다시 입력해 주세요.
- 구매할 상품과 수량 형식이 올바르지 않은 경우:
실행 결과 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
안녕하세요. W편의점입니다.
현재 보유하고 있는 상품입니다.
- 콜라 1,000원 10개 탄산2+1
- 콜라 1,000원 10개
- 사이다 1,000원 8개 탄산2+1
- 사이다 1,000원 7개
- 오렌지주스 1,800원 9개 MD추천상품
- 오렌지주스 1,800원 재고 없음
- 탄산수 1,200원 5개 탄산2+1
- 탄산수 1,200원 재고 없음
- 물 500원 10개
- 비타민워터 1,500원 6개
- 감자칩 1,500원 5개 반짝할인
- 감자칩 1,500원 5개
- 초코바 1,200원 5개 MD추천상품
- 초코바 1,200원 5개
- 에너지바 2,000원 5개
- 정식도시락 6,400원 8개
- 컵라면 1,700원 1개 MD추천상품
- 컵라면 1,700원 10개
구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])
[콜라-3],[에너지바-5]
멤버십 할인을 받으시겠습니까? (Y/N)
Y
==============W 편의점================
상품명 수량 금액
콜라 3 3,000
에너지바 5 10,000
=============증 정===============
콜라 1
====================================
총구매액 8 13,000
행사할인 -1,000
멤버십할인 -3,000
내실돈 9,000
감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)
Y
안녕하세요. W편의점입니다.
현재 보유하고 있는 상품입니다.
- 콜라 1,000원 7개 탄산2+1
- 콜라 1,000원 10개
- 사이다 1,000원 8개 탄산2+1
- 사이다 1,000원 7개
- 오렌지주스 1,800원 9개 MD추천상품
- 오렌지주스 1,800원 재고 없음
- 탄산수 1,200원 5개 탄산2+1
- 탄산수 1,200원 재고 없음
- 물 500원 10개
- 비타민워터 1,500원 6개
- 감자칩 1,500원 5개 반짝할인
- 감자칩 1,500원 5개
- 초코바 1,200원 5개 MD추천상품
- 초코바 1,200원 5개
- 에너지바 2,000원 재고 없음
- 정식도시락 6,400원 8개
- 컵라면 1,700원 1개 MD추천상품
- 컵라면 1,700원 10개
구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])
[콜라-10]
현재 콜라 4개는 프로모션 할인이 적용되지 않습니다. 그래도 구매하시겠습니까? (Y/N)
Y
멤버십 할인을 받으시겠습니까? (Y/N)
N
==============W 편의점================
상품명 수량 금액
콜라 10 10,000
=============증 정===============
콜라 2
====================================
총구매액 10 10,000
행사할인 -2,000
멤버십할인 -0
내실돈 8,000
감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)
Y
안녕하세요. W편의점입니다.
현재 보유하고 있는 상품입니다.
- 콜라 1,000원 재고 없음 탄산2+1
- 콜라 1,000원 7개
- 사이다 1,000원 8개 탄산2+1
- 사이다 1,000원 7개
- 오렌지주스 1,800원 9개 MD추천상품
- 오렌지주스 1,800원 재고 없음
- 탄산수 1,200원 5개 탄산2+1
- 탄산수 1,200원 재고 없음
- 물 500원 10개
- 비타민워터 1,500원 6개
- 감자칩 1,500원 5개 반짝할인
- 감자칩 1,500원 5개
- 초코바 1,200원 5개 MD추천상품
- 초코바 1,200원 5개
- 에너지바 2,000원 재고 없음
- 정식도시락 6,400원 8개
- 컵라면 1,700원 1개 MD추천상품
- 컵라면 1,700원 10개
구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])
[오렌지주스-1]
현재 오렌지주스은(는) 1개를 무료로 더 받을 수 있습니다. 추가하시겠습니까? (Y/N)
Y
멤버십 할인을 받으시겠습니까? (Y/N)
Y
==============W 편의점================
상품명 수량 금액
오렌지주스 2 3,600
=============증 정===============
오렌지주스 1
====================================
총구매액 2 3,600
행사할인 -1,800
멤버십할인 -0
내실돈 1,800
감사합니다. 구매하고 싶은 다른 상품이 있나요? (Y/N)
N
프로그래밍 요구 사항 1
- Kotlin 1.9.24에서 실행 가능해야 한다.
- Java 코드가 아닌 Kotlin 코드로만 구현해야 한다.
- 프로그램 실행의 시작점은
Application
의main()
이다. build.gradle.kts
파일은 변경할 수 없으며, 제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.- 프로그램 종료 시
System.exit()
또는exitProcess()
를 호출하지 않는다. - 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 코틀린 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Kotlin Style Guide를 원칙으로 한다.
프로그래밍 요구 사항 2
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
- 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다.
프로그래밍 요구 사항 3
- else를 지양한다.
- 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- Enum 클래스를 적용하여 프로그램을 구현한다.
- 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
프로그래밍 요구 사항 4
- 함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
입출력을 담당하는 클래스를 별도로 구현한다.
- 아래
InputView
,OutputView
클래스를 참고하여 입출력 클래스를 구현한다. 클래스 이름, 메소드 반환 유형, 시그니처 등은 자유롭게 수정할 수 있다.
1 2 3 4 5 6 7 8
class InputView { fun readItem(): String { println("구매하실 상품명과 수량을 입력해 주세요. (예: [사이다-2],[감자칩-1])") val input = Console.readLine() // ... } // ... }
1 2 3 4 5 6 7
class OutputView { fun printProducts() { println("- 콜라 1,000원 10개 탄산2+1") // ... } // ... }
- 아래
4주 간의 프리코스, 어려웠던 점, 아쉬웠던 점.
얼렁뚱땅, 기능 단위 개발
1주차부터 기능 단위로 commit를 하도록 권장했는데, 이 규칙을 지키는 것이 꽤 어려웠다. 그래서, 개발을 하다보면 처음에 설계하였던 내 청사진이 잘못되었다라는 것을 알게 될 때가 많이 있었는데,
이런 일이 생길 때, 결국 다른 함수 및 클래스의 부분 부분을 수정해야하는 경우 등의 기능을 전반적을 수정해야하는 일이 발생한다.
이러한 경우 순서, 명확한 목적이 없이 코드를 변경하다보면 기능 단위 commit을 하는 규칙을 조금씩 어기게 되었던 것 같다.
마지막에는 정말 작은 단위로 개발을 한 뒤, commit을 하였는데 이러한 방법으로 개발을 하게되면 정말 시간이 많이 걸린다.
그래서 이 부분은 경험 부족이라고 생각한다.
지속적으로 공부하고 경험을 쌓으면서 더 나아지기를 바란다.
너무나도 많은 규칙
나는 대학 학부과정에서 개발을 파이썬으로 시작했다.
파이썬에도 많은 규칙이 있지만, 비교적 자유로운 코드 스타일을 가지고 있었다.
또한, 인공지능/머신러닝 등의 결과 값을 추출해내는 작업을 많이 하였는데, 학부(대학원) 수준에서는 대부분 개발하려는 모델의 규칙을 제외하면 아주 자유롭게 개발을 할 수 있었다.
따라서, 이번 프리코스에서 처음 kotlin을 접하면서 너무나도 많은 규칙을 완벽히 지키는 것은 불가능했다.
아마, 이번 우테코의 테크코스에는 불합격하게 될 것 같다.
정말 아쉽게 되었지만, 이번 프리코스를 통해 많은 것들을 경험하고 배울 수 있었던 것 같다.
앞으로 Kotlin을 지속할 지, Swift를 도전해볼 것인지는 고민스럽겠지만, 우선 Kotlin에 대한 첫 인상은 매우 괜찮은 것 같아, 이어 공부해보고자 한다.
읽어주셔서 감사합니다. 😊