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

[2단계 - 블랙잭(베팅)] 허브(방대의) 미션 제출합니다. #537

Merged
merged 56 commits into from
Mar 13, 2023

Conversation

greeng00se
Copy link
Member

안녕하세요 검프🍫! 잘 지내셨나요?

검프와 같이 협업 한다고 생각하고, 납득이 가는 방향이라면 남겨주신 코멘트를 최대한 반영하려고 노력했습니다.

변경점은 다음과 같습니다!!

  • 베팅 기능 추가
  • 메시지 메서드 구현에 그대로 노출되도록 수정
  • enum의 상태를 확인하는 메서드 제거
  • Controller가 BlackjackGame을 필드로 가지고 있도록 수정
  • 카드 리스트에 대한 Fixture 추가
    • CardsTest의 경우에는 Input을 자세하게 다루고 싶어서 사용하지 않았습니다.

부족하거나, 더 좋은 방법이 있다면 코멘트 남겨주시면 바로 반영하겠습니다!


private static void validateBetUnit(final int value) {
if (isInvalidUnit(value)) {
throw new IllegalArgumentException("베팅은 100 단위로 할 수 있습니다.");
Copy link
Member Author

Choose a reason for hiding this comment

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

"베팅은 " + BETTING_UNIT + " 단위로 할 수 있습니다."

상수 사용 하겠습니다! 😢

Copy link

@Livenow14 Livenow14 left a comment

Choose a reason for hiding this comment

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

안녕하세요 허브 :) 구현 잘해주셨어요.
전반적인 수정이 필요한 피드백을 남겼는데요. 학습하는 의미로 한번 적용해보셨으면 좋겠어요 :)

진행하시다가 어렵거나 질문사항이 있으면, 언제든 DM 및 커멘트 달아주세요.
그럼 수고 많으셨습니다.

README.md Outdated
Comment on lines 25 to 33
Hand -- 사용자의 패 --> Cards
Hand -- 핸드의 상태 --> State
end

Players --> 플레이어 --> Hand

subgraph 플레이어
direction BT
Gambler -.-> Player

Choose a reason for hiding this comment

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

인텔리제이에서 사용하는 의존성 다이어 그램을 활용해도 좋겠네요 :)

Copy link
Member Author

@greeng00se greeng00se Mar 12, 2023

Choose a reason for hiding this comment

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

유료버전에서 제공하는 다이어그램도 좋은 것 같습니다!
IntelliJ에서 markdown mermaid 설정 체크만 해준다면 IntelliJ와 깃허브에서도 둘다 확인이 가능하기 때문에 mermaid도 괜찮다고 생각됩니다 👍


public class BlackjackApplication {
public final class BlackjackApplication {

Choose a reason for hiding this comment

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

정적 메서드만 가지는 객체에서, final을 사용하는 이유가 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

네오의 수업을 보고 적용해보았는데요~ 상속을 할 수 없다고 명시적으로 작성하는 것이 좋다고 생각되었습니다!

Choose a reason for hiding this comment

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

클래스 final의 경우, 상속으로 인한 사이드 이펙트가 생길 수 있는 클래스 혹은, 추가 확장이 없음이 확실할 때 걸어주시는 게 좋다고 생각하는데요.

intellij 에서 class 생성시 기본적으로 public class로 생성되기 때문에, 협업시 혼란의 여지가 있을 수 있습니다.
(final이 안걸려있으면, 확장을 막 해도 되는건가? final의 기준이 뭘까?)

모든 클래스에 걸어주는것 vs 필요시에 걸어주는 것의 부분에서 고민해보시길 바래요 :)

이부분은 허브 말씀처럼, 선택에 맡길게요~

Comment on lines 24 to 26
private boolean isSameCommand(final String command) {
return this.command.equalsIgnoreCase(command);
}

Choose a reason for hiding this comment

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

대/소문자를 구분하지 않는 구현의 추상화가 잘되어있네요 :)

