Skip to content

Conversation

@suyeon1218
Copy link

@suyeon1218 suyeon1218 commented Jul 9, 2024

미션을 수행하며 어려웠던 점

1. 도메인 역할 분리

  • 결론부터 얘기하자면 TDD 를 기준으로 구현하지 못했습니다... . 먼저 평소 하던대로 코드를 짜본다음 TDD 로 구현하고 싶다 생각했는데 평소에 하던대로 코드를 작성하는 게 너무 오래 걸렸어요... ㅠ_ㅠ 이번주에 TDD를 꼭 적용시키겠습니다.
  • Lotto 도메인에 대해 얼마만큼의 역할을 줘야할지 고민을 했어요. 로또를 구매하는 행위, 로또를 당첨 로또와 비교하는 행위... 등등 애매한 것들이 많았고 나름대로 다음과 같이 분리했습니다. 그런데 이 방법 말고 좀 더 획기적이고 직관적인 방법이 있을 것 같은데... 라는 느낌을 지울 수가 없네요.
  • Lotto : 일반 로또 클래스, set 과 get 가능
  • WinLotto : 당첨 로또 클래스, set 과 get 가능, 일반 로또와 비교 가능
  • LottoRule : 로또의 최소 번호, 최대 번호, 기본 금액, 환전(?) 등등 로또를 구입할 때 기본 원칙이 되는 기능 & 상수 모음

2. 도메인을 관리하는 코드관리

  • main 파일에서 도메인과 뷰을 제어해주는 역할을 하고 있습니다.
  • 그런데 매번 input 에서 받아온 뒤 Domain 으로 넘겨주고, Domain 을 view 로 출력해주고... 하는 코드가 너무 길어진 것 같아요. 더 깔끔하게 Domain 과 view 를 중재할 수 있는 방법이 없을까에 대해 고민해봐야할 것 같습니다.

궁금한 점

  1. 유효성 검사 테스트 코드를 중복작성 해주는 편이 좋은가요? 예를 들어 다음과 같은 코드가 있을 때 setData 에서 유효성 검사에 대한 테스트 케이스도 작성하고, getError 에 대한 테스트 케이스도 따로 작성해야 하나요?
setData(data) {
  if (getError(data)) throw new Error('Error');

  this.data = data;
}

getError() {
// ... 
}
  1. input 값을 받아올 때 에러처리와 후처리에 대한 부분을 함수로 넘겨 View 클래스에서 처리하도록 시도해봤습니다. View 가 담당하는 역할을 늘린 것 같기도 한데 이전엔 main 폴더에서 while 이 수없이 많아지던 걸 View 로 옮겨버려서 깔끔해졌다는 생각도 들어요. 좋은 시도였을까요?

  2. utils 함수 내부의 파일 이름을 Array, Number, String... 과 같이 매개변수의 타입이 어떤 것이 들어오는지에 따라 작성했는데 이 방법이 그렇게 좋은 방법은 아니라는 생각이 들어요. 보통 util 함수 내부의 파일 이름은 어떻게 관리해야 직관적인가요?

리뷰받고 싶은 점

궁금한 점과 미션 수행을 하며 어려웠던 점에 대부분 다 작성해놓은 것 같습니다...! 이외에도 눈에 띄는 점이 있다면 리뷰 남겨주세요! 🙇‍♀️

Summary by CodeRabbit

  • New Features

    • Added configuration for ESLint and Prettier to ensure consistent code quality and styling.
    • Introduced View class for handling user interactions in the lotto application.
    • Implemented Lotto, LottoRule, and WinLotto classes to encapsulate lotto game logic and validation.
    • Added utility functions for array operations, random number generation, money formatting, and asynchronous input reading.
  • Tests

    • Included unit tests for Lotto, LottoRule, and WinLotto classes to ensure functionality and error handling.
  • Chores

    • Updated project dependencies in package.json.

suyeon1218 added 19 commits July 5, 2024 15:26
- accord: 비교할 두 배열을 받아서 일치하는 개수를 반환하는 함수
- generateRandomNumberArray: 배열의 개수를 받아서 그 개수만큼 랜덤 번호를 생성하는 함수
- LottoRule 의 RANK_BY_ACCORD 프로퍼티가 잘못 작성되어 있던 문제
- Lotto 가 LottoRule 을 상속받지 않도록 변경
- Lotto 의 static 이 아니라 LottoRule 의 static 을 참조하도록 변경
@suyeon1218 suyeon1218 self-assigned this Jul 9, 2024
@suyeon1218 suyeon1218 requested a review from JunilHwang July 9, 2024 07:01
Copy link

@seeyoujeong seeyoujeong left a comment

Choose a reason for hiding this comment

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

코드 잘 읽었습니다!
뭔가 여러가지 시도를 해보는게 보이는군요 ㅎㅎ


유효성 검사 테스트 코드를 중복작성 해주는 편이 좋은가요? 예를 들어 다음과 같은 코드가 있을 때 setData 에서 유효성 검사에 대한 테스트 케이스도 작성하고, getError 에 대한 테스트 케이스도 따로 작성해야 하나요?

getError에서 통과되면 setData에서 중복해서 테스트할 필요는 없을 것 같아요!
제가 본 책에서 코드도 부채라고 생각하면 좋다고 하더라구요!

utils 함수 내부의 파일 이름을 Array, Number, String... 과 같이 매개변수의 타입이 어떤 것이 들어오는지에 따라 작성했는데 이 방법이 그렇게 좋은 방법은 아니라는 생각이 들어요. 보통 util 함수 내부의 파일 이름은 어떻게 관리해야 직관적인가요?

흠... 저는 그냥 유틸 함수명으로 관리하는게 직관적인거 같아요!

Comment on lines +13 to +19
const set = new Set();

while (set.size < amount) {
set.add(generateRandomNumber(min, max));
}

return [...set].sort((a, b) => a - b);

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.

함수 자체는 랜덤한 번호를 반환하는 거고, 정렬이 된 채 반환되면 좋을 것 같다고 생각했어요. 정렬을 외부에서 해주는 것이 좋을까요? 🤔

Choose a reason for hiding this comment

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

유틸에 속해 있어서 최소한의 일만하면 좋다고 생각했어요! 그래서 정렬은 외부에서 해주면 좋지않을까 ㅎㅎ

Choose a reason for hiding this comment

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

오 직접 구현하셨군요! 👍

Comment on lines +10 to +18
set(lotto) {
const lottoError = Lotto.getLottoError(lotto);

if (lottoError !== undefined) {
throw new Error(lottoError);
}

this._lotto = lotto;
}

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.

  1. set메서드를 재사용하고 싶어서 입니다!
  2. 유효성 검사가 있으니 외부에서 함부로 변화시킬 수 없다고 생각해서 입니다!

Comment on lines +5 to +24
static RANK_BY_ACCORD = Object.freeze({
3: { bonus: 5, base: 5 },
4: { bonus: 4, base: 4 },
5: { bonus: 2, base: 3 },
6: { bonus: 1, base: 1 },
});
static REWARD = Object.freeze({
1: 200000000,
2: 30000000,
3: 1500000,
4: 50000,
5: 5000,
});
static ACCROD_STRING_BY_RANK = Object.freeze({
1: `6개 일치 ${changeStandardMoneyString(LottoRule.REWARD[1])}`,
2: `5개 일치, 보너스 볼 일치 ${changeStandardMoneyString(LottoRule.REWARD[2])}`,
3: `5개 일치 ${changeStandardMoneyString(LottoRule.REWARD[3])}`,
4: `4개 일치 ${changeStandardMoneyString(LottoRule.REWARD[4])}`,
5: `3개 일치 ${changeStandardMoneyString(LottoRule.REWARD[5])}`,
});

Choose a reason for hiding this comment

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

오! Object.freeze 저도 써야겠네요!

Comment on lines +26 to +40
static get MAX_NUMBER() {
return 45;
}

