따꿍의 프로젝트

[Precourse] 로또 본문

우테코

[Precourse] 로또

공장 주인 따꿍 2025. 11. 1. 19:08

학습 목표

  • 관련 함수를 묶어 클래스를 만들고, 객체들이 협력하여 하나의 큰 기능을 수행하도록 한다.
  • 클래스와 함수에 대한 단위 테스트를 통해 의도한 대로 정확하게 작동하는 영역을 확보한다.
  • 2주 차 공통 피드백(디스코드 참고)을 최대한 반영한다.

프로그래밍 요구 사항 

  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • Jest를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.
  • 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
  • else를 지양한다.
    • 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
  • 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
    • 단위 테스트 작성이 익숙하지 않다면 LottoTest를 참고하여 학습한 후 테스트를 작성한다.

참고할 라이브러리

import { Console, MissionUtils } from "@woowacourse/mission-utils";
await Console.readLineAsync("덧셈할 문자열을 입력해 주세요.");
Console.print(input);

MissionUtils.Random.pickNumberInRange(0, 9);

MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6);

기능 요구 사항

간단한 로또 발매기를 구현한다.

  • 로또 번호의 숫자 범위는 1~45까지이다.
  • 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
  • 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
  • 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
    • 1등: 6개 번호 일치 / 2,000,000,000원
    • 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
    • 3등: 5개 번호 일치 / 1,500,000원
    • 4등: 4개 번호 일치 / 50,000원
    • 5등: 3개 번호 일치 / 5,000원
  • 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
  • 로또 1장의 가격은 1,000원이다.
  • 당첨 번호와 보너스 번호를 입력받는다.
  • 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
  • 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시키고 해당 메시지를 출력한 다음 해당 지점부터 다시 입력을 받는다.


과제 진행 소감

1. __tests__안에 있는 예제 무조건 먼저 보기

여기 안에 있는 예제들은 약간 기능 명세서 같은 역할을 하는 것이다. 

이거 안 읽고 Lotto.js를 내 마음대로 구성을 했다. 

그 당시에는 로또 종이 개수 count를 constructor에 넣고,
그러면 자동으로 종이를 생성해주는 그런 클래스를 생성했다. 

 

근데 나중에 npm test를 돌려보니까 뭐 어떻게 해도 테스트가 실패하는 것 아니겠는가

그래서 LottoTest.js를 직접 들어가서 보니

describe("로또 클래스 테스트", () => {
  test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => {
    expect(() => {
      new Lotto([1, 2, 3, 4, 5, 6, 7]);
    }).toThrow("[ERROR]");
  });

  // TODO: 테스트가 통과하도록 프로덕션 코드 구현
  test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => {
    expect(() => {
      new Lotto([1, 2, 3, 4, 5, 5]);
    }).toThrow("[ERROR]");
  });

  // TODO: 추가 기능 구현에 따른 테스트 코드 작성
});

Lotto 클래스는 로또 종이 한장 그 자체를 뜻하고 있었는 것이다. 

로또 종이 안에 찍을 숫자를 넣으면

종이 한장을 생성해주는 그런 느낌이었다....

 

애초에 생각하고 있던 Lotto.js의 정의가 달랐으니 뭐 어떻게 해도 테스트가 안 돌아갔던 것이다.

다음에는 뭐 작성하기 전에 미리 테스트부터 보도록 하겠다.

 

2. 함수 쪼개기

error.js랑 io.js에서 반복되는 구절이 계속 있어서

"공통 에러 확인", 그리고 "공통 io"를 꺼내줬다. 

그러니까 코드 줄 수가 확 줄어들었다. 

귀찮긴 해도, 코드를 다시 보고 리팩토링하고, 반복되는 코드는 따로 빼서 만들어주는 것이 얼마나 중요한지 느꼈다. 

 

3. 새로 배운 문법 / 복습해야하는 문법

- array에 element가 있는지 확인하기

arrayName.includes(element)

 

- 숫자인지 확인하기

isNan(num)

 

- integer인지 확인하기

Number.isInteger(num)

 

- class 구조

import { Console } from "@woowacourse/mission-utils";
class Lotto {
  #numbers;

  constructor(numbers) {
    this.#validate(numbers);
    this.#numbers = numbers;
  }

  #validate(numbers) {
    if (numbers.length !== 6)
      throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
    if (new Set(numbers).size !== numbers.length)
      throw new Error("[ERROR] 로또 번호는 중복될 수 없습니다.");
  }

  getNumbers() {
    return this.#numbers;
  }
  print() {
    Console.print(`[${this.#numbers.join(", ")}]`);
  }
}

export default Lotto;

#는 private을 뜻한다고 한다. 

# 없는 모든 애들은 public이다. 

 

- count개 Array의 Array를 만들기

Array.from(arrayLike, mapFn)

 

  • arrayLike: something with a length property (like { length: 3 } or arguments or a NodeList).
  • mapFn: a function to run for each element being created.

 

Array.from({length: count}, () => MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6))

 

- Array ascending order로 나열하기

ArrayName.sort((a,b) => a-b)

 

- total 구할때 reduce 사용하기

let moneyEarned = 0;
LottoRank.forEach((rank) => {
    moneyEarned += rank.prize * rank.count;
});

//는 이걸로 줄일 수 있다. 
const moneyEarned = LottoRank.reduce(
  (sum, rank) => sum + rank.prize * rank.count,
  0
);

 

- 한 array가 다른 array랑 얼마나 겹치는지 확인하기

const matchCount = ArrayA.filter((num)=> ArrayB.includes(num)).length

arrayA의 각 element에 대해서 ArrayB에 존재하는지 확인하고,

존재하는 녀석들만 새 array에 넣어준다. 

결국에는 ArrayB에 존재하는 arrayA element들만 존재하게 되고,

그걸 length로 세면 얼마나 겹치는지 알 수 있다. 

 

4. 좀 더 자세한 README 작성

리드미에 모든게 정의되어 있으니까 

뭐 해야할지 모르겠으면 거기 가면 되니 편했다.