@@ -1,9 +1,8 @@
package blackjack.controller;

public class Retry {
public final class Retry {

private static final int DEFAULT_COUNT = 5;

Choose a reason for hiding this comment

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

DEFAULT_COUNT를 사용하지 않고,
applicatoin 구동시 넣어줘도 좋을 것 같네요.

Copy link
Member Author

Choose a reason for hiding this comment

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

더 깔끔한 것 같습니다! 👍 👍 👍

Comment on lines 18 to 24
private InputView() {
this.scanner = new Scanner(System.in);
}

public static InputView getInstance() {
return INSTANCE;
}

Choose a reason for hiding this comment

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

[input | ouput]View가 싱글턴 패턴으로 구현되어야 하는 이유가 있을까요?!

싱글턴 패턴을 사용하신 이유를 알 수 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

static으로 사용할까, 싱글턴으로 사용할까 고민하다가 싱글턴이 더 괜찮을 것 같다는 생각이 들었습니다.
지금보니 특별히 싱글턴을 도입할 이유가 없는 것 같네요! 생성자를 사용하는 것이 명확할 것 같습니다.

Comment on lines 31 to 39
public Result play(final Hand other) {
if (state.isBlackjack()) {
if (state == BLACKJACK) {
return playWithBlackjack(other);
}
if (state.isBust()) {
if (state == BUST) {
return Result.LOSE;
}
return playWithScore(other);
}

Choose a reason for hiding this comment

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

if 문도, 줄일 수 있을 것 같구요 :)

Comment on lines 26 to 28
public boolean isAce() {
return this == ACE;
}

Choose a reason for hiding this comment

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

Ace를 가져와서, Cards에서 10을 더해주고 있네요, enum에서

예외로 Ace는 1 또는 11로 계산할 수 있으며,

의 요구사항을 표현할 수 없을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

음.. 카드의 상태에 따라 10을 더해 11로 계산할 수 있다고 생각해서 Cards에 있는게 더 좋은 것 같다는 생각이 듭니다!

Comment on lines 11 to 13
public Bets() {
bets = new LinkedHashMap<>();
}

Choose a reason for hiding this comment

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

LinkedHashMap 이 필요한 이유가 있을까요?

bets의 순서에 따라 로직이 달라지나요?!

단순히 UI에 순서대로 보여주기 위함이라면, UI를 위한 로직이 Bets에 포함된건 아닐까요?
Dealer와 Gambler를 통해, 출력시, 딜러 -> 플레이어 등으로 표현해볼 수 잇을 것 같아서요.

Copy link
Member Author

Choose a reason for hiding this comment

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

UI 로직이 반영된 부분 맞습니다!
수정하겠습니다 👍

public final class Name {
private static final int LENGTH_LOWER_BOUND = 1;
private static final int LENGTH_UPPER_BOUND = 5;
private static final String RESERVED_DEALER_NAME = "딜러";

Choose a reason for hiding this comment

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

RESERVED_DEALER_NAME -> DEALER

Copy link
Member Author

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 33
Arguments.of(CardFixtures.BLACKJACK, BLACKJACK),
Arguments.of(CardFixtures.BUST, BUST),

Choose a reason for hiding this comment

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

CardsFixutre를 만들어도 좋겠네요 :)

클래스이름은 단수로 설정해주세요 :)

Copy link
Member Author

@greeng00se greeng00se Mar 12, 2023

Choose a reason for hiding this comment

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

이 부분을 놓쳤네요 😢 명확하게 분리하는 것 너무 좋은 것 같습니다 👍

@greeng00se
Copy link
Member Author

검프🍫 안녕하세요!

객체의 다형성을 이용해보았습니다!
cards에 blackjack이나 bust를 확인하는 메서드를 제거하고 싶었는데 state에서 전부 오버라이딩하거나, 제거를 하게된다면 중복된 부분이 많아져서 제거하지 못했습니다. 😢

더 좋은 방법이 있을까요?!

Copy link

@Livenow14 Livenow14 left a comment

Choose a reason for hiding this comment

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

안녕하세요 허브 :) 다형성을 사용해서 객체의 상태들을 잘 관리해 주셨어요.

몇가지 피드백 남겼습니다. 이것까지 반영해보면 좋을 것 같아서, 한번더 변경 요청보낼께요 :)

