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

oereo 2차과제 제출합니다! #7

Open
wants to merge 72 commits into
base: oereo
Choose a base branch
from

Conversation

oereo
Copy link
Member

@oereo oereo commented Apr 8, 2021

1차 과제를 진행하면서 리뷰도 많이 받았고 기존 구조와 naming 등이 이상하다고 판단이 되서 다시 새롭게 만들어보는게 좋겠다는 생각을 하였습니다!
과제 난이도가 점점 어려워지고 있다는 것이 확연히 느껴지는 것 같아요 ㅠㅠㅠㅠ
중간에 오류가 나서 오랫동안 해결이 안되서 애를 먹은 부분에 대해서는 따로 슬랙에 공유를 하도록 하겠습니다!

oereo added 30 commits April 1, 2021 16:17
oereo added 26 commits April 9, 2021 04:35
Copy link

@Jummi10 Jummi10 left a comment

Choose a reason for hiding this comment

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

수고하셨습니다!!! 사소한 것까지 모두 적다보니 리뷰가 너무 많아졌네요..

전체적으로 lotto 패키지에 있는 객체들은 모두 빈약하고, LottoStore와 LottoMachine이 모든 책임을 떠맡고 있네요.
LottoStore나 LottoMachine이 모든 것을 관리하게 해서 여러 기능들이 섞인 메서드나 클래스가 나오게 된 것 같아요. application이 돌아가는 순서대로 메서드나 클래스를 분리하지 않고, 객체와 그가 가진 로직별로 나누고 각 도메인의 상태를 이용하여 그들에게 책임을 부여하면 좋을 것 같습니다!

자율적인 객체 공동체 설계하기 이 또한 동민님이 1단계에서 공유해주신 자료인데 저는 도움이 많이 되어서 세훈님께도 공유드립니다 ㅎㅎ

Comment on lines 8 to 16
public PurchaseAmount(int purchaseAmount) {
PurchaseAmountValidation purchaseAmountValidation = new PurchaseAmountValidation();
this.purchaseAmount = purchaseAmount;
purchaseAmountValidation.checkPurchaseAmountNotPositive(this.purchaseAmount);
}

public int getPurchaseAmount() {
return purchaseAmount;
}
Copy link

Choose a reason for hiding this comment

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

저도 로또 1단계 구현했을 때 세훈님과 같이 PurchaseAmount와 PurchaseAmountValidation을 분리했었는데요, 게터를 제외한 행위가 아예 없는 빈약한 도메인 모델이라는 피드백을 받고 이에 대해서 생각해보았습니다. PurchaseAmount가 아무런 비즈니스 로직도 가지고 있지 않고 Validate 로직도 하나밖에 존재하지 않으니 PurchaseAmount를 더 풍부하게 만들어주면 어떨까요

첨부 동민님이 공유해주셨던 자료인데 다른 좋은 글들도 많으니 더 찾아보시면 좋을 것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

자료 감사합니다!!!
정미님의 말씀에 동의하는 바에요!! 근데 하나 질문이 있습니다!
validation class를 따로 두는 이유가 각각의 클래스에서 공통적으로 사용하는 Validation체크로직이 있을 때 사용될 수 있다고 생각을 했는데 만약에 이렇게 사용할 경우 좀 더 generic한 Validation class를 만드는게 좋을까요?? 아니면 각각의 도메인 모델 안에 넣는 것이 유지 보수 면이나 여러면을 봤을 때 더 효율적일까요?

Copy link
Member Author

@oereo oereo Apr 9, 2021

Choose a reason for hiding this comment

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

추가적으로 위에 정미님의 댓글에 답변을 못달아서 ㅠㅠ 여기에 답니다!!
먼저 시험기간일텐데 ㅠㅠ 리뷰 정말 감사드립니다!!
공유해주신 자료를 읽어보고 구조를 다시 한번 짜야겠다는 생각이 드네요 ㅠ 너무 현실세계에 맞춰서 Class를 만들다 보니 빈약한 도메인모델들이 나오게 되고 method들이 하나의 클래스안에 너무 많이 배치가 된 것 같아요!

Copy link

Choose a reason for hiding this comment

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

공통적으로 사용하는 Validation체크로직이 있을 때 사용될 수 있다고 생각을 했는데 만약에 이렇게 사용할 경우 좀 더 generic한 Validation class를 만드는게 좋을까요?? 아니면 각각의 도메인 모델 안에 넣는 것이 유지 보수 면이나 여러면을 봤을 때 더 효율적일까요?

