├── README.md
├── lotto
├── Readme.md
├── step1
│ ├── hope.md
│ ├── img
│ │ ├── schema82.png
│ │ ├── schema84.png
│ │ └── schema92.png
│ ├── lokba.md
│ ├── lotto-step1-integrated-review.md
│ ├── marco.md
│ └── movie.md
└── step2
│ ├── hope.md
│ ├── kkojae.md
│ ├── lokba.md
│ ├── lotto-step2-intergrated-review.md
│ ├── marco.md
│ └── movie.md
├── racing-car
└── marco.md
├── vending-machine
└── README.md
└── youtube
├── README.md
├── step1
├── hope.md
├── img
│ └── class_diagram.png
├── intergrated-review.md
├── kkojae.md
├── lokba.md
├── marco.md
└── movie.md
└── step2
├── hope.md
├── intergrated-review.md
├── kkojae.md
├── lokba.md
├── marco.md
└── movie.md
/README.md:
--------------------------------------------------------------------------------
1 | # 코드리뷰스터디(코리스)
2 |
3 | ## 0. 코드리뷰스터디 결과물
4 |
5 | |통합정리본|참여자|
6 | |:---:|:---:|
7 | |[자동차게임 미션](https://github.com/woowacourse-study/2022-code-review-study/blob/main/racing-car/marco.md)|마르코|
8 | |[로또 미션 step1](https://github.com/woowacourse-study/2022-code-review-study/blob/main/lotto/step1/lotto-step1-integrated-review.md)|마르코, 호프, 록바, 무비|
9 | |[로또 미션 step2](https://github.com/woowacourse-study/2022-code-review-study/blob/main/lotto/step2/lotto-step2-intergrated-review.md)|마르코, 호프, 록바, 무비, 꼬재|
10 | |[유튜브 미션 step1](https://github.com/woowacourse-study/2022-code-review-study/blob/main/youtube/step1/intergrated-review.md)|마르코, 호프, 록바, 무비, 꼬재|
11 | |[유튜브 미션 step2](https://github.com/woowacourse-study/2022-code-review-study/blob/main/youtube/step2/intergrated-review.md)|마르코, 호프, 록바, 무비, 꼬재|
12 |
13 | ## 1. 스터디 소개
14 |
15 | - 우아한테크코스 4기 미션 코드리뷰를 '리뷰'하는 스터디입니다.
16 | - 주요 활동은 다른 크루들이 받은 좋은 코드리뷰들을 각자 분량을 나누어 정리한 후 함께 공유하는 것입니다.
17 | - 목표는 코드리뷰를 통하여 `메타인지` 및 `클린 코드` 역량 향상입니다.
18 | - 스터디원
19 |
20 | |Nickname|Github|
21 | |:---:|:---:|
22 | |마르코|[@wonsss](https://github.com/wonsss)|
23 | |록바|[@lokba](https://github.com/lokba)|
24 | |무비|[@byhhh2](https://github.com/byhhh2)|
25 | |호프|[@moonheekim0118](https://github.com/moonheekim0118)|
26 | |꼬재|[@kkojae91](https://github.com/kkojae91)|
27 |
28 | ## 2. 일정
29 |
30 | - 스터디 운영 기한
31 | - 2.28. ~ level1 로또 미션까지 한시적으로 진행합니다.
32 | - 이후 참여의사 확인 및 피드백을 거쳐서 모임을 재시작합니다.
33 | - 스터디 모임(화상회의) 주기
34 | - step1 리뷰 스터디는 step1 종료한 날(일요일)의 다음 주 금요일 저녁 9시
35 | - step2 리뷰 스터디는 step2 종료한 날(월요일) 주 금요일 저녁 9시
36 | - 정리한 내용 PR 기한
37 | - 모임 전날인 목요일 밤 12시까지
38 | - 토론 주제 상정 기한
39 | - 모임 당일 오후 5시까지
40 |
41 | ## 3. 주요활동
42 |
43 | - 1인당 담당하는 코드리뷰PR은 8개이며, 자신과 페어를 포함하여 페어프로그래밍한 총 네 팀의 코드를 묶어서 맡습니다.
44 |
45 | ### 3-1. 피드백 정리 후 PR
46 |
47 | - 스터디원들은 각자 맡은 코드리뷰PR 피드백들을 정리하고 작성합니다.
48 | - 크게 `아키텍처 분석`과 `피드백 정리`를 수행합니다. `아키텍처 분석`은 담당한 4개(=페어 4팀)의 소프트웨어의 아키텍처 중 느낌이 오는 코드를 선택하여 간단히 분석하고 설명합니다(부담 갖지 말고 그냥 느끼는 대로 편하게 설명해주시면 될 것 같아요). `피드백 정리`는 리뷰어들이 8개의 PR에 달은 코멘트 중 `Aha` 모멘트가 있던 피드백을 메모하는 작업입니다.
49 | - 양식은 아래와 같습니다.
50 |
51 | ```plain
52 | # Level번호 미션명 Step번호(담당 PR 번호들) - 작성자닉네임
53 | - 분석 담당 코드
54 | - PR 번호(PR 링크)
55 | - PR 번호(PR 링크)
56 | ...
57 |
58 | ## 아키텍처 분석(desc: 담당한 4개의 소프트웨어의 아키텍처를 간단히 분석하고 설명합니다)
59 | - [해당 PR 번호] 내용
60 | - 상세 내용
61 |
62 | ## 피드백 정리
63 | ### 대분류(ex: 아키텍처, 함수/클래스, 컨벤션, DOM, 테스트 등)
64 | - [해당 PR 번호] 내용
65 | - 상세 내용
66 | ```
67 |
68 | - 정리한 내용을 본 저장소에 `Pull Requests`합니다(기한: 모임 전날 목요일 밤12시).
69 | - 다음과 같이 폴더링하여 올려주세요. 예시: `lotto/step1/{닉네임}}.md`
70 | - PR 제목 : `[lotto-step1-lokba] 리뷰 분석 제출합니다.`
71 | - 아키텍처 분석 및 피드백 정리 도중 의문이 든 주제가 있을 경우, `Discussion`에 글을 올립니다(기한: 모임 당일 오후 5시).
72 | - `Discussion`에 올라온 토론 주제나 질문을 보고 자신의 의견을 미리 생각해보고 토론을 준비합니다.
73 |
74 | ### 3-2. 발표 및 토론
75 |
76 | - `매주 금요일 오후 9시`에 `모임(줌)`을 가지어, 발표 및 토론을 2시간 이내로 진행합니다.
77 | - 발표
78 | - 각자 정리한 내용을 10분씩 발표합니다.
79 | - 질의응답
80 | - 1명씩 발표를 마친 후, 질의응답 시간을 5분씩 갖습니다.
81 | - 토론
82 | - 발표 및 질의응답을 모두 마친 후, `Discussion`에 게시된 주제에 대해 토론을 진행합니다.
83 | - 토론 후, 각 `Discussion`의 주제에 대해 새로 알게 된 내용이나 정리된 본인의 의견을 `answer`로 남깁니다.
84 |
85 | ## 4. 운영 관련
86 |
87 | - 매주 스터디원들이 번갈아 가며 모임 운영 역할을 맡습니다.
88 | - 운영자 역할
89 | - 스터디원들에게 담당 코드리뷰PR 분배
90 | - 스터디원들이 올린 PR 검토 후 merge
91 | - PR 마감 기한 지연 시, 스터디원에게 일정 준수 요청
92 | - 발표 및 토론 시 모임 진행
93 | - 각자 올린 정리 파일을 하나로 편집하여 통합(주말까지)
94 | - 스터디원은 총 4명으로 구성합니다(추후 검토).
95 | - 매주 각자 분석할 코드리뷰PR의 분량은 8개입니다.
96 | - 나와 나의 페어(2개), 페어 3팀(6개)
97 | - 4*8=32... 37-32=5... 기본 배정되지 않은 5개의 PR은 스터디원들이 자율적으로 담당합니다.
98 |
--------------------------------------------------------------------------------
/lotto/Readme.md:
--------------------------------------------------------------------------------
1 | # 로또 미션
2 |
3 | ## 페어 확인
4 | - [x] 동키콩, 온스타
5 | - [x] 위니, 자스민
6 | - [x] 앨버, 결
7 | - [x] 안, 코이
8 | - [x] 나인, 비녀
9 | - [x] 하리, 록바, 코카콜라
10 | - [x] 후이, 콤피
11 | - [x] 블링, 태태
12 | - [x] 아놀드, 빅터
13 | - [x] 돔하디, 민초
14 | - [x] 시지프, 호프
15 | - [x] 우연, 소피아
16 | - [x] 병민, 티거
17 | - [x] 유세지, 준찌
18 | - [x] 꼬재, 도리
19 | - [x] 샐리, 해리
20 | - [x] 마르코, 무비
21 | - [x] 밧드, 우디
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 |
--------------------------------------------------------------------------------
/lotto/step1/hope.md:
--------------------------------------------------------------------------------
1 | # Level1 로또 Step1(담당 PR 번호들) - 호프
2 | - 분석 담당 코드
3 | - [#83-시지프 ](https://github.com/woowacourse/javascript-lotto/pull/83)
4 | - [#89-호프](https://github.com/woowacourse/javascript-lotto/pull/89)
5 | - [#116-티거](https://github.com/woowacourse/javascript-lotto/pull/116)
6 | - [#119-병민](https://github.com/woowacourse/javascript-lotto/pull/119)
7 | - [#112-도리](https://github.com/woowacourse/javascript-lotto/pull/112)
8 | - [#113-꼬재](https://github.com/woowacourse/javascript-lotto/pull/113)
9 | - [#107-소피아](https://github.com/woowacourse/javascript-lotto/pull/107)
10 | - [#110-우연](https://github.com/woowacourse/javascript-lotto/pull/110)
11 | …
12 |
13 | ## 아키텍처 분석
14 |
15 | ### #83 시지프
16 | 
17 | - Model, Controller, View 로 분리
18 | #### Model
19 | - 발급받은 로또를 관리한다.
20 | - 발급될 로또 수량을 매개변수로 받으면 로또 넘버를 생성해 준 후, 저장한다.
21 |
22 | #### Controller
23 | - View로 넘겨줄 이벤트 핸들러를 정의한다.
24 | - 이 때, 이벤트 핸들러는 여러가지의 View와 Model을 조작하는 역할을 한다.
25 | - View에 이벤트 핸들러를 넘겨줄 때는 아래와 같이 해당하는 View의 `bindSubmitCash` 메서드의 인자로서 핸들러를 넘겨준다.
26 |
27 | ```javascript
28 | #bindEventHandlers() {
29 | this.#purchaseFormView.bindSubmitCash(cash => this.#handleSubmitCash(cash));
30 | }
31 | ```
32 |
33 | #### View
34 | - 해당 영역(section)의 이벤트 핸들러를 붙여준다.
35 | - 이벤트가 View에서만 관리될 수도 있고, 필요시 위의 Controller로부터 추가 핸들러를 받아온다.
36 |
37 | - 전형적인 MVC패턴으로 Model 과 View를 Controller가 연결해준다.
38 | - Model과 View 간의 영역 침범(?)이 없다. 둘은 서로의 존재를 아예 모른다.
39 |
40 |
41 |
42 | ### #89 호프
43 | - 위의 83번 페어코드와 다른 점만 설명합니다.
44 | ```javascript
45 | export default class View {
46 | constructor(props = {}) {
47 | this.props = props;
48 | this._init();
49 | }
50 | _init() {
51 | this._configureDOM();
52 | this._bindEvents();
53 | }
54 | _bindEvents() {}
55 | _configureDOM() {}
56 | }
57 | ```
58 | - View 추상클래스를 정의해서, 반복되는 부분을 제거한다.
59 | - configureDOM : 해당 View에서 필요한 Dom Element를 선택하는 영역
60 | - bindEvents : 해당 View 에서 필요한 이벤트 리스너를 붙이는 영역
61 | - 객체가 생성되면 필요한 Dom Elemet를 선택하고, 이벤트 리스너를 붙인다.
62 | ```javascript
63 | #purchaseFormView = new PurchaseFormView({
64 | submitCashHandler: cash => this.#submitCashHanlder(cash),
65 | });
66 | ```
67 | - #83 의 코드에서는 Controller 의 이벤트 핸들러를 View 로 전달할 때, 해당 View 인스턴스의 bindEvents 메서드를 직접 실행한 반면, 해당 코드에서는 View의 생성자 매개변수(props)로 필요한 이벤트 핸들러를 전달
68 | - 로또 번호를 생성하는 함수는 모델 -> util로 분리
69 | - 왜 ? 로또 번호 생성은 엄밀히 말하면 Model의 역할이 아니라고 판단했기 때문입니다. Model은 `데이터를 생성하고 저장`하는 영역이라고 생각했기 때문에 로또 번호를 만드는 로직을 Model 클래스가 알 필요가 없다고 생각합니다. 추후에 로또 번호 생성 로직이 달라질 수도 있구요.
70 |
71 |
72 |
73 |
74 | ### #116 티거 / #119 병민
75 | 
76 | - flux 패턴을 사용하여 Component를 Store의 구독자로 등록. Store 내부의 상태가 변경될 시, Store의 구독자 Component를 모두 리렌더링하여 새로운 상태를 반영하도록 구현
77 |
78 | #### Store
79 | ```javascript
80 | class Store {
81 | #subscribers = [];
82 | #state = {};
83 |
84 | constructor(initialState) {
85 | this.#state = initialState;
86 | }
87 |
88 | subscribe(component) {
89 | this.#subscribers.push(component);
90 | }
91 |
92 | dispatch(action) {
93 | const newState = reducer(this.getState(), action);
94 | this.#setState(newState);
95 | this.#subscribers.forEach((subscriber) => {
96 | subscriber.notify();
97 | });
98 | }
99 | getState() {
100 | return this.#state;
101 | }
102 |
103 | #setState(newState) {
104 | this.#state = newState;
105 | }
106 | }
107 | ```
108 |
109 | - 생성자에서 초기 상태값을 받아온다.
110 | - subscribe : 해당 메서드를 통해 원하는 컴포넌트를 구독자로 등록한다.
111 | - dispatch : 컴포넌트에서 상태를 변경 할 때 실행하는 메서드. action을 매개변수로 받고, 등록된 action에 따라 새로운 상태값이 reducer로부터 반환되고 상태가 업데이트 된다. 업데이트 된 후, 구독자 Component들에게 notify로 상태값이 변경되었음을 알린다.
112 |
113 |
114 | #### Reducer
115 | ```javascript
116 | export default function reducer(state, { type, payload }) {
117 | const newState = { ...state };
118 | switch (type) {
119 | case ACTION.PURCHASE_LOTTO: {
120 | newState.money = payload;
121 | newState.lottoList = generateLottoList(payload);
122 | return newState;
123 | }
124 | case ACTION.TOGGLE_LOTTO_LIST:
125 | newState.lottoListVisibility = payload;
126 | return newState;
127 | case ACTION.SET_WINNING_NUMBERS:
128 | newState.winningNumbers = payload;
129 | return newState;
130 | default:
131 | return state;
132 | }
133 | }
134 | ```
135 |
136 | - 위의 Store에서 사용된 Reducer 함수
137 | - 액션 타입에 따라서 다른 상태값을 받아온다.
138 | - payload 는 액션과 함께 받아온 새로운 데이터를 의미한다.
139 | - 알려지지 않은 액션타입이 들어온 경우 기존의 상태를 반환한다.
140 |
141 | #### Component
142 | ```javascript
143 | class Component extends HTMLElement {
144 | connectedCallback() {
145 | this.render();
146 | this.subscribe();
147 | this.setEvent();
148 | }
149 | render() {
150 | this.innerHTML = this.template();
151 | }
152 | template() {
153 | return '';
154 | }
155 |
156 | subscribe() {
157 | window.store.subscribe(this);
158 | }
159 | setEvent() {}
160 |
161 | addEvent(eventType, selector, callback) {
162 | const children = [...this.querySelectorAll(selector)];
163 | const isTarget = (target) => children.includes(target) || target.closest(selector);
164 | this.addEventListener(eventType, (event) => {
165 | if (!isTarget(event.target)) {
166 | return false;
167 | }
168 | return callback(event);
169 | });
170 | }
171 | notify() {
172 | this.render();
173 | }
174 | hide() {
175 | this.setAttribute('hidden', true);
176 | }
177 | show() {
178 | this.removeAttribute('hidden');
179 | }
180 | }
181 | ```
182 | - Custom Element 를 사용하였다.
183 | - Component 추상 클래스가 애초에 HTMLElement 를 상속받는다.즉, Component 자체가 하나의 Element를 의미한다.
184 | - connectedCallback : Custom Element의 라이프사이클 중 하나로, Custom Element가 등록될 때 제일 처음 실행되는 메서드
185 | - render : 해당 Element의 innerHTML을 수정한다.
186 | - subscribe: 해당 Component 생성과 함께 실행되어, Component를 위에서 생성된 Store의 구독자로 등록한다.
187 | - setEvent: 아래의 addEvent 메서드를 호출한다.
188 | - addEvent: 이벤트 타입, 셀렉터, 이벤트 핸들러 콜백 함수를 받아온다. 이벤트 리스너를 맨 위의 root (this) 에 등록하고, eventTarget 을 검사하여 콜백을 실행한다.
189 | - notify : Store에서 상태 변경시 실행되어 리렌더링 한다.
190 |
191 | - 개인적인 의견으로 아쉬운 점은 모든 컴포넌트가 하나의 스토어를 바라보고 있다는 점입니다. 물론 로또앱 자체가 작아서 리렌더링이 심하게 일어나지는 않지만, 로또 상태와 관련이 없는 불필요한 부분에도 리렌더링이 지속적으로 일어날 가능성이 높아서 아쉽습니다. 상태에 상관 없는 부분, 예를들면 Form 같은 부분은 Container 로 또 다른 추상 클래스를 정의해서 상속 받아서 불필요한 notify를 줄이는건 어떨까요 ?
192 | - 혹은 해당 Store의 상태를 직접적으로 참조하는 Component만 리렌더링되게 하는 방법이 있지 않을까요?
193 |
194 |
195 | ### #112- 도리 / 113- 꼬재
196 | 
197 | - LottoApp 영역에서 이벤트 바인딩, 핸들링, UI 조작 등을 모두 담당한다.
198 | - LottoApp 에서 LottoList 라는 데이터를 관리하는데, LottoList 는 Lotto 객체의 배열로 이루어져 있다.
199 | - index.html에 다른 Element가 등록되어 있지 않기 때문에 이벤트를 app 엘리먼트 (최상위) 에 바인딩 시켜준 점이 인상깊다.
200 |
201 | #### bindEventListener 함수
202 | ```javascript
203 | export const bindEventListener = ({ appElement, type, selector, callback }) => {
204 | const children = [...getElements(selector)];
205 | const isTarget = (target) =>
206 | children.includes(target) || target.closest(selector);
207 | appElement.addEventListener(type, (e) => {
208 | if (!isTarget(e.target)) return;
209 | e.preventDefault();
210 | callback(e);
211 | });
212 | };
213 | ```
214 |
215 | - 위의 함수가 클로져를 잘 사용했다고 생각해서 발췌
216 | - app에 이벤트를 바인딩 시킨다.
217 | - 받은 selector를 사용해서 app 내부에 있는 selector에 해당하는 모든 element를 가져온다.
218 | - isTarget 함수를 클로져로 저장하여, 아래 이벤트 리스너 콜백에서 현재 event.target이 등록된 이벤트 타겟인지 검사한다.
219 | - 아쉬운 점이 있다면 ! e.preventDefault(); 호출을 type 이 submit에만 해줘도 되지 않을까? 하는 점
220 |
221 |
222 | ### #107- 소피아 / 110 - 우연
223 | 
224 | - LottoMachine, LottoManager, LottoMachineView, Lotto 클래스로 나뉘어진다.
225 | - MVC 패턴으로 보이지만, 클래스 이름을 도메인과 관련하여 지으니까 훨씬 유의미 하다는 것을 깨달았다.
226 |
227 | #### LottoMachine
228 | - lottoManager (Model) 과 lottoMachineView (View) 인스턴스를 생성한다.
229 | - 이벤트를 바인딩하고 처리한다.
230 |
231 | #### LottoMachineView
232 | - UI 조작을 담당한다.
233 |
234 | #### LottoManager
235 | - 모델 역할을 하여 로또를 생성한다.
236 | - lottos [] 배열은 Lotto 객체 배열이다.
237 |
238 | #### Lotto
239 | - 로또 번호를 생성한다.
240 |
241 |
242 | ## 피드백 정리
243 | ### 컨벤션
244 | - [#116] switch 문
245 | - break를 하기보다 return을 바로 하는게 더 안전하지 않을까요? 또한 break를 하고 마지막에 return하기보다 액션이 없는경우 default로 return 하는것이 더 명료할것 같아요.
246 |
247 | ### 클래스
248 | - [#83] 은닉화
249 | - 외부에서 사용하지 않는 변수나 메서드는 무조건 private 으로 처리해주면 좋은가?
250 | - 외부에서 알 필요가 없는 메서드라면 private로 처리해주는게 맞다고 생각합니다. 예를들어 전기포트가 있다고 할 때 저희는 전원을 키고 끄는법에 대해서만 알면되고 내부에서 어떻게 전기포트에서 열이 올라서 물을 끓이는지 세부적인 내용에 대해 사용자가 알 필요가 없는 부분과 같은 맥락이라 생각합니다.중요한 데이터를 외부에서 건드릴 수 없도록 + 외부에서 사용하지 않는다의 맥락이 모두 위의 예시에 빗대어 생각해보시면 어느정도 윤곽이 잡히시지 않을 까 생각해봅니다.
251 | - [#112] 생성자의 파라미터로 넘기기 vs setter 사용하기
252 | - 생성자의 파라미터로 넘기는 건, 인스턴스를 생성할 때부터 값을 셋팅해주는거고, 기본적으로 OOP 에서는 외부에서 직접적으로 객체의 데이터에 접근하는 것을 막기 위해 setter 를 사용합니다. (같이 봐야하는 개념으로 private 이 있습니다) 만약 외부에서 변경이 가능하다면 우리가 정의한 객체의 무결성이 깨질 수 있기 때문입니다. 예를 들어, lottoList 가 private 하지 않는다면 아래와 같이 객체를 깨뜨릴 수 있겠죠. 그렇기 때문에 setter 를 사용합니다..!
253 |
254 | ### 함수
255 | - [#83] 함수 분리 기준
256 | - 함수를 분리하지 않더라도 충분히 이해할 수 있는 범주의 역할인데다가 과도한 분리는 오히려 유지보수를 어렵게 만든다는 생각을 하고있기에 코멘트를 드렸습니다. 혹 재사용을 하더라도 분리할 필요성이 떨어지는 함수라고 생각이 들어요. 우선은 제가 함수를 분리하는 관점은 기능 확장 될 여지가 있거나 내부의 구현이 복잡하다면 추상화를 통해 미리 해서 분리한다. 기능이 확장 될 여지가 없거나 코드가 짧거나(코드를 처음 봤을때 맥락이 애해되는 범주) 재사용성이 떨어진다면 분리하지 않는 방향성들을 고려해보고 판단하는게 맞는 방향이라고 생각됩니다
257 |
258 |
259 | ### 도메인 관련 함수
260 | - [#83] 로또 번호 추첨 함수
261 | - 더 나아가서 Set을 통해서 중복되었을 때 한번 더 루프를 도는 방식이 아니라 무조건 NUMBERS_COUNT번만에 끝낼 수 있는 방식으로 구현할 수 있을까요? 실제로 로또 번호를 추첨하는 방식처럼요!
262 | - 1~45까지의 배열에서 최대값이 배열의 크기인 랜덤한 수를 얻어 해당 index를 기반으로 spliced를 통해 로또 번호를 뽑는 방식
263 |
264 | - [#112] validation 함수 네이밍 및 역할 관련
265 | ```javascript
266 | const isDivisibleBy = (payment, price) => {
267 | if (payment % price !== 0) {
268 | throw new Error(ERROR_MESSAGE.MONEY_OUT_OF_STANDARD)
269 |
270 | }
271 | return parseInt(payment / price);
272 | };
273 | ```
274 | - 저는 파라미터가 두번째 파라미터로 나누어 떨어져? 라고 물어봤는데 응/아니야 라고 대답하는 게 아니라 Error! / 계산된 값 줄게 으로 대답하는 게 조금은 이상한데 어떻게 생각하시는 지 궁금합니다…! isDivisibleBy 보단 차라리, divideBy 가 어떨까 싶어요..! (이걸로 나눠줘! -> 불가능 그러니까 에러 발생시킬게! / 그래 여기)
275 |
276 | ### 네이밍
277 | - [#83] - set
278 | - setOnSubmitCash 네이밍도 나쁘진 않은 것 같은데 아무래도 네이밍 앞에 set이 붙으면 이벤트 바인딩보다는 내부에 있는 상태를 설정하는 느낌이 드네요
279 |
280 | ### DOM
281 | - [#113] SVG vs PNG
282 | - 이미지를 svg로 사용하게 되면 좋은 이유:
283 | 1. 모든 크기의 브라우저를 원할하게 지원한다. (무한한 확장이 가능)
284 | 2. svg일부를 스타일링 할 수 있다.
285 | 3. dom을 사용하여 svg를 실시간으로 수정할 수 있다.
286 | 4. 용량이 적어 출력이 빠르다.
287 | 이미지를 png로 사용해야하는 경우:
288 | * 백그라운드 이미지와 같이 이미지를 벡터화하기 어려울 경우
289 | 디자이너가 넘겨주는 파일을 사용하는게 맞겠지만,
290 | 백그라운드 이미지와 같이 이미지를 벡터화하기 어렵다면 png를 사용하고 그렇지 않은 경우에는 svg를 사용하는 편이 좋은 거군요?!
291 |
292 | ### CSS
293 | - [#116] CSS 선택자
294 | - 순서에 의존하는 선택자는 지양
295 | - lotto-list > . Lotto-list-container . 와 같은 선택자는 지양
296 | - [#97] 0px
297 | - margin,padding 같은 속성에서 0으로 속성을 줘야할 때 px을 주지 않는 것이 성능상 유리하다.
298 |
299 | ### 웹 접근성
300 | - [#110] hidden
301 | - 스크린리더기에서 읽혀야 하지만, 화면에 보이지 않아야 할 때는 어떻게 해야할까?
302 | - CSS의 visibility hidden 속성을 주도록 한다.
303 | - 따라서 , section 내부 타이틀을 설정하고 , hidden 속성을 주도록 한다.
304 |
305 |
306 |
--------------------------------------------------------------------------------
/lotto/step1/img/schema82.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woowacourse-study/2022-code-review-study/35261e14ace715a90b33c211f885ac269a6ee996/lotto/step1/img/schema82.png
--------------------------------------------------------------------------------
/lotto/step1/img/schema84.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woowacourse-study/2022-code-review-study/35261e14ace715a90b33c211f885ac269a6ee996/lotto/step1/img/schema84.png
--------------------------------------------------------------------------------
/lotto/step1/img/schema92.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woowacourse-study/2022-code-review-study/35261e14ace715a90b33c211f885ac269a6ee996/lotto/step1/img/schema92.png
--------------------------------------------------------------------------------
/lotto/step1/lokba.md:
--------------------------------------------------------------------------------
1 | # Level1 Lotto Step1(담당 PR 번호들) - 록바
2 |
3 | - 분석 담당 코드
4 | - 후이 [#93](https://github.com/woowacourse/javascript-lotto/pull/93)
5 | - 콤피 [#98](https://github.com/woowacourse/javascript-lotto/pull/98)
6 | - 돔하디 [#108](https://github.com/woowacourse/javascript-lotto/pull/108)
7 | - 민초 [#104](https://github.com/woowacourse/javascript-lotto/pull/104)
8 | - 블링 [#88](https://github.com/woowacourse/javascript-lotto/pull/88)
9 | - 태태 [#91](https://github.com/woowacourse/javascript-lotto/pull/91)
10 | - 하리 [#115](https://github.com/woowacourse/javascript-lotto/pull/115)
11 | - 록바 [#103](https://github.com/woowacourse/javascript-lotto/pull/103)
12 | - 코카콜라 [#111](https://github.com/woowacourse/javascript-lotto/pull/111)
13 | - 안 [#114](https://github.com/woowacourse/javascript-lotto/pull/114)
14 |
15 |
16 |
17 | ## 아키텍처 분석(desc: 담당한 4개의 소프트웨어의 아키텍처를 간단히 분석하고 설명합니다)
18 |
19 | ### [#93, #98] 후이, 콤피
20 |
21 | 파일 구조도는 MVC 패턴을 따르고 있다.
22 |
23 | - Model - Lotto, LottosModel
24 | - View - MoneyInputView, LottoListView
25 | - Controller - LottoController
26 |
27 | #### Controller
28 |
29 | - Controller는 Model, View에게 지시하는 역할을 수행하고 있다.
30 |
31 | - Model, View 인스턴스를 생성 후, 멤버 변수로 가지고 있다.
32 |
33 | - 인스턴스 생성을 constructor()에서 하지 않고 private 필드에서 진행하였다.
34 |
35 | ```javascript
36 | class LottoController{
37 | #MoneyInputView = new MoneyInputView($(`.${SELECTOR.CLASS.LOTTO_MONEY_SECTION}`));
38 | #LottoListView = new LottoListView($(`.${SELECTOR.CLASS.LOTTO_LIST_SECTION}`));
39 | #LottosModel = new LottosModel();
40 |
41 | constructor() {...}
42 | }
43 | ```
44 |
45 | - constructor()에서 이벤트를 바인딩하는 함수를 호출하고 있다.
46 | - dom을 View에서 관리하다 보니, Controller에서 수행햐는 함수를 콜백함수로 전달하고 있다.
47 | - Controller는 메인 로직인, 입력받는 금액을 바탕으로 Model에 접근하여 상태 데이터를 간접적으로 변경하고 View에 화면의 변화를 주도록 지시하고 있다.
48 |
49 |
50 |
51 | #### View
52 |
53 | - View는 MoneyInputView와 LottoListView로 구성되어 있다.
54 |
55 | - Controller에서 인스턴스 생성시 전달한 dom 객체를 멤버변수로 할당하고 있다.
56 |
57 | ```javascript
58 | //View
59 | #container;
60 |
61 | constructor($element){
62 | this.#container = $element;
63 | }
64 | ```
65 |
66 | - 특정 영역을 show/hide 및 toggle은 classList를 활용해 변화를 담당했다.
67 | - View는 Model, Controller에 직접 접근하는 경우가 없다.
68 |
69 |
70 |
71 | #### Model
72 |
73 | - Model은 Lotto, LottosModel로 구성되어 있다.
74 | - Lotto 모델은 pickNumbers(선택된 로또번호들)을 멤버 변수로 가지고 있다. Set() 자료형을 사용했다.
75 | ```javascript
76 | #pickNumbers = new Set();
77 | ```
78 | - LottosModel은 lottos(로또 객체들)을 멤버 변수로 가지고 있다. 배열 [] 자료형을 사용했다.
79 | - Controller에서 Model의 상태 데이터에 접근하기 위해서 getter를 사용하고 있다.
80 |
81 |
82 |
83 | #### 인상 깊은 점
84 |
85 | - Array.from 활용 부분
86 |
87 | ```javascript
88 | const lottoCount = inputMoney / LOTTO_SETTING.PRICE;
89 |
90 | Array.from({ length: lottoCount }, () => {
91 | const lottoInstance = new Lotto();
92 | lottoInstance.generateNumberList();
93 |
94 | this.#lottos.push(lottoInstance);
95 | });
96 | ```
97 |
98 | Array.from의 length 속성을 이용하고, Array.from의 스펙을 잘 이용한 것 같다.
99 |
100 |
101 |
102 | - scss 사용
103 |
104 | ```css
105 | $border-color: #b4b4b4;
106 | $border-radius: 5px;
107 | $primary-color: #00bcd4;
108 | ```
109 |
110 | 자주 쓰이는 속성값을 변수에 담아서 활용하였다.
111 |
112 |
113 |
114 | ### [#103, #111, #115] 하리, 록바, 코카콜라
115 |
116 | 파일 구조도는 MVC 패턴을 따르고 있다.
117 |
118 | - Model - LottoNumbers, PurchasedLottos
119 | - View - View, PurchasedMoneyView, PurchasedLottoView, WinningNumberView
120 | - Controller - LottoMachineController
121 |
122 | #### Controller
123 |
124 | - Controller는 Model, View에게 지시하는 역할을 수행하고 있다.
125 | - constructor에서 Model, View 인스턴스를 생성 후, 멤버 변수로 가지고 있다.
126 | - **dom을 View에서 관리하고 있기 때문에, Controller에서 실행되어야 하는 함수를 콜백함수로 전달하는 것이 아닌, 부모 클래스인 View의 handlers(멤버 변수)에 저장한다.**
127 |
128 |
129 |
130 | #### View
131 |
132 | - View는 PurchasedMoneyView, PurchasedLottoView, WinningNumberView, View로 구성되어 있다.
133 |
134 | - View(부모 클래스)는 View에서 실행해야하는 Controller의 handler 함수들을 관리하고 있다.
135 |
136 | ```javascript
137 | export default class View {
138 | handlers = new Map();
139 |
140 | addHandler({ name, handler }) {
141 | const data = this.handlers.get(name) || [];
142 | data.push(handler);
143 | this.handlers.set(name, data);
144 | }
145 | }
146 | ```
147 |
148 | - View(부모 클래스)를 상속하고 있는 PurchasedMoneyView 클래스는 View(부모 클래스)에 handler 멤버 변수에 접근하여, Controller에 있는 함수를 호출한다.
149 |
150 | ```javascript
151 | //PurchasedMoneyView
152 | submitHandler(e) {
153 | e.preventDefault();
154 |
155 | const purchaseMoney = convertToNumber(this.input.value);
156 |
157 | try {
158 | validatePurchaseMoney(purchaseMoney);
159 | this.handlers
160 | .get('purchasedMoneySubmit')
161 | .forEach(func => func(purchaseMoney));
162 | } catch (error) {
163 | this.resetInputValue();
164 | alert(error);
165 | }
166 | }
167 | ```
168 |
169 | - View는 Model, Controller에 직접 접근하는 경우가 없다.
170 |
171 |
172 |
173 | #### Model
174 |
175 | - Model은 LottoNumbers, PurchasedLottos로 구성되어 있다.
176 | - 각 Model의 상태 데이터는 #을 이용해 private field에 선언하였다.
177 | - 외부에서 Model 상태 데이터를 접근하는 방식을 getter/setter 방식을 사용하였다.
178 |
179 |
180 |
181 | #### 인상 깊은 점
182 |
183 | - 로또 번호를 6개를 생성하는 방식
184 |
185 | - 1 ~ 45 배열을 생성한다.
186 | - 해당 배열을 shuffle하여 뒤섞는다.
187 | - 배열에서 6개의 번호를 추첨한다.
188 |
189 | ```javascript
190 | export const pickLottoNumber = (count) => {
191 | //로또번호 1 ~ 45를 소유하고 있는 배열
192 | const lottoNumbers = [...Array(RULES.MAX_LOTTO_NUMBER)].map(
193 | (_, index) => index + 1
194 | );
195 | shuffleArray(lottoNumbers);
196 |
197 | const numbers = [];
198 |
199 | for (let i = 0; i < count; i++) {
200 | numbers.push(lottoNumbers.pop());
201 | }
202 |
203 | return numbers;
204 | };
205 |
206 | //immutable 적용하기ㅠㅠ
207 | function shuffleArray(inputArray) {
208 | inputArray.sort(() => Math.random() - 0.5);
209 | }
210 | ```
211 |
212 | - [리뷰어 답변] 효율/성능을 따져야할 만큼 심각한 성능저하가 따르는 경우가 아닌 한은 읽기좋은 코드 / 간단한 코드를 작성하는 다양한 방법을 생각해 보시면 좋겠다.
213 |
214 |
215 |
216 | ### [#104, #108] 민초, 돔하디
217 |
218 | 파일 구조도는 MVC 패턴을 따르고 있다.
219 |
220 | - Model - Lotto
221 | - View - lottoView
222 | - Controller - LottoController
223 |
224 | #### Controller
225 |
226 | - Controller는 LottoController로 구성되어 있다.
227 | - constructor에서 lottos(로또번호를 포함하고 있는 멤버들로 구성된)멤버변수를 가지며, 이벤트리스너를 등록하고 있다.
228 |
229 |
230 |
231 | #### View
232 |
233 | - View는 lottoView로 구성되어 있다.
234 | - 클래스가 아닌, 함수표현식으로 구성되어 있다.
235 | ```javascript
236 | export const showResult = (lottos) => {
237 | deactivateForm();
238 | showResultElements();
239 | showNumberOfLottos(lottos.length);
240 | showLottoImage(lottos);
241 | };
242 | ```
243 | - showResult함수에서, view에 관련된 함수를 호출하는 형태로 구성되어있다.
244 |
245 | #### Model
246 |
247 | - Model은 Lotto로 구성되어 있다.
248 | - constructor에는 lottoNumbers(로또 번호들) 멤버변수와 로또 번호를 생성하는 함수를 호출하고 있다.
249 |
250 |
251 |
252 | ### [#114] 안
253 |
254 | 파일 구조도는 MVC 패턴을 따르고 있다.
255 |
256 | - Model - Lotto
257 | - View - LottoGameView
258 | - Controller - LottoGame
259 |
260 | #### Controller
261 |
262 | - Controller는 LottoGame로 구성되어 있다.
263 | - constructor에는 Model과 View 인스턴스를 생성한 후, 멤버변수로 할당하고 있다.
264 | - constructor에서 이벤트리스너를 등록하고 있다.
265 |
266 |
267 |
268 | #### View
269 |
270 | - View는 LottoGameView로 구성되어 있다.
271 | - View에서 토글 이벤트리스너를 등록하고 있다.
272 |
273 |
274 |
275 | #### Model
276 |
277 | - Model은 Lotto로 구성되어 있다.
278 | - constructor에는 lottos(로또번호를 포함하고 있는 멤버들로 구성된)멤버변수를 가지고 있다.
279 |
280 |
281 |
282 | #### 인상깊은 부분
283 |
284 | ```javascript
285 | resetLottoList() {
286 | this.lottoNumberList.replaceChildren("");
287 | }
288 | ```
289 |
290 | - MDN에 따르면, Element.replaceChildren()메서드는 기존 자식을 지정된 새 자식 집합으로 바꿉니다. 이것들은 DOMString 또는 Node객체가 될 수 있다.
291 |
292 |
293 |
294 | ### [#88, #91] 블링, 태태
295 |
296 | 파일 구조도는 MVC 패턴을 따르고 있다.
297 |
298 | - Model - lotto, lottoManager
299 | - View - lottoView
300 | - Controller - lottoController
301 |
302 | #### Controller
303 |
304 | - Controller는 lottoController로 구성되어 있다.
305 | - constructor에는 Model과 View 인스턴스를 생성한 후, 멤버변수로 할당하고 있다.
306 | - View에서 dom을 관리하고 있기 때문에, 이벤트 동작과정에서 Controller에 필요한 부분을 callback으로 View에게 전달하고 있다.
307 | - 외부에 드러낼 필요없는 부분은 #을 이용해 은닉화를 진행하였다.
308 |
309 |
310 |
311 | #### View
312 |
313 | - View는 lottoView로 구성되어 있다.
314 | - constructor에는 화면에 연관되어 있는 element를 멤버변수로 할당하고 있다.
315 | - 로또 element는 템플릿을 사용하지 않고, tag와 className을 이용해 element를 생성하고 text를 넣는 식으로 구성하였다.
316 | - 외부로 드러낼 필요없는 부분은 #을 이용해 은닉화를 진행하였다.
317 |
318 |
319 |
320 | #### Model
321 |
322 | - Model은 lotto, lottoManager로 구성되어 있다.
323 | - Lotto클래스는 lottoNumberSet(로또 번호들)을 멤버변수로 가지고 있으며, Set 자료형을 사용하고 있다. Set 객체를 생성할 때, 로또 번호를 랜덤하게 가져오는 함수를 호출하여 리턴 값을 매개변수로 전달하고 있다.
324 | ```javascript
325 | class Lotto {
326 | constructor() {
327 | this.lottoNumberSet = new Set(
328 | generateRandomNumberInRange({
329 | min: LOTTO_NUMBER_RANGE.MIN,
330 | max: LOTTO_NUMBER_RANGE.MAX,
331 | count: LOTTO_NUMBER_COUNT,
332 | })
333 | );
334 | }
335 | }
336 | ```
337 | - LottoManager클래스는 lottoPrice(로또 가격), purchaseAmount(구매한 로또 갯수), lottos(로또 번호들로 이루어진 배열)을 멤버변수로 초기화하고 있다.
338 |
339 |
340 |
341 | #### 인상깊은 부분
342 |
343 | ```javascript
344 | #toggleLottoNumbersShow = ({ target: { checked: isVisible } }) => {
345 | const { classList: lottoGridClassList } = this.lottoGrid;
346 | if (isVisible) {
347 | lottoGridClassList.add(CLASSNAMES.ONE_COLUMN_GRID_CLASSNAME);
348 | lottoGridClassList.remove(CLASSNAMES.HIDE_NUMBERS_CLASSNAME);
349 | return;
350 | }
351 | lottoGridClassList.remove(CLASSNAMES.ONE_COLUMN_GRID_CLASSNAME);
352 | lottoGridClassList.add(CLASSNAMES.HIDE_NUMBERS_CLASSNAME);
353 | };
354 | ```
355 |
356 | - 구조 분해(destructuring)가 인상깊다.
357 |
358 |
359 |
360 | ```javascript
361 | #generateLottoElement(lotto) {
362 | const lottoElement = createElementWithClassName('div', CLASSNAMES.LOTTO_CLASSNAME);
363 |
364 | const lottoImage = createElementWithClassName('p', CLASSNAMES.LOTTO_IMAGE_CLASSNAME);
365 | lottoImage.textContent = LOTTO_IMAGE;
366 |
367 | const lottoNumbers = createElementWithClassName('p', CLASSNAMES.LOTTO_NUMBERS_CLASSNAME);
368 | lottoNumbers.textContent = Array.from(lotto.lottoNumberSet).join(', ');
369 |
370 | lottoElement.append(lottoImage, lottoNumbers);
371 | return lottoElement;
372 | }
373 | ```
374 |
375 | - innerHTML, insertAdjacentHTML을 피하고, append을 쓴게 인상깊다.
376 |
377 |
378 |
379 | ---
380 |
381 | ## 피드백 정리
382 |
383 | ### 대분류(ex: 아키텍처, 함수/클래스, 컨벤션, DOM, 테스트 등)
384 |
385 |
386 |
387 | #### 아키텍처
388 |
389 | - [#103] Controller와 View의 의존성을 끊는 방법
390 | - Controller와 View의 의존성을 끊기 위해서는 다양한 방법이 있는데, 웹에서는 흔히 customEvent를 활용합니다. View에서 이벤트핸들러를 등록하고, 그 핸들러 내부에서 위의 customEvent를 dispatch합니다. Controller에서는 여러가지 customEvent를 수신해서 동작을 수행합니다.
391 | - window객체에 메서드(dispatch)를 만들어두는 방법으로 우회하는 방법도 있습니다. View에서 등록한 핸들러 내부에서 적정한 타입/값을 담은 dispatch를 호출합니다. dispatch함수는 인자를 받아 controller들에게 알립니다(publish). 해당 타입을 subscribe하고 있는 controller가 이를 처리합니다.
392 |
393 |
394 |
395 | - [#114] 이벤트등록은 View에서 해야할까? Controller에서 해야할까?
396 | - 셀렉터라는게 view에서 element를 선택하자는 거니까 view에 의존적인게 당연하죠. 당연히 view에 의존적일 수밖에 없는 내용이라면 view에 몰아넣는게 합리적이지 않나요? 모든 크루들이 view와 controller의 의존관계를 끊어내려 하기보다 오히려 controller가 적극적으로 view에 개입하고 있는데, 그 이유는 제가 추측하기로는 크루분들 사이에 eventListener를 controller에서 등록해야 한다는 강박 같은게 자리잡은게 아닌가 싶거든요.. 왜 그런 컨센서스가 생긴건지 모르겠는데, **이벤트등록은 뷰에서 하는게 맞습니다.** 이벤트 발생시 컨트롤러에서의 동작은 별도의 함수를 마련, 호출함으로써 해결하면 되구요.
397 |
398 |
399 |
400 | #### 함수/클래스
401 |
402 | - [#93] 토글 조작으로 인한 화면 show/hide 관련 함수
403 |
404 | ```javascript
405 | showLottoList() {
406 | this.#container.classList.add('show');
407 | }
408 |
409 | hideLottoList() {
410 | this.#container.classList.remove('show');
411 | }
412 | ```
413 |
414 | - toggle 이라는거는 show -> hide, hide -> show 를 번갈아가면서 하겠다는 것을 내포하고 있는데 show 가 왜 붙어야하는지 모르겠습니다!
415 | - showLottoList, hideLottoList -> toggleLottoList로 정정
416 |
417 |
418 |
419 | - [#103] 불필요한 초기화 메서드를 만들지 마라.
420 | - initDom(), initEventListener() 등 불필요한 메서드를 만들지 말고, constructor에서 초기화를 진행해라.
421 |
422 |
423 |
424 | - [#103] getter/setter의 장점
425 | - IDE 환경에 따라 다르겠지만, `.`만 찍어도 접근자들을 확인할 수 있다.
426 | - 내부 로직을 은닉화하고, 외부에는 필요한 접근자들만 제공한다.
427 | - Object.assign, destructuring assignment등에 의해서도 노출되지 않는다.
428 |
429 |
430 |
431 | - [#103] 이벤트 리스너 내부 익명함수 -> 핸들러 분리
432 | - 이벤트 리스너는 원래 `등록`과 `해제`를 함께 고려해야 한다.
433 | - 해제해주지 않으면, 메모리가 낭비될 뿐만 아니라 경우에 따라서 중복 등록되어 이벤트에 대해 함수가 여러번 호출될 수 있다.
434 | - 익명함수를 사용할 경우 해제를 할 수 없기때문에 핸들러로 분리해야 한다.
435 |
436 |
437 |
438 | - [#115] #을 이용한 은닉화의 범위
439 | - 객체지향 관점에서 외부로 노출될 메소드들만 public으로 지정해놓는게 좋고,
440 | 노출되지 않을 메소드는 private으로 만드는게 좋습니다!
441 |
442 |
443 |
444 | - [#91] static 메서드의 기준은 무엇일까?
445 | - class는 기본적으로 인스턴스를 생성하는 틀입니다. 인스턴스 내부 '메소드'의 정의는 인스턴스 '자신'의 내부 동작을 수행하는 함수입니다. '자신'에 관여하는 내용이 없다면 이는 '메서드'가 아닌 '함수'로 불러야 하겠죠. 그런 맥락에서 보면, **this를 사용하지 않는 메서드는 인스턴스에서 접근해야할 필요가 없습니다. 인스턴스에 제공될 필요가 없으니 굳이 상속시키지 않게끔 static으로 분리하라는 것이죠.** 그런데 전부 static 메서드만 있는 클래스의 경우, 이를 바탕으로 인스턴스로 만들어봐야 그 인스턴스는 빈껍데기만 있는 객체에 불과합니다. 실제로 인스턴스로 만드는 시도조차 안하실거구요. 그렇다면 해당 클래스는 클래스로서가 아니라 일반 객체로서의 의미밖에 없는 것이니, static으로만 구성된 클래스는 차라리 처음부터 객체로 만드는 편이 좋다고 생각합니다.
446 |
447 |
448 |
449 | ### 컨벤션
450 |
451 | - [#93] 혹시 class 네이밍 컨벤션 관련 검색 키워드 추천 해주실 만한게 있을까요?
452 |
453 | - (정답은 아니고, 제안) label과 input을 감싸는 애들은 **wrapper**로, 여러 컴포넌트를 감싸는 애들은 **container**로 한다.
454 |
455 |
456 |
457 | - [#93] 함수 매개변수명 관련 코멘트
458 | ```javascript
459 | handleMoneyInputSubmit({ moneyInput });
460 | ```
461 | - moneyInput -> inputElment가 넘어오는 걸로 착각을 일으킨다.
462 | - moneyInput -> money 수정
463 |
464 |
465 |
466 | - [#93] 배열을 할당받고 있는 변수명 네이밍 관련 코멘트
467 | - 배열은 보통 ~~s 또는 ~~List로 네이밍을 짓는다.
468 |
469 |
470 |
471 | - [#93] 변수명 관련 코멘트
472 | - 현재, 두 가지 View의 멤버변수는 `this.#container = $element`로 되어있다.
473 | - [리뷰어님 답변] : container 같이 element 에 대한 변수명을 지을 때 number, string 과 같은 값을 의미하는 변수와 구분을 두면 가독성이 좋아질 것 같습니다. 예를 들어 변수명 뒤에 ~~~Element 를 붙인다던지, element 변수명 앞에는 $ 를 붙인다던지 등등 규칙을 정하자!
474 |
475 |
476 |
477 | - [#98] [리뷰어 질문] : selector 도 모두 상수화하신 게 인상깊네요. 개인적으로는 대문자 상수를 많이 쓰게 되면 가독성이 오히려 떨어지는 느낌인데, 쓰면서 어떠셨나요~?
478 | - 콤피의 답변
479 | - 가독성은 좋지는 않다.
480 | - 추후 선택자를 수정사항이 생기면, 한 번에 변경이 가능하다.
481 | - vscode의 자동 표기법이 편하다.
482 |
483 |
484 |
485 | - [#98] 아래 코드에서는 Object.freeze를 할 필요없을까?
486 | ```javascript
487 | export const LOTTO_SETTING = {
488 | MIN_RANDOM_NUMBER: 1,
489 | MAX_RANDOM_NUMBER: 45,
490 | PRICE: 1000,
491 | LOTTO_NUMBER_LENGTH: 6,
492 | };
493 | ```
494 | - 객체는 변경가능해지기도 해서 상수는 보통 문자열로 선언해 사용한다.
495 | - 변수명 자체를 대문자로 쓰는 편이라, Object.freeze를 하지 않는다.
496 |
497 |
498 |
499 | - [#103] Object.freeze가 인상적인데, "굳이"라는 생각이 든다
500 |
501 | - 이미 '상수'임을 모두가 아는 상태에서, 객체에 접근하는 경우가 발생한다면 건든 사람이 책임을 져야하는 일이다. 오버스펙이라 생각한다.
502 |
503 |
504 |
505 | - [#111] css 파일명 원칙
506 | - 보통 css 파일명으로 카멜케이스를 사용하지 않는다.
507 |
508 |
509 |
510 | - [#104] && 연산자 관련 코멘트
511 | ```javascript
512 | const isValidMoneyInput = (money) =>
513 | isThousandMultiple(money) &&
514 | isOverThouand(money) &&
515 | isUnderMillion(money) &&
516 | isValidMoneyRange(money);
517 | ```
518 | - && 연산자를 사용할 때, 값에 대한 평가로만 사용하고 함수 호출을 지양하는 편이다.
519 |
520 |
521 |
522 | - [#104] 네이밍에 전치사?
523 | - 클린코드 원칙에는 전치사 사용을 자제하는것을 추천한다.
524 |
525 |
526 |
527 | - [#91] 상수는 굳이 멤버로 할당할 필요가 없다.
528 | ```javascript
529 | this.lottoPrice = LOTTO_PRICE;
530 | ```
531 |
532 |
533 |
534 | -[#88] model에 직접 접근은 지양해라.
535 |
536 | ```javascript
537 | //직접 접근
538 | this.view.renderLottos(this.lottoManager.lottos);
539 |
540 | //간접 접근
541 | const lottos = this.lottoManager.getLottos();
542 | this.view.renderLottos(lottos);
543 | ```
544 |
545 |
546 |
547 | ### 패키지
548 |
549 | - [#115] ^(캐럿)을 붙인 이유는 무엇인가요?
550 | ```javascript
551 | "devDependencies": {
552 | "@babel/core": "^7.16.12",
553 | "@babel/preset-env": "^7.16.11",
554 | "babel-jest": "^27.4.6",
555 | "babel-loader": "^8.2.3",
556 | "clean-webpack-plugin": "^4.0.0",
557 | "html-webpack-plugin": "^5.5.0",
558 | "css-loader": "^6.6.0",
559 | "eslint": "^8.9.0",
560 | ```
561 | - npm 측에서의 버전 간의 호환성을 위하여 도입.
562 |
563 |
564 |
565 | ### CSS
566 |
567 | - [#115] 공통적으로 쓰이는 색상은 global css variable로 만들자.
568 | - 추후 개발을 진행하거나 했을때 일관적으로 색상 팔레트가 관리되고 있지 않다면, 찾아다니는게 대규모 프로젝트에선 생각외로 힘들다.
569 |
570 |
571 |
572 | ### 새로운 접근
573 |
574 | - [#114] 꼭, constructor에서 element에 접근해서 멤버변수로 해야할까?
575 | - HTML상에 위 element의 생명주기가 JS의 생명주기보다 먼저 시작해서 끝까지 살아있다는 전제하에서는, 위 값을 상수 또는 변수로 만들어두면 안될까요?
576 | ```javascript
577 | const ELEMENTS = {
578 | ...
579 | SWITCH_INPUT: $(".switch-input")
580 | }
581 | ```
582 |
--------------------------------------------------------------------------------
/lotto/step1/marco.md:
--------------------------------------------------------------------------------
1 | # Level1 Lotto Step1(담당 PR 번호들) - 마르코
2 |
3 | - 분석 담당 코드
4 | - 준찌 [#81](https://github.com/woowacourse/javascript-lotto/pull/81)
5 | - 유세지 [#82](https://github.com/woowacourse/javascript-lotto/pull/82)
6 | - 동키콩 [#92](https://github.com/woowacourse/javascript-lotto/pull/92)
7 | - 온스타 [#85](https://github.com/woowacourse/javascript-lotto/pull/85)
8 | - 위니 [#96](https://github.com/woowacourse/javascript-lotto/pull/96)
9 | - 자스민 [#86](https://github.com/woowacourse/javascript-lotto/pull/86)
10 | - 아놀드 [#84](https://github.com/woowacourse/javascript-lotto/pull/84)
11 | - 빅터 [#106](https://github.com/woowacourse/javascript-lotto/pull/106)
12 | - 코이 [#105](https://github.com/woowacourse/javascript-lotto/pull/105)
13 |
14 | ## 1. 코드 분석
15 |
16 | ### [#81] 준찌, [#82] 유세지
17 |
18 | 
19 |
20 | #### Controller
21 |
22 | - 진입점인 index.js가 바로 부르는 RacingGameManager() 클래스에서 .start() 메서드로 시작한다. 해당 클래스는 컨트롤러 역할인 듯하다.
23 |
24 | - start 메서드는 `다른 클래스 인스턴스 생성`, `dom요소 가져오기`, `event 바인딩`을 init한다.
25 |
26 | - (1) model과 view 클래스 인스턴스 생성하여 멤버변수로 할당
27 | - (2) DOM 요소 가져와서 멤버변수 할당
28 |
29 | - constructor가 없다. 메서드 내부에서 멤버 변수를 만들고 있다.
30 |
31 | - (3) 선택된 DOM 요소에 이벤트 바인딩
32 | - [네이밍] 핸들러 네이밍을 할 때, 접두사로 on을 붙인다. `onSubmitChargeInputForm`
33 |
34 | - controller는 이벤트를 통해 발생한 데이터를 가공하고, view의 렌더링 메서드를 호출하며 그 데이터를 매개변수로 전달한다.
35 |
36 | #### View
37 |
38 | - constructor 내부에 dom 요소를 멤버변수 할당하지 않고, 프라이빗 메서드 내부에서 dom 요소를 멤버변수로 할당하여 사용하고 있다.
39 | - render하는 메서드들은 매개변수로 렌더링할 데이터를 (컨트롤러로부터) 받고 있다.
40 | - view가 model에 의존하고 있지 않다.
41 |
42 | #### model
43 |
44 | - model에는 `LottoGameModel`과 `LottoModel` 2개가 있다.
45 | - 그런데 두 모델의 네이밍이 와닿지 않는다는 리뷰어의 의견이 있었다. ["Lotto 클래스는 어떤 정보를 담는 녀석인지, LottoGame은 또 어떤 녀석인지, 이름만 봤을 때 대략적으로라도 유추할 수 있는 네이밍을 고민해보시면 좋겠어요."]
46 | - controller에서 의존하고 있는 model인 LottoGameModel은 lottoList(2차원 배열)만 멤버변수로 관리하고 있다.
47 | - LottoGameModel이 의존하고 있는 LottoModel은 lottoNumbers(1차원 배열, 길이 6)만 멤버변수로 관리하고 있다.
48 |
49 | #### 클로저 활용
50 |
51 | - 6개 랜덤 숫자가 담긴 lotto 배열 만들 때, closure 사용
52 |
53 | ```jsx
54 | function shuffle(array) {
55 | const copy = [...array]; // 값을 받을 때 깊은복사
56 |
57 | copy.sort(() => Math.random() - 0.5); // random은 0과 1사이 랜덤값이고 sort메서드의 compareFunction은 음수냐 양수냐를 기준으로 두 값씩 비교하며 정렬하므로, 값들이 한 쪽으로 편중되지 않도록 random 값에 0.5를 빼서 음수 또는 양수가 랜덤하게 나오도록 compareFunction을 만들어서 사용한다.
58 |
59 | return [...copy]; // 값을 반환할 때도 깊은복사하여 반환
60 | }
61 |
62 | function lottoNumberClosure() {
63 | const pickNumbers = shuffle([...new Array(45)].map((_, idx) => idx + 1)); // [1,2,3,4,...,45] -> 섞어서
64 |
65 | return () => pickNumbers.pop(); // 배열에서 마지막 값을 하나씩 반환하고 배열에서 제거
66 | }
67 |
68 | class Lotto {
69 | constructor() {
70 | this.lottoNumbers = this.createLottoNumbers();
71 | }
72 | createLottoNumbers() {
73 | const getLottoNumber = lottoNumberClosure();
74 | // getLottoNumber()는 1부터 45 사이의 값 중 하나를 반환하고, 한 번 반환된 값은 다시 반환되지 않는다.
75 | return [...new Array(6)].map(() => getLottoNumber());
76 | }
77 | }
78 | const lotto = new Lotto();
79 | console.log(lotto); // Lotto { lottoNumbers: [ 7, 14, 45, 33, 18, 36 ] }
80 | ```
81 |
82 | ### [#92] 동키콩, [#85] 온스타
83 |
84 | 
85 |
86 | - MVC에서 벗어나, 뷰로직과 비즈니스 로직을 분리하는데에만 집중하였다고 한다. `뷰에서 입력을 받고` `비즈니스 로직을 통해 데이터를 가공하여` `다시 뷰에서 렌더링`하는 방식으로 구현을 하였다.
87 |
88 | - 리뷰어는 이에 대해 `View 영역이 너무 방대해진 것` 같다는 의견을 주기도 했다. 이를 해결하기 위해, `도메인과 UI를 연결할 수 있는 Controller와 같은 영역의 추가`를 제안했다.
89 | - "꼭 특정 패턴을 맹목적으로 따르고 일부러 구조를 맞출 필요는 없지만 현재 고민하시는 내용에 MVC 패턴이 아이디어를 제공할 수 있을 것 같습니다"
90 |
91 | - DOM이 로드되면, 처음 발생하는 인스턴스는 LottoView 클래스의 인스턴스이다. LottoView의 constructor에서 인스턴스 발생시 , LottoMachine(모델, 멤버변수 #inputMoney / #lottos / #strategy) 클래스의 인스턴스를 만들고, 이벤트를 바인딩하였다.
92 | - LottoMachine 클래스에 있는 generateLottos() 메서드에서 Lotto(모델) 클래스의 인스턴스를 찍어내여 구입한 개수의 Lotto들의 2차원 배열을 만든다. 즉, LottoMachine 클래스는 받은 돈으로 로또들을 Lotto 클래스(로또 한 장을 책임)를 통해 만드는 책임을 지녔다.
93 | ```jsx
94 | generateLottos() {
95 | return Array(this.lottoQuantity)
96 | .fill()
97 | .map(() => new Lotto(this.#strategy).generate());
98 | }
99 | ```
100 |
101 | ### [#96] 위니 , [#86] 자스민
102 |
103 | - MVC 패턴을 사용했다.
104 | - Controller는 model과 view(resultView, inputView)에 의존한다.
105 | - 또한, Controller가 DOM을 조작하고 있고 이벤트 바인딩까지 해주고 있다. 그리고 이벤트의 콜백함수로서 비즈니스 로직 메서드들을 두고 있다.
106 | - > DOM 조작은 View의 역할로 보고 나눠주는 것이 어떨까? DOM 조작과 이벤트 바인딩은 View에서 하도록 분리할 수도 있을 것 같다.
107 | - view는 담당하는 화면 섹션에 따라 resultView, inputView로 분리해준 것 같다. 관리하는 DOM 멤버 변수는 렌더링을 받을 요소들로만 지정하고 있고, 메서드들 또한 어디에 어떤 HTMLString을 주입할 지 시키는 대로 하는 역할을 수행하고 있다. View의 역할 분리가 잘 된 것 같다.
108 |
109 | ### [#84] 아놀드, [#106] 빅터
110 |
111 | 
112 |
113 | - 클래스를 사용하지 않고 함수와 객체만을 이용하여 프로그래밍을 하였다. 또한, MVC처럼 설계하지 않고 도메인 중심으로 짠 것 같다.
114 | - 코드 진입하면, app.js에서 DOM 요소(금액 입력 폼, 체크박스) 를 선택하여 각각의 이벤트를 바인딩한다.
115 | - 이벤트의 콜백함수는 eventListener.js에 모아두었고, 이를 import해서 사용한다. eventListener.js에 있는 메서드는 비즈니스 로직을 담당하는 것 같다. 메서드 내부에서 validator 모듈에게 유효성 검사 요청 메시지 전달, lottoGame 모듈에게 데이터 생성 요청 메시지 전달, view 모듈에게 렌더링 요청 메시지 전달 등을 하고 있다.
116 | - lottoGame 모듈은 model 역할에 가까운 것 같고, view 모듈은 이름대로 view역할을 하고 있다. 기존 MVC에서 controller와 비슷한 역할(model과 view를 잇는)은 eventListener.js 모듈이 일부 담당하는 것 같다. 왜냐하면 eventListener.js는 lottoGame.js와 view.js에 의존하고 있으며, view에게 렌더링 요청 시 lottoGame에서 가져온 데이터를 인자로 넘겨주기 때문이다.
117 | - lottoGame 모듈은 모델의 역할을 하지만, 클래스 없이 전역변수와 함수로만 이루어져 있다. 그러면 싱글톤 객체와 같은 역할을 하게 되는 것 같다.
118 | - 이 때 만약 "여러 라운드의 로또 생성 내역을 히스토리로 보여줘야 할 니즈"가 생길 경우, 어떻게 해결할 수 있을까?
119 | - 반면, 클래스를 사용했다면, 라운드마다 모델에 새 인스턴스를 생성하고, 히스토리를 보여줄 때 여러 인스턴스의 데이터를 보여주는 방식으로 가능할 것 같다.
120 |
121 | ### [#105] 코이
122 |
123 | - MVC를 사용하였다.
124 | - controller에서 dom 요소를 멤버변수로 두고 이벤트를 바인딩하고 있다. 그리고 콜백함수도 함께 있다.
125 | - element를 disabled해주는 유틸 함수를 따로 빼서 사용하고 있는데, 좋은 것 같다.
126 |
127 | ```jsx
128 | export const setDisabled = element => element.setAttribute("disabled", true);
129 | ```
130 |
131 | - view에서 렌더링을 지울 때, replaceChildren()메서드를 활용하고 있다.
132 |
133 | ```jsx
134 |
135 | resetLottoList() {
136 | this.lottoNumberList.replaceChildren("");
137 | }
138 | ```
139 |
140 | ## 2. 피드백 정리
141 |
142 | ### 아키텍처
143 |
144 | - [#84] 누가 어떤 역할을 하고 있는지는 명확히 보이도록 설계하는 것이 좋다.
145 |
146 | - [#86] 외부에서 알 필요가 없는 메서드라면 private로 처리해주는게 맞다.
147 |
148 | - 예를들어 전기포트가 있다고 할 때 사용자는 전원을 키고 끄는법에 대해서만 알면 되고 내부에서 어떻게 전기포트에서 열이 올라서 물을 끓이는지 세부적인 내용에 대해 알 필요가 없는 부분과 같은 맥락이다. "중요한 데이터를 외부에서 건드릴 수 없도록 하고, 외부에서 사용하지 않는다"의 맥락을 모두 이 전기포트의 예시에 빗대어 생각해볼 수 있다.
149 |
150 | - [#86] MVC에서 event 바인딩하는 함수를 controller보다 view에 두는 것이 일반적이긴 하나, 상황에 따라 자신이 좋다고 판단하고 작성한 코드가 오히려 정답일 수 있다.
151 |
152 | - 일단, 위임을 할지 말지에 따라서도 다르게 가져갈 것 같고 앱의 규모에 따라 다르게 가져갈 여지도 있다. 참고로 view에서 event 바인딩하는 함수를 구현한다면 유지보수 측면에서 원하는 요소를 찾기 쉬울 것 같다는 장점이 있다.
153 |
154 | - [#81] Model의 getter 함수에서 깊은 복사를 하여 반환해주는 것은 상황에 따라 전략을 취하면 된다.
155 | - 깊은 복사를 하여 불변성을 통해 얻는 이점이 해당 코드에서 있다면 할 수도 있다.
156 | - 그리고 중첩된 객체의 경우 전체 depth를 모두 깊은복사해야 하는 경우도 있겠지만, 1depth까지만 깊은 복사를 해도 충분한 경우, 또는 얕은 복사로도 아무 문제 없는 경우도 많다.
157 | - 그리고 성능최적화가 고도로된 V8 엔진에서는 깊은 복사를 해도 발생하는 성능 손실은 생각보다 미미하므로, 필요시 깊은복사를 해도 되겠다.
158 |
159 | ```jsx
160 | // 예시
161 | // 유틸
162 | export const deepCopy = object => JSON.parse(JSON.stringify(object));
163 | // 이것은 아마 1depth까지만 깊은 복사가 되는 코드?
164 |
165 | // 모델
166 | class LottoGameModel {
167 | constructor() {
168 | this.lottoList = null;
169 | }
170 |
171 | getLottoList() {
172 | /** getter로 가져간 lottoList를 변경하여도 lottoList의 멤버에겐 영향이 없다. */
173 | return deepCopy(this.lottoList);
174 | }
175 | }
176 | ```
177 |
178 | - [#92] 캡슐화가 잘됐다면 객체가 외부에 반드시 제공해야 되는 기능만 드러내고 나머지는 은닉화해야 한다.
179 | - 어떤 객체의 getter, setter가 특별한 이유없이 public 이라면 캡슐화를 온전히 적용했다고 보기 어렵다. 캡슐화가 잘됐다면 객체가 외부에 반드시 제공해야 되는 기능만 드러내고 나머지는 은닉화 해야한다.
180 | - [#92] 이벤트 바인딩 시점을 조절해야 하는 것이 아니라면, 이벤트 바인딩을 묶은 메서드를 생성자 안으로 옮기는 것이 낫다.
181 | - [#92] 여러 곳에서 범용적으로 쓰일 법한 함수는 밖으로 옮기는 것이 낫다.
182 | - [#92] 뷰로직과 비즈니스 로직을 분리한다. 비즈니스 로직(=도메인 로직)이란, 실제 서비스의 핵심 기능을 구현하기 위해 `데이터를 어떻게 가공하고 관리할 것인가` 전반에 걸친 로직을 포함한다. https://en.wikipedia.org/wiki/Business_logic
183 | - 비즈니스 로직은 데이터가 생성, 저장 및 변경될 수 있는 방법을 결정하는 것이다. 반면, 그 외의 로직은 데이터베이스를 관리하거나 사용자 인터페이스를 표시하거나 일반적으로 프로그램의 다양한 부분을 연결하는 하위 수준 세부 정보와 관련되어 있다.
184 |
185 | ### 함수/클래스
186 |
187 | - [#105] 특정 길이만큼 배열을 만들어서 값을 넣어주려면, `for문과 push 메서드` 대신에 `특정 길이의 빈 배열에 map 메서드`를 쓸 수도 있다.
188 | - 특정 length의 빈 배열을 만들고 싶을 때, `[...Array(6)]` 또는 `Array.from({ length: 6 })` 를 활용할 수 있다.
189 |
190 | ```jsx
191 | for (let i = 0; i < count; i += 1) {
192 | const lotto = new Lotto();
193 | lotto.generateRandomNumber();
194 | this.lottos.push(lotto);
195 | }
196 | ```
197 |
198 | ```jsx
199 | this.lottos = Array.from({ length: count }).map(() => {
200 | const lotto = new Lotto();
201 | lotto.generateRandomNumber();
202 | return lotto;
203 | });
204 | ```
205 |
206 | - [#86] 중복된 값을 저장하지 않는 Set 자료구조 및 해당 메서드(add)등도 활용할 수 있다.
207 |
208 | ```jsx
209 | const lottoNumberSet = new Set();
210 | while (lottoNumberSet.size < LOTTO_NUMBERS.LOTTO_LENGTH) {
211 | lottoNumberSet.add(
212 | getRandomNumber(
213 | LOTTO_NUMBERS.MIN_LOTTO_NUMBER,
214 | LOTTO_NUMBERS.MAX_LOTTO_NUMBER
215 | )
216 | );
217 | }
218 | return [...lottoNumberSet];
219 | ```
220 |
221 | - [#81] 고차함수를 추천하는 이유 중 하나는 가독성이 좋아지는 점이 가장 크다.
222 | - map 메서드로 리팩토링 사례
223 |
224 | ```jsx
225 | // before
226 | const lottoList = [];
227 | for (
228 | let lottoCount = 0;
229 | lottoCount < availableLottoAmount;
230 | lottoCount = lottoCount + 1
231 | ) {
232 | const lottoNumbers = this.createLottoNumbers();
233 | const lotto = Lotto.create(lottoNumbers);
234 | lottoList.push(lotto);
235 | }
236 | ```
237 |
238 | ```jsx
239 | // after
240 | const lottoList = [...new Array(availableLottoAmount)].map(() => new Lotto());
241 | ```
242 |
243 | - [#81] Prototype에 커스텀 메소드를 추가해서 사용하는 것 주의
244 | - Prototype에 커스텀 메소드를 추가해서 사용하는 것은, 내장 객체가 가지는 전역 범위때문에 생길 수 있는 변경 가능성 때문에 별로 추천하는 방법이 아니다. 또한, 유틸성 함수의 역할로 쓰려는 목적이라면 명시적으로 유틸 함수를 정의하고 모듈화하는 것이 더 좋다.
245 | - built-in 객체의 prototype에 이미 있던 함수를 새로운 내용으로 변경하는 것은 당연히 위험한 일이니 지양하는게 맞는데,
246 | 개인적으로는 built-in 객체의 prototype에 새로운 내용을 추가하는 것 역시 권장하고 싶지 않습니다. 다른 모듈(파일)들을 작업하는 협업자 입장에서는 deepCopy라는 메서드가 있는지 여부를 알기 어려워서 안쓰게 될 확률이 높아요.
247 | 명시적으로 import해서 쓸 수 있도록 static한 함수로 구현하는 것이 더 좋다고 생각해요. 그렇게 하면 prototype 체이닝상의 존재여부와 무관하게 다른 객체들도 쓸 수 있을테니까요. (deepCopy는 배열 뿐 아니라 객체에서도 쓰기 좋은 함수입니다).
248 | 굳이 this 및 prototype 체이닝을 이용한 '메서드'로 써보고 싶다! 라고 하면, Array를 extends하는 새로운 subClass를 만들어서 그 안에 method를 정의하는 편이 좋습니다. 이러면 앞서 설명드린 문제점들은 모두 해소됩니다. 다만 이 경우엔 리터럴 선언 방식은 유효하지 않다는 점이 아쉽긴 한데, prototype 메서드를 활용하고자 하는 니즈와 이 아쉬움 사이에서 잘 저울질해서 선택하면 되겠죠.
249 |
250 | ```jsx
251 | Array.prototype.deepCopy = function () {
252 | return JSON.parse(JSON.stringify(this));
253 | };
254 | ```
255 |
256 | - [#82] `new.target` 사용
257 |
258 | - [new.target](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/new.target)
259 | - new.target 속성(property)은 함수 또는 생성자가 new 연산자를 사용하여 호출됐는지를 감지할 수 있습니다. new 연산자로 인스턴스화된 생성자 및 함수에서, new.target은 생성자 또는 함수 참조를 반환합니다. 일반 함수 호출에서는, new.target은 undefined입니다.
260 |
261 | - [#82] 드모르간 법칙을 활용하여 every 메서드에서 some 메서드로 변경하면 더 효율적인 로직일 수 있다.
262 | - 조건을 '모두 충족시켜야만' true가 반환되는 every보다는, 이를 뒤집어서 '일부 조건이라도 충족시' true가 반환되는 some을 쓰면 좀더 효율적인 검색이 이뤄질 수 있다. (드모르간 법칙 이용)
263 |
264 | ```jsx
265 | function isValidNumber(lottoNumbers) {
266 | return lottoNumbers.every(
267 | number =>
268 | Number.isInteger(number) &&
269 | number >= NUMBER.LOTTO_MIN_NUMBER &&
270 | number <= NUMBER.LOTTO_MAX_NUMBER
271 | );
272 | }
273 | ```
274 |
275 | ### 컨벤션/네이밍
276 |
277 | - [#96] `this.model` 같은 추상적이고 모호한 이름보다 사용하는 곳에서 바로 알 수 있도록 구체적인 이름으로 바꾸는 것이 좋다.
278 |
279 | - [#86] 안 쓰는 인자는 언더바(\_)를 사용하면 남들이 이해하기 쉽다.
280 |
281 | ```jsx
282 | (e, idx) => idx + 1, // before
283 | (_, idx) => idx + 1, // after
284 | ```
285 |
286 | - [#81] utils, constants에서 파일을 많이 생성하여, 코드들을 분리하는 것도 명확하다는 장점이 있다.
287 | - [#82] 하나의 로또 티켓에 들어가는 번호가 6개라는 의미로, 6을 값으로 갖는 `LOTTO_NUMBER_LENGTH`라는 변수명에서 `length`는 '자릿수'를 의미하는 것으로 오해할 여지가 있으므로, `length`를 'amount'로 변경해보는 것도 고민해보자.
288 |
289 | ### 테스트
290 |
291 | - [#105] 단위테스트는 어떤 기능 하나에 대한 테스트라고 볼 수 있으므로, 각 파일별로 테스트 파일을 작성하여, 그 파일별로 테스트 코드를 응집하는 게 나을 수 있다.
292 |
293 | - [#92] private 메서드는 세부 기능이기 때문에 특별한 경우가 아니라면 테스트 작성하지 않는 편이다. 링크 참고 http://shoulditestprivatemethods.com
294 |
295 | - [#92] 랜덤테스트의 가장 큰 문제는 검증을 실패하거나 성공했을 때 왜 성공했는지? 왜 실패했는지? 동일한 상황 재현이 어렵다는 것이다.
296 |
297 | - 랜덤테스트를 진행한다면 여러 테스트 케이스의 히스토리를 캡쳐하고 그 기록을 관리하는 체계가 필요하다.
298 |
299 | - [#92] 랜덤테스트를 안하려면 숫자를 테스트할 때 수동으로 입력해줘야 한다.
300 |
301 | - mock, spyOn 등의 키워드로 검색하여 방법 강구
302 | - > Mock 객체란 모듈의 겉모양이 실제 모듈과 비슷하게 보이도록 만든 가짜 객체이다. 모듈이 필요로 하는 의존성이 테스트 케이스 작성을 어렵게 만드는데, 이 의존성을 단절하기 위해 Mock 객체가 사용된다. https://eminentstar.github.io/2017/07/24/about-mock-test.html
303 | - > mocking에는 스파이(spy)라는 개념이 있습니다. 현실이나 영화 속에서 스파이라는 직업은 “몰래” 정보를 캐내야 합니다. 테스트를 작성할 때도 이처럼, 어떤 객체에 속한 함수의 구현을 가짜로 대체하지 않고, 해당 함수의 호출 여부와 어떻게 호출되었는지만을 알아내야 할 때가 있습니다. 이럴 때, Jest에서 제공하는 jest.spyOn(object, methodName) 함수를 이용하면 됩니다. https://www.daleseo.com/jest-fn-spy-on/
304 |
305 | ```jsx
306 | const calculator = {
307 | add: (a, b) => a + b,
308 | };
309 |
310 | const spyFn = jest.spyOn(calculator, "add");
311 |
312 | const result = calculator.add(2, 3);
313 |
314 | expect(spyFn).toBeCalledTimes(1);
315 | expect(spyFn).toBeCalledWith(2, 3);
316 | expect(result).toBe(5);
317 | ```
318 |
319 | - [#81] 유틸 함수에 대한 테스트에 가까울 경우, 서비스 로직 테스트와 분리하는 것이 더 분명한 의미를 전달할 수 있다.
320 |
321 | ### 함수/메서드 분리
322 |
323 | - [#84] 코드를 한 줄로 짜는 것(반환값을 다시 함수의 인자로 넣고 그 반환값을 다시 함수의 인자로 넣는 상황)도 좋지만, 뒤에서부터 읽고 반환값을 추측해야해서 코드를 이해하는 데에는 힘든 점이 있다. 함수에서 반환된 값은 따로 변수에 할당하고, 그 변수를 다른 함수에게 인수로 넘겨주도록 분리하자.
324 |
325 | - [#86] 과도한 분리는 오히려 복잡성을 증가시킬 수 있다.
326 |
327 | - [#82] 함수/메서드를 분리해야 하는 목적은 `외부에서의 접근수단 제공` 및 `여러 곳에서 호출`을 기준으로 판단해야 한다. 단순히 기능이 다르다는 이유만으로 잘게 분리하면 오히려 혼란을 줄 수도 있다.
328 | - 아래 코드의 경우 지나치게 잘게 분리되었다고 볼 수 있다. renderLottoList, renderPurchasedMessage는 모두 renderLottoSection 한군데에서만 호출하고 있는, 분리의 목적이 불분명한 메서드들이다. 이런 메서드들은 협업시 다른 사람들로 하여금 혹시 다른데서도 호출하는 곳이 있나? 하는 혼란만 주게 될 수 있지 않는지 고민할 필요가 있다.
329 |
330 | ```jsx
331 | renderLottoSection(lottoList) {
332 | this.renderPurchasedMessage(lottoList.length);
333 | this.renderLottoList(lottoList);
334 | }
335 |
336 | renderLottoList(lottoList) {
337 | this.$lottoContainer.innerHTML = lottoList
338 | .map((lotto) => this.generateLottoTemplate(lotto))
339 | .join('');
340 | }
341 |
342 | renderPurchasedMessage(lottoAmount) {
343 | this.$purchasedMessage.innerText = `총 ${lottoAmount}개를 구매하였습니다.`;
344 | }
345 | ```
346 |
347 | ```jsx
348 | // 이 또한 과도한 메서드 분리라고 볼 수 있다. 기능별로 분리를 해두었지만, 실제 호출은 start메서드 한군데에서만 이뤄지고 있기 때문이다.
349 | class LottoGameManager {
350 | #initializeGame() {
351 | this.lottoGameModel = new LottoGameModel();
352 | this.lottoGameView = new LottoGameView();
353 | }
354 |
355 | #initializeDOM() {
356 | this.$chargeForm = findElement(SELECTOR.CHARGE_INPUT_FORM);
357 | this.$chargeInput = findElement(SELECTOR.CHARGE_INPUT);
358 | this.$alignConverter = findElement(SELECTOR.ALIGN_CONVERTER);
359 | }
360 |
361 | #initializeHandler() {
362 | this.$chargeForm.addEventListener("submit", this.onSubmitChargeInputForm);
363 | this.$alignConverter.addEventListener("change", this.onChangeAlignState);
364 | }
365 | start() {
366 | this.#initializeGame();
367 | this.#initializeDOM();
368 | this.#initializeHandler();
369 | }
370 | }
371 | ```
372 |
373 | ### 예외처리
374 |
375 | - [#85] 무한대의 값을 입력할 수 있도록 두면, 일정 금액 이상이 넘어가게 되면 앱이 정지하게 되므로, 예외처리를 해야 한다.
376 |
377 | ### DOM
378 |
379 | - [#86] Element가 받는 value 중 Number Input의 경우 value 대신 `valueAsNumber` 프로퍼티를 사용할 수도 있다.
380 |
381 | ### CSS
382 |
383 | - [#86] 중복되는 CSS 컬러는 CSS 변수로 관리해보자.
384 | - https://developer.mozilla.org/ko/docs/Web/CSS/Using_CSS_custom_properties
385 |
--------------------------------------------------------------------------------
/lotto/step1/movie.md:
--------------------------------------------------------------------------------
1 | # LEVEL 1 행운의 로또 STEP 1 - 무비😎
2 |
3 | ## 분석 담당 코드
4 |
5 | - 마르코 [PR 101](https://github.com/woowacourse/javascript-lotto/pull/101) X 무비 [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99)
6 | - 밧드 [PR 102](https://github.com/woowacourse/javascript-lotto/pull/102) X 우디 [PR 109](https://github.com/woowacourse/javascript-lotto/pull/109)
7 | - 앨버 [PR 90](https://github.com/woowacourse/javascript-lotto/pull/90) X 결 [PR 87](https://github.com/woowacourse/javascript-lotto/pull/87)
8 | - 나인 [PR 95](https://github.com/woowacourse/javascript-lotto/pull/95) X 비녀 [PR 94](https://github.com/woowacourse/javascript-lotto/pull/94)
9 | - 샐리 [PR 100](https://github.com/woowacourse/javascript-lotto/pull/100)
10 |
11 |
12 |
13 |
14 | ## 아키텍처 분석
15 |
16 | ### 마르코 [PR 101](https://github.com/woowacourse/javascript-lotto/pull/101) X 무비 [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99)
17 |
18 | - MVC 패턴
19 | - custom 이벤트를 subscribe하는 형식
20 | - `emit`으로 이벤트를 송신하고 `on`으로 이벤트를 수신한다.
21 | - controller가 직접적으로 dom 이벤트를 처리하는 방식이 아닌, view가 dom 이벤트를 controller에게 알리고, controller가 처리하는 방식
22 |
23 | ### 밧드 [PR 102](https://github.com/woowacourse/javascript-lotto/pull/102) X 우디 [PR 109](https://github.com/woowacourse/javascript-lotto/pull/109)
24 |
25 | - MVC 패턴
26 | - 큰 controller가 존재하고 그 아래에 sub controller가 존재하는 방식, sub controller는 생성자의 매개변수로 큰 controller의 인스턴스를 받는다.
27 | - PR 109에서는 [core](https://github.com/woowacourse/javascript-lotto/tree/greenblues1190/src/js/core)에 부모 controller, model등이 존재하고 자식들이 상속받는 방식으로 변경됨
28 | - view에 `addEventListener`를 등록하는 함수가 존재하고 그 함수를 controller에서 호출한다. controller에서는 로직을 처리하는 함수를 `callback`으로 전달한다.
29 | - `index.html`에서 template를 만들어 놓고 `show`, `hide`하는 방식이 아닌, 이벤트가 발생할 때마다 `innerHTML`을 통해 template를 교체하는 방식
30 |
31 | ### 앨버 [PR 90](https://github.com/woowacourse/javascript-lotto/pull/90) X 결 [PR 87](https://github.com/woowacourse/javascript-lotto/pull/87)
32 |
33 | - MVC 패턴
34 | - `index.js`에서 model, view, controller를 선언해주는 방식이다.
35 | - custom 이벤트를 subscribe하는 형식 (마르코, 무비와 같음)
36 | - controller와 model은 하나씩 존재하고, view는 여러개
37 |
38 | ### 나인 [PR 95](https://github.com/woowacourse/javascript-lotto/pull/95) X 비녀 [PR 94](https://github.com/woowacourse/javascript-lotto/pull/94)
39 |
40 | - MVC 패턴
41 | - controller에서 이벤트를 등록해주고, model과 view에 존재하는 함수를 불러와 실행하는 방식
42 | - controller와 view는 하나씩 존재하며, model은 두개 존재 (로또와 로또 더미들)
43 | - controller를 `app.js`에서 생성해주고 controller의 생성자 안에서 model과 view를 생성하는 구조
44 |
45 | ### 샐리 [PR 100](https://github.com/woowacourse/javascript-lotto/pull/100)
46 |
47 | - 드디어 MVC 패턴이 아니다!
48 | - `app.js`에서 이벤트를 등록해주는 방식
49 | - `App`에서 validator라던지, view를 render해주는 모듈을 import해서 이벤트를 처리
50 | - `App`에 lotto에 대한 데이터, 사용자가 입력한 금액 등을 저장
51 | - 로직들이 있는 모듈은 `core`폴더에 존재, 이 곳에 `Lotto` 클래스도 존재
52 |
53 |
54 |
55 |
56 | ## 피드백 정리
57 |
58 | 😛 링크를 누르면 해당 discussion으로 이동! 😛
59 |
60 |
61 |
62 | ### MVC 패턴
63 |
64 | - 컨트롤러에서 모델에게 전달받은 상태를 직접 뷰에게 전달하는 것이 적절하다. - [PR 102](https://github.com/woowacourse/javascript-lotto/pull/102#discussion_r815267289)
65 | - 컨트롤러가 여러개의 view를 가질 수 있으니 확장성을 고려하며 설계할 것 - [PR 109](https://github.com/woowacourse/javascript-lotto/pull/109#discussion_r813851182)
66 | - MVC 패턴 관련 공유 내용 - [PR 90](https://github.com/woowacourse/javascript-lotto/pull/90#issuecomment-1049971008), [PR 90 - 추가 코멘트](https://github.com/woowacourse/javascript-lotto/pull/90#issuecomment-1050580916)
67 |
68 | ```
69 | Model은 Controller와 View에 의존하지 않아야 한다.
70 | → Model 내부에 Controller와 View에 관련된 코드가 있으면 안 된다.
71 | → 즉 모델 내부에서 컨트롤러와 뷰에 있는 것을 임포트하면 안 됨
72 | View는 Model에만 의존해야하고, Controller에 의존하면 안 됨.
73 | → View에는 Model의 코드만 있을 수 있고, Controller의 코드가 있으면 안 됨. *
74 | View가 Model로부터 데이터를 받을 때,
75 | 사용자마다 다르게 보여주어야 하는 데이터에 대해서만 Model에서 받야아 한다.
76 | → 공통으로 보여지는 부분은 View가 자체적으로 가지고 있어야하는 것.
77 | Controller는 Model과 View에 의존해도 된다.
78 | View가 Model로부터 데이터를 받을 때, 반드시 Controller에서 받아야 한다. **
79 | → 데이터를 받을 때 Controller 코드 안에서만 받야 한다.
80 |
81 | + 리뷰어님의 추가 코멘트
82 | View에는 Model의 코드만 있을 수 있고, Controller의 코드가 있으면 안 됨. *
83 | -> Controller를 통해 Model의 데이터가 업데이트되기때문에 Controller의 코드가 있을 필요가 없다.
84 | View가 Model로부터 데이터를 받을 때, 반드시 Controller에서 받아야 한다. **
85 | -> Controller가 Model을 업데이트하고 그 업데이트된 Model의 데이터를 받아야한다. 위 글에서 "반드시 Controller에서 받아야 한다."는 Controller의 액션의 통해서 받아야한다 라고 말씀드리는게 명확했던 것 같네요.
86 | ```
87 |
88 | - (_구조에 대한 생각?_) 리뷰어님의 개인적인 취향은 controller에서 model과 view 인스턴스를 생성하고, 생성하고 `index.js`에서 controller를 생성하는 편 - [PR 90](https://github.com/woowacourse/javascript-lotto/pull/90#issuecomment-1049971008)
89 | - (_view와 controller중 어디에서 `addEventListener`를 해주는 게 맞을까요?_) 리뷰어님의 개인적인 취향은 DOM에 대한 이벤트 바인딩은 view에서 담당하고 그 이벤트에 대한 액션은 controller에서 하는게 좀 더 역할 분담이 명확해 보임 - [PR 90](https://github.com/woowacourse/javascript-lotto/pull/90#issuecomment-1049971008)
90 |
91 | ### OOP
92 |
93 | - (_js에서 클래스를 통해서 OOP를 했을 때 가지는 장점이 있을까요?_) class를 사용하면 접근성이 좋아지고, 쉽고 일반적이다. - [PR 94](https://github.com/woowacourse/javascript-lotto/pull/94#pullrequestreview-894384954)
94 |
95 | ### 상속
96 |
97 | - 부모 클래스가 자식 클래스의 도메인 명을 알고 있지 않도록 주의 - [PR 109](https://github.com/woowacourse/javascript-lotto/pull/109#discussion_r813851496)
98 |
99 | ### 상수화
100 |
101 | - 리뷰어님이 기준이 명확하다고 한 상수화 - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r814070274)
102 | 1. 재사용과 변경이 될 수 있는 값을 상수화한다.
103 | 2. 그 자체로 의미를 알아볼 수 있는 값이면 상수화하지 않아도 된다.
104 | - 테스트 코드에도 상수를 사용하여 로또 가격이 바뀔 것을 고려할 것 - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r814079068)
105 | - 커스텀 이벤트를 사용한 경우 커스텀 이벤트가 많아질 경우를 대비해 상수화 할 것 - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r814082393)
106 |
107 |
108 |
109 | ### 변수
110 |
111 | - (_메서드 안에 임시변수를 쓰는 것은 지양되어야 할까요?_) 리뷰어님의 개인적인 의견은 남이 유츄가 가능한지 아닌지로 임시변수를 선언할지 말지를 결정한다. - [PR 90](https://github.com/woowacourse/javascript-lotto/pull/90#issuecomment-1049971008)
112 | - 예외의 값은 저장하지 않아도 된다. - [PR 90](https://github.com/woowacourse/javascript-lotto/pull/90#discussion_r813976935)
113 |
114 |
115 |
116 | ### 네이밍
117 |
118 | - `Controller`와 같은 일반적인 이름을 사용한다면 라이브러리인지, 구현체인지 헷갈리기 쉽다. `LottoControlle`와 같이 구체적으로 작명해줄 것 - [PR 94](https://github.com/woowacourse/javascript-lotto/pull/94#discussion_r815283358)
119 | - 함수의 네이밍은 동사/명령형 - [PR 100](https://github.com/woowacourse/javascript-lotto/pull/100#discussion_r814729214)
120 |
121 |
122 |
123 | ### 고차함수
124 |
125 | - 반복이면 바로 `forEach`를 쓸 수 있지만, 만약 반환을 해줘야 하는 경우라면 `map`을 고려해볼 것 - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r814069099)
126 | - 리뷰어님이 괜찮다고 해주신 커스텀 반복함수 - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r814030886)
127 |
128 | ```js
129 | const repeatCallback = (count, callback) => {
130 | for (let i = 0; i < count; i += 1) {
131 | callback();
132 | }
133 | };
134 | ```
135 |
136 | - 템플릿을 만들 때 중복이 된다면 `map`을 고려해보자. - [PR 109](https://github.com/woowacourse/javascript-lotto/pull/109#discussion_r813861348)
137 | - 순차적인 데이터로 배열을 채울 때 활용할 수 있는 내장함수 - [PR 109](https://github.com/woowacourse/javascript-lotto/pull/109#discussion_r816423887)
138 |
139 | ```js
140 | //before
141 | const array = [];
142 |
143 | for (let i = start; i <= end; i += 1) {
144 | array.push(i);
145 | }
146 |
147 | return array;
148 | };
149 |
150 | //after : for문을 사용하지 않는 방식
151 | Array.from({ length: end - start + 1 }).map((_, index) => start + index);
152 | ```
153 |
154 | - 배열을 생성할 때 `map`을 생각해보자 - [PR 95](https://github.com/woowacourse/javascript-lotto/pull/95#discussion_r814757511)
155 |
156 |
157 |
158 | ### 함수
159 |
160 | - 데이터가 변경될 것을 대비해서 그 데이터를 대표하는 단어를 함수에 쓰지 말 것 - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r814071346)
161 | - 특정 값에 종속적인 함수는 분리할 필요가 없다. - [PR 101](https://github.com/woowacourse/javascript-lotto/pull/101#discussion_r815254485)
162 | - 역할을 분리하는 것은 좋지만 역할을 분리함으로써 불필요한 코드가 더 생기진 않는지 생각해보자. - [PR 95](https://github.com/woowacourse/javascript-lotto/pull/95#discussion_r814765497)
163 |
164 |
165 |
166 | ### 콜백함수
167 |
168 | - 콜백함수를 넘긴다는건 그 함수에게 역할을 위임 하겠다는 것 - [PR 102](https://github.com/woowacourse/javascript-lotto/pull/102#discussion_r815267289)
169 |
170 |
171 |
172 | ### 유용한 내장 함수
173 |
174 | - 가격에 단위표시를 하고 싶다면 `toLocaleString()`을 사용해보자. - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r815812894)
175 | - 현재 문자열의 시작을 다른 문자열로 특정 길이만큼 채우고 싶다면 [`padStart()`](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/padStart)를 사용해보자. - [PR 95](https://github.com/woowacourse/javascript-lotto/pull/95#discussion_r814791814)
176 |
177 |
178 |
179 | ### 중복 판단
180 |
181 | - 중복을 판단하는 로직을 작성하기보다는 Set을 활용해보자 - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r814076018)
182 |
183 |
184 |
185 | ### 예외 처리
186 |
187 | - `Throw`와 `try-catch`문을 함께 사용할 때 `alert`를 `catch`에 작성해주면 코드량도 줄고 유연한 구조가 된다. - [PR 94](https://github.com/woowacourse/javascript-lotto/pull/94#discussion_r815283789)
188 |
189 |
190 |
191 | ### 테스트
192 |
193 | - TDD의 잔재주의! 테스트는 실제 모듈로 진행하자 - [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r815802271)
194 | - 성공을 보장해야하는 테스트도 존재한다. [PR 99](https://github.com/woowacourse/javascript-lotto/pull/99#discussion_r816019589)
195 | - 테스트코드를 Given, When, Then 구조로 구체적인 설명을 적는다면 역할과 흐름이 잘 보여진다. - [PR 102](https://github.com/woowacourse/javascript-lotto/pull/102#pullrequestreview-894376083)
196 | - 경계값에 대한 테스트도 다양하게 작성해보자. - [PR 94](https://github.com/woowacourse/javascript-lotto/pull/94#discussion_r816456100)
197 |
198 |
199 |
200 | ### UI/UX
201 |
202 | - 디자인을 이유로 input의 focus를 알려주는 outline을 삭제하지 말 것. 어떤 요소에 접근했는지 알려주는 역할이기 때문이다. 이는 웹 접근성 관련 - [PR 101](https://github.com/woowacourse/javascript-lotto/pull/101#discussion_r814892644)
203 | - 버튼이 disable일때는 표시해주자. - [PR 100](https://github.com/woowacourse/javascript-lotto/pull/100#discussion_r814748074)
204 |
205 |
206 |
207 | ### HTML
208 |
209 | - [Heading](https://developer.mozilla.org/ko/docs/Web/HTML/Element/Heading_Elements#%EC%82%AC%EC%9A%A9_%EC%9D%BC%EB%9E%8C) 단계를 뛰어넘지 말 것. 순차적으로 기입하세요. Heading은 글자 크기를 위해 존재하는 게 아닙니다. - [PR 102](https://github.com/woowacourse/javascript-lotto/pull/102#discussion_r815262983)
210 | - `label`과 `input`을 `for`로 연결해줄 것 - [PR 102](https://github.com/woowacourse/javascript-lotto/pull/102#discussion_r815269072)
211 | - `innerHTML`은 XSS 공격에 취약하고, 무겁고 비싸다. 대신 `createElement`나 `insertAdjacentHTML`를 사용할 수 있다. - [PR 100](https://github.com/woowacourse/javascript-lotto/pull/100#discussion_r815269744)
212 |
213 | ### CSS
214 |
215 | - 기본 태그 (`input`, `p` 등)으로 스타일을 조정하면 서비스가 커질 때 사이드 이펙트를 발생시킬 확률이 높다. - [PR 109](https://github.com/woowacourse/javascript-lotto/pull/109#discussion_r813836290)
216 | - user agent stylesheet를 고려한 [reset.css](https://abcdqbbq.tistory.com/9) - [PR 90](https://github.com/woowacourse/javascript-lotto/pull/90#discussion_r813975297)
217 | - css를 HTML에 link를 통해 import할 경우 css의 rule이 전역에서 사용된다. 이는 사이드 이팩트를 일으킴. 이런 문제를 해결하기 위해 [css 모듈화](https://blog.toycrane.xyz/css%EC%9D%98-%EC%A7%84%ED%99%94-%EA%B3%BC%EC%A0%95-f7c9b4310ff7)가 진행 - [PR 94](https://github.com/woowacourse/javascript-lotto/pull/94#pullrequestreview-894384954)
218 | - js에서 직접 스타일을 조정하지 않고 class를 달아주고 제거하는 방식을 택하자. - [PR 95](https://github.com/woowacourse/javascript-lotto/pull/95#discussion_r814778236)
219 |
220 |
221 |
222 | ### util
223 |
224 | - 도메인에 속해있는 로직은 util로 분리하지 마세요. 범용적인 성격을 가진 아이들만 분리해주세요. - [PR 102](https://github.com/woowacourse/javascript-lotto/pull/102#discussion_r815270046)
225 |
226 |
227 |
228 | ### YAGNI!
229 |
230 | - `You Aren't Gonna Need It` - 처음부터 확장성이나 미래를 심히 고려해 오버엔지니어링하는 걸 경계하자. - [PR 100](https://github.com/woowacourse/javascript-lotto/pull/100#pullrequestreview-894510935)
231 |
--------------------------------------------------------------------------------
/lotto/step2/hope.md:
--------------------------------------------------------------------------------
1 |
2 | ## 분석 담당 코드
3 | - [시지프](https://github.com/woowacourse/javascript-lotto/pull/151)
4 | - [호프](https://github.com/woowacourse/javascript-lotto/pull/154)
5 | - [병민](https://github.com/woowacourse/javascript-lotto/pull/133)
6 | - [티거](https://github.com/woowacourse/javascript-lotto/pull/150)
7 | - [우연](https://github.com/woowacourse/javascript-lotto/pull/149)
8 | - [소피아](https://github.com/woowacourse/javascript-lotto/pull/139)
9 | - [해리](https://github.com/woowacourse/javascript-lotto/pull/130)
10 |
11 | ### 함수
12 | - [#130] Math.floor는 소수점 내림, parseInt는 소수점을 버림 / 따라서 양수에는 동일하게 작용하나 음수에는 다르게 작용
13 | - parseInt는 정확하게 string 형을 number로 만들어준다.
14 | - string -> number 변환시 박싱 / 언박싱이 일어난다.
15 | - 즉, 메모리 계층에서 기존 메모리 타입인 string을 갖고 있다가, number 형태의 메모리 블록을 만들고 그 후 string을 number로 옮기며 메모리의 비효율이 일어난다.
16 | - Math.floor는 타입캐스팅하지 않고 오롯이 계산만 하기 때문에 박싱 / 언박싱에서 자유로움
17 |
18 | ### 네이밍
19 | ```javascript
20 | intersection(arrayA, arrayB)
21 | ```
22 | - [#150] 집합 개념을 사용하고 있을 때에는 1,2 보다 A, B가 들어간 이름이 더 적합하다.
23 |
24 |
25 | ### 이벤트 관련
26 | - [#151] input은 값이 들어온 직후 발생되는 이벤트 / keyup은 keyboard에서 떨어질때 발생하는 이벤트
27 |
28 |
29 | ### 반복문
30 | - [#110] 정해진 횟수만큼 반복할 때는 for문, 그러지 않을때에는 while문 사용
31 |
32 |
33 | ### 테스트
34 | - [#110] 변경으로 인해 사이드 이펙트가 없고 해당 로직이 변경되지 않는다는 확신이 있다면 테스트 할 필요성이 떨어진다.
35 | - [#149] 테스트를 위한 분리는 지양하기. 하지만 함수가 하나의 행동만 해야된다는 원칙에 위배된다면 분리해는게 좋은 방향성이라고 생각하는데 단일 행동을 하게 작성하다보면 테스트하기 좋은 구조가 나온다.
36 | ```javascript
37 | import * as Utils from '../utils/util';
38 |
39 | jest
40 | .spyOn(Utils, 'generateRandomInRange')
41 | // ReturnValue chaining하여 정의
42 |
43 | ```
44 | - [#149] jest의 spyOn 메서드를 통해 특정 object의 key에 해당하는 함수를 mocking하기
45 | - [#150] [단위테스트vs인수테스트vs통합테스트](https://tecoble.techcourse.co.kr/post/2021-05-25-unit-test-vs-integration-test-vs-acceptance-test/), 하나의 테스트를 위해 여러가지 함수를 불러와서 실행시킨 후, 결과를 테스트한다면 단위테스트가 아니다.
46 |
47 |
48 |
49 | ### DOM 관련
50 |
51 | #### 자식노드 초기화 방법 [#110]
52 | - 부모에서 firstChild가 없을 때 까지 자식노드를 삭제한다 (자식 엘리먼트에서 remove를 사용하거나 부모 엘리먼트에서 removeChild를 활용) => 반복적으로 호출이 필요한 부분이지만 브라우저와 하위 노드 수에 따라 차이가 있겠지만 하위 노드의 수가 1000개를 기준으로 벤치마크에서 innerHTML보다 좋은 성능
53 | - textContent나 innerHTML을 “”로 변경
54 | 추가적으로 엘리먼트를 createElement로 생성했다면 replaceChildren으로 생성한 노드로 교체할 수 있다.
55 | - [#130] aria label은 해당 section이 어떤 것을 의미하는지 알려주고 웹에 더 정확한 정보를 전달하기 위해 사용한다.
56 |
57 | ### CSS
58 | - [#130] position 속성이 지정된 엘리먼트보다 position 속성이 지정되지 않은 엘리먼트가 먼저 렌더링 되어서 따로 z-index 설정을 할 필요가 없다.
59 | - 따라서 position이 absolute, fixed, sticky 등은 position이 지정되지 않은 엘리먼트보다 후에 렌더링한다.
60 | - 쌓임맥락으로 인해 z index 설정이 필요 없고 쌓임맥락 기준으로 그래픽 레이어가 렌더링되기 때문
61 | - 기본적인 position 설정을 안해주면 static이 되는데, static은 쌓임 맥락이 없으므로 가장 바닥에 쌓이고, 쌓임 맥락은 그 위로 쌓이기 때문에 그래픽 레이어 생성시 앞서 보이기 때문이다. [관련 링크](https://developer.mozilla.org/ko/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context)
62 |
--------------------------------------------------------------------------------
/lotto/step2/kkojae.md:
--------------------------------------------------------------------------------
1 | # STEP2. 꼬재 담당 크루 PR 링크
2 |
3 | ---
4 |
5 | - [#132꼬재](https://github.com/woowacourse/javascript-lotto/pull/132)
6 | - [#138도리](https://github.com/woowacourse/javascript-lotto/pull/138)
7 | - [#145후이](https://github.com/woowacourse/javascript-lotto/pull/145)
8 | - [#144콤피](https://github.com/woowacourse/javascript-lotto/pull/144)
9 | - [#124나인](https://github.com/woowacourse/javascript-lotto/pull/124)
10 | - [#128비녀](https://github.com/woowacourse/javascript-lotto/pull/128)
11 | - [#164코이](https://github.com/woowacourse/javascript-lotto/pull/134)
12 |
13 | ## 아키텍쳐 분석
14 |
15 | ---
16 |
17 |
18 |
19 | ### #132꼬재
20 |
21 | ---
22 |
23 | #### LottoConsumer
24 |
25 | 로또 구매자 티켓 관련 담당
26 |
27 | #### LottoSeller
28 |
29 | 로또 판매자 티켓 관련 담당
30 |
31 | #### LottoApp
32 |
33 | 이벤트 바인딩과 전체 프로그램 로직을 담당
34 |
35 | #### 그 외 파일
36 |
37 | util, template, dom 요소를 함수로 가지고 있고 필요한 영역에서 import 후 사용
38 |
39 |
40 |
41 | ### #138도리
42 |
43 | ---
44 |
45 | #### Lotto
46 |
47 | 로또 티켓 한 장을 생성하는 객체
48 |
49 | #### PurchasedLotto
50 |
51 | 구매한 전체 로또를 저장하고 있음
52 |
53 | #### LottoApp
54 |
55 | 이벤트 바인딩과 전체적인 프로그램 로직을 담당
56 |
57 | #### view 폴더
58 |
59 | view에 해당하는 로직과 dom 요소, templates들을 분리하여 관리
60 |
61 |
62 |
63 | ### #145후이, #124나인, #128비녀, #164코이, #144콤피
64 |
65 | ---
66 |
67 | #### model
68 |
69 | 나인, 비녀 : 로또 한 장의 객체 / 유저가 구매한 로또들, 당첨 번호를 관리하는 객체
70 | 코이 : 유저가 구매한 로또들, 당첨 번호를 관리하는 객체
71 | 후이 : 로또 한 장의 객체 / 당첨 로또 한 장의 객체 / 유저가 구매한 로또를 관리하는 객체 / 유저가 구매한 로또의 당첨 결과를 관리하는 객체
72 |
73 | 후이)
74 | js에서는 지원하지 않는 protected 사용 \_(언더바를 이용)
75 |
76 | 상속을 통해 로또 한장의 객체와 당첨 로또 한 장의 객체에서 overriding해서 사용해 중복된 코드 제거
77 |
78 | ```js
79 | export default class Lotto {
80 | // _는 protected 입니다.
81 | _lottoNumbers = [];
82 |
83 | generate() {
84 | const { MAX_RANDOM_NUMBER: MAX, MIN_RANDOM_NUMBER: MIN } = LOTTO_SETTING;
85 | const shuffledList = shuffle(
86 | [...Array(MAX - MIN + 1)].map((_, idx) => idx + MIN)
87 | );
88 | this._lottoNumbers = shuffledList.slice(
89 | 0,
90 | LOTTO_SETTING.LOTTO_NUMBER_LENGTH
91 | );
92 |
93 | return this;
94 | }
95 | }
96 | ```
97 |
98 | ```js
99 | export default class WinningLotto extends Lotto {
100 | generate(winningNumbers, bonusNumber) {
101 | try {
102 | checkValidWinningNumberInput(winningNumbers, bonusNumber);
103 |
104 | this._lottoNumbers = [...winningNumbers];
105 | this.#bonusNumber = bonusNumber;
106 |
107 | return this;
108 | } catch (error) {
109 | throw new Error(error);
110 | }
111 | }
112 | }
113 | ```
114 |
115 | #### controller
116 |
117 | 전체적인 프로그램 로직을 담당
118 |
119 | - 이벤트 바인딩
120 | - validate
121 |
122 | #### views 폴더
123 |
124 | 각각의 view 별로 파일을 나눠 view를 관리
125 |
126 | #### utils
127 |
128 | document에서 돔요소를 찾는 경우와 부모 엘리먼트에서 돔 요소를 찾을 수 있게 자동화 시킨 부분이 인상 깊어 코드를 가져왔습니다.
129 |
130 | ```js
131 | export const $ = (parentElement, childSelector = null) => {
132 | const target = childSelector || parentElement;
133 | const $parent = childSelector
134 | ? parentElement
135 | : document.getElementById(SELECTOR.ID.APP);
136 |
137 | return $parent.querySelector(target);
138 | };
139 |
140 | export const $$ = (parentElement, childSelector = null) => {
141 | const target = childSelector || parentElement;
142 | const $parent = childSelector
143 | ? parentElement
144 | : document.getElementById(SELECTOR.ID.APP);
145 |
146 | return $parent.querySelectorAll(target);
147 | };
148 | ```
149 |
150 |
151 |
152 | 에러 메시지를 관리할 때 줄 바꿈을 활용하는 부분이 인상 깊어 코드를 가지고 왔습니다.
153 |
154 | ```js
155 | export const ERROR_MESSAGE = {
156 | NEGATIVE_INPUT_ERROR:
157 | "양수를 입력해 주세요! \n (please enter positive number)",
158 | NOT_INTEGER_INPUT_ERROR:
159 | "정수를 입력해 주세요! \n (please enter integer number)",
160 | NOT_MUTIPLE_THOUSAND:
161 | "1000단위로 입력해 주세요! \n (please enter number that is mutiples of thousand)",
162 | };
163 | ```
164 |
165 |
166 |
167 | ## 피드백 정리
168 |
169 | ### CSS
170 |
171 | ---
172 |
173 | #### [#145] !important는 지양
174 |
175 | - CSS 케스케이딩에 대해 명확하게 확인해볼 필요가 있음.
176 | 정확하게 정리되어 있는 블로그를 찾지는 못함.
177 |
178 | #### [#145] vw, vh 와 em, rem 의 차이를 알아볼까요?
179 |
180 | 현재 실행중인 스크린 크기에 맞춰 상대적 크기를 반환하겠다는 의미의 vw, vh
181 |
182 | - vw
183 |
184 | - viewport width
185 | - 1vw는 viewport의 넓이의 1%와 같다
186 |
187 | - vh
188 | - viewport height
189 | - 1vh는 viewport의 높이의 1%와 같다
190 |
191 |
192 |
193 | em과 rem은 font-size 속성 값에 비례해서 결정되는 상대단위
194 |
195 | - em
196 |
197 | - 해당하는 단위가 사용되고 있는 요소의 font-size 속성 값이 기준이된다.
198 |
199 | - rem
200 | - 최상위 요소의 font-size 속성 값이 기준이된다.
201 |
202 | #### [#145] 의미있는 단위로 줄 간격을 줘볼까요?
203 |
204 | - 코드 라인수에 제한을 두는 것 보다, 의미있는 단위로 줄 간격을 나눠주는 것이 가독성을 높이는데 더 효율적이다.
205 |
206 | #### [#164] color 값은 css 변수로 관리해보자!
207 |
208 | ```css
209 | :root {
210 | --color-primary: #00bcd4;
211 | --color-light-gray: #b4b4b4;
212 | --color-white: #ffffff;
213 | --color-table-border: #c8c8c8;
214 | }
215 | ```
216 |
217 | #### [#164] 불필요한 z-index를 사용하지 않는다.
218 |
219 | - z-index를 1로 둔 이유가 있을까요?
220 | 만약 1로 두지 않았다면 어떻게 되었을까요?
221 | 쌓임의 맥락에 대해 한 번쯤 살펴볼 필요가 있다.
222 |
223 | ### JS
224 |
225 | ---
226 |
227 | #### [#132] from을 사용하면 fill,map 두가지 메소드를 사용하지 않고 하나의 메소드 구현할 수 있습니다. 더불어 현재 map에서 첫번째 파라미터 \_를 사용하지않고 있네요!
228 |
229 | ```js
230 | setLottoList(count) {
231 | this.#lottoList = Array(count)
232 | .fill(0)
233 | .map((_, index, list) => (list[index] = this.createLottoList(count)));
234 | }
235 | ```
236 |
237 | ```js
238 | setLottoList(count) {
239 | this.#lottoList = Array.from({length: count}, () => this.createLottoList(count));
240 | }
241 | ```
242 |
243 | #### [#145] setter 가 아닌 addMoney 함수가 더 어울리네요! 그리고 setter 는 안만들수록 좋으니 더더욱 addMoney 가 좋겠네요!
244 |
245 | - setter는 가급적 사용하지 않는게 좋다.
246 | - setter 대신에 생성자에 값을 넘겨주거나, 빌더 패턴을 사용하는 방법이 있다.
247 |
248 | 빌더 패턴이란..?!
249 |
250 | #### [#145] WinningLotto 가 Lotto 를 상속해보면 어떨까요?
251 |
252 | - 위에 코멘트 남긴대로 pushNumberIntoPickedNumbers 가 필요없으니 pushPickNumbers 같은 함수가 생길테니 재사용가능하고, generate 는 재정의하고, pickedNumbers 를 활용하면 bonusNumber 관련 로직만 추가하면 될 것 같은데요! 어떤가요?
253 |
254 | ```js
255 | export default class Lotto {
256 | // _는 protected 입니다.
257 | _lottoNumbers = [];
258 |
259 | generate() {
260 | const { MAX_RANDOM_NUMBER: MAX, MIN_RANDOM_NUMBER: MIN } = LOTTO_SETTING;
261 | const shuffledList = shuffle(
262 | [...Array(MAX - MIN + 1)].map((_, idx) => idx + MIN)
263 | );
264 | this._lottoNumbers = shuffledList.slice(
265 | 0,
266 | LOTTO_SETTING.LOTTO_NUMBER_LENGTH
267 | );
268 |
269 | return this;
270 | }
271 | }
272 | ```
273 |
274 | ```js
275 | export default class WinningLotto extends Lotto {
276 | generate(winningNumbers, bonusNumber) {
277 | try {
278 | checkValidWinningNumberInput(winningNumbers, bonusNumber);
279 |
280 | this._lottoNumbers = [...winningNumbers];
281 | this.#bonusNumber = bonusNumber;
282 |
283 | return this;
284 | } catch (error) {
285 | throw new Error(error);
286 | }
287 | }
288 | }
289 | ```
290 |
291 | #### [#145] 상속에 대한 리뷰어님의 생각
292 |
293 | - 아하 상속은 항상 장단점이 있죠! 한 함수만을 추상화한다해도 여러 자식 클래스에서 상속할 부모 클래스라면 그럴 가치는 있죠!
294 |
295 | 다만 프론트는 서버에 비해 변경 가능성이 크기 때문에 (예를 들어 숫자 인풋에서 문자 인풋으로 바뀔 수도 있죠?) 뷰 단까지 상속 구조를 써야 하나..? 내가 실무에서 뷰 단을 상속한 적이 있었나..? 라고 생각했을 땐 케이스가 별로 없네요 😅
296 |
297 | 아마 나중에 api 단을 구현하실 때 상속할 일이 좀 나올거에요! 그 때 더 고민하면서 적용해보시죠!
298 |
299 | #### [#144] 재사용되지 않는 View 들은 굳이 selector 들을 외부에서 주입해줘야할까? 라는 생각이 들긴 하는군요 🤔
300 |
301 | - 재사용되지 않는 인스턴스는 필요한 곳에서 생성해서 사용하는 것이 좋다. 맴버 변수로 관리할 필요가 없기 때문
302 |
303 | #### [#144] 이건 fill 을 이용해 초기화할 수도 있을 거 같습니다! (수정할 필요는 없어용)
304 |
305 | ```js
306 | const output = Array.from(
307 | { length: LOTTO_SETTING.RACKING_START_NUMBER },
308 | () => 0
309 | );
310 | const output = Array(LOTTO_SETTING.RACKING_START_NUMBER).fill(0);
311 | ```
312 |
313 | #### [#144] 오... 이벤트를 한 번만 걸어주고 자동으로 지워주는 게 있군요! 하나 더 배워갑니다ㅎ
314 |
315 | ```js
316 | $element.addEventListener(eventType, callback, {
317 | once: true,
318 | });
319 | ```
320 |
321 | ### 컨벤션
322 |
323 | ---
324 |
325 | #### [#144] SELECTOR를 상수화 할 경우 class선택자와 id 선택자를 분리하여 작성해주는게 좋다.
326 |
327 | - 물론, 당연히 SELECTOR, DOM_NAME 의 쓰임새에 대한 '약속'을 했지만, 휴먼에러는 언제든 발생할 수 있으니까요.
328 | ID SELECTOR 인데 CLASS 로 생각해서 iterator 문법을 사용할 수도 있고요 ;ㅁ;
329 |
330 | #### [#132] MONEY를 객체로 만들 필요가 있을까요?ㅎ 혹시 제가 생각못한 확장성 부분이 있을까요?
331 |
332 | - 현재 프로젝트에서는 모두 객체로 사용하지만 실제 상수는 enum, array, 값 등 다양하게 사용됩니다. 그렇기때문에 통일성을 맞추기보다 그 역할에 맞게 작성하시는게 좋을 것 같습니다 ㅎ
333 |
334 | ```js
335 | export const MONEY = {
336 | STANDARD: 1000,
337 | };
338 | ```
339 |
340 | ```js
341 | export const MONEY_STANDARD = 1000;
342 | ```
343 |
344 | ### 테스트
345 |
346 | ---
347 |
348 | #### [#145] 의미 없는 테스트 코드는 지워준다.
349 |
350 | - 유효성 체크를 처음부터 모두 짜놓고 코드를 구현하겠지만, 코드 구현 후 테스트 코드로 돌아왔을 때 막상 필요없는 테스트라면 지우는게 맞지 않을까 싶습니다!
351 |
352 | #### [#132] 모든 테스트 케이스마다 cy.visit('./index.html');가 있는데요 이는 before으로 따로 빼도 좋을 것 같습니다ㅎ
353 |
354 | [before/beforeEach](https://docs.cypress.io/guides/core-concepts/writing-and-organizing-tests#Hooks)
355 |
356 | - before : 같은 describe 안에서 테스트가 많더라도 단 한 번만 실행되는 경우 사용
357 |
358 | cy.visit()의 경우 한 번 방문한 후 계속해서 동작을 이어나갈 수 있다.
359 |
360 | - beforeEach : 같은 describe 안에서 테스트별로 각각 한 번씩 사용되어야할 경우 사용
361 |
--------------------------------------------------------------------------------
/lotto/step2/lokba.md:
--------------------------------------------------------------------------------
1 | # Level1 Lotto Step2(담당 PR 번호들) - 록바
2 |
3 | - 분석 담당 코드
4 | - 돔하디 [#126](https://github.com/woowacourse/javascript-lotto/pull/126)
5 | - 민초 [#155](https://github.com/woowacourse/javascript-lotto/pull/155)
6 | - 블링 [#129](https://github.com/woowacourse/javascript-lotto/pull/129)
7 | - 태태 [#125](https://github.com/woowacourse/javascript-lotto/pull/125)
8 | - 하리 [#122](https://github.com/woowacourse/javascript-lotto/pull/122)
9 | - 록바 [#148](https://github.com/woowacourse/javascript-lotto/pull/148)
10 | - 코카콜라 [#137](https://github.com/woowacourse/javascript-lotto/pull/137)
11 | - 안 [#153](https://github.com/woowacourse/javascript-lotto/pull/153)
12 |
13 |
14 |
15 | ## 아키텍처 분석(desc: 담당한 소프트웨어의 아키텍처를 간단히 분석하고 설명합니다)
16 |
17 |
18 |
19 | ### [#122] 하리
20 |
21 | #### 인상 깊은 점
22 |
23 | - 자주 쓰이는 color를 변수로 관리한 것
24 |
25 | ```javascript
26 | :root {
27 | --main-bg-color: #e5e5e5;
28 | --app-bg-color: #ffffff;
29 | --popup-bg-color: #00000080;
30 | ...
31 | }
32 | ```
33 |
34 |
35 |
36 | - [MDN](https://developer.mozilla.org/ko/docs/Web/CSS/Using_CSS_custom_properties) 내용 - 흔히 보이는 패턴은 :root 의사 클래스에 선언해서 여러분의 HTML 문서 어디에서나 사용자 지정 속성에 접근할 수 있도록 구성하는 것입니다.
37 |
38 | ### [#129] 블링
39 |
40 | #### 인상 깊은 점
41 |
42 | - fieldset과 legend 사용
43 |
44 | ```html
45 |