프리코스 3주 차 - 로또 게임

1. 개요

22년 11월 9일 15시에 3주 차 미션 안내 이메일이 도착했다. 일주일 동안 이루어진 우아한테크코스 프리코스 3주 차 로또 게임 미션에 대한 간단한 소개와 소감을 작성한다.


2. 미션 소개

프리코스 3주 차의 미션은 로또 게임을 만드는 것이었다. 추가된 프로그래밍 요구 사항은 아래와 같다.

  1. 함수(또는 메서드)의 길이가 15라인 넘어가지 않도록 구현한다.

  2. else를 지양한다.

  3. 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(Console.readLine, Console.print) 로직에 대한 단위 테스트는 제외한다.

또한 메일에는 3주차 미션에서 추가된 학습 목표도 제시하였다. 내용은 아래와 같다.

  1. 클래스(객체)를 분리하는 연습

  2. 도메인 로직에 대한 단위 테스트를 작성하는 연습

새롭게 추가된 프로그래밍 요구 사항과 학습 목표를 바탕으로 이번 주차 미션을 해결하고자 노력하였다.

더욱 자세한 내용은 아래의 미션 저장소를 참고하면 된다.

우아한 테크코스 3주차 로또 게임 미션 저장소


3. 간결한 기능 목록 작성

저번 주 차 미션인 숫자 야구 게임 미션에서의 기능 목록 작성은 자세하지만 간결하지 못했던 아쉬움이 있다. 그래서 3주 차 미션에서는 자세함은 유지하지만 간결함을 더할 수 있도록 다짐하였다.

일단 내가 정의한 자세함이란 기능 목록만 보고도 전체를 파악할 수 있고 예외 사항도 알 수 있다. 이다. 이를 바탕으로 2주 차 미션에서는 함수 하나하나 까지 세분화하였다. 3주 차 미션에서 바뀐 점은 함수 하나하나 까지 세분화하는 것이 아니라 여러 함수들이 포함 될 수 있는 기능까지 작성하였다는 것이다.

즉, 내가 정의한 간결함이란 함수는 유연하게 추가, 수정이 될 수 있으므로 여러 함수를 포함할 수 있는 기능 이다. 물론 하나의 기능에 하나의 함수만 필요할 수도 있다. 간결함을 추가하고 나니 개인적으로 이전 보다 더욱 깔끔한 기능 목록을 작성을 할 수 있었다고 생각한다.

추가적으로 함수 하나하나를 세분화하여 기능 목록을 작성하지 않은 이유는 아래와 같다.

  1. 함수 하나까지 생각하며 기능 목록을 작성하면 전체적인 흐름 파악에 어려움을 느낄 수 있다. (주객전도)

  2. 필요한 함수는 직접 코드를 작성하면서 추가, 삭제될 수 있다.

  3. 리팩터링을 통해 함수를 분리할 수 있고 함수명 또한 변경될 수 있다.

  4. 수정이 언제든지 가능한 살아있는 기능 목록을 만들고자 하였다.

3주차 미션의 기능 목록 링크


4. 클래스의 기본 학습

3주 차 미션부터 클래스를 사용하였고 클래스를 분리하는 연습을 하고있다. 클래스란 무엇일까? 내가 알고 있는 클래스의 개념은 현실 세계에 있는 어떤 것을 프로그래밍 세계로 그대로 옮긴 무언가이다. 또한 클래스를 통해 인스턴스가 만들어진다는 것을 알고 있었다.

여러 프로젝트를 진행하면서 단 한 번도 클래스를 사용한 적이 없다. 정말 없다...(그나마 자료 구조를 공부할 때 링크드리스트, 힙 등을 구현할 때가 유일하다.) 그렇기 때문에 다소 취약한 점이 있기에 클래스에 대한 학습을 진행하였다.

이번 학습을 통해 알게 된 핵심을 간단히 정리하면 아래와 같다.

  • 정적 메서드에서는 this 키워드를 사용할 수 없다.

  • Class field declarations proposal을 통해 클래스 변수 앞 "#"의 의미

물론 아직 클래스에 대한 모든 학습이 이루어지지 않았다. 상속, super, 캡슐화 등등 배워야 할 것들이 산더미이다. 모든 것을 학습하겠다고 무작정 달려들었다면 금방 지칠 테니 필요한 개념이 생기면 바로바로 정리하고자 한다.

아래는 이번 클래스를 학습하면서 정리한 TIL이다.

클래스의 기본


5. Lotto 클래스의 의미

제공된 Lotto 클래스를 활용해 구현해야 한다. 이라는 프로그래밍 요구 사항이있다. 제공된 Lotto 클래스의 구조는 아래와 같다.

class Lotto {
  #numbers;

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

  validate(numbers) {
    if (numbers.length !== 6) {
      throw new Error();
    }
  }

