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

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

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

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

 

오늘은 처음으로 루터회관을 갔고, 그 안에서 하루 종일 페어 프로그래밍을 진행하였습니다.

 

 

데일리 미팅

루터 회관에서의 첫 오프라인 데일리 미팅이 진행되었습니다! 이번에는 모든 인원이 루터 회관으로 모였고, 데일리 미팅 진행자는 '마갸'였습니다. 오늘의 말하기 주제는 '페어 프로그래밍을 할 때, 온라인 방식과 오프라인 방식의 장단점'이었습니다.

 

하지만, 저는 오늘이 첫 오프라인이었기 때문에.. 거의 저격이 아닌가 싶은 주제로 말하기를 진행하였네요. 다들 대부분 온라인보다는 직접 소통이 가능한 오프라인을 선호하였고, 저는 오프라인은 안 해 봤기 때문에 온라인의 장점을 몇 가지 말해 보았습니다.

 

일단.. 잠을 많이 잘 수 있고, 통학 시간을 아껴서 생산적인(?) 일을 할 수 있습니다. 그리고 code with me가 자동 완성이 안 되는 등 불편한 점이 많은데, 전 어차피 자동 완성 키를 잘 몰라서 사실 크게 불편하지는 않았습니다.

 

그런데, 막상 오늘 오프라인해 보니까 무조건 대면 페어 프로그래밍이 훨씬 좋습니다. 앞으로도 매일 루터회관에 나올 수 있으면 좋겠습니다.

 

 

페어 프로그래밍 - 1

데일리 미팅 이후, 회의실을 잡아서 오후 12시 40분까지 블랙잭 미션 리팩토링을 진행하였습니다. 오전 동안 크게는 리팩토링을 하는 것이었는데, 레거시 코드를 보면서 명확하게 어디를 고쳐야할 지는 잘 모르겠어서 쉬워보이는 것부터 해결하기로 하였습니다.

 

먼저, 저번 포스팅에서 언급하였던 indent를 1로 맞추기로 하였습니다.

 

 

    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;
                }
            }
        }
    }

 

 

해당 코드는 플레이어에게 카드를 더 뽑을지 물어보고, 더 뽑는다면 카드를 주고, 이때 카드의 합이 21이 넘는지 판단하는 로직입니다. 위 코드는 indent가 3이어서 1로 줄이기 위하여 일단, 'while (true)' 구문을 통째로 하나의 메소드로 추출하였습니다.

 

그리고 'while (true)'의 종료 조건은 '사용자의 입력이 n' 또는 '카드의 합이 21 초과'입니다. 따라서, 무한 루프가 아니라 재귀 함수를 통하여 이를 해결할 수 있었습니다.

 

 

    private void singlePlayerGameProgress(final CardDeck cardDeck, final Player player) {
        if (END_GAME_MARK.equals(askPlayerMoreCard(player))) {
            OutputView.showPlayerCard(player.getName(), player.getPlayerCards());
            return;
        }
        player.receiveCard(cardDeck.distribute());
        OutputView.showPlayerCard(player.getName(), player.getPlayerCards());
        if (isBust(player)) {
            return;
        }
        singlePlayerGameProgress(cardDeck, player);
    }

 

 

위와 같이 종료 조건에만 return을 취하고, 그 이외에는 동일한 함수를 호출하면 됩니다. 하지만, 사실은 위 코드가 나오기 까지는 굉장히 오랜 시간이 걸렸습니다. 왜냐하면, 위 메소드의 초기 상태는 메소드 라인이 10줄을 초과하였기 때문이죠.

 

아무리 생각해도 잘 줄여지지가 않아서, 재귀 함수 대신 무한루프문을 유지한 채 리팩토링을 진행하였습니다. 각자 순서도를 그려서 나름대로 구현해 보았지만, 결국은 indent를 줄이자고, 혹은 그깟 메소드 라인 한 두 줄을 줄이겠다고, 가독성을 떨어뜨리면서 코드를 짜는 것은 좋지 않다고 판단되어 재귀 함수 코드로 롤백하였습니다.

 

하지만, 그 과정에서 얻어낸 성과가 있었습니다.

 

 

    private boolean isBust(final Player player) {
        if (player.isBust()) {
            OutputView.bustMessage();
            return true;
        }
        return false;
    }

 

 

바로, 위와 같이 조건 하나를 메소드로 추출해서 출력문도 실행해 주는 것입니다. 카드의 합이 21이 넘을 경우, 게임 오버 메시지를 띄워 주어야 하는데, 위 메소드를 통해서 기존 재귀 메소드의 라인이 줄어들 게 된 것이었죠. 이것은 조엘과 바다가 구현하는 것을 보고 영감을 얻었습니다.

 

그 외에 남은 오전 시간은 매직 넘버를 상수화 처리를 하고, 출력 라인을 깔끔하게 맞춰주는 작업을 하였습니다. 그리고 나서 점심 먹고 커피 내기로 우리가 구현한 블랙잭을 실행하였고, 아쉽게도 제가 게임에서 패배해버렸습니다 ㅋㅋㅋㅋ

 

그래서 조엘과 바다한테 아메리카노를 사 드리고 저는 달달한 망고 에이드를 마셨습니다. 아 그리고 지하 식당가에 곱창 순두부가 존맛인데, 이 포스팅 보시는 크루들은 꼭 가보시길 바랍니다.

 

 

