Skip to content

Conversation

@yangseungin
Copy link

@yangseungin yangseungin commented Nov 19, 2025

안녕하세요 자바지기님. 이전 단계의 피드백을 반영해보는것으로부터 시작을 해봤습니다.

WinningResult에서 일급컬렉션으로 분리하는것에 대한 의견을 주셨는데요.
#4196 (comment)

생각해봤을떄 Rank와 int 갯수를 갖는 객체가 될 것 같은데 특정 등수의 결과 객체가 되어 그 등수의 전체 금액이 얼마인지 물어볼 수 있게 될 것 같아요. 지금도 Map<Rank, Integer>로 맵에 랭크별 갯수를 담는다는 의미로 충분히 잘 쓰이지 않았나 생각이 들어 수정하지는 않았습니다.
의견을 남기면서 코드를 보니 WinningResult의 아래 구현부분을 보지않고 딱 Map<Rank, Integer>만 보고 Integer가 랭킹별 총 금액라던가 다른 의미로 해석할 수도 있을 것 같네요

@yangseungin
Copy link
Author

이전 단계의 피드백만 반영하고 끝내기 아쉬워 3단계도 진행하였습니다.
WinningNumbers를 추가하고나서 수정하다보니 에러나는부분이 끊어지질 않아서 너무 크게 commit이 되버린 것 같아요
진행은 이런식으로 하였습니다.
WinningNumbers 구현 -> 기존 Rank에 매치보너스 필드 추가 -> Lotto의 로또끼리비교하던 match메서드를 WinningNumbers와 비교하도록 수정 -> WInningResult에도 Lotto대신 WinningNumbers -> IntputView, OutputView 수정

@yangseungin
Copy link
Author

#4200 (comment)
이것과 관련해서 궁금한점이 있어서 남겼습니다.

Q1. 기존 Rank가 변함에 따라 연쇄적으로 실패하는 코드들이 발생하였고 이를 수정하다보니 마지막 InputView OutputView까지 모두 수정하고 나서 모든 에러가 잡히게 되어버렸는데요. 이런 경우 커밋을 어느단위로 나눠야할지 애매한것 같은데 좋은 방법이 있을까요?

Copy link
Contributor

@javajigi javajigi left a comment

Choose a reason for hiding this comment

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

확실히 2단계 객체 설계를 잘하다보니 3단계에서 많은 변화를 하지 않은 상태로 기능을 추가했네요. 👍
피드백 내용이 많지 않아 바로 머지하려다 안정적으로 리팩터링하는 연습해 봤으면 하는 바람으로 request changes로 처리했어요.

Q1. WinningNumbers 구현 -> 기존 Rank에 매치보너스 필드 추가 -> Lotto의 로또끼리비교하던 match메서드를 WinningNumbers와 비교하도록 수정 -> WInningResult에도 Lotto대신 WinningNumbers -> IntputView, OutputView 수정

A1. 위와 같이 접근할 수 밖에 없고, 컴파일 에러가 생길 수 밖에 없습니다.
단, 이와 같이 클래스 타입, 메서드 시그니처를 변경할 때 컴파일 에러를 최소화하면서 리팩터링하는 연습해볼 것을 추천합니다. 다음 주 강의 주제이기도 해요.

힌트: WinningResult(LottoTickets tickets, Lotto winningNumbers) -> WinningResult(LottoTickets tickets, WinningLotto winningNumbers) 리팩터링할 때 컴파일 에러가 생기지 않도록 하려면 리팩터링을 끝내기 전까지 이 두개의 생성자가 동시에 존재할 수도 있음.

이와 같이 리팩터링할 경우 리팩터링 단위가 작다면 모든 에러를 해결하고 리팩터링을 완료한 후에 커밋하는 것이 좋고, 리팩터링 단위가 클 경우 객체 하나씩 리팩터링을 끝내는 것과 같이 독립된 단위를 구분해 커밋하는 것을 추천합니다.

