Skip to content

[🚀 사이클1 - 미션 (블랙잭 게임 실행)] 서여 미션 제출합니다.#1005

Open
yeo-li wants to merge 76 commits intowoowacourse:yeo-lifrom
yeo-li:step1
Open

[🚀 사이클1 - 미션 (블랙잭 게임 실행)] 서여 미션 제출합니다.#1005
yeo-li wants to merge 76 commits intowoowacourse:yeo-lifrom
yeo-li:step1

Conversation

@yeo-li
Copy link

@yeo-li yeo-li commented Mar 7, 2026

체크 리스트

  • 미션의 필수 요구사항을 모두 구현했나요?
  • Gradle test를 실행했을 때, 모든 테스트가 정상적으로 통과했나요?
  • 애플리케이션이 정상적으로 실행되나요?

기능 요구 사항에 없어 스스로 정한 내용

  1. 게임 참가자(이하 참가자)와 관련된 내용

    1. 참가자의 이름은 2글자 이상 5글자 이하이다.
      • 참가자 이름 중간에 공백을 허용한다.(앞뒤 공백은 허용하지 않는다.)
    2. 게임 참가자는 2명 이상 8명 이하이다.
  2. 블랙잭과 관련된 내용

    1. 사용하는 카드는 조커를 제외한 52장이고, 총 6벌을 사용한다.
    2. 참가자의 카드 숫자의 합이 21이 초과되면 카드를 뽑을 수 없다.(자동으로 다음 참가자 순서로 넘어간다.)
  3. 예외 처리와 관련된 내용

    1. 게임 진행 중 예외가 발생하면 게임을 종료한다.

패키지 구조 설계 이유

코드의 구조를 보실 때 조금이나마 도움이 되고자 각 패키지의 존재 목적을 기재해보았습니다!

  • Controller: 비지니스 로직의 흐름을 담당합니다. View와 Service의 메서드를 적재적소에 배치합니다. 이때 Domain을 직접적으로 사용하지 않습니다.
  • Service: 비지니스 로직을 담당합니다. 여러 Domain의 협력을 통해 하나의 기능을 만듧니다. 서비스끼리는 DTO를 이용해 소통합니다.
  • Domain: 비지니스 개념을 담당합니다. 각 개념들이 마땅히 해야 하거나 할 것들을 정의합니다.
  • View: 사용자에게 필요한 UI를 제공합니다.
  • Converter: 도메인을 DTO로 변환하는 역할을 합니다.

어떤 부분에 집중하여 리뷰해야 할까요?

1. Controller까지 Domain을 가져오는건 괜찮을까요?

저희는 이번 미션에서 Controller에 도메인을 가져가지 않기로 했고, 이를 지키기 위해 도메인을 DTO로 담아 컨트롤러에 반환하도록 구현했습니다. 이유는 도메인을 Controller까지 가져오면 도메인 조작을 Controller에서 할 수 있기 때문에 이를 방지하고자 하는 목적이었습니다. 하지만 이로 인해 코드에 PlayersDto라는 단순히 도메인 객체를 한 번더 감싼 형식의 DTO가 만들어지기도 했습니다. 이렇게 Controller에서 Domain을 직접적으로 가져오는건 괜찮은지 주디의 의견이 궁금해요!

2. DTO 생성은 어디에서 하는게 좋을까요?

저는 도메인을 가공하여 DTO를 생성하는 Converter 계층을 따로 두었습니다. 이유는 DTO를 생성하는 것도 비지니스 로직의 일부라고 생각했기 때문입니다. 하지만 Converter 클래스 하나에서 모든 DTO 생성을 담당하다 보니 그냥 DTO 자체에서 생성하면 괜찮지 않을까란 생각을 해보았습니다. 이때 충돌 되었던 점이 DTO는 데이터를 담는 가벼운 클래스인데, DTO 생성과 같은 비지니스 로직이 들어가는게 좋은 선택인지 고민도 됩니다. 그래서 저는 주디가 사용하는 DTO 생성 방법과, 방법의 이유가 궁금해요!

