각종 후기/우아한테크코스

[우아한 테크코스 3기] LEVEL 1 회고 (30일차)

제이온 (Jayon) 2021. 3. 3.

안녕하세요? 제이온입니다.

 

오늘은 수업 없이 하루 종일 페어 프로그래밍을 진행하였습니다.

 

 

데일리 미팅

어제와는 다르게 오늘은 온라인으로 데일리 미팅을 진행하였습니다. 파피가 데일리 미팅 진행자였고, 어제 루터 회관에서 지낸 소감을 이야기하는 것이 말하기 주제였습니다. 하지만.. 저는 루터 회관에 아직 가지 못하였기 때문에 무슨 말을 해야하냐고 물어보았더니, 그냥 어제 뭐했고 가서 뭐할건지 등등 자유롭게 말하라는 답변을 들었습니다.

 

그래서 저는 저 빼고 크루들이 즐거워보여서 얼른 가고 싶다고 말하였고, 프로필 사진을 찍을 때의 포즈나 소품을 추천해 달라고 하였습니다. 여러 가지 답변이 있었는데, 그 중에서 눈에 띄었던 것이 '검'을 들고오라는 것이었고, 저는 검은 없고 야구 배트는 있다고 하니까 워니가 그거라도 들고오라고 하였습니다.

 

얼떨결에 야구 배트를 찾아 보겠다고 하였고, 창고에 예전에 쓰던 알루미늄 배트가 남아있었습니다. 어.. 그러면 저는 프로필 사진에 야구 선수를 빙의하는 것일까요? 아무튼 다음주 화요일에 사진을 찍어야하는데 조금 걱정이 됩니다.

 

 

페어 프로그래밍

데일리 미팅이 끝나자마자 조엘과 바다와 어제 하던 블랙잭 미션을 수행하였습니다. 어제까지 에이스 카드는 11로 우선 순위로 두고 카드의 합을 계산하기로 합의하였습니다. 이후, 오늘부터는 나머지 요구 사항을 하나 하나 해결해 나갔습니다.

 

오늘 처음 힘들었던 부분은 '에이스 카드를 고려한 카드의 합'입니다. 예를 들어, 'A, A'는 12이지만, 'A, A, Q'는 12이어야 합니다. 이처럼 에이스 카드는 상황에 따라 1이 될 수도 있고, 11이 될 수도 있는 유동적인 특징을 가졌습니다. 그만큼 게임의 재미를 더하지만, 반대로 구현하는 사람 입장에서는 빡셌습니다.

 

하지만, 21을 넘으면 게임에서 패배하는 조건이 있었기 때문에 에이스는 경우의 수가 별로 없었습니다. 예를 들어, 에이스 카드가 2장이면 (1, 11) 또는 (1, 1)만 존재하고, 에이스 카드가 3장이면 (1, 1, 11) 또는 (1, 1, 1)만 존재하고, 에이스 카드가 4장이면 (1, 1, 1, 11) 또는 (1, 1, 1, 1)만 존재하였습니다. 에이스 카드는 최대 4장까지 얻을 수 있기 때문에 위의 8가지 경우의 수만 잘 고려해 주면 해결되었습니다.

 

저는 가능한 에이스 카드 합의 경우의 수를 '(b - a) * [에이스 카드의 수] * a' 또는 '[에이스 카드의 수] * a'로 공식화 하였습니다. 여기서 a, b는 에이스가 될 수 있는 수이며, a < b이고 2b > 21 이어야 합니다.

 

물론, 이것은 알고리즘 상으로는 좋은 방식일 수 있으나 객체지향에는 어긋나는 코드였습니다. 가령 (b - a) 같은 경우는 매직 넘버가 되어서 상수 처리를 해야 하는데, 이를 Player 객체에서 에이스와 관련된 상수를 갖게 하는 것은 바람직하지 않다고 판단하였습니다. 이를 조엘이 지적해 주었고, 바다와 조엘이 같이 객체지향적인 코드로 바꿔 주었습니다.

 

 

    private List<Integer> calculateAceSum(int aceCount) {
        int oneNormalRestExtra = CardNumber.ACE.getValue();
        int allExtra = CardNumber.ACE.getExtraValue();

        for (int i = 1; i < aceCount; i++) {
            oneNormalRestExtra += CardNumber.ACE.getExtraValue();
            allExtra += CardNumber.ACE.getExtraValue();
        }

        return new ArrayList<>(Arrays.asList(oneNormalRestExtra, allExtra));
    }

 

 

여기서 CardNumber는 A, 2, 3, 4, ... , K, Q, J를 의미하고, value는 각각의 숫자를 뜻합니다. 이때, extraNumber가 존재하는데 이것은 에이스를 위한 것으로, A의 value는 11, extraValue는 1이 됩니다. (순서를 바꿔도 무방합니다.)

 

