[2단계 - 행운의 로또 미션] 신세한탄(신세희) 미션 제출합니다#56
Conversation
|
질문에 대한 답변
|
wow9144
left a comment
There was a problem hiding this comment.
Result를 중심으로 개선해야 할 부분들이 있을 것 같습니다. 세부 코멘트 확인 부탁드립니다 :)
차근차근 잘 나아가고 계십니다! 지금 처럼 계속 화이팅!
src/js/Model/Result.js
Outdated
| }); | ||
|
|
||
| this.matchingCounts = tmpMatchingCounts; | ||
| $(ELEMENT.WIN_NUMBER_CONTAINER).dataset.totalPrize = totalPrize; |
There was a problem hiding this comment.
Model은 View 를 알지 못합니다. DOM에 접근 하는 코드가 존재해서는 안됩니다.
There was a problem hiding this comment.
넵 따로 dataset을 사용하지 않고 receiptView에서 submitController에 totalPrize를 요청, submitController에서는 Result에서 리턴한 totalPrize값을 받아와서 receiptView에 전달하는 방식으로 변경해보았습니다.
뷰가 모델로부터 데이터를 받을 때에는 반드시 컨트롤러에서 받아야 한다는 것을 염두에 두면서 바꾸려니 쉽지 않았습니다ㅠㅠ
사실, totalPrize를 Result에 저장하면 가장 편할 것 같은데 총 수익금은 수익률을 계산하는 과정에서만 사용되고 직접 렌더 되거나 하는 일은 없기 때문에 저장까지는 불필요할 것 같아서 저장하지 않았는데 아직도 맞는 판단인지 잘 모르겠습니다.
저장해야 하는 정보와 그렇지 않은 정보를 어떤 기준으로 정해야 할까요?
src/js/Model/Result.js
Outdated
| } from "../Util/constants.js"; | ||
| import { $ } from "../Util/querySelector.js"; | ||
|
|
||
| class Result { |
There was a problem hiding this comment.
이 클래스를 보고 전체 코드를 다시 훑어보게 되었습니다.
그리고 문제를 하나 찾았는데, 대부분의 메서드를 void형으로 작성하고 계신다는 점이었습니다.
객체가 자신의 상태를 바꾸는 메서드를 공개해놓고 메서드로 요청이 들어오면 그 요청에 따라 자신이 관리하는 상태가 변경되어야 한다면 자신의 상태를 바꾸고 요청에 맞는 결과값을 응답으로 리턴해줘야 하는곳에서는 리턴을 해줘야 하는데 리턴을 하지 않고 상태만 바꾸고 전혀 다른곳에서 이 상태를 가져다 쓰는 식으로 코드가 전개되는 경향을 발견했습니다.
이런 패턴으로 코드가 전개되면, 지속적으로 상태가 변경되기 때문에 저장해둬야 하는 값이 아니라 일시적으로 계산만 하고 결과를 반환해도 되는 값 조차 전부 내부 상태로 두는식으로 코드가 작성되기 때문에 사용되지도 않는 불필요한 프로퍼티가 생길 수 있습니다.
또한 버그 발생시 디버깅이 힘들어집니다.
이 값이 나오면 안되는 값인데 어디서 이 값이 세팅 되었는지 추적하기 힘들 수 있습니다.
따라서 불필요한 프로퍼티가 있지는 않은지, 메서드의 시그니처(파라미터와 리턴 타입)가 적절한지 한 번 살펴보시면 좋을 것 같습니다.
winning, rank 등을 계산하는 책임을 TicketBundle에 두지 않고(몰아두셨던 분들이 많았었는데) 따로 빼신 부분은 잘 하신 부분입니다.
이름만 Result 라는 너무 추상적인 이름보다는 조금 더 구체적이면 더 좋지 않을까 싶습니다. :)
There was a problem hiding this comment.
피드백을 반영하기 위해 흐름을 따라가면서, 리턴 없이 저장만 하고 필요한 곳에서 모델을 찾아 가져다 쓰는 상태가 얼마나 디버깅에 취약할 지 짐작이 되었습니다..!
적절히 리턴하고 필요한 곳에서 파라미터로 받아서 사용하도록 바꾸다보니 Result 모델에 있던 ranks도 저장할 필요가 없었던 값인 것을 발견했습니다.
Result모델의 이름은 WinningResult로 변경했습니다!
src/js/Util/constants.js
Outdated
| export const ALERT_MESSAGE = { | ||
| INVALID_NUMBER: "문자 및 공백은 입력 불가능합니다.", | ||
| INVALID_RANGE: "1000원 이상, 5000원 이하만 입력 가능합니다.", | ||
| INVALID_MONEY_RANGE: "1000원 이상, 5000원 이하만 입력 가능합니다.", |
There was a problem hiding this comment.
ONE_TICKET_PRICE, MAX_PURCHASE_PRICE 가 변경 되었을 때 지금은 여기도 같이 직접 변경해줘야 합니다.
휴먼 에러가 나기 쉬운 지점이므로 여기서도 ONE_TICKET_PRICE, MAX_PURCHASE_PRICE 를 쓰도록 개선하는게 좋아보입니다.
There was a problem hiding this comment.
alert message내부에서도 상수를 쓰면 좋군요!!! 바로 적용했습니다 :)
| closeModal, | ||
| } from "../Handler/elementHandler.js"; | ||
| import TicketBundle from "../Model/TicketBundle.js"; | ||
| import Result from "../Model/Result.js"; |
There was a problem hiding this comment.
import 해온 Result가 클래스 또는 생성자 함수가 아니므로(export 하는 쪽에서 객체를 생성해서 넘겨줬기 때문) Result가 아닌 result 가 더 좋을 것 같습니다.
아래 Result를 사용하는 쪽에서 관련 설명 이어가겠습니다.
There was a problem hiding this comment.
TicketBundle도 동일하게 export하는 쪽에서 객체를 생성해서 넘겨주는 방식을 사용했기 때문에 import ticketBundle하여 사용하는 것으로 변경했습니다 :)
| }; | ||
|
|
||
| const setNumbers = (winningNumbers, bonusNumber) => { | ||
| Result.setWinningNumbers(winningNumbers); |
There was a problem hiding this comment.
Result.setWinningNumbers() 는 Result 가 클래스이고 setWinningNumbers 는 스태틱 메서드로 생각되게 합니다.
근데 스태틱 메서드가 아니므로 Result가 아니라 result 가 되어야 합니다.
아래 Result 로 쓰고 있는곳 전부 이에 해당됩니다.
There was a problem hiding this comment.
아직 클래스 사용에 미숙해서 export default new Result();를 사용하면 클래스를 export하는 동시에 생성할 수 있다는 정도만 알고, export한 이후에 제대로 사용하는 방법을 몰랐던 것 같습니다.
export로 내보낸 Result를 import result from "../Model/Result.js"로 import하면 import한 이름인 result로 사용할 수 있고, 여러 곳에서 import하더라도 동일한 하나의 인스턴스를 참조한다는 것을 공부하게 되었습니다 :)
wow9144
left a comment
There was a problem hiding this comment.
코멘트 드린 부분은 3단계로 넘어가서 고민 하셔도 될 것 같습니다.
화이팅!!
| export const printWinningResult = () => { | ||
| export const printWinningResult = (matchingCounts) => { | ||
| const winningCounts = $$(ELEMENT.WINNING_COUNT); | ||
| const totalPrize = getTotalPrize(); |
There was a problem hiding this comment.
MVC 패턴에서 View와 Model은 Controller를 모릅니다.
따라서 View가 Controller의 기능에 의존하면 안되고 Controller로 부터 주입을 받아야 합니다.
getTotalPrize를 직접 호출 하지 말고 필요한 값은 파라미터로 받아서 그리기만 해야 합니다.
There was a problem hiding this comment.
의존의 의미를 자의적으로 해석했던 것 같습니다. 직접 호출하지 않고 파라미터로 받아서 그릴 수 있도록 변경했습니다 :)
| import { ELEMENT } from "../Util/constants.js"; | ||
| import { $, $$ } from "../Util/querySelector.js"; | ||
| import result from "../Model/Result.js"; | ||
| import { getTotalPrize } from "../Controller/submitController.js"; |
There was a problem hiding this comment.
View 파일에서 Controller 파일을 import 하면 의존의 방향이 잘못된 것이라고 볼 수 있습니다.
There was a problem hiding this comment.
View에서 이 함수 하나만 import 해서 사용하고 있는 것이 어색하다고 생각하기는 했는데, 역시 잘못된 거였네요. 앞으로 import로 의존의 방향을 한 번 더 체크해볼 수 있을 것 같습니다 :) 감사합니다.
| const winningCounts = $$(ELEMENT.WINNING_COUNT); | ||
| const totalPrize = getTotalPrize(); | ||
| const money = Number($(ELEMENT.TICKET_IMAGE_NUMBER_CONTAINER).dataset.money); | ||
| const earningRate = ((totalPrize - money) / money) * 100; |
There was a problem hiding this comment.
이런 계산 로직은 View에서 직접 계산 하기보다는 외부에서 계산되어 주입되는게 좋습니다.
There was a problem hiding this comment.
넵 외부에서 계산해서 주입하는 방식으로 변경했습니다 :)
|
|
||
| setRanks(ticketBundle) { | ||
| let ranks = []; | ||
| ranks = ticketBundle.map((ticket) => { |
There was a problem hiding this comment.
const ranks = ticketBundle.map(....
하셔도 되셨을것 같습니다. :)
| this.matchingCounts = []; | ||
| } | ||
|
|
||
| initializeWinningResult() { |
There was a problem hiding this comment.
constructor 에서 초기화했던 프로퍼티들을 지금처럼 프로퍼티 전체를 다시 초기화 하는 형태의 메서드가 필요하게 되면 십중팔구 이 메서드가 필요했던것이 아니라 처음부터 객체를 새로 생성해서 썼어야 하는것일겁니다.
(하나를 추가하는데 매번 두 곳 이상의 코드가 똑같이 수정되면 이는 중복으로써 처읍부터 하나였어야 할 확률이 매우 높습니다.)
다시 말해 WinningResult 에서 필요한 프로퍼티가 하나 늘어날 때 마다 constructor와 initializeWinningResult 두 곳을 똑같이 수정해야 한다면 이는 중복이고 관리의 어려움이 따르므로 제거해야 할 대상이 됩니다.
There was a problem hiding this comment.
SubmitController를 클래스형으로 변환해서 모든 모델을의 객체를 새로 생성하는 방식으로 수정해보았는데... 이 구조가 괜찮은 구조인지는 잘 모르겠습니다ㅠㅠ 3차 PR에서 다시 한 번 피드백 부탁드립니다.
| } | ||
|
|
||
| setMatchingCounts(ranks) { | ||
| const rankInfo = [ |
There was a problem hiding this comment.
calculateTotalPrize 의 rankInfo 와 중복으로 보입니다.
선언은 한군데서 하고 양쪽에서 가져와 쓰는걸로 변경 하시면 좋을 것 같습니다.
| return !/^[0-9]+$/.test(money); | ||
| }; | ||
|
|
||
| export const isValidWinningNumbers = (array) => { |
There was a problem hiding this comment.
파라미터의 이름을 array로 쓰는것은 아무 정보를 전달하지 못하기 때문에 좀 더 의미 있는 이름을 써주시는게 좋습니다.
array가 중요한게 아니라 어떤 array인지가 중요합니다.
배열이라면 복수형으로 이름을 지어주시면 되고 이경우에는 winningNumbers의 유효성을 검사하는 것이기 때문에 array 대신 winningNumbers 가 더 적절하고, 함수 이름에 이미 winning 이 들어가 있으니 정보의 번복을 피하는 차원에서 numbers 정도로 해주셔도 괜찮습니다.
값이 만약 숫자가 아닌 문자열로 들어올 수도 있고 내부에서 문자면 숫자로 변환하는 등의 로직이 있을 때는 numbers 보다는 values 정도로 해주면 좀 더 미묘한 차이의 의도까지 전달 할 수 있습니다.
There was a problem hiding this comment.
3단계 기능이 추가되면서, 이 함수로 기존의 검증 대상이던 당첨 번호들 외에도 수동구매로 입력한 로또 번호들selfPurchaseLottoNumbers까지 검증을 하게 되었습니다.
함수의 이름은 isValidWinningNumbers에서 isValidNumbers로 변경했고, 파라미터의 이름도 Numbers외에 공통점을 아우르는 더 좋은 이름이 떠오르지 않네요...
여러 곳에서 사용하는 검증 함수의 경우 이렇게 의미가 생략되어도 괜찮을까요?
|
🧚🏼♀️🧚 레벨 1의 마지막 미션을 진행하고 있는 시점에 남겨보는 행운의 로또 미션 셀프 리뷰 🧚🏼♀️🧚 내가 생각했을 때 바보같지만 나눠보고 싶은 질문
그때는 몰랐지만 지금은 알 수 있는 것
그때도 몰랐고 아직도 잘 모르겠는 것
반복되고 있는 실수 및 습관이 있다면?
기존에 작성하던 방식에서 코드를 단순히 가져와서 사용한 경우는 없는지?
확실하게 말할 수 있는 발전한 1가지
다시 로또 미션 코드를 작성한다면 어떻게 작성할 것인가?
갈 길이 멀다 신세한탄... 🤦🏻♀️ |
안녕하세요. 리뷰어님 :)
@jum0 와 함께 로또 미션을 진행한 신세한탄입니다.
2단계 미션을 진행하며 궁금했던 사항을 정리해보았습니다.
2단계 데모페이지
vs code 형식으로 코드 보기
alert메시지를 상수 처리할 때, 메시지 내용을 기준으로 네이밍을 하는 것이 나은지, 검사하는 메서드를 기준으로 네이밍을 하는 것이 나은지 궁금합니다. 예를 들어, 다음과 같은 메서드가 있습니다.alert메시지가 “공백은 포함될 수 없습니다“인 경우, 메시지를 기준으로BLANK_IS_FORBIDDEN라고 하는 것이 나은가요, 메서드를 기준으로 BLANK_INCLUDED라고 하는 게 나은가요? 현업에서는 어떤 방식을 더 선호하고, 많이 사용하는지 궁금합니다.그럼 2단계 미션 피드백도 잘 부탁드립니다! 🧚🏼♀️🧚🏼♂️