[완두콩] 박주희 로또 미션 제출합니다 (1단계-4단계) - #202
Conversation
There was a problem hiding this comment.
안녕하세요 주희님 만나서 반갑습니다! 😄
리뷰를 맡게 된 조성현입니다! 🙇♂️
미션 구현하시느라 고생하셨습니다.
우선 PR에 작성해주신 이야기부터 해볼게요
(혹시 힌트를 최대한 따르는 것을 권장하나요?)
힌트를 따르는 것은 자유라고 생각합니다. 힌트는 정답이 아니라고 생각합니다. 오히려 힌트를 막무가내로 따라가는 것은 안 좋다고 생각해요. 스스로 방법을 생각하고 시도해보는 것이 좋은 자세라고 생각합니다!
그리고 자신의 선택에 충분한 이유와 자신감을 가지면 좋을 것 같네요.
만약 자신이 선택한 것보다 힌트의 내용이나 다른 선택지가 더 좋았다면, 그 부분에서도 Trade off를 비교해보며 학습해가면 됩니다 👍
Enum을 적용하기 위해 우아한형제들 기술 블로그(Java Enum 활용기) 등을 찾아보았습니다.
학습에 대한 열정이 대단하시네요.. 💯 👍
그치만 너무 어렵죠? 기초적인 사용 예시들을 뒤적이며 라고 하셨는데,
❓ 어떤 예시들을 뒤적였나요?. 어떻게 학습하고 계신지 궁금합니다~
테스트 코드 작성 범위
테스트 코드 작성 범위: 이번 로또 미션 요구사항에는 명시적으로 테스트 코드를 작성하라는 언급이 따로 없었습니다. 실무적/학습적 관점에서 요구사항에 없더라도 단위테스트 작성은 필수인지 궁금합니다!
❓ 우선 테스트의 목적과 장점이 무엇이라고 생각하는지 코멘트로 남겨주세요 😃
물론 있는게 무조건 좋은 방향일거같습니다만
❓ 그럼 테스트 코드를 작성할 때는 단점이 없을까요? 한 번 고민해보고 가볍게 의견 남겨주세요
더불어 테스트코드는 구현 단계별로 작성하는게 좋을지, 아니면 지금처럼 일정수준 이상 진행되고 해도 좋을지 궁금합니다.
개인적으로는 feat 커밋 단위로 테스트를 함께 작성하는 방식을 선호합니다.
테스트를 나중에 한 번에 몰아서 작성하면 이미 구현된 구조에 테스트를 끼워 맞추게 되는 경우가 많고, 중간에 놓친 예외 케이스를 발견하기도 어려워질 수 있습니다. 반대로 기능 단위로 구현하면서 테스트를 같이 작성하면, 해당 기능의 동작과 예외 조건을 더 명확히 정의하면서 개발할 수 있어서 설계에도 도움이 되는 것 같아요!
변수명, 클래스명, 메서드명 네이밍
저도 네이밍은 참 어려운 것 같아요 😅 메서드명으로 의도를 잘 표현하는 것이 참 중요한 것 같아요
이해하기 쉽게 메서드명을 작성하지 못하는 원인이 무엇인지 파악하는 것이 중요할 것 같아요
단순히 영어를 몰라서 작성을 못한다면 검색하면 됩니다.
하지만 이 메서드가 어떤 역할을 하는지 명확하지 않다면 메서드 이름이 떠오르지 않을 수 있어요.
이는 메서드의 책임이 명확하지 않은 신호일 수 있다고 생각해요. 하나의 메서드가 여러 역할을 하고 있으면 이름도 자연스럽게 길어지거나 애매해지는 경우가 많더라고요.
그래서 네이밍이 막힐 때는 먼저 이 메서드가 정확히 어떤 일을 하는지 다시 정리해보고, 필요하면 역할을 나눠보는 것도 좋을 것 같습니다. 영어 표현이 고민되는 정도라면 검색하거나 AI에게 후보를 받아보는 것도 충분히 좋은 방법이라고 생각합니다.
네이밍은 코드 읽는 사람에게 의도를 전달하는 가장 빠른 수단이라, 이 부분은 조금 더 과감하게 개선해봐도 좋을 것 같아요.
저는 개인적으로 메서드가 하는 역할을 AI에게 설명하고, 네이밍 추천을 받습니다 😄
네이밍정도는 시원하게
리팩토링
3번과 비슷한 맥락으로, 리팩토링 하기 쉬우려면 어떤 포인트에 유의하면서 코드를 짜면 좋을지 조언 부탁드립니다. 자동차 경주와 이번 로또 둘다 코드를 고치려면 단순히 "기존에 짠걸 고치려니까 어렵다"는 것을 넘어서,,,, 마구 꼬여있는(?) 느낌이 드니까 더 힘들더라구요.
이런식으로 한 번 그려보는 것도 구조에 도움이 되는 것 같아요
클래스별로 어떻게 메시지를 주고받는지 생각해보면 어디서 꼬여있는지 발견될 것 같기도 합니다..!
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| public class Application { |
There was a problem hiding this comment.
사용자 입장으로 볼 때 어떠신가요?
6개로 입력을 잘 했고, 콤마로 구분 잘 한 것 같아요.
그리고 로또 구입금액이 얼마인지 모르겠어요. 1000원이지만 100원을 입력해도 정상 진행이 되네요.
1000원 미만으로 구매하면 안내 문구를 띄워주면 좋을 것 같아요
자동차 경주 미션의 핵심 키워드는 예외 처리였던 것 같아요.
사용자가 잘못 입력할 수 있는 상황을 미리 고려하고, 이를 적절히 방어하는 것은 중요한 개발 역량이라고 생각합니다.
위의 경우 이외에도 다양한 예외 상황이 있을 수 있겠죠?
아무리 코드를 잘 작성했더라도, 실제 사용하는 입장에서 불편함이 생긴다면 그 코드가 충분한 가치를 제공한다고 보기 어려울 수 있어요.
There was a problem hiding this comment.
1. 생각해보니 아예 1000원 단위로 구매하도록 수정해볼까 합니다!
private static void validatePurchaseAmount(int price) {
if (price < PRICE_PER_ONE_LOTTO_TICKET) {
throw new IllegalArgumentException("구입 금액은 " + PRICE_PER_ONE_LOTTO_TICKET + "원 이상이어야 합니다.");
}
if (price % PRICE_PER_ONE_LOTTO_TICKET != 0) {
throw new IllegalArgumentException("구입 금액은 " + PRICE_PER_ONE_LOTTO_TICKET + "원 단위로 입력해야 합니다.");
}
}
2. [1,2,3,4,5,6] 과 같은 형식일 경우도 처리 가능하도록 구현해보는 방향을 떠올려봤습니다
InputView.inputWinningLottoNumbers().split(", ") 이 부분을
InputView.inputWinningLottoNumbers().split(",\\s*")로 수정하여 콤마 뒤에 공백이 있든 없든 유연하게 파싱하도록 변경해두었습니다
이는 쉼표 뒤에 0개 이상의 공백(\s*)이 올 때 이를 기준으로 문자열을 쪼개주는 방식으로, 지금 간단하게 적용하기에 좋은 방법같아서 이렇게 해보았습니다.
|
|
||
| public Lotto(List<Integer> userSelectedNumbers) { | ||
| if (userSelectedNumbers.size() != LOTTO_NUMBER_COUNT) { | ||
| throw new IllegalArgumentException("로또 번호는 6개여야 합니다."); |
There was a problem hiding this comment.
기획이 변경되어서 로또 번호 갯수를 7개로 변경한다고 가정해봅시다.
다른 누군가는 이렇게 생각합니다.
LOTTO_NUMBER_COUNT만 7로 변경하면 될 것 같아요.
하지만 해당 예외는 그대로 6개이네요. 어떻게 수정하면 좋을까요?
There was a problem hiding this comment.
throw new IllegalArgumentException("로또 번호는" + LOTTO_NUMBER_COUNT + "개여야 합니다.");
이렇게 간단하게 수정 가능합니다!
| import java.util.Arrays; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public class LottoChecker { |
There was a problem hiding this comment.
생성자에 메서드를 호출하는 순서와 실제 메서드 선언 순서가 다릅니다.
생성자 내부에서는 wrappingToIntegerLottoNumbers()를 먼저 호출하지만, 메서드 선언은 validateBonusNumber()가 먼저 나오고 있어요.
사용하는 순서에 맞춰 메서드 선언 순서를 정리하면 코드 흐름을 따라가기 더 쉬울 것 같습니다~
public LottoChecker(...) {
wrappingToIntegerLottoNumbers(...);
validateBonusNumber(...);
}
private void wrappingToIntegerLottoNumbers(...) {
}
private void validateBonusNumber(...) {
}또한 일반적으로 클래스 내부에서는 public, protected, private처럼 접근 범위가 넓은 메서드부터 좁은 메서드 순으로 배치하는 관례를 많이 따릅니다. LottoResult클래스에서도 동일하게 public을 먼저 배치해주세요 ~ 다른 클래스도 검토 부탁드려요
There was a problem hiding this comment.
생성자 내 호출순서와 접근제어자
두가지를 고려해서 배치를 수정해보았습니다!
| private void validateBonusNumber(String bonusNumber) { | ||
| try { | ||
| int number = Integer.parseInt(bonusNumber); | ||
| if (number < 1 || number > 45) { |
There was a problem hiding this comment.
1과 45는 어떤 것을 의미하는 숫자인가요?
매직넘버를 제거해봅시다.
매직넘버의 의미를 잘 모르겠다면 코멘트로 개념을 정리해주세요 ~
There was a problem hiding this comment.
매직넘버란 코드 안에 직접 적어둔 의미를 알기 힘든 숫자나 문자열 값입니다.
의미를 알 수 없어 코드를 읽기 어렵고, 값이 바뀔 때 수정하기 불편합니다.
따라서 의미 있는 이름의 상수로 선언하는 식으로 해결 가능합니다!
자동차 경주때에도 매직넘버를 제거해보았습니다만 구현하다보니 이런저런 고려사항에 정신이 팔려서(?)
일부 매직넘버를 사용하게 된거같습니다. 한번 더 의식하고 기피해보려고 노력해봐야겠습니다..ㅎㅎ
There was a problem hiding this comment.
Enum에서 사용된 숫자들을 제거해야할지 말아야할지 잘 모르겠어서 탐색을 해봤습니다.
-
의미 전달 관점 - 매직 넘버가 아님
FIRST_PLACE, LottoWinningType이라는 이넘 요소와 함께 적혀 있기 때문에, 이 숫자가 "1등 상금(20억 원)"이라는 도메인 의미가 명확함. 따라서 단순 로직 한가운데 적힌 매직 넘버와는 다름. -
유지보수 관점
문자열("6개 일치 (2000000000원)- ") 내부와 람다식(tickets * 2000000000)에 동일한 숫자가 중복해서 들어가 있음.
만약 상금이 변경된다면 두 곳을 모두 수정해야 하고, 한쪽을 빼먹는 실수가 발생할 수 있는 '변경 취약성'의 문제를 지닌다.
-> 보통의 매직넘버가 아니어도 수정 대상이 맞음
이 내용은 5단계 구현하면서 반영해보겠습니다!
|
|
||
| public class Application { | ||
|
|
||
| public static void main(String[] args) { |
There was a problem hiding this comment.
함수(또는 메서드)의 길이가 10라인을 넘어가지 않도록 구현한다.
main 메서드에도 동일하게 프로그래밍 요구사항을 지켜주세요
| @@ -0,0 +1,36 @@ | |||
| # 로또 (Lotto) 미션 | |||
|
|
|||
| ## 기능 요구 사항 | |||
There was a problem hiding this comment.
다양한 내용이 더 있을 것 같아요
로또가 얼마인지도 추가하면 좋겠네요!
하지만 해당 리뷰 반영은 선택 사항입니다 😄
There was a problem hiding this comment.
readme 작성법에 대해 잘 몰랐는데 참고하여 수정해보았습니다😊
| ### Controller | ||
| - **`Application`**: 사용자 입력, 비즈니스 로직 처리, 결과 출력으로 이어지는 전체 애플리케이션의 흐름을 순차적으로 제어합니다. | ||
|
|
||
| ### Domain |
There was a problem hiding this comment.
클래스별 역할을 README에 자세히 적기보다는, README는 기능 요구사항이나 실행 방법처럼 사용자가 확인해야 할 내용 중심으로 두는 게 더 좋을 것 같아요.
특히 TreeSet, ArrayList, Map 같은 내부 구현 방식은 코드가 바뀔 때 README와 쉽게 불일치할 수 있어서, 문서 유지보수 비용이 커질 수 있습니다. 클래스 책임은 코드와 테스트를 통해 드러나게 하고, README에서는 도메인 규칙이나 프로그램 동작 중심으로 정리해보면 어떨까요?
There was a problem hiding this comment.
readme 작성법에 대해 잘 몰랐는데 참고하여 수정해보았습니다😊
| public static final int LOTTO_NUMBER_BOUND = 45; | ||
| public static final int LOTTO_NUMBER_COUNT = 6; |
There was a problem hiding this comment.
해당 클래스에서만 사용되는데, 접근 제어자를 public으로 하신 이유가 있을까요?
There was a problem hiding this comment.
public을 해서 다른 클래스에서 사용할수있도록 하려고 했었는데, 제가 그렇게 안썼더라구요....
동일한 상수선언을 LottoChecker에 중복으로 선언해서 사용했었던것을 삭제하고,
Lotto.LOTTO_NUMBER_BOUND 형태로 사용하도록 수정했습니다.
따라서 LOTTO_NUMBER_BOUND에 대해서는 public으로 그대로 두었습니다.
public static final int LOTTO_NUMBER_LOWER_BOUND = 1;
public static final int LOTTO_NUMBER_BOUND = 45;
private static final int LOTTO_NUMBER_COUNT = 6;
다만 LOTTO_NUMBER_COUNT 는 해당 클래스에서만 사용하기때문에 private으로 고쳐두었습니다
| package domain; | ||
|
|
||
| public class LottoTicketCount { | ||
| public static final int PRICE_PER_ONE_LOTTO_TICKET = 1000; |
There was a problem hiding this comment.
LottoResult에도 PRICE_PER_ONE_LOTTO_TICKET이 있어요.
로또 하나의 가격을 2000원으로 변경한다면, 변경 지점이 여러 곳 이네요!
조금 개선해보면 좋을 것 같아요
There was a problem hiding this comment.
LottoTicketCount 에 두는것이 나을거같아서 여기 클래스에 있는 티켓가격상수를 살려두고
LottoResult에 있는것은 지웠습니다.
Application.java에서 사용시에도 LottoTicketCount.PRICE_PER_ONE_LOTTO_TICKET 해서 사용하도록 수정했습니다.
| import java.util.ArrayList; | ||
| import java.util.Scanner; | ||
|
|
||
| public final class InputView { |
|
로또 번호 관리 객체를 설계하며, 자료구조로 자료구조 비교
1. ArrayList
2. TreeSet (현재 프로젝트 채택)
최종 결론결과적으로 로또 번호는 단 6개뿐이라 두 자료구조 간의 성능(시간/공간 복잡도) 차이는 무의미에 가깝습니다. 하지만 '도메인의 규칙(중복 불가, 정렬)을 객체 스스로가 얼마나 잘 보장하는가?'라는 관점에서 접근했을 때, |
Enum 공부제가 참고했던 블로그들입니다.
예시 코드를 많이 모방하면서 구현했습니다..ㅎㅎ
먼저 기본적인 Enum 정의를 일차적으로 하고, |
제가 생각한 테스트 코드의 목적: 구현 중에는 기능 단위가 올바르게 동작하는지를 확인할수있고, 서비스 배포 이전에 이 테스트 코드를 돌려봄으로써 어떠한 이상이 없는지를 간단하게 확인 가능하다고 생각합니다. 탐색을 해보니, 테스트의 목적과 장점으로 크게 아래 6가지가 있었습니다.
제가 시간에 쫓겨서 테스트코드를 못넣었다보니까 ,, 테스트코드 작성을 위한 시간이 많이 든다라는 것이 단점이 아닐까 싶은데요, 길게 보면 기술적 부채를 줄이는 길이라는 생각이 듭니다. |
이 두가지 내용은 5단계 리팩토링과 일맥상통하는 부분이 있다고 생각해서 5단계 학습하면서 빠르게 반영해보겠습니다! |
Eian1106
left a comment
There was a problem hiding this comment.
안녕하세요 주희님~
전반적으로 리뷰 답변 퀄리티가 좋네요 💯
질문 있으시면 연락 주셔요~ 이번에도 화이팅입니다 👍
|
|
||
| public class LottoTicketCount { | ||
| public static final int PRICE_PER_ONE_LOTTO_TICKET = 1000; | ||
| private int lottoTicketCount; |
There was a problem hiding this comment.
lottoTicketCount를 객체의 상태로 두어야 하는 이유가 무엇일까요?
단순히 특정 메서드 안에서 계산하고 바로 반환하는 값 인 것 같아요 ~
There was a problem hiding this comment.
큰 고민없이 클래스의 인스턴스 변수로 두었었는데요, 성현님 질문을 통해 새롭게 배운 내용입니다.
- 객체지향프로그래밍에서 클래스의 인스턴스 변수는 그 객체가 존재하는동안 유지하고 관리해야할 데이터이다.
- 인스턴스 변수 (=상태)는 여러 메서드에 의해 변경되고 사용된다.
- 하지만 현재 LottoTicketCount 클래스의 lottoTicketCount 필드는 convertLottoPriceToTicketCount 메서드가 실행되는 동안에만 잠깐 사용되고, 그 값이 바로 반환된다. 다른 메서드에서 이 값을 다시 사용하지도 않고, 이 객체가 생성된 후에 lottoTicketCount 값이 계속해서 의미를 갖지도 않는다.
=> 결론: 인스턴스 변수로 두게 되면, 불필요한 상태가 되는 셈이다. 즉, 인스턴스 변수가 아닌, 메서드 안에서 사용하는 지역변수이면 충분하다.
There was a problem hiding this comment.
위와 같은 이유로 LottoTickectCount에서 상태를 가지지 않게 수정을 했습니다.
수정을 하고보니, LottoTicketCount 클래스를 유틸리티 클래스로 사용하는게 좋겠다는 판단이 들어서 유틸리티 클래스로 고쳐보았고 이 클래스 사용하던 곳에서도 객체생성없이 바로 클래스 호출하도록 코드를 수정했습니다.
| if (this.randomNumberSet.size() != LOTTO_NUMBER_COUNT) { | ||
| throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다."); | ||
| } | ||
| } |
There was a problem hiding this comment.
현재 Lotto 생성자는 전달받은 번호의 개수와 중복 여부만 검증하고 있습니다.
따라서 다른 개발자가 List.of(-1, 2, 3, 4, 5, 50)처럼 로또 번호 범위를 벗어난 값을 전달해도 Lotto 객체가 생성될 수 있습니다.
Lotto는 항상 유효한 로또 번호만 가지도록 1~45 범위 검증도 생성자 내부에서 함께 수행하는 것이 좋겠네요.
다른 개발자가 로또를 이렇게 생성한다면?
List<Integer> lottoNumbers = List.of(-1, 2, 3, 4, 5, 50);
Lotto lotto = new Lotto(lottoNumbers);There was a problem hiding this comment.
자동로또 생성자를 만들 당시에는 어차피 주어진 범위내에서 랜덤번호가 만들어졌다보니 로또번호 범위검증을 안넣었습니다.
그래서 수동로또를 만들때 미처 그부분을 고려하지 못했는데 아주 당연한 부분을 빼먹은듯합니다...ㅎ
수동로또 생성자에 번호 범위 검증을 추가했습니다!
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| public class Application { |
There was a problem hiding this comment.
기존은 수동 구매 개수를 사용자로부터 입력받을 때, 해당 입력값이 총 구매 금액으로 구매 가능한 로또의 총 개수(totalCount)를 초과할 수 있는지에 대한 검증이 없었습니다.
이 때문에 autoCount = totalCount - manualCount 계산 시 manualCount가 totalCount보다 커져서 autoCount가 음수가 되는게 가능했던 것입니다.
그래서 수동 구매 개수를 입력받는 시점에 총 구매 가능 개수를 초과하는지 검증하는 로직을 추가했습니다.
만약 입력값이 구매 가능한 총량을 넘어서면, 사용자에게 오류 메시지를 보여주고 재입력을 요청하도록 에러처리를 해두었습니다.
그리고 이와 동시에 수동 및 자동 구매 개수가 항상 0 이상임을 보장하는 방향으로 수정했습니다.
| private static void validateLottoNumbers(String numbersString) { | ||
| List<Integer> numbers = Arrays.stream(numbersString.split(",\\s*")) | ||
| .map(Integer::parseInt) | ||
| .collect(Collectors.toList()); | ||
| new Lotto(numbers); | ||
| } |
There was a problem hiding this comment.
메서드명은 validateLottoNumbers인데, 내부에서는 입력 문자열을 콤마 기준으로 분리하고 숫자로 변환한 뒤 Lotto 객체 생성을 통해 검증을 위임하고 있습니다.
먼저 로또 번호의 유효성 검증 책임이 View에 있어야 하는지 고민해보면 좋겠습니다. 객체는 자신의 상태를 스스로 보호해야 하므로, "로또 번호가 1~45 사이인지", "중복이 없는지", "6개인지" 같은 규칙은 Lotto가 보장하는 편이 더 적절해 보입니다.
View에서는 사용자 입력을 List로 변환하는 역할까지만 담당하고, 메서드명도 실제 역할에 맞게 parseLottoNumbers() 또는 parseNumbers()처럼 표현하면 더 자연스러울 것 같습니다.
추가로
객체 생성 책임도 함께 고민해보면 좋겠습니다.
View의 주된 책임은 사용자에게 메시지를 출력하고, 사용자의 입력을 받아 애플리케이션이 사용할 수 있는 형태로 전달하는 것이라고 생각합니다. 반면 Lotto 같은 도메인 객체를 언제, 어떤 입력으로 생성할지는 애플리케이션 흐름이나 도메인 규칙과 더 가까운 책임입니다.
따라서 View에서 new Lotto(numbers)를 호출하기보다는, View는 입력값을 List<Integer> 또는 문자열 형태로 반환하고 Application 에서 Lotto를 생성하도록 분리하면 역할이 더 명확해질 것 같습니다~
| import domain.LottoWinningType; | ||
| import java.util.Map; | ||
|
|
||
| public final class OutputView { |
There was a problem hiding this comment.
의도적으로 유틸리티 클래스로 만들어보았습니다! 객체의 필드 상태를 바꾸지 않고, 넣은 값에 따라 결과만 나오는 경우이므로 이렇게 하는 것이 좋겠다는 판단을 했습니다.
말씀하신것처럼 메서드를 static으로 선언하여 객체 생성 없이 클래스명.메서드명()으로 호출가능하게 했습니다.
|
|
||
| @DisplayName("1000원 미만일 경우 예외를 발생시킨다.") | ||
| @ParameterizedTest | ||
| @ValueSource(ints = {0, 100, 999}) |
There was a problem hiding this comment.
@ValueSource를 활용해 1,000원 미만의 여러 케이스를 검증해주신 점 좋았습니다 👍






🙇♂️ 리뷰어에게 전하는 간단한 인사말
안녕하세요 성현님! 이번 로또 미션 리뷰를 맡아주셔서 감사합니다. 잘 부탁드리겠습니다!
🧑💻 본인의 현재 상황
🧗 이번 미션에서 어려웠던 부분
1. 자료구조 전략적 선택과 타입 불일치 에러
1단계 도입 시점부터 로또에는 중복된 번호가 들어갈 수 없다는 점을 고려해 의도적으로
TreeSet을 사용했습니다. 1단계 힌트에 있던 sort는 그러한 이유에서 사용을 따로 안하게 되었습니다. shuffle또한 사용하지 않았습니다. (혹시 힌트를 최대한 따르는 것을 권장하나요?)그런데 3단계에서 입력받은 보너스 번호를
String타입 그대로TreeSet<Integer>의contains()에 넘겼다가ClassCastException을 마주했습니다. 다행히 오류 지점을 찾아서 수정해두었습니다.2. 보너스 번호 추가로 인한 판정 로직 수정과 클래스 역할 분리
2단계까지는 당첨 번호와 일치하는 '개수'만을 기준으로 등수를 판단했기에 비교적 무리 없이 구현했습니다. 하지만 3단계 진입 후 보너스 번호가 추가되자 기존 로직으로는 2등 판별이 불가능해져서 코드가 많이 꼬였습니다.... 결국 로직을 분리해 내어
LottoChecker(비교),LottoStatistics(통계),LottoResult(수익률)로 각각의 책임을 나누는 리팩토링을 거치면서 보너스 번호를 처리할수있게 해두었습니다. 특히,LottoChecker에hasBonusNumber()메서드를 추가하여 특정 티켓의 보너스 번호 포함 여부를boolean matchBonus로 추출하고, 이를 일치 개수와 함께 Enum(LottoWinningType)의 상태 값으로 넘겨 2등(5개+보너스)과 3등(5개)을 깔끔하게 판별할수있도록 구조를 개선해보았습니다.3. Enum ???
제가 Enum에 대해 거의 아는 바가 없고 사용해본적도 없어서 미션 3단계 시점에 Enum을 적용하기 위해 우아한형제들 기술 블로그(Java Enum 활용기) 등을 찾아보았습니다. 하지만 결제 시스템 기반의 예시라 현재 로또 미션에서 요구하는 수준보다 훨씬 난이도가 높고 복잡하게 느껴졌습니다. 그래서 더 기초적인 사용 예시들을 뒤적이며, 상태와 금액 계산식(람다)을 구현하는 선에서 저만의 Enum을 어찌저찌 완성해 보았습니다. Enum을 사용하라고만 적혀있어서 이 부분에서 가장 시간을 많이 들였던거 같습니다만, "데이터들의 연관관계 표현"에서 이점이 있다는 점을 기반으로 제 나름대로 [로또 당첨 등수 - 당첨 시 출력되는 설명문구 - 당첨금 계산식] 이 세가지를 Enum으로 엮어보았습니다.
4. 수익률 잘못 계산되는 버그
4단계를 구현하고 테스트하던 중 총 수익률이 2.14가 아닌 2143.21로 계산되는 황당한 버그를 겪었습니다ㅎㅎ... 원인을 찾아보니
Application에서 이미 1,000원이 곱해진 총금액을LottoResult로 넘겼는데, 객체 내부에서 또 1,000원을 곱해 구입 금액이 1,400만 원으로 계산된거였습니다. 이때는 비교적 원인 지점을 찾아내기 쉬웠던거 같습니다.5. 일급 컬렉션의 확장과 무한 재귀 에러
4단계 수동 구매를 구현하면서 새로운 수동 전용 클래스를 만들기보다는, 기존
Lotto객체에 수동 번호를 주입받는 생성자를 추가하여 하나의LottoTickets가 두 종류의 로또를 모두 생성할수있도록 구현했습니다. 추가로InputView에서 자원을 해제하려다closeScanner()내부에 자기 자신을 호출하는 오타를 내어StackOverflowError를 만나는 등 자잘한 시행착오도 있었습니다.🔍 리뷰에서 중점적으로 봐주셨으면 하는 부분 & 질문
LottoWinningType) 설계의 적절성: 제가 기초적인 수준으로 고민하여 적용해 본 현재의 Enum 구조(상태와 람다식을 활용한 금액 계산)가 객체지향적으로 올바른 방향인지, 피드백을 부탁드립니다.