Comment on lines 72 to 76
public Rank match(WinningNumbers winningNumbers) {
int matchCount = countMatch(winningNumbers.getLotto());
boolean matchBonus = hasMatchBonus(winningNumbers, matchCount);
return Rank.valueOf(matchCount, matchBonus);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

WinningNumbers의 값을 꺼내고 있다.
이 match 메서드가 WinningNumbers으로 이동하면 어떨까?
Rank rank = winningNumbers.match(lotto);
이와 같이 구현할 경우 현재 구현과 어떻게 달라지는지 고민해 보면 어떨까?

@@ -1,27 +1,35 @@
package lotto.domain;

public class PurchaseAmount {
Copy link
Contributor

Choose a reason for hiding this comment

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

PurchaseAmount, Money를 굳이 분리하지 않고 하나로 합치는 것은 어떨까?
둘의 역할이 애매함이 있는 것 같은 느낌

Copy link
Author

Choose a reason for hiding this comment

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

Money는 실제로 PurchaseAmount에서만 사용하긴하네요.
PurchaseAmount가 이미 로또 구매 금액이라는 책임을 가지고 있으니 이쪽으로 통합해도 될 것 같아요

Comment on lines +9 to +13
public WinningNumbers(Lotto lotto, LottoNumber bonusNumber) {
validateDuplicate(lotto, bonusNumber);
this.lotto = lotto;
this.bonusNumber = bonusNumber;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public WinningNumbers(Lotto lotto, LottoNumber bonusNumber) {
validateDuplicate(lotto, bonusNumber);
this.lotto = lotto;
this.bonusNumber = bonusNumber;
}
public WinningNumbers(List<Integer> lottoNumbers, int bonusNumber) {
this(new Lotto(lottoNumbers), new LottoNumber(bonusNumber);
}

이와 같은 부 생성자를 추가하면 WinningNumbers을 사용하는 개발자 입장에서 객체를 생성할 때 편리함을 얻을 수 있지 않을까?
객체를 생성하기 위한 여러 타입을 가지는 생성자를 추가하는 방식으로 접근해 보면 어떨까?

Rank rank = Rank.valueOf(6, false);

assertThat(rank).isEqualTo(Rank.FIRST);
assertThat(rank.getWinningAmount()).isEqualTo(2_000_000_000);
Copy link
Contributor

Choose a reason for hiding this comment

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

상금은 고정된 상수 값인데 굳이 테스트할 필요가 있을까?

Comment on lines 16 to 20
List<Lotto> lottos = tickets.getLottos();
for (Lotto lotto : lottos) {
int matchCount = lotto.countMatchNumber(winningNumbers);
Rank rank = Rank.valueOf(matchCount);
Rank rank = lotto.match(winningNumbers);
result.put(rank, result.get(rank) + 1);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

WinningResult가 WinningNumbers, LottoTickets와 의존관계를 현재와 같이 가지는 것이 어색해 보임
WinningResult는 로또 match 결과만 저장하는 것이지 WinningNumbers, LottoTickets의 의존 관계를 연결하는 역할까지 담당하는 이 객체의 역할로 적합해 보이지 않음

WinningResult result = winningNumbers.match(tickets);
또는
'WinningResult result = tickets.match(winningNumbers);`
와 같이 접근하는 것은 어떨까?

Comment on lines 20 to 23
List<Integer> numbers = lotto.getNumbers();
if (numbers.contains(bonusNumber.getValue())) {
throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}
Copy link
Author

Choose a reason for hiding this comment

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

Suggested change
List<Integer> numbers = lotto.getNumbers();
if (numbers.contains(bonusNumber.getValue())) {
throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}
List<Integer> numbers = lotto.getNumbers();
if (lotto.contains(bonusNumber)) {
throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}

lotto에서 getNumbers 사용을 지양하고 객체에 책임을 넘겨봤습니다.

Comment on lines 32 to 41
private int countMatch(Lotto userLotto) {
List<Integer> userNumbers = userLotto.getNumbers();
List<Integer> winningNumbers = lotto.getNumbers();
int count = 0;
for (Integer number : userNumbers) {
if (winningNumbers.contains(number)) {
count++;
}
}
return count;
Copy link
Author

Choose a reason for hiding this comment

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

Suggested change
private int countMatch(Lotto userLotto) {
List<Integer> userNumbers = userLotto.getNumbers();
List<Integer> winningNumbers = lotto.getNumbers();
int count = 0;
for (Integer number : userNumbers) {
if (winningNumbers.contains(number)) {
count++;
}
}
return count;
private int countMatch(Lotto userLotto) {
return lotto.countMatch(userLotto);
}

이부분도 마찬가지로 변경해봤습니다.

Copy link
Contributor

@javajigi javajigi left a comment

Choose a reason for hiding this comment

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

피드백 반영하느라 수고했어요. 👍
메서드 분리 후 인덴트가 2에서 더 이상 줄일 수 없으면 stream api 사용해 리팩터링해 보세요.
LottoNumber가 불변 객체인데요.
불변 객체로 구현할 때의 성능 이슈 관련해 상황 부여해 봤으니 해결 방법을 한번 찾아보면 좋겠습니다.

Comment on lines 43 to 48
private void validateDuplicate(List<LottoNumber> numbers) {
Set<LottoNumber> uniqueNumbers = new HashSet<>(numbers);
if (uniqueNumbers.size() != numbers.size()) {
throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다.");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

중복 체크를 위해 Set으로 변환하고 있다.
Lotto 필드인 List를 Set로 구현해 보면 어떨까?
Set으로 관리하다 외부에서 이 값을 접근할 때 순서를 보장해야 한다면 정렬해서 반환하는 것은 어떨까?

Comment on lines +76 to 84
public int countMatch(Lotto other) {
int count = 0;
for (LottoNumber number : numbers) {
for (LottoNumber number : this.numbers) {
if (other.contains(number)) {
count++;
}
}
return count;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

들여쓰기가 2이다.
stream api를 활용해 구현해 본다.
힌트: filter

return new ArrayList<>(lottos);
}

public Map<Rank, Integer> matchWith(WinningNumbers winningNumbers) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public Map<Rank, Integer> matchWith(WinningNumbers winningNumbers) {
public WinningResult matchWith(WinningNumbers winningNumbers) {

이미 구현해 놓은 WinningResult을 사용해 반환하면 어떨가?

Comment on lines +21 to 28
public static Rank valueOf(int matchCount, boolean matchBonus) {
for (Rank rank : values()) {
if (rank.matchCount == matchCount) {
if (rank.match(matchCount, matchBonus)) {
return rank;
}
}
return MISS;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

들여쓰기 2
stream api로 구현해 본다.

Comment on lines +16 to +17
assertThat(winningNumbers.getLotto().getNumbers()).contains(1, 2, 3, 4, 5, 6);
assertThat(winningNumbers.getBonusNumber().getValue()).isEqualTo(7);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
assertThat(winningNumbers.getLotto().getNumbers()).contains(1, 2, 3, 4, 5, 6);
assertThat(winningNumbers.getBonusNumber().getValue()).isEqualTo(7);
assertThat(winningNumbers).isEqualTo(new WinningNumbers(List.of(1, 2, 3, 4, 5, 6), 7));

값을 꺼내 비교하기보다 위와 같이 객체 단위로 비교해 보면 어떨까?

WinningNumbers winningNumbers = new WinningNumbers(winningLotto, bonusNumber);
PurchaseAmount purchaseAmount = new PurchaseAmount(2000);

WinningResult result = new WinningResult(tickets, winningNumbers);
Copy link
Contributor

Choose a reason for hiding this comment

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

상황

  • 1만명의 사용자가 동시에 당첨 여부를 확인할 수 있어야 한다.
  • 1명의 사용자는 평균 5장의 로또를 구매한 상태이다.

위 요구사항을 서버에서 생성되는 LottoNumber의 인스턴스의 갯수는?
1 * 5장 * 6개 숫자/1장 * 1만명 = 30만개이다.

동시에 생성되는 인스턴스 갯수가 너무 많다.
인스턴스 갯수를 줄일 수 있는 방법은?
로또 숫자 값을 LottoNumber 객체로 래핑하는 경우 매번 인스턴스가 생성되기 때문에 인스턴스의 갯수가 너무 많아져 성능이 떨어질 수 있다.
LottoNumber 인스턴스를 생성한 후 재사용할 수 있도록 구현한다.

힌트 : Map과 같은 곳에 인스턴스를 생성한 후 재사용하는 방법을 찾아본다.

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