Skip to content

Conversation

@seeyoujeong
Copy link

미션을 수행하면서 어려웠던 점

  • 이름 짓기
    함수명과 클래스명을 짓느라 많은 고민을 했습니다... 역할도 잘 나타내면서 쉬운 단어를 선택하는건 참으로 어렵네요...

  • 당첨 번호와 보너스 번호 입력
    당첨 번호와 보너스 번호를 한번에 입력하는게 아닌 두번의 입력을 통해서 값을 받다보니, 유효성 검사 재활용과 에러 처리에 대한 고민을 많이 했습니다.

리뷰 받고 싶은 부분

  • createWinningLotto 함수
    두번의 입력을 통해 값을 받다보니 WinningLotto 클래스의 인수를 두번에 걸쳐서 전달해야했습니다. 그래서 WinningLotto 클래스의 생성자가 이상해졌네요. 커링 비스므리한 형태가 되어버렸습니다. 어떤 방식으로 수정하면 좋을까요?

  • MVC 패턴
    MVC 패턴으로 폴더 구조를 구성해봤습니다. 제대로 분리된게 맞을까요?

궁금한 점

  • mock을 어떻게 써야할지 감이 잡히질 않네요... 외부 의존성을 mock으로 대체하면 되는걸로 알고 있는데 이전, 현재 미션에서 readlineAsync를 mock으로 대체하는게 가장 정답에 가까울까요?

Comment on lines 7 to 10
class Lotto {
static MIN_NUMBER = 1;
static MAX_NUMBER = 45;
static NUMBERS_SIZE = 6;
Copy link
Contributor

Choose a reason for hiding this comment

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

LottoNumber 라는 친구를 만들어서 1~45에 대한 검사를 위임할 수 있을 것 같아요!

Comment on lines 33 to 35
static #createLottos(count) {
return Array.from({ length: count }).map(() => LottoMachine.#createLotto());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
static #createLottos(count) {
return Array.from({ length: count }).map(() => LottoMachine.#createLotto());
}
static #createLottos(count) {
return Array.from({ length: count }).map(LottoMachine.#createLotto);
}

아마 이렇게 표현해도 무방해보이네요!

Comment on lines 1 to 8
class LottoCalculator {
static #LOTTO_PRIZES = {
1: 2_000_000_000,
2: 30_000_000,
3: 1_500_000,
4: 50000,
5: 5000,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

"로또 계산기" 라는건 조금 어색해보여요!
LottoResult 처럼 표현하면 어떨까 싶기도..?

Comment on lines 22 to 30
this.#winningCounts = LottoCalculator.#updateWinningCounts({
winningCounts: this.#winningCounts,
lottos,
winningLotto,
});
this.#rateOfReturn = LottoCalculator.#calcRateOfReturn(
this.#winningCounts,
price
);
Copy link
Contributor

Choose a reason for hiding this comment

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

이렇게 두 개의 결과를 반환하고 있는데, 다르게 생각하면 두 개의 클래스로 분리할 수도 있지 않을까요!?

lotto.numbers,
winningLotto.numbers
),
isMatchedBonusNumber: lotto.numbers.includes(winningLotto.bonusNumber),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
isMatchedBonusNumber: lotto.numbers.includes(winningLotto.bonusNumber),
matchedBonusNumber: lotto.numbers.includes(winningLotto.bonusNumber),

이렇게만 표현해도 좋아보이네요!

Comment on lines 64 to 77
(counts, { matchedCount, isMatchedBonusNumber }) => {
const prizeMap = {
6: 1,
5: isMatchedBonusNumber ? 2 : 3,
4: 4,
3: 5,
};

if (prizeMap[matchedCount]) {
counts[prizeMap[matchedCount]] += 1;
}

return counts;
},
Copy link
Contributor

Choose a reason for hiding this comment

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

reduce에 쓰이는 콜백도 함수로 분리해주면 어떨까요!?

Comment on lines 44 to 58
static #getLottoNumbers() {
const lottoNumbers = [];
const addLottoNumber = (number) => {
const nextLottoNumbers = lottoNumbers.concat(number);
!isDuplicated(nextLottoNumbers) && lottoNumbers.push(number);
};

while (lottoNumbers.length < Lotto.NUMBERS_SIZE) {
const lottoNumber = LottoMachine.#getLottoNumber();

addLottoNumber(lottoNumber);
}

return lottoNumbers;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

2주차 회고시간에 이야기했던 shuffle 참고!

