[우아한테크코스] 프리코스 3주차 회고

정균·2022년 11월 15일
1

우아한테크코스

목록 보기
3/15
post-thumbnail

3주차 미션 - 로또

미션 레포지토리

🚀 기능 요구 사항

로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.

로또 번호의 숫자 범위는 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원이다.
  • 당첨 번호와 보너스 번호를 입력받는다.
  • 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
  • 사용자가 잘못된 값을 입력할 경우 throw문을 사용해 예외를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 종료한다.

추가된 프로그래밍 요구사항

  • 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
    • 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
  • else를 지양한다.
    • 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
    • 때로는 if/else, switch문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
  • 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(Console.readLine, Console.print) 로직에 대한 단위 테스트는 제외한다.
    • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
    • 단위 테스트 작성이 익숙하지 않다면 __tests__/LottoTest.js를 참고하여 학습한 후 테스트를 구현한다.

🎯 3주차의 주요 목표

1. 클래스(객체)를 분리하는 연습
2. 도메인 로직에 대한 단위 테스트를 작성하는 연습

✏️ 3주차에서 배운점

1. 단위 테스트

이번 주차에는 도메인 로직에 대한 단위 테스트를 연습하는 것이 주요 목표 중 하나이다.

처음 이 목표를 봤을 때 가장 먼저 든 생각은 도메인 로직이 뭐지? 라는 생각이었다.

1-1. 도메인 로직이란?

도메인 로직이라는 말이 잘 와닿지 않아서 용어를 구글링 해본 결과, 프로그래밍으로 해결하고자 하는 주제에 대한 영역 이라고 한다.

그러나 이 말도 조금 추상적이라서 나에게 확 와닿지 않았고, 조금 더 찾아본 결과 현실과 가까운 영역, 소프트웨어의 성능 보다는 하나의 행위에 대한 로직을 의미한다고 이해했다.

UI(입출력), DB, 통신과 같은 로직은 제외하고 순수한 비즈니스 영역에 대한 솔루션 로직을 의미한다고 한다.

예를 들어, 쇼핑몰을 개발 한다고 할 때, 상품 주문, 장바구니 상품 금액 계산과 같은 로직이 쇼핑몰의 도메인 로직이 되고,
모바일 송금의 경우 계좌 잔액 확인, 수수료 계산 등이 도메인 로직이 될 수 있다.

1-2. 로또 애플리케이션의 도메인은?

도메인에 대해 조금은 알 것 같다.

그렇다면, 이번 주차에서 구현해야 하는 로또 애플리케이션의 도메인 로직은 어떻게 될까?

  • 입력받은 금액에 따른 로또 생성
  • 입력받은 당첨 번호와 보너스 번호로 당첨 로또 생성
  • 당첨 로또와 플레이어가 구매한 로또를 비교하여 등수 계산
  • 구매 금액과 총 상금을 비교하여 수익률 계산

내가 생각한 로또 앱의 큰 도메인 로직은 위와 같다.

크게 4개의 큰 로직을 따라 애플리케이션이 실행된다고 생각했다.

아직 도메인 로직에 대해 많이 어설픈 상태이기 때문에 위 목록처럼 나누는게 맞는지는 모르겠다.

혹시 다른 의견이 있다면 댓글로 남겨주시면 좋을 것 같다.

1-3. 도메인에 맞게 단위 테스트 작성하기

각 도메인 별로 테스트 파일을 만들었다.

LottoTest

로또 생성에 대한 단위 테스트이다.

로또를 생성할 때 비정상적인 번호가 입력 될 경우의 에러 처리들을 테스트한다.

LottoMachineTest

입력받은 금액에 따른 로또 생성 도메인에 대한 테스트이다.

비정상적인 금액이 입력될 경우 에러 처리를 테스트하고,

정상적인 금액이 입력될 경우 금액에 맞는 개수의 로또가 생성 되는지 테스트한다.

WinningLottoTest

입력 받은 당첨 번호와 보너스 번호로 당첨 로또 생성 도메인에 대한 테스트이다.

