✔ 로또
미션 제출 방법
- 미션 요구사항을 파악해 기능을 구현한 후 GitHub을 통해 add, commit, push
- 미션을 모두 완료 후 Pull Request로 woowacourse에 제출
- 우아한테크코스 지원 플랫폼에 GitHub ID, Pull Request 주소, 과제 진행 소감을 프리코스 과제로 제출
2주차 공통 피드백 정리
1) 리드미를 상세히 작성한다
2) 기능 목록을 재검토한다
3) 기능 목록을 업데이트한다
4) 값을 하드 코딩하지 않는다
5) 구현 순서도 코딩 컨벤션이다 (상수, 멤버 변수, 생성자, 메소드 순으로 작성)
6) 변수 이름에 자료형은 사용하지 않는다
7) 한 함수가 한 가지 기능만 담당하게 한다
8) 함수가 한 가지 기능을 하는지 확인하는 기준을 세운다 (중복이 없는지 확인, 15라인을 넘어가지 않도록 하기)
9) 처음부터 큰 단위의 테스트를 만들지 않는다
진행 방식
1) 과제 진행 요구 사항 파악하기
1. 미션 저장소를 Fork & Clone해 시작하기
2. 기능을 구현하기 전에 구현할 기능 목록을 정리해 추가하기
3. 커밋 단위는 기능 목록 단위로 추가 (커밋 메시지 컨벤션 가이드를 참고해 커밋 메시지를 작성)
2) 프로그래밍 요구 사항 파악하기
1. JDK 17 버전에서 실행
2. 프로그램 실행의 시작점은 Application의 main()
3. Java 코드 컨벤션 가이드를 준수하여 프로그래밍
4. build.gradle 파일은 변경할 수 없음
5. ApplicationTest의 모든 테스트가 성공해야 함
6. 파일, 패키지 이름을 수정하거나 이동하지 않음
7. Randoms의 pickNumberInRange() 및 Console의 readLine() API를 사용하여 구현
8. indent depth를 3이 넘지 않도록 구현
9. 3항 연산자를 쓰지 않음
10. 함수가 한 가지 일만 하도록 최대한 작게 만들어라
11. 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인하라 (각 함수별로 테스트를 작성)
12. 함수의 길이가 15라인을 넘어가지 않도록 구현하라
13. else 예약어를 쓰지 마라
14. Java Enum을 적용해라
15. 도메인 로직에 단위 테스트를 구현해라 (단, UI 로직은 제외)
16. 제공된 Lotto 클래스를 활용해 구현해라
3) 기능 요구 사항 파악하기
[게임 설명 - 로또]
1. 로또 구입 금액을 입력하여 구입 금액에 해당하는 로또를 발행
2. 당첨 번호와 보너스 번호를 입력 받아 비교
3. 당첨 내역 및 수익률을 출력하고 로또 게임을 종료
[기능 요구 사항 설명]
1. 로또 구입 금액을 입력
2. 로또 1장의 가격인 1000원에 따라 해당하는 만큼 로또 발행
2. 당첨 번호와 보너스 번호를 입력
3. 발행된 로또 번호와 당첨 번호를 비교
4. 당첨 내역 및 수익률 출력 후 게임 종료
5. 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고
'[ERROR]'로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받음
[입출력 요구 사항 설명]
구입금액을 입력해 주세요.
8000
8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]
당첨 번호를 입력해 주세요.
1,2,3,4,5,6
보너스 번호를 입력해 주세요.
7
당첨 통계
---
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.
구현할 기능 목록 정리
1) 게임이 시작되면 로또 구입 금액을 입력받기 위해 '구입금액을 입력해 주세요.'를 출력
2) 로또 구입 금액 저장
로또 1장의 가격은 1000원이므로 1000원 단위로 입력 받으며 구입 금액에 해당하는 로또 수량 계산
[예외]
숫자가 아닐 때
1000원보다 작을 때
1000원으로 나누어 떨어지지 않을 때
→ 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고
'[ERROR]'로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받음
3) 로또 발행
1 ~ 45까지의 숫자를 가지고 중복되지 않은 6개의 숫자를 뽑아 구입한 로또 수량만큼 발행
로또 번호 오름차순 정렬
4) 발행된 로또 출력
오름차순으로 정렬된 로또 출력
5) 당첨 번호를 입력받기 위해 '당첨 번호를 입력해 주세요.'를 출력
6) 당첨 번호 저장
중복되지 않는 숫자 6개
[예외]
쉼표(,)로 구분되지 않을 때
1 ~ 45 범위의 숫자가 아닐 때 (문자, 음수, 소수, 0이 아닐 때)
숫자가 중복될 때
→ 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고
'[ERROR]'로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받음
7) 보너스 번호를 입력받기 위해 '보너스 번호를 입력해 주세요.'를 출력
8) 보너스 번호 저장
당첨 번호와 중복되지 않는 숫자 1개
[예외]
1 ~ 45 범위의 숫자가 아닐 때 (문자, 음수, 소수, 0이 아닐 때)
당첨 번호와 중복될 때
→ 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고
'[ERROR]'로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받음
9) 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률 계산
3개 일치, 4개 일치, 5개 일치, 5개 일치 및 보너스 볼 일치, 6개 일치 비교 계산
수익률 계산 = (총 수익 / 로또 구매 비용) * 100
10) 당첨 통계 출력
11) 총 수익률 출력
소수점 둘째 자리에서 반올림하여 출력
기능 구현
1) MVC 패턴을 적용해서 설계해보자
Model : 애플리케이션의 정보, 데이터 등의 가공을 책임지는 컴포넌트
View : 사용자 인터페이스 요소로 데이터를 기반으로 사용자들이 볼 수 있는 화면
Controller : 데이터와 사용자 인터페이스 요소를 잇는 다리 역할로, 이벤트들을 처리
[필요한 Model]
1. 로또 객체
2. 사용자 객체
3. 사용자의 로또 구매 수량 객체
4. 당첨 번호 객체
5. 상금 객체
6. 결과 객체
+) 추가적으로 로또의 숫자 정수 리스트에 대한 domain도 작성했었으나 미션 프로그래밍 요구 사항에 맞도록 복구하였음
[필요한 View]
1. 사용자 입력 화면
2. 사용자 출력 화면
[필요한 Controller]
1. 메인 컨트롤러
2. 사용자 컨트롤러
3. 당첨 번호 컨트롤러
4. 결과 컨트롤러
[IllegalArgumentException을 상속받은 커스텀 예외 처리 exception]
1. 숫자가 아닐 때 예외처리
2. 쉼표가 없을 때 예외처리
3. 구입 금액의 최소 금액보다 작을 때 예외처리
4. 구입 금액이 1000원 단위가 아니어서 나누어지지 않을 때 예외처리
5. 로또 숫자 중복 예외처리
6. 로또 숫자 갯수 예외처리
7. 로또 숫자 범위 예외처리
[그 외 utils]
1. 상수 값 (Constant)
2. 로또 발행값 생성 (Generator)
3. 입력값 형변환 (InputParser)
4. 입력 변수 예외처리 (Validator)
model
|- lotto
|- Lotto : 로또 객체 Domain
|- Player : 사용자 객체 Domain
|- PlayerAmount : 사용자의 로또 구매 수량 객체 Domain
|- Winning : 당첨 번호 객체 Domain
|- result
|- Prize : 상금 객체 Domain
|- Result : 결과 객체 Domain
view
|- InputView.java : 사용자 입력 View
|- OutputView.java : 사용자 출력 View
controller
|- MainController : 메인 시작 Controller
|- PlayerController : 사용자 관련 Controller
|- WinningController : 당첨 번호 관련 Controller
|- ResultController : 결과 관련 Controller
exception
|- InvalidNonNumericInputException : 숫자가 아닐 때 예외처리
|- InvalidCommaMissingException : 쉼표가 없을 때 예외처리
|- InvalidMinimumAmountException : 구입 금액의 최소 금액보다 작을 때 예외처리
|- InvalidDivisionAmountException : 구입 금액이 1000원 단위가 아니어서 나누어지지 않을 때 예외처리
|- InvalidDuplicateLottoNumberException : 로또 숫자 중복 예외처리
|- InvalidLottoNumberCountException : 로또 숫자 갯수 예외처리
|- InvalidLottoNumberRangeException : 로또 숫자 범위 예외처리
utils
|- Constant : 룰과 관련된 상수 Constant
|- Generator : 로또 발행값 생성 Util
|- InputParser : 입력값 형변환 Util
|- Validator : 입력 변수 예외처리 Util
Application.java
2) TDD를 적용해가면서 구현해보자
1. 테스트 코드 작성
2. 테스트 코드를 통과하기 위한 최소한의 코드 작성
3. 테스트 코드 통과를 유지하면서 리팩토링
+) 테스트 코드를 짜기 힘들 경우 코드 먼저 짜고서라도 테스트 코드 작성하기
3) 리팩토링
클린 코드에 맞춰 리팩토링을 하려고 노력했다.
1. 의미 있는 메소드명, 변수명, 클래스명을 사용하자
2. 중복을 최대한 제거하자
3. 한 메소드에서는 하나의 일만 하도록 하자
4. 상수 대신 열거형을 사용하자
5. 인터페이스 사용과 의존성 주입을 통해 결합도를 낮추자
6. final 사용을 통해 변수를 불변하도록 하자
7. 생성자 대신 정적 팩토리 메서드를 사용해보자
8. 접근 권한을 최소화하자
9. 가독성이 떨어지지 않는다면 스트림을 사용해보자10. 각 함수별로 테스트를 작성하여 정상 동작함을 확인하자
이 외에도 다양한 곳을 리팩토링하려고 했다.
회고
이번 미션 동안, 코드의 품질을 향상하기 위해 다섯 가지 주요 고민과 개선 사항을 고려하였습니다:
첫 번째로 요구사항으로 주어진 Java Enum에 관해 활용입니다. 이전 미션을 토대로, 하드 코딩을 피하기 위한 매직 넘버와 리터럴을 피하기 위한 노력을 다시 생각했습니다. 이전에는 각각의 모든 Enum 클래스를 만든 후 static import 하여 사용했습니다. 하지만 이번 미션을 진행하면서 View와 같이 특정 클래스에만 오로지 사용되는 경우에는 가독성을 위해 각 클래스만의 상수로 만들어 private 하게 관리했습니다. 또한 기존에는 Enum 클래스를 단지 상수를 관리하고 상수의 내용(message, value 등)을 참고하는 데 사용했었습니다. 이번 미션에서는 더 나아가 Map에 적용하여 로또 번호 일치 횟수를 세기 위한 용도로도 활용해 보았습니다.
두 번째로 한 가지 일만 하도록 함수를 더욱 작게 작성하는 것입니다. 이전 미션에는 if 문 안에 들어가는 boolean 문 블록에 대해 !numbers.contains(bonus)과 같이 이해가 필요한 코드를 적고는 했으나, 이번 미션에는 isBonusNumberDistinct()와 같은 private 함수를 작성하고 이름을 적절히 지은 후 이를 호출하도록 하여 코드에 대한 가독성과 이해도를 높이기 위해 노력했습니다.
세 번째로 값 객체인 VO에 대한 활용입니다. VO인 PlayerAmount를 작성하여 사용자의 로또 구매 수량 필드에 대해 클래스 감싸 캡슐화했습니다. 이를 통해 데이터의 불변성을 유지하고 코드의 가독성을 높이기 위한 명확한 의미를 부여할 수 있습니다. PlayerAmount 외에도 로또의 List<Integer>에 대해 LottoNumber와 같은 VO를 사용했었으나, 이는 제공된 Lotto 클래스를 활용해 구현해야 한다는 프로그래밍 요구 사항에 맞추어 삭제했습니다.
네 번째로 커스텀 예외 클래스 작성입니다. 이전 미션에서는 에러 메시지에 대한 Enum 클래스를 생성한 후 예외를 처리하면서 IllegalArgumentException을 발생하도록 한 후 상수로부터 import 하여 에러 메시지를 집어넣어 주었습니다. 이번 미션에서는 IllegalArgumantException을 발생시키고 에러 메시지 출력을 위한 커스텀 예외 클래스를 생성했습니다. 이를 통해 예외 유형을 명확하게 식별되도록 했습니다.
다섯 번째로 컨트롤러의 분리입니다. 기존에는 하나의 컨트롤러에서 모든 작업을 하도록 코드를 작성했으나, 이번 미션에서는 단일 책임 원칙을 더욱 준수하도록 하기 위해 각각의 컨트롤러가 특정 역할에 집중하도록 분리했습니다.
이외에도 꾸준히 SOLID 원칙을 준수하여 코드의 품질과 유지보수성을 향상시키고자 노력했습니다.
'Community > 우테코 프리코스' 카테고리의 다른 글
[우테코] 웹 백엔드 프리코스 4주차 미션 (0) | 2023.11.09 |
---|---|
[우테코] 웹 백엔드 프리코스 2주차 미션 (0) | 2023.10.26 |
[우테코] 웹 백엔드 프리코스 1주차 미션 (0) | 2023.10.19 |
[우테코] 2024 우아한테크코스 입학설명회 (0) | 2023.10.06 |