Skip to content
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

[1단계 - 로또 구현] 에드(김진우) 미션 제출합니다. #585

Open
wants to merge 53 commits into
base: jinu0328
Choose a base branch
from

Conversation

jinu0328
Copy link

@jinu0328 jinu0328 commented Feb 13, 2025

신경쓴 점

  1. WinningLotto가 프로그램에서 유일하게 존재하는 당첨 로또라는 의미로 싱글톤 패턴으로 구현하였는데, 우선 테스트할 때 어려움을 겪었습니다.
  2. Getter와 Setter를 최대한 지양하기 위해 Lotto와 WinningLotto 사이에서 양방향 메서드를 사용하거나 DTO를 도입하였습니다. 구현하고보니 getter는 사용하지 않게 되었지만 양방향 메서드를 통한 Lotto와 WinningLotto가 서로를 참조하게 되는 상황입니다. 이에 대해서 그냥 getter를 일부 허용하는 것이 더 나은 방법일지 혹은 다른 개선 방안이 있을 지 궁금합니다.

Prolog 링크

Prolog

java-lotto

로또 미션 저장소

입력

  • 구입 금액을 입력한다
    • 음수면 예외처리
    • 숫자가 아니면 예외처리
    • 1000원으로 안나눠지면 예외처리
  • 지난 주 당첨번호를 입력한다
    • 콤마(,)를 구분자로 사용한다
    • 숫자가 6개가 아니면 예외처리
    • 숫자가 아닌 값이 들어오면 예외처리
    • 1~45 범위의 숫자가 아니면 예외처리
    • 중복된 숫자면 예외처리
  • 보너스 번호를 입력한다.
    • 앞선 당첨번호랑 중복되면 예외처리
    • 숫자가 아닌 값이 들어오면 예외처리
    • 1~45 범위의 숫자가 아니면 예외처리
  • 문자열로 입력 받은 구입 금액을 정수로 변환한다.
  • 문자열로 입력 받은 당첨 번호를 정수 리스트로 변환한다.
  • 문자열로 입력 받은 보너스 번호를 정수로 변환한다.

도메인

로또 발행

  • 구입 금액을 로또 가격으로 나눈만큼 로또를 생성한다
  • 로또는 6개의 번호를 가진다
    • 6개의 번호를 중복없이 1~45 랜덤으로 생성한다
      • 생성된 번호를 오름차순 정렬한다

당첨 판단

  • 당첨 통계를 생성한다
  • 당첨 개수에 따른 상금은 다음과 같다
3개 일치 (5000원)
4개 일치 (50000원)
5개 일치 (1500000원)
5개 일치, 보너스 볼 일치(30000000원)
6개 일치 (2000000000원)
  • 총 상금을 계산한다
  • 수익률을 계산한다

출력

  • 발행된 로또의 개수를 산출하여 출력한다
  • 발행된 로또의 번호를 출력한다
  • 당첨 통계를 출력한다

- 구입 금액 입력 받는 함수 getPurchaseInput 구현
- 구매개수 출력 메서드 구현
- 기본 검증 메서드 구현(개수, 범위, 중복)
- 당첨번호 일치, 보너스번호 일치 판단 메서드 구현
- 생성된 난수 오름차순 정렬하여 Lotto 객체 생성
- 구입 금액 유효성 검사 메서드 구현
- Lotto 클래스 내에 toDto 메서드 구현
- 싱글턴 패턴 적용
- 보너스 번호 중복 및 범위 유효성 검사 메서드 구현
- Lotto와 WinningLotto 객체의 양방향 메서드로 구현
- 분리된 메서든 간 데이터 전달을 위해 record를 사용
@yebink
Copy link

yebink commented Feb 13, 2025

안녕하세요 에드!

작성해주신 신경쓴 점에 대해 피드백 드려요 😄

  • WinningLotto 싱글톤 관련
    • 테스트 시 어려움을 겪었음에도 싱글톤으로 작성한 이유는 무엇인가요~? 싱글톤이 아니게 되면 발생하는 문제가 있을까요?
    • 이와 별개로, 테스트 순서에 따라 성공/실패 여부가 달라진다면 테스트 간 의존이 있다는 것인데요! 만약 신규 요구사항으로 인해 WinningLottoTest 에 새로운 테스트가 생겼다고 가정해보아요. 그때마다 @Order 를 지정해야겠죠? 실무에서는 하나의 프로젝트에 수 백, 수 천 개의 테스트 코드가 있습니다. 그렇다보니 테스트 간 의존성은 없애는 것이 좋습니다 🙂
    • 해당 내용은 당장 반영하지 않아도 되니 2단계에서도 충분히 고민해보아요~
  • 순환참조 관련
    • Lotto#rankTier 에서 WinningLotto 를 참조하고 있군요! 저는 getter를 남발하는 것이 아니라면 사용하는 것이 크게 문제는 아니라고 생각합니다 🙂 의존성 방향에 대한 부분도 아직 고민할 단계가 아니니 걱정하지 마세요! 작성해주신 코드도 충분히 잘하셨습니다. 👏