음 저도 처음에는 일단 모든 검증 로직을 각자 사용되는 클래스 밖으로 빼내서 따로 구현을 했었는데요! 뭐가 더 낫다라고는 아직 공부를 하는 입장이기에 단언할 수는 없지만 저는 이 부분을 구현하며 어떻게 생각을 했냐하면

  1. PurchaseAmount에서 유효한 값이 주입되었는지 확인해야 한다
    • 상태가 0 이하임을 체크하는 로직은 구입금액(PurchaseAmount)에서밖에 쓰이지 않음
    • PurchaseAmount 내부에 자신의 상태를 이용하는 로직이 한 개도 없었다
    • 그리고 purchaseAmount를 검증하는 로직은 단 하나뿐이다
    • checkPurchaseAmountNotPositive()를 PurchaseAmount 안으로 넣어주자
  2. 수동 생성되는 번호 6개, 지난주 당첨 번호 6개, 보너스 번호 1개는 모두 1~45 사이의 유효한 값인지 확인해야 한다
    • 숫자의 범위를 검증해주는 공통 검증 로직을 만들어야겠다
    • validateRange()를 가진 클래스 만들고 여기저기서 필요할 때 가져다 써야겠다

라는 생각의 흐름으로 리팩토링을 진행했습니다

흠 이에 대해서 다른 분들의 의견도 궁금하긴 하네요! 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

흐음 class로 따로 빼둬야 하는지에 대한 이유가 다른 class에서 공통적으로 사용하는지에 대한 유무로 판단을 하신 거군요! 결국 그러면 validationRange() 라는 class도 좀 더 generic하게 그리고 다른 class에서 사용할 수 있도록 naming도 중요하겠군요!

Copy link

Choose a reason for hiding this comment

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

넵넵 저는 그런 식으로 생각을 했는데 음 꼭 이렇게 해야 한다까지의 의견은 아니어서 오늘 멘토분들이나 다른 분들께 한 번 질문해보는 것도 좋을 것 같아요

Comment on lines +32 to +40
private List<Integer> convertStringToInt(List<String> lottoManualTicket) {
return lottoManualTicket.stream()
.map(Integer::parseInt)
.collect(Collectors.toList());
}

private List<String> splitInputDelimiter(String lottoManualTicket) {
return Arrays.asList(lottoManualTicket.split(DELIMITER));
}
Copy link

Choose a reason for hiding this comment

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

input을 받아 application에서 사용할 수 있는 값으로 만들어주는 로직이 ui에 들어갔군요

Copy link
Member

Choose a reason for hiding this comment

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

문자열을 split하는 것은 비즈니스 로직이 아닐까요?

Comment on lines +13 to +17
LottoTicketOfNumberValidation lottoTicketOfNumberValidation = new LottoTicketOfNumberValidation();
this.totalNumberOfLottoTicket = calculateLottoTicketOfNumber(purchaseAmount.getPurchaseAmount());
this.numberOfManualLottoTicket = purchaseManualLottoOfNumber;
this.automaticLottoTicketOfNumber = calculateAutomaticLottoTicketOfNumber(totalNumberOfLottoTicket, numberOfManualLottoTicket);
lottoTicketOfNumberValidation.checkLottoTicketOfNumber(this.totalNumberOfLottoTicket);
Copy link

Choose a reason for hiding this comment

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

totalNumberOfLottoTicket을 계산하고 다른 값을 넣어주기 전에 바로 Validation.check()를 해주는게 검증의 의미가 있을 것 같아요

Copy link
Member Author

Choose a reason for hiding this comment

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

그 저번주에 코드진행으로 보여주신 것에서
this.purchasedAmount = purchaseAmount 다음에 validateNotPositive()를 넣으신 것을 예제로 보여주신 부분이 있는데 이것과 다른건가요?

Copy link

Choose a reason for hiding this comment

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

음 아뇨 값을 주입한 후에 validate를 진행하는 순서에 대해 얘기한 것이 아니라

this.automaticLottoTicketOfNumber = calculateAutomaticLottoTicketOfNumber(totalNumberOfLottoTicket, numberOfManualLottoTicket);  // line 16

에서 totalNumberOfLottoTicket이 사용되는데, 여기서 검증이 된 totalNumberOfLottoTicket가 사용되어야지 맞지 않나라는 말이었습니다!

this.totalNumberOfLottoTicket = totalNumberOfLottoTicket -> this.totalNumberOfLottoTicket 검증 -> automaticLottoTicketOfNumber 계산할 때 totalNumberOfLottoTicket 사용 이 순서요!

Comment on lines +28 to +30
public int getLottoTicketPrice() {
return LOTTO_TICKET_PRICE;
}
Copy link

Choose a reason for hiding this comment

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

static이면 getter가 아니라 public으로 열어두고 사용해도 될 것 같아요

import java.util.ArrayList;
import java.util.List;

public class LottoStore {
Copy link

Choose a reason for hiding this comment

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

어플리케이션의 전체적인 흐름을 담당하는 클래스군요

}