Comment on lines 68 to 75
static #validatePrice(price) {
validate.integer(price, "로또 구입 금액으로 정수를 입력해야 합니다.");

throwErrorWithCondition(
price < LottoMachine.#PRICE_PER_ONE,
`로또 구입 금액은 ${LottoMachine.#PRICE_PER_ONE}원이상이어야 합니다.`
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

도메인에서는 에러를 발생하고 이때 에러 코드 같은걸 얹어주고, 뷰에서 에러 코드를 기반으로 메세지를 만들어주도록 하면 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

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

도메인에서의 에러 메세지를 [ERR_001] LottoMachine 클래스의 생성자 인수는 정수여야 합니다. 이렇게 작성하고, 뷰에서 해당 에러 메세지의 에러 코드를 파싱해서 에러 코드에 해당하는 사용자가 이해하기 쉬운 메세지를 띄워주는 방식은 괜찮을까요?

Copy link
Contributor

Choose a reason for hiding this comment

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

그런 방법도 좋을 것 같아요 ㅋㅋ
가령 다국어 페이지를 만든다고 생각해보면 좋긴합니다!

Comment on lines 8 to 45
class WinningLotto extends Lotto {
#bonusNumber;

constructor(lottoNumbers, bonusNumber) {
super(lottoNumbers);

if (bonusNumber === undefined) {
return (bonusNumber) => new WinningLotto(lottoNumbers, bonusNumber);
}

WinningLotto.#validateBonusNumber(this.numbers, bonusNumber);

this.#bonusNumber = bonusNumber;
}

get bonusNumber() {
return this.#bonusNumber;
}

static #validateBonusNumber(winningNumbers, bonusNumber) {
validate.integer(bonusNumber, "보너스 번호는 정수여야 합니다.");

throwErrorWithCondition(
Lotto.isLessThanMinLottoNumber(bonusNumber),
`보너스 번호는 ${Lotto.MIN_NUMBER}이상이어야 합니다.`
);

throwErrorWithCondition(
Lotto.isGreaterThanMaxLottoNumber(bonusNumber),
`보너스 번호는 ${Lotto.MAX_NUMBER}이하여야 합니다.`
);

throwErrorWithCondition(
isDuplicated(winningNumbers.concat(bonusNumber)),
"당첨 번호 중에 보너스 번호와 중복되는 번호가 있습니다."
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

회고 시간에 이야기했던 내용 참고!

Comment on lines +7 to +16
export const validate = {
type(value, typeValue, errorMessage) {
throwErrorWithCondition(typeof value !== typeValue, errorMessage);
},
integer(value, errorMessage) {
throwErrorWithCondition(!Number.isInteger(value), errorMessage);
},
array(value, errorMessage) {
throwErrorWithCondition(!Array.isArray(value), errorMessage);
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Validators 같은 이름이 더 어울릴 것 같네요!

@JunilHwang
Copy link
Contributor

createWinningLotto 함수: 두번의 입력을 통해 값을 받다보니 WinningLotto 클래스의 인수를 두번에 걸쳐서 전달해야했습니다. 그래서 WinningLotto 클래스의 생성자가 이상해졌네요. 커링 비스므리한 형태가 되어버렸습니다. 어떤 방식으로 수정하면 좋을까요?

const createWinningLotto = async () => {
  const [winningNumbers, bonusNumber] = await Promise.all([
    inputManager.retryScan(
      "> 당첨 번호를 입력해 주세요. ",
      (inputValue) => inputValue.split(",").map((value) => Number(value.trim()))
    ),
    inputManager.retryScan(
      "> 보너스 번호를 입력해 주세요. ",
      (inputValue) => Number(inputValue)
    )
  ]);

  return new WinningLotto(winningNumbers, bonusNumber);
};

export default createWinningLotto;

이런 느낌은 어떨까요?

MVC 패턴으로 폴더 구조를 구성해봤습니다. 제대로 분리된게 맞을까요?

일단 역할별로 잘 구분된 것 같아요!
나중에 실제로 UI를 만들게 되면 Controller가 무척 비대해질 수 있답니다 ㅋㅋ

mock을 어떻게 써야할지 감이 잡히질 않네요... 외부 의존성을 mock으로 대체하면 되는걸로 알고 있는데 이전, 현재 미션에서 readlineAsync를 mock으로 대체하는게 가장 정답에 가까울까요?

readlineAsync 쪽은 굳이 신경쓰지 않아도 무방해보여요! 한다고 치면 이 구간을 목킹하는게 맞긴 합니다 ㅎㅎ
일단은 도메인에 대해 테스트 하는게 제일 중요!!

Copy link

@jgjgill jgjgill left a comment

Choose a reason for hiding this comment

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

수고 많으셨습니다..! 🙇‍♂️

이름 짓기

네넵.. 참 어려운 부분인 것 같습니다. 저는 이럴 때 그냥 한글로 합니다. 🧐
개인적으로는 영어로 했을 때는 와닿지 않는 단어를 한글로 하면 가독성을 굉장히 높여준다고 생각하는데 사람마다 호불호가 갈리는 것 같더라구요.. ex) 에러가 발생할 위험이 있다? 불안하다? 낯설다?

당첨 번호와 보너스 번호 입력

넵 이번 미션의 핵심이 '규칙을 얼마나 깔끔하게 구성하는가'로 보여지더라구요.
여기서 보너스 번호가 난이도를 높여주는 역할을 한 것 같습니다 😵

mock을 어떻게 써야할지

오오 제가 생각했을 때는 이번 미션에서는 로또가 해당되지 않을까 싶네요..!

(inputValue) => {
const bonusNumber = Number(inputValue);

return attachBonusNumber(bonusNumber);
Copy link

Choose a reason for hiding this comment

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

오오.. 이렇게 구현하셨군요..! 🧐

(counts, { matchedCount, isMatchedBonusNumber }) => {
const prizeMap = {
6: 1,
5: isMatchedBonusNumber ? 2 : 3,
Copy link

Choose a reason for hiding this comment

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

isMatchedBonusNumber 떄문에 reduce 안에서 prizeMap을 계속 생성하는게 아쉽게 느껴지네요..! 😂

Copy link
Author

Choose a reason for hiding this comment

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

step2에서 이부분을 수정했습니다 ㅎㅎ

static #calcRateOfReturn(winningCounts, price) {
const sumOfPrize = Object.entries({ ...winningCounts })
.map(([ranking, count]) => LottoCalculator.#LOTTO_PRIZES[ranking] * count)
.reduce((acc, cur) => acc + cur, 0);
Copy link

Choose a reason for hiding this comment

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

이미 아실 것 같지만 reduce에서 초기값이 0이면 생략도 가능하더라구요..! (근데 저도 안합니다.. 😇)

.reduce((acc, cur) => acc + cur)

Copy link
Author

Choose a reason for hiding this comment

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

아 그쵸 ㅎㅎ 저는 명시적으로 추가하는 편입니다..!


static #createLotto() {
const lottoNumbers = LottoMachine.#getLottoNumbers();
const sortedlottoNumbers = [...lottoNumbers].sort((a, b) => a - b);
Copy link

Choose a reason for hiding this comment

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

로또 번호도 정렬하네요! 필요한 부분이 있었나 보군요..! 🧐

Copy link
Author

Choose a reason for hiding this comment

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

실제 로또를 봤더니 정렬이 되어있어서 정렬을 해봤지만... 어디서 정렬을 해줄지 확신이 서지 않은 상태입니다 ㅎㅎ

Copy link

Choose a reason for hiding this comment

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

아 실제 로또는 정렬해주는군요.. 몰랐습니다 😅
그럼 하는게 맞네요 🙃

Copy link

Choose a reason for hiding this comment

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

이런 함수를 보면 괜히 망가트리고 싶더라구요.. (예외 케이스는 없나..) 🙃

@JunilHwang
Copy link
Contributor

AI가 리뷰해줬어요!


코드 분석

  1. 로또 구매:
    • inputPurchaseLotto 함수에서 사용자가 로또 구입 금액을 입력받아 로또 티켓 수를 계산합니다.
  2. 로또 번호 생성:
    • Lotto 클래스와 LottoRule을 이용해 로또 번호를 생성합니다.
  3. 당첨 번호 입력:
    • inputWinLottoinputBonusNumber 함수에서 당첨 번호와 보너스 번호를 입력받습니다.
  4. 당첨 여부 계산:
    • WinLotto 클래스에서 사용자의 로또 번호와 당첨 번호를 비교하여 당첨 여부를 계산합니다.
  5. 수익률 계산 및 출력:
    • calculateProfit 함수에서 총 수익률을 계산하고 결과를 출력합니다.
  6. 재시작/종료:
    • 당첨 통계 출력 후 재시작 여부를 입력받아 게임을 재시작하거나 종료합니다.

강점

  1. 모듈화:
    • 로또 번호 생성, 입력 처리, 당첨 결과 계산 등 각 기능이 모듈화되어 있어 코드의 가독성과 유지보수성이 높습니다.
  2. 유효성 검사:
    • 사용자의 입력값에 대해 철저한 유효성 검사를 수행하여 오류를 사전에 방지합니다.
  3. 테스트 코드:
    • 각 주요 기능에 대한 단위 테스트가 포함되어 있어 코드의 신뢰성을 높입니다.

개선점

  1. 코드 중복:
    • 일부 유효성 검사 로직이 중복되어 있습니다. 이를 통합하여 코드 중복을 줄일 수 있습니다.
  2. 확장성:
    • 새로운 기능 추가 시 수정해야 할 부분이 여러 곳에 분산되어 있어, 요구사항 변경에 유연하게 대응하기 어려울 수 있습니다.
  3. 매직 넘버:
    • 코드 내에 하드코딩된 숫자들이 있어 상수로 정의하여 사용하면 더 좋습니다.

구체적 제안

  1. 중복 코드 제거:

    • 유효성 검사 로직을 통합하여 중복 코드를 줄입니다.
    static getLottoError(lotto) {
        const commonError = LottoRule.getCommonLottoError(lotto);
        if (commonError !== undefined) {
            return commonError;
        }
        if (new Set(lotto).size !== LottoRule.DEFAULT_LENGTH) {
            return `로또 번호는 ${LottoRule.DEFAULT_LENGTH}개여야 합니다.`;
        }
        return undefined;
    }
  2. 확장성을 고려한 구조 개선:

    • 당첨 기준을 객체로 관리하여, 기준이 변경될 때 이 객체만 수정하면 되도록 구조를 개선합니다.
    const PRIZE_RULES = {
        FIRST: { match: 6, prize: 2000000000 },
        SECOND: { match: 5, bonus: true, prize: 30000000 },
        THIRD: { match: 5, prize: 1500000 },
        FOURTH: { match: 4, prize: 50000 },
        FIFTH: { match: 3, prize: 5000 },
    };

학습 포인트

  1. 단일 책임 원칙(SRP):
    • 각 클래스와 메서드는 하나의 책임만 가지도록 작성되었습니다. 이는 코드의 유지보수성과 확장성을 높입니다.
  2. 모듈화 및 재사용성:
    • 코드를 기능별로 모듈화하여 재사용성을 높이고, 필요한 경우 쉽게 수정할 수 있도록 작성되었습니다.
  3. 테스트 주도 개발(TDD):
    • 단위 테스트를 통해 코드의 신뢰성을 확보하고, 새로운 기능 추가 시 기존 기능이 잘 동작하는지 확인할 수 있습니다.

요구사항 변경 시 문제될 부분

  1. 당첨 기준 변경:
    • 기존 코드 구조는 당첨 기준이 하드코딩되어 있어, 변경 시 여러 부분을 수정해야 합니다.
  2. 로또 번호 범위 변경:
    • 로또 번호의 범위가 변경될 경우, 이를 처리하는 로직이 여러 곳에 분산되어 있어 수정이 어렵습니다.
  3. 새로운 기능 추가:
    • 예를 들어, 추가 보너스 게임 등의 기능이 추가될 경우, 현재 구조로는 큰 변경이 필요할 수 있습니다.

요구사항 변경 대응

새로운 요구사항 예시: "로또 번호의 범위를 145에서 150으로 변경하고, 당첨 기준에 새로운 등급 추가"

대응 방안:

  1. 로또 번호 범위 변경:

    • LottoRule 객체 내 MIN_NUMBER, MAX_NUMBER 값을 수정합니다.
    const LottoRule = {
        MIN_NUMBER: 1,
        MAX_NUMBER: 50,
        ...
    };
  2. 당첨 기준 변경:

    • 새로운 당첨 등급을 PRIZE_RULES 객체에 추가합니다.
    const PRIZE_RULES = {
        FIRST: { match: 6, prize: 2000000000 },
        SECOND: { match: 5, bonus: true, prize: 30000000 },
        THIRD: { match: 5, prize: 1500000 },
        FOURTH: { match: 4, prize: 50000 },
        FIFTH: { match: 3, prize: 5000 },
        SIXTH: { match: 2, prize: 1000 }, // 새로운 등급 추가
    };

이와 같이, 코드의 유연성과 확장성을 고려하여 설계하고, 변경 사항에 쉽게 대응할 수 있도록 작성하는 것이 중요합니다.

@coderabbitai
Copy link

coderabbitai bot commented Jul 23, 2024

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants