[우테코5기] 프리코스 모바일 3주차 회고록

sehyonii·2022년 11월 23일
0

우아한 테크코스

목록 보기
3/3
post-thumbnail

모든 프리코스 과정을 다 끝내고 3주차 프리코스를 되돌아보며 내가 어떤식으로 기능을 구현했고 피드백을 통해 고쳐야할 점은 무엇인지 기록하고 싶어 작성하는 회고록

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

📌 입출력 요구사항

입력

  • 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,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개
  • 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%)
총 수익률은 62.5%입니다.
  • 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다.
[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.

과제 repository를 fork&clone 하여 로또를 위의 기능요구사항, 프로그래밍 요구사항 그리고 과제 진행 요구사항을 만족하도록 구현한 뒤 pull request로 제출한다.

구현

💻 기능 목록

  • 로또 번호의 숫자 범위가 1~45인 6개의 중복되지 않은 로또를 발행하는 기능
  • 입력된 금액에 맞게 로또를 발행하는 기능
    • 입력된 금액 예외 테스트(올바른 값이 입력되지 않을 시)
      • 금액에 아무것도 입력되지 않았을 경우
      • 금액이 null 일 때
      • 금액이 숫자가 아닌 경우
      • 금액이 음수인 경우
      • 금액이 1000으로 나누어 떨어지지 않는 경우
  • 입력된 당첨 번호 예외 테스트(올바른 값이 입력되지 않을 시)
    • 당첨 번호가 아무것도 입력되지 않았을 경우
    • 당첨 번호가 null일 때
    • 당첨 번호의 길이가 6이 아닌 경우
    • 당첨 번호에 숫자가 아닌 값이 입력된 경우
  • 입력된 보너스 번호 예외 테스트(올바른 값이 입력되지 않을 시)
    • 보너스 번호에 아무것도 입력되지 않았을 경우
    • 보너스 번호가 null일 경우
    • 숫자가 아닌 값이 입력되었을 경우
    • 1에서 45 사이의 숫자가 아닌 경우
  • 로또 번호의 중복이 있는지 예외사항 처리하는 기능
  • 로또 번호가 1에서 45사이의 범위에 있는지 예외사항 처리하는 기능
  • 로또 번호와 당첨 번호를 비교하여 일치하는 개수 확인하는 기능
  • 보너스 번호가 로또 번호에 있는지 확인하는 기능
  • 일치하는 개수에 따라 등수 확인하는 기능
    • 3개 일치하면 5등 / 5,000원
    • 4개 일치하면 4등 / 50,000원
    • 5개가 일치하면 3등 / 1,500,000원
    • 5개 + 보너스 번호가 일치하면 2등 / 30,000,000원
    • 6개가 일치하면 1등 / 2,000,000,000원
  • 로또 수량에 맞는 당첨내역 출력하는 기능
    • 로또 수량에 맞는 당첨내역 확인하는 기능
  • 수익률 계산하는 기능

💻 enum 클래스 사용

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주차 프리코스를 다시 구현해봐야갰다.

0개의 댓글