[🚀 사이클2 - 미션 (블랙잭 베팅)] 바니 미션 제출합니다.#1103
Conversation
Signed-off-by: leejaeheon <jaeheon@leejaeheons-MacBook-Air.local>
Signed-off-by: leejaeheon <jaeheon@leejaeheons-MacBook-Air.local>
Signed-off-by: leejaeheon <jaeheon@leejaeheons-MacBook-Air.local>
Signed-off-by: leejaeheon <jaeheon@leejaeheons-MacBook-Air.local>
Signed-off-by: leejaeheon <jaeheon@leejaeheons-MacBook-Air.local>
세부사항: - 덱에 카드가 존재하지 않는 경우 예외 발생
세부사항: - ACE는 기본 11 - ACE가 존재하고 합계가 21을 초과하는 경우 1로 변경 - Cards 일급 컬렉션 도입
- Participant의 Card 일급 컬렉션은 Hand로 정의, Deck의 일급 컬렉션은 Cards로 정의
|
안녕하세요, 주노! |
Choi-JJunho
left a comment
There was a problem hiding this comment.
바니~! 이번미션도 너무 고생하셨습니다 👏
수정사항을 잘 반영해주셨네요 👍
몇가지 고민이 더 필요한 부분과 수정이 필요한 부분이 있어 코멘트를 남겨놨으니 확인 부탁드릴게요!
| .map(Player::new).toList() | ||
| ); | ||
|
|
||
| players.getPlayers().forEach(player -> player.bet(inputView.getBetAmount(player.name()))); |
There was a problem hiding this comment.
소프트웨어 개발은 트레이드오프의 연속이라 적절한 방식이라는 정답은 제시해드리기 어려울것 같네요! ㅎㅎ
다만 이번 미션에서는 객체의 불변성을 조금 더 신경쓰면서 구조화를 해보는것을 제안드려봅니다
어떻게하면 Player 객체를 불변으로 유지하면서 bet을 수행할 수 있는 방법을 한번 적용해볼까요?
| import java.math.BigDecimal; | ||
|
|
||
| public class ProfitCalculator { | ||
| public static BigDecimal calculatePlayerProfit(Player player, Dealer dealer) { |
There was a problem hiding this comment.
이 로직을 static으로 구성한 이유가 궁급합니다!
static 메서드를 선택할때 어떤 기준을 가지고 선택하시나요?
There was a problem hiding this comment.
뭔가 단순히 계산을 한다고 생각해서 util 메서드라고 판단하여 static으로 선택하였는데, 다시 보니 도메인과 밀접한 메서드라... 적합하지 않다는 생각이 드네요..!
There was a problem hiding this comment.
오~! ProfitCalculator를 인스턴스화해서 사용하도록 변경해주셨군요!
저는 static 메서드여도 괜찮아보인다는 생각이였는데요~!
ProfitCalculator 정도는 util 메서드로 활용해도 괜찮다고 생각하는데 바니가 적합하지 않다고 생각한 근거가 궁금합니다 ㅎㅎ
다시 보니 도메인과 밀접한 메서드라... 적합하지 않다는 생각이 드네요..!
도메인과 밀접한 메서드는 static 일 수 없을까요?
There was a problem hiding this comment.
도메인과 밀접한 메서드라고 해서 반드시 static으로 만들 수 없다고 단정할 수는 없다고 생각합니다.
다만 스스로도 제 근거가 부족하다고 판단하여... static 메서드에 대해 찾아보고 두 가지로 정리했습니다!
-
static 메서드는 도메인 로직이 객체 밖으로 분리되며 절차 지향적으로 흐를 수 있다는 점이 있었습니다. 그래서 도메인과 관련된 객체의 책임을 외부로 빼는 구조가 될 수 있다고 생각했습니다.
-
또한 static 메서드는 오버라이딩이 불가능하기 때문에, 향후 계산 전략이 다양해지거나 정책이 변경되는 경우 유연하게 확장하기 어렵다는 점도 있다고 생각했습니다..!
static 메서드 자체가 항상 부적절하다고 생각하지는 않지만, 도메인 규칙이 담겨 있는 로직의 경우 객체 간 협력으로 표현하는 것이 좋다고 생각했고, 나중에 정책이나 계산 방식이 변경될 가능성을 고려했을 때도 확장에 유리하지 않을까 판단했습니다!
There was a problem hiding this comment.
좋습니다~
바니가 지금처럼 본인의 선택에 대해 타당한 이유를 찾아내고 설명해 나가는 과정에서 더 단단한 논리를 갖추는 데 좋은 연습이 되었을 거라고 생각해요 💪
|
안녕하세요, 주노! 피드백해주신 부분들 뿐만 아니라, 일부 메서드가 테스트코드에서만 사용되더라구요..! |
Choi-JJunho
left a comment
There was a problem hiding this comment.
바니~! 점점 성장하고계심이 잘 느껴지네요!
몇가지 더 이야기해보고싶은 부분이있어 마지막으로 Request changes를 남겨봅니다 🙏
마지막까지 화이팅입니다! 💪
| import java.math.BigDecimal; | ||
|
|
||
| public class ProfitCalculator { | ||
| public static BigDecimal calculatePlayerProfit(Player player, Dealer dealer) { |
There was a problem hiding this comment.
오~! ProfitCalculator를 인스턴스화해서 사용하도록 변경해주셨군요!
저는 static 메서드여도 괜찮아보인다는 생각이였는데요~!
ProfitCalculator 정도는 util 메서드로 활용해도 괜찮다고 생각하는데 바니가 적합하지 않다고 생각한 근거가 궁금합니다 ㅎㅎ
다시 보니 도메인과 밀접한 메서드라... 적합하지 않다는 생각이 드네요..!
도메인과 밀접한 메서드는 static 일 수 없을까요?
| import static domain.game.BlackjackRule.BLACK_JACK; | ||
|
|
||
| public class Player extends Participant { | ||
| private BetAmount betAmount; |
There was a problem hiding this comment.
BetAmount를 VO로 잘 래핑해주셨네요 👏 하지만 betAmount 필드가 bet() 메서드로 외부에서 변경 가능한 상태로 Player객체는 불변이라고 보기 어려울 것 같은데요! 이 부분도 한번 불변으로 만들어볼까요?
There was a problem hiding this comment.
DM을 통해 말씀해주신 방식 덕분에 불변을 유지하는 방법에 대해 다시 고민해볼 수 있었습니다 🐰
처음에는 '이름을 입력 받고 -> Player 객체 생성 -> 이후 베팅'이라는 흐름을 실제 과정과 맞추고 싶어서 해당 구조를 선택했었습니다! 현재 기능에는 없지만, 만약 라운드가 룰이 도입된다면 라운드가 진행되면서 베팅 금액이 달라질 수 있다고 생각해, Player가 생성된 이후에 베팅을 하는 것이 자연스럽다고 느꼈던 것 같습니다.
그런데 다시 고민해보니, 만약 라운드 개념이 도입된다면(너무 과한 몰입이겠지만요 ㅎㅎ..) Player는 불변으로 유지하고, 라운드별로 Player와 BetAmount를 함께 가지는 별도의 객체로 분리하는 것이 더 자연스러운 모델이라고 판단했습니다.
이러한 관점으로 다시 보니, 현재 구조에서는 Player가 하나의 게임 단위에서 베팅까지 완료된 참가자라고 해석하는 것이 더 적절하다고 느껴졌습니다. 그래서 Player가 BetAmount를 함께 가지도록 하여 생성 시점에 완전한 상태를 가지도록 수정하게 되었습니다!
덕분에 불변 객체를 어떻게 다뤄야 할지에 대해 한 단계 더 이해하게 된 것 같습니다 감사합니다 🙇♂️
| String choice = inputView.getChoice(player.name()); | ||
|
|
||
| if (!choice.equals("y") && !choice.equals("n")) { | ||
| throw new IllegalArgumentException(); |
There was a problem hiding this comment.
전체적으로 예외가 발생할 때 IllegalArgumentException 만 반환하고있는데요!
자바의 표준 예외를 적절히 사용한 점은 좋지만 예외 메시지가 없어서 어떤 에러인지 한눈에 파악하기 어려운것 같아요
에러를 보기 쉽게 메시지를 작성해볼까요?
There was a problem hiding this comment.
예외 메시지를 추가하여 에러 발생 시 확인할 수 있도록 수정하겠습니다!
|
오늘도 안녕하세요, 주노! 😊 |
Choi-JJunho
left a comment
There was a problem hiding this comment.
바니~ 블랙잭 미션 마지막까지 정말 고생 많으셨습니다!
처음에는 내가 무엇을 모르는지조차 모르는 상태였을 수 있지만 미션을 수행하면서 내가 무엇을 모르는지 명확히 아는 상태로 나아간 것만으로도 엄청난 진전이라고 생각해요 👍
이번 미션을 진행하시면서 바니가 점차 메타인지를 훌륭하게 해내는 모습을 지켜볼 수 있어 저도 많이 배웠습니다 ㅎㅎ
남겨드린 코멘트도 깊이 고민해 주시고 요구사항도 잘 충족해 주셔서 이번 블랙잭 미션은 여기서 마무리하도록 하겠습니다.
추가로 궁금하신 내용은 DM으로 편하게 질문주셔도 됩니다! 고생 많으셨습니다! 🎉
안녕하세요, 주노! 바니에요🐰
벌써 두번째 리뷰를 요청드리게 됐네요! 우테코에서 보내는 시간은 정말 빠르게 흘러가는 것 같아요 ㅎㅎ
이번 리뷰도 잘 부탁드리며, 즐거운 주말 보내세요 😄
체크 리스트
test를 실행했을 때, 모든 테스트가 정상적으로 통과했나요?어떤 부분에 집중하여 리뷰해야 할까요?
베팅 금액을 관리하는 객체가 따로 필요할까요?
저는 베팅 금액 객체를 별도로 생성할까 고민을 하다가 따로 생성하지는 않았습니다!
일단 객체를 생성하는 이유는 역할을 부여하고 그에 맞는 책임을 다하도록 하는 거라고 생각해요.
제 생각에는 지금 단계에서 베팅 금액 자체가 가져야 할 책임이 그렇게 많지 않은 것 같아요.
베팅 금액 후 최종 수익 계산 로직 외에는 베팅 금액에 대한 규칙도 없고,
GameResult에서 calculatePlayerProfit 함수로도 현재 기능을 충분히 구현할 수 있다고 생각했습니다!
이 부분을 고민하면서 과한 설계와 확장 가능한 설계 사이에서 어떻게 균형을 잡아야 할지 궁금해졌어요.
도메인 객체를 분리할 때 어느 정도의 책임이나 규칙이 생기면 객체로 분리하는 것이 적절한지,
혹은 미래 확장을 고려해 미리 객체를 만드는 것이 좋은 경우는 언제인지 궁금해요!
일단 제 생각은 책임이 늘어가거나 도메인 규칙이 추가되면 객체로 분리하는 것이 좋다고 생각합니다.
과한 추상화는 오히려 코드 복잡도를 높일 수 있다고 생각하기 때문입니다!
테스트 주도 개발은 어떻게 해야 잘할 수 있는 걸까요?
물론 이제 막 TDD를 겨우 시작한 단계에서 벌써 잘하기를 바라는 건 조금 욕심이겠지만, 아직도 TDD가 너무 어렵고 낯설게 느껴지는 것 같아요.
예를 들어 구현을 하다 보면 테스트를 먼저 작성하려고 해도 어떤 인터페이스나 구조를 가정하고 작성해야 할지 막히는 순간이 있었고, 테스트를 작성한 뒤에도 구현 또는 테스트를 많이 수정하게 되는 경우가 있어서
이 과정이 제대로 된 TDD 사이클인지 고민이 되기도 합니다..!
그래서 테스트를 먼저 작성할 때 어느 수준까지 설계를 가정하고 테스트를 작성하는 것이 적절한지
실제로 TDD를 잘 하기 위해서 연습하거나 의식적으로 신경 쓰면 좋은 부분이 무엇인지 궁금합니다!