study/우아한테크코스

우아한테크코스 7기 백엔드 레벨1 블랙잭 미션 회고

듀2 2025. 4. 8. 00:36

블랙잭은.. 쉬운 줄 알았으나 나에게 상속과 다형성이라는 큰 숙제를 안겨준 미션이었다.
 
출석 미션에서 전혀 모르는 크루였던 포포와 함께 했던 기억이 좋았어서 이번 미션도 새로운 크루와 하게 될 기대를 하고 있었다.
그런데 같은 데일리 조의 드라고가 페어로 배정이 되었다.
새로운 페어를 만난다는 기대보다는 긴장에 가까웠던 건지 드라고와 함께 페어를 하게 되어 다행이라고 생각했다🤣
 
출석 미션이 복잡했던 것인지, 드라고와 잘 맞았던 것인지 블랙잭 미션은 하루 만에 대부분의 틀이 잡혔고, 여유롭게 진행할 수 있었다.
 

불변 객체와 가변 객체

처음 드라고와 미션을 시작할 때 학습의 측면에서, 그리고 안정성의 측면에서 모든 도메인을 불변으로 관리해 보자고 이야기했다.
그래서 모든 도메인을 불변으로 만들고, 객체의 상태가 변화되면 새로운 객체를 반환하는 방식으로 테스트 코드를 짜고, 프로덕션 코드를 구현했다.
한창 크루들 사이에서 final 키워드와 불변 객체에 대한 이야기들이 많이 돌았던 때이기도 했고, '불변이면 당연히 좋은 거 아니야?'라는 생각을 가지고 있었다.
 
그런데, 이렇게 직접 모든 도메인을 불변 객체로 관리하다 보니 생각하지 못했던 불편한 점들도 있었다.
 
블랙잭 미션에서 주요 도메인은 카드 한 장 한 장을 뜻하는 Card, 그 카드들을 관리하는 일급컬렉션인 Cards, 게임을 플레이하는 Player, 게임을 진행하는 Dealer로 구성되어 있었다.
Card와 Cards까지는 불변 객체로 사용하는 것이 자연스러웠는데, Player와 Dealer는 결국 GameManager에서 바뀌는 객체들을 항상 관리해줘야 해서 이게 맞는가?라는 생각을 했다.

실제로 코드를 봐도 딜러가 카드를 ’뽑는다‘, 플레이어가 카드를 ’뽑는다‘의 역할을 하는 메서드인데 반환값이 상태가 바뀐 딜러나 플레이어가 나와야 해서 어색한 코드가 나오기도 했다.

public T drawCard(List<Card> providedCards) {
    return createParticipant(providedCards);
}



불변 객체를 사용하는 것이 좋다고 했는데.. 왜 불편한 점이 있는 걸까?라고 의문이 들어 리뷰를 요청할 때 이와 관련된 질문을 했고, 리뷰어 파랑은 멋진 답변을 해주셨다.
 

개인적인 의견이지만 나에게 레벨1 최고의 리뷰였다!

파랑의 ‘너무 객체의 불변을 유지해야 한다는 원칙에만 매몰된 것은 아닐까요?’라는 질문이 너무 좋았다.
프로그래밍을 하면서 항상 트레이드오프 지점을 만나게 되는데, 불변 객체 또한 그런 지점인 것이라고 얘기해 주는 것 같았다.
덕분에 미션마다 지켜야 하는 객체지향 생활 체조 원칙들도 이런 관점으로 바라볼 수 있게 되었다.

그리고 브라운과 원온원을 하면서 알게된 점이기도 한데, 처음 드라고와 함께 세웠던 '모든 도메인을 불변으로 관리하자'는 기준을 끝까지 지키려고 노력했기 때문에 이런 리뷰를 받을 수도 있었고 파랑의 리뷰를 통해 깨달음을 얻을 수도 있었던 것이다.
 
브라운은 그런 깨달음을 얻기 위해서 틀리다고 생각하더라도 우선 그냥 끝까지 밀고 나가보라고 했다(ㅋㅋㅋ)
그렇게 한다면 틀리더라도 기억에 더 많이 남기 때문이 아닐까!

객체 생성의 이유

초기 코드에서는 Dealer와 Player가 불변을 유지하고 있어 상태가 변화하면 바뀐 객체 스스로를 반환했다.
두 객체가 Participant라는 추상 클래스를 상속받고 있으면서도 다형성을 활용하지 못하고 있었다.