당첨 번호 혹은 보너스 번호가 비정상적일 경우 에러 처리를 테스트한다.

RankCalculatorTest

당첨 로또와 플레이어의 로또들을 비교해서 등수 계산을 하는 도메인에 대한 테스트이다.

로또 배열과 당첨 로또 객체를 비교해서 올바른 등수 횟수들을 담은 배열을 반환하는지 테스트한다.

YieldCalculatorTest

구매 금액과 총상금을 비교해서 수익률 계산 도메인에 대한 테스트이다.

구매 금액과 등수를 담은 배열을 입력 후 수익률이 정상적으로 나오는지 테스트한다.

2. 클래스 분리

2-1. 클래스

지난 주차에서 따로 학습했던 클래스가 이번 주차의 주요 목표로 제시되었다.

지난 주차에는 자바스크립트 클래스의 이론을 주로 공부했는데, 이번 주차는 클래스를 직접 구현해가며 학습 할 수 있다는 점이 기대가 되었었다.

그러나 클래스 분리는 생각보다 쉽지않았다..

단순히 잘게 쪼개기만 했던 지난 주차의 함수 분리와는 달리 클래스 분리는 하나의 객체를 만드는 것이므로, 객체를 나누는 기준을 세워야 했다.

뿐만 아니라 클래스 명이나 클래스 변수들을 객체의 의도에 맞도록 잘 설계를 해야했다.

모니터 앞에서 가만히 고민하는 시간이 함수 분리를 할 때 보다 훨씬 길었다.

이번 주차 동안..

클래스명이나 클래스의 구조를 계속해서 수정했고, 수정했던 구조를 다시 예전 구조로 롤백하기도 했다.

또한, 하나의 클래스를 계속 나누기도 하고 병합하기도 했다.

그러다보니 커밋 수가 이전 주차보다 엄청 많아졌고, 코드를 객체 지향적으로 잘 짜는 것은 정말 많은 고민이 필요하다는 것을 이번 주차에서 많이 느꼈다.

2-2. 도메인에 따른 클래스 분리 과정

도메인 단위 별로 하나의 클래스를 만들고자 했다.

사실 위에 작성된 도메인은 최신 도메인이고, 초기의 도메인과는 약간의 차이가 있다.

초기 도메인과 클래스

초기 도메인은 다음과 같았다.

- 로또 구매 및 생성
- 당첨 로또 생성
- 당첨 통계 계산

위와 같이 세개의 도메인을 중심으로 클래스들을 구성했다.

- LottoMachine class
- WinningLotto class
- WinningCalculator class

그러나 위 클래스 목록은 각 클래스마다 덩치가 크다고 느껴졌다.

로또 구매 및 생성 도메인의 경우 ‘금액 입력’과 ‘로또 생성’ 기능이 같이 있었고,

당첨 통계는 ‘등수 계산’, ‘상금 계산’ 기능이 같이 있었다.

나는 이 클래스을 더 세분화하고 싶었다.

세분화한 도메인과 클래스

세분화한 도메인은 다음과 같았다.

- 구매 금액 저장
- 금액에 따른 로또 생성
- 당첨 로또 생성
- 당첨 로또와 플레이어가 구매한 로또를 비교하여 등수 계산
- 등수를 통해 총 상금 계산

최대한 도메인 당 하나의 기능을 하도록 했고 총 5개의 도메인이 분리되었다.

다섯개의 도메인은 다섯개의 클래스로 표현되었다.

- LottoSeller class
- LottoGenerator class
- WinningLotto class
- RankCaculator class
- PrizeCalculator class

하지만 이 클래스 분리도 아쉬운 점이 존재했다.

LottoSeller 클래스가 독립된 하나의 클래스로 존재하기에는 기능(구매 금액 저장)이 너무 부실해보였다.

이 점에 대해 계속해서 고민을 했고, 결국 구매 금액 저장과 로또 생성은 하나의 연결된 흐름이므로 하나의 클래스에 저장되는 것이 맞다고 판단했다.

