-
Notifications
You must be signed in to change notification settings - Fork 188
[로또] 주유나 미션 제출합니다. #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
[로또] 주유나 미션 제출합니다. #154
Changes from all commits
8f05464
80cf7e5
1160ac4
8c2b3d3
87c9b9f
d438f68
0881663
081f18a
379eceb
75477a8
4676e41
85d88a6
a025804
739027a
e68d447
7890fa8
8b3e560
1aa4cd7
7ee3783
f26d1ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,66 @@ | ||
| # javascript-lotto-precourse | ||
|
|
||
| # 🎰 로또 | ||
|
|
||
| ## 📋 구현 기능 목록 | ||
|
|
||
| --- | ||
|
|
||
| ### 1️⃣ 로또 구입 금액 입력 | ||
|
|
||
| - [x] 금액 입력 받기 | ||
| - [x] 1,000원 단위인지 검증 | ||
| - [x] 1,000원 미만 또는 1,000으로 나누어떨어지지 않으면 `[ERROR]` 출력 후 **해당 입력부터 다시 받기** | ||
|
|
||
| --- | ||
|
|
||
| ### 2️⃣ 로또 발행 | ||
|
|
||
| - [x] 구입 금액 ÷ 1,000 = 발행 개수 계산 | ||
| - [x] 랜덤으로 로또 번호 생성 | ||
| - [x] 1개 로또는 중복 없는 6개 번호 | ||
| - [x] 번호는 **오름차순 정렬**해서 보관 | ||
| - [x] `N개를 구매했습니다.` 출력 후 각 로또 번호를 `[1, 2, 3, 4, 5, 6]` 형식으로 출력 | ||
|
|
||
| --- | ||
|
|
||
| ### 3️⃣ 당첨 번호 입력 | ||
|
|
||
| - [x] 당첨 번호 입력 받기 | ||
| - [x] 쉼표(,) 기준으로 6개인지 검증 | ||
| - [x] 각 숫자가 1~45 범위인지 검증 | ||
| - [x] 중복 숫자 없도록 검증 | ||
| - [x] 잘못되면 `[ERROR] ...` 출력 후 **당첨 번호부터 다시 받기** | ||
|
|
||
| --- | ||
|
|
||
| ### 4️⃣ 보너스 번호 입력 | ||
|
|
||
| - [x] 보너스 번호 입력 받기 | ||
| - [x] 1~45 범위 검증 | ||
| - [x] 당첨 번호와 **겹치지 않도록** 검증 | ||
| - [x] 잘못되면 `[ERROR] ...` 출력 후 **보너스 번호부터 다시 받기** | ||
|
|
||
| --- | ||
|
|
||
| ### 5️⃣ 당첨 통계 계산 | ||
|
|
||
| - [x] 사용자가 구매한 모든 로또와 당첨 번호를 비교 | ||
| - [x] 일치 개수에 따라 3개/4개/5개/5개+보너스/6개 집계 | ||
| - [x] 등수별 상금 합계 계산 | ||
|
|
||
| --- | ||
|
|
||
| ### 6️⃣ 수익률 계산 및 출력 | ||
|
|
||
| - [x] 총 상금 ÷ 구입 금액 × 100 | ||
| - [x] 소수점 둘째 자리에서 반올림해 **소수 첫째 자리까지만** 출력 (예: `62.5%`) | ||
| - [x] `총 수익률은 62.5%입니다.` 형식으로 출력 | ||
|
|
||
| --- | ||
|
|
||
| ### 7️⃣ 예외 처리 및 재입력 | ||
|
|
||
| - [x] 잘못된 값 입력 시 `[ERROR]`로 시작하는 메시지를 출력 | ||
| - [x] **예외를 던지되**(throw), `App`에서 잡아서 해당 단계만 다시 입력 | ||
| - [x] `process.exit()` 사용하지 않음 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,37 @@ | ||
| import { LOTTO_PRICE } from "./constants/constants.js"; | ||
| import InputHandler from "./InputHandler.js"; | ||
| import LottoService from "./LottoService.js"; | ||
| import { printNewLine } from "./utils/output.js"; | ||
|
|
||
| class App { | ||
| async run() {} | ||
| #inputHandler; | ||
| #lottoService; | ||
|
|
||
| constructor() { | ||
| this.#inputHandler = new InputHandler(); | ||
| this.#lottoService = new LottoService(); | ||
| } | ||
|
|
||
| async run() { | ||
| const purchaseAmount = await this.#inputHandler.readPurchaseAmount(); | ||
| const ticketCount = purchaseAmount / LOTTO_PRICE; | ||
| printNewLine(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. printNewLine 도 좋지만 |
||
|
|
||
| const tickets = this.#lottoService.publishLottos(ticketCount); | ||
| this.#lottoService.printTickets(tickets); | ||
|
|
||
| const winningNumbers = await this.#inputHandler.readWinningNumbers(); | ||
| const bonusNumber = await this.#inputHandler.readBonusNumber( | ||
| winningNumbers | ||
| ); | ||
|
|
||
| const stats = this.#lottoService.calculateStats( | ||
| tickets, | ||
| winningNumbers, | ||
| bonusNumber | ||
| ); | ||
| this.#lottoService.printStats(stats, purchaseAmount); | ||
| } | ||
|
Comment on lines
+15
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. run() 안에 있는 흐름을 메소드로 분리시켜서 아래와 같이 해보면 어떨까요? |
||
| } | ||
|
|
||
| export default App; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| import { Console } from "@woowacourse/mission-utils"; | ||
| import { | ||
| LOTTO_COUNT, | ||
| LOTTO_MAX, | ||
| LOTTO_MIN, | ||
| LOTTO_PRICE, | ||
| NUMBER_REGEX, | ||
| } from "./constants/constants.js"; | ||
| import { ERROR_MESSAGES, INPUT_MESSAGES } from "./constants/messages.js"; | ||
| import { printNewLine } from "./utils/output.js"; | ||
|
|
||
| class InputHandler { | ||
| async readPurchaseAmount() { | ||
| while (true) { | ||
| const input = await Console.readLineAsync(INPUT_MESSAGES.PURCHASE_AMOUNT); | ||
| try { | ||
| return this.#parsePurchaseAmount(input); | ||
| } catch (error) { | ||
| Console.print(error.message); | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+13
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. App.js에서 |
||
|
|
||
| #trimInput(input) { | ||
| return (input ?? "").trim(); | ||
| } | ||
|
|
||
| #parseNumber(input, errorMessage = ERROR_MESSAGES.LOTTO_NUMBER_OUT_OF_RANGE) { | ||
| if (!NUMBER_REGEX.test(input)) { | ||
| throw new Error(errorMessage); | ||
| } | ||
| return Number(input); | ||
| } | ||
|
|
||
| #parsePurchaseAmount(input) { | ||
| const trimmedInput = this.#trimInput(input); | ||
| const amount = this.#parseNumber( | ||
| trimmedInput, | ||
| ERROR_MESSAGES.PURCHASE_AMOUNT_NOT_NUMBER | ||
| ); | ||
|
|
||
| if (amount < LOTTO_PRICE) { | ||
| throw new Error(ERROR_MESSAGES.PURCHASE_AMOUNT_TOO_LOW); | ||
| } | ||
| if (amount % LOTTO_PRICE !== 0) { | ||
| throw new Error(ERROR_MESSAGES.PURCHASE_AMOUNT_NOT_DIVISIBLE); | ||
| } | ||
|
Comment on lines
+42
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이곳 말고도 에러 메시지와 로직 결합 완화가 필요해 보입니다! 각 검증 함수에서 에러 메시지를 직접 지정하고 있다. 메시지 상수는 좋지만, 파라미터를 통해 주입하는 대신 내부에서 하드코딩하는 부분이 많은듯 해요. 에러 유형별로 ValidationError 클래스를 따로 두거나, 메시지 맵을 한 곳에서 관리하면 코드 일관성이 더 좋아질 것 같습니다! |
||
| return amount; | ||
| } | ||
|
|
||
| async readWinningNumbers() { | ||
| printNewLine(); | ||
| while (true) { | ||
| const input = await Console.readLineAsync(INPUT_MESSAGES.WINNING_NUMBERS); | ||
| try { | ||
| return this.#parseWinningNumbers(input); | ||
| } catch (error) { | ||
| Console.print(error.message); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #parseWinningNumbers(input) { | ||
| const trimmedInput = this.#trimInput(input); | ||
| const parts = trimmedInput.split(",").map((value) => value.trim()); | ||
| if (parts.length !== LOTTO_COUNT) { | ||
| throw new Error(ERROR_MESSAGES.WINNING_NUMBERS_COUNT_INVALID); | ||
| } | ||
|
|
||
| const numbers = parts.map((numberString) => | ||
| this.#parseNumber(numberString) | ||
| ); | ||
| this.#validateLottoNumbers(numbers); | ||
|
|
||
| const unique = new Set(numbers); | ||
| if (unique.size !== LOTTO_COUNT) { | ||
| throw new Error(ERROR_MESSAGES.WINNING_NUMBERS_DUPLICATE); | ||
| } | ||
| return numbers; | ||
| } | ||
|
Comment on lines
+63
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #parseWinningNumbers가 입력 다듬기, 분리, 변환, 검증까지 모두 맡고 있는 것 같아요. #splitAndTrimInput(문자열 처리)과 #validateLottoNumbers(검증)를 분리해 단일 책임 원칙을 적용하면 각 함수의 목적이 더 명확해질듯 합니다! |
||
|
|
||
| #validateLottoNumbers(numbers) { | ||
| if ( | ||
| !numbers.every( | ||
| (number) => | ||
| Number.isInteger(number) && number >= LOTTO_MIN && number <= LOTTO_MAX | ||
| ) | ||
| ) { | ||
| throw new Error(ERROR_MESSAGES.LOTTO_NUMBER_OUT_OF_RANGE); | ||
| } | ||
| } | ||
|
|
||
| async readBonusNumber(winningNumbers) { | ||
| printNewLine(); | ||
| while (true) { | ||
| const input = await Console.readLineAsync(INPUT_MESSAGES.BONUS_NUMBER); | ||
| try { | ||
| return this.#parseBonusNumber(input, winningNumbers); | ||
| } catch (error) { | ||
| Console.print(error.message); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #parseBonusNumber(input, winningNumbers) { | ||
| const trimmedInput = this.#trimInput(input); | ||
| const bonus = this.#parseNumber(trimmedInput); | ||
| this.#validateLottoNumbers([bonus]); | ||
|
|
||
| if (winningNumbers.includes(bonus)) { | ||
| throw new Error(ERROR_MESSAGES.BONUS_NUMBER_DUPLICATE); | ||
| } | ||
| return bonus; | ||
| } | ||
| } | ||
|
|
||
| export default InputHandler; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
저도 다른 분 코드리뷰 하면서 배우게 된 내용인데
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,6 @@ | ||
| import { LOTTO_COUNT, LOTTO_MAX, LOTTO_MIN } from "./constants/constants.js"; | ||
| import { ERROR_MESSAGES } from "./constants/messages.js"; | ||
|
|
||
| class Lotto { | ||
| #numbers; | ||
|
|
||
|
|
@@ -7,12 +10,24 @@ class Lotto { | |
| } | ||
|
|
||
| #validate(numbers) { | ||
| if (numbers.length !== 6) { | ||
| throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); | ||
| if (numbers.length !== LOTTO_COUNT) { | ||
| throw new Error(ERROR_MESSAGES.LOTTO_NUMBERS_COUNT_INVALID); | ||
| } | ||
|
|
||
| if ( | ||
| !numbers.every( | ||
| (number) => | ||
| Number.isInteger(number) && number >= LOTTO_MIN && number <= LOTTO_MAX | ||
| ) | ||
| ) { | ||
| throw new Error(ERROR_MESSAGES.LOTTO_NUMBER_OUT_OF_RANGE); | ||
| } | ||
| } | ||
|
|
||
| // TODO: 추가 기능 구현 | ||
| const uniqueCount = new Set(numbers).size; | ||
| if (uniqueCount !== LOTTO_COUNT) { | ||
| throw new Error(ERROR_MESSAGES.LOTTO_NUMBERS_DUPLICATE); | ||
| } | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. validate 로직이 다소 가독성이 떨어지는것 같긴합니다! |
||
| } | ||
|
|
||
| export default Lotto; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
재입력에 대한 테스트 너무좋네요!