Skip to content

[1단계 - 행운의 로또 미션] 신세한탄(신세희) 미션 제출합니다#20

Merged
wow9144 merged 38 commits intowoowacourse:shinsehantanfrom
shinsehantan:shinsehantan
Feb 25, 2021
Merged

[1단계 - 행운의 로또 미션] 신세한탄(신세희) 미션 제출합니다#20
wow9144 merged 38 commits intowoowacourse:shinsehantanfrom
shinsehantan:shinsehantan

Conversation

@shinsehantan
Copy link

안녕하세요. 리뷰어님 :)
행운의 로또 미션 1단계 리뷰 요청드립니다.
로또 미션 데모 페이지입니다.

  1. 지난 미션 때, MVC 패턴에 대한 이해없이 맹목적으로 사용했기 때문에 이번 미션 때에는 필요하지 않다면 사용하지 말아야겠다 생각했지만, 알고있는 다른 디자인 패턴이 없고 초기에 패턴을 잡지 않고 시작하려니 그것 역시 막막하여 결국 MVC 패턴을 사용하게 되었습니다. 각 미션마다 적합한 디자인 패턴을 찾기 위해서는 역시 다른 사람들의 작업을 많이 찾아보면서 느끼는 방법밖에 없을까요?

  2. 페이지를 실행시키면 reciept-container가 처음부터 숨겨진 상태여야 하기 때문에 classList에 hidden을 추가함으로써 보여짐 여부를 컨트롤 하기로 했습니다. 그런데, index.js에 미리 주어져있던 onModalShow와 onModalClose 함수는 classList에 open을 추가하는 형태를 사용하고 있었습니다. 통일성을 위해서는 reciept-container 역시 open이나 show를 쓰면 좋겠지만 페이지가 로드 되자마자 show를 add하고, 바로 remove하는 작업이 다소 불필요하다고 느껴져서 통일성을 포기하기로 했는데, 잘 선택한 것이 맞을까요?

  3. 로또에서 생성된 번호가 1-45 범위 안에 있는지 확인하기 위해 클래스 메서드를 cypress에서 불러오려고 했는데 잘 되지 않았습니다. cypress 테스트 코드에서 클래스의 메서드를 사용할 수 없나요? (ex. Ticket.generateRandomNumbers)

  4. #ticket-image-number-container의 classList에 flex-col이 없는 상태에서 classList.remove('flex-col')을 했을 때 오류가 발생하지 않았습니다. 없는 클래스를 삭제하려고 할 때 오류가 나지 않은 것이 이상한데, 제가 놓친 부분이 있을까요?
    (없어도 오류는 나지 않지만, 논리적으로 이상한 것 같아 index.html에서 #ticket-image-number-container의 classList에 flex-col을 추가해둔 상태입니다.)

  5. 1000원을 의미하는 '1000'의 변수를 티켓 한 장의 가격의 의미로 쓰는 경우도 있고, 구매 시 지불해야하는 최소 금액의 의미로 쓰는 경우도 있는데, 이 경우에는 하나의 변수에 상수를 두 개 만드는 것이 맞을까요? (ONE_TICKET_PRICE: 1000, MIN_PURCHASE_PRICE: 1000)

페어와 함께 미션을 진행하며 궁금했던 부분을 정리해보았는데,
피드백과 함께 답변주시면 감사하겠습니다 :)

jum0 and others added 24 commits February 16, 2021 18:26
Copy link

@wow9144 wow9144 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요.
자세한 내용은 각 코멘트에 남겼으니 참고 부탁드립니다.

질답

  1. View 가 포함된 설계 패턴은 MVC, MVVM, MVP 등 다양합니다. 디자인 패턴은 이런 큰 틀의 설계 뿐만 아니라 객체와 객체의 의존 관계를 어떻게 풀어내는지, 객체 생성은 어떻게 하는지 등 보다 더 작은 코드를 작성 할 때의 패턴도 아우르는 고유명사 처럼 사용됩니다. 그래서 디자인 패턴은 찾아보는게 아니고 문제 해결을 보다 더 쉽게 하는 패턴 이기에 여러가지 문제 상황을 직접 경험하고 해결해나가는 시도가 우선입니다. 문제를 겪지 않고 문제를 해결하는 디자인 패턴부터 관심을 갖으면 공부를 해도 코드에 적용을 못해서 계속 겉돌고 까먹고 잘못 적용하고 하는 경우가 많으니 일단 뭔가를 만들어보고 요구사항이 바뀌는 상황에 많이 노출 되어야 합니다. 그럼 요구사항이 바뀌었을 때 '아 그 때 이렇게 코드를 작성하면 안되는것이었구나' 를 직접 느끼는 시간이 필요합니다. 그러니 말씀하신것처럼 많이 찾아보는것 보다는 직접 작성하고 완성하고 요구사항이 바뀌고 수정하고 같은 싸이클을 빠르게 도는게 더 좋습니다.

  2. 세부 코멘트에 따로 작성했습니다.

  3. cypress 를 사용해보지 않아서 제공하는 API가 있는지 여부는 잘 모르겠습니다. 다른 분들 코드를 참고하시거나 문서를 찾아보셔야 할 것 같습니다.

  4. 메서드에 어떤 요청을 했을 때 적절히 처리 하지 못하면 에러를 내뱉도록 만드는 경우도 있고 그냥 조용히 무시되도록 만드는 경우도 있습니다. 이 경우는 후자라고 생각하시면 됩니다. 돈이 없는데 로또를 달라고 하면 에러를 뱉어야 하지만 안보이는걸 안보이게 해달라고 하면 이미 안보인다고 에러를 내뱉기 보다는 조용히 무시하는게 메서드를 사용하는 사용자도 편합니다.

  5. 세부 코멘트에 따로 작성했습니다.



