-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
[로또] 신진영 미션 제출합니다. #1357
base: main
Are you sure you want to change the base?
[로또] 신진영 미션 제출합니다. #1357
Conversation
- 구입 금액 입력 시 금액이 1,000원 이상이며 1,000원 단위로 입력되는지 검증 - 숫자가 아닌 값 입력 시 예외 처리 - 금액이 int 범위를 초과할 경우 예외 처리 - 잘못된 입력이 있을 경우 재입력 기능 추가
- Money 클래스명을 LottoPurchaseMoney로 변경하여 로또 구입 금액과 관련된 역할이 명확히 드러나도록 개선함 - 패키지 구조 및 테스트 케이스명을 좀 더 구분하기 쉽고 일관되게 변경함
- 입력된 구입 금액을 바탕으로 발행할 로또 개수를 계산하는 기능 구현 - 구입 금액을 1,000원 단위로 나누어 발행할 로또 티켓 수를 결정 - 금액 검증 로직 적용을 통해 1,000원 단위로 나누어 떨어지지 않는 경우 예외 처리 - 랜덤 로또 발행을 위한 LottoNumberGenerator 클래스 구현
- InputHandler에서 입력 검증 로직을 분리하여 책임 명확화 - LottoPurchaseMoney는 금액 검증 및 로또 개수 계산만 수행하도록 수정 - WinningLotto는 당첨 번호 파싱과 검증 후 정렬 처리로 역할 한정
- 로또 관련 상수를 별도의 클래스로 분리 - static import 사용하여 코드 가독성 유지
- WinningLotto 클래스를 통해 사용자가 입력한 당첨 번호 처리 - 당첨 번호의 유효성 검증 로직 구현
- 보너스 번호를 입력받고 검증하는 로직 추가 - 당첨 번호의 파싱 중 예외처리 추가
- 구매한 로또 번호와 당첨 번호를 비교하여 각 등수별 당첨 횟수 계산(determineWinningRanks) - 등수는 Rank Enum으로 관리 - determineWinningRanks 구현을 위해 도메인 로직과 서비스 로직 분리
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.
객체지향 설계에 대해 공부하시고 열심히 적용하신게 느껴져서 너무 좋았습니다!! 고생많으셨고, 마지막 주차도 함께 힘내봅시다!! 😄
int totalPrize = winningResult.calculateTotalPrize(); | ||
double profitRate = winningResultService.calculateProfitRate(totalPrize, purchaseAmount.getAmount()); | ||
|
||
OutputHandler.printWinningStatistics(winningResult, profitRate); |
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.
현재 InputHandler와 OutputHandler는 정적 메서드로 사용되고 있어, play() 메서드가 직접적으로 InputHandler와 OutputHandler에 의존하고 있습니다. 의존성을 낮추고, 향후 테스트나 확장을 용이하게 하기 위해 InputHandler와 OutputHandler를 생성자로 주입받는 방식도 좋을 것 같습니다!
|
||
OutputHandler.printWinningStatistics(winningResult, profitRate); | ||
|
||
Console.close(); |
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.
Console.close()와 같은 종료 처리가 필요하다면, close() 메서드를 따로 분리해 play() 메서드가 실제 게임 진행 부분만 담당하도록 하는게 더 책임이 명확한 것 같습니다~
|
||
private List<Integer> parseWinningNumbers(String input) { | ||
if (!input.matches(WINNING_LOTTO_REGEX)) { | ||
throw new IllegalArgumentException("[ERROR] 당첨 번호는 숫자와 쉼표만 포함할 수 있습니다."); |
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.
예외 메시지를 상수화하시면 더 정확(오타 방지)하고 일관적이고 유지보수 높게 메시지를 드러낼 수 있을 것 같습니다!
|
||
public class WinningLotto { | ||
private static final String NUMBER_DELIMITER = ","; | ||
private static final String WINNING_LOTTO_REGEX = "^[0-9,]+$"; |
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_DELIMITER와 WINNING_LOTTO_REGEX와 같은 상수들이 클래스 외부에서 재사용될 필요가 있게된다면, 해당 상수들을 한 곳에 모아 따로 관리하는 방식도 좋을 것 같습니다!
private Map<Rank, Integer> initializeRankCountMap() { | ||
Map<Rank, Integer> rankCountMap = new EnumMap<>(Rank.class); | ||
for (Rank rank : Rank.values()) { | ||
rankCountMap.put(rank, 0); |
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.
initializeRankCountMap() 메서드는 EnumMap을 초기화하는 데 잘 사용되고 있는데, EnumMap을 초기화할 때 직접 Rank.values()를 순회하지 않고 Collections.fill() 등의 방식을 사용할 수도 있습니다!
public WinningResult determineWinningRanks(LottoTickets lottoTickets, WinningLotto winningLotto) { | ||
WinningResult winningResult = new WinningResult(); | ||
|
||
lottoTickets.getLottoTickets().forEach(lotto -> { |
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.
현재, for each를 사용해 LottoTickets의 각 로또 티켓을 순회하며 당첨 순위를 계산하고 있는데, stream api를 사용하면 가독성이 더욱 높아지고, 코드의 흐름을 좀 더 선언적으로 표현할 수 있을 것 같습니다. 또한, if문 대신 filter를 사용하여 처리하면 코드가 더 간결해질 것 같습니다!
public static WinningLotto getWinningLotto() { | ||
while (true) { | ||
try { | ||
System.out.println(System.lineSeparator() + "당첨 번호를 입력해 주세요."); |
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.
InputHandler는 입력을 처리하고, 질의문 출력과 같은 화면 출력 관련 작업은 OutputHandler의 역할이라고 저는 생각합니다.
OutputHandler에서 모든 출력 로직을 관리하면 책임 분리가 명확해지고, InputHandler는 입력과 관련된 역할에만 집중할 수 있어 코드가 더욱 간결해질 것 같은데 어떻게 생각하시나용?
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.
저는 실제 입, 출력보다는 논리적인 의미의 '입력을 받는 부분'과 '출력을 받는 부분'으로 나누는게 더 로직의 응집도가 높다고 생각했습니다 :)
void 당첨번호가_1에서_45범위를_벗어나면_예외가_발생한다() { | ||
assertThatThrownBy(() -> WinningLottoValidator.validate(List.of(0, 2, 3, 4, 5, 6), 7)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessageContaining(ERROR_MESSAGE); |
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.
각 테스트에서 ERROR_MESSAGE의 일부만 확인하고 있는데, 테스트에 따라 메시지를 세분화해서 각 검증 케이스에 맞는 구체적인 에러 메시지를 작성하면, 문제가 발생한 정확한 원인을 파악하는 데 더 도움이 될 것 같습니다!
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,000원 단위로 입력 받아야 하며, 양수여야 한다. | ||
- 예외 상황: | ||
- 구입 금액이 공백일 경우: `IllegalArgumentException`을 발생시키고, "[ERROR] 입력값은 필수 입력 항목입니다." 메시지를 출력한다. | ||
- 구입 금액이 숫자가 아닐 경우: `NumberFormatException`을 발생시키고, "[ERROR] 입력값은 숫자여야 합니다." 메시지를 출력한다. |
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.
결과적으로는 IllegalArgumentException를 발생시키기 때문에 'IllegalArgumentException'을 발생시킨다. 가 좋을 거 같아요!
try { | ||
Long.parseLong(input); | ||
} catch (NumberFormatException e) { | ||
throw new IllegalArgumentException("[ERROR] 입력값은 숫자여야 합니다."); |
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.
실수를 입력해도 해당 예외가 일어나기 때문에 "입력값은 정수여야 합니다."가 좋을 거 같아요!
System.out.println(e.getMessage()); | ||
} | ||
} | ||
} |
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.
당첨 번호를 입력 받는 것과 보너스 번호를 입력 받는 것을 나누어서 처리하면 사용자 입장에서 좋을 거 같아요!
} catch (NumberFormatException e) { | ||
throw new IllegalArgumentException("[ERROR] 당첨 번호는 숫자여야 합니다."); | ||
} | ||
} |
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.
쉼표를 기준으로 숫자를 나눌 때 쉼표와 숫자 사이에 공백을 무시할 수 있도록 하면 사용자 입장에서 좋을 거 같아요!
구현할 기능 목록
IllegalArgumentException
을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.IllegalArgumentException
,IllegalStateException
등과 같은 명확한 유형의 예외를 처리해야 한다.로또 구입 금액을 입력받는다.
IllegalArgumentException
을 발생시키고, "[ERROR] 입력값은 필수 입력 항목입니다." 메시지를 출력한다.NumberFormatException
을 발생시키고, "[ERROR] 입력값은 숫자여야 합니다." 메시지를 출력한다.IllegalArgumentException
을 발생시키고, "[ERROR] 입력값이 너무 큽니다." 메시지를 출력한다.IllegalArgumentException
을 발생시키고, "[ERROR] 구입 금액은 1,000원 이상이어야 합니다." 메시지를 출력한다.IllegalArgumentException
을 발생시키고, "[ERROR] 구입 금액은 1,000원 단위여야 합니다." 메시지를출력한다.
발행할 로또의 개수를 구한다.
로또 번호를 발행한다.
IllegalArgumentException
발생, "[ERROR] 로또 번호는 6개여야 합니다." 메시지를 출력한다.IllegalArgumentException
발생, "[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다." 메시지를 출력한다.IllegalArgumentException
발생, "[ERROR] 로또 번호는 중복될 수 없습니다." 메시지를 출력한다.발행한 로또 수량 및 번호를 출력한다.
발행한 로또의 수량과 로또 번호를 오름차순으로 정렬하여 출력한다.
예시
당첨 번호를 입력받는다.
IllegalArgumentException
발생, "[ERROR] 입력값은 공백일 수 없습니다." 메시지를 출력한다.NumberFormatException
발생, "[ERROR] 당첨 번호는 숫자여야 합니다." 메시지를 출력한다.IllegalArgumentException
발생, "[ERROR] 당첨 번호는 1부터 45 사이의 숫자여야 합니다." 메시지를 출력한다.IllegalArgumentException
발생, "[ERROR] 당첨 번호는 6개여야 합니다." 메시지를 출력한다.IllegalArgumentException
발생, "[ERROR] 당첨 번호는 중복될 수 없습니다." 메시지를 출력한다.IllegalArgumentException
발생, "[ERROR] 당첨 번호는 숫자와 쉼표만 포함할 수 있습니다." 메시지를출력한다.
보너스 번호를 입력받는다.
IllegalArgumentException
발생, "[ERROR] 입력값은 공백일 수 없습니다." 메시지를 출력한다.NumberFormatException
발생, "[ERROR] 입력값은 숫자여야 합니다." 메시지를 출력한다.IllegalArgumentException
발생, "[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다." 메시지를 출력한다.IllegalArgumentException
발생, "[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다." 메시지를 출력한다.구매한 로또 번호와 당첨 번호를 비교하여 당첨 여부를 판별한다.
총 당첨금액을 계산한다.
총 수익률을 계산한다.
(총 당첨 금액 / 로또 구입 금액) * 100
을 계산한다.당첨 통계를 출력하고 로또 게임을 종료한다.
당첨 통계는 당첨 내역을 출력한 후에, 계산된 총 수익률을 포함한다.
당첨 내역은 각 등수별 당첨 개수와 상금을 포함한다.
예시
프로젝트 구조