3. extends vs implements 의 적절한 사용법이 궁금해요!

Player와 Dealer의 상당 부분이 겹쳤지만 Dealer에서 추가로 필요한 부분이 있어 Player와 Dealer의 인터페이스를 만들어 implements 하지 않고 Dealer가 Player를 extends 하고 Dealer에 추가 기능을 구현했습니다. 이 방법에 대한 주디의 의견(extends를 사용한 이유의 타당성)이 궁금해요! 추가로 extends와 implements를 적절히 사용하는 주디만의 기준이 있는지 궁금합니다!

4. Controller 멤버 변수 개수를 2개 이하로 줄이는 방법이 있을까요?

Controller에서 추가 요구사항인 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.를 지키지 못했습니다. 각 클래스가 컨트롤러에서 반드시 필요한 것들이라 판단했기 때문입니다. Controller에서 추가 요구사항을 만족할 수 있는 방법이 궁금합니다!

5. 테스트를 위한 메서드를 만들어도 될까요?

Player 도메인에는 카드를 뽑다 라는 기능이 있습니다. 하지만 뽑은 카드의 개수를 확인하는 기능은 없습니다. 명시적으로는 문제가 없었지만, 카드를 뽑는 기능의 테스트 코드를 작성할 때 문제가 생겼습니다. 바로 뽑은 카드를 확인할 방법이 없었기 때문입니다. 이를 위해서 Player의 카드 개수를 반환하는 메서드를 만들 수 있었지만, 이는 비지니스 로직에서는 전혀 사용하지 않는 메서드였습니다. 여기서 궁금증이 생겼습니다. 테스트를 위한 메서드를 만들어도 될까요? 만들지 않아야 한다면, 테스트를 포기해야 할까요? 주디의 의견이 궁금합니다ㅎㅎ

6. VO 사용 범위는 어디까지 일까요?

Player 도메인의 name 필드를 String으로 저장하고 있습니다. 이는 원시값 및 문자열 포장 원칙에 어긋났지만, name 필드를 VO로 감싸는 것이 실질적으로 유효하지 않다 생각했기 때문에 원칙을 준수하지 않았습니다. 따라서 이런 경우에도 VO를 사용해야 하는지 궁금하고, 사용해야 한다면 이유도 알고싶습니다!

7. Exception 메시지 관리방법에는 어떤 것들이 있을까요?

저의 애플리케이션은 ErrorMessage 라는 enum 클래스에서 모든 에러 메세지를 관리합니다. 이렇게 관리하면 한 파일에서 모든 에러 메세지를 사용하고 변경할 수 있다는 이점이 있지만, 이 메세지가 정확히 어디에서 사용되는지는 알기가 어렵다는 단점이 있었습니다. 저는 이렇게 예외 메시지를 중앙에서 통합 관리하는 방식이 적절한지, 다른 더 좋은 방법이 있는지 궁금합니다!

8. 정책 상수 변경에 유연한 테스트 코드를 작성하는 방법은 무엇일까요?

저는 이번 코드에서 하드 코딩된 값들을 모두 PolicyConstant 클래스에 분리하여 정책과 관련된 변수들을 한 번에 관리할 수 있도록 리팩토링 했습니다. 이를 통해 이제 참가자의 이름 길이, 참가자 수를 편리하게 변경할 수 있었지만, 테스트명과 테스트 방식, 테스트 데이터를 정책에 맞게 변경할 수는 없어 테스트 코드는 직접 수정해야만 했습니다. 이런 문제 때문에 정책 변경에 유연한 테스트 코드를 작성하는 방법이 궁금합니다!

리뷰어에게

