From 8955d7e543c343951d77a3cf590e2863ec75a12b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 13:20:01 +0900 Subject: [PATCH 01/30] =?UTF-8?q?docs(README):=20=EC=9A=94=EA=B5=AC?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=B6=84=EC=84=9D=20=EB=B0=8F=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 문제를 바탕으로 요구사항을 분석하고, 분석한 요구사항을 바탕으로 TODO를 작성하였습니다. --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/README.md b/README.md index b168a180..604d16c4 100644 --- a/README.md +++ b/README.md @@ -1 +1,91 @@ # javascript-planetlotto-precourse + +## 요구사항 분석 + +### 1. 구입 금액 입력 + +- "구입금액을 입력해 주세요." 라는 문구와 함께 입력받는다. + +#### 입력 요구사항 + +- 구입 금액은 500원 단위의 정수만 가능하다. +- 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받는다. + +### 2. 구매 내역 출력 + +- "${개수}개를 구매했습니다." 라는 문구와 함께 구입한 로또의 개수와 번호를 출력한다. +- 로또의 가격은 1개당 500원이며, 구매한 로또 개수는 구입금액 / 500 이다. +- 로또 번호는 1~30까지의 중복되지 않은 정수이다. +- 로또 번호는 오름차순으로 정렬해 보여준다. + +### 3. 당첨 번호 입력 + +- "당첨 번호를 입력해 주세요."라는 문구와 함께 당첨 번호를 입력받는다. + +#### 입력 요구사항 + +- 번호는 쉽표를 기준으로 구분한다. +- 번호는 1~30까지의 중복되지 않은 정수이다. +- 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받는다. + +e.g. + +``` +[ERROR] 로또 번호는 1부터 30 사이의 숫자여야 합니다. +``` + +### 4. 보너스 번호 입력 + +- "보너스 번호 번호를 입력해 주세요." 문구와 함께 보너스 번호를 입력받는다. + +#### 입력 요구사항 + +- 번호는 1~30까지의 중복되지 않은 정수이다. +- 이전에 입력한 당첨 번호와 중복될 수 없다. +- 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받는다. + +### 5. 당첨 통계 출력 + +- "당첨 통계\n---\n" 문구와 함께 당첨 통계를 출력한다. +- 당첨은 1등부터 5등까지 있으며, 당첨 기준과 금액은 아래와 같다. + - 1등: 5개 번호 일치 / 100,000,000원 + - 2등: 4개 번호 + 보너스 번호 일치 / 10,000,000원 + - 3등: 4개 번호 일치 / 1,500,000원 + - 4등: 3개 번호 일치 + 보너스 번호 일치 / 500,000원 + - 5등: 2개 번호 일치 + 보너스 번호 일치 / 5,000원 + +--- + +## 구현 사항 + +- [ ] 1. 구입 금액 입력 + - [ ] "구입금액을 입력해 주세요." 라는 문구와 함께 입력 받음 + - [ ] 구입 금액은 500원 단위의 정수만 가능 + - [ ] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 + +- [ ] 2. 구매 내역 출력 + - [ ] "${개수}개를 구매했습니다." 라는 문구와 함께 구입한 로또의 개수와 번호를 출력 + - [ ] 로또의 가격은 1개당 500원이며, 구매한 로또 개수는 `구입금액 / 500` + - [ ] 로또 번호는 1~30까지의 중복되지 않은 정수 + - [ ] 로또 번호는 오름차순으로 정렬 + +- [ ] 3. 당첨 번호 입력 + - [ ] "당첨 번호를 입력해 주세요."라는 문구와 함께 당첨 번호를 입력받는다. + - [ ] 번호는 쉽표를 기준으로 구분 + - [ ] 번호는 1~30까지의 중복되지 않은 정수 + - [ ] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 + +- [ ] 4. 보너스 번호 입력 + - [ ] "보너스 번호 번호를 입력해 주세요." 문구와 함께 보너스 번호를 입력받음 + - [ ] 번호는 1~30까지의 중복되지 않은 정수 + - [ ] 입력했던 당첨 번호와 중복되지 않음 + - [ ] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 + +- [ ] 5. 당첨 통계 출력 + - [ ] "당첨 통계\n---\n" 문구와 함께 당첨 통계를 출력 + - 당첨은 1등부터 5등까지 있으며, 당첨 기준과 금액은 아래와 같다. + - 1등: 5개 번호 일치 / 100,000,000원 + - 2등: 4개 번호 + 보너스 번호 일치 / 10,000,000원 + - 3등: 4개 번호 일치 / 1,500,000원 + - 4등: 3개 번호 일치 + 보너스 번호 일치 / 500,000원 + - 5등: 2개 번호 일치 + 보너스 번호 일치 / 5,000원 From ae76123304377cca32af93657d2e65d060766030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 13:37:26 +0900 Subject: [PATCH 02/30] =?UTF-8?q?test(ValidatorTest):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=EC=9D=98=20=EA=B8=88=EC=95=A1=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=9D=84=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 요구사항에 맞추어 Validator 객체의 checkIsValidAmount 테스트 코드를 작성하였습니다. - 구매 금액은 500원 단위의 양의정수여야한다. --- __tests__/ValidatorTest.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 __tests__/ValidatorTest.js diff --git a/__tests__/ValidatorTest.js b/__tests__/ValidatorTest.js new file mode 100644 index 00000000..b04e5f9a --- /dev/null +++ b/__tests__/ValidatorTest.js @@ -0,0 +1,18 @@ +import Validator from "../src/Validator.js"; + +describe("ValidatorTest", () => { + describe("checkIsValidAmount", () => { + describe("올바른 입력을 받으면 true를 반환한다. ", () => { + it("구매 금액은 500원 단위의 양의 정수여야한다.", () => { + const testcase = 500; + const result = Validator.checkIsValidAmount(testcase); + expect(result).toBeTruthy(); + }); + }); + describe("올바르지 않은 입력을 받으면 '[ERROR]' 로 시작하는 에러를 던진다.", () => { + it.each([123, -500])("%s 를 입력받으면 '[ERROR]' 로 시작하는 에러를 던진다.", (testcase) => { + expect(() => Validator.checkIsValidAmount(testcase)).toThrow("[ERROR]"); + }); + }); + }); +}); From a7ae5490c3ef2e41e9b9bce60926223feb6f3896 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 13:38:12 +0900 Subject: [PATCH 03/30] =?UTF-8?q?feat(Validator):=20=EA=B5=AC=EC=9E=85=20?= =?UTF-8?q?=EA=B8=88=EC=95=A1=EC=9D=80=20500=EC=9B=90=20=EB=8B=A8=EC=9C=84?= =?UTF-8?q?=EC=9D=98=20=EC=A0=95=EC=88=98=EB=A7=8C=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20checkIsValidAmount=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- src/Validator.js | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 src/Validator.js diff --git a/README.md b/README.md index 604d16c4..10bbd0dd 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,8 @@ e.g. ## 구현 사항 - [ ] 1. 구입 금액 입력 - - [ ] "구입금액을 입력해 주세요." 라는 문구와 함께 입력 받음 - - [ ] 구입 금액은 500원 단위의 정수만 가능 + - [x] "구입금액을 입력해 주세요." 라는 문구와 함께 입력 받음 + - [x] 구입 금액은 500원 단위의 정수만 가능 - [ ] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 - [ ] 2. 구매 내역 출력 diff --git a/src/Validator.js b/src/Validator.js new file mode 100644 index 00000000..560fffd4 --- /dev/null +++ b/src/Validator.js @@ -0,0 +1,15 @@ +// TODO: 상수 따로 관리하기 + +const ERROR_MESSAGE = "[ERROR]"; + +const Validator = { + checkIsValidAmount(amount) { + if (typeof amount !== "number") throw new Error(ERROR_MESSAGE); + if (amount % 500 !== 0) throw new Error(ERROR_MESSAGE); + if (amount < 0) throw new Error(ERROR_MESSAGE); + + return true; + }, +}; + +export default Validator; From 88a6df39ea861cd0cf8d375b14c93e14c1c3d25f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 13:43:48 +0900 Subject: [PATCH 04/30] =?UTF-8?q?fix(Validator):=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=EC=97=90=EC=84=9C=20[ERROR]=20?= =?UTF-8?q?=EC=A0=91=EB=91=90=EC=82=AC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OutputView의 printErrorMessage 메서드에서 접두사를 붙이고 있습니다. 이에 따라서 중복으로 메시지에 접두사를 붙이지 않도록 수정하였습니다. 변경된 코드에 맞추어 테스트 코드도 수정하였습니다. --- __tests__/ValidatorTest.js | 6 +++--- src/Validator.js | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/__tests__/ValidatorTest.js b/__tests__/ValidatorTest.js index b04e5f9a..eccfa520 100644 --- a/__tests__/ValidatorTest.js +++ b/__tests__/ValidatorTest.js @@ -9,9 +9,9 @@ describe("ValidatorTest", () => { expect(result).toBeTruthy(); }); }); - describe("올바르지 않은 입력을 받으면 '[ERROR]' 로 시작하는 에러를 던진다.", () => { - it.each([123, -500])("%s 를 입력받으면 '[ERROR]' 로 시작하는 에러를 던진다.", (testcase) => { - expect(() => Validator.checkIsValidAmount(testcase)).toThrow("[ERROR]"); + describe("올바르지 않은 입력을 받으면 에러를 던진다.", () => { + it.each([123, -500])("%s 를 입력받으면 에러를 던진다.", (testcase) => { + expect(() => Validator.checkIsValidAmount(testcase)).toThrow(); }); }); }); diff --git a/src/Validator.js b/src/Validator.js index 560fffd4..829dd0d1 100644 --- a/src/Validator.js +++ b/src/Validator.js @@ -1,12 +1,12 @@ // TODO: 상수 따로 관리하기 -const ERROR_MESSAGE = "[ERROR]"; +const AMOUNT_ERROR_MESSAGE = "구매 금액은 500원 단위로 입력해주세요."; const Validator = { checkIsValidAmount(amount) { - if (typeof amount !== "number") throw new Error(ERROR_MESSAGE); - if (amount % 500 !== 0) throw new Error(ERROR_MESSAGE); - if (amount < 0) throw new Error(ERROR_MESSAGE); + if (typeof amount !== "number") throw new Error(AMOUNT_ERROR_MESSAGE); + if (amount % 500 !== 0) throw new Error(AMOUNT_ERROR_MESSAGE); + if (amount < 0) throw new Error(AMOUNT_ERROR_MESSAGE); return true; }, From 6b881305c8560d85551851e371b911426009da75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 13:49:38 +0900 Subject: [PATCH 05/30] =?UTF-8?q?feat(utils):=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EA=B0=92=EC=9D=B4=20=EC=98=AC=EB=B0=94=EB=A5=B4=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EA=B2=BD=EC=9A=B0=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EB=8B=A4=EC=8B=9C=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=9D=84=20=EB=B0=9B=EB=8A=94=20=EC=9C=A0=ED=8B=B8=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 입력을 받는 모든 곳에서 에러 메시지 출력 및 다시 입력을 받는 기능이 수반됩니다. 이에 따라서 해당 기능을 추상화한 handleInputError를 구현하였습니다. handleInputError는 아래와 같이 구성되어있습니다. - 입력 로직 : 입력을 받는 비동기 함수. - validator : 입력 로직이 반환한 값을 넣어서 실행하는 함수 - parser : 입력받은 값을 변경해서 반환하는 함수 각 단계에서 에러가 잡히게 되면, 다시 해당 입력 로직이 실행되도록 하였습니다. --- README.md | 4 ++-- src/App.js | 12 +++++++++++- src/utils.js | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 src/utils.js diff --git a/README.md b/README.md index 10bbd0dd..3fbc238d 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,10 @@ e.g. ## 구현 사항 -- [ ] 1. 구입 금액 입력 +- [x] 1. 구입 금액 입력 - [x] "구입금액을 입력해 주세요." 라는 문구와 함께 입력 받음 - [x] 구입 금액은 500원 단위의 정수만 가능 - - [ ] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 + - [x] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 - [ ] 2. 구매 내역 출력 - [ ] "${개수}개를 구매했습니다." 라는 문구와 함께 구입한 로또의 개수와 번호를 출력 diff --git a/src/App.js b/src/App.js index 091aa0a5..c91b9a50 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,15 @@ +import { handleInputError } from "./utils.js"; +import Validator from "./Validator.js"; +import { InputView } from "./view.js"; + class App { - async run() {} + async run() { + // const amountInput = await InputView.askAmount(); + const amount = handleInputError({ + input: InputView.askAmount, + validator: Validator.checkIsValidAmount, + }); + } } export default App; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 00000000..c02e5582 --- /dev/null +++ b/src/utils.js @@ -0,0 +1,14 @@ +import { OutputView } from "./view.js"; + +export async function handleInputError({ input, validator, parser }) { + while (true) { + try { + const inputValue = await input(); + validator(inputValue); + if (parser) return parser(inputValue); + return inputValue; + } catch (e) { + OutputView.printErrorMessage(e.message); + } + } +} From bffdfa7dfda2f66d886911196a99ac97f2f2919e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 14:06:23 +0900 Subject: [PATCH 06/30] =?UTF-8?q?feat(constants):=20"${=EA=B0=9C=EC=88=98}?= =?UTF-8?q?=EA=B0=9C=EB=A5=BC=20=EA=B5=AC=EB=A7=A4=ED=96=88=EC=8A=B5?= =?UTF-8?q?=EB=8B=88=EB=8B=A4."=20=EB=9D=BC=EB=8A=94=20=EB=AC=B8=EA=B5=AC?= =?UTF-8?q?=EC=99=80=20=ED=95=A8=EA=BB=98=20=EA=B5=AC=EC=9E=85=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EB=98=90=EC=9D=98=20=EA=B0=9C=EC=88=98=EC=99=80=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=EB=A5=BC=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또 1개의 가격을 상수로서 관리하도록 구현 --- README.md | 4 ++-- src/App.js | 7 +++++-- src/constants.js | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 src/constants.js diff --git a/README.md b/README.md index 3fbc238d..a3a027d8 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ e.g. - [x] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 - [ ] 2. 구매 내역 출력 - - [ ] "${개수}개를 구매했습니다." 라는 문구와 함께 구입한 로또의 개수와 번호를 출력 - - [ ] 로또의 가격은 1개당 500원이며, 구매한 로또 개수는 `구입금액 / 500` + - [x] "${개수}개를 구매했습니다." 라는 문구와 함께 구입한 로또의 개수와 번호를 출력 + - [x] 로또의 가격은 1개당 500원이며, 구매한 로또 개수는 `구입금액 / 500` - [ ] 로또 번호는 1~30까지의 중복되지 않은 정수 - [ ] 로또 번호는 오름차순으로 정렬 diff --git a/src/App.js b/src/App.js index c91b9a50..5436b763 100644 --- a/src/App.js +++ b/src/App.js @@ -1,14 +1,17 @@ +import { LOTTO_COST } from "./constants.js"; import { handleInputError } from "./utils.js"; import Validator from "./Validator.js"; -import { InputView } from "./view.js"; +import { InputView, OutputView } from "./view.js"; class App { async run() { - // const amountInput = await InputView.askAmount(); const amount = handleInputError({ input: InputView.askAmount, validator: Validator.checkIsValidAmount, }); + + const purchasedLottosCount = amount / LOTTO_COST; + OutputView.printPurchasedLottos(purchasedLottosCount); } } diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 00000000..6363f509 --- /dev/null +++ b/src/constants.js @@ -0,0 +1 @@ +export const LOTTO_COST = 500; From 67d9f80b8e9450d33de453d108a99a41467e3084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 14:20:19 +0900 Subject: [PATCH 07/30] =?UTF-8?q?test(LottoTest):=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또의 가격은 500원 이어야 한다. 이는 정적 멤버여야한다. - 로또는 생성자를 통해 발행 가능하다. - 생성자에는 올바른 번호 6자리를 입력받아야 한다. - 로또 번호의 범위는 1 ~ 30 이다. - 생성한 로또의 번호는 getLottoNumbers 를 통하여 가져올 수 있어야 한다. --- __tests__/LottoTest.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 __tests__/LottoTest.js diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js new file mode 100644 index 00000000..3335ce60 --- /dev/null +++ b/__tests__/LottoTest.js @@ -0,0 +1,30 @@ +import Lotto from "../src/Lotto.js"; + +describe("LottoTest", () => { + it("로또의 가격은 500원이다.", () => { + const cost = Lotto.cost; + expect(cost).toBe(500); + }); + + describe("생성자에 수를 입력하여 로또를 발행한다.", () => { + it("6자리의 중복되지 않은 수를 입력해야한다.", () => { + const numbers = [1, 2, 3, 4, 5, 5]; + expect(() => new Lotto(numbers)).toThrow(); + }); + it("수의 범위는 1~30이다. 범위를 넘으면 에러를 던진다.", () => { + expect(() => new Lotto([-1, 2, 3, 4, 5, 6])).toThrow(); + expect(() => new Lotto([1, 2, 3, 4, 5, 31])).toThrow(); + }); + it("6자리 수를 입력받이 않으면 에러를 던진다.", () => { + const numbers = [1, 2, 3, 4, 5]; + expect(() => new Lotto(numbers)).toThrow(); + }); + }); + describe("getLottoNumbers", () => { + it("발행한 로또의 번호를 가져온다.", () => { + const numbers = [1, 2, 3, 4, 5, 6]; + const lotto = new Lotto(numbers); + expect(lotto.getLottoNumbers()).toEqual([1, 2, 3, 4, 5, 6]); + }); + }); +}); From 3ee556ca159b371d0cc5cee3c013f846862f80c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 14:20:41 +0900 Subject: [PATCH 08/30] =?UTF-8?q?feat(Lotto):=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=97=90=20=EB=A7=9E=EC=B6=94=EC=96=B4=20Lotto=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Lotto.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/Lotto.js diff --git a/src/Lotto.js b/src/Lotto.js new file mode 100644 index 00000000..913f7c6e --- /dev/null +++ b/src/Lotto.js @@ -0,0 +1,30 @@ +// TODO:에러 메시지 상수화 하기 + +class Lotto { + #numbers; + + static cost = 500; + + constructor(numbers) { + this.#validate(numbers); + this.#numbers = numbers; + } + + #validate(numbers) { + if (numbers.length !== 6) throw new Error("로또 번호는 6개여야 합니다."); + + const set = new Set(); + numbers.forEach((n) => { + if (n > 30 || n < 1) throw new Error("로또 번호의 범위는 1 ~ 30까지 입니다."); + set.add(n); + }); + if (set.size !== numbers.length) throw new Error("로또 번호는 중복될 수 없습니다."); + } + + // TODO: 단순 가져오기보다는 일을 할 수 있도록 리팩토링 필요 + getLottoNumbers() { + return this.#numbers; + } +} + +export default Lotto; From 4539f5d6f0cd08fc9bb319229fc3d2e3fdb6aa17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 14:28:31 +0900 Subject: [PATCH 09/30] =?UTF-8?q?feat(utils):=20=EB=9E=9C=EB=8D=A4?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EB=98=90=20=EB=B2=88=ED=98=B8=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20=EC=9C=A0=ED=8B=B8=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 외부 의존성이다보니 해당 로직을 따로 빼서 사용하는 곳에서 테스트가 쉽도록 주입하게 하려했습니다. --- src/utils.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils.js b/src/utils.js index c02e5582..bfde3104 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,4 @@ +import { Random } from "@woowacourse/mission-utils"; import { OutputView } from "./view.js"; export async function handleInputError({ input, validator, parser }) { @@ -12,3 +13,7 @@ export async function handleInputError({ input, validator, parser }) { } } } + +export function getRandomLottoNumbers() { + return Random.pickUniqueNumbersInRange(1, 30, 5); +} From 3122a8fb1b9536ede1f782511a7fb6530f57b58d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 14:45:52 +0900 Subject: [PATCH 10/30] =?UTF-8?q?fix(Lotto):=20=EB=A1=9C=EB=98=90=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=EB=A5=BC=206=EA=B0=9C=EB=A1=9C=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=EB=B0=9B=EC=9D=84=20=EC=88=98=20=EC=9E=88=EB=8D=98?= =?UTF-8?q?=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 요구사항에 맞추어 5개만 입력받을 수 있도록 수정하였습니다. --- __tests__/LottoTest.js | 12 ++++++------ src/Lotto.js | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 3335ce60..57f691c1 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -12,19 +12,19 @@ describe("LottoTest", () => { expect(() => new Lotto(numbers)).toThrow(); }); it("수의 범위는 1~30이다. 범위를 넘으면 에러를 던진다.", () => { - expect(() => new Lotto([-1, 2, 3, 4, 5, 6])).toThrow(); - expect(() => new Lotto([1, 2, 3, 4, 5, 31])).toThrow(); + expect(() => new Lotto([-1, 2, 3, 4, 5])).toThrow(); + expect(() => new Lotto([1, 2, 3, 4, 31])).toThrow(); }); - it("6자리 수를 입력받이 않으면 에러를 던진다.", () => { - const numbers = [1, 2, 3, 4, 5]; + it("5자리 수를 입력받이 않으면 에러를 던진다.", () => { + const numbers = [1, 2, 3, 4, 5, 6]; expect(() => new Lotto(numbers)).toThrow(); }); }); describe("getLottoNumbers", () => { it("발행한 로또의 번호를 가져온다.", () => { - const numbers = [1, 2, 3, 4, 5, 6]; + const numbers = [1, 3, 4, 5, 6]; const lotto = new Lotto(numbers); - expect(lotto.getLottoNumbers()).toEqual([1, 2, 3, 4, 5, 6]); + expect(lotto.getLottoNumbers()).toEqual([1, 3, 4, 5, 6]); }); }); }); diff --git a/src/Lotto.js b/src/Lotto.js index 913f7c6e..d81dd0b5 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -11,7 +11,7 @@ class Lotto { } #validate(numbers) { - if (numbers.length !== 6) throw new Error("로또 번호는 6개여야 합니다."); + if (numbers.length !== 5) throw new Error("로또 번호는 5개여야 합니다."); const set = new Set(); numbers.forEach((n) => { From 2918ee02f997a8201c4639857b55e3f49af28081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 14:51:55 +0900 Subject: [PATCH 11/30] =?UTF-8?q?test(LottoDeviceTest):=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EB=B0=9B=EC=9D=80=20=EA=B0=80=EA=B2=A9=EC=9D=84=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=20=EA=B5=AC=EB=A7=A4=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EB=98=90=20=EA=B0=9C=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 특정 객체에 종속되지 않는 것이 좋아보여 static으로 구현하였습니다. --- __tests__/LottoDeviceTest.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 __tests__/LottoDeviceTest.js diff --git a/__tests__/LottoDeviceTest.js b/__tests__/LottoDeviceTest.js new file mode 100644 index 00000000..43d3af93 --- /dev/null +++ b/__tests__/LottoDeviceTest.js @@ -0,0 +1,17 @@ +import LottoDevice from "../src/LottoDevice.js"; + +describe("LottoDeviceTest", () => { + let lottoDevice; + + beforeEach(() => { + lottoDevice = new LottoDevice(); + }); + + describe("getCanIssueAmount", () => { + it("입력받은 가격을 통해 구매 가능한 로또 개수를 반환한다.", () => { + const amount = 1000; + const result = LottoDevice.getCanIssueAmount(amount); + expect(result).toBe(2); + }); + }); +}); From 9df755fe297d2b90f04f4e877e6448c4a540b288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 14:53:29 +0900 Subject: [PATCH 12/30] =?UTF-8?q?feat(LottoDevice):=20=EA=B5=AC=EB=A9=94?= =?UTF-8?q?=20=EB=82=B4=EC=97=AD=20=EC=B6=9C=EB=A0=A5=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로또를 구매하고 구매한 로또와 관련된 일련의 작업을 LottoDevice에서 하도록 하였습니다. - 로또를 발행하기 위해 랜덤한 수를 뽑아야 합니다. 다만 이는 "로또 발행"에 종속된 동작이므로 유틸 함수가 아닌 해당 클래스에 넣었습니다. --- README.md | 6 +++--- src/App.js | 11 +++++++---- src/LottoDevice.js | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) create mode 100644 src/LottoDevice.js diff --git a/README.md b/README.md index a3a027d8..49a40738 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,11 @@ e.g. - [x] 구입 금액은 500원 단위의 정수만 가능 - [x] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 -- [ ] 2. 구매 내역 출력 +- [x] 2. 구매 내역 출력 - [x] "${개수}개를 구매했습니다." 라는 문구와 함께 구입한 로또의 개수와 번호를 출력 - [x] 로또의 가격은 1개당 500원이며, 구매한 로또 개수는 `구입금액 / 500` - - [ ] 로또 번호는 1~30까지의 중복되지 않은 정수 - - [ ] 로또 번호는 오름차순으로 정렬 + - [x] 로또 번호는 1~30까지의 중복되지 않은 정수 + - [x] 로또 번호는 오름차순으로 정렬 - [ ] 3. 당첨 번호 입력 - [ ] "당첨 번호를 입력해 주세요."라는 문구와 함께 당첨 번호를 입력받는다. diff --git a/src/App.js b/src/App.js index 5436b763..007f41a7 100644 --- a/src/App.js +++ b/src/App.js @@ -1,17 +1,20 @@ -import { LOTTO_COST } from "./constants.js"; +import LottoDevice from "./LottoDevice.js"; import { handleInputError } from "./utils.js"; import Validator from "./Validator.js"; import { InputView, OutputView } from "./view.js"; class App { async run() { - const amount = handleInputError({ + const amount = await handleInputError({ input: InputView.askAmount, validator: Validator.checkIsValidAmount, }); - const purchasedLottosCount = amount / LOTTO_COST; - OutputView.printPurchasedLottos(purchasedLottosCount); + const lottoDevice = new LottoDevice(); + + lottoDevice.issueLottos(amount); + const lottos = lottoDevice.getLottos(); + OutputView.printPurchasedLottos(lottos); } } diff --git a/src/LottoDevice.js b/src/LottoDevice.js new file mode 100644 index 00000000..1d87e46f --- /dev/null +++ b/src/LottoDevice.js @@ -0,0 +1,37 @@ +import { Random } from "@woowacourse/mission-utils"; +import Lotto from "./Lotto.js"; + +class LottoDevice { + #lottos; + + #proceeds; + + constructor() { + this.#lottos = []; + this.#proceeds = 0; + } + + issueLottos(amount) { + this.#proceeds = +amount; + const issuedCount = LottoDevice.getCanIssueAmount(amount); + while (this.#lottos.length !== issuedCount) { + const numbers = LottoDevice.getRandomLottoNumbers(); + const lotto = new Lotto(numbers); + this.#lottos.push(lotto); + } + } + + getLottos() { + return this.#lottos.map((lotto) => lotto.getLottoNumbers()); + } + + static getCanIssueAmount(amount) { + return amount / Lotto.cost; + } + + static getRandomLottoNumbers() { + return Random.pickUniqueNumbersInRange(1, 30, 5).sort((a, b) => a - b); + } +} + +export default LottoDevice; From 697086eefd8b426db160598f7e579ea33ee179b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:13:08 +0900 Subject: [PATCH 13/30] =?UTF-8?q?test(ValidatorTest):=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EB=B0=9B=EC=9D=80=20=EB=8B=B9=EC=B2=A8=20=EB=B2=88=ED=98=B8?= =?UTF-8?q?=EB=A5=BC=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/ValidatorTest.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/__tests__/ValidatorTest.js b/__tests__/ValidatorTest.js index eccfa520..ee08569b 100644 --- a/__tests__/ValidatorTest.js +++ b/__tests__/ValidatorTest.js @@ -15,4 +15,19 @@ describe("ValidatorTest", () => { }); }); }); + describe("checkWinningNumber", () => { + describe("올바른 입력을 받으면 true를 반환한다. ", () => { + it("당첨 번호는 1~30의 양의 정수 5개이다.", () => { + const testcase = [1, 2, 3, 4, 5]; + const result = Validator.checkWinningNumber(testcase); + expect(result).toBeTruthy(); + }); + }); + // TODO: 추후 작성하기 + // describe("올바르지 않은 입력을 받으면 에러를 던진다.", () => { + // it.each([123, -500])("%s 를 입력받으면 에러를 던진다.", (testcase) => { + // expect(() => Validator.checkWinningNumber(testcase)).toThrow(); + // }); + // }); + }); }); From d54bfa8e3c1731491cf45a3ef99a2071e3da28dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:13:35 +0900 Subject: [PATCH 14/30] =?UTF-8?q?feat(Validator):=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=EB=A5=BC=20=EA=B2=80=EC=A6=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Validator.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Validator.js b/src/Validator.js index 829dd0d1..c9b0e474 100644 --- a/src/Validator.js +++ b/src/Validator.js @@ -1,6 +1,7 @@ // TODO: 상수 따로 관리하기 const AMOUNT_ERROR_MESSAGE = "구매 금액은 500원 단위로 입력해주세요."; +const WINNING_NUMBER_ERROR_MESSAGE = ""; const Validator = { checkIsValidAmount(amount) { @@ -10,6 +11,21 @@ const Validator = { return true; }, + + checkWinningNumber(winningNumbers) { + // TODO: 매직 넘버 상수화 하기 + if (winningNumbers.length !== 5) throw new Error(WINNING_NUMBER_ERROR_MESSAGE); + const set = new Set(); + winningNumbers.forEach((n) => { + if (Number.isNaN(n)) throw new Error(WINNING_NUMBER_ERROR_MESSAGE); + if (n > 30 || n < 1) throw new Error(WINNING_NUMBER_ERROR_MESSAGE); + set.add(n); + }); + + if (set.size !== 5) throw new Error(WINNING_NUMBER_ERROR_MESSAGE); + + return true; + }, }; export default Validator; From a106341c379e82a34ee3e75a7eafb831f58960ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:14:54 +0900 Subject: [PATCH 15/30] =?UTF-8?q?feat(App):=20=EB=8B=B9=EC=B2=A8=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=EB=A5=BC=20=EC=9E=85=EB=A0=A5=EB=B0=9B?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- src/App.js | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 49a40738..8f1500b8 100644 --- a/README.md +++ b/README.md @@ -69,11 +69,11 @@ e.g. - [x] 로또 번호는 1~30까지의 중복되지 않은 정수 - [x] 로또 번호는 오름차순으로 정렬 -- [ ] 3. 당첨 번호 입력 - - [ ] "당첨 번호를 입력해 주세요."라는 문구와 함께 당첨 번호를 입력받는다. - - [ ] 번호는 쉽표를 기준으로 구분 - - [ ] 번호는 1~30까지의 중복되지 않은 정수 - - [ ] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 +- [x] 3. 당첨 번호 입력 + - [x] "당첨 번호를 입력해 주세요."라는 문구와 함께 당첨 번호를 입력받는다. + - [x] 번호는 쉽표를 기준으로 구분 + - [x] 번호는 1~30까지의 중복되지 않은 정수 + - [x] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 - [ ] 4. 보너스 번호 입력 - [ ] "보너스 번호 번호를 입력해 주세요." 문구와 함께 보너스 번호를 입력받음 diff --git a/src/App.js b/src/App.js index 007f41a7..dc61b63b 100644 --- a/src/App.js +++ b/src/App.js @@ -15,6 +15,11 @@ class App { lottoDevice.issueLottos(amount); const lottos = lottoDevice.getLottos(); OutputView.printPurchasedLottos(lottos); + + const winningNumbers = await handleInputError({ + input: InputView.askWinningLotto, + validator: Validator.checkWinningNumber, + }); } } From 22d576f12f028edf78b632461dd3e6b23a36443b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:19:50 +0900 Subject: [PATCH 16/30] =?UTF-8?q?test(ValidatorTest):=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EB=B0=9B=EC=9D=80=20=EB=B3=B4=EB=84=88=EC=8A=A4=20=EC=A0=90?= =?UTF-8?q?=EC=88=98=EA=B0=80=20=EC=98=AC=EB=B0=94=EB=A5=B8=EC=A7=80=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/ValidatorTest.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/__tests__/ValidatorTest.js b/__tests__/ValidatorTest.js index ee08569b..543ace9f 100644 --- a/__tests__/ValidatorTest.js +++ b/__tests__/ValidatorTest.js @@ -30,4 +30,17 @@ describe("ValidatorTest", () => { // }); // }); }); + + describe("checkBonusNumber", () => { + describe("올바른 입력을 받으면 true를 반환한다.", () => { + it("예외 번호를 제외한 1 ~ 30의 양의 정수이다.", () => { + expect(Validator.checkBonusNumber(1, [7, 8, 9, 11])).toBeTruthy(); + }); + }); + describe("올바르지 않은 입력을 받은 경우 에러를 던진다.", () => { + it("예외 번호에 포함된 번호 입력시 에러 발생", () => { + expect(() => Validator.checkBonusNumber(1, [1, 8, 9, 11])).toThrow(); + }); + }); + }); }); From 04d067c7948c7315adc0ead708c4e5612bfc03ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:20:55 +0900 Subject: [PATCH 17/30] =?UTF-8?q?feat(Validator):=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5=EC=9D=84=20?= =?UTF-8?q?=EC=9A=94=EA=B5=AC=EC=82=AC=ED=95=AD=EC=97=90=20=EB=A7=9E?= =?UTF-8?q?=EA=B2=8C=20=EA=B2=80=EC=A6=9D=ED=95=98=EB=8A=94=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Validator.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Validator.js b/src/Validator.js index c9b0e474..e697e1f3 100644 --- a/src/Validator.js +++ b/src/Validator.js @@ -2,6 +2,7 @@ const AMOUNT_ERROR_MESSAGE = "구매 금액은 500원 단위로 입력해주세요."; const WINNING_NUMBER_ERROR_MESSAGE = ""; +const BONUS_NUMBER_ERROR_MESSAGE = ""; const Validator = { checkIsValidAmount(amount) { @@ -26,6 +27,14 @@ const Validator = { return true; }, + + checkBonusNumber(input, excepts) { + if (typeof input !== "number") throw new Error(BONUS_NUMBER_ERROR_MESSAGE); + if (input > 30 || input < 1) throw new Error(BONUS_NUMBER_ERROR_MESSAGE); + if (excepts.includes(input)) throw new Error(BONUS_NUMBER_ERROR_MESSAGE); + + return true; + }, }; export default Validator; From 3f0b2d6c466846acc992c72fbb49ca61e9cb7a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:21:35 +0900 Subject: [PATCH 18/30] =?UTF-8?q?feat(App):=20=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=EB=A5=BC=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EB=B0=9B=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++----- src/App.js | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8f1500b8..8588de9a 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,11 @@ e.g. - [x] 번호는 1~30까지의 중복되지 않은 정수 - [x] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 -- [ ] 4. 보너스 번호 입력 - - [ ] "보너스 번호 번호를 입력해 주세요." 문구와 함께 보너스 번호를 입력받음 - - [ ] 번호는 1~30까지의 중복되지 않은 정수 - - [ ] 입력했던 당첨 번호와 중복되지 않음 - - [ ] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 +- [x] 4. 보너스 번호 입력 + - [x] "보너스 번호 번호를 입력해 주세요." 문구와 함께 보너스 번호를 입력받음 + - [x] 번호는 1~30까지의 중복되지 않은 정수 + - [x] 입력했던 당첨 번호와 중복되지 않음 + - [x] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 - [ ] 5. 당첨 통계 출력 - [ ] "당첨 통계\n---\n" 문구와 함께 당첨 통계를 출력 diff --git a/src/App.js b/src/App.js index dc61b63b..52d5a229 100644 --- a/src/App.js +++ b/src/App.js @@ -20,6 +20,11 @@ class App { input: InputView.askWinningLotto, validator: Validator.checkWinningNumber, }); + + const bonusNumber = await handleInputError({ + input: InputView.askBonusNumber, + validator: Validator.checkWinningNumber, + }); } } From e7f254f338761aabe22c2cfc8e8a97697aa25eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:42:10 +0900 Subject: [PATCH 19/30] =?UTF-8?q?test(LottoDeviceTest):=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EB=B0=9B=EC=9D=80=20=EB=A1=9C=EB=98=90=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EC=99=80=20=EB=B2=88=ED=98=B8,=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=EB=A5=BC=20=ED=86=B5=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EB=9E=AD=ED=81=AC=EB=A5=BC=20=EB=B0=98=ED=99=98?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/LottoDeviceTest.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/__tests__/LottoDeviceTest.js b/__tests__/LottoDeviceTest.js index 43d3af93..37ff06d0 100644 --- a/__tests__/LottoDeviceTest.js +++ b/__tests__/LottoDeviceTest.js @@ -1,3 +1,4 @@ +import Lotto from "../src/Lotto.js"; import LottoDevice from "../src/LottoDevice.js"; describe("LottoDeviceTest", () => { @@ -14,4 +15,12 @@ describe("LottoDeviceTest", () => { expect(result).toBe(2); }); }); + + describe("static calcRank", () => { + it("입력받은 로또 객체와 번호, 보너스 번호를 통하여 랭크를 반환한다.", () => { + const lotto = new Lotto([1, 2, 3, 4, 5]); + const result = LottoDevice.calcRank(lotto, [1, 2, 3, 4, 5], 7); + expect(result).toBe(1); + }); + }); }); From 07ec9c5d18bb573a05c2da16e024d0cb198597f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:43:05 +0900 Subject: [PATCH 20/30] =?UTF-8?q?feat(LottoDevice):=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EB=B0=9B=EC=9D=80=20=EB=A1=9C=EB=98=90=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EC=99=80=20=EB=B2=88=ED=98=B8,=20=EB=B3=B4=EB=84=88=EC=8A=A4?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=EB=A5=BC=20=ED=86=B5=ED=95=98=EC=97=AC=20?= =?UTF-8?q?=EB=9E=AD=ED=81=AC=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/LottoDevice.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/LottoDevice.js b/src/LottoDevice.js index 1d87e46f..67de377f 100644 --- a/src/LottoDevice.js +++ b/src/LottoDevice.js @@ -32,6 +32,24 @@ class LottoDevice { static getRandomLottoNumbers() { return Random.pickUniqueNumbersInRange(1, 30, 5).sort((a, b) => a - b); } + + static calcRank(lotto, numbers, bonusNumber) { + let correctCount = 0; + const lottoNumbers = lotto.getLottoNumbers(); + + numbers.forEach((n) => { + if (lottoNumbers.includes(n)) correctCount += 1; + }); + + const isBonusNumCorrect = lottoNumbers.includes(bonusNumber); + + if (correctCount === 5) return 1; + if (correctCount === 4 && isBonusNumCorrect) return 2; + if (correctCount === 4) return 3; + if (correctCount === 3 && isBonusNumCorrect) return 4; + if (correctCount === 2 && isBonusNumCorrect) return 5; + return 0; + } } export default LottoDevice; From 53ee274bc8317799e2fec5221bf41a79d177b90d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:44:24 +0900 Subject: [PATCH 21/30] =?UTF-8?q?feat(LottoDevice):=20=EA=B5=AC=EB=A7=A4?= =?UTF-8?q?=ED=95=9C=20=EB=A1=9C=EB=98=90=20=EB=8B=B9=EC=B2=A8=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EB=A5=BC=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++------ src/LottoDevice.js | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8588de9a..51937086 100644 --- a/README.md +++ b/README.md @@ -83,9 +83,9 @@ e.g. - [ ] 5. 당첨 통계 출력 - [ ] "당첨 통계\n---\n" 문구와 함께 당첨 통계를 출력 - - 당첨은 1등부터 5등까지 있으며, 당첨 기준과 금액은 아래와 같다. - - 1등: 5개 번호 일치 / 100,000,000원 - - 2등: 4개 번호 + 보너스 번호 일치 / 10,000,000원 - - 3등: 4개 번호 일치 / 1,500,000원 - - 4등: 3개 번호 일치 + 보너스 번호 일치 / 500,000원 - - 5등: 2개 번호 일치 + 보너스 번호 일치 / 5,000원 + - [x] 당첨 통계 게산 + - [x]1등: 5개 번호 일치 + - [x]2등: 4개 번호 + 보너스 번호 일치 + - [x]3등: 4개 번호 일치 + - [x]4등: 3개 번호 일치 + 보너스 번호 일치 + - [x]5등: 2개 번호 일치 + 보너스 번호 일치 diff --git a/src/LottoDevice.js b/src/LottoDevice.js index 67de377f..60b612dc 100644 --- a/src/LottoDevice.js +++ b/src/LottoDevice.js @@ -33,6 +33,26 @@ class LottoDevice { return Random.pickUniqueNumbersInRange(1, 30, 5).sort((a, b) => a - b); } + getRankResult(winningNumbers, bonusNumber) { + const ranks = new Map([ + [1, 0], + [2, 0], + [3, 0], + [4, 0], + [5, 0], + [0, 0], + ]); + + this.#lottos.forEach((lotto) => { + const rank = LottoDevice.calcRank(lotto, winningNumbers, bonusNumber); + if (!rank) return; + const prev = ranks.get(rank); + ranks.set(rank, prev + 1); + }); + + return ranks; + } + static calcRank(lotto, numbers, bonusNumber) { let correctCount = 0; const lottoNumbers = lotto.getLottoNumbers(); From e4a5928d21559ffc8f8ac6498bb2eddba568cef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:52:41 +0900 Subject: [PATCH 22/30] =?UTF-8?q?fix(App):=20=EB=B3=B4=EB=84=88=EC=8A=A4?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=EB=A5=BC=20=EC=98=AC=EB=B0=94=EB=A5=B4?= =?UTF-8?q?=EA=B2=8C=20=EA=B2=80=EC=A6=9D=ED=95=98=EC=A7=80=20=EB=AA=BB?= =?UTF-8?q?=ED=95=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 올바른 validator로 수정하였습니다. --- src/App.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 52d5a229..d760d7a8 100644 --- a/src/App.js +++ b/src/App.js @@ -23,8 +23,11 @@ class App { const bonusNumber = await handleInputError({ input: InputView.askBonusNumber, - validator: Validator.checkWinningNumber, + validator: (input) => Validator.checkBonusNumber(input, [...winningNumbers]), }); + + const lottoServiceResult = lottoDevice.getRankResult(winningNumbers, bonusNumber); + OutputView.printResult(lottoServiceResult); } } From 171008e3181c3cb2a40db743ee209726e0fdbd0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 15:59:27 +0900 Subject: [PATCH 23/30] =?UTF-8?q?fix(LottoDevice):=20=EC=95=84=EB=AC=B4?= =?UTF-8?q?=EA=B2=83=EB=8F=84=20=EB=A7=9E=EC=B6=94=EC=A7=80=20=EB=AA=BB?= =?UTF-8?q?=ED=95=9C=20=EA=B2=BD=EC=9A=B0=20=EC=B9=B4=EC=9A=B4=ED=8A=B8?= =?UTF-8?q?=EA=B0=80=20=EC=95=88=EB=90=98=EB=8D=98=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit calcRank를 통하여 등수를 반환할 때 0으로 반환된 값을 카운트 하도록 수정하였습니다 --- src/LottoDevice.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/LottoDevice.js b/src/LottoDevice.js index 60b612dc..b00ba54a 100644 --- a/src/LottoDevice.js +++ b/src/LottoDevice.js @@ -4,15 +4,11 @@ import Lotto from "./Lotto.js"; class LottoDevice { #lottos; - #proceeds; - constructor() { this.#lottos = []; - this.#proceeds = 0; } issueLottos(amount) { - this.#proceeds = +amount; const issuedCount = LottoDevice.getCanIssueAmount(amount); while (this.#lottos.length !== issuedCount) { const numbers = LottoDevice.getRandomLottoNumbers(); @@ -45,7 +41,6 @@ class LottoDevice { this.#lottos.forEach((lotto) => { const rank = LottoDevice.calcRank(lotto, winningNumbers, bonusNumber); - if (!rank) return; const prev = ranks.get(rank); ranks.set(rank, prev + 1); }); From dbfa9815fb3888c23882629955793495e1032c68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 16:00:04 +0900 Subject: [PATCH 24/30] =?UTF-8?q?docs(README):=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=EC=82=AC=ED=95=AD=20=EB=AC=B8=EC=84=9C=20=EC=97=85=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 51937086..bf225070 100644 --- a/README.md +++ b/README.md @@ -81,11 +81,28 @@ e.g. - [x] 입력했던 당첨 번호와 중복되지 않음 - [x] 올바르지 않은 입력을 한 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 다시 입력 받음 -- [ ] 5. 당첨 통계 출력 - - [ ] "당첨 통계\n---\n" 문구와 함께 당첨 통계를 출력 +- [x] 5. 당첨 통계 출력 + - [x] "당첨 통계\n---\n" 문구와 함께 당첨 통계를 출력 - [x] 당첨 통계 게산 - [x]1등: 5개 번호 일치 - [x]2등: 4개 번호 + 보너스 번호 일치 - [x]3등: 4개 번호 일치 - [x]4등: 3개 번호 일치 + 보너스 번호 일치 - [x]5등: 2개 번호 일치 + 보너스 번호 일치 + +--- + +## 리팩토링 구현 사항 + +### 최소 조건 + +- [ ] 모든 함수는 15줄을 넘지 않는다. +- [ ] 모든 함수는 index level 2를 넘지 않는다. 단, class, try...cach 문의 들여쓰기는 제외한다. +- [ ] 사용하지 않는 코드는 지운다. + +### 추가 조건 + +- [ ] (테스트 코드를 포함하여) 코드의 반복을 최대한 줄인다. +- [ ] 객체의 상태 접근과 관련한 리팩터링을 한다. + - [ ] 클래스는 단순히 값을 반환하는 메서드를 가지는 것 보다는 그 기능을 하도록 구현한다. + - [ ] 어떤 값을 감춰야 할지 고민한다. From 1ac231b2383275cb4c8f09ea7d28841ef8fc86cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 16:18:12 +0900 Subject: [PATCH 25/30] =?UTF-8?q?chore:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=BD=94=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - constants.js에 있는 LOTTO_COST값 제거 - utils.js에 있는 getRandomLottoNumbers 제거 - handleInputError의 parser 문 제거 --- README.md | 2 +- src/constants.js | 1 - src/utils.js | 8 +------- 3 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 src/constants.js diff --git a/README.md b/README.md index bf225070..0ac9e480 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ e.g. - [ ] 모든 함수는 15줄을 넘지 않는다. - [ ] 모든 함수는 index level 2를 넘지 않는다. 단, class, try...cach 문의 들여쓰기는 제외한다. -- [ ] 사용하지 않는 코드는 지운다. +- [x] 사용하지 않는 코드는 지운다. ### 추가 조건 diff --git a/src/constants.js b/src/constants.js deleted file mode 100644 index 6363f509..00000000 --- a/src/constants.js +++ /dev/null @@ -1 +0,0 @@ -export const LOTTO_COST = 500; diff --git a/src/utils.js b/src/utils.js index bfde3104..89eb3fde 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,19 +1,13 @@ -import { Random } from "@woowacourse/mission-utils"; import { OutputView } from "./view.js"; -export async function handleInputError({ input, validator, parser }) { +export async function handleInputError({ input, validator }) { while (true) { try { const inputValue = await input(); validator(inputValue); - if (parser) return parser(inputValue); return inputValue; } catch (e) { OutputView.printErrorMessage(e.message); } } } - -export function getRandomLottoNumbers() { - return Random.pickUniqueNumbersInRange(1, 30, 5); -} From faa57a3c3ab6ab22d3e4cc73826955b023c1074b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 16:23:39 +0900 Subject: [PATCH 26/30] =?UTF-8?q?refactor(Validator):=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=20=EB=A9=94=EC=8B=9C=EC=A7=80=EB=A5=BC=20=EB=94=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=83=81=EC=88=98=ED=99=94=20=ED=95=98=EC=97=AC=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Validator.js | 26 +++++++++++--------------- src/constants/errorMessages.js | 7 +++++++ 2 files changed, 18 insertions(+), 15 deletions(-) create mode 100644 src/constants/errorMessages.js diff --git a/src/Validator.js b/src/Validator.js index e697e1f3..1f692b4f 100644 --- a/src/Validator.js +++ b/src/Validator.js @@ -1,37 +1,33 @@ -// TODO: 상수 따로 관리하기 - -const AMOUNT_ERROR_MESSAGE = "구매 금액은 500원 단위로 입력해주세요."; -const WINNING_NUMBER_ERROR_MESSAGE = ""; -const BONUS_NUMBER_ERROR_MESSAGE = ""; +import ERROR_MESSAGE from "./constants/errorMessages.js"; const Validator = { checkIsValidAmount(amount) { - if (typeof amount !== "number") throw new Error(AMOUNT_ERROR_MESSAGE); - if (amount % 500 !== 0) throw new Error(AMOUNT_ERROR_MESSAGE); - if (amount < 0) throw new Error(AMOUNT_ERROR_MESSAGE); + if (typeof amount !== "number") throw new Error(ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); + if (amount % 500 !== 0) throw new Error(ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); + if (amount < 0) throw new Error(ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); return true; }, checkWinningNumber(winningNumbers) { // TODO: 매직 넘버 상수화 하기 - if (winningNumbers.length !== 5) throw new Error(WINNING_NUMBER_ERROR_MESSAGE); + if (winningNumbers.length !== 5) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); const set = new Set(); winningNumbers.forEach((n) => { - if (Number.isNaN(n)) throw new Error(WINNING_NUMBER_ERROR_MESSAGE); - if (n > 30 || n < 1) throw new Error(WINNING_NUMBER_ERROR_MESSAGE); + if (Number.isNaN(n)) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); + if (n > 30 || n < 1) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); set.add(n); }); - if (set.size !== 5) throw new Error(WINNING_NUMBER_ERROR_MESSAGE); + if (set.size !== 5) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); return true; }, checkBonusNumber(input, excepts) { - if (typeof input !== "number") throw new Error(BONUS_NUMBER_ERROR_MESSAGE); - if (input > 30 || input < 1) throw new Error(BONUS_NUMBER_ERROR_MESSAGE); - if (excepts.includes(input)) throw new Error(BONUS_NUMBER_ERROR_MESSAGE); + if (typeof input !== "number") throw new Error(ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); + if (input > 30 || input < 1) throw new Error(ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); + if (excepts.includes(input)) throw new Error(ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); return true; }, diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js new file mode 100644 index 00000000..dfeda0a2 --- /dev/null +++ b/src/constants/errorMessages.js @@ -0,0 +1,7 @@ +const ERROR_MESSAGE = { + AMOUNT_ERROR_MESSAGE: "구매 금액은 500원 단위로 입력해주세요.", + WINNING_NUMBER_ERROR_MESSAGE: "올바른 당첨 번호가 아닙니다.", + BONUS_NUMBER_ERROR_MESSAGE: "올바른 보너스 번호가 아닙니다.", +}; + +export default ERROR_MESSAGE; From e88c893cec5d23ad926b627548fe994f24389a78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 16:34:59 +0900 Subject: [PATCH 27/30] =?UTF-8?q?refactor:=20=EB=AA=A8=EB=93=A0=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=EA=B0=80=2015=EB=9D=BC=EC=9D=B8=EC=9D=84=20?= =?UTF-8?q?=EB=84=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- src/App.js | 30 +++++++++++++++++++++++++++++- src/LottoDevice.js | 19 ++++++++++--------- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 0ac9e480..ec8df714 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,8 @@ e.g. ### 최소 조건 -- [ ] 모든 함수는 15줄을 넘지 않는다. -- [ ] 모든 함수는 index level 2를 넘지 않는다. 단, class, try...cach 문의 들여쓰기는 제외한다. +- [x] 모든 함수는 15줄을 넘지 않는다. +- [x] 모든 함수는 index level 2를 넘지 않는다. 단, class, try...cach 문의 들여쓰기는 제외한다. - [x] 사용하지 않는 코드는 지운다. ### 추가 조건 diff --git a/src/App.js b/src/App.js index d760d7a8..8dcabd88 100644 --- a/src/App.js +++ b/src/App.js @@ -4,31 +4,59 @@ import Validator from "./Validator.js"; import { InputView, OutputView } from "./view.js"; class App { - async run() { + async getAmount() { const amount = await handleInputError({ input: InputView.askAmount, validator: Validator.checkIsValidAmount, }); + return amount; + } + + printPurchase(amount) { const lottoDevice = new LottoDevice(); lottoDevice.issueLottos(amount); const lottos = lottoDevice.getLottos(); OutputView.printPurchasedLottos(lottos); + return lottoDevice; + } + + async getWinningNumber() { const winningNumbers = await handleInputError({ input: InputView.askWinningLotto, validator: Validator.checkWinningNumber, }); + return winningNumbers; + } + + async getBonusNumber(winningNumbers) { const bonusNumber = await handleInputError({ input: InputView.askBonusNumber, validator: (input) => Validator.checkBonusNumber(input, [...winningNumbers]), }); + return bonusNumber; + } + + printResult(lottoDevice, winningNumbers, bonusNumber) { const lottoServiceResult = lottoDevice.getRankResult(winningNumbers, bonusNumber); OutputView.printResult(lottoServiceResult); } + + async run() { + const amount = await this.getAmount(); + + const lottoDevice = this.printPurchase(amount); + + const winningNumbers = await this.getWinningNumber(); + + const bonusNumber = await this.getBonusNumber(winningNumbers); + + this.printResult(lottoDevice, winningNumbers, bonusNumber); + } } export default App; diff --git a/src/LottoDevice.js b/src/LottoDevice.js index b00ba54a..4f9fdbb7 100644 --- a/src/LottoDevice.js +++ b/src/LottoDevice.js @@ -1,6 +1,15 @@ import { Random } from "@woowacourse/mission-utils"; import Lotto from "./Lotto.js"; +const DEFAULT_RANKS = [ + [1, 0], + [2, 0], + [3, 0], + [4, 0], + [5, 0], + [0, 0], +]; + class LottoDevice { #lottos; @@ -30,14 +39,7 @@ class LottoDevice { } getRankResult(winningNumbers, bonusNumber) { - const ranks = new Map([ - [1, 0], - [2, 0], - [3, 0], - [4, 0], - [5, 0], - [0, 0], - ]); + const ranks = new Map(DEFAULT_RANKS); this.#lottos.forEach((lotto) => { const rank = LottoDevice.calcRank(lotto, winningNumbers, bonusNumber); @@ -55,7 +57,6 @@ class LottoDevice { numbers.forEach((n) => { if (lottoNumbers.includes(n)) correctCount += 1; }); - const isBonusNumCorrect = lottoNumbers.includes(bonusNumber); if (correctCount === 5) return 1; From 1e3cc3b38a2d734e3571ab6c294691fd4b55b9c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 16:42:26 +0900 Subject: [PATCH 28/30] =?UTF-8?q?refactor(Validator):=20=EB=B0=98=EB=B3=B5?= =?UTF-8?q?=EB=90=98=EB=8A=94=20=EA=B2=80=EC=A6=9D=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/Validator.js | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ec8df714..d3541424 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ e.g. ### 추가 조건 -- [ ] (테스트 코드를 포함하여) 코드의 반복을 최대한 줄인다. +- [x] (테스트 코드를 포함하여) 코드의 반복을 최대한 줄인다. - [ ] 객체의 상태 접근과 관련한 리팩터링을 한다. - [ ] 클래스는 단순히 값을 반환하는 메서드를 가지는 것 보다는 그 기능을 하도록 구현한다. - [ ] 어떤 값을 감춰야 할지 고민한다. diff --git a/src/Validator.js b/src/Validator.js index 1f692b4f..2ae6ed59 100644 --- a/src/Validator.js +++ b/src/Validator.js @@ -1,8 +1,16 @@ import ERROR_MESSAGE from "./constants/errorMessages.js"; const Validator = { + checkIsValidRange(input, errorMessage) { + if (input > 30 || input < 1) throw new Error(errorMessage); + }, + + checkIsNumber(input, errorMessage) { + if (typeof input !== "number") throw new Error(errorMessage); + }, + checkIsValidAmount(amount) { - if (typeof amount !== "number") throw new Error(ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); + Validator.checkIsNumber(amount, ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); if (amount % 500 !== 0) throw new Error(ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); if (amount < 0) throw new Error(ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); @@ -15,7 +23,7 @@ const Validator = { const set = new Set(); winningNumbers.forEach((n) => { if (Number.isNaN(n)) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); - if (n > 30 || n < 1) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); + Validator.checkIsValidRange(n, ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); set.add(n); }); @@ -25,8 +33,8 @@ const Validator = { }, checkBonusNumber(input, excepts) { - if (typeof input !== "number") throw new Error(ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); - if (input > 30 || input < 1) throw new Error(ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); + Validator.checkIsNumber(input, ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); + Validator.checkIsValidRange(input, ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); if (excepts.includes(input)) throw new Error(ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); return true; From d54b2ddd9ac8261007b24370ffe67d39753eabe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 16:43:45 +0900 Subject: [PATCH 29/30] =?UTF-8?q?refactor(Validator):=20=EB=8B=B9=EC=B2=A8?= =?UTF-8?q?=20=EB=B2=88=ED=98=B8=20=EA=B0=9C=EC=88=98=20=EC=83=81=EC=88=98?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Validator.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Validator.js b/src/Validator.js index 2ae6ed59..f5ec4691 100644 --- a/src/Validator.js +++ b/src/Validator.js @@ -1,6 +1,7 @@ import ERROR_MESSAGE from "./constants/errorMessages.js"; const Validator = { + WINNING_NUMBERS_LENGTH: 5, checkIsValidRange(input, errorMessage) { if (input > 30 || input < 1) throw new Error(errorMessage); }, @@ -18,8 +19,8 @@ const Validator = { }, checkWinningNumber(winningNumbers) { - // TODO: 매직 넘버 상수화 하기 - if (winningNumbers.length !== 5) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); + if (winningNumbers.length !== Validator.WINNING_NUMBERS_LENGTH) + throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); const set = new Set(); winningNumbers.forEach((n) => { if (Number.isNaN(n)) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); From ee18773b63b9fa2f36c8a775c4e0c6d76e282ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=87=E1=85=A1=E1=86=A8=E1=84=80=E1=85=A5=E1=86=AB?= =?UTF-8?q?=E1=84=80=E1=85=B2?= Date: Sat, 10 Jan 2026 16:51:32 +0900 Subject: [PATCH 30/30] =?UTF-8?q?refactor:=20=EC=B6=94=EA=B0=80=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Lotto.js | 8 ++++---- src/Validator.js | 21 +++++++++++---------- src/constants/errorMessages.js | 14 +++++++++++--- 3 files changed, 26 insertions(+), 17 deletions(-) diff --git a/src/Lotto.js b/src/Lotto.js index d81dd0b5..a7d88ecb 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,4 +1,4 @@ -// TODO:에러 메시지 상수화 하기 +import ERROR_MESSAGE from "./constants/errorMessages.js"; class Lotto { #numbers; @@ -11,14 +11,14 @@ class Lotto { } #validate(numbers) { - if (numbers.length !== 5) throw new Error("로또 번호는 5개여야 합니다."); + if (numbers.length !== 5) throw new Error(ERROR_MESSAGE.LOTTO.LOTTO_NUMBER); const set = new Set(); numbers.forEach((n) => { - if (n > 30 || n < 1) throw new Error("로또 번호의 범위는 1 ~ 30까지 입니다."); + if (n > 30 || n < 1) throw new Error(ERROR_MESSAGE.LOTTO.LOTTO_RANGE); set.add(n); }); - if (set.size !== numbers.length) throw new Error("로또 번호는 중복될 수 없습니다."); + if (set.size !== numbers.length) throw new Error(ERROR_MESSAGE.LOTTO.LOTTO_DUPLICATE); } // TODO: 단순 가져오기보다는 일을 할 수 있도록 리팩토링 필요 diff --git a/src/Validator.js b/src/Validator.js index f5ec4691..57474ee7 100644 --- a/src/Validator.js +++ b/src/Validator.js @@ -11,32 +11,33 @@ const Validator = { }, checkIsValidAmount(amount) { - Validator.checkIsNumber(amount, ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); - if (amount % 500 !== 0) throw new Error(ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); - if (amount < 0) throw new Error(ERROR_MESSAGE.AMOUNT_ERROR_MESSAGE); + Validator.checkIsNumber(amount, ERROR_MESSAGE.VALIDATOR.AMOUNT_ERROR_MESSAGE); + if (amount % 500 !== 0) throw new Error(ERROR_MESSAGE.VALIDATOR.AMOUNT_ERROR_MESSAGE); + if (amount < 0) throw new Error(ERROR_MESSAGE.VALIDATOR.AMOUNT_ERROR_MESSAGE); return true; }, checkWinningNumber(winningNumbers) { if (winningNumbers.length !== Validator.WINNING_NUMBERS_LENGTH) - throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); + throw new Error(ERROR_MESSAGE.VALIDATOR.WINNING_NUMBER_ERROR_MESSAGE); const set = new Set(); winningNumbers.forEach((n) => { - if (Number.isNaN(n)) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); - Validator.checkIsValidRange(n, ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); + if (Number.isNaN(n)) throw new Error(ERROR_MESSAGE.VALIDATOR.WINNING_NUMBER_ERROR_MESSAGE); + Validator.checkIsValidRange(n, ERROR_MESSAGE.VALIDATOR.WINNING_NUMBER_ERROR_MESSAGE); set.add(n); }); - if (set.size !== 5) throw new Error(ERROR_MESSAGE.WINNING_NUMBER_ERROR_MESSAGE); + if (set.size !== 5) throw new Error(ERROR_MESSAGE.VALIDATOR.WINNING_NUMBER_ERROR_MESSAGE); return true; }, checkBonusNumber(input, excepts) { - Validator.checkIsNumber(input, ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); - Validator.checkIsValidRange(input, ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); - if (excepts.includes(input)) throw new Error(ERROR_MESSAGE.BONUS_NUMBER_ERROR_MESSAGE); + Validator.checkIsNumber(input, ERROR_MESSAGE.VALIDATOR.BONUS_NUMBER_ERROR_MESSAGE); + Validator.checkIsValidRange(input, ERROR_MESSAGE.VALIDATOR.BONUS_NUMBER_ERROR_MESSAGE); + if (excepts.includes(input)) + throw new Error(ERROR_MESSAGE.VALIDATOR.BONUS_NUMBER_ERROR_MESSAGE); return true; }, diff --git a/src/constants/errorMessages.js b/src/constants/errorMessages.js index dfeda0a2..2908a442 100644 --- a/src/constants/errorMessages.js +++ b/src/constants/errorMessages.js @@ -1,7 +1,15 @@ const ERROR_MESSAGE = { - AMOUNT_ERROR_MESSAGE: "구매 금액은 500원 단위로 입력해주세요.", - WINNING_NUMBER_ERROR_MESSAGE: "올바른 당첨 번호가 아닙니다.", - BONUS_NUMBER_ERROR_MESSAGE: "올바른 보너스 번호가 아닙니다.", + VALIDATOR: { + AMOUNT_ERROR_MESSAGE: "구매 금액은 500원 단위로 입력해주세요.", + WINNING_NUMBER_ERROR_MESSAGE: "올바른 당첨 번호가 아닙니다.", + BONUS_NUMBER_ERROR_MESSAGE: "올바른 보너스 번호가 아닙니다.", + }, + + LOTTO: { + LOTTO_NUMBER: "로또 번호는 5개여야 합니다.", + LOTTO_RANGE: "로또 번호의 범위는 1 ~ 30까지 입니다.", + LOTTO_DUPLICATE: "로또 번호는 중복될 수 없습니다.", + }, }; export default ERROR_MESSAGE;