우테코-프리코스 🎱 로또
📌 미션 - 로또
🔍 진행 방식
- 미션은 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 세 가지로 구성되어 있다.
- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.
🚀 기능 요구 사항
로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.
1 | - 로또 번호의 숫자 범위는 1~45까지이다. |
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
- 로또 1장의 가격은 1,000원이다.
- 당첨 번호와 보너스 번호를 입력받는다.
- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우 throw문을 사용해 예외를 발생시키고, “[ERROR]”로 시작하는 에러 메시지를 출력 후 종료한다.
입출력 요구 사항
입력
- 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다.
1 | 14000 |
- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.
1 | 1,2,3,4,5,6 |
- 보너스 번호를 입력 받는다.
1 | 7 |
출력
- 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.
1 | 8개를 구매했습니다. |
- 당첨 내역을 출력한다.
1 | 3개 일치 (5,000원) - 1개 |
- 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)
1 | 총 수익률은 62.5%입니다. |
- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 “[ERROR]”로 시작해야 한다.
1 | [ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다. |
실행 결과 예시
1 | 구입금액을 입력해 주세요. |
🎯 프로그래밍 요구 사항
- Node.js 14 버전에서 실행 가능해야 한다. Node.js 14에서 정상적으로 동작하지 않을 경우 0점 처리한다.
- 프로그램 실행의 시작점은 App.js의 play 메서드이다. 아래와 같이 프로그램을 실행시킬 수 있어야 한다.
예시
1 | const app = new App(); |
- package.json을 변경할 수 없고 외부 라이브러리(jQuery, Lodash 등)를 사용하지 않는다. 순수 Vanilla JS로만 구현한다.
- JavaScript 코드 컨벤션을 지키면서 프로그래밍 한다
- 프로그램 종료 시 process.exit()를 호출하지 않는다.
- 프로그램 구현이 완료되면 ApplicationTest의 모든 테스트가 성공해야 한다. 테스트가 실패할 경우 0점 처리한다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- Jest를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
추가된 요구 사항
- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
- else를 지양한다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- 때로는 if/else, switch문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(Console.readLine, Console.print) 로직에 대한 단위 테스트는 제외한다.
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
- 단위 테스트 작성이 익숙하지 않다면 tests/LottoTest.js를 참고하여 학습한 후 테스트를 구현한다.
라이브러리
- MissionUtils 라이브러리에서 제공하는 Random 및 Console API를 사용하여 구현해야 한다.
- Random 값 추출은 MissionUtils 라이브러리의 Random.pickUniqueNumbersInRange()를 활용한다.
- 사용자의 값을 입력 받고 출력하기 위해서는 MissionUtils 라이브러리에서 제공하는 Console.readLine, Console.print를 활용한다.
사용 예시
1 | const numbers = MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6); |
Lotto 클래스
- 제공된 Lotto 클래스를 활용해 구현해야 한다.
- numbers의 # prefix를 변경할 수 없다.
- Lotto에 필드를 추가할 수 없다.
1 | class Lotto { |
회고
🐥 당첨 번호 및 로또 번호 예외 처리
- 당첨번호 에러 처리할 때 중복검사 뿐만 아니라 범위도 확인해줘야 한다. 게다가 보너스 번호는 당첨번호와 중복 검사도 추가로 해줘야 한다.
🦉 typeof NaN === ‘number’
- “4r”이라는 문자열을 숫자로 형변환 해주면 NaN 값이 나오게된다. 에러처리를 할 때 type이 ‘number’인지를 확인해주도록 하였는데, NaN은 number 타입을 가지고 있으므로 이에 대한 처리를 추가해줘야했다.
🦆 하드코딩 vs 소프트코딩
score 객체에 3등 4등 5등 의 키 값은 숫자로만 주었는데, bonus 등수는 문자열로 주어서 출력시 5등 4등 3등 2등 1등 Bonus등수가 출력되어서 이를 해결하기 위해 score의 key 값을 문자열로 변경하였다.
lotto 배열의 요소도 [1, 2, 3, 4, 5, 6] 이런 형태의 배열인데, print 결과물이 “[1, 2, 3, 4, 5, 6]” 이렇게 나와야 하므로 이에 대한 처리를 해주었다.
- 백틱만 사용
1 | this.Lotto.forEach((lotto) => MissionUtils.Console.print(`${lotto}`)); |
- 문자열에 [ ] 삽입
1 | this.Lotto.forEach((lotto) => MissionUtils.Console.print(`[${lotto}]`)); |
- 문자열 배열처럼 처리
1 | this.Lotto.forEach((lotto) => |
🐒 숫자를 천 단위 콤마찍은 문자열로 변환하기
Number.prototype.toLocaleString()
메서드를 사용하면 천단위에 ,를 찍은 문자열로 변환한다.
✏️ 피드백 (22.11.17 추가)
비즈니스 로직과 UI로직 구분
- 한 클래스에서 담당하지 않고 구분하여야 한다.
1 | class Lotto { |
객체의 상태 접근 제한
1 | // 내 코드 |
- 위 와 같은 경우 객체의 데이터를 외부에서 접근 가능하므로 이는 디버깅과 유지보수를 어렵게하므로 접근 제한자를 설정하고 선택된 메서드만으로 데이터를 참조하거나 변경할 수 있도록 개선한다.
객체는 객체답게 사용
1 | class Lotto { |
- 로또 클래스는 로또 숫자를 가져오는 역할만 한다.
- 로또게임 클래스는 play() 메서드를 호출 하였을 경우 이와 관련된 로직으로 구성한다.
1 | class Lotto { |
- getter를 사용하여 값을 꺼내 사용하는 대신 다른 객체에 메시지를 건네주자. 객체가 스스로 일하도록 하는 객체 지향 프로그래밍 기법을 사용하자.
- 출력을 위한 값, 순수 값 프로퍼티를 가져오기 위해서라면 getter를 허용한다.
필드 수를 줄이도록 하자
1 | // 리팩터링 전 |
- 위 객체의 profitRate와 totalPrize는 등수 별 당첨 내역(result)만 있어도 모두 구할 수 있는 값이다. 따라서 위 객체는 다음과 같이 하나의 필드만으로 구현할 수 있다.
🏓 소감
2주차때 클래스구조와 테스트에 대해서 조금은 익숙해져서 3주차 미션을 받았을 때 구조가 비슷하여 조금 마음이 놓였다.
jest expect
함수와matcher
함수를 찾아보면서 단위 테스트 기능에 대해 공부해볼 수 있는 기회여서 좋았다.- 로또 클래스와 비즈니스 로직 클래스를 구분하여 테스트를 진행하고 관심사를 분리하면서 개발을 하니 유지보수가 편리하다는 것을 느낄 수 있었다.