@ParameterizedTest 를 사용해 테스트를 아주 잘 작성하셨네요! 👏 💯
@ParameterizedTest 는 1개의 테스트로 비슷한 여러 케이스를 묶어서 확인할 수 있다는 장점이 있지만, 코드를 읽기 어렵다는 치명적인 단점이 있습니다. 다음 단계에서는 @Test 도 충분히 학습해보시길 바라요 👍

Copy link

@yebink yebink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 에드~
1단계 리뷰어 엘리입니다 😄

로또 미션 구현하느라 고생 많으셨어요! 👏 👏 👏
리뷰 확인해보시고 궁금한 부분이 있으면 편하게 DM 주세요~


@ParameterizedTest
@MethodSource("provideInvalidLottoNumbersCount")
void 번호가_6개가_아니면_예외가_발생한다(List<Integer> numbers) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

경계값(5, 7)에 대해 잘 테스트하셨네요! 💯

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다음 단계에서는 @DisplayName 에 대해 학습하고 사용해보아요!


@ParameterizedTest
@MethodSource("provideValidLottoNumbers")
void 제대로된_번호가_오면_예외가_발생하지_않는다(List<Integer> numbers) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서로 다른 4가지 경우의 수에 대해 테스트하고 있는데, 작성된 테스트를 읽는 것도 코드 유지보수 비용이랍니다.

4가지 모두 반드시 필요한 테스트일지 고민해보아요. 🙂