안녕하세요 주디!
우선 우아한테크코스의 리뷰어로 참여해주셔서 정말 감사해요!
이번 페어 프로그래밍을 하면서 궁금했던 점이 많아 질문이 조금 많아졌어요🥹
천천히 읽어주시고 편하게 답변해주시면 감사하겠습니다!

좋은 주말 되세요☺️

yeo-li added 30 commits March 4, 2026 16:56
inputPlayers()와 inputHitOrStand() 에서 BufferedReader를 공통으로 사용하고 있어 재사용성을
높이기 위해 분리했습니다.
List<Player>로 반환할 경우, 의도하지 않은 삭제 또는 추가가 일어날 수 있어 이를 방지하기 위해 불변 Dto로 반환하도록 변경했습니다.
BufferedReader를 사용하면 IOException을 try-catch 해야하는데, Scanner를 사용하면 예외처리를 하지 않아도 되어 변경했습니다.
yeo-li added 27 commits March 6, 2026 22:02
PLAYER_MAX_COUNT와 PLAYER_NAME_MAX_LENGTH의 값이 바뀌어있어 이를 서로 변경했습니다.
Copy link
Author

@yeo-li yeo-li left a comment

Choose a reason for hiding this comment

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

각 질문과 관련된 부분에 질문들을 다시 언급해놓았습니다!☺️

Copy link
Author

Choose a reason for hiding this comment

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

2. DTO 생성은 어디에서 하는게 좋을까요?

저는 도메인을 가공하여 DTO를 생성하는 Converter 계층을 따로 두었습니다. 이유는 DTO를 생성하는 것도 비지니스 로직의 일부라고 생각했기 때문입니다. 하지만 Converter 클래스 하나에서 모든 DTO 생성을 담당하다 보니 그냥 DTO 자체에서 생성하면 괜찮지 않을까란 생각을 해보았습니다. 이때 충돌 되었던 점이 DTO는 데이터를 담는 가벼운 클래스인데, DTO 생성과 같은 비지니스 로직이 들어가는게 좋은 선택인지 고민도 됩니다. 그래서 저는 주디가 사용하는 DTO 생성 방법과, 방법의 이유가 궁금해요!

Copy link

Choose a reason for hiding this comment

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

음 일단 도메인 -> DTO -> 도메인 이렇게 변환해서 사용하고 계신데, 서여의 생각이 궁금한 부분은 DTO가 왜 필요할까요?
DTO를 생성하는 것도 비지니스 로직의 일부라고 생각하셨다면 PR에 작성해주신처럼 서비스에 있어야하는게 맞지 않나요~? 비즈니스 로직이 뭔지도 다시 생각해보시면 좋을 것 같아요!
수정 필요하다 싶으시면 수정해주시고 위 문의에 답변주시면 정답은 아니지만 제가 사용하는 DTO 생성 방법과, 방법의 이유 공유드릴게요~!

Copy link
Author

Choose a reason for hiding this comment

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

3. extends vs implements 의 적절한 사용법이 궁금해요!

Player와 Dealer의 상당 부분이 겹쳤지만 Dealer에서 추가로 필요한 부분이 있어 Player와 Dealer의 인터페이스를 만들어 implements 하지 않고 Dealer가 Player를 extends 하고 Dealer에 추가 기능을 구현했습니다. 이 방법에 대한 주디의 의견(extends를 사용한 이유의 타당성에 대한 평가)이 궁금해요! 추가로 extends와 implements를 적절히 사용하는 주디만의 기준이 있는지 궁금합니다!

Copy link

Choose a reason for hiding this comment

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

일단 제가 생각하기에는 is-a 관계는 아닌 것 같아요~
손에 가진 것을 꺼내는 행위는 Dealer가 가지고 있지 않는 것으로 보이거든요.

정답은 아니지만 저의 기준은 클래스를 상속받는건 부모와 자식이 강결합되는데, 추가 요구사항이 생겼을때 수정이 적은 방법은 무엇인가?를 고민해보는 것 같아요~!
어떤 요구사항에도 is-a 관계를 유지하기때문에 중복 코드를 줄일 수 있어 extends를 사용하는 것이 낫다면 extends를 사용하고 이외의 경우는 implements를 사용하고 있어요!

Comment on lines +17 to +19
private final InputView inputView;
private final OutputView outputView;
private final BlackjackService blackjackService;
Copy link
Author

Choose a reason for hiding this comment

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

4. Controller 멤버 변수 개수를 2개 이하로 줄이는 방법이 있을까요?

Controller에서 추가 요구사항인 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.를 지키지 못했습니다. 각 클래스가 컨트롤러에서 반드시 필요한 것들이라 판단했기 때문입니다. Controller에서 추가 요구사항을 만족할 수 있는 방법이 궁금합니다!

Copy link

Choose a reason for hiding this comment

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

PR에 말씀해 주신 것처럼 현재 Controller에서는 InputView, OutputView, BlackjackService 모두 필요한 객체이기 때문에 자연스럽게 인스턴스 변수가 늘어날 수 있는 구조라고 보입니다.

몇 가지 다른 방향도 한 번 고려해 볼 수 있을 것 같네요~!

먼저 View가 반드시 Controller의 멤버 변수로 존재해야 하는지에 대해 생각해 볼 수 있을 것 같습니다. 예를 들어 View를 외부에서 사용하거나, 메서드 호출 시점에 전달하는 방식으로 구성할 수도 있겠지요!

또 하나는 상태(인스턴스 변수)를 가지지 않는 클래스라면 static 메서드 형태로 사용하는 방법도 있습니다. View가 단순히 입출력 역할만 수행하고 별도의 상태를 가지지 않는다면 유틸성 클래스로 두는 방식도 하나의 선택지가 될 수 있을 것 같아요.

다만 중요한 것은 요구사항을 맞추기 위해 구조를 억지로 변경하기보다는, 현재 구조에서 각 객체의 역할이 자연스럽게 나뉘어 있는지를 먼저 고민해 보는 것이라고 생각합니다. 여러 방법 중 어떤 방식이 지금 구조에 가장 자연스러운지 한 번 고민해 보셔도 좋을 것 같습니다 🙂

Comment on lines +73 to +85
@Test
void 카드를_추가하면_손패에_카드가_저장되어야_한다() {

// given
Player player = new Player("jacob");
Card card = new Card(Rank.ACE, Suit.HEART);

// when
player.addCard(card);

// then
assertThat(player.getHand().getCardNames().size()).isEqualTo(1);
}
Copy link
Author

Choose a reason for hiding this comment

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

5. 테스트를 위한 메서드를 만들어도 될까요?

Player 도메인에는 카드를 뽑다 라는 기능이 있습니다. 하지만 뽑은 카드의 개수를 확인하는 기능은 없습니다. 명시적으로는 문제가 없었지만, 카드를 뽑는 기능의 테스트 코드를 작성할 때 문제가 생겼습니다. 바로 뽑은 카드를 확인할 방법이 없었기 때문입니다. 이를 위해서 Player의 카드 개수를 반환하는 메서드를 만들 수 있었지만, 이는 비지니스 로직에서는 전혀 사용하지 않는 메서드였습니다. 이 문제를 결국 우회하여 해결하긴 했지만, 궁금증이 생겼습니다. 테스트를 위한 메서드를 만들어도 될까요? 만들지 않아야 한다면, 테스트를 포기해야 할까요? 주디의 의견이 궁금합니다ㅎㅎ