  // TODO: 추가 기능 구현
}

기능 목록을 작성하면서 이 Lotto 클래스가 과연 어떤 역할을 하는지 고민을 하였다. 특히 Lotto 클래스의 의미를 먼저 정의하고 코드를 구현하기로 했다.

  1. 플레이어가 구매한 로또(복수)를 의미

  2. 각각의 로또 하나(단수)를 의미

과연 어떤 의미일까? 정답이 정해져 있는 것일까? 잘못 의미를 부여하면 어떡하지? 등과 같은 고민이 시작되었고 고민 끝에 각각 하나씩 존재하는 로또로 결론을 내렸다. 즉, 플레이어가 구매 금액을 입력하게 되면 Lotto 클래스를 통해 구매 금액에 맞게 Lotto 인스턴스들이 생성될 수 있도록 코드를 작성하였다.

그렇다면 Lotto 클래스를 통해 만들어진 다수의 Lotto 인스턴스들은 어디에서 관리를 해야 할까? 난 Lottos 클래스를 만들어 관리를 하였다. Lottos 클래스의 변수로 list를 만들어 배열의 형태로 관리를 하였고 Lottos 클래스를 통해 Lotto 인스턴스를 만들고자 하였다.

아래의 Lottos 클래스를 간단하게 분석해보자.

class Lottos {
  constructor(money) {
    this.validate(money);
    this.count = money / MONEY.UNIT;
    this.list = [];
    this.publish();
  }

  validate(money) {
    // 생략
  }

  publish() {
    for (let num = 0; num < this.count; num++) {
      const newLotto = this.createNewLotto();
      this.list.push(newLotto);
    }
  }

  createNewLotto() {
    const newNumbers = Random.pickUniqueNumbersInRange(
      LOTTO.MIN_NUMBER,
      LOTTO.MAX_NUMBER,
      LOTTO.NUMBERS_COUNT
    );

    return new Lotto(newNumbers);
  }

  // 생략
}

Lottos 클래스는 money를 받아 인스턴스를 생성한다. 이때 전달된 money 만큼 로또를 발행한다. 사용되는 메서드는 publishcreateNewLotto이다. createNewLotto 메서드에서 new Lotto()를 호출하여 Lotto 인스턴스를 생성하는 것을 확인할 수 있다.


6. 각각의 클래스에 대한 테스트 코드 작성

3주 차 미션인 로또 게임을 구현하면서 사용한 클래스는 Lotto, Lottos, WinningNumbers, BonusNumber이고 사용한 객체는 Validation이다. 이를 위한 테스트 코드를 각각 따로 파일을 만들어 작성하였다.

각각의 파일에서는 전체를 먼저 테스트하지 않고 클래스의 메서드를 차례대로 테스트 하였다. 특히 예외 사항도 꼼꼼하게 테스트를 할 수 있도록 노력하였다.

하지만 아쉬운 점이 있다 바로, UI에 대한 테스트를 직성하지 않았다는 것이다. 기존에 존재하는 ApplicationTest.js을 보면 Console.print(), Console.readLine()를 테스트하기 위해 mocking functions을 사용하고 있다. 현재 4주 차 미션을 진행하고 있는 시점에서는 해당 개념에 대한 학습을 하였기에 UI에 대한 테스트 코드를 작성하였지만 3주 차에서는 하지 못했다는 것이 아쉽게 느껴진다.


7. Conclusion

3주 차 미션에서는 놓친 출력 요구 사항이 있다. 바로, 수익률에서의 ,이다. 구현은 크게 어렵지 않았던 것이라 더욱 아쉽게만 느껴졌다. 더 꼼꼼하게 볼 순 없었던 것일까?... 스스로 자책도 했지만ㅠㅠ 이미 지나간 미션이라 더 이상의 미련은 두지 말고 마지막 남은 4주 차 미션에 더욱 신경을 쓰기로 하였다. 그래도 마음 한 편으론 계속 생각이 난다. 흑흑 아쉬운 점을 뒤로 하고 이번 주차에서 가장 많은 고민을 했던 부분이 클래스의 분리이다. 아무래도 클래스를 많이 다뤄보지 않았기 때문에 분리하는 데 큰 어려움을 겪었다. 클래스를 분리하지 않고 모두 App 클래스, Lottos 클래스, Lotto 클래스를 통해 구현을 할 수 있었지만 당장의 편의가 아니라 앞으로 프로그래머가 된 후 구현하게 될 큰 기능을 위해 분리하는 기준을 정하고자 하였다. 일단 내가 정한 기준은 어떤 인스턴스가 필요한가?이다. 너무 간단하지만 현재로선 이것이 최선이다. 많은 경험을 바탕으로 구체적인 기준을 세워보도록 하자. 남은 4주 차 미션도 파이팅~!!!!!


📅 2022-11-19

Last updated