generateRandomNumbers() {
const randomNumbers = Array.from({ length: Number.LOTTO_MAX_NUMBER }, (_, i) => i + 1);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 시점의 이 변수는 1~45까지의 숫자를 가진 배열이기 때문에 randomNumbers는 적절한 이름이라고 보기 어렵습니다.
메서드 이름에서 generateRandomNumbers 로 이미 의도가 명확히 표현 되었기 때문에 내부에서는 그저 numbers 로 해도 충분 할 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generateRandomNumbers로 생성해서 별 생각없이 randomNumbers로 이름 지었는데, 생각해보니 이 시점에서는 이미 의미를 가진 배열이 되어서 numbers로 해도 괜찮을 것 같네요! 감사합니다.

ONE_TICKET_PRICE: 1000,
LOTTO_MAX_NUMBER: 45,
TICKET_NUMBER_LENGTH: 6,
MIN_PURCHASE_PRICE: 1000,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

질문 5에 대한 답변입니다.

최소 한게임만 구입 가능하기 때문에 이 경우에 굳이 MIN_PURCHASE_PRICE이 존재 할 필요는 없긴 합니다.
그러나 의미상 2개로 나누는게 필요할 경우가 생긴다면
MIN_PURCHASE_PRICE = this.ONE_TICKET_PRICE
하시면 하나의 값을 2개의 의미로 한 곳에서 관리 할 수 있습니다.
만약 최소 구입 가격이 변경 된다면 그 때 값을 직접 할당하여 서로 연관을 끊어주시면 됩니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저희는 constants.js를 클래스로 관리하고 있지 않아서
MIN_PURCHASE_PRICE = this.ONE_TICKET_PRICE를 사용하기는 어려웠습니다.
this를 쓰지 않고도 하나의 값을 2개의 의미로 관리할 수 있는 방법이 있을까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const ONE_TICKET_PRICE = 1000
export let STANDARD_NUMBER = {
  ONE_TICKET_PRICE: ONE_TICKET_PRICE,
  MIN_PURCHASE_PRICE: ONE_TICKET_PRICE
};

으로 하시면 될 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let을 사용하면 되는군요! 적용했습니다. 감사합니다 :)

src/js/index.js Outdated
class App {
constructor() {
this.intializeTickets();
onPurchaseResultHidden();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 경우라면 html/css 에서 디폴트로 아예 숨겨놓고, 보여줘야 할 때 js로 보여줘야 합니다.

질문 2번의 답변이 되기도 합니다.
화면을 조작 할 기능을 js에서 제공하고 있다고 하여 화면의 이 기능으로만 화면을 컨트롤 해야 할 필요는 없습니다.
어쨌든 화면은 최초의 상태를 가지고 그려질 것이니까요.
그래서 최초에 안보여야 하는것은 CSS를 이용하여 안보이는 상태로 그리면 됩니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네! 최초로 보여지는 상황에 맞추어 index.htmlhidden을 넣어두고 필요할 때 remove해서 보여주는 것으로 수정했습니다.
그리고 존재하지만, 보여지는 부분에서만 숨김 처리하는 visibility hidden보다 입력 전에 dom element가 노출되지 않게 하는 display none이 더 적합할 것 같아 cssdisplay none으로 변경하였습니다 :)

const onModalShow = () => {
$modal.classList.add('open')
}
class App {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App의 역할이 무엇인가 고민해보시고 그 역할에 맞는 일을 App 이 하고 있는지 검토해보시면 좋을 것 같습니다.
App은 전체 프로그램을 구동시키는 역할을 하기에 컨트롤러 뷰 모델을 생성하고 구동하는 역할이지 않을까 싶습니다.
그래서 컨트롤러에게 요청 해야 할 것은 요청해야 하는데 App 이 컨트롤러와 뷰의 일까지 직접하고 있는것으로 보입니다.
tickets 와 ticketCount 로 별도로 관리할 필요 없이 tickets 와 tickets.length 로 할 수 있을 것 같습니다.
그러나 이 상태들이 App이 알고 있어야 하는 정보는 아닌것으로 생각됩니다.
다른 옮길 더 적절한 곳이 있을지 고민해보시면 좋을 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App의 역할에 대해 다시 고민해보는 계기가 되었습니다.
App은 초기에 전체 프로그램을 구동시키는 역할만 할 수 있도록 기능을 덜어낸 뒤 Model의 구조 또한 함께 바꿔보았습니다.

} from "../View/receiptView.js";
import { onPurchaseResultShow } from "./viewController.js";

export const handlePurchaseAmountSubmit = () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이름에 비해 너무 많은 일을 하고 있는것 같습니다.
내부의 책임들을 적절히 분산시킬 필요가 있을 것 같습니다.
input에 입력한 금액을 다룬다는 이름이지만, 실제로는 로또를 발행하는 기능까지 이 안에 다 들어있는것으로 보입니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기존에 담당했던 입력한 금액에 대한 검증, 티켓 생성, 티켓 렌더 기능 중 일부를 분리하여
입력한 금액에 대한 검증, publishTickets(티켓을 생성하고 렌더)함수를 호출하는 일까지만 담당하도록 변경했습니다. :)