public void printAllMatchedLottoResults(Map<WinningStatus, Integer> lottoPrices) {
List<WinningStatus> keySet = new ArrayList(lottoPrices.keySet());
Copy link

Choose a reason for hiding this comment

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

Suggested change
List<WinningStatus> keySet = new ArrayList(lottoPrices.keySet());
List<WinningStatus> keySet = new ArrayList<>(lottoPrices.keySet());

Boolean isProfit = profit.isProfit();

//then
assertThat(true).isEqualTo(isProfit);
Copy link

Choose a reason for hiding this comment

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

assertThat(actual).isEqualTo(expected) 의 형식입니다
연산의 결과로 가져온 isProfit이 true이길 기대한다이므로 두 개의 위치가 바뀌어야겠네요

List<Integer> expected = lottoAutomaticTicket.getLotto();
System.out.println(expected);
//then
assertThat(expected).isEqualTo(actual);
Copy link

Choose a reason for hiding this comment

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

인자가 들어갈 위치에 커서를 두고 ctrl+p를 해보면 어떤 parameter가 들어가야할 자리인지 알려준답니다!

Comment on lines +25 to +36
@Test
void 로또_객체를_생성한다(){
//given
List<Integer> actual = new ArrayList<>(Arrays.asList(6,7,8,9,10,11));


//when
List<Integer> expected = lottoAutomaticTicket.getLotto();
System.out.println(expected);
//then
assertThat(expected).isEqualTo(actual);
}
Copy link

Choose a reason for hiding this comment

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

객체의 생성 자체를 테스트하는 것이 의미가 있을까요,,?

Comment on lines +54 to +55
assertThatExceptionOfType(NotValidLottoTicketOfNumberException.class).
isThrownBy(() -> new NumberOfLottoTicket(purchaseAmount, manualLottoTicketOfNumber));
Copy link

Choose a reason for hiding this comment

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

Suggested change
assertThatExceptionOfType(NotValidLottoTicketOfNumberException.class).
isThrownBy(() -> new NumberOfLottoTicket(purchaseAmount, manualLottoTicketOfNumber));
assertThatExceptionOfType(NotValidLottoTicketOfNumberException.class)
.isThrownBy(() -> new NumberOfLottoTicket(purchaseAmount, manualLottoTicketOfNumber));

…s 에서 체크할 수 있도록 구조 변경
Copy link
Member

@begaonnuri begaonnuri left a comment

Choose a reason for hiding this comment

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

구현 잘 해주셨네요 👍🏻
몇가지 변경사항 남겼으니 확인해주시고 궁금한 점이 있으면 DM주세요 :)

적절한 객체로 위임하는 것에 대해 초점을 맞춰서 변경해보시면 좋을것 같습니다😊

@@ -1,5 +1,21 @@
# 🚀 로또 1단계 - 자동

## 구현할 기능 목록
Copy link
Member

Choose a reason for hiding this comment

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

기능목록을 구현하면 - [x]로 채워주는게 더 좋을것 같아요!

Comment on lines +26 to +27
LottoApplication app = new LottoApplication();
app.run();
Copy link
Member

Choose a reason for hiding this comment

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

Application이 Application을 생성해서 실행시키는 것이 어색하게 느껴지네요.
run()은 애플리케이션의 흐름을 제어하면서 로직을 진행시키는 것 같은데 이것을 따로 객체로 분리해보면 어떨까요?
MVC 패턴과 Controller의 역할에 대해 학습해보면 좋을것같아요.

https://m.blog.naver.com/jhc9639/220967034588

Comment on lines +15 to +22
PurchaseAmount purchaseAmount = lottoStore.inputLottoPurchaseAmount();
int lottoManualTicketNumber = lottoStore.inputLottoManualTicketNumber();
LottoManualTickets lottoManualTickets = lottoStore.inputLottoManualTickets(lottoManualTicketNumber);
NumberOfLottoTicket numberOfLottoTicket = lottoStore.informNumberOfLottoTicket(purchaseAmount, lottoManualTicketNumber);
LottoAutomaticTickets lottoAutomaticTickets = lottoStore.informLottoAutomaticTickets(lottoManualTickets, numberOfLottoTicket);
LastWeekWinningLotto lastWeekWinningLotto = lottoStore.inputLastWeekWinningLotto();
LastWeekWinningBonusBall lastWeekWinningBonusBall = lottoStore.inputLastWeekWinningBonusBall();
lottoStore.informLottoStatistics(numberOfLottoTicket, lottoManualTickets, lottoAutomaticTickets, lastWeekWinningLotto, lastWeekWinningBonusBall);
Copy link
Member

Choose a reason for hiding this comment

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

다양한 객체에게 역할을 위임하고 값을 리턴 받으면서 로직이 진행되어야 하는데,
지금처럼 LottoStore에게서만 위임한다면 굳이 LottoApplication으로 값을 리턴받을 필요가 있을까요?