public class LottoFactory {
public static Lotto createLotto(List<Integer> numbers) {
return new Lotto(numbers);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LottoFactory#createLotto 는 사실상 Lotto 생성자를 한 번 감싼 형태인데요~ 해당 메서드가 필요한 이유가 무엇일까요?

다른 Factory 클래스들도 꼭 필요한 것인지 고민해보아요 😄

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서비스 레이어에서 모델 객체를 직접 new를 통해 생성하기보다 팩토리 클래스를 둠으로써 객체 생성의 책임을 한 곳에서 관리하려 했던 것 같습니다!

엘리의 리뷰를 보고 다시 고민해보니, 현재 Lotto를 비롯한 모델 객체들의 생성 방식이 단순한 편이라 "객체 생성"이라는 책임이 그렇게 무겁지 않음을 깨달았습니다.
즉, 모든 모델 객체에 팩토리 패턴을 적용할 필요는 없을 것 같고, Lottos나 Statistics와 같은 모델에는 팩토리 클래스를 따로 두지 않는 방향이 더 적절할 것 같습니다.

다만, LottoFactory의 경우 다음과 같이 리팩토링 한다면 유지하는 것이 의미가 있을 것 같습니다.
실제 로또 시스템에서는 자동/수동을 선택하여 구매할 수 있으며, 현재 프로젝트 요구사항에서도

사용자가 구매한 로또 → 자동 생성
지난주 당첨 로또 → 수동 생성
이 적용되므로, 생성 방식을 분리하는 것이 팩토리 패턴을 적용할 이유가 될 수 있다고 생각했습니다.

💡 리팩토링 방향

public class LottoFactory {

    // 수동 로또 생성
    public static Lotto createLotto(List<Integer> numbers) {
        return new Lotto(numbers);
    }

    // 자동 로또 생성
    public static Lotto createRandomLotto() {
        List<Integer> randomNumbers = RandomGenerator.generateNumbers(1, 45, 6); // 유틸 호출
        return new Lotto(randomNumbers);
    }
}

이렇게 변경하면,

현재는 객체 생성 방식이 2가지(자동/수동)로 나뉜 것이지만
추후 로또 생성 방식이 더 확장되더라도 쉽게 적용할 수 있는 구조를 가질 수 있다는 점에서 팩토리 패턴이 의미를 가질 수 있을 것 같습니다!
피드백 주시면 감사하겠습니다! 🙏😊

Comment on lines +68 to +72
for (Integer number : numbers) {
if (number < 1 || number > 45) {
throw new IllegalArgumentException(ErrorMessage.NUMBER_OUT_OF_RANGE.getMessage());
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

규칙 1: 한 메서드에 오직 한 단계의 들여쓰기(indent)만 한다. 규칙에 맞게 고쳐볼까요~?

Comment on lines +11 to +13
public void addLotto(Lotto lotto) {
lottos.add(lotto);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

일급컬렉션의 중요한 특징 중 하나는 컬렉션의 불변을 보장한다. 입니다.
addLotto 메서드는 lottos 에 변경을 가하고 있네요! 😅

일급컬렉션의 목적에 맞게 코드를 수정해볼까요?
참고 블로그 : https://jojoldu.tistory.com/412

Copy link
Author

@jinu0328 jinu0328 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

첫 리뷰 꼼꼼히 달아주셔서 감사합니다!!
피드백 주신 부분들 더 깊게 고민해보고 반영하여 다시 리뷰 요청 드리겠습니다 😄


public class LottoFactory {
public static Lotto createLotto(List<Integer> numbers) {
return new Lotto(numbers);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

서비스 레이어에서 모델 객체를 직접 new를 통해 생성하기보다 팩토리 클래스를 둠으로써 객체 생성의 책임을 한 곳에서 관리하려 했던 것 같습니다!

엘리의 리뷰를 보고 다시 고민해보니, 현재 Lotto를 비롯한 모델 객체들의 생성 방식이 단순한 편이라 "객체 생성"이라는 책임이 그렇게 무겁지 않음을 깨달았습니다.
즉, 모든 모델 객체에 팩토리 패턴을 적용할 필요는 없을 것 같고, Lottos나 Statistics와 같은 모델에는 팩토리 클래스를 따로 두지 않는 방향이 더 적절할 것 같습니다.

다만, LottoFactory의 경우 다음과 같이 리팩토링 한다면 유지하는 것이 의미가 있을 것 같습니다.
실제 로또 시스템에서는 자동/수동을 선택하여 구매할 수 있으며, 현재 프로젝트 요구사항에서도

사용자가 구매한 로또 → 자동 생성
지난주 당첨 로또 → 수동 생성
이 적용되므로, 생성 방식을 분리하는 것이 팩토리 패턴을 적용할 이유가 될 수 있다고 생각했습니다.

💡 리팩토링 방향

public class LottoFactory {

    // 수동 로또 생성
    public static Lotto createLotto(List<Integer> numbers) {
        return new Lotto(numbers);
    }

    // 자동 로또 생성
    public static Lotto createRandomLotto() {
        List<Integer> randomNumbers = RandomGenerator.generateNumbers(1, 45, 6); // 유틸 호출
        return new Lotto(randomNumbers);
    }
}

이렇게 변경하면,

현재는 객체 생성 방식이 2가지(자동/수동)로 나뉜 것이지만
추후 로또 생성 방식이 더 확장되더라도 쉽게 적용할 수 있는 구조를 가질 수 있다는 점에서 팩토리 패턴이 의미를 가질 수 있을 것 같습니다!
피드백 주시면 감사하겠습니다! 🙏😊

@jinu0328
Copy link
Author

  • 테스트 시 어려움을 겪었음에도 싱글톤으로 작성한 이유는 무엇인가요~? 싱글톤이 아니게 되면 발생하는 문제가 있을까요?

싱글톤이 아니게 된다면 이미 당첨 로또 객체가 존재하더라도 다른 객체가 추가로 생성될 수 있다는 위험성이 증가하고 한번 생성한 뒤에 해당 당첨 로또 객체를 계속 들고 다니며 코드를 작성해야한다는 단점이 있을 것 같습니다!

싱글톤을 유지하면서 테스트를 구현할 방법으로 WinningLotto에 객체 초기화 메서드를 따로 두는 것도 고민해봤지만 테스트만을 위한 메서드가 메인 로직에 포함되는 것은 바람직하지 않다고 생각했습니다.

테스트에서 어려움이 있었던 만큼, 싱글톤을 유지하는 것보다 서비스 레이어에서 1회만 생성 후 주입하는 방식으로 변경하는 것이 더 나은 선택일까요??

- createCustomLotto는 당첨 로또 생성시, createRandomLotto는 사용자 구매 로또 생성시 사용
- Lottos 객체 생성의 경우 별도의 복잡한 로직이나 여러개의 방식을 제공하지 않으므로 서비스 레이어에서 직접 new로 생성하는 방식으로 변경
- Statistics 객체 생성의 경우 별도의 복잡한 로직이나 여러개의 방식을 제공하지 않으므로 서비스 레이어에서 직접 new로 생성하는 방식으로 변경
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants