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단계 - 블랙잭 게임 실행] 허브(방대의) 미션 제출합니다. #427

Merged
merged 71 commits into from
Mar 7, 2023

Conversation

greeng00se
Copy link
Member

안녕하세요 검프!
전 허브🌿라고 합니다. 잘부탁드립니다!

블랙잭 미션동안 잘 부탁드립니다!

부족한 부분이나 개선을 하면 좋겠다고 생각하시는 부분에 대해 리뷰 남겨주시면 적극적으로 반영하겠습니다! 감사합니다 😄

greeng00se and others added 30 commits February 28, 2023 16:37
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
- 점수 계산 기능 포함

Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
Co-authored-by: Combi153 <ssrno009@gmail.com>
- drawByDealer -> drawToDealer
@greeng00se
Copy link
Member Author

greeng00se commented Mar 6, 2023

안녕하세요 검프! 😄

꼼꼼하게 코멘트 남겨주셔서 감사합니다.
각각의 코멘트들에 대해 하나하나씩 깊게 생각할 수 있어서 정말 좋았습니다!

아래에는 반영한 내용들과 약간의 질문들이 포함되어있습니다!

감사합니다! 👍

리팩터링 및 리뷰 반영한 내용

  1. 다이어그램
    다음과 같이 핵심 클래스만 남겨보았습니다~~ 👍
flowchart TD
    BlackjackController --> BlackjackGame
    BlackjackController --> InputView
    BlackjackController --> OutputView

    BlackjackGame --> BlackjackGameResult
    BlackjackGame --> Players

    subgraph 카드
        ShuffledDeck --> Card
        Card --> Rank
        Card --> Shape
        Cards --> Card
        Hand -- 사용자의 패 --> Cards
        Hand -- 핸드의 상태 --> State
    end

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

    subgraph 플레이어
        direction BT
        Gambler -.-> Player
        Dealer -.-> Player
    end
Loading
  1. 패키지 분리
    도메인의 클래스가 너무 많아서 패키지를 분리했습니다. 분리한 패키지는 다음과 같습니다.
  • card: 카드와 관련된 클래스
  • player: 사용자와 관련된 클래스
  • game: BlackjackGame과 관련된 클래스
    패키지의 의존 방향은 다음과 같이 단방향으로 의존하도록 설정하였습니다.
flowchart LR
    player --> card
    game --> player
    game --> card
Loading
  1. BlackjackGame 적절한 책임 추가
    짧은 미션 시간을 고려해서 플레이어에게 카드를 나눠주는 것, 상태를 변경하는 메서드를 Controller에서 호출하도록 설정했었고, 이에 따라 BlackjackGame의 책임이 적어지는 느낌을 받았습니다.
    플레이어에게 카드를 나눠주고, 플레이어의 상태를 변경하도록 하는 메서드를 추가하였습니다. 😄

여기서 BlackjackGame, Players 클래스에서 drawTo, stay 메서드가 단순 위임 형태로 구현되었습니다.
검프가 남겨주신 코멘트의 의도대로 BlackjackGame에게 적절한 책임을 부여한 것이 맞을까요?

추가로 Player를 가져오는 부분도 List를 반환하도록 설정했습니다!!

  1. Cards 생성자 추가
    Hand, Cards에 카드 리스트를 전달할 수 있는 생성자를 추가했습니다.
    기존에 빈 생성자를 제거해봤지만, Player가 카드를 초기에 2장을 가지고 있는 형태가 되어 매개변수를 받지 않는 생성자를 추가했습니다.
    이 부분에 대해서 검프가 남겨주신 코멘트의 의도대로 변경한 것이 맞을까요? 🤔

  2. TestFixture 추가
    테스트 픽스처를 사용하면 픽스처에 의존하는 형태가 되지만, 현재 테스트 코드가 너무 복잡하고 읽기 어려워서
    Card에 대한 부분을 추가했습니다.
    이렇게 Fixture를 사용하는 것에 대해 검프는 어떻게 생각하시나요? 🤔

  3. AbstractPlayer 추상 클래스 추가
    Gambler와 Dealer가 가지고 있는 중복되는 부분을 제거했습니다!

Copy link

@shin-mallang shin-mallang left a comment

Choose a reason for hiding this comment

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

고수..👍👍

}

private Result playWithScore(final Hand other) {
if (other.state.isBust() || cards.calculateTotalScore() > other.cards.calculateTotalScore()) {

Choose a reason for hiding this comment

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

other.state.isBust() 대신 other.isBust()가 가능하게끔 구현하는 것은 어떤가요?
뒤에 있는 other.cards.cal~~ 도 마찬가지로요

Copy link
Member Author

Choose a reason for hiding this comment

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

안녕하세요 말랑🍡 늦은 시간에 리뷰 남겨주셔서 감사합니다.
방금 간단하게 반영을 해보았습니다~

현재 Hand가 State 자체를 필드로 가지고 있는데, 단순히 이 메서드만을 위해 private 메서드를 추가로 3개나 만들어야 해서 해당 메서드의 가독성은 올라가지만 전체적으로 가독성이 떨어지는 느낌을 받았습니다.

말씀해주신 other.cards.cal~~ 부분은 아래에 존재하는 calculateScore() 메서드를 사용한다면 가독성을 개선할 수 있을 것 같아요! 감사합니다 👍

package blackjack.domain.card;

public enum Rank {
ACE("A", 1),

Choose a reason for hiding this comment

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

Rank는 도메인인데, 뷰와 관련된 책임을 담당하는 것 아닌가요?
ACE를 A로 바꾸어 출력해달라는 요구사항이 생기면 뷰가 아닌 도메인의 코드가 바뀌는 것이 바람직한가요?

Copy link
Member Author

Choose a reason for hiding this comment

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

전 상황에 따라 작성을 하는 편입니다.
새로운 도전을 하지 않는 이상 복잡한 방법보다 최대한 간단한 방법으로 해결하는 것을 선호하는 편입니다.
이번 미션에서는 빠르게 구현하고, 다른 부분에 집중하는 것이 더 좋다고 생각이 들었어요!

뷰에 요구사항에 따라 도메인 코드가 변경되지 않는 것이 왜 바람직할까요?
크게 본다면 변경사항에 대한 파급력을 줄이기 위해 관심사를 분리하는 것이고, 이는 결국 유지보수를 용이하게 하기 위함이라고 생각됩니다.
현재 미션에서는 단순하고 명료함을 포기할만큼, 해당 부분에서 큰 이득이 있다고 느껴지지 않았습니다.

package blackjack.domain.card;

public enum Shape {
DIAMOND("다이아몬드"),

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.

위와 동일합니다 👍

@Override
public Card draw() {
final Card card = cards.removeFirst();
cards.addLast(card);

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.

Deck에서 카드가 한 장씩 사라지는게 개인적으로 마음에 안들었어요!
필요한 경우 shuffle 메서드를 추가한다면 재사용할 수도 있지 않을까 상상만 해보았습니다~ 🤔

Choose a reason for hiding this comment

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

Deck에, 무덤패 등의 속성이 추가되도 좋겠네요 :0

그럴일은 없겠지만, 51개가 다 돌면 골치아플수도 있을 것 같아서요.

이후 shuffle을 할 때, 무덤패 + 사용패 후 shuffle을 해도 좋을 것 같구요 :)

다만, 이부분은 세밀한 부분이라, 편하게 진행해주세요!

Copy link
Member Author

Choose a reason for hiding this comment

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

무덤패 속성을 추가하는 것도 재밌을 것 같군요 👍

import blackjack.domain.card.Hand;
import java.util.List;

public abstract class AbstractPlayer implements Player {

Choose a reason for hiding this comment

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

추상 골격 클래스로 구현해 주신 것 같은데, 그냥 Player를 추상 클래스로 정의하는 것이 더 간편하지않나요?
굳이 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.

일단 처음에 인터페이스로 구현을 했기 때문에 해당 부분을 지우지 않고 새로운 도전을 해보고 싶었습니다. 추상 골격 클래스가 뭔지는 알지만, 정말 사용하면 장점이 있을까? 라고 생각을 하면서 적용을 해보았는데요. 😄

복잡한 인터페이스일 때 사용하면 좋다고 알고 있는데, 그렇게 복잡한 인터페이스가 아니었기 때문에 장점이라고 할 만한 부분을 느끼지 못했습니다.

이 부분은 말랑이 코멘트 남겨주신대로 추상 클래스 하나만 사용하는 것이 간결한 선택이라고 생각이 듭니다 👍

리뷰 남겨줘서 감사합니다~ 다른 이야기도 더 많이 나눠보면 좋을 것 같아요!

Choose a reason for hiding this comment

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

학습을 위한 도전이라면 좋은 도전이라 생각합니다 :)

저는 추상클래스를 주로, public method의 구현을 도와주는 protected abstract method가 필요할 때, 사용하곤 합니다.

Copy link
Member Author

Choose a reason for hiding this comment

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

검프만의 기준을 알려주셔서 감사합니다 🙇

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 생성자 추가

Player가 카드를 초기에 2장을 가지고 있는 형태가 되어
블랙잭 게임을 진행하며, Hand에 카드가 2장이 있다고 하셧는데요!,

    public Hand(final List<Card> cards) {
        this.cards = new Cards(cards);
        this.state = State.calculateState(this.cards);
    }

의 경우, 기본생성자에서만 사용되고 있어요, 0장을 가진다면, 사용하는곳에서 인자로, new ArrayList<>()를 넣어주는게 어떨까 ? 라는 의문에서 시작된 피드백이였는데요.
즉, 사용하는곳에서 빈 List를 만들어 생성자의 인자로 넣어줘도 문제 없어보입니다 :)

TestFixture 추가

Fixture를 사용하게되면, 준비,실행,검증 구절 중 준비 구절의 양을 줄일 수 있다고 생각하여, 저도 많이 즐겨쓰는 방법이에요 :)
다만, 사용하신 픽스쳐를 가져와, List를 만드는 걸 많이 확인할 수 있었는데요.
이경우, List를 제공하는 픽스처를 만들어 보는건 어떨까요?!

수고많으셨습니다 :)

Comment on lines +28 to +30
public boolean isHit() {
return this == HIT;
}

Choose a reason for hiding this comment

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

넵, enum 자체가 HIT을 표현하고 있기에, 그대로 비교를 해도 문제없어 보입니다 :)