static get MIN_NUMBER() {
return 1;
}

static get MONEY() {
return 1000;
}

static get DEFAULT_LENGTH() {
return 6;
}

Choose a reason for hiding this comment

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

외부에서 수정 못하게 하기위한 방법이군요?!

Choose a reason for hiding this comment

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

rule에 정리해놓으니까 더 읽기 편할 것 같네요 👀

Copy link
Author

Choose a reason for hiding this comment

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

넵 맞습니다...! 인스턴스에 종속되지도 않고 값만 가져다 쓰는 거면 이런식으로 써볼 수 있을 것 같았어요...!

Comment on lines +26 to +40

get() {
return [...this._winLotto];
}

checkRank(compareLotto) {
const compareLottoError = Lotto.getLottoError(compareLotto);
const winLottoError = WinLotto.getLottoError(this._winLotto);

if (compareLottoError !== undefined || winLottoError !== undefined) {
return compareLottoError || winLottoError;
}

const accordCount = accord(
this.get().slice(0, LottoRule.DEFAULT_LENGTH),

Choose a reason for hiding this comment

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

get보다는 명확한 이름으로 바꾸는게 좋을 것 같아요!
읽다보니 이 get이 어떤걸 가져오지라는 의문이 들었거든요 ㅎㅎ 제가 길게 기억을 못해서 그런것도 있지만..

Copy link
Author

Choose a reason for hiding this comment

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

getLottogetNumber 등으로 명시하려고 노력해야겠네요!!

Comment on lines +28 to +38
const winLotto = new WinLotto([...winPureLotto, bonusNumber]);
const rankInfo = {
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
};
lottos.forEach((lotto) => {
rankInfo[winLotto.checkRank(lotto.get())] += 1;
});

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.

안 그래도 이번 main.js파일에서 코드를 모두 분리해보려고 했는데 별로 좋은 시도는 아니었던 것 같아요. 적절히 메서드로 분리해보겠습니다!

test('get 메서드는 현재 lotto 의 값을 가져올 수 있다.', () => {
const lotto = makeLottoMockingData(initialLotto);

expect(lotto.get()).toStrictEqual(initialLotto);

Choose a reason for hiding this comment

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

.toStrictEqual(initialLotto)에서 initialLotto 부분은 하드코딩하는게 책에서 읽기 좋다고 하더라구요!

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.

그런 것 같네요...! 아무래도 다른 테스트에서 값을 변경시킬 수 있을 위험이 있을 것 같아요.

Choose a reason for hiding this comment

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

직접 값을 적어주는거죠! 예를 들어, [1,2,3,4,5,6] 이렇게요!

Copy link

@hyoribogo hyoribogo left a comment

Choose a reason for hiding this comment

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

수연님 안녕하세요! 😸

이번 로또 과제에서 로직을 분리하고, 또 어떻게 그걸 결합해야 할지 너무 어려워서 며칠동안 헤매다 리뷰 먼저 하러 왔는데 .. 수연님도 같은 고민을 하고 계셨네요 ..!
그럼에도 완성시킨 게 정말 대단합니다.. 많이 참고할게요 !!


방금 코드를 다 읽었는데, 수연님이 고민하신 과정이 저와 너무 똑같아서 읽는 내내 공감이 되었어요 ....ㅋㅋㅋㅜㅜ 그리고 많은 해결 방향을 얻어가요. 감사합니다!

수연님의 궁금한 점은 동시에 제 궁금한 점이기 때문에 .. 답변은 하지 않겠습니다. 왜냐면 저도 궁금하기 때문입니다 😂 다른 분들 답변 참고하러 오겠습니다!

전체적으로 일관성 있게 작성되어 있어서 수연님의 코드 스타일을 단번에 파악하기 쉬웠어요! 이것 또한 나름대로 수연님의 클린 코드라고 볼 수 있겠네요 !! 너무너무 고생하셨습니다!

Comment on lines +97 to +111
static getPostProcessFnError(postProcessFn) {
if (postProcessFn !== undefined && typeof postProcessFn !== 'function') {
return '후처리 함수(postProcessFn)는 함수 타입이어야 합니다.';
}

return undefined;
}

static getErrorCheckFnError(errorCheckFn) {
if (errorCheckFn !== undefined && typeof errorCheckFn !== 'function') {
return '에러 검사 함수(errorCheckFn)는 함수 타입이어야 합니다.';
}

return undefined;
}

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.

이전에 도메인의 유효성 검사 함수를 외부에 꺼내 쓴 적이 있던 것 같아서 private은 적용시키지 않았어요...! 그런데 이렇게 할 바엔 아예 validator폴더를 만드는 방법이 좋을 것 같아서 4주차 코드엔 아예 따로 빼버렸답니다. ㅎㅎ..!

Comment on lines +41 to +66
async inputPurchaseLotto(
options = {
postProcessFn: undefined,
errorCheckFn: undefined,
},
) {
return this.input('> 구입금액을 입력해 주세요:', options);
}

async inputWinLotto(
options = {
postProcessFn: undefined,
errorCheckFn: undefined,
},
) {
return this.input('> 당첨 번호를 콤마(,)로 구분해 입력해 주세요:', options);
}

async inputBonusNumber(
options = {
postProcessFn: undefined,
errorCheckFn: undefined,
},
) {
return this.input('> 보너스 번호를 입력해 주세요: ', options);
}

Choose a reason for hiding this comment

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

요기도 options의 기본값을 설정하신 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

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

options가 undefined일 때의 처리를 하느냐 or 기본값을 넣어주느냐...! 를 고민했을 때 후자는 options에 어떤 값이 들어가는지 유추할 수 있다고 생각해서 기본값을 넣어주게 되었어요!

Choose a reason for hiding this comment

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

로또에 관련된 View니까 LottoView라고 명시하면 좋을 것 같아요!

Copy link

Choose a reason for hiding this comment

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

View 폴더만 대문자인 이유가 있을까요??

Copy link
Author

Choose a reason for hiding this comment

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

그러게요...!! 컨벤션 오류인것 같아요 수정하겠습니다!! 이름도 명시하기! 다음에 적용해볼게요!!

test('get 메서드는 현재 lotto 의 값을 가져올 수 있다.', () => {
const lotto = makeLottoMockingData(initialLotto);

expect(lotto.get()).toStrictEqual(initialLotto);

Choose a reason for hiding this comment

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

하드코딩이라는 게 테스트 내부에 바로 배열을 전달해 주는 건가요?!

])(
'getLottoError 메서드는 인수($lotto)가 배열이 아니면 undefined 를 반환하지 않는다.',
({ lotto }) => {
expect(Lotto.getLottoError(lotto)).toBeDefined();

Choose a reason for hiding this comment

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

오 toBeDefined 메서드 알아가요 !

Copy link
Author

Choose a reason for hiding this comment

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

헤헤..이건 제가 노션에 간단히 정리한 건데 도움이 될까 싶어 올려봐요!!

expect

assertion을 만드는 함수이다.

import { expect } from 'vitest'

const input = Math.sqrt(4)

expect(input).to.equal(2) // chai API
expect(input).toBe(2) // jest API

soft

expect와 유시하지만 테스트가 실패했을 때 터미널을 끝내지 않고 계속해서 테스트 실패를 체크해나간다. 이렇게 실패한 테스트 케이스는 테스트가 완료된 후 집계되어 나온다.

import { expect, test } from 'vitest'

test('expect.soft test', () => {
  expect.soft(1 + 1).toBe(3) 
  expect.soft(1 + 2).toBe(4) 
})

만일 soft를 사용하지 않은 expect가 있다면 해당 expect 구문에서 터미널이 종료되고, 이전까지의 실패개수들이 나타난다.

not

assertion을 부정하는 함수이다.

import { expect, test } from 'vitest'

const input = Math.sqrt(16) 

expect(input).toBe(4)
expect(input).not.toBe(2) 

toBe

assertion의 원시값이 동일하거나 객체가 같은 참조값을 가지는 경우 사용한다. 참조값이 같지 않지만 같은 구조를 가지고 있는지 판별하기 위해서는 toEqual 을 사용한다.

import { expect, test } from 'vitest'

const stock = {
  type: 'apples',
  count: 13,
}

test('stock has 13 apples', () => {
  expect(stock.type).toBe('apples')
  expect(stock.count).toBe(13)
})

test('stocks are the same', () => {
  const refStock = stock // 같은 참조값을 지닌다

  expect(stock).toBe(refStock)
})

toBeCloseTo

toBeCloseTo 는 float 형 숫자를 비교하기 위해 사용한다. numDigits 옵션은 소수 점 뒤 자릿수를 제거한다.

import { expect, test } from 'vitest'

test.fails('0.2 + 0.1 은 자바스크립트에서 0.3 이 아니다...!!', () => {
  expect(0.2 + 0.1).toBe(0.3) // 0.2 + 0.1 = 0.30000000000000004
})

test('소수점 5번째까지만 비교한다!', () => {
  // 0.2 + 0.1 is 0.30000 
  expect(0.2 + 0.1).toBeCloseTo(0.3, 5)
  // 50으로 주면 제거 되는 게 없다 0.30000000000000004
  expect(0.2 + 0.1).not.toBeCloseTo(0.3, 50)
})

toBeDefined

toBeDefinedundefined 가 아닌지 판별한다. 함수가 undefined 가 아닌 특정 값을 반환하는지 판별할 때 유용하다.

import { expect, test } from 'vitest'

function getApples() {
  return 3
}

test('함수가 어떤 값을 반환합니다.', () => {
  expect(getApples()).toBeDefined()
})

toBeUndefined

toBeDefined 와 반대되는 함수이다. 값이 undefined 라고 판별한다.

import { expect, test } from 'vitest'

function getApplesFromStock(stock: string) {
  if (stock === 'Bill')
    return 13
}

test('mary 에게 남은 재고는 없어요...', () => {
  expect(getApplesFromStock('Mary')).toBeUndefined();
})

toBeTruthy

값을 true 로 추정한다.

toBeFalsy

값을 false 로 추정한다.

toBeNull

값을 null 로 추정한다.

toBeNaN

isNaN 이 true, 즉 숫자가 아닌 값으로 추정한다.

toBeTypeOf

반환받은 값의 타입이 인자로 받은 타입과 일치한다고 추정한다.

import { expect, test } from 'vitest'

const actual = 'stock'

test('stock is type of string', () => {
  expect(actual).toBeTypeOf('string')
})

toBeInstanceOf

생성자로 생성한 인스턴스가 해당 클래스의 인스턴스인지 확인한다.

import { expect, test } from 'vitest'
import { Stocks } from './stocks.js'

const stocks = new Stocks()

test('stocks are instance of Stocks', () => {
  expect(stocks).toBeInstanceOf(Stocks)
})

toBeGreaterThan | toBeGreaterThanOrEqual

값이 인자로 받은 값보다 크다고/크거나 같다고 추정한다.

import { expect, test } from 'vitest'
import { getApples } from './stocks.js'

test('have more then 10 apples', () => {
  expect(getApples()).toBeGreaterThan(10)
})

toBeLessThan | toBeLessThanOrEqual

값이 인자로 받은 값보다 작다고/작거나 같다고 추정한다.

import { expect, test } from 'vitest'
import { getApples } from './stocks.js'

test('have less then 20 apples', () => {
  expect(getApples()).toBeLessThan(20)
})

toEqual

수신된 값의 참조값이 같거나, 실제 구조가 같은 경우(재귀적으로 호출하여 판단)를 증명한다.

import { expect, test } from 'vitest'

const stockBill = {
  type: 'apples',
  count: 13,
}

const stockMary = {
  type: 'apples',
  count: 13,
}

test('stocks have the same properties', () => {
  expect(stockBill).toEqual(stockMary)
})

test('stocks are not the same', () => {
  expect(stockBill).not.toBe(stockMary)
})

toStrictEqual

toEqual 과 비슷하지만 다음 부분에서 다르다.

  • undefined 키에 대해서도 검사를 진행한다.
    • .toStrictEqual 를 사용하면 {a: undefined, b: 2} 의 경우 {b: 2} 와 다르다.
    • [, 1] 과 [undefined, 1] 도 다르다.
  • 객체 유형에 대해 검사한다. 예를 들어 a, b 프로퍼티를 가진 객체와 a, b 프로퍼티를 가진 클래스는 다르다.
import { expect, test } from 'vitest'

class Stock {
  constructor(type) {
    this.type = type
  }
}

test('구조적으론 같으나 의미적으로 다르다.', () => {
  expect(new Stock('apples')).toEqual({ type: 'apples' })
  expect(new Stock('apples')).not.toStrictEqual({ type: 'apples' })
})

toContain

값이 배열 안에 있는지, 어떤 하위 문자열이 특정 문자열 안에 있는지 판별할 때 쓰인다. Vitest 1.0부터는 브라우저와 유사한 환경에서 테스트하는 경우 클래스가 ClassList 안에 포함돼 있는지, 혹은 엘리먼트가 다른 엘리먼트 안에 있는지를 판별할 수도 있다.

import { expect, test } from 'vitest'
import { getAllFruits } from './stocks.js'

test('the fruit list contains orange', () => {
  expect(getAllFruits()).toContain('orange')

  const element = document.querySelector('#el')
  expect(element.classList).toContain('flex')
  expect(document.querySelector('#wrapper')).toContain(element)
})

}).toThrow();
});

test('winLotto 가 위 조건을 모두 통과하면 에러를 반환하지 않는다.', () => {

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.

조건이 많은 경우엔 어떻게 작성해야할지 고민인데, 다음엔 다 적어봐야겠습니다!

({ lotto, rank }) => {
const winLotto = makeWinLottoMockingData(initialLotto);

expect(winLotto.checkRank(lotto)).toBe(rank);

Choose a reason for hiding this comment

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

헉 당첨 로또를 어떤 도메인에서 관리해야 할지 어려웠는데... 영감 받아갑니다 ❤️

Comment on lines +26 to +40
static get MAX_NUMBER() {
return 45;
}

static get MIN_NUMBER() {
return 1;
}

static get MONEY() {
return 1000;
}

static get DEFAULT_LENGTH() {
return 6;
}

Choose a reason for hiding this comment

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

rule에 정리해놓으니까 더 읽기 편할 것 같네요 👀

Choose a reason for hiding this comment

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

Lotto 클래스를 상속하고 있는 상태인데 set, get이 중복되이는 게 보이네요!

여기 코드 보니까 뭔가 더 간결하게 작성할 수 있을 것 같은 게
_lotto_winLottonumbers로 표현하면 어떨까요?
Lotto.numbers WinLotto.numbers 이렇게 나타낼 수 있게요 !

그리고 validation은 constructor에서 검사해주면 될 것 같아요.

constructor(lotto) {
  super(lotto);
  WinLotto.getLottoError(lotto);
}

물론 Lotto와 WinLotto에서 각각 다른 길이로 에러 검사를 하고 있는데, 길이도 매개변수로 받아서 처리하면 괜찮을 것 같기도..합니당.

이렇게 작성 하면 !!!!
WinLotto에서 get이랑 set 작성 부분을 지울 수 있을 것 같아요.
그냥 주저리주저리 의견이었습니다 ... 😊

Copy link
Author

Choose a reason for hiding this comment

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

저도 이 부분에 대해 고민을 했어요...! 원래는 #lotto와 같이 private 변수로 선언을 해준 상태였거든요...! 그리고 둘다 constructor에서 setLotto를 통해 #lotto에 접근하고 있는데, WinLotto에서 super()를 호출하면 Lotto의 #lotto에 접근하게 되는 에러가 발생하더라구요. 그래서 private으로 사용할 경우엔 서로 다른 이름을 지어줘야하는 경우도 있는 것 같아요...! set과 get의 경우는 일부러 만들어둔 것도 있지만 중복되는 부분을 줄이는 방법에 대해선 더 생각해볼게요!

Comment on lines +1 to +13
export function changeStandardMoneyString(money) {
const reversedMoney = String(money).split('').reverse();
const stack = [];

reversedMoney.forEach((num, index) => {
if (reversedMoney[index + 1] !== undefined && index % 3 === 0) {
stack.push(',');
}
stack.push(num);
});

return stack.reverse().join('');
}

Choose a reason for hiding this comment

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

오!! 이거 제가 참석 못했을 때 해결법은 이미 나왔던 것 같은데 그래도 답변 달아봅니다..!

new Intl.NumberFormat().format(money)

이렇게 한 줄로 표현할 수 있어용 ! :)

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.

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

테스트 코드에서 들었던 생각은 toBeDefined이 많이 쓰이고 있더라구요.

개인적인 생각으로 저같은 경우 테스트 코드에서 궁금한 부분은 어떻게 쓰일까? 값이 뭐가 오는거지? 어떤 경우에서 에러가 발생할까? 와 같은 경우인 것 같아요..!

근데 undefined 를 반환하지 않는다.는 크게 정보를 얻기에는 도움이 되지 않는다는 생각을 했습니다..! 🙇‍♂️

utils 함수 내부의 파일 이름, 매개변수의 타입

으음 사용하는 용도에 따라 다를 것 같긴 합니다..!
예를 들어 유틸 함수를 관리하는 라이브러리 경우 비슷한 폴더 구조를 띄는 것 같아요..!

근데 지금은 로또가 주 관심사라는 생각이 들어서 이에 맞게 적절한 폴더명으로 바뀌지 않을까 싶네요..!

Copy link

Choose a reason for hiding this comment

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

View 폴더만 대문자인 이유가 있을까요??

}).not.toThrow();
});