질문의 대한 답입니다.

cards에 blackjack이나 bust를 확인하는 메서드를 제거하고 싶었는데 state에서 전부 오버라이딩하거나, 제거를 하게된다면 중복된 부분이 많아져서 제거하지 못했습니다. 😢

저는 지금 작성해주신대로 구현되도 나쁘지 않다고 생각이 들어요.
각 상태별로 WIN, LOSE 판별 규칙이 다르니까요.
또한 Cards에서 blackJack이냐 Bust인지 판단하는 책임은 Cards가 가져도 충분히 좋을 책임이라 생각해서요 :)

수고많으셨어요.

README.md Outdated
Comment on lines 37 to 51
```mermaid
---
title: Hand 상태
---
flowchart BT
AbstractHand -.-> Hand
Running --> AbstractHand
Finished --> AbstractHand
Hit --> Running
Blackjack --> Finished
Bust --> Finished
Stay --> Finished

```

Choose a reason for hiding this comment

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

uml 다이어 그램 형식을 따르게 작성해도 좋겟네요 :)

image

Copy link
Member Author

Choose a reason for hiding this comment

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

간단하게 작성해보겠습니다!

@@ -18,14 +16,12 @@ public static BlackjackCommand from(final String command) {
return Arrays.stream(values())
.filter(value -> value.isSameCommand(command))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(INVALID_COMMAND_MESSAGE + command));
.orElseThrow(() -> new IllegalArgumentException(
HIT.command + " 또는 " + STAY.command + "을 입력해야 합니다. 입력값:" + command)

Choose a reason for hiding this comment

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

    var collect = Arrays.stream(values()).map(it -> it.command).collect(Collectors.joining(" 또는 "));

확장성을 고려한다면 위와 같이 상수 반복을 추가해도 괜찮겠네요 :)

Copy link
Member Author

Choose a reason for hiding this comment

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

오.. 좋은 것 같습니다! 👍 메시지까지 꼼꼼하게 확장을 고려할 수 있겠군요!!

Comment on lines 13 to 14
private static final InputView inputView = new InputView();
private static final OutputView outputView = new OutputView();

Choose a reason for hiding this comment

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

생성자 인자로 받아와도 좋을 것 같네요 :)
IntputView Outputview가 도메인과 밀접한 것 같아서요.

Copy link
Member Author

Choose a reason for hiding this comment

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

좋습니다! 하지만 필드가 많아지는 것 같아 Retry를 run의 인자로 받아와 3개로 줄여보겠습니다!

retry = retry.decrease();
}
}
throw new IllegalArgumentException(this.retry.getFailMessage());

Choose a reason for hiding this comment

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

Retry내에 상수로 관리되도 좋겠네요 :)

Copy link
Member Author