Comment on lines +22 to +26
final BlackjackGame blackjackGame = initializeGame();
initialDraw(blackjackGame);
draw(blackjackGame);
play(blackjackGame);
}

Choose a reason for hiding this comment

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

오우 추상화 단계가 깔끔하네요 💯

}

private BlackjackGame initializeGame() {
final Players players = createPlayers(new Retry());

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

Choose a reason for hiding this comment

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

2단계때는 Controller가 Retry와 BlackjackGame을 가지고 있는 방향으로 수정 해봐야겠군요! 👍


private void draw(final BlackjackGame blackjackGame) {
for (final Player player : blackjackGame.getPlayers()) {
drawOnce(blackjackGame, player);

Choose a reason for hiding this comment

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

몇번을 메서드에 표현하는것 보단,

Suggested change
drawOnce(blackjackGame, player);
draw(blackjackGame, 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.

명확해서 더 좋은 것 같습니다! 👍

Comment on lines +16 to +18
public Cards() {
this(new ArrayList<>());
}

Choose a reason for hiding this comment

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

테스트에서만 사용되는 생성자는 삭제부탁드립니다.

Comment on lines +20 to +22
public Cards(final List<Card> cards) {
this.cards = new ArrayList<>(cards);
}

Choose a reason for hiding this comment

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

방어적 복사 💯

Comment on lines +25 to +39
public boolean isBlackjack() {
return this == BLACKJACK;
}

public boolean isPlayable() {
return this == PLAY;
}

public boolean isBust() {
return this == BUST;
}

public boolean isNotBust() {
return this != BUST;
}

Choose a reason for hiding this comment

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

자료구조인 enum에 메서드를 추가할 이유는 없어보여요!!

Comment on lines +15 to +29
private static final String NEW_LINE = System.lineSeparator();
private static final int INITIAL_DRAW_COUNT = 2;
private static final int DEALER_OPEN_CARD_INDEX = 0;
private static final String INITIAL_DRAW_MESSAGE = "에게 " + INITIAL_DRAW_COUNT + "장을 나누었습니다.";
private static final String DELIMITER = ", ";
private static final String PLAYER_CARD_MESSAGE_FORMAT = "%s 카드: %s";
private static final String PLAYER_SCORE_MESSAGE_FORMAT = " - 결과: %d";
private static final String ERROR_MESSAGE = "[ERROR] ";
private static final String DEALER_DRAW_MESSAGE = NEW_LINE + "딜러는 16이하라 한 장의 카드를 더 받았습니다." + NEW_LINE;
private static final String GAME_RESULT_MESSAGE = NEW_LINE + "##최종 승패";
private static final String GAME_RESULT_PLAYER_MESSAGE_FORMAT = "%s: %s";
private static final String WIN_MESSAGE = "승";
private static final String PUSH_MESSAGE = "무";
private static final String LOSE_MESSAGE = "패";
private static final String GAME_RESULT_DEALER_MESSAGE_FORMAT =

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 7, 2023

Choose a reason for hiding this comment

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

package-private 상수 사용한 부분 제거와 함께 반영해보도록 하겠습니다~ 👍

Comment on lines +12 to +31
void mission2() {
final String[] arrays = {"first", "second"};
final SimpleList<String> result = SimpleList.<String>fromArrayToList(arrays);

assertAll(
() -> assertThat(result.contains("first")).isTrue(),
() -> assertThat(result.contains("second")).isTrue()
);
}

@Test
void mission3() {
final SimpleList<Double> doubleValues = new SimpleArrayList<Double>(0.5, 0.7);
final SimpleList<Integer> intValues = new SimpleArrayList<Integer>(1, 2);

assertAll(
() -> assertThat(sum(doubleValues)).isEqualTo(1.2),
() -> assertThat(sum(intValues)).isEqualTo(3)
);
}

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 7, 2023

Choose a reason for hiding this comment

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

미니 미션이라고 크게 생각을 하지 않고 메서드명을 작성했네요!
이 부분도 의미있는 테스트명으로 수정해보겠습니다 👍

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.

3 participants