블랙잭 미션에서는 제네릭 활용보다 추상 클래스를 상속하며 다형성을 학습하는 것이 우테코가 나에게 알려주는 학습 방향이라고 생각했다.
그래서 드라고와 함께 고민했던(실제로 데일리에서도 제네릭 도입했냐 우리는 해봤는데 맞는지 모르겠다는 이야기도 했었다ㅋㅋㅋ) 제네릭을 코드에서 지우고, 다형성을 활용해보려고 했다.

Dealer와 List<Player>를 Participants라는 객체로 함께 관리했지만 결국 GameManager에서 findDealer, findPlayers로 다시 구분하여 찾아오고 있었다.
게다가 findDealer, findPlayers이면서 부모 타입인 Participant로 Dealer를 받고, List<Participant>로 List<Player>를 받고 있었다.

나도 이런 코드를 작성하면서 이게 다형성을 활용하고 있는 게 맞나.. 이상한 느낌이 들었는데 파랑이 콕 찝어주셨다.
 
처음엔 이 리뷰가 어떤 의미인지 잘 모르겠다고 생각했다.
그런데 '상속이 별로니 무조건 없애라고 드리는 리뷰가 아니라'라는 문장을 곰곰이 곱씹어보았다.
상속이 뭔가 계속 찜찜해서 상속 대신 조합이나 인터페이스를 고려하기도 했었다는 말씀을 드리기도 했었기에 이런 말씀을 해주신 것 같았다.
그렇다면 '상속이 아니라 어떤 부분을 고려해야 할까?' 의문을 가지게 되었고, 'Dealer와 List<Player>를 Participants로 묶을 이유가 있을까?'라는 생각까지 하게 됐다.
이렇게 생각이 연결되니 'Participants 객체를 생성할 이유가 뭘까?' 질문해 보게 되었고, '생성할 마땅한 이유가 없다'라고 결론을 내리게 되었다.
파랑의 리뷰는 상속이나 조합, 인터페이스와 같은 방법론적인 부분이 아니라 Participants, Participant, Dealer, Player와 같은 내가 설계한 객체가 제 역할을 하고 있는지에 대한 질문이라고 생각했다.
 
그래서 객체 생성의 이유가 없는 Participants를 없앴고, 요구사항인 인스턴스 변수 3개를 포기하고 GameManager에서 CardProvider, Dealer와 List<Player>의 일급 컬렉션인 Players를 관리하도록 설계했다.
 

이 부분도 너무 좋네요. 라는 리뷰를 받은 내 기분이 너무 좋았다!

객체 생성의 이유를 더 중요시했다는 근거를 내세우고, 타입을 명시하도록 변경했다고 했더니 기분 좋은 피드백을 주셨다.
이렇게 나만의 기준을 세우고 이 기준을 리뷰어에게 이야기했을 때, 리뷰어가 그것을 수용하는 것이 기분 좋은 경험이라는 것을 느꼈다.
 
불변 객체도, 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다는 원칙도 결국에는 최소한의 장치일 뿐이다.
블랙잭 미션에서 이 외에도 얻은 것들이 많지만, 이것이 그중 가장 큰 깨달음이었다.

나만의 기준을 세우고 그 기준의 이유가 명확하다면 원칙들을 반드시 지켜야 하는 것은 아니다.
다만 이 원칙들은 협업을 위한 최소한의 장치이므로 최대한 지키려고 하되, 명확한 이유가 있다면 변경할 수 있다.

 

레벨1이 끝나갈 때쯤 남겨주신 파랑의 피드백..😭
내가 질문을 잘 이해하지 못한 것 같았을 때 따로 DM도 주시고, 깨달음을 주시는 리뷰를 해주셔서 너무 감사했는데 피드백도 좋게 남겨주셔서 감사했다.
덕분에 이후에 오브젝트 스터디를 하면서 상속과 다형성에 대해 다시 한번 생각해 볼 수 있는 기회도 가지게 되었다.
나 또한 레벨1에서 가장 기억에 남고 재미있게 진행했던 미션으로 기억될 것 같다!
 
1단계 PR: https://github.com/woowacourse/java-blackjack/pull/796

 

[1단계 - 블랙잭] 듀이(이주희) 미션 제출합니다. by ljhee92 · Pull Request #796 · woowacourse/java-blackjack

안녕하세요 파랑! 백엔드 7기 듀이입니다. 블랙잭 미션 리뷰 잘 부탁드립니다😄 체크 리스트 미션의 필수 요구사항을 모두 구현했나요? Gradle test를 실행했을 때, 모든 테스트가 정상적으로 통과

github.com

 
2단계 PR: https://github.com/woowacourse/java-blackjack/pull/865

 

[2단계 - 블랙잭 베팅] 듀이(이주희) 미션 제출합니다 by ljhee92 · Pull Request #865 · woowacourse/java-blackj

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

github.com

728x90