-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[부나] 2단계 로또 제출합니다. #30
Conversation
- lotto 패키지 내 Number 관련 클래스는 lotto.number 패키지로 이동 - Rank 클래스는 Rank 패키지로 분리
- IntTest로 변경
말씀하신 것처럼 제가 제안 드린 방향은 enum 의 모든 element 에 추가 필드를 넣어줘야하는 일이 발생합니다. 모든 구현에는 트레이드 오프요소가 존재합니다. 100% 장점만 있는 구현방식은 존재하지 않지요 ㅎㅎ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2단계 미션도 잘 마무리해주셨네요 👍
질문 주신 내용을 비롯하여 몇가지 코멘트 남겼는데, 확인 후 리뷰요청 부탁드릴게요 :)
private val NUMBERS: Map<Int, LottoNumber> = (MIN_LOTTO_NUMBER..MAX_LOTTO_NUMBER).associateWith(::LottoNumber) | ||
|
||
fun from(number: String): LottoNumber { | ||
val lottoNumber = requireNotNull(number.trim().toIntOrNull()) { ERROR_MESSAGE_NOT_NUMERIC_LOTTO_NUMBER } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
number 가 int 로 변환할 수 있는 숫자 형태인지는
입력 레벨에서도 검증이 가능할 것 같은데요.
타입에 대한 검증을 view 에서 하는게 적절할지, domain 에서 하는게 적절할지 고민해봐도 좋겠습니다 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제리님의 피드백과 미션의 힌트를 함께 참고하여 수정해보았습니다.
논리적인 오류가 아닌, 단순히 값을 잘못 입력받은 상황이기 때문에 예외를 발생시키기보다 에러메시지를 출력해주고 재입력받도록 수정하였습니다!
init { | ||
require(amount >= MIN_AMOUNT) { ERROR_MESSAGE_NEGATIVE_AMOUNT } | ||
} | ||
|
||
fun divideByThousand() = amount.div(THOUSAND) | ||
fun divideBy(divisor: Money) = amount.div(divisor.amount) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 함수도 operator 로 사용해보는 것은 어떨까요?
사칙연산에 대한 일관적인 구현 방식이 제공되면 좋을 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
감사합니다 : )
해당 메서드를 operator 로 변경하였습니다!
println(PURCHASED_LOTTO_SIZE_FORMAT.format(lottos.size)) | ||
lottos.map { it.getSortedLotto() }.forEach { sortedNumber -> | ||
fun printPurchasedLottos(manualLottos: List<ManualLotto>, autoLottos: List<AutoLotto>) { | ||
println() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
println(..) 에 대한 구현을 별도 함수로 한번 감싸보는 것은 어떨까요?
만약 표준 입출력에 대한 스펙 혹은 구현사항이 바뀔경우, 현재 구조에서는 println() 이 사용된 모든 코드에 변경이 필요 할 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오.. 미처 거기까지 생각하지 못했습니다!
표준 입출력에 대한 스펙과 구현이 언젠가 변경될 수도 있음을 감안하는 좋은 시야를 얻을 것 같습니다~ 👏
|
||
import domain.lotto.number.LottoNumber | ||
|
||
class AutoLotto(lottoNumbers: List<LottoNumber>) : PurchasedLotto(lottoNumbers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AutoLotto 와 ManualLotto 의 구현상 차이가 없는데, 별도 클래스로 나누는 이유가 있을까요?
클래스로 나눴다면, 관련된 비즈니스 로직을 각 클래스에서 스스로 처리할 수 있도록 역할을 좀 더 부여해보면 좋겠어요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
출력 예시를 보면 수동으로 3장, 자동으로 11개를 구매했습니다.
를 출력하고, 그 아래에 수동 로또 번호와 자동 로또 번호를 순서대로 출력
하고 있습니다.
만약 ResultView에서 fun printPurchasedLottos(manualLottos: List<PurchasedLotto>, autoLottos: List<PurchasedLotto>)
와 같은 형태로 전달받도록 한다면, 타입이 같다보니 실수로 반대로 출력하는 경우가 발생할 수도 있어서 이 둘을 별도의 클래스로 분리하고 상속하였습니다!
하지만 제리님 말씀대로 클래스로 나눴다면, 관련 비즈니스 로직을 포함하고 있으면 더 좋을 듯 싶습니다!
리팩터링 과정에서 고민해보겠습니다 : )
답변해주셔서 감사합니다! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
빠르게 코멘트 반영해주셨네요 👍
미처 코멘트 드리지 못했던 부분들도 스스로 개선하시는 부분도 멋지십니다 ㅎㅎ
관련하여 몇가지 고민해볼 점에 대해 추가로 코멘트 남겼는데 확인 부탁드릴게요!
} | ||
|
||
private fun purchaseLotto(): Pair<Money, List<PurchasedLotto>> { | ||
private fun inputLottos(): Triple<List<PurchasedLotto>, List<PurchasedLotto>, Money> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
인풋을 각 함수로 분리해보는 것은 어떨까요?
현재 구조에서는 input 으로 받는 값이 추가될 때마다 리턴형이 계속 바뀌어야 할 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
관련 메서드끼리 묶어보려고 하다보니 오히려 변경에 취약한 구조가 된 것 같습니다..!
별도의 메서드로 분리해보겠습니다!
|
||
fun from(number: String): LottoNumber = | ||
LottoNumber(requireNotNull(number.toIntOrNull()) { ERROR_MESSAGE_NOT_NUMERIC_LOTTO_NUMBER }) | ||
private val NUMBERS: Map<Int, LottoNumber> = (MIN_LOTTO_NUMBER..MAX_LOTTO_NUMBER).associateWith(::LottoNumber) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
캐시 활용 👍
|
||
return Rank.values().find { it.countOfMatch == countOfMatch } ?: MISS | ||
} | ||
fun valueOf(countOfMatch: Int, matchBonus: Boolean): Rank = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
보너스 번호를 요구하는 등수가 추가되더라도 해당 함수에는 변경이 발생하지 않겠네요 👍
): Pair<Money, List<PurchasedLotto>> { | ||
require(lottoSize.value == lottoNumbers.size) { ERROR_MESSAGE_INVALID_PURCHASING_LOTTO_SIZE } | ||
require(money.isGreaterThan(Money(LOTTO_PRICE * lottoSize.value))) { ERROR_MESSAGE_INSUFFICIENT_AMOUNT } | ||
return Pair( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pair 를 사용하는 것이 나쁜 것은 아니지만, 부득이한 경우가 아니라면 별도 클래스를 생성하고 그것을 이용해보는 것은 어떨까요?
Pair 단위의 데이터는 왜 묶였는지, 각각이 어떤 역할을 하는지 알기 어려울 수 있을 것 같아요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
확실히 Pair로만 반환한다면, 각 Pair의 요소가 어떤 의미인지 파악하기 어려울 수 있을 것 같습니다!
반환하고자 하는 객체를 담고 있는 별도의 data class를 생성해서 return해보는 방향으로 구현해보겠습니다.
기존 코드가 Pair 객체로 변수를 초기화 할 때 구조 분해 방식을 사용하고 있었기 때문에, 이를 수정하지 않아도 되는 data class를 사용하여 수정해보겠습니다!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가적으로 코멘트 드릴 부분이 보이지 않습니다 👍
로또 미션 진행하시느라 고생 많으셨어요.
해당 미션을 통해 얻어가는 것이 있으셨길 바랍니다 :)
남은 미션도 화이팅이십니다!
안녕하세요 제리님!
제리님의 피드백 덕분에 구현과 구조에 대해서 더 생각해볼 수 있었습니다.
그런데 궁금한 점이 생겨서 질문을 남겨보려고 합니다..!
1단계 미션의 마지막 피드백으로
rank 의 각 element 가 보너스 번호 검사 여부를 알고 있다면, 아래 조건문도 제거할 수 있지 않을까요? :)
라고 리뷰를 남겨주셨는데제가 이해한 바로는 Rank의 프로퍼티로 matchBonus와 같은 Boolean 타입을 추가해서, valueOf() 메서드 호출시 2등에 대한 비교를 하지 않아도 되는 구조로 변경해보기를 추천하신 것 같습니다!
여기서 궁금한 점은 만약 matchBonus와 같은 프로퍼티가 추가된다면, FIRST부터 MISS까지 요소를 각각 다른 이름으로 하나씩 더 만들어줘야 하는 불편함이 발생할 수도 있지 않을까 입니다..!
그럼에도 valueOf()에서 비교하는 것보다 어떠한 부분에서 이점이 있는지 궁금합니다! 🙂