public class Player {

protected final String name;
Copy link
Author

Choose a reason for hiding this comment

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

6. VO 사용 범위는 어디까지 일까요?

Player 도메인의 name 필드를 String으로 저장하고 있습니다. 이는 원시값 및 문자열 포장 원칙에 어긋났지만, name 필드를 VO로 감싸는 것이 실질적으로 유효하지 않다 생각했기 때문에 원칙을 준수하지 않았습니다. 따라서 이런 경우에도 VO를 사용해야 하는지 궁금하고, 사용해야 한다면 이유도 알고싶습니다!

Copy link

Choose a reason for hiding this comment

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

모든 원시 값과 문자열을 포장한다.라는 프로그래밍 요구사항이 있지만 요구사항은 요구사항일뿐 법칙은 아닙니다.

모든 원시값과 문자열을 포장해야한다는건 저도 동의하지 않는데요~
다만 name의 경우, 수행하는 로직들이 있지요~!(이름 길이, 공백 검증)
이 부분을 VO로 옮겨 수행한다면 블랙잭에서 중요한 도메인이라고 볼 수 있는 Player의 행위들이 파악하기 쉬울 것 같아요~

Copy link
Author

@yeo-li yeo-li Mar 7, 2026

Choose a reason for hiding this comment

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

7. Exception 메시지 관리방법에는 어떤 것들이 있을까요?

저의 애플리케이션은 ErrorMessage 라는 enum 클래스에서 모든 에러 메세지를 관리합니다. 이렇게 관리하면 한 파일에서 모든 에러 메세지를 사용하고 변경할 수 있다는 이점이 있지만, 이 메세지가 정확히 어디에서 사용되는지는 알기가 어렵다는 단점이 있었습니다. 저는 이렇게 예외 메시지를 중앙에서 통합 관리하는 방식이 적절한지 궁금합니다.

Copy link

Choose a reason for hiding this comment

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

Exception 메시지를 한 곳에서 관리해보면서 장단점을 직접 느끼신 것 같네요! 💯 이처럼 직접 시도해보고 고민해보는 과정이 정말 중요한 것 같아요 ㅎㅎ

Exception 메시지를 관리하는 방법에는 정답이 있는 것은 아니고, 보통은 팀에서 컨벤션으로 정하는 경우가 많습니다.

말씀해주신 것처럼 하나의 파일에서 모든 에러 메시지를 관리하면 애플리케이션에서 어떤 종류의 에러가 발생할 수 있는지 한눈에 파악하기 쉽다는 장점이 있는 것 같습니다.
다만 한 가지 고민해볼 만한 점은, 프로그램 규모가 커져서 에러의 종류가 매우 많아진다면 어떻게 관리할 수 있을까? 하는 부분입니다. 에러 메시지가 계속 추가되다 보면 하나의 enum이 지나치게 커질 수도 있을 것 같습니다.

그리고 "이 메시지가 어디에서 사용되는지 알기 어렵다" 라고 말씀해주셨는데, 보통 IDE에서 사용처 찾기(Find Usages) 기능을 통해 해당 메시지가 사용되는 위치를 확인할 수 있기 때문에, 제가 이해한 것과 조금 다른 의미일 수도 있을 것 같다는 생각이 들었습니다. 이 부분은 어떤 상황에서 그렇게 느끼셨는지 조금 더 궁금하네요~! 🙂

그래서 제 개인적인 생각으로는,

작은 프로젝트에서는 어떤 에러가 발생할 수 있는지 한눈에 볼 수 있다는 장점이 있기 때문에 enum이나 상수 클래스로 중앙에서 관리하는 방식도 충분히 좋은 선택일 수 있고,
프로젝트 규모가 커지는 경우에는 메시지 자체보다는 어떤 도메인에서 발생한 예외인지가 더 중요해질 수 있기 때문에 도메인별로 예외와 메시지를 함께 관리하는 방식도 고려해볼 수 있을 것 같습니다.

결국 프로젝트의 규모와 팀의 컨벤션에 맞게 선택하는 것이 가장 중요할 것 같네요! 🙂

@jurlring
Copy link

jurlring commented Mar 9, 2026

서여 안녕하세요!
저랑 같은학교 같은과 출신이시군요!! 반가워요 🔆

이번 블랙잭 미션에선 어떤 것을 학습하고 싶으신지 궁금해요~!
모르는 것이 어떤 것이고, 배워야 할 것이 뭔지 자신이 알고있는 것은 너무너무 중요하거든요!!
나중에 미션이 끝나고 제가 확인할게요 🐰

Copy link

@jurlring jurlring left a comment

Choose a reason for hiding this comment

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

서여 안녕하세요~!
블랙잭 미션을 함께하게 된 주디입니다 🐰
서여의 생각이 궁금한 부분&수정하면 좋을 부분은 코멘트로 남겨두었어요.
시간 괜찮으실 때 확인해주세요~!

Comment on lines +17 to +19
private final InputView inputView;
private final OutputView outputView;
private final BlackjackService blackjackService;
Copy link

Choose a reason for hiding this comment

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

PR에 말씀해 주신 것처럼 현재 Controller에서는 InputView, OutputView, BlackjackService 모두 필요한 객체이기 때문에 자연스럽게 인스턴스 변수가 늘어날 수 있는 구조라고 보입니다.

몇 가지 다른 방향도 한 번 고려해 볼 수 있을 것 같네요~!

먼저 View가 반드시 Controller의 멤버 변수로 존재해야 하는지에 대해 생각해 볼 수 있을 것 같습니다. 예를 들어 View를 외부에서 사용하거나, 메서드 호출 시점에 전달하는 방식으로 구성할 수도 있겠지요!

또 하나는 상태(인스턴스 변수)를 가지지 않는 클래스라면 static 메서드 형태로 사용하는 방법도 있습니다. View가 단순히 입출력 역할만 수행하고 별도의 상태를 가지지 않는다면 유틸성 클래스로 두는 방식도 하나의 선택지가 될 수 있을 것 같아요.

다만 중요한 것은 요구사항을 맞추기 위해 구조를 억지로 변경하기보다는, 현재 구조에서 각 객체의 역할이 자연스럽게 나뉘어 있는지를 먼저 고민해 보는 것이라고 생각합니다. 여러 방법 중 어떤 방식이 지금 구조에 가장 자연스러운지 한 번 고민해 보셔도 좋을 것 같습니다 🙂

Comment on lines +7 to +12
public static int DEALER_HIT_MAX_SCORE = 16;

public static int PLAYER_MIN_COUNT = 2;
public static int PLAYER_MAX_COUNT = 8;
public static int PLAYER_NAME_MIN_LENGTH = 2;
public static int PLAYER_NAME_MAX_LENGTH = 5;
Copy link

Choose a reason for hiding this comment

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

final이 빠진 것 같아요!
final이라는 키워드는 왜 필요할까요?
클래스, 메서드, 필드에 붙을 때 각각 어떻게 다른지도 알고 계실까요~?

Copy link
Author

Choose a reason for hiding this comment

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

앗 이 부분을 작성 중에 실수한 것 같습니다..!

그리고 final이라는 키워드가 필드에서는 필드 값의 변경 금지를 강제하기 위함이라는 것을 알고 있었지만, 메서드와 클래스에서 final이 무엇을 의미하는지는 생각해보지 못한 것 같아요! 그래서 이것과 관련해서 찾아보았습니다:)

  • 클래스에 final을 붙이면 상속 금지를 강제하기 위함입니다.
  • 메서드에 final을 붙이면 오버라이딩 금지를 강제하기 위함입니다.

