목차
- ✏️ 3주차 문제
- 📃 추가된 요구 사항
- 🥺 부족했던 부분
- 🤔 새롭게 알게 된 지식
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
- 로또 1장의 가격은 1,000원이다.
- 당첨 번호와 보너스 번호를 입력받는다.
- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 종료한다.
만약 금액이 1000원 단위가 아니거나, 로또를 구매할 수 없는 금액이라면 예외를 발생시키고 프로그램을 종료해야 합니다.
이 외에도 숫자가 아닌 문자를 입력, 당첨 번호의 범위에 포함되지 않는 경우, 중복된 번호가 포함되어 있는 경우, 보너스 번호가 당첨 번호에 포함되어 있는 경우 등 2주차 문제보다 다양한 예외처리를 해주어야 했습니다.
📃 추가된 요구 사항
- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
15줄은 그렇게 각박한 제한은
함수의 길이가 15라인을 넘어가지 않도록 구현함으로써 함수 별로 역할이 명확해졌습니다.
또한, Unit 테스트를 할 때 public 메서드의 경우 함수별 테스트에 적합해졌습니다.
- else를 지양한다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
백엔드(Java)로 참여한 지인에게 이야기를 들어보니, Java로 구현할 때에는 else와 switch를 모두 금지하였다고 합니다.
즉, 지양이 아닌 제한이었습니다.
else, switch문을 자주 사용하다보면, 가독성이 급격히 떨어지기 때문에, 어떤 case에서 로직이 실행되는지 파악하기 힘들어진다고 합니다.
그렇기 때문에 Early return pattern을 사용하여 더 읽기 편안한 코드를 작성하는 데에 초점을 둘 필요가 있습니다.
그러나, 안드로이드(Kotlin)의 경우 제한이 아닌 지양이었는데요.
2번째 부분설명에 나와있듯이 if/else와 when문을 사용하는 것이 더 깔끔할 수 있다는 점 때문이었습니다.
이번 로또 미션의 경우, else를 사용하지 않았기 때문에 when expression을 언제 사용하면 적절할지 고민해보았습니다.
로또의 경우 숫자를 몇 개 맞췄는지에 따라서 순위가 달라집니다.
이러한 순위를 Enum class로 표현해주었고 (Enum 클래스를 적용해 프로그래밍을 구현한다.), 개수에 따라 적절한 LottoRank 클래스를 반환해주었습니다.
return when (matchedNumberCount) {
MATCH_SIX -> LottoRank.FIRST
MATCH_FIVE -> getRankWhichBonus(lotto, bonusNumber)
MATCH_FOUR -> LottoRank.FOURTH
MATCH_THREE -> LottoRank.FIFTH
else -> LottoRank.NO_LUCK
}
이렇게 코드를 작성하면서 동일한 데이터에 대해 서로 다른 상황인 경우, when 키워드를 사용하면 코드가 훨씬 간결하고 명확해 보였습니다.
- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
해당 요구사항은 어플리케이션 개발에 있어서도 필수적인 요구사항입니다.
예시로, Android에는 MVC, MVVM, MVP 등 여러 가지 패턴이 존재합니다.
이 패턴의 공통적인 특징은 UI 로직과 비즈니스(도메인) 로직의 분리에 관심을 가지고 있다는 점입니다.
레이어별로 관심사를 분리함으로써 유지보수와 테스트를 유연하게 할 수 있다는 점 때문입니다.
domain과 presentation의 로직을 각 패키지에 구현하고, 중간에 사용자와 입출력(I/O)을 대리자로 수행하고, domain의 비즈니스 로직을 호출하도록 하는 중간 레이어가 있으면 좋겠다고 생각했습니다.
중간에 LottoService라는 클래스를 두어 presentation과 domain이 각 역할에 충실히 하고, 필요에 따라 각 기능을 Service에서 호출해주도록 하였습니다.
🥺 돌이켜 보니 아쉬운 부분
다만, 지금 보면서 아쉬웠던 점은 Service가 아닌, Controller에서 입출력을 받고, 별도의 Service를 통해 비즈니스 로직을 수행하도록 하는 것이 더 좋지 않았나 싶습니다.
이러한 아쉬운 부분을 4주차 과제에서 개선하는 것을 목표로 잡고, 과제가 주어지기 전까지 MVC 패턴에 대해 공부해보는 시간을 갖고자 하였습니다.
🤔 새롭게 알게 된 지식
- 원시 타입의 Wrapping
- 일급 컬렉션(First Class Collection)
원시 타입의 Wrapping
Clean code 원칙을 공부하다 보니, 원시 타입을 Wrapping 하라는 문장이 있었습니다.
(Kotlin의 원시 타입에 대해 자세히 알아보니, Primitive 타입과 Reference 타입을 따로 구분하지 않고, Runtime 시점에 효율적인 방식으로 표현된다는 사실도 알게 되었습니다.)
요구사항으로 주어진 Lotto 클래스의 필드는 Int 타입을 요소로 가지는 List입니다.
로또를 모르는 사람은 거의 없지만, 만약 로또를 처음 접하는 사람들이라고 가정해본다면 Int가 무슨 값을 의미하는지 분간하기 힘들 수 있습니다.
그렇기 때문에, Int를 LottoNumber라는 하나의 타입으로 만들어주면 어떨까? 라는 생각을 하였습니다.
class LottoNumber(private val number: Int) {
init {
validateLottoNumberRange(number)
}
override fun equals(other: Any?): Boolean {
if (other is LottoNumber) {
return this.number == other.number
}
return false
}
}
원시 타입을 감싸줌으로써 숫자 범위에 대한 검증을 외부에서 처리하지 않아도 되는 장점을 가지게 되었습니다.
또한, 다른 숫자와 값의 일치 여부를 판단할 때, 내부의 number 필드를 기준으로 잡았습니다.
일급 컬렉션(First Class Collection)
직접적으로 일급 컬렉션 이라는 언급은 없었지만, 이러한 요구사항에 무언가 의미가 있지 않을까 싶어서 서칭을 해보았습니다.
- 불변성을 가지고 있다 (* kotlin에서 List는 값을 변경할 수 없습니다)
- 필드로 하나의 컬렉션만을 가질 수 있다.
- 비즈니스 로직에 종속되어 있다
이러한 특징을 통해 주어진 요구사항이 일급 컬렉션으로 구현하라는 것이 맞는구나라고 판단했습니다.
class Lotto(private val numbers: List<LottoNumber>) : List<LottoNumber> by numbers {
init {
validateLottoSize(this)
validateLottoNumberDuplication(this)
}
override fun toString(): String =
numbers.joinToString(", ", "[", "]")
}
우선, by 키워드를 통해 Lotto 클래스 자체를 List<LottoNumber> 타입으로 delegate 하여 사용하였습니다.
이렇게 구현하면 size를 구하는 기능이나, 특정 요소의 값을 반환받는 기능 등을 추가로 구현할 필요가 없었습니다.
그리고 Lotto(일급 컬렉션) 클래스가 생성될 때, LottoNumber를 Element로 가지고 있는 리스트에 대해 검증하도록 하였습니다.
다만, 입력에 있어서 검증이 필요한 부분(입력한 값이 숫자인가?)은 입출력 상황에서 예외 처리를 해주었습니다.
또한, toString()을 override 하여 Lotto 클래스의 객체를 출력했을 때 [1, 2, 3, 4, 5, 6] 형태로 출력되도록 재정의해주었습니다.
일급 컬렉션을 사용해보면서 느낀 장점은 아래와 같습니다.
- 불변성을 띈다는 점에서 numbers 필드가 신뢰성을 가질 수 있어 외부에서 안전하게 사용할 수 있었습니다.
- Lotto라는 자료구조를 구현함으로써 만약 여러 명이 작업을 한다고 했을 때, 네이밍에 혼란을 줄만한 상황을 줄일 수 있었습니다.
- 비즈니스 로직을 담고 있기 때문에, Lotto 클래스의 재사용성을 높일 수 있었습니다. (사용하는 곳마다 검증해줄 필요가 없음)
3주차 미션도 무사히 완료하였습니다.
이래저래 포스팅을 늦게 올리긴 하였지만, 다시 복기하는 느낌으로 작성해보니 역시 배운 것이 너무나도 많은 한 주였습니다.
다음 포스팅은 우테코 4주차 미션으로 찾아뵙겠습니다.
github : https://github.com/tmdgh1592
'대외활동 > 우아한테크코스' 카테고리의 다른 글
[우아한테크코스] 2주차 - TDD(Test Driven Development) (1) | 2023.02.15 |
---|---|
[우아한테크코스 5기] 1주차 회고 - (양념 반 후라이드 반) 설렘 반 걱정 반 (6) | 2023.02.13 |
[우아한테크코스 5기] 프리코스 4주차 회고 - Kotlin 안드로이드 (2) | 2022.11.23 |
[우아한테크코스 5기] 프리코스 2주차 회고 - Kotlin 안드로이드 (2) | 2022.11.17 |
[우아한테크코스 5기] 프리코스 1주차 회고 - Kotlin 안드로이드 (2) | 2022.11.06 |