또한, PrizeCalculator(상금 계산기) 클래스의 클래스명도 조금 이상하다고 생각했다.

최종적으로 구해야 하는 데이터는 수익률인데, 수익률 계산기가 아닌 상금 계산기라고 하는 점을 바꾸고 싶었다.

최종 도메인과 클래스

고민 끝에 결정된 최종 도메인은 다음과 같다.(1번 목차의 도메인과 같음)

- 입력받은 금액에 따른 로또 생성
- 입력받은 당첨 번호와 보너스 번호로 당첨 로또 생성
- 당첨 로또와 플레이어가 구매한 로또를 비교하여 등수 계산
- 구매 금액과 총 상금을 비교하여 수익률 계산

도메인에 따른 최종 클래스 분리는 다음과 같다.

- LottoMahcine class
- WinningLotto class
- RankCaculator class
- YieldCalculator class

LottoSeller 클래스와 LottoGenerator 클래스를 합쳐서 다시 LottoMachine 클래스로 롤백했다.

그리고 PrizeCalculator 클래스명도 클래스의 의도에 맞게 YieldCalculator(수익률 계산기)로 바꿨다.

3. 이전 주차 공통 피드백 적용

이전 주차의 공통 피드백을 모두 지키려고 노력했다.

그 중에서 특히 더 신경 쓴 피드백들은 다음과 같다.

3-1. README를 상세히 작성하라

이전 주차까지는 docs 폴더의 README에 기능목록만 작성했었다.

그러나 저번 주 공통 피드백에서 저장소의 README를 상세히 해보라는 피드백이 있었다.

Velog 글 작성을 위해 기존에 학습했던 마크다운 문법을 이번에 열심히 적용해봤다.

또한 마크 다운으로 표현 불가능한 스타일(가운데 정렬 등)은 html 태그를 사용해서 작성했다.

README를 작성하면서 이 미션을 참여하지 않는 사람도 쉽게 이해할 수 있도록 게임 방법을 상세히 작성하려고 노력했고,

각 클래스 별 생성자와 메소드의 사용법 또한 작성했다.

작성한 README는 여기서 볼 수 있다.

3-2. 값을 하드 코딩하지 않는다.

사실, 값들의 상수 처리는 이전 주차에 적용 했었지만, 제출 전 예기치 못한 오류로 인해 다 지워버렸다 ㅠ

그렇기 때문에 저번 주차에 지워버렸던 아쉬움을 만회하기 위해 이번 주차에 더 신경써서 적용하려고 했다.

  LOTTO_LENGTH: 6,
  MAX_NUMBER: 45,
  MIN_NUMBER: 1,

  MATCH_COUNT: {
    FIRST_RANK: 6,
    SECOND_RANK: 5,
    THIRD_RANK: 5,
    FORTH_RANK: 4,
    FIFTH_RANK: 3,
  },

  PRIZE_AMOUNT: {
    FIRST_RANK: 2000000000,
    SECOND_RANK: 30000000,
    THIRD_RANK: 1500000,
    FORTH_RANK: 50000,
    FIFTH_RANK: 5000,
  },

MATCH_COUNT와 PRIZE_AMOUNT 처럼 여러 값이 필요한 상수는 객체를 사용해 여러 값을 저장했다.

문자열을 담은 상수를 저장할 때의 고민

일반적인 문자열의 경우 상관이 없었지만, 문자열 중간에 동적인 값을 넣고 싶을 때 어떻게 상수로 저장할지 고민에 빠졌다.

고민 끝에 다음과 같은 방법으로 상수를 저장했다.

PURCHASE_LOTTO(number) {
  return `${number}개를 구매했습니다.`;
},
  
RATE_OF_RETURN(rateOfReturn) {
  return `총 수익률은 ${rateOfReturn.toLocaleString()}%입니다.`;
},

위와 같이 함수를 사용해서 상수들을 저장했다.

함수를 사용하니 중간에 값을 넣은 문자열을 가져올 수 있었다.

profile
💻✏️🌱

0개의 댓글