@greeng00se greeng00se Mar 13, 2023

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 34
private void addPlayers() {
Retry retry = this.retry;
while (retry.isRepeatable()) {

Choose a reason for hiding this comment

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

Retry retry 를 파라미터로 받으면,

        Retry retry = this.retry;

를 줄일 수 있겠네요

Copy link
Member Author

Choose a reason for hiding this comment

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

왜 이런 생각을 못했을까요 😢

Comment on lines 47 to 49
for (final Name player : blackjackGame.getGamblerNames()) {
addBet(player);
}

Choose a reason for hiding this comment

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

    blackjackGame.getGamblerNames()
        .forEach(this::addBet);

로도 사용할 수 있을 것 같은데,
과연 메서드로 분리해야 했을까요?!

Copy link
Member Author

Choose a reason for hiding this comment

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

addPlayers(retry);
addBets(retry);
initialDraw();
draw(retry);
play();

run 메서드가 위와 같이 추상화 되어있기 때문에 분리하는 것도 괜찮을 것 같다는 생각이 듭니다!

public abstract class AbstractHand implements Hand {
private final Cards cards;

AbstractHand(final Cards cards) {

Choose a reason for hiding this comment

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

해당 생성자는 default 접근 지정자를 가지는 걸까요?

Choose a reason for hiding this comment

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

추상클래스와 구현클래스의 생성자의 접근지정자는 어떻게 가져가는게 좋을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

protected가 더 적절할 것 같습니다. 다른 패키지에서도 상속받아서 사용할 수도 있으니까요!

import java.util.Map;

public final class Bets {
private final Map<Name, Money> bets;

Choose a reason for hiding this comment

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

map의 key를Name이 아닌, Player등으로 설정해도 어색하지 않겠네요 :)

Copy link
Member Author

Choose a reason for hiding this comment

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

반영했습니다! 테스트가 조금 더 복잡해져서 Name으로 변경을 했었는데, 의미상으로는 Player가 Key로 있는데 더 깔끔한 것 같습니다!

Comment on lines 28 to 29
draw();
play();

Choose a reason for hiding this comment

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

draw하는 과정은 play 과정이 아닐까요?!

Copy link
Member Author

Choose a reason for hiding this comment

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

play에 draw를 넣었을 때 initialDraw도 넣어야할 것 같고, 둘 다 넣으면 베팅하는 부분도 넣어야할 것 같은 느낌이 들어서 지금도 괜찮을 것 같다는 생각이 드는데 검프는 어떻게 생각하시나요? 🤔

Choose a reason for hiding this comment

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

베팅, initialDraw 등을 initialize 로 빼고, play에 draw를 포함하는 방법도 있겠네요 :)

Copy link
Member Author

Choose a reason for hiding this comment

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

오 좋은 방법인 것 같습니다! 2개로 나눠지겠군요 😄

@greeng00se
Copy link
Member Author

피드백 남겨주신 부분 반영해보았습니다!
검프랑 페어프로그래밍 하는 느낌이어서 너무 재밌었습니다! 😄
더 반영하거나 추가해야될 부분이 있을까요?

항상 꼼꼼하게 코멘트 달아주셔서 감사합니다 검프!

Copy link

@Livenow14 Livenow14 left a comment

Choose a reason for hiding this comment

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

안녕하세요 허브 ! 구현 잘해주셨어요 :)
꽤나 복잡한 리뷰였을 텐데도, 잘 적용해주셨어요...!

여기까지 진행해도 다음레벨을 진행하는데 무리 없을거라 판단하여, 이번 미션은 여기서 머지하겠습니다
수고많으셨어요 :)

간단한 리뷰를 남긴다면

다음 레벨의 미션에는 학습의 취지로 테스트에 given when then 패턴을 한번 적용해보시면 좋을 것 같아요 :)
테스트의 "일관성"을 지킬 수 있거든요.
물론 테스트가 요구사항을 잘 검증하는지가 1순위이긴 하지만요.

Comment on lines -42 to +63
throw new IllegalArgumentException(retry.getFailMessage());
throw retry.getException();

Choose a reason for hiding this comment

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

헉 retry.getFailMessage()를 상수화 하자는 의미였어요..!
명확한 피드백을 못드렸었네요 ㅠㅠㅠ 요건 다음 레벨에서 적용해주세요 :)

Copy link
Member Author

Choose a reason for hiding this comment

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

메시지 자체를 상수화하는 거였군요! 😄

블랙잭 미션을 진행하면서 다양한 방법을 제시해주셔서 재밌었습니다!
항상 꼼꼼하게 리뷰 남겨주셔서 감사합니다!

@Livenow14 Livenow14 merged commit 2949f85 into woowacourse:greeng00se Mar 13, 2023
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