그리고 버스트가 발생하지 않는 선에서 가능한 모든 에이스 합의 경우의 수를 리스트로 반환하는 것입니다.

 

 

지금까지의 코드를 보면, Player와 Dealer의 중복된 코드가 상당히 많았습니다. 그래서 이를 상속 관계로 묶기로 결정하였습니다.

 

 

 

 

이 사진은 draw.io로 작성한 것인데, 객체의 특징을 정리하는 데 도움이 되었습니다. 객체가 갖고 있는 속성과 메소드를 한 눈에 알아볼 수 있었고, 중복된 부분을 처리하기 쉬웠습니다.

 

여기서, Dealer는 Player에게 상속받고 MAX_SUM_FOR_MORE_CARD 상수나 checkMoreCardAvailable() 메소드만 따로 정의해 주기로 하였습니다.

 

여기까지 도메인은 모두 구현하였습니다.

 

 

하지만, 입출력을 하고 도메인 연결하는 컨트롤러를 설계하는 과정에서 많은 어려움을 겪었습니다. 결과적으로는 오후 6시까지 어떻게든 기능 구현을 모두 하기는 하였습니다. 하지만, 중복된 코드가 많았고 상수도 제대로 처리하지 않았고, indent도 지키지 않았습니다. 그럼에도, 기능을 전부 구현해 놓고 리팩토링하는 것과 기능을 다 구현하지 못한 상태에서 리팩토링하는 것은 차이가 크기 때문에 일단 오늘은 비효율적인 코드를 무시하고 기능을 구현하는 데 힘썼습니다.

 

우선, 구현하면서 막막했던 부분을 말씀드리겠습니다.

 

 

 

 

위 사진처럼 프로그램이 작동해야하는데, 플레이어의 이름을 입력받고 딜러와 누구누구에게 카드 2장을 나누어주었다고 출력하는 것까지는 쉬웠습니다. 물론, 여기서도 한 가지 문제가 있었죠.

 

딜러는 이름이 "딜러"로 정해져있는데, 딜러는 플레이어에게 상속을 받으므로 플레이어 리스트 안에 딜러를 같이 껴 넣을지, 아니면 딜러 객체만 따로 정의할 지 정하는 것이 문제였습니다. 일단은 플레이어 리스트 안에 넣어 놓기로 하였습니다.

 

하지만, 바로 밑에 출력문에서 막혔습니다. 딜러 외에 나머지 플레이어는 'xx카드: '인 반면에, 딜러만 '딜러: '였기 때문이죠. 또한, 딜러는 플레이어와 달리 원하는 대로 카드를 뽑을 수도 없어서 부득이하게 딜러 객체를 따로 생성하기로 결정하였습니다.

 

그런데, 딜러는 따로 객체로 빼는 것과 동시에 반복된 코드가 생기기 시작하였습니다. 딜러나 플레이어의 카드를 나열하는 메소드의 경우, 분명 특정 대상의 카드를 나열하는 행위이지만, 메소드를 2개로 분리할 수 밖에 없었습니다. 이러한 문제로 컨트롤러 객체의 무수한 반복된 코드가 작성되었습니다.

 

그 외에는 indent 문제가 있었는데, 아래 코드가 대표적입니다.

 

 

     private void playerGameProgress(final List<Player> players, final CardDeck cardDeck) {
        for (Player player : players) {
            while (true) {
                String choice = InputView.askMoreCard(player.getName());
                if ("n".equals(choice)) {
                    OutputView.showPlayerCard(player.getName(), player.getMyCards());
                    break;
                }
                player.receiveCard(cardDeck.distribute());
                OutputView.showPlayerCard(player.getName(), player.getMyCards());

                if (player.isBust()) {
                    System.out.println("카드의 합이 21을 넘어, 게임에서 패배하였습니다.");
                    break;
                }
            }
        }
    }

 

 

위 함수는 특정 플레이어가 'n'을 입력할 때까지 계속 카드를 뽑거나, 카드의 합이 21이 넘어서 버스트될 때까지 카드를 뽑는 메소드입니다. 우리는 indent를 1로 지켜야하지만, 일단 이 고민은 내일로 미루고 우선 기능을 구현하였습니다.

 

정리하자면, 내일은 하루 종일 리팩토링을 해야하는 날로, 중복된 코드를 추상화하고 indent를 줄이는 것이 최우선 과제입니다. 하지만, 페어와 마음이 잘 맞는만큼 해결할 수 있다는 생각이 듭니다.

 

 

정리

오늘은 기능을 모두 구현하자는 목표를 가졌는데, 다행히 협업이 원활하게 진행되어서 달성할 수 있었습니다. 내일은 드디어 루터 회관에 가는 날입니다. 부디, 원하는 대로 리팩토링에 성공하고 즐겁게 크루들과 시간을 보낼 수 있기를 기원합니다.

댓글

추천 글