주디가 해주신 질문 덕분에 새로운 지식을 얻었어요! 감사합니다☺️


@ParameterizedTest
@ValueSource(strings = {"a", "aaa aa"})
void 게임_참가자_이름의_길이가_2_이상_5_이하가_아니라면_예외를_발생_시켜야_한다(String name) {
Copy link

Choose a reason for hiding this comment

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

  1. 정책 상수 변경에 유연한 테스트 코드를 작성하는 방법은 무엇일까요?
    저는 이번 코드에서 하드 코딩된 값들을 모두 PolicyConstant 클래스에 분리하여 정책과 관련된 변수들을 한 번에 관리할 수 있도록 리팩토링 했습니다. 이를 통해 이제 참가자의 이름 길이, 참가자 수를 편리하게 변경할 수 있었지만, 테스트명과 테스트 방식, 테스트 데이터를 정책에 맞게 변경할 수는 없어 테스트 코드는 직접 수정해야만 했습니다. 이런 문제 때문에 정책 변경에 유연한 테스트 코드를 작성하는 방법이 궁금합니다!

저는 정책 변경으로 인해 테스트 코드가 함께 변경되는 것은 자연스러운 일이라고 생각합니다.

테스트 코드의 역할을 한 번 생각해 보면 좋을 것 같은데요~!
테스트는 단순히 코드가 동작하는지 확인하는 것을 넘어서 1) 정책이 프로덕션 코드에 올바르게 반영되어 있는지 검증하고 2)해당 정책을 다른 개발자들도 이해할 수 있도록 문서 역할을 하며 3)이후 다른 개발자가 코드를 수정할 때 의도와 다르게 변경되는 것을 방지하는 역할도 한다고 생각해요.