const ticketImageNumberContainer = $(Element.TICKET_IMAGE_NUMBER_CONTAINER);
let ticketImageNumberTemplate = "";

tickets.map((lotto) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array의 메서드인 forEach와 map 을 언제 어떻게 쓰는지 추가적인 학습이 필요할 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forEach 메서드는 단순히 반복문을 대체하기 위해 사용하는 함수이고, map 메서드는 요소값을 다른 값으로 매핑하여 새로운 배열을 생성하기 위해 사용하는 함수이므로 이 부분에서는 단순 반복을 위한 forEach 사용이 더 적절했던 것 같습니다.

Copy link

@wow9144 wow9144 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드가 diff 없이 거의 다 새로 만들어지고 주요 모델의 설계를 뒤흔드는 변화가 있는것으로 보여서 히스토리 파악이 힘들었습니다. ㅠㅜ 세부 코멘트 확인 부탁드립니다.

ONE_TICKET_PRICE: 1000,
LOTTO_MAX_NUMBER: 45,
TICKET_NUMBER_LENGTH: 6,
MIN_PURCHASE_PRICE: 1000,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const ONE_TICKET_PRICE = 1000
export let STANDARD_NUMBER = {
  ONE_TICKET_PRICE: ONE_TICKET_PRICE,
  MIN_PURCHASE_PRICE: ONE_TICKET_PRICE
};

으로 하시면 될 것 같습니다.

@@ -0,0 +1,20 @@
import { STANDARD_NUMBER } from "../Util/constants.js";

export default class Ticket {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용되지 않는 파일은 지우는게 좋습니다.
그리고 동일한 TicketBundle이라는 새파일을 만드시는것보다 이 파일을 수정하시는게 좋았을 것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ticket 모델 삭제를 했어야 하는데ㅠㅠㅠ 혼란드려서 죄송합니다.

this.ticketBundle = [];
}

makeTicketBundle(ticketLength) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이전 코드에서 Ticket이 등장하여 로또용지인가보다 하고 별 코멘트 없이 넘어갔었는데,
makeTicketBundle이 등장하면서 제가 잘못 생각한듯 느껴집니다. (객체를 생성해서 export 한것도)
모델이 이렇게 바뀌어 코드 전체가 바뀐듯 한 느낌인데 코드만 보고는 의도를 알기 힘들어서 의견을 먼저 여쭤봅니다.

  1. Ticket의 역할이 무엇인가요?
  2. Ticket이 ticketBundle을 가지고 있는것이 어떤 의도인가요? (Ticket이 여러개 있는것이 ticketBundle일것으로 생각되는데, 그렇다면 ticketBundle = [ticket, ticket, ticket..] 일텐데 ticket이 ticketBundle을 가지고 있음으로써 ticket이 무엇인지 모르겠습니다.)
  3. new Ticket()하여 하나의 객체를 export 하고 있는데, 하나만 존재해야 하는 이유는 무엇인가요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ticket 모델의 이름은 TicketBundle이 더 적절한 것으로 생각되어 TicketBundle로 수정했습니다. 동일한 의미인데 파일/클래스명이 TicketBundle이면 어색하지 않나 싶어서 두 이름을 다르게 만든 것이 혼란을 야기한 것 같습니다😢

  1. TicketBundle 클래스는 메서드로 각 티켓의 숫자를 만드는 generateRandomNumbers와 ticket의 묶음인 ticketBundle을 만드는 makeTicketBundle을 가지고 있으며, ticketBundle의 상태를 관리합니다.

  2. 이 부분은 이해하셨던 것이 맞는데 역시 네이밍 때문에 혼란을 드린 것 같습니다.

  3. TicketBundle은 ticket들의 묶음이라 한 게임당 1개만 필요하기 때문에 여러 곳에서 생성해서 사용하지 않아도 된다고 생각해 export default new TicketBundle()을 사용했습니다. 이것 역시 네이밍에서 비롯된 문제일 수 있겠네요ㅠㅠ

Copy link

@wow9144 wow9144 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

설명해주신 의도부분이 충분히 이해되었습니다. :)
2단계로 넘어가서 계속 이어가보시죠!

@wow9144 wow9144 merged commit bcfe3e9 into woowacourse:shinsehantan Feb 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants