오늘은 3주차에 했던 과제에 대해서 리뷰를 해볼까한다.
이번 피드백에서는 메타인지를 위한 최고의 도구로 회고를 말씀하셨다. 우리는 학습과 경험을 그냥 지나치지 않고 반성하고 개선할 수 있다. 그래서 나도 늦었지만, 주차별 어떻게 작업을 했는지 이렇게 블로그로 작성을 해본다.
- README.md를 상세히 작성한다
- 기능 목록을 재검토한다
- 기능 목록을 업데이트한다
- 값을 하드 코딩하지 않는다
- 구현 순서도 코딩 컨벤션이다 (ex. 클래스- 필드,생성자, 메서드 순서)
- 한 메서드가 한 가지 기능만 담당하게 한다
- 메서드가 한 가지 기능을 하는지 확인하는 기준을 세운다
- 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다
- 처음부터 큰 단위의 테스트를 만들지 않는다
- JavaScript에서 객체를 만드는 다양한 방법을 이해하고 사용한다.
JavaScript에서 객체를 만드는 다양한 방법에 대해서 다음 포스팅에서 다루어 볼 예정이다. 아직 내가 부족한 부분이 많다는 것을 느꼈다.
주제 : 간단한 로또 발매기를 구현한다.
입출력 요구 사항
고려 사항
JAVASCRIPT-LOTTO-7
├── __tests__ # 테스트 폴더
│ ├── ApplicationTest.js
│ ├── LottoProcessorTest.js
│ ├── LottoTest.js
│ └── WinningLottoTest.js
├── node_modules # 외부 모듈 폴더
├── src # 소스 코드 폴더
│ ├── io # 입출력 핸들러 폴더
│ │ ├── InputHandler.js
│ │ └── OutputHandler.js
│ ├── lotto # 로또 관련 기능 폴더
│ │ ├── Lotto.js
│ │ ├── LottoProcessor.js
│ │ └── WinningLotto.js
│ ├── utils # 유틸리티 폴더
│ │ ├── constants.js
│ │ └── ErrorHandler.js
│ ├── App.js # 애플리케이션 메인 파일
│ └── index.js # 엔트리 포인트 파일
├── .gitignore # Git 무시 파일
├── .npmrc # npm 설정 파일
├── package-lock.json # npm 패키지 잠금 파일
├── package.json # npm 패키지 설정 파일
└── README.md # 프로젝트 설명 파일
우선, 저번 1,2주차와는 다르게 파일구조를 조금 다르게 구성을 해봤다. 저번 주차까지는 기능이 그렇게 복잡하지 않아,
App
클래스 내부에서 모든 로직이 동작하게끔 만들었다. 내 생각엔 오히려 그게 더 가독성이 좋다고 느껴졌다. 하지만, 이번 과제부터는 복잡해졌다. 기능요구사항도 굉장히 길고, 기본으로 주어진Lotto
클래스를 이용해야했기 때문에 이번엔 입출력부터 로또 관련 클래스들로 파일 구조를 나눠서 과제를 수행했다.
1. __tests__
폴더
테스트 코드 관리
ApplicationTest.js
, LottoProcessorTest.js
, LottoTest.js
, WinningLottoTest.js
예외처리 및 발생할 일들을 테스트 코드로 작성하는 연습을 하기 위해 기본 제공 테스트 코드에 추가로 작성을 했다.
2. io
폴더
InputHandler.js
: 입력을 처리하는 파일.
OutputHandler.js
: 출력을 처리하는 파일.
3. lotto
폴더
로또 기능 관련 모듈
Lotto.js
: 로또 번호를 생성하고 관리하는 클래스.
LottoProcessor.js
: 로또 관련 비즈니스 로직 처리 (로또와 관련된 모든 것)
WinningLotto.js
: 당첨 번호 및 보너스 번호 관리하는 클래스.
4. utils
폴더
constants.js
: 애플리케이션 전반에서 사용하는 상수들을 정의한 파일.
ErrorHandler.js
: 에러 처리를 위한 모듈.
5. README.md
파일
README.md: 과제에 대한 명세 + 기능명세서
우선, 이번에 기본 제공해준 lotto 클래스를 보면서 클래스에 대해서 공부를 해야겠다는 생각이 들었다.
Lotto.js
파일
class Lotto {
#numbers;
constructor(numbers) {
this.#validate(numbers);
this.#numbers = numbers;
}
#validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
}
// TODO: 추가 기능 구현
}
export default Lotto;
처음 프로젝트에 구현되어있던 Lotto.js파일이다. 이걸 처음엔 어떻게 사용해야할지 막막했다. 모든 로또를 여기서 생성해야하는지(당첨번호+보너스번호 포함),, 로또와 관련된 기능들은 이 곳에 다 넣어야하는지,, 고민이 많았고 여러 시도를 진행한 결과, 이 코드는 아래와 같이 변경되었다.
import { Random } from "@woowacourse/mission-utils";
import {
ERROR_MESSAGES,
LOTTO_BONUS_FLAG,
LOTTO_SIZE,
LOTTO_WINNING_FLAG,
MAX_NUMBER,
MIN_NUMBER,
} from "../utils/constants.js";
import ErrorHandler from "../utils/ErrorHandler.js";
// 로또 번호 생성 및 관리
class Lotto {
#numbers;
constructor(numbers) {
this.#validate(numbers);
this.#numbers = numbers.sort((a, b) => a - b); // 오름차순 정렬
}
#validate(numbers) {
if (numbers.length !== LOTTO_SIZE) {
ErrorHandler.throwError(ERROR_MESSAGES.WINNING_SIZE);
} else if (new Set(numbers).size !== LOTTO_SIZE) {
ErrorHandler.throwError(ERROR_MESSAGES.DUPLICATE_NUMBER);
} else if (
numbers.some((number) => number < MIN_NUMBER || number > MAX_NUMBER)
) {
ErrorHandler.throwError(ERROR_MESSAGES.RANGE);
}
}
//랜덤 로또 번호 반환과 동시에 인스턴스 생성
static generateLottoNumbers() {
return new Lotto(
Random.pickUniqueNumbersInRange(MIN_NUMBER, MAX_NUMBER, LOTTO_SIZE)
);
}
// 로또번호 반환
getNumbers() {
return `[${this.#numbers.join(", ")}]`;
}
// 당첨 번호와 비교 후 맞는 갯수 리턴
countMatchingNumbers(winningNumbersArray) {
return this.#numbers.filter(
(num) => winningNumbersArray[num] === LOTTO_WINNING_FLAG
).length;
}
// 보너스 번호가 있는지 확인
hasBonusNumber(winningNumbersArray) {
return this.#numbers.some(
(num) => winningNumbersArray[num] === LOTTO_BONUS_FLAG
);
}
}
export default Lotto;
Lotto
클래스에서는 오로지 랜덤으로 생성되는 로또 번호만 관리하기로 역할을 부여했다.
1. constructor(numbers)
역할: 주어진 numbers
배열을 검증하고, 유효한 경우 오름차순으로 정렬하여 인스턴스 변수 #numbers
에 저장한다.
동작:
#validate(numbers)
메서드를 호출하여 입력된 번호 배열을 검증한다.
검증을 통과하면 번호를 오름차순으로 정렬하여 #numbers
에 저장한다.
2. #validate(numbers)
역할: 로또 번호 배열이 유효한지 검사한다.
3. static generateLottoNumbers()
역할: Lotto
인스턴스를 생성하며 랜덤한 로또 번호를 반환한다.
static으로 선언해서 인스턴스가 아닌 클래스 단위로 접근할 수 있도록 했다.그리고 생성과 동시에 retrun 값으로 인스턴스들을 만들어줬다.
4. getNumbers()
역할: 현재 로또 번호를 문자열 형태로 반환한다.
5. countMatchingNumbers(winningNumbersArray)
역할: 당첨 번호와 비교하여 일치하는 숫자의 개수를 반환한다.
6. hasBonusNumber(winningNumbersArray)
역할: 로또 번호 중 보너스 번호가 있는지 확인한다.
WinningLotto.js
파일import {
LOTTO_BONUS_FLAG,
LOTTO_WINNING_FLAG,
MAX_NUMBER,
} from "../utils/constants.js";
// 당첨 번호 및 보너스 번호 관리
class WinningLotto {
#winningNumbersArray;
constructor() {
this.#winningNumbersArray = Array(MAX_NUMBER + 1).fill(0);
}
// 당첨 번호 가공
setWinningNumbers(winningNumbers) {
winningNumbers.forEach((number) => {
this.#winningNumbersArray[number] = LOTTO_WINNING_FLAG; // 해당 인덱스 === 당첨 번호를 1로 변경
});
}
// 보너스 번호 가공
setBonusNumber(bonusNumber) {
this.#winningNumbersArray[bonusNumber] = LOTTO_BONUS_FLAG; // 보너스 번호는 2로 표시
}
getWinningNumbersArray() {
return this.#winningNumbersArray;
}
}
export default WinningLotto;
Lotto
클래스와 더불어WinningLotto
클래스를 만들어 당첨번호와 보너스 번호를 관리하게끔 만들어놨다.
this.#winningNumbersArray = Array(MAX_NUMBER + 1).fill(0);
#winningNumbersArray 배열을 46 크기로 선언을 하고 이 안을 모두 0으로 채웠다. 그리고 해당 당첨번호가 들어오면 같은 인덱스를 1로 변경하는 방식으로 진행했다. , 인덱스 기반 해싱테이블 방식으로 개발을 했다. 이렇게 하면 시간 복잡도를 O(1)로 줄일 수 있기 때문에 특정번호를 당첨 또는 보너스 번호인지 빠르게 확인이 가능하다!
1. constructor()
역할: 클래스 인스턴스를 생성할 때 #winningNumbersArray 배열을 초기화한다.
동작:
이 배열의 인덱스는 로또 번호와 일치하며, 인덱스의 값은 해당 번호의 상태를 나타낸다 (0: 미선택, 1: 당첨 번호, 2: 보너스 번호)
2. setWinningNumbers(winningNumbers)
역할:
입력받은 당첨 번호 배열을 #winningNumbersArray
에 저장
동작:
#winningNumbersArray
에서 해당 번호가 당첨 번호임을 나타내기 위해 값을 1로 설정
3. setBonusNumber(bonusNumber)
역할: 보너스 번호를 #winningNumbersArray
에 저장한다.
동작:
#winningNumbersArray
에서 해당 번호가 보너스 번호임을 표시하기 위해 값을 2로 설정한다.
4. getWinningNumbersArray()
역할: #winningNumbersArray
배열을 반환하여 외부에서 당첨 및 보너스 번호 정보를 조회할 수 있도록 한다.
LottoProcessor.js
파일import { LOTTO_UNIT, PRIZE_AMOUNT } from "../utils/constants.js";
import Lotto from "./Lotto.js";
//로또 관련 비즈니스 로직 처리
class LottoProcessor {
constructor(outputHandler, winningLotto) {
this.outputHandler = outputHandler;
this.winningLotto = winningLotto;
this.PurchaseLottoNumbersArray = [];
this.winningRanks = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
}
//로또 번호 생성 및 출력
setLottoNumbers(lottoCount) {
this.outputHandler.printLottoCount(lottoCount);
for (let i = 0; i < lottoCount; i++) {
const lottoNumbers = Lotto.generateLottoNumbers();
this.outputHandler.printLottoNumbers(lottoNumbers.getNumbers());
this.PurchaseLottoNumbersArray.push(lottoNumbers);
}
}
// 내가 산 로또 번호와 당첨 번호 비교
compareLottoNumbers() {
const winningNumbersArray = this.winningLotto.getWinningNumbersArray();
this.PurchaseLottoNumbersArray.forEach((lotto) => {
const matchCount = lotto.countMatchingNumbers(winningNumbersArray);
const isBonusMatched = lotto.hasBonusNumber(winningNumbersArray);
this.setWinningRanks(matchCount, isBonusMatched);
});
}
//winningRanks 업데이트
setWinningRanks(matchCount, isBonusMatched) {
if (matchCount === 6) this.winningRanks[1]++;
else if (matchCount === 5 && isBonusMatched) this.winningRanks[2]++;
else if (matchCount === 5) this.winningRanks[3]++;
else if (matchCount === 4) this.winningRanks[4]++;
else if (matchCount === 3) this.winningRanks[5]++;
}
//winningRanks 출력
getWinningRanks() {
return this.winningRanks;
}
// 수익률 계산
calculateRateOfReturn(lottoCount) {
const totalPrize = Object.keys(this.winningRanks).reduce(
(acc, rank) => acc + this.winningRanks[rank] * PRIZE_AMOUNT[rank],
0
);
const rate = (totalPrize / (lottoCount * LOTTO_UNIT)) * 100;
if (rate % 1 === 0) {
return rate;
} else if ((rate * 10) % 1 === 0) {
return rate.toFixed(1);
}
return rate.toFixed(2);
}
}
export default LottoProcessor;
사실 mvc 패턴 생각하고 코드를 짠건 아니지만, 까보면 내 나름대로 mvc 패턴대로 작업을 하고 있었던 것 같다.
LottoProcessor
클래스는 사실 컨트롤러 역할을 한다. lotto와 관련된 모든 비즈니스 로직들이 이 클래스 안에서 돌아가고 있기 때문이다.Lotto
와WinningLotto
는 model이 되는 것이다.
1. constructor(outputHandler, winningLotto)
outputHandler
: 출력 처리를 담당하는 객체 외부에서 받아오기
winningLotto
: 당첨 번호 및 보너스 번호를 관리하는 WinningLotto 객체
PurchaseLottoNumbersArray
: 사용자가 구매한 로또 번호들을 저장하는 배열
winningRanks
: 당첨 등수별 당첨 개수를 저장하는 객체 (ex. winningRanks[1]은 1등 당첨 횟수를 나타냄)
2. setLottoNumbers(lottoCount)
역할:
지정된 개수만큼 로또 번호를 생성하고, 출력하는 기능을 수행
동작:
- outputHandler.printLottoCount
를 호출하여 로또 구매 개수를 출력
- lottoCount
만큼 반복하여 로또 번호를 생성하고, PurchaseLottoNumbersArray
에 추가
- 각 로또 번호를 outputHandler.printLottoNumbers
로 출력
3. compareLottoNumbers()
역할: 구매한 로또 번호와 당첨 번호를 비교하여, 당첨 여부를 확인
동작:
- winningLotto.getWinningNumbersArray()
를 호출하여 당첨 번호와 보너스 번호가 표시된 배열을 가져온다.
- PurchaseLottoNumbersArray
에 저장된 각 로또 번호를 순회하며:
1. countMatchingNumbers
를 통해 당첨 번호와 일치하는 숫자의 개수를 계산
2. hasBonusNumber
를 통해 보너스 번호 일치 여부를 확인
3. setWinningRanks
메서드를 호출하여 당첨 결과를 winningRanks
에 업데이트한다.
4. setWinningRanks(matchCount, isBonusMatched)
역할:
당첨 개수(matchCount
)와 보너스 번호 여부(isBonusMatched
)에 따라 winningRanks
객체를 업데이트한다.
동작:
matchCount
와 isBonusMatched
값에 따라 당첨 횟수를 각각 증가
5. getWinningRanks()
역할:
현재 당첨 결과(winningRanks
)를 반환
6. calculateRateOfReturn(lottoCount)
역할:
수익률을 계산하여 반환
이렇게 총 3개의 파일이 lotto 객체와 관련된 일을 하는 로직들이다. 그래서 나는 이 3개의 파일을 한 폴더에 넣어놓고 관리를 했다. 지금 생각해보면
LottoProcessor
파일 같은 경우는, rank를 업데이트 해주는 모델을 하나 더 파서 관리했어도 좋았을 것 같고, 계산 관련 메소드도 다른 곳에서 관리했으면LottoProcessor
의 파일의 가독성이 더 좋아졌을 것 같다.
InputHandler.js
파일저번 주차까지는 App.js
파일 내부에서 사용자 입력들도 한꺼번에 관리했다. 하지만 이번엔 가독성과 코드 유지보수를 위해서 InputHandler
라는 클래스로 빼서 관리했다.
import { Console } from "@woowacourse/mission-utils";
import ErrorHandler from "../utils/ErrorHandler.js";
import {
ERROR_MESSAGES,
LOTTO_SIZE,
LOTTO_UNIT,
MAX_NUMBER,
MIN_NUMBER,
PROMPTS,
} from "../utils/constants.js";
// input들을 관리하는 클래스
class InputHandler {
async handleInput(prompt, validateFn) {
while (true) {
const input = await Console.readLineAsync(prompt);
try {
return validateFn(input);
} catch (error) {
Console.print(error.message);
}
}
}
async getPurchaseAmount() {
return this.handleInput(
PROMPTS.PURCHASE_AMOUNT,
this.validatePurchaseAmount
);
}
async getWinningNumber() {
return this.handleInput(PROMPTS.WINNING_NUMBER, this.validateWinningNumber);
}
async getBonusNumber(WinningLottoNumbersArray) {
return this.handleInput(PROMPTS.BONUS_NUMBER, (input) =>
this.validateBonusNumber(input, WinningLottoNumbersArray)
);
}
validatePurchaseAmount(input) {
const purchaseAmount = Number(input);
if (
isNaN(purchaseAmount) ||
purchaseAmount <= 0 ||
purchaseAmount % LOTTO_UNIT !== 0
)
ErrorHandler.throwError(ERROR_MESSAGES.PURCHASE_AMOUNT);
return purchaseAmount / LOTTO_UNIT;
}
validateWinningNumber(input) {
const numbers = input.split(",").map(Number);
if (numbers.length !== LOTTO_SIZE)
ErrorHandler.throwError(ERROR_MESSAGES.WINNING_SIZE);
if (numbers.some((number) => isNaN(number)))
ErrorHandler.throwError(ERROR_MESSAGES.NUMBER_ONLY);
if (numbers.some((number) => number < MIN_NUMBER || number > MAX_NUMBER))
ErrorHandler.throwError(ERROR_MESSAGES.RANGE);
if (new Set(numbers).size !== numbers.length)
ErrorHandler.throwError(ERROR_MESSAGES.DUPLICATE_NUMBER);
return numbers;
}
validateBonusNumber(input, WinningLottoNumbersArray) {
const bonusNumber = Number(input);
if (isNaN(bonusNumber)) ErrorHandler.throwError(ERROR_MESSAGES.NUMBER_ONLY);
if (bonusNumber < MIN_NUMBER || bonusNumber > MAX_NUMBER)
ErrorHandler.throwError(ERROR_MESSAGES.RANGE);
if (WinningLottoNumbersArray[bonusNumber] === 1)
ErrorHandler.throwError(ERROR_MESSAGES.DUPLICATE_WINNING_BONUS);
return bonusNumber;
}
}
export default InputHandler;
// handleInput 메서드
async handleInput(prompt, validateFn) {
while (true) {
const input = await Console.readLineAsync(prompt);
try {
return validateFn(input);
} catch (error) {
Console.print(error.message);
}
}
}
1. handleInput(prompt, validateFn)
역할: 사용자가 유효한 입력값을 제공할 때까지 입력을 받는 공통 메서드
동작:
Console.readLineAsync로 비동기적으로 사용자 입력을 받는다.
입력값을 validateFn을 사용해 검증하고, 유효하면 값을 반환(return)-> while 문이 종료된다.
입력값이 유효하지 않으면, 에러 메시지를 출력하고 다시 입력을 받는다.(while 문 유효)
공통메서드로 뺀 이유는 과제 명세서에 대답이 오류가 있으면 다시 재질문을 던지라는 명세가 있었기 때문에 공통메서드로 빼서 관리를 했다.
2. getPurchaseAmount()
역할: 로또 구매 금액을 입력받고 검증
3. getWinningNumber()
역할: 당첨 번호를 입력받고 검증
4. getBonusNumber(WinningLottoNumbersArray)
역할: 보너스 번호를 입력받고 검증
WinningLottoNumbersArray
를 넘기는 이유는 이미 당첨번호로 입력한 번호를 보너스 번호로 입력할 수 없기 때문에 검증상 파라미터로 전달한다.
OuputHandler.js
파일import { Console } from "@woowacourse/mission-utils";
// Output들을 관리하는 클래스
class OutputHandler {
// 로또 갯수 출력
printLottoCount(lottoCount) {
Console.print(`\n${lottoCount}개를 구매했습니다.`);
}
// 랜덤 로또 번호 출력
printLottoNumbers(lottoNumbers) {
Console.print(lottoNumbers);
}
// 최종 당첨 결과 출력
printResult(winningRanks) {
Console.print("\n당첨 통계");
Console.print("---");
Console.print(`3개 일치 (5,000원) - ${winningRanks[5]}개`);
Console.print(`4개 일치 (50,000원) - ${winningRanks[4]}개`);
Console.print(`5개 일치 (1,500,000원) - ${winningRanks[3]}개`);
Console.print(
`5개 일치, 보너스 볼 일치 (30,000,000원) - ${winningRanks[2]}개`
);
Console.print(`6개 일치 (2,000,000,000원) - ${winningRanks[1]}개`);
}
// 수익률 출력
printRateOfReturn(rateOfReturn) {
Console.print(`총 수익률은 ${rateOfReturn}%입니다.`);
}
}
export default OutputHandler;
OutputHandler
클래스는 로또 구매 개수, 생성된 로또 번호, 당첨 결과, 수익률과 같은 정보를 사용자에게 출력하는 클래스이다.
constants.js
파일export const MIN_NUMBER = 1;
export const MAX_NUMBER = 45;
export const LOTTO_SIZE = 6;
export const LOTTO_UNIT = 1000;
export const LOTTO_WINNING_FLAG = 1; // 당첨 번호 표시
export const LOTTO_BONUS_FLAG = 2; // 보너스 번호 표시
export const ERROR_MESSAGES = Object.freeze({
LOTTO_SIZE: `로또 번호는 ${LOTTO_SIZE}개여야 합니다.`,
DUPLICATE_NUMBER: "로또 번호에 중복된 숫자가 있습니다.",
PURCHASE_AMOUNT: `${LOTTO_UNIT}원 단위로 입력해 주세요.`,
WINNING_SIZE: `당첨 번호를 ${LOTTO_SIZE}개 입력해주세요.`,
NUMBER_ONLY: "숫자만 입력해주세요.",
RANGE: `${MIN_NUMBER}~${MAX_NUMBER} 사이의 숫자만 입력해주세요.`,
DUPLICATE_WINNING_BONUS: "당첨 번호와 중복됩니다.",
});
export const PROMPTS = Object.freeze({
PURCHASE_AMOUNT: "구입금액을 입력해 주세요.\n",
WINNING_NUMBER: "\n당첨 번호를 입력해 주세요.\n",
BONUS_NUMBER: "\n보너스 번호를 입력해 주세요.\n",
});
export const PRIZE_AMOUNT = Object.freeze({
1: 2000000000,
2: 30000000,
3: 1500000,
4: 50000,
5: 5000,
});
로또 프로그램에 사용되는 상수와 메시지를 정의한 constants
파일이다. 프로그램에서 반복적으로 사용되는 값들을 중앙에서 관리하며, 가독성과 유지보수성을 높이기 위해 개별 파일로 관리했다.
App.js
파일 import InputHandler from "./io/InputHandler.js";
import LottoProcessor from "./lotto/LottoProcessor.js";
import WinningLotto from "./lotto/WinningLotto.js";
import OutputHandler from "./io/OutputHandler.js";
class App {
constructor() {
this.inputHandler = new InputHandler();
this.outputHandler = new OutputHandler();
this.winningLotto = new WinningLotto();
this.lottoProcessor = new LottoProcessor(this.outputHandler, this.winningLotto);
}
async run() {
// 1. 로또 구매 개수 입력 받기
const lottoCount = await this.inputHandler.getPurchaseAmount();
this.lottoProcessor.setLottoNumbers(lottoCount);
// 2. 당첨 번호 입력 받기
const winningNumber = await this.inputHandler.getWinningNumber();
this.winningLotto.setWinningNumbers(winningNumber);
// 3. 보너스 번호 입력 받기
const bonusNumber = await this.inputHandler.getBonusNumber(this.winningLotto.getWinningNumbersArray());
this.winningLotto.setBonusNumber(bonusNumber);
// 4. 로또 번호 비교
this.lottoProcessor.compareLottoNumbers();
// 5. 당첨 결과 출력
this.outputHandler.printResult(this.lottoProcessor.getWinningRanks());
//6. 수익률 계산 및 출력
const rate = this.lottoProcessor.calculateRateOfReturn(lottoCount);
this.outputHandler.printRateOfReturn(rate);
}
}
export default App;
Lotto 과제의 핵심 흐름을 제어하는 클래스이다.!! App
클래스는 프로그램의 주요 단계를 순차적으로 실행하며, 여러 모듈을 조합하여 로또 게임이 돌아가게끔 작업을 해놨다.
1. constructor()
초기화된 객체들:
inputHandler
: 사용자 입력을 처리하는 InputHandler 객체.outputHandler
: 출력 처리를 담당하는 OutputHandler 객체.winningLotto
: 당첨 번호와 보너스 번호를 저장하는 WinningLotto 객체.lottoProcessor
: 구매한 로또와 당첨 번호를 비교하고 수익률을 계산하는 LottoProcessor 객체.2. run()
inputHandler
를 통해 사용자가 구매할 로또 개수를 입력받고, lottoProcessor
에서 갯수만큼 랜덤 로또 생성inputHandler
로 당첨 번호를 입력받고, winningLotto 객체에 당첨번호 전달inputHandler
로 보너스 번호를 입력받고, winningLotto
객체에 보너스 번호 전달lottoProcessor
가 구매한 로또와 당첨 번호를 비교하여 당첨 결과를 계산outputHandler
를 통해 각 당첨 등급의 결과를 출력lottoProcessor
에서 수익률을 계산하고, outputHandler
를 통해 수익률을 출력npm test
명령어로 테스트 코드 실행3주차과제도 마찬가지로, 우테코에서 주어진 기본 테스트말고, 내가 테스트하고 싶은 예외처리들을 테스트케이스로 추가 구현해봤다.
위 사진처럼 Lotto
모델 테스트, WinningLotto
모델 테스트, LottoProcessor
컨트롤러 테스트 를 추가해서 진행했다.
npm start
명령어로 직접 프로그램 실행처음에 이렇게 작업을 했는데, 계속 오류가 나는것이다.
일일이 console 을 찍어보니,
print한 로또 번호들과 , 실제 구매한 로또 배열에 들어가 있는 값이 다른 것이다,,
ㅜㅡ 코드를 생각하면서 짜자!
예전에 알고리즘 책을 읽다가 해시테이블에 대한 시간복잡도 효율성에 대해 알게되었다. 그래서 이번에 이를 한 번 적용해보고자 했다. 그래서 당첨번호와 보너스번호를 인덱스기반의 배열로 만들어 작업을 진행했다.
ResultLottoNumbers
0~46까지 0으로 배열 미리 생성
ResultLottoNumbers
배열을 활용해 1부터 45까지의 인덱스에 당첨 여부를 표시해 두면 비교 효율이 확실히 높아진다. 이렇게 하면:
O(1)
시간 복잡도ResultLottoNumbers[number]
형태로 접근해 빠르게 확인 가능하며, 코드를 간결하게 유지할 수 있다.3주차 과제를 진행하며 시간이 유난히 길게 느껴졌습니다. 이번 과제는 다소 높은 난이도로 느껴졌고, 특히 클래스화에 익숙하지 않아 도전적인 과제였습니다. 처음에는 1주차 과제 피드백에 나온 영상처럼 기능을 주석으로 정리하며 기본적인 동작이 가능하도록 만들었습니다. 이후, 모든 기능이 정상적으로 작동한 후 매일 조금씩 코드를 고민하며 개선해 나갔습니다. 기능 자체는 복잡하지 않았지만, 이를 클래스로 모듈 단위로 설계하고 구조화하는 좋은 경험이었습니다.
로또 클래스를 활용하는 부분에서 많은 고민이 있었습니다. #number
변수에 랜덤 로또 번호를 넣어 관리해야 하는지, 당첨 번호를 넣어야 하는지 혼란스러웠습니다. 여러 시도 끝에 주어진 Lotto
클래스는 오직 랜덤 번호 생성을 위한 용도로 활용하고, 추가로 WinningLotto
클래스를 만들어 당첨 번호와 보너스 번호를 관리하는 방향으로 진행했습니다. 이를 LottoProcessor
클래스에서 조합해 처리할 수 있도록 설계하였습니다.
또한, 로또 번호 확인 시 최대한 시간 복잡도를 줄이기 위해 해시 테이블 기반으로 작업을 시도했습니다. 크기가 45인 배열을 만들어 모든 값을 0으로 채워놓고, 인덱스 기반으로 당첨 번호를 해싱할 수 있도록 했습니다. 여러 시도 끝에 반복문을 사용하는 것보다 해시 테이블 기반이 성능 면에서 더 효율적이라는 점을 실감할 수 있었습니다.
2주차 피드백을 반영해, 값을 하드코딩하지 않으려고 노력했고, 주요 상수는 constants
파일로 분리해 관리했습니다. 또한 Object.freeze
메서드를 사용해 상수가 외부에서 변형되지 않도록 했습니다. 작업과 동시에 README.md
파일을 열어두고 계속 수정해나갔습니다. 이번 과제는 1, 2주차보다 고민을 많이 한 과제여서 README.md
의 수정도 빈번했습니다. 처음부터 모든 내용을 작성하기보다는, 2주차 피드백처럼 기능을 업데이트하고 추가해나가는 방식으로 진행했습니다.
아쉬운 점은 테스트 코드입니다. 이번 우아한 테크코스 프리코스를 진행하며 jest
라는 도구를 처음 알게 되었고, 이를 통해 오류를 빠르게 잡고 코드의 문제를 빠르게 수정할 수 있는 장점을 실감했습니다. 중간중간 테스트 코드를 작성해야 한다고 인지하고 있었으나, 실제 작업 중에는 자주 잊어버려 나중에 한꺼번에 테스트 코드를 작성하게 되었습니다. 다음 4주차 과제에서는 테스트 코드와 함께 작업하는 방식을 더 의식하며 진행할 계획입니다.
매주 과제를 하면서 어려웠던 점이나 새로 배운 점을 블로그와 제가 만든 회고 사이트인 라이톤(Lighton)에 기록하며 하루하루 성장하는 모습을 보며 뿌듯함을 느낍니다. 이제 1주밖에 남지 않은 프리코스지만, 끝까지 최선을 다해 마무리할 수 있도록 노력하겠습니다.
열심히 참여하신 노력과 정성이 느껴집니다!