세 번째 미션, 블랙잭
블랙잭의 난이도는 그리 어렵지 않았다. 오히려 저번 주차인 출석 미션이 훨씬 어렵다는 개인적인 생각이 있다.

다만 딜러와 플레이어의 기능이 매우 비슷했고, 이에 대한 중복 코드를 제거해야 하는 조건이 있었다. 나는 중복을 줄이기 위해 상속을 사용했다.
public abstract class Participant {
private static final int BURST_UPPER_BOUND = 21;
private final String name;
private final List<Card> cards;
protected Participant(String name) {
this.name = name;
this.cards = new ArrayList<>();
}
public class Dealer extends Participant {
private static final String DEALER_NAME = "딜러";
private static final int PICK_DECISION_VALUE = 16;
public Dealer() {
super(DEALER_NAME);
}
public class Player extends Participant {
private static final int PICK_DECISION_VALUE = 21;
public Player(String name) {
super(name);
}
위와 같은 구조로 상속을 사용해 중복 코드를 삭제해 봤다.
내가 생각했을 때 중복 코드를 줄이기 위해서는 상속밖에 생각이 안 났었는데, 다른 크루들이 구현한 걸 보니까 조합이라는 방식을 사용해 구현한 크루들도 많이 보였다.
또 이후에 스터디를 진행하면서 배운 방법인데, 인터페이스를 먼저 만들고 추상 골격 구현 클래스를 만들어 인터페이스로 구현하는 방법도 있다는 것을 알았다.(이후에 따로 포스팅할 생각이다)
미션의 구현은 생각보다 어렵지 않아 딱히 이야기할 것이 없고, 크루들이 어떤 식으로 중복 코드를 줄였는지에 대해 이야기를 나눠봤었다. 자연스럽게 조합에 대해 학습하면서 추상클래스를 사용했을 때는 어떤 장점이 있고, 조합을 사용했을 때는 어떤 장점이 있는지 자연스럽게 알게 되었다. 조합이라는 방식은 처음 듣는 방식이었는데 내가 생각해보지 못한 관점이었어서 신기했다.
이번 미션의 페어는 후유였다.
나는 리팩토링에 강점이 있었고, 후유는 비즈니스 로직 구현에 강점을 가지고 있었다. 예를 들어 A 카드는 1 or 11로 계산될 수 있는데 이런 경우의 수에 대한 구현을 후유가 담당하고 나에게 설명해 주었다. 알고리즘 고수답게 구현능력이 진짜 뛰어났다. 서로의 단점을 보완해주며 페어 프로그래밍을 진행했던 것 같다. 나 같은 경우에는 리팩토링을 진행하며 validate의 책임과 일급컬렉션에 대해 후유에게 설명했었다. 내가 알고 있는 개념을 확실하게 설명해주었고, 후유가 잘 이해해 줘서 뿌듯했다ㅋ
후유는 집이 멀었고, 휴식을 중요하게 생각하는 크루였다. 그런 모습에 한 편으로는 조바심을 느꼈다. 나는 우테코 생활을 하며 집에 늦게 가는 게 습관이 되어있었고, 오래 남아있어야만 많은 걸 배워갈 수 있다고 생각했다. 함께 조금 더 구현을 하고 수정을 하고 싶었지만 후유의 입장을 고려해야 했다. 나 혼자서 더 하고 싶다고 더 할 수 있는 상황이 아니었다. 후유는 집이 멀기에 일찍 가야 하는 걸 존중해줘야 했고, 내 의견을 강요할 수 없었다.
후유의 휴식을 보장해 주기 위해서 보다 집중해서 코딩을 했던 것 같다. 하지만 중간중간 휴식을 할 때마다 조바심이 많이 났다. 개인적으로 목표로 하는 코드 퀄리티가 있었고, 이를 지키고 싶었다. 어떻게 하면 후유의 휴식을 보장해 주면서 제출 시점에 목표로 하는 퀄리티를 갖출 수 있을까를 생각해 봤고, 이를 이루기 위해 쉬면서 구현에 대해 지속적으로 이야기를 나눴다. 같이 블랙잭 게임을 하면서 도메인에 대해 공부를 하며 예외 상황을 체크하며 어떤 방식으로 구현해야 할지 이야기를 나눴다. 후유 또한 이런 이야기에 대해 부담감을 느끼지 않았고, 오히려 열정적으로 같이 논의해주며 좋아했던 것 같다. 이후에 이야기를 나는 것을 토대로 구현을 시작하니 시간이 훨씬 단축되는 걸 느낄 수 있었다. 그 결과 구현도 넉넉한 시간을 남기고 마무리했고, 리팩토링까지 진행해 내가 원하는 코드 퀄리티를 갖춘 상태로 미션을 맞출 수 있었다.
나는 지난 번 출석 미션 페어 중 길을 잃었었고, 레오에게 휴식을 요구했다. 이후에 다시 집중할 수 있었다. 후유와 페어를 진행하며 오래 앉아있는 것 만이 정답은 아니라는 걸 알게 되었다. 오히려 적당한 휴식은 집중하는 데에 있어 중요하다. 후유 덕분에 오래 앉아있는 것에 대한 집착을 조금 떨쳐낸 것 같아 고맙다. 휴식은 중요하다!
페어 프로그래밍은 뭐랄까... 배우는 점이 많은 것 같다. 나와 비슷한 생각을 가진 크루도 있고, 다른 생각을 가진 크루도 있다. 그 과정에서 원활하게 협업을 하기 위해 상대방을 어떻게 대해야 하는지, 어떻게 상대방을 배려해야 하는지, 또 어디까지 내 의견을 주장해야 할지가 참 어렵다. 그래도 상대방을 존중하는 나만의 방법이 조금씩 생기는 것 같다. 이 경험을 기반으로 나중에 팀 프로젝트를 잘해봐야겠다는 생각이 든다...!
우테코를 시작한 지 벌써 한 달
우테코를 시작한 지 벌써 한 달이라는 시간이 지났다. 한 달 동안 가장 많이 바뀐 부분은 생각하는 힘이 길러졌다고 생각한다. 시스템 자체가 왜 이런 코드를 작성했고, 어떤 코드를 의도했는지를 본인이 설명할 수 있어야 한다.
첫 번째 미션인 로또에서는 라라에게 서비스 계층의 도입에 대한 피드백을 받았었다.

그리고 이번 미션인 블랙잭에서는 엘리에게 Controller에 대해 생각해 볼 수 있는 피드백을 받았다.

Controller의 역할은 무엇일까? 나는 왜 Controller를 도입했을까?
한 번도 생각해 보지 못했다. 그냥 습관처럼 사용했었다.
Controller를 사용해도 된다. 하지만, 모든 코드에는 명확한 이유가 있어야 한다. 내가 생각하는 controller의 역할은 view와 domain의 중간다리라고 생각한다. 하지만 나의 controller에서는 중간 다리 역할이 아닌, 비즈니스 로직 또한 수행하고 있었다.
private void receiveCardProcessOfParticipants(Participants participants, CardDeck cardDeck) {
for (Participant participant : participants.getParticipants()) {
participant.addCard(cardDeck.getAndRemoveFrontCard());
participant.addCard(cardDeck.getAndRemoveFrontCard());
}
outputView.printInitialParticipantHands(participants.getParticipants());
}
위와 같은 코드가 controller에 존재했다. 내가 생각한 controller의 책임은 단순히 view에서 입력을 받아 domain으로 값을 전달해 주고, 이후에 결괏값을 받아 view에 출력해 주는 그런 중간다리 역할을 해야 하는 녀석이다.
하지만 비즈니스 로직 또한 수행하고 있어 controller의 책임이 너무 과하다는 생각이 들게 되었고, 엘리의 피드백에 대한 의미를 알게 되었다. Controller의 책임을 보다 명확히 하기 위해서 BlackJackGame이라는 객체를 하나 만들고, 해당 객체에서 모든 게임을 관리하기로 결정했다. 위와 같이 피드백을 진행하니 controller에서는 비즈니스 로직 없이, 중간 다리 역할만 진행할 수 있었다.
private static void extraCardProcessOnPlayer(Participant participant,
BlackJackGame blackJackGame) {
while (blackJackGame.canExtraReceiveOfPlayer(participant)) {
String userOpinion = inputView.inputPlayerWantMoreCard(participant);
if (userOpinion.equalsIgnoreCase("y")) {
blackJackGame.receiveExtraCardProcessOfPlayer(participant);
}
outputView.printParticipantNameAndCard(participant);
if (userOpinion.equalsIgnoreCase("n")) {
break;
}
}
}
위와 같이 controller의 책임을 보다 명확히 수정하니 controller의 역할이 정확히 어떤 것인지 알 수 있었다.
나아가 config를 통한 의존성 주입 + controller의 책임을 모두 Application에서 수행하니 Application의 책임이 너무 늘어난 거 아닐까? 이 책임을 분리하기 위해 Controller를 도입하고, 의존성 관리는 config에서 진행하면 좋지 않을까?라는 생각이 들었다.
이전에는 생각 없이 config, controller를 사용했지만, 지금은 보다 명확한 이유와 근거를 가지고 도입할 수 있게 되었다. 내가 왜 이런 구조로 설계했는지 자신 있게 설명할 수 있다.

비효율적인 코드를 작성할 수 있다. 하지만 내가 왜 그런 코드를 작성했는지에 대한 명확한 의도가 있어야 한다. 그래야 팀원을 설득할 수 있고, 내 의견에 대해서 피드백을 받으며 성장해 나갈 수 있다. 우테코는 이런 시스템에 최적화되어있는 것 같다. 앞으로도 의식적으로 내 코드에 의도를 담으려고 노력해 봐야겠다.
'우아한테크코스' 카테고리의 다른 글
| 우테코 Level1 회고록 (4) | 2025.04.07 |
|---|---|
| 유연성 강화 스터디 회고록 (0) | 2025.04.06 |
| 우테코 Level1 미션2(출석) 회고 (1) | 2025.03.06 |
| 우테코 Level1 1주차 회고 (5) | 2025.02.16 |
| 우테코[BE 7기] 최종 합격 후기 (22) | 2024.12.28 |