모든 프리코스 과정을 다 끝내고 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
14000
1,2,3,4,5,6
7
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]
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%입니다.
[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.
과제 repository를 fork&clone 하여 로또를 위의 기능요구사항, 프로그래밍 요구사항 그리고 과제 진행 요구사항을 만족하도록 구현한 뒤 pull request로 제출한다.
enum class LottoGrade(val prize : Int) {
FIRST(2_000_000_000) {
override fun toString(): String {
return "6개 일치 (2,000,000,000원) - "
}
},
SECOND(30_000_000) {
override fun toString(): String {
return "5개 일치, 보너스 볼 일치 (30,000,000원) - "
}
},
THIRD(1_500_000) {
override fun toString(): String {
return "5개 일치 (1,500,000원) - "
}
},
FOURTH(50_000) {
override fun toString(): String {
return "4개 일치 (50,000원) - "
}
},
FIFTH(5_000) {
override fun toString(): String {
return "3개 일치 (5,000원) - "
}
},
NOTHING(0);
}
위 처럼 연관성이 있는 상수는 enum class를 사용하여 구현하였다. toString()함수를 사용해서 등수에 맞는 string을 리턴할 수 있도록 하였다.
- 입력값의 예외를 검증하는 Validator 클래스
- 값을 입력받는 InputView 클래스
- 로또 번호 6개 list를 필드로 가지는 Lotto 클래스
- List<Lotto> 를 필드로 가지는 Lottos 클래스
- 로또 당첨결과를 담당하는 WinningResult 클래스
1. 예외
입력받는 로또 번호와 보너스 번호의 예외 발생 상황 한 개를 놓친 걸 3주차 프리코스 끝나고 나서 발견하게 되었다. 보너스 번호는 입력받은 로또 번호 6개에 포함되어 있는 번호가 아닌 번호로 구성되어 있어야하는데 그 예외는 생각하지 못한 채 구현을 해서 너무 아쉬웠다. 예외가 일어날 수 있는 상황을 꼼꼼하게 체크해서 다음 번엔 이런 실수를 하지 않도록 해야겠다.
2. 예외 테스트
기능 구현을 완료하고 ApplicationTest를 실행했을 때 예외테스트 부분에서 자꾸 에러가 났다. throw IllegalArgumentException을 하고 error메시지를 출력하도록 했지만 계속 실패했고 해결방법을 생각하고 인터넷에 검색도 해보고 코드도 계속 고쳐보았지만 시간만 계속 흐른 채 제출 전날 까지도 해결하지 못하고 있었다. 요구사항을 다시 읽어보고 try catch문을 사용하여 throw된 IllegalArgumentException을 잡고 error문을 출력하였더니 ApplicationTest를 통과할 수 있었다. 요구사항을 좀 더 자세히 읽어보고 생각했더라면 시간이 오래 걸리지 않았을 것 같았다.
1. 비즈니스 로직과 UI로직을 분리한다.
비즈니스 로직과 UI 로직을 한 클래스가 담당하지 않도록 한다. 단일 책임의 원칙에도 위배된다.
class Lottos(private val lottos: List<Lotto>) {
fun matchLotto(answer: Lotto, bonus : Int) : WinningResult {
val winningResult = WinningResult()
lottos.forEach {
val result = it.matchLotto(answer, bonus)
winningResult.setWinnigResult(result)
}
return winningResult
}
fun printLottos() {
println("${lottos.size}개를 구매했습니다.")
lottos.forEach { it.printLotto() }
println()
}
}
이번 과제에서 위 코드처럼 Lottos 클래스 안에 비즈니스 로직과 출력하는 로직을 같이 넣었다. Lottos 클래스에 여러개의 책임을 준 것이다. 이는 객체 지향 설계에 맞지 않다. 다음엔 출력을 담당하는 클래스를 만들어 책임을 분리시켜야겠다.
2. 테스트 코드도 코드다
테스트 코드도 코드이므로 리팩터링을 통해 개선해나가야 한다. 특히 반복적으로 하는 부분을 중복되지 않게 만들어야 한다. 예를 들어 단순히 파라미터의 값만 바뀌는 경우라면 아래와 같이 테스트할 수 있다.
@ValueSource(ints = [999, 0, -123])
@ParameterizedTest
fun `천원 미만의 금액에 대한 예외 처리`(input: Int) {
assertThrows<IllegalArgumentException> {
Money(input)
}
}
피드백을 통해 구현코드 뿐만 아니라 테스트 코드도 리팩토링에 신경써야겠다고 생각했다. @ValueSource, @ParameterizedTest도 처음 접했기 때문에 jUnit5에 대한 공부도 많이 필요함을 느꼈다.
마지막 프리코스까지 완주하고 3주차 회고록을 작성하며 3주차 작성한 코드를 보았는데 아쉬운 점이 많이 보였다. 그래도 피드백을 받아 4주차에서는 그 부분을 고쳐서 과제를 해결하려고 노력했다. 객체 지향 설계는 아직 어렵지만 계속 클래스를 분리해보고 리팩토링 해보려는 시도도 계속 하고 있다. 이 때까지 받은 피드백과 공부를 토대로 3주차 프리코스를 다시 구현해봐야갰다.