그리고 한 가지 객체(LottoStore)에게만 값을 요청한다는 것은 그 객체가 갖고 있는 책임이 많다는 것을 의미하기도 해요.
역할에 맞는 다양한 객체로 분리해보는건 어떨까요?

}

public LottoManualTickets inputLottoManualTickets(int lottoManualTicketNumber) {
ArrayList<LottoManualTicket> lottoManualTicketDummy = new ArrayList<>();
Copy link
Member

Choose a reason for hiding this comment

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

private final static int LOTTO_SIZE = 6;
private final static int WINNING_LOTTO_WITH_BONUS_BALL_MATCHED_COUNT = 5;

private final RandomLottoNumberGenerator randomLottoNumberGenerator = new RandomLottoNumberGenerator();
Copy link
Member

Choose a reason for hiding this comment

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

LottoMachine에 대한 테스트가 없어서 체감을 못하셨을텐데, 랜덤으로 인해 generateLottoAutomaticTicket()를 테스트하기 어려울거에요.
RandomLottoNumberGenerator를 인터페이스로 추출하고 LottoMachine을 생성하는 부분에서 RandomLottoNumberGenerator를 넣어주는 방식으로 변경해보세요!

Comment on lines +67 to +69
private int calculateMatchedLottoAutomaticTicket(
LottoAutomaticTicket lottoAutomaticTicket,
LastWeekWinningLotto lastWeekWinningLotto) {
Copy link
Member

Choose a reason for hiding this comment

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

맞아요. 동일한 기능을 하는 객체를 추출하는 방식으로 변경해보는건 어떨까요?

Comment on lines +75 to +76
Boolean isMatchLottoNumber = lottoTicket.contains(lastWeekWinningLottoTicket.get(lottoNumber));
matchedCount += countMatchedLottoNumber(isMatchLottoNumber);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Boolean isMatchLottoNumber = lottoTicket.contains(lastWeekWinningLottoTicket.get(lottoNumber));
matchedCount += countMatchedLottoNumber(isMatchLottoNumber);
if(lottoTicket.contains(lastWeekWinningLottoTicket.get(lottoNumber)) {
matchCount++;
}
continue;

이렇게 하고 lottoTicket.contains(lastWeekWinningLottoTicket.get(lottoNumber)를 적절한 이름으로 추출하는 것이 더 나아보여요

Comment on lines +98 to +101
for (int lottoNumber = 0; lottoNumber < LOTTO_SIZE; lottoNumber++) {
Boolean isMatchLottoNumber = lottoTicket.contains(lastWeekWinningLottoTicket.get(lottoNumber));
matchedCount += countMatchedLottoNumber(isMatchLottoNumber);
}
Copy link
Member

Choose a reason for hiding this comment

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

위와 마찬가지로 변경해보세요 :)

LottoManualTicket lottoManualTicket,
LastWeekWinningBonusBall lastWeekWinningBonusBall) {

List<Integer> lottoTicket = lottoManualTicket.getLotto();
Copy link
Member

Choose a reason for hiding this comment

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

이곳 뿐만 아니라 대부분의 코드에서,
로또 티켓이 List 타입이라서 관련된 행위들이 전부다 get()해서 사용되어지고 있어요.
그리고 그러다보니 제약사항이나 관련된 기능도 모두 선언하는 곳이 아니라 사용하는 곳에서 이루어지고 있어요.

원시값을 포장하고, 관련된 로직을 해당 객체 안에서 처리하는 방식으로 변경하면 좋을 것 같아요 :)

}

private void compareWinningStatus(WinningStatus winningStatus, int lottoMatchedCount, Boolean hasBonusBall) {
if ((winningStatus.getMatchCount() == lottoMatchedCount) && (lottoMatchedCount != WINNING_LOTTO_WITH_BONUS_BALL_MATCHED_COUNT)) {
Copy link
Member

Choose a reason for hiding this comment

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

if문이 길어질땐 if문의 조건 식을 메소드로 추출해서 직관적으로 표현하는 것이 좋습니다 :)

LottoAutomaticTickets lottoAutomaticTickets = lottoStore.informLottoAutomaticTickets(lottoManualTickets, numberOfLottoTicket);
LastWeekWinningLotto lastWeekWinningLotto = lottoStore.inputLastWeekWinningLotto();
LastWeekWinningBonusBall lastWeekWinningBonusBall = lottoStore.inputLastWeekWinningBonusBall();
lottoStore.informLottoStatistics(numberOfLottoTicket, lottoManualTickets, lottoAutomaticTickets, lastWeekWinningLotto, lastWeekWinningBonusBall);
Copy link
Member Author

Choose a reason for hiding this comment

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

수정하기 -> method에 4개이상의 인자 받지 않기 -> 객체로 빼기

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.

None yet

3 participants