그래서 정책 자체가 변경되었다면, 그 정책을 검증하고 있는 테스트 역시 함께 수정되는 것이 자연스러운 흐름이라고 생각합니다.
즉 정책 변경 → 테스트 실패 → 테스트 수정 → 코드 수정과 같은 과정은 필수적이라고 생각해요!

물론 테스트 코드에서 불필요하게 하드코딩된 값이 많아 유지보수가 어려운 경우라면 상수나 테스트 픽스처를 활용해 정리할 수는 있겠지만, 정책 자체가 변경된 경우라면 테스트가 함께 수정되는 것은 크게 문제라고 보지는 않습니다. 한 번 이런 관점에서도 생각해 보셔도 좋을 것 같습니다 🙂

Copy link

Choose a reason for hiding this comment

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

Exception 메시지를 한 곳에서 관리해보면서 장단점을 직접 느끼신 것 같네요! 💯 이처럼 직접 시도해보고 고민해보는 과정이 정말 중요한 것 같아요 ㅎㅎ

Exception 메시지를 관리하는 방법에는 정답이 있는 것은 아니고, 보통은 팀에서 컨벤션으로 정하는 경우가 많습니다.

말씀해주신 것처럼 하나의 파일에서 모든 에러 메시지를 관리하면 애플리케이션에서 어떤 종류의 에러가 발생할 수 있는지 한눈에 파악하기 쉽다는 장점이 있는 것 같습니다.
다만 한 가지 고민해볼 만한 점은, 프로그램 규모가 커져서 에러의 종류가 매우 많아진다면 어떻게 관리할 수 있을까? 하는 부분입니다. 에러 메시지가 계속 추가되다 보면 하나의 enum이 지나치게 커질 수도 있을 것 같습니다.

그리고 "이 메시지가 어디에서 사용되는지 알기 어렵다" 라고 말씀해주셨는데, 보통 IDE에서 사용처 찾기(Find Usages) 기능을 통해 해당 메시지가 사용되는 위치를 확인할 수 있기 때문에, 제가 이해한 것과 조금 다른 의미일 수도 있을 것 같다는 생각이 들었습니다. 이 부분은 어떤 상황에서 그렇게 느끼셨는지 조금 더 궁금하네요~! 🙂

그래서 제 개인적인 생각으로는,

작은 프로젝트에서는 어떤 에러가 발생할 수 있는지 한눈에 볼 수 있다는 장점이 있기 때문에 enum이나 상수 클래스로 중앙에서 관리하는 방식도 충분히 좋은 선택일 수 있고,
프로젝트 규모가 커지는 경우에는 메시지 자체보다는 어떤 도메인에서 발생한 예외인지가 더 중요해질 수 있기 때문에 도메인별로 예외와 메시지를 함께 관리하는 방식도 고려해볼 수 있을 것 같습니다.