페어 프로그래밍 - 2

완태와 로키의 테코톡 이후에 다시 블랙잭 리팩토링을 진행하였습니다. 이번에는 중복된 Dealer와 Player 코드를 수정하는 것과 비대해진 컨트롤러를 깔끔하게 만드는 것이 목적이었습니다.

 

하지만, 생각보다 굉장히 어려웠습니다. 우리는 컨트롤러의 역할을 '플레이어와 딜러 객체 생성', '게임 진행', '결과 출력'이었는데, 중간 중간 출력 로직이 끼어들어와서 마땅히 도메인으로 책임을 분배하기도 만만치 않았습니다. 각자 자기가 아는 크루에게 가서 컨트롤러에 대한 조언을 듣기로 하였는데, 막상 실행에 옮기기는 어려웠습니다.

 

결국 이러지도 저러지도 못한 채 시간만 계속 흐르게 되었습니다. 저는 남들이 사용하는 DTO라는 것을 도입하면 뭔가 구조가 깔끔해지지 않을까라는 생각으로 고민은 해 보았으나, DTO가 아직도 어색해서 사용하기는 버거웠습니다. 그때 조엘이 포비에게 도움을 요청하였고, 짧은 시간이지만 포비의 말씀이 저한테 굉장히 와닿았습니다.

 

먼저, 포비는 자신의 코드에 대해서 확신을 가지고 남 코드는 신경쓰지 말라고 조언해 주셨습니다. 우리 셋이 열심히 노력해서 만든 코드이므로 주눅들지 말고, 설계한 것에 있어서 자신감을 가지라고 것이죠.

 

두 번째는 남이 말하는 흔히 '잘해 보이는' 개념은 자기가 진정 필요로 할 때 사용하라는 것입니다. 그 예로 'DTO'를 말씀해 주셨는데, 만약 비즈니스 로직을 처리하다가 "여러 가지 메소드도 있고 상태가 변하는 객체를 인자로 넘기면 위험하지 않을까?"라는 생각이 들어서 어쩌보면 좋을지 고민할 때, 다른 크루가 말했던 DTO라는 개념을 도입해 보라는 내용이었습니다.

 

저는 그 이야기를 듣고, 이번 미션에서는 리뷰어 님의 1차 피드백 이후에 꼭 적용해 보기로 결정하였습니다. 왜냐하면, Player는 가변 객체이고, 그 객체를 비즈니스 로직에서 처리하기 때문이죠. 그리고 조엘이 간단하게 나마 DTO를 만들 때는 Player에서 불변값을 추출해서 DTO 객체를 생성한 후 getter를 이용하면 된다고 말해주었습니다. 포비와 조엘의 말씀을 잘 참고해 보아야겠습니다.

 

 

포비의 조언 이후에 우리는 비록 컨트롤러 자체는 길지만, 기능 요구 사항은 모두 만족하였으니 훌륭한 코드라고 자신감을 갖게 되었습니다. 또한, 바다가 Player 객체 내에 있는 myCards를 일급 컬렉션으로 묶을면 어떨지 의견을 제시해 주면서 리팩토링에 속도가 붙었습니다.

 

우선, 우리가 myCards를 일급 컬렉션으로 묶을 생각을 하였으나, 그 당시 Player 객체 자체가 name을 반환하는 것도 아니고 모두 카드와 관련된 계산을 하고 있었기 때문에, 일급 컬렉션을 사용하면 Player의 기능이 모두 그 쪽으로 위임되어서 별로 의미가 없다고 판단하였습니다. 하지만, 컨트롤러를 완성하는 과정에서 특정 플레이어가 딜러와의 승부에서 이겼는지, 졌는지 판단는 로직이 새로 생겼고, 이것 덕분에 myCards를 일급 컬렉션으로 묶는 것이 의미가 있어졌습니다!

 

또한, 이전부터 계속 말이 나온 김에 단순히 Dealer가 Player에게 상속을 받는 것이 아닌, Dealer와 Player가 Participants에게 상속을 받도록 구조를 바꿔 보았습니다. Dealer와 Player가 한결 깔끔해져서 가독성이 올라가는 효과를 느꼈습니다.

 

다만, Participants 객체는 실제로 컨트롤러에서는 사용하지 않는데, 리뷰어 님과의 대화를 통해서 추가해 나갈 예정입니다.

 

 

정리

많은 어려움이 있었지만, 그럼에도 기능을 모두 구현하였고 어느 정도 리팩토링을 한 것에 만족합니다. 3인 페어라서 소통에서 충돌이 있지 않을까 걱정했는데, 다들 의견을 잘 수용하고 상대방을 배려해 주는 태도를 보여주셔서 참 감사했습니다. 상대방의 의견에 대해 반대를 할 때도, 강하게 말하는 것이 아니라 부드럽고, 또 그 속에서 반대의 이유를 명확하게 제시해 주셔서 많은 지식을 배워갈 수 있었습니다. 페어는 끝이 난 것은 아쉬웠지만, 편하게 말할 수 있는 크루가 생겨서 기분이 좋습니다 ㅎㅎ

 

위 미션의 코드가 궁금하신 분은 이곳을 참고하시길 바랍니다.

댓글

추천 글