test('get 메서드는 현재 lotto 의 값을 가져올 수 있다.', () => {
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.

종길님 리뷰를 보고 나니 헷갈릴 수 있을만한 문구를 적지 않게 조심해야겠다는 생각이 드네요 👀

Comment on lines +15 to +17
while (set.size < amount) {
set.add(generateRandomNumber(min, max));
}
Copy link

Choose a reason for hiding this comment

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

1만 1억 번이 나온다면?! 🤪 (딴지 걸기)

Copy link
Author

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년차 프론트엔드 개발자를 위한 코드리뷰 멘토

주요 고려사항:

  1. 코드의 유연성과 확장성
    • 요구사항 변경 시 코드 수정의 용이성 평가
    • 새로운 기능 추가 시 필요한 변경사항 예측
  2. 프로그래밍 요구사항 준수
    • 프론트엔드 개발 best practices 적용 여부
    • 성능, 접근성, 보안 등 요구사항 충족 여부
  3. 단일 책임 원칙(SRP) 준수
    • 클래스/컴포넌트의 책임 범위 적절성 검토
    • 과도한 책임을 가진 요소 식별
  4. 모듈화 및 재사용성
    • 코드 분리 가능성 탐색
    • 재사용 가능한 컴포넌트/함수 제안

응답 형식:

  1. 코드 분석: 제시된 코드의 주요 특징 요약
  2. 강점: 잘 작성된 부분 언급
  3. 개선점: 위 고려사항을 바탕으로 개선이 필요한 부분 지적
  4. 구체적 제안: 코드 개선을 위한 실제적인 예시 또는 방향 제시
  5. 학습 포인트: 리뷰를 통해 배울 수 있는 주요 개념이나 기술 강조

추가 지침:

  • 건설적이고 교육적인 피드백 제공
  • 1년차 개발자 수준에 맞는 설명과 용어 사용
  • 필요시 관련 문서나 학습 자료 추천

1차 리뷰

코드 분석

  1. 구입 금액 입력: inputPurchaseLotto 함수로 사용자가 구입 금액을 입력받고, 이를 통해 로또 티켓 개수를 계산합니다.
  2. 로또 번호 생성: generateLottoNumber 함수를 사용하여 로또 번호를 생성하고, Lotto 클래스 인스턴스를 생성합니다.
  3. 당첨 번호 및 보너스 번호 입력: inputWinLottoinputBonusNumber 함수를 통해 당첨 번호와 보너스 번호를 입력받습니다.
  4. 당첨 결과 계산: checkRank 함수를 사용해 각 로또 티켓의 당첨 여부를 확인하고, 등수별로 개수를 집계합니다.
  5. 수익률 계산 및 출력: 총 당첨 금액을 구입 금액과 비교하여 수익률을 계산하고 출력합니다.

강점

  1. 모듈화: 로또 번호 생성, 입력 처리, 에러 검증 등이 잘 분리되어 있어 코드의 모듈화가 잘 되어 있습니다.
  2. 에러 처리: 사용자가 잘못된 값을 입력했을 때 에러 메시지를 출력하고, 해당 부분부터 다시 입력받도록 설계된 점이 좋습니다.
  3. 테스트 코드: 단위 테스트 코드가 존재하여 각 기능이 의도한 대로 동작하는지 검증할 수 있습니다.

개선점

  1. 함수의 들여쓰기 깊이: 일부 함수에서 들여쓰기 깊이가 2단계 이상으로 되어 있어 가독성이 떨어질 수 있습니다.
  2. 매개변수 수: 일부 함수의 매개변수 수가 2개를 초과합니다.
  3. 순수 함수 활용: 부수 효과가 있는 함수를 순수 함수로 분리하여 코드의 예측 가능성을 높일 수 있습니다.

구체적 제안

  1. 함수의 들여쓰기 깊이 줄이기:

    • 조건문이나 반복문을 함수로 분리하여 들여쓰기 깊이를 줄일 수 있습니다.
    async function main() {
      const view = new View();
      const money = await getValidInput(view.inputPurchaseLotto, LottoRule.getMoneyError);
      const quantity = LottoRule.calculateQuantity(money);
      const lottos = generateLottos(quantity);
    
      view.printPurchaseLotto(quantity);
      view.printAllLotto(lottos);
      const winLottoNumbers = await getValidInput(view.inputWinLotto, Lotto.getLottoError);
      const bonusNumber = await getValidInput(view.inputBonusNumber, WinLotto.getBonusNumberError);
      const winLotto = new WinLotto([...winLottoNumbers, bonusNumber]);
      const rankInfo = calculateRankInfo(lottos, winLotto);
    
      const totalMoney = calculateTotalMoney(rankInfo);
      view.printAccordByRank(rankInfo);
      view.printRate(totalMoney / money);
    }
    
    async function getValidInput(inputFunc, errorCheckFn) {
      const input = await inputFunc({
        postProcessFn: (input) => Number(input),
        errorCheckFn: (input) => errorCheckFn(input),
      });
      return input;
    }
    
    function generateLottos(quantity) {
      return new Array(quantity).fill(null).map(() => new Lotto(LottoRule.generateLottoNumber()));
    }
    
    function calculateRankInfo(lottos, winLotto) {
      const rankInfo = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
      lottos.forEach((lotto) => {
        rankInfo[winLotto.checkRank(lotto.get())] += 1;
      });
      return rankInfo;
    }
    
    function calculateTotalMoney(rankInfo) {
      return Object.keys(rankInfo).reduce((money, rank) => money + rankInfo[rank] * LottoRule.REWARD[rank], 0);
    }
  2. 매개변수 수 줄이기:

    • calculateRankInfo와 같은 함수에서 매개변수를 객체로 전달하도록 변경할 수 있습니다.
  3. 순수 함수 활용:

    • 부수 효과가 있는 부분을 순수 함수로 분리하여 재사용성을 높일 수 있습니다.

학습 포인트

  1. 모듈화: 기능별로 모듈화된 코드를 작성하여 유지보수성과 재사용성을 높이는 방법.
  2. 에러 처리: 사용자의 입력 오류를 처리하고, 이를 다시 입력받는 방식으로 사용자 경험을 개선하는 방법.
  3. 테스트 코드 작성: 각 기능별 단위 테스트를 통해 코드의 신뢰성을 높이는 방법.
  4. 순수 함수: 부수 효과를 최소화하여 예측 가능하고 테스트하기 쉬운 코드를 작성하는 방법.

2차 리뷰

기존 요구사항에 새로운 요구사항이 추가되거나 변경되었을 때 문제가 되는 부분이 있을까?
추가될 요구사항의 예시와 이에 따른 대응이 어떤식으로 이루어지게될지 피드백 부탁해.

기존 요구사항에 새로운 요구사항이 추가되거나 변경될 경우 문제가 될 수 있는 부분은 다음과 같습니다:

예시 요구사항 추가 및 변경

  1. 로또 구매 한도 추가: 사용자가 한 번에 구매할 수 있는 로또 개수를 최대 10개로 제한합니다.
  2. 로또 번호 유효성 검사 강화: 로또 번호는 1부터 45 사이의 숫자로 구성되어야 하며, 중복된 숫자가 없어야 합니다.
  3. 다양한 로또 게임 모드 추가: 사용자가 선택할 수 있는 다양한 게임 모드(예: 자동, 반자동, 수동)를 추가합니다.

각 변경 사항에 대한 피드백 및 대응 방안

1. 로또 구매 한도 추가

문제가 될 수 있는 부분:

  • inputPurchaseLotto 함수에서 구매 금액을 입력받아 로또 개수를 계산하는 로직이 있습니다. 이 부분에서 구매 개수를 제한하는 로직이 추가되어야 합니다.

대응 방안:

  • calculateQuantity 함수에서 최대 구매 한도를 검증하도록 변경합니다.
  • 한도 초과 시 에러 메시지를 출력하고 재입력 받는 로직을 추가합니다.
async function inputPurchaseLotto() {
  const money = await getValidInput(view.inputPurchaseLotto, LottoRule.getMoneyError);
  const quantity = LottoRule.calculateQuantity(money);
  if (quantity > 10) {
    throw new Error('최대 10개까지 구매할 수 있습니다.');
  }
  return quantity;
}

2. 로또 번호 유효성 검사 강화

문제가 될 수 있는 부분:

  • Lotto 클래스에서 로또 번호 생성 시 유효성 검사가 필요합니다.
  • 이미 존재하는 LottoRule.generateLottoNumber 함수에서 유효성 검사를 추가해야 합니다.

대응 방안:

  • generateLottoNumber 함수에 중복 숫자 검사를 추가합니다.
  • 1부터 45 사이의 숫자인지 검사하는 로직을 추가합니다.
class Lotto {
  constructor(numbers) {
    this.numbers = this.validateNumbers(numbers);
  }

  validateNumbers(numbers) {
    if (numbers.length !== 6 || new Set(numbers).size !== 6) {
      throw new Error('로또 번호는 6개의 서로 다른 숫자로 구성되어야 합니다.');
    }
    numbers.forEach(num => {
      if (num < 1 || num > 45) {
        throw new Error('로또 번호는 1부터 45 사이의 숫자여야 합니다.');
      }
    });
    return numbers;
  }
}

3. 다양한 로또 게임 모드 추가

문제가 될 수 있는 부분:

  • 게임 모드 선택에 따라 로또 번호 생성 방식을 달리해야 합니다.
  • 현재는 자동 생성 방식만 구현되어 있습니다.

대응 방안:

  • 사용자가 게임 모드를 선택할 수 있도록 입력을 받습니다.
  • 각 모드에 따른 로또 번호 생성 로직을 추가합니다.
async function selectGameMode() {
  const mode = await getValidInput(view.inputGameMode, LottoRule.getGameModeError);
  return mode;
}

function generateLottoNumbers(mode) {
  switch (mode) {
    case '자동':
      return LottoRule.generateLottoNumber();
    case '반자동':
      return LottoRule.generateSemiAutoLottoNumber();
    case '수동':
      return LottoRule.generateManualLottoNumber();
    default:
      throw new Error('잘못된 게임 모드입니다.');
  }
}

총평 및 학습 포인트

총평

기존 코드베이스는 모듈화와 유효성 검사, 에러 처리 등이 잘 이루어져 있어 유지보수성과 확장성이 높은 편입니다. 그러나 새로운 요구사항이 추가될 때, 코드의 일부를 수정하거나 확장해야 하는 경우가 있습니다. 이러한 경우를 대비하여 함수의 역할을 명확히 분리하고, 유연한 구조를 유지하는 것이 중요합니다.

학습 포인트

  1. 유효성 검사 강화: 로직이 복잡해질수록 유효성 검사를 강화하여 오류를 사전에 방지하는 것이 중요합니다.
  2. 확장 가능한 구조: 새로운 기능이 추가될 때 기존 코드를 크게 수정하지 않고도 쉽게 확장할 수 있는 구조를 유지해야 합니다.
  3. 에러 처리: 사용자 입력 오류에 대한 처리를 철저히 하여 사용자 경험을 개선할 수 있습니다.
  4. 테스트 코드 작성: 변경 사항이 발생할 때마다 테스트 코드를 통해 기존 기능이 정상적으로 동작하는지 확인하는 습관을 들여야 합니다.

@JunilHwang
Copy link
Contributor

@coderabbitai review please

@coderabbitai
Copy link

coderabbitai bot commented Jul 17, 2024

@JunilHwang, thank you for the detailed context and the previous review insights. Based on the information provided, here's my review:

Code Flexibility and Scalability

The current implementation shows a good level of modularization which aids in maintaining flexibility. However, as noted, certain areas could be improved to better handle potential requirement changes:

  • Lotto Purchase Limit: Introducing a limit on the number of Lotto tickets a user can purchase at once could be seamlessly integrated if the calculateQuantity function includes a check for maximum allowable tickets.
  • Enhanced Validity Checks for Lotto Numbers: The Lotto class could include more robust validation to ensure that all numbers are within the specified range and are unique, which will be crucial as the system scales.

Compliance with Programming Requirements

The adherence to frontend best practices such as error handling and input validation is commendable. However, the depth of function nesting and the number of parameters in some functions could be reduced to enhance readability and maintainability.

Single Responsibility Principle (SRP)

The separation of concerns appears to be well managed with distinct functionalities encapsulated within dedicated classes and functions. However, considering the Single Responsibility Principle, the main function could be refactored to further decouple the logic and streamline the flow of data between user inputs and system outputs.

Module Reuse and Reusability

The current structure facilitates reuse to some extent, especially with functions like generateLottoNumber. To enhance this, you could consider:

  • Abstracting Common Logic: Functions that handle input validations and transformations could be abstracted into more generic handlers that can be reused across different parts of the application.
  • Utility Functions: Refactoring utility functions to be more generic so they can handle a variety of data types and operations, which will be beneficial as the application grows.

Specific Suggestions

  1. Refactor main Function: Break down the main function into smaller, more focused functions that handle specific aspects of the application logic. This will not only improve readability but also enhance testability and maintainability.
  2. Parameter Reduction: Wherever possible, reduce the number of parameters passed to functions, possibly by using object deconstruction to pass parameters. This will make the function calls cleaner and the functions themselves easier to manage.

Learning Points

  • Modularization: Continue to enhance the modularization of the code to improve maintainability and scalability.
  • Error Handling and Validation: Maintain rigorous standards for input validation and error handling to ensure the application behaves predictably under all circumstances.
  • Test Coverage: Ensuring comprehensive test coverage will help in maintaining the reliability of the application as new features are added or existing features are modified.

Overall, the codebase exhibits a solid foundation with good practices in modularization and error handling. With a few refinements, particularly in managing function complexity and enhancing modularity, the application will be well-positioned for future expansion and maintenance.

Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Jul 17, 2024

Walkthrough

The recent updates introduce configuration files for ESLint and Prettier, enhancing code formatting and linting. New classes and methods for handling lotto operations and validations were added, along with extensive unit testing to ensure code robustness. Utility functions for array operations, number generation, string formatting, and asynchronous input reading were also introduced. These changes centralize lottery-related logic, enhance code quality, and streamline user interactions.

Changes

Files / Groups Change Summaries
.eslintrc, .prettierrc Introduced ESLint and Prettier configuration settings for code formatting and linting.
eslint.config.js Exported combined ESLint configuration settings.
package.json Updated type field and added dependencies for ESLint and Prettier.
src/View/View.js Added View class with methods for handling user input and printing lotto-related messages.
src/__tests__/Lotto.test.js Introduced unit tests for Lotto class.
src/__tests__/LottoRule.test.js Introduced unit tests for LottoRule class.
src/__tests__/WinLotto.test.js Introduced unit tests for WinLotto class.
src/domain/Lotto.js Added Lotto class for managing lotto numbers and validations.
src/domain/LottoRule.js Added LottoRule class encapsulating lotto game rules and logic.
src/domain/WinLotto.js Added WinLotto class extending Lotto with additional methods for handling win lotto logic and validations.
src/main.js Updated main function to handle interactions with View, LottoRule, Lotto, and WinLotto classes.
src/utils/Array.js Introduced utility functions accord and generateRandomNumberArray for array operations.
src/utils/Number.js Introduced generateRandomNumber function for generating random integers within a specified range.
src/utils/String.js Introduced changeStandardMoneyString function for formatting money strings with thousand separators.
src/utils/readline.js Introduced readLineAsync function for asynchronously reading input from the standard input.

Poem

In code we trust, our lines align,
With ESLint's might and Prettier's shine.
A lotto game, with numbers pure,
In tests, our logic will endure.
Utilities, like stars, they gleam,
Together, we build our dream.
🎉🐰✨


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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Outside diff range, codebase verification and nitpick comments (2)
src/__tests__/LottoRule.test.js (2)

4-12: Confirm range validation in generateLottoNumber method.

The test checks if the generated numbers are within the 1 to 45 range, which is a good practice. However, it might be beneficial to also confirm the length of the array to ensure exactly six numbers are generated, as expected for a Lotto draw.

    lotto.forEach((num) =>
      expect(num).greaterThanOrEqual(1).lessThanOrEqual(45),
    );
+   expect(lotto).toHaveLength(6);

24-34: Improve test description clarity for type validation in getMoneyError.

The test checks for the return of undefined when money is not of type number. The description "returns undefined when not a number type" could be clearer to explicitly state the expected behavior when invalid types are passed.

-    'getMoneyError 는 money($money)가 숫자 타입이 아닌 경우 undefined 를 반환하지 않는다.',
+    'getMoneyError should not return undefined when money is not of type number.',
Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between c602cd4 and 25ddc4b.

Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
Files selected for processing (16)
  • .eslintrc (1 hunks)
  • .prettierrc (1 hunks)
  • eslint.config.js (1 hunks)
  • package.json (2 hunks)
  • src/View/View.js (1 hunks)
  • src/tests/Lotto.test.js (1 hunks)
  • src/tests/LottoRule.test.js (1 hunks)
  • src/tests/WinLotto.test.js (1 hunks)
  • src/domain/Lotto.js (1 hunks)
  • src/domain/LottoRule.js (1 hunks)
  • src/domain/WinLotto.js (1 hunks)
  • src/main.js (1 hunks)
  • src/utils/Array.js (1 hunks)
  • src/utils/Number.js (1 hunks)
  • src/utils/String.js (1 hunks)
  • src/utils/readline.js (1 hunks)
Files not reviewed due to server errors (2)
  • src/tests/Lotto.test.js
  • src/tests/WinLotto.test.js
Files skipped from review due to trivial changes (2)
  • .eslintrc
  • .prettierrc
Additional comments not posted (23)
src/utils/Number.js (1)

1-3: Well-implemented utility function for generating random numbers.

The function generateRandomNumber correctly calculates a random number between the specified minimum and maximum values. It's a clean and efficient implementation for a commonly needed utility.

eslint.config.js (1)

1-9: Proper configuration for ESLint and Prettier integration.

The setup correctly integrates ESLint with Prettier, ensuring that linting and formatting are handled efficiently. The combination of global settings for both browser and node environments is a thoughtful addition, catering to cross-environment compatibility.

src/utils/Array.js (1)

3-10: Efficient implementation for calculating array intersections.

The function accord uses a Set to efficiently calculate how many elements from the compare array are present in the base array. This is a clean and efficient approach.

package.json (2)

16-21: Addition of ESLint and Prettier dependencies approved.

Adding these dependencies supports better code quality and consistency, which is beneficial for the project.


6-6: Query regarding the unchanged type field.

The type field is updated from "module" to "module", which seems redundant. Could you clarify if this change was intentional or necessary?

src/domain/WinLotto.js (3)

18-25: Error Handling in set Method

The method correctly checks for errors before setting the lotto numbers, which is good practice. However, consider improving the clarity of the error message in WinLotto.getLottoError to make it more specific about what went wrong.


50-64: Validation Logic in getLottoError

The method correctly checks for common errors and specific conditions for the winning lotto numbers. However, consider adding more detailed error messages to help developers understand the validation failures better.


66-78: Detailed Validation for getBonusNumberError

This method provides thorough validation for the bonus number, which is crucial for the integrity of the game logic. The detailed checks for number type and range are well-implemented.

src/View/View.js (6)

68-71: Good implementation of printPurchaseLotto.

The method is straightforward and effectively communicates the number of lottos purchased to the user.


73-77: Effective implementation of printAllLotto.

The method correctly iterates through the lottos and logs each one, providing clear output to the user.


93-95: Correct implementation of printRate.

The method accurately displays the rate of return, providing clear and concise information to the user.


97-111: Proper validation in static methods getPostProcessFnError and getErrorCheckFnError.

These methods correctly ensure that the provided functions are of the correct type, which is crucial for the robustness of input handling.


50-56: Review default parameter usage in inputWinLotto.

As with inputPurchaseLotto, ensure that the default parameters are handled correctly in the input method to prevent any unintended behavior.

Verification successful

Default parameters in inputWinLotto are handled correctly.

The input method properly extracts and utilizes the default parameters from the options object, ensuring no unintended behavior.

  • inputWinLotto method definition and usage are consistent.
  • input method handles default parameters as expected.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the handling of default parameters in `inputWinLotto`.

# Test: Search for usage of `inputWinLotto`. Expect: Proper handling of default parameters.
rg --type python -A 5 $'inputWinLotto'

Length of output: 68


Script:

#!/bin/bash
# Description: Check the handling of default parameters in `inputWinLotto`.

# Test: Search for the definition and usage of `inputWinLotto`.
rg --type js -A 5 'inputWinLotto'

# Test: Search for the definition of the `input` method to understand how it handles default parameters.
rg --type js -A 10 'input('

Length of output: 707


Script:

#!/bin/bash
# Description: Check the handling of default parameters in `inputWinLotto`.

# Test: Search for the definition and usage of `inputWinLotto`.
rg --type js -A 5 'inputWinLotto'

# Test: Search for the definition of the `input` method to understand how it handles default parameters.
rg --type js -A 10 'input\('

Length of output: 2253


59-65: Check default parameter handling in inputBonusNumber.

This method, like the others, uses undefined defaults which may lead to issues if not properly managed. Ensure robust handling in the input method.

src/domain/LottoRule.js (6)

5-17: Well-defined constants for lotto ranks and rewards.

Using Object.freeze ensures that these objects are immutable, which is appropriate for constant values.


18-24: Effective use of utility functions for formatting strings.

The changeStandardMoneyString function is used correctly to format monetary values, which enhances the readability of the constants.


26-40: Proper encapsulation of lotto configuration values.

The use of static getters for configuration values like MAX_NUMBER, MIN_NUMBER, etc., is a good practice as it encapsulates these values and allows for easy adjustments.


42-48: Correct implementation of lotto number generation.

The method generateLottoNumber effectively uses the generateRandomNumberArray utility with appropriate parameters to generate lotto numbers within the specified range.


50-57: Well-implemented method for calculating lotto ticket quantity.

The method calculateQuantity correctly calculates the number of tickets that can be bought and includes robust error handling to ensure that the input is valid.


60-89: Clear and correct implementation of validation methods.

The methods getMoneyError and getCommonLottoError provide comprehensive validation checks and return informative error messages, which enhances the robustness of the input handling.

src/__tests__/LottoRule.test.js (3)

13-22: Ensure clarity in test descriptions for calculateQuantity.

The test description uses template literals to dynamically insert values, which is good for readability. However, the description could be clearer about what the test is verifying, specifically that it's checking the division of money by a constant to determine the number of lottos purchasable.


36-48: Validate error handling for boundary values in getMoneyError.

The tests for boundary values (less than the minimum and exactly at the minimum) are well-implemented. It ensures that the function correctly identifies invalid monetary inputs, which is crucial for preventing incorrect lotto purchases.


64-76: Verify range and duplication checks in getCommonLottoError.

The tests for checking the range and duplication of lotto numbers are correctly implemented. It's crucial that these checks are thorough, as they prevent the acceptance of invalid lotto tickets.

Comment on lines +8 to +11
constructor(lotto) {
super(lotto);
this.set(lotto);
}
Copy link

Choose a reason for hiding this comment

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

Constructor Overhead and Redundancy in Initialization

The constructor calls super(lotto) and then immediately sets this.set(lotto). This duplicated handling of lotto might be unnecessary if the parent class Lotto already sets the lotto numbers correctly.

-    super(lotto);
-    this.set(lotto);
+    super(lotto);

Consider removing the this.set(lotto) if the parent class's constructor already performs the necessary initialization.

Committable suggestion was skipped due to low confidence.

Comment on lines +27 to +29
get() {
return [...this._winLotto];
}
Copy link

Choose a reason for hiding this comment

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

Ambiguous Getter Method Name

The get method name is too generic. It should be more descriptive, such as getNumbers or getWinLottoNumbers, to clarify what it retrieves.

-  get() {
+  getWinLottoNumbers() {

Renaming the method would enhance code readability and maintainability.

Committable suggestion was skipped due to low confidence.

Comment on lines +3 to +23
export function readLineAsync(query) {
return new Promise((resolve, reject) => {
if (arguments.length !== 1) {
reject(new Error('arguments must be 1'));
}

if (typeof query !== 'string') {
reject(new Error('query must be string'));
}

const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

rl.question(query, (input) => {
rl.close();
resolve(input);
});
});
}
Copy link

Choose a reason for hiding this comment

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

Well-implemented asynchronous read function with robust error handling.

The function correctly handles errors related to the number of arguments and the type of the query. Using promises for asynchronous operations is a good practice.

Suggestion: Simplify argument count check.

Instead of using arguments.length, you could use default parameters or rest parameters to handle unexpected numbers of arguments more gracefully.

- if (arguments.length !== 1) {
+ if (!query) { // Assuming `query` is always required and must be a string
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function readLineAsync(query) {
return new Promise((resolve, reject) => {
if (arguments.length !== 1) {
reject(new Error('arguments must be 1'));
}
if (typeof query !== 'string') {
reject(new Error('query must be string'));
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(query, (input) => {
rl.close();
resolve(input);
});
});
}
export function readLineAsync(query) {
return new Promise((resolve, reject) => {
if (!query) { // Assuming `query` is always required and must be a string
reject(new Error('arguments must be 1'));
}
if (typeof query !== 'string') {
reject(new Error('query must be string'));
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question(query, (input) => {
rl.close();
resolve(input);
});
});
}

Comment on lines +1 to +13
export function changeStandardMoneyString(money) {
const reversedMoney = String(money).split('').reverse();
const stack = [];

reversedMoney.forEach((num, index) => {
if (reversedMoney[index + 1] !== undefined && index % 3 === 0) {
stack.push(',');
}
stack.push(num);
});

return stack.reverse().join('');
}
Copy link

Choose a reason for hiding this comment

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

Consider using built-in JavaScript functions for formatting.

The function changeStandardMoneyString manually formats the number, which can be simplified using Intl.NumberFormat as suggested in a previous comment. This approach is more efficient and readable.

-	const reversedMoney = String(money).split('').reverse();
-	const stack = [];
-	reversedMoney.forEach((num, index) => {
-	  if (reversedMoney[index + 1] !== undefined && index % 3 === 0) {
-	    stack.push(',');
-	  }
-	  stack.push(num);
-	});
-	return stack.reverse().join('');
+	return new Intl.NumberFormat().format(money);
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function changeStandardMoneyString(money) {
const reversedMoney = String(money).split('').reverse();
const stack = [];
reversedMoney.forEach((num, index) => {
if (reversedMoney[index + 1] !== undefined && index % 3 === 0) {
stack.push(',');
}
stack.push(num);
});
return stack.reverse().join('');
}
export function changeStandardMoneyString(money) {
return new Intl.NumberFormat().format(money);
}

Comment on lines +31 to +48
checkRank(compareLotto) {
const compareLottoError = Lotto.getLottoError(compareLotto);
const winLottoError = WinLotto.getLottoError(this._winLotto);

if (compareLottoError !== undefined || winLottoError !== undefined) {
return compareLottoError || winLottoError;
}

const accordCount = accord(
this.get().slice(0, LottoRule.DEFAULT_LENGTH),
compareLotto,
);
const isBonusCorrect = compareLotto.includes(this._winLotto.at(-1));

return isBonusCorrect
? LottoRule.RANK_BY_ACCORD[accordCount].bonus
: LottoRule.RANK_BY_ACCORD[accordCount].base;
}
Copy link

Choose a reason for hiding this comment

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

Complexity in checkRank Method

This method is doing too much: it validates both lotto sets and calculates the rank. Consider splitting this into separate methods for single-responsibility and clearer error handling.

+  validateLottos(compareLotto) {
+    const errors = [
+      Lotto.getLottoError(compareLotto),
+      WinLotto.getLottoError(this._winLotto)
+    ].filter(error => error !== undefined);
+    if (errors.length > 0) return errors[0];
+  }

+  calculateRank(compareLotto) {
+    const accordCount = accord(this.getWinLottoNumbers().slice(0, LottoRule.DEFAULT_LENGTH), compareLotto);
+    const isBonusCorrect = compareLotto.includes(this._winLotto.at(-1));
+    return isBonusCorrect ? LottoRule.RANK_BY_ACCORD[accordCount].bonus : LottoRule.RANK_BY_ACCORD[accordCount].base;
+  }

-  checkRank(compareLotto) {
-    const compareLottoError = Lotto.getLottoError(compareLotto);
-    const winLottoError = WinLotto.getLottoError(this._winLotto);
-    if (compareLottoError !== undefined || winLottoError !== undefined) {
-      return compareLottoError || winLottoError;
-    }
-    const accordCount = accord(this.get().slice(0, LottoRule.DEFAULT_LENGTH), compareLotto);
-    const isBonusCorrect = compareLotto.includes(this._winLotto.at(-1));
-    return isBonusCorrect ? LottoRule.RANK_BY_ACCORD[accordCount].bonus : LottoRule.RANK_BY_ACCORD[accordCount].base;
-  }

Splitting the method improves clarity and separation of concerns.

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
checkRank(compareLotto) {
const compareLottoError = Lotto.getLottoError(compareLotto);
const winLottoError = WinLotto.getLottoError(this._winLotto);
if (compareLottoError !== undefined || winLottoError !== undefined) {
return compareLottoError || winLottoError;
}
const accordCount = accord(
this.get().slice(0, LottoRule.DEFAULT_LENGTH),
compareLotto,
);
const isBonusCorrect = compareLotto.includes(this._winLotto.at(-1));
return isBonusCorrect
? LottoRule.RANK_BY_ACCORD[accordCount].bonus
: LottoRule.RANK_BY_ACCORD[accordCount].base;
}
validateLottos(compareLotto) {
const errors = [
Lotto.getLottoError(compareLotto),
WinLotto.getLottoError(this._winLotto)
].filter(error => error !== undefined);
if (errors.length > 0) return errors[0];
}
calculateRank(compareLotto) {
const accordCount = accord(this.getWinLottoNumbers().slice(0, LottoRule.DEFAULT_LENGTH), compareLotto);
const isBonusCorrect = compareLotto.includes(this._winLotto.at(-1));
return isBonusCorrect ? LottoRule.RANK_BY_ACCORD[accordCount].bonus : LottoRule.RANK_BY_ACCORD[accordCount].base;
}
checkRank(compareLotto) {
const compareLottoError = Lotto.getLottoError(compareLotto);
const winLottoError = WinLotto.getLottoError(this._winLotto);
if (compareLottoError !== undefined || winLottoError !== undefined) {
return compareLottoError || winLottoError;
}
const accordCount = accord(this.get().slice(0, LottoRule.DEFAULT_LENGTH), compareLotto);
const isBonusCorrect = compareLotto.includes(this._winLotto.at(-1));
return isBonusCorrect ? LottoRule.RANK_BY_ACCORD[accordCount].bonus : LottoRule.RANK_BY_ACCORD[accordCount].base;
}

Comment on lines +6 to +45
async function main() {
const view = new View();
const money = await view.inputPurchaseLotto({
postProcessFn: (input) => Number(input),
errorCheckFn: (input) => LottoRule.getMoneyError(input),
});
const quantity = LottoRule.calculateQuantity(money);
const lottos = [...new Array(quantity)].map(
() => new Lotto(LottoRule.generateLottoNumber()),
);

view.printPurchaseLotto(quantity);
view.printAllLotto(lottos.map((lotto) => lotto.get()));

const winPureLotto = await view.inputWinLotto({
postProcessFn: (input) => input.split(',').map((el) => Number(el.trim())),
errorCheckFn: (input) => Lotto.getLottoError(input),
});
const bonusNumber = await view.inputBonusNumber({
postProcessFn: (input) => Number(input),
errorCheckFn: (input) => WinLotto.getBonusNumberError(input),
});
const winLotto = new WinLotto([...winPureLotto, bonusNumber]);
const rankInfo = {
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
};
lottos.forEach((lotto) => {
rankInfo[winLotto.checkRank(lotto.get())] += 1;
});
const totalMoney = Object.keys(rankInfo).reduce(
(money, rank) => money + rankInfo[rank] * LottoRule.REWARD[rank],
0,
);

view.printAccordByRank(rankInfo);
view.printRate(totalMoney / money);
Copy link

Choose a reason for hiding this comment

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

Good use of async functions and modular interaction patterns in main function.

The function effectively uses asynchronous operations and interacts with domain classes in a modular way. This enhances readability and maintainability.

Suggestion: Further modularize the main function.

Consider breaking down the main function into smaller, more focused functions to further enhance readability and maintainability.

- async function main() {
+ async function setupLottoPurchase(view) {
+   // code for setting up lotto purchase
+ }
+ async function processWinningLotto(view) {
+   // code for processing winning lotto
+ }
+ async function main() {
+   const view = new View();
+   await setupLottoPurchase(view);
+   await processWinningLotto(view);
+ }

Committable suggestion was skipped due to low confidence.

Comment on lines +9 to +38
async input(
message,
options = {
postProcessFn: undefined,
errorCheckFn: undefined,
},
) {
const { postProcessFn, errorCheckFn } = options;
const postProcessFnError = View.getPostProcessFnError(postProcessFn);
const errorCheckFnError = View.getErrorCheckFnError(errorCheckFn);

if (postProcessFnError !== undefined || errorCheckFnError !== undefined) {
throw new Error(postProcessFnError || errorCheckFnError);
}

while (true) {
const inputValue = await this.rl(message);
const postInputValue = postProcessFn
? postProcessFn(inputValue)
: inputValue;
const error = errorCheckFn
? errorCheckFn(postInputValue)
: postInputValue;

if (error === undefined) {
return postInputValue;
}

console.log(error);
}
Copy link

Choose a reason for hiding this comment

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

Consider refactoring the input method for clarity and efficiency.

The input method handles both input processing and error handling, which could be separated for better readability and adherence to SRP. Additionally, the loop control could be made more explicit to avoid potential infinite loops in edge cases.

Comment on lines +41 to +47
async inputPurchaseLotto(
options = {
postProcessFn: undefined,
errorCheckFn: undefined,
},
) {
return this.input('> 구입금액을 입력해 주세요:', options);
Copy link

Choose a reason for hiding this comment

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

Ensure consistent handling of default parameters in inputPurchaseLotto.

The method uses default parameters that are undefined, which might lead to unexpected behaviors if not handled properly in the input method. Consider providing meaningful defaults or ensuring that the input method can handle undefined values gracefully.

Comment on lines +80 to +91
printAccordByRank(rankInfo) {
const ranks = Object.keys(rankInfo);

console.log('당첨 통계');
console.log('--------------------');

ranks.forEach((rank) => {
console.log(
`${LottoRule.ACCROD_STRING_BY_RANK[rank]} - ${rankInfo[rank]}개`,
);
});
}
Copy link

Choose a reason for hiding this comment

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

Well-implemented printAccordByRank, consider enhancing rank handling.

The method effectively displays rank information. Consider using a more dynamic way to handle ranks to accommodate possible changes in ranking rules without modifying the code.

Comment on lines +50 to +62
test.each([
{ lotto: 1 },
{ lotto: '1' },
{ lotto: {} },
{ lotto: undefined },
{ lotto: null },
{ lotto: function () {} },
])(
'getCommonLottoError는 인자 lotto ($lotto) 가 배열 타입이 아닌 경우 undefined 를 반환하지 않는다.',
({ lotto }) => {
expect(LottoRule.getCommonLottoError(lotto)).toBeDefined();
},
);
Copy link

Choose a reason for hiding this comment

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

Enhance test descriptions for getCommonLottoError.

The test checks if the getCommonLottoError method correctly identifies non-array inputs. It's important to also verify the behavior when the input is an empty array or an array of incorrect length, which are common edge cases in lotto number validations.

-    'getCommonLottoError는 인자 lotto ($lotto) 가 배열 타입이 아닌 경우 undefined 를 반환하지 않는다.',
+    'getCommonLottoError should not return undefined when lotto is not a valid array type or is improperly structured.',
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test.each([
{ lotto: 1 },
{ lotto: '1' },
{ lotto: {} },
{ lotto: undefined },
{ lotto: null },
{ lotto: function () {} },
])(
'getCommonLottoError는 인자 lotto ($lotto) 가 배열 타입이 아닌 경우 undefined 를 반환하지 않는다.',
({ lotto }) => {
expect(LottoRule.getCommonLottoError(lotto)).toBeDefined();
},
);
test.each([
{ lotto: 1 },
{ lotto: '1' },
{ lotto: {} },
{ lotto: undefined },
{ lotto: null },
{ lotto: function () {} },
])(
'getCommonLottoError should not return undefined when lotto is not a valid array type or is improperly structured.',
({ lotto }) => {
expect(LottoRule.getCommonLottoError(lotto)).toBeDefined();
},
);

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.

5 participants