결국 프로젝트의 규모와 팀의 컨벤션에 맞게 선택하는 것이 가장 중요할 것 같네요! 🙂


public class Player {

protected final String name;
Copy link

Choose a reason for hiding this comment

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

모든 원시 값과 문자열을 포장한다.라는 프로그래밍 요구사항이 있지만 요구사항은 요구사항일뿐 법칙은 아닙니다.

모든 원시값과 문자열을 포장해야한다는건 저도 동의하지 않는데요~
다만 name의 경우, 수행하는 로직들이 있지요~!(이름 길이, 공백 검증)
이 부분을 VO로 옮겨 수행한다면 블랙잭에서 중요한 도메인이라고 볼 수 있는 Player의 행위들이 파악하기 쉬울 것 같아요~

Comment on lines +137 to +139
private int calculateScore(Player player) {
return player.calculateScore();
}
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 +41 to +44
private void validatePlayerNames(List<String> names) {
validatePlayerCount(names);
validatePlayerCount(names.size());
}
Copy link

Choose a reason for hiding this comment

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

validatePlayerCount 메서드 오버로딩을 활용해주셨는데, validatePlayerNames만 봐서는 이해가 안가서 내부까지 들어가야 이해가 가능할 것 같아요 😢

}
}

public Card drawCard() {
Copy link

Choose a reason for hiding this comment

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

BlackJackService에서만 사용되는 메서드로 보여요!

import constant.PolicyConstant;
import exception.ErrorMessage;

public class Player {
Copy link

Choose a reason for hiding this comment

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

  1. 테스트를 위한 메서드를 만들어도 될까요?
    Player 도메인에는 카드를 뽑다 라는 기능이 있습니다. 하지만 뽑은 카드의 개수를 확인하는 기능은 없습니다. 명시적으로는 문제가 없었지만, 카드를 뽑는 기능의 테스트 코드를 작성할 때 문제가 생겼습니다. 바로 뽑은 카드를 확인할 방법이 없었기 때문입니다. 이를 위해서 Player의 카드 개수를 반환하는 메서드를 만들 수 있었지만, 이는 비지니스 로직에서는 전혀 사용하지 않는 메서드였습니다. 여기서 궁금증이 생겼습니다. 테스트를 위한 메서드를 만들어도 될까요? 만들지 않아야 한다면, 테스트를 포기해야 할까요? 주디의 의견이 궁금합니다ㅎㅎ

저는 테스트를 위한 메서드는 존재하지 않는 것이 좋다고 생각합니다~!
프로덕션 코드만으로도 객체가 가져야 할 책임이 충분히 많을 수 있는데, 운영에서 사용되지도 않을 메서드가 추가된다면 그 메서드는 또 어떻게 검증해야 할지 고민이 생길 것 같아요. 결국 테스트를 위한 테스트가 필요해질 수도 있을 것 같습니다.

그래서 테스트를 위해 별도의 메서드를 만들어야 하는 상황이라면, 현재 구조를 한 번 의심해볼 필요가 있을 것 같아요.
보통 이런 경우는 객체의 책임이나 설계가 조금 어색해졌다는 신호일 수도 있다고 생각합니다.

그래서 테스트를 위해서만 사용하는 메서드를 도메인에 추가하기보다는, 현재 구조에서 외부로 드러난 행동만으로 검증할 수 있는 방법은 없는지, 혹은 책임을 다른 객체로 분리할 수는 없는지 같은 방향을 먼저 고민해보면 좋을 것 같습니다. :)

import view.InputView;
import view.OutputView;

public class BlackjackController {
Copy link

Choose a reason for hiding this comment

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

Image bust가 됐는데도 카드를 받겠냐고 물어보는데, 실제로 카드를 받고있진 않네요..!

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