2023 우테코 프리코스 4주차 후기
개요
마지막 프리코스 후기!
이번 주차에서는 “내 코드가 그렇게 이상한가요?” 라는 책을 읽은 후, 해당 내용을 최대한 의식하며 코드에 적용하고자 했다.
특히 지난 1~3주차의 내용을 집대성하는 최종 연습과도 같았는데 이를 통해 책에서 배운 이론을 실제 코딩에 어떻게 적용할 수 있는지 깊이 있게 탐색하는 기회가 되었다고 생각한다
문제 요구 사항
[추가된 요구사항]
내용 열기/닫기
아래에 제공되는 InputView, OutputView 객체를 활용해 구현한다.
입력과 출력을 담당하는 객체를 별도로 구현한다.
InputView, OutputView의 파일 경로는 변경할 수 있다.
InputView, OutputView의 메서드의 이름과 인자는 필요에 따라 추가하거나 변경할 수 있다.
값 출력을 위해 필요한 메서드를 추가할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
export default InputView = {
async readDate() {
const input = await Console.readLineAsync("12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)");
// ...
},
// ...
};
export default OutputView = {
printMenu() {
Console.print("<주문 메뉴>");
// ...
},
// ...
};
[기능 요구 사항]
내용 열기/닫기
🚀 기능 요구 사항
이번 미션은 이메일 형식의 기능 요구 사항입니다. 문제를 구현하는 데 필요한 요구사항과 배경지식은 이메일 내용에 전부 담겨있으니, 꼼꼼하게 확인하고 필요하다면 주어진 문제의 내용을 통해 유추하고 스스로 판단해 구현해 주시면 됩니다. 문제의 모든 내용은 충분히 검토되었으며, 출제 의도를 담은 내용임을 알려드립니다.
보낸 사람: 비즈니스팀 <biz@woowacourse.io
>
받는 사람: 개발팀 <dev@woowacourse.io
>
제목: 12월 이벤트를 위한 개발 요청
안녕하세요. 비즈니스팀입니다!
다가오는 2023년 12월에 우테코 식당에서 1년 중 제일 큰 이벤트를 개최하려고 합니다.
12월을 위해 이벤트 예산을 넉넉히 확보해 두었으니, 예산은 걱정하지 마세요~
특별히 이번 12월 이벤트를 진행하기 위해서, 개발팀의 도움이 많이 필요합니다.
아래 메뉴와 달력 이미지를 보면서 12월 이벤트 계획과 요청 내용을 본격적으로 설명해 드릴게요.
메뉴
1
2
3
4
5
6
7
8
9
10
11
<애피타이저>
양송이수프(6,000), 타파스(5,500), 시저샐러드(8,000)
<메인>
티본스테이크(55,000), 바비큐립(54,000), 해산물파스타(35,000), 크리스마스파스타(25,000)
<디저트>
초코케이크(15,000), 아이스크림(5,000)
<음료>
제로콜라(3,000), 레드와인(60,000), 샴페인(25,000)
달력
이벤트 목표
- 중복된 할인과 증정을 허용해서, 고객들이 혜택을 많이 받는다는 것을 체감할 수 있게 하는 것
- 올해 12월에 지난 5년 중 최고의 판매 금액을 달성
- 12월 이벤트 참여 고객의 5%가 내년 1월 새해 이벤트에 재참여하는 것
12월 이벤트 계획
- 크리스마스 디데이 할인
- 이벤트 기간: 2023.12.1 ~ 2023.12.25
- 1,000원으로 시작하여 크리스마스가 다가올수록 날마다 할인 금액이 100원씩 증가
- 총주문 금액에서 해당 금액만큼 할인
(e.g. 시작일인 12월 1일에 1,000원, 2일에 1,100원, …, 25일엔 3,400원 할인)
- 평일 할인(일요일~목요일): 평일에는 디저트 메뉴를 메뉴 1개당 2,023원 할인
- 주말 할인(금요일, 토요일): 주말에는 메인 메뉴를 메뉴 1개당 2,023원 할인
- 특별 할인: 이벤트 달력에 별이 있으면 총주문 금액에서 1,000원 할인
- 증정 이벤트: 할인 전 총주문 금액이 12만 원 이상일 때, 샴페인 1개 증정
- 이벤트 기간: ‘크리스마스 디데이 할인’을 제외한 다른 이벤트는 2023.12.1 ~ 2023.12.31 동안 적용
혜택 금액에 따른 12월 이벤트 배지 부여
- 총혜택 금액에 따라 다른 이벤트 배지를 부여합니다. 이 배지는 2024 새해 이벤트에서 활용할 예정입니다.
배지에 따라 새해 이벤트 참여 시, 각각 다른 새해 선물을 증정할 예정입니다.
- 5천 원 이상: 별
- 1만 원 이상: 트리
- 2만 원 이상: 산타
고객에게 안내할 이벤트 주의 사항
- 총주문 금액 10,000원 이상부터 이벤트가 적용됩니다.
- 음료만 주문 시, 주문할 수 없습니다.
- 메뉴는 한 번에 최대 20개까지만 주문할 수 있습니다.
(e.g. 시저샐러드-1, 티본스테이크-1, 크리스마스파스타-1, 제로콜라-3, 아이스크림-1의 총개수는 7개)
‘12월 이벤트 플래너’ 개발 요청 사항
- 고객들이 식당에 방문할 날짜와 메뉴를 미리 선택하면 이벤트 플래너가 주문 메뉴, 할인 전 총주문 금액, 증정 메뉴, 혜택 내역, 총혜택 금액, 할인 후 예상 결제 금액, 12월 이벤트 배지 내용을 보여주기를 기대합니다.
- 12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)
- 방문할 날짜는 1 이상 31 이하의 숫자로만 입력받아 주세요.
- 1 이상 31 이하의 숫자가 아닌 경우, “[ERROR] 유효하지 않은 날짜입니다. 다시 입력해 주세요.”라는 에러 메시지를 보여 주세요.
- 모든 에러 메시지는 “[ERROR]”로 시작하도록 작성해 주세요.
- 주문하실 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)
- 고객이 메뉴판에 없는 메뉴를 입력하는 경우, “[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.”라는 에러 메시지를 보여 주세요.
- 메뉴의 개수는 1 이상의 숫자만 입력되도록 해주세요. 이외의 입력값은 “[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.”라는 에러 메시지를 보여 주세요.
- 메뉴 형식이 예시와 다른 경우, “[ERROR] 유효하지 않은 주문입니다. 다시 입력해 주세요.”라는 에러 메시지를 보여 주세요.
- 중복 메뉴를 입력한 경우(e.g. 시저샐러드-1,시저샐러드-1), “[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
안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.
12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)
26
주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)
타파스-1,제로콜라-1
12월 26일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!
<주문 메뉴>
타파스 1개
제로콜라 1개
<할인 전 총주문 금액>
8,500원
<증정 메뉴>
없음
<혜택 내역>
없음
<총혜택 금액>
0원
<할인 후 예상 결제 금액>
8,500원
<12월 이벤트 배지>
없음
기대하는 ‘12월 이벤트 플래너’의 예시 모습
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
안녕하세요! 우테코 식당 12월 이벤트 플래너입니다.
12월 중 식당 예상 방문 날짜는 언제인가요? (숫자만 입력해 주세요!)
3
주문하실 메뉴를 메뉴와 개수를 알려 주세요. (e.g. 해산물파스타-2,레드와인-1,초코케이크-1)
티본스테이크-1,바비큐립-1,초코케이크-2,제로콜라-1
12월 3일에 우테코 식당에서 받을 이벤트 혜택 미리 보기!
<주문 메뉴>
티본스테이크 1개
바비큐립 1개
초코케이크 2개
제로콜라 1개
<할인 전 총주문 금액>
142,000원
<증정 메뉴>
샴페인 1개
<혜택 내역>
크리스마스 디데이 할인: -1,200원
평일 할인: -4,046원
특별 할인: -1,000원
증정 이벤트: -25,000원
<총혜택 금액>
-31,246원
<할인 후 예상 결제 금액>
135,754원
<12월 이벤트 배지>
산타
기대하는 예시를 한 개만 들어서 설명했지만, 더 다양한 사례가 있을 것으로 예상됩니다.
개발이 완료되는 대로 공유해 주시면, 비즈니스팀에서 1주일간 테스트를 진행하고 오픈할 예정입니다.
1주일 뒤에 예정된 ‘12월 이벤트 플래너’ 개발 회의에서 더 자세한 얘기를 해보면 좋겠습니다.
감사합니다. :)
추가된 요구사항을 확인해보면, UI로직을 분리하라는 의도가 크게 느껴졌다.
이는 3주차 피드백에도 관련된 내용이 있었기때문에 해당 요구사항이 크게 이질적으로 받아들여지지는 않았다.
기능 설계
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
133
134
## 기능 요구 사항 정리
### 가격
- [x] 금액을 가진다
- [x] [예외처리] 음수의 금액을 가질 수 없다
- [x] 금액을 더할 수 있어야 한다
- [x] 금액을 뺄 수 있어야 한다
- [x] 금액을 연산할때에는 같은 Money타입의 객체여야만 한다
- [x] 가진 금액 정보를 반환한다
- [x] 금액과 정수를 곱할 수 있어야 한다
### 날짜
- [x] 년,월,일에 대한 정보를 가진다
- [x] [예외처리] 존재하지 않는 날짜에 대한 정보는 가질 수 없다
- [x] 해당 날짜의 요일정보를 반환한다
- [x] 해당 날짜가 평일인지, 주말인지 정보를 반환한다
- [x] [예외처리] 연산 대상이 되는 파라메터가 같은 객체타입이 아니라면 에러를 발생시킨다
- [x] 두 날짜 사이의 간격 정보를 계산한다
- [x] 주어진 날짜가 두 간격 사이에 존재하는지 여부를 반환한다
- [x] 두 날짜가 동일한지 아닌지 정보를 반환한다
- [x] 월, 일 정보를 반환한다
### 음식
- [x] 가격을 가진다
- [x] 메뉴 이름을 가진다
- [x] 메뉴 이름은 빈 문자열일 수 없다
- [x] 메뉴 종류 정보를 가진다
- [x] 메뉴 종류는 애피타이저, 메인, 디저트, 음료 정보를 가진다
### 메뉴
- [x] 식당에서 판매중인 메뉴의 정보를 가진다
- [x] 특정 메뉴를 판매중이라면 해당 음식정보를 반환한다
- [x] [예외처리] 특정 메뉴를 판매중이지 않다면 에러를 발생시킨다
### 주문 정보
- [x] 음식리스트 정보를 가진다(음식, 개수 정보를 가진다)
- [x] [예외처리] 음료만 주문 시 주문할 수 없다
- [x] [예외처리] 최대 20개까지만 주문할 수 있다(주문하는 메뉴의 개수)
- [x] [예외처리] 0개는 주문할 수 없다
- [x] [예외처리] 각 음식은 1개 이상을 주문해야한다
- [x] [예외처리] 중복된 음식메뉴는 주문할 수 없다
- [x] 사용자가 방문할 날짜 정보를 가진다
- [x] 전체 주문 금액 정보를 반환한다
- [x] 특정 타입의 메뉴 개수를 반환한다
### 각 할인 이벤트 (인터페이스)
- [x] 할인 적용 조건 로직을 가진다
- [x] 할인의 상세 정보를 반환한다
- [x] 할인의 경우 증정품, 금액 차감 2종류의 타입을 가진다
#### 크리스 마스 디데이 할인 이벤트
- [x] 조건 확인
- [x] 2023.12.1 ~ 2023.12.25 범위의 날짜인지 확인한다
- [x] 총 주문금액이 10000원 이상이어야 한다
- [x] 1,000원으로 시작하여 크리스마스가 다가올수록 날마다 할인 금액이 100원씩 증가하도록 값을 계산한다
#### 평일 할인
- [x] 조건 확인
- [x] 2023.12.1 ~ 2023.12.31 범위의 날짜인지 확인한다
- [x] 평일이 맞는지 확인한다
- [x] 총 주문금액이 10000원 이상이어야 한다
- [x] 디저트 메뉴가 1개 이상이어야 한다
- [x] 디저트 메뉴 하나당 2023원을 할인한다
#### 주말 할인
- [x] 조건 확인
- [x] 2023.12.1 ~ 2023.12.31 범위의 날짜인지 확인한다
- [x] 주말이 맞는지 확인한다
- [x] 총 주문금액이 10000원 이상이어야 한다
- [x] 메인 메뉴가 1개 이상이어야 한다
- [x] 메인 메뉴 하나당 2023원을 할인한다
#### 특별 할인
- [x] 조건 확인
- [x] 2023.12.1 ~ 2023.12.31 범위의 날짜인지 확인한다
- [x] 3,10,17,24,25,31일에 해당하는 날짜인지 확인한다
- [x] 총 주문금액이 10000원 이상이어야 한다
- [x] 총 금액에서 1000원을 할인한다
#### 증정 이벤트
- [x] 조건 확인
- [x] 2023.12.1 ~ 2023.12.31 범위의 날짜인지 확인한다
- [x] 총 주문금액이 120,000원 이상이어야 한다
- [x] 샴페인 1개를 증정한다
### 할인 관리자
- [x] 주문정보, 할인 로직정보를 가진다
- [x] 적용된 할인 이벤트와 내용을 반환한다
- [x] 증정품 목록을 반환한다(상품, 개수)
- [x] 총 할인 금액과 사은품 가지를 반환한다
### 배지
- [x] 이름을 가진다
- [x] [예외처리] 빈 문자열의 배지는 존재할 수 없다
### 이벤트 배지 관리자
- [x] 총 혜택 금액정보를 가지고 있다
- [x] 총 혜택 금액에 따라 새해 선물을 반환한다
- [x] 5천원 이상 - 별
- [x] 1만원 이상 - 트리
- [x] 2만원 이상 - 산타
### 유틸
- [x] 금액의 경우 세자리마다 쉼표를 넣어 반환되어야 한다
- [x] 에러가 발생하면 인자로 넘겨받은 함수를 계속 실행해야한다 (입력 반복 처리)
### 전체 흐름
- [x] 플래너 안내 메세지 출력
- [x] 사용자에게 예상 방문 날짜 입력 받기 (입력)
- [x] 사용자에게 주문 메뉴, 개수를 입력받기 (입력)
- [x] 주문 생성 및 주문 메뉴 출력
- [x] 할인 전 총주문 금액 계산 및 출력
- [x] 할인 및 증정 메뉴 계산
- [x] 총 혜택 금액 및 예상 결제금액 출력
- [x] 이벤트 배치 계산 및 출력
모델 설계는 가장 작은 단위에서 시작하여 점진적으로 확장해 나갔다.
특히 주문 모델은 중요한 핵심 요소로, 내부에 음식 과 금액이라는 필드 정보를 가지고 있다.
이러한 요소들을 단순히 주문 객체 안에 직접 넣는 방식도 가능하지만, 복잡한 금전적 연산, 날짜 처리, 그리고 각각의 음식 요소에 대한 세부적인 관리를 고려할 때 이를 분리하는 것이 더 효과적이라고 판단했고 실제 명확하고 관리하기 쉬운 구조를 생성하는데 크게 도움이 되었다.
무엇보다 기능이 복잡해지고 프로젝트의 범위가 넓어짐에 따라, 진행 상황과 다음 작업 단계를 파악하는 것이 햇갈렸는데, 이러한 상황에서 정리된 기능 명세서 내용이 이정표 역할을 적합하게 수행했고 이는 전체적인 개발 과정에서 큰 도움이 되었다.
괜히 1주차부터 강조했던 내용이 아닌듯ㅎ;
적용 요소
ESLint
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
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
},
extends: ["airbnb-base", "prettier"],
overrides: [
{
env: {
node: true,
},
files: [".eslintrc.{js,cjs}"],
parserOptions: {
sourceType: "script",
},
},
],
parserOptions: {
ecmaVersion: "latest",
},
rules: {
// 들여쓰기 깊이 제한
"max-depth": ["error", 2],
// 함수의 길이 제한
"max-lines-per-function": ["error", { max: 15 }],
},
};
처개발을 시작하기에 앞서, ESLint 설정을 우선적으로 진행했다.
이전 주차때 실수로 요구사항을 만족하지 못하는 경우가 있었는데, 아무래도 사람이다보니 개발 중 발생할 수 있는 오류를 미연에 방지하고 바로잡을 수 있는 강력한 도구나 환경을 빠르게 설정하는 것은 필수적이지 않을까
실제 이러한 초기 단계의 ESLint 설정은 프로젝트 전반에 걸친 코드 품질 관리에 큰 도움이 되었으며, 개발 효율성을 높이는 데 기여했다.
불변 객체
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
class Money {
#price;
constructor(price) {
RestaurantValidator.validateMoneyModel(price);
this.#price = price;
}
add(money) {
RestaurantValidator.validateMoneyType(money, Money);
const addedPrice = this.#price + money.getPrice();
return new Money(addedPrice);
}
minus(money) {
RestaurantValidator.validateMoneyType(money, Money);
const minusPrice = this.#price - money.getPrice();
return new Money(minusPrice);
}
multiply(number) {
const multiplyedPrice = this.#price * number;
return new Money(multiplyedPrice);
}
getPrice() {
return this.#price;
}
}
export default Money;
위 코드의 Money객체는 금액을 더하거나, 곱하는등의 금액 관련 연산을 처리하는 중요한 역할을 합니다
여기서 큰 특징은 바로 price라는 필드값을 private로 선언하고 변경된 값에 대해서는 새로운 인스턴스를 반환하는 부분이다.
정확한 불변성을 위해서는 Object.freeze()로 묶었어야 했다. final같은 명령어가 js는 없기떄문
사실 price
변수에 대한 변경을 허용하는 것도 시스템 자체에 문제는 없지만 재할당을 통해 값을 업데이트한다면 어래의 문제점을 발생시킬 수 있다.
1
2
3
4
5
6
7
8
AttackPower a = new AttackPower(20);
AttackPower b = new AttackPower(20);
Weapon c = new Weapon(a);
Weapon bad = new Weapon(a);
c.attacPower.value+=5. // 이 코드는 의도치않게 bad인스턴스에게도 영향을 미친다.
Weapon good. = new Weapon(b); // 따로 만들어라
문제 1 - 가변 인스턴스의 재활용은 의도치 않은 영향을 미칠 수 있다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AttackPower {
void reinforce(int increment) {
value += increment;
}
void disable() {
value = MIN;
}
}
AttackPower attackPower = new AttackPower(20);
attackPower.reinforce(15);
attackPower.disable();
문제 2 - 이 코드는 순서에 따라 다른 결과를 가져온다.
만약 다른스레드에서 disabled()를 호출했다면 값자기 값이 변경되는 일이 발생하는 것
즉 이 방법은 작업 순서에 의존하지 않고 동일한 결과를 제공하며, 코드의 영향 범위를 한정적으로 유지할 수 있다.
이는 전체 시스템의 안정성을 향상시키고, 유지보수를 용이하게 만드는 데 기여한다!
응집도
주문 시스템 개발 중, 날짜와 관련된 다양한 연산 로직이 필요했다. 예를 들어, 주문 날짜가 특정 이벤트 기간 내에 포함되는지 확인하는 기능이 그중 하나이다.
1
2
3
4
const orderDate = order.getDate();
const { start, end } = this.#appliedPeriod;
if (!orderDate.isBetween(start, end)) return false;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CustomDate {
#date;
equal(otherDate) {
}
calculateDiff(otherDate) {
}
isBetween(startDate, endDate) {
...
}
이 코드는 주문 날짜가 설정된 기간 내에 있는지를 판단한다. 하지만 이와 같은 날짜 비교 외에도 ‘특정 날짜 포함 여부’, ‘주말 여부’ 등 다양한 날짜 관련 연산이 필요했다.
만약 이를 처리하기 위해 날짜 객체로부터 직접 값을 추출하여 연산을 처리하면, 로직이 여러 곳에 분산되어 응집도가 낮아지는 문제가 발생할 수 있다.
따라서 이러한 복잡한 연산로직이 되는 요소인 금액과 날짜를 따로 분리하고, 최대한 연산로직의 책임은 해당 객체에 위임하도록 했다.
또한, 하나의 범용 유틸리티(Utils) 클래스를 사용하여 날짜 연산을 처리하는 방안도 괜찮지 않을까 싶었지만, 이는 날짜 연산이 일반적으로 ‘일’, ‘월’, ‘시간’ 등 동일한 필드를 사용하기 때문에 공통된 필드값에 대한 연산을 하나의 클래스에서 처리하는 방법이 보다 객체 지향적 원칙에 더 부합하며, 날짜 관련 로직을 보다 효과적으로 관리할 수 있게 된다고 생각했다.
3주차 피드백, 위 내용 관련있다고 생각했다
getter, setter는 다른 클래스를 확인하고 조작하는 메서드 구조가 되기 쉽다, 호출되는 쪽에서 복잡한 작업을 하는게 좋다 (내 코드가 그렇게 이상한가요? 12.3장)
가독성
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
isApplicable(order) {
const orderDate = order.getDate();
const { start, end } = this.#appliedPeriod;
if (!orderDate.isBetween(start, end)) return false;
if (order.getTotalMoney().getPrice() < 10000) return false;
if (orderDate.isWeekend()) return false;
const desertMenuCount = this.#getTotalDesertCount(order);
if (desertMenuCount < 1) return false;
return true;
}
-
조기 리턴 사용하기
위 코드와 같이 else문은 최대한 지양하고, depth가 커지지 않도록 주의했다.
특히 조건 확인문은 상단에, 이후 로직은 뒷 부분에 몰려있으므로 향후 코드 수정이나 유지보수에도 용이한 구조가 된다.
내 코드가 그렇게 이상한가요 6장(조건 분기)
1
2
3
4
5
6
// validator
validateFoodModel(name, type, menuType) {
validateNotNull(name, ERROR_MESSAGE.emptyMenuName);
validateIncluded(type, menuType, ERROR_MESSAGE.invalidMenuType);
},
- 논리 부정 연산자를 최대한 지양하기
!validateNull() ,
!validateNotIncluded()`와 같이 논리 부정연산자를 사용하면 읽는 사람이 한번 더 생각해야하므로 가독성이 떨어진다.
최대한 부정 연산자를 사용하지 않도록 메서드명을 지정하고 활용했다
내 코드가 그렇게 이상한가요? 14장
추상 클래스
할인 로직의 경우 요구사항에 따르면 총 5개의 요소가 있었다. (크리스마스 디데이, 평일, 주말, 특별 할인, 증정 이벤트)
반복문을 돌리며, 전체 할인 로직을 구현해도 괜찮겠지만 이러한 할인요소는 가변적인 요소라고 생각했다. 당장 12월이 지나면 새로운 이벤트로 대체되어야하고
이벤트 특성상 조기종료라거나 변동가능성이 큰 부분이라고 생각이 들었기때문에 최대한 유지보수에 용이한 구조를 선택하고자 고민했다.
결국 이벤트라는건 "1. 이벤트 조건에 충족하는가?" , "2. 혜택 내용" 두 가지 부분에 대한 공통적인 연산을 처리한다고 정의했고
그 부분에 맞게 추상클래스를 먼저 선언했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class DiscountEvent {
constructor() {
if (this.constructor === DiscountEvent) {
throw new Error("[ERROR] DiscountEvent는 추상클래스 입니다.");
}
}
isApplicable(order) {
throw new Error("[ERROR] DiscountEvent는 추상클래스 입니다.");
}
getDiscountDetails(order) {
throw new Error("[ERROR] DiscountEvent는 추상클래스 입니다.");
}
}
export default DiscountEvent;
// 각 할인 이벤트가 구현해야 할 기본 틀을 제공
그리고, 각 할인에 대한 비즈니스 로직은 해당 클래스를 상속받아서 내부에서 처리하도록 구현했고 할인에 대한 요소를 관리하는 클래스에서는 간단한 반복문으로 혜택 정보를 가져올 수 있었다.
이 방법은 새로운 혜택이 추가되더라도, 공통적인 메서드 구현을 통해 손쉽게 적용할 수 있으며, 이로 인해 시스템의 유지보수가 용이하다는 장점을 크게 살릴 수 있을 것 같았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 할인 관리 클래스의 혜택 정보 계산
getDiscountResults() {
const discountResults = [];
this.#discountEvents.forEach((event) => {
if (!event.isApplicable(this.#order)) return; // 각 이벤트 인스턴스를 반복하며 조건 충족 검사
const details = event.getDiscountDetails(this.#order); // 조건을 충족하면 혜택 내용을 계산한다
discountResults.push({
name: details.name,
content: details.content,
});
});
return discountResults;
}
[관련된 고민 사항]
그러나 “할인 내용” 이라는 공통 메서드에 내용을 다 넣다보니, 응답구조가 복잡해졌다.
1
2
3
4
5
{
gift: 증정품 객체
count: 증정품 개수,
money : 할인 금액
},
결과적으로 각 할인 객체로부터 응답을 받고, 키 값을 순회하면서 데이터를 꺼냈는데 만약 할인 쿠폰 데이터가 추가된다면 저 응답 구조의 필드값에 해당 키값도 추가 해야한다.
뭐랄까 공통 기능을 묶다보니 응답 구조가 복잡해지고 기능추가에 대해 과연 유연한 구조인지 의문이 들었는데 만약 로직이 더 복잡해지고, 요소가 추가될때마다 불필요한 필드값이 늘어나기 때문이다.
다른 분들의 코드를 보니 아예 분리하신분도 계셨는데, 만약 로직이 더 복잡해지거나 새로운 요소가 추가될 경우, 금액과 선물 정보를 별도로 분리하는 방식으로 전환할 것 같다고 생각했다.
불필요한 중복?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class WeekendDiscount extends DiscountEvent {
static DISCOUNT_MENU_TYPE = MENU_TYPES.main;
static DISCOUNT_AMOUNT = 2023;
#eventName;
#appliedPeriod;
constructor() {
super();
this.#eventName = EVENT_NAMES.weekend;
this.#appliedPeriod = {
start: new CustomDate(2023, 12, 1),
end: new CustomDate(2023, 12, 31),
};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class WeekdayDiscount extends DiscountEvent {
static DISCOUNT_MENU_TYPE = MENU_TYPES.dessert;
static DISCOUNT_AMOUNT = 2023;
#eventName;
#appliedPeriod;
constructor() {
super();
this.#eventName = EVENT_NAMES.weekday;
this.#appliedPeriod = {
start: new CustomDate(2023, 12, 1),
end: new CustomDate(2023, 12, 31),
};
}
두 클래스는 각각 주말할인과 평일할인을 나타내는 할인 클래스이다.
처음에는 할인 금액 및 날짜정보가 중복되므로, 하나의 상수로 분리해서 한번에 선언하는 것도 좋은 방법이 될 수 있겠다고 생각했지만 이는 우연히 정보가 같았을 뿐이지 “평일 할인” 과 “주말 할인”은 엄연히 개념이 다르다.
만약에 평일 할인의 금액정보가 바뀐다면?? 하나로 묶었던 상수를 다시 분리해야하는데 비슷한 작업을 처리하는 코드를 범용 처리 모듈로 변경하게되면 해당 모듈과의 의존성이 생기므로 더 주의깊게 보아야했다.
“같은 로직, 비슷한 로직이라도 개념이 다르면 중복을 허용해야한다” - 8장
의존성 주입
시스템 내에서 변경 가능성이 높은 요소들, 예를 들어 이벤트 로직이나 음식 정보 등은 인스턴스 할당을 통해 데이터를 전달하는 방식으로 처리했다.
이전에는 컨트롤러 내부에서 직접 객체를 import하여 접근했으나, 이벤트 변경이 필요할 때마다 import 코드를 수정하는 것은 비효율적같다고 생각했기 때문이다.
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
class App {
async run() {
const discountEvents = this.#getDisCountEvnets();
const restaurantController = new RestaurantController(
Menu,
EventBadgeManager,
discountEvents,
OutputView,
InputView
);
await restaurantController.start();
}
#getDisCountEvnets() {
const discountEvents = [
new ChristmasDdayDiscount(),
new GiftDiscount(),
new WeekdayDiscount(),
new WeekendDiscount(),
new SpecialDayDiscount(),
];
return discountEvents;
}
}
export default App;
이러한 의존성 주입 방식은 각 컴포넌트의 의존성을 외부에서 관리할 수 있게 하며, 시스템의 변경이 필요할 때 유연하게 대응할 수 있게 한다.
특히 이벤트 같이 변동성이 큰 요소들에 대해 더 빠르고 효과적으로 대응할 수 있으며, 코드의 가독성과 관리가 쉬워지는 장점이 있다.
느낀점
2023 우테코 프리코스를 마무리하면서, 코드 작성이 마치 그림을 그리는 것과 같다는 생각이 들었는데 요구사항을 해석하고 미래의 변화에 대응하는 방식은 확장성을 고려하는 데 중요하지만, 지나치면 불필요한 코드 생성과 가독성 저하를 초래할 수 있기 때문이다. 이러한 균형 잡힌 접근 방식은 도전적이면서도 매우 재미있었던거같다.
아무래도 프론트엔드에서는 객체지향적으로 코드를 분리하고 설계하는 기회가 많지 않았기때문에 더 의미있는 공부 경험이 될 수 있었고, 객체지향의 개념과 장점을 이해하고 필요에 따라 적절하게 적용하는 것의 중요성을 크게 느꼈다.
이러한 깨달음은 앞으로의 개발 경험에 큰 영향을 미치며, 필요에 따라 적절한 기술을 선택하고 적용하는 능력 있는 개발자로 성장하는 데 중요한 기반이 될 수 있었으면 바란다👏👏
지금 4주차 코드에도, 복잡하게 얽힌 의존관계의 여러 클래스와 개선할점은 많이 남아있지만..ㅜㅜ 개인적으로 1~4주차 코드를 다시 보면서 개선해보고 싶다
댓글남기기