Coderoad

우아한테크코스 레벨1 - 블랙잭

2024-03-24 at 우아한테크코스 category

블랙잭 - Clean Code

사다리 타기 미션의 여운이 채 가시기도 전인 3월 초, 새로운 미션의 날이 밝았습니다. 아는 사람들도 많고 꽤 유명한 카드 게임인 블랙잭을 구현하는 것이 이번 미션의 목표였습니다. 물론, 저는 블랙잭을 플레이 해본 적도, 룰을 잘 알고 있는 것도 아니었어서 조금 걱정됐습니다.

1단계 - 블랙잭 게임 실행

첫 단계는 블랙잭 게임을 실행하는 것이 핵심 목표였습니다. 물론, 제목만 블랙잭 게임 실행이지, 블랙잭의 거의 모든 핵심 비즈니스 로직을 구현해야 했습니다. 이번 미션의 페어였던 안나와 저는 개발 스타일이 달라 1단계 구현에 어려움을 조금 겪었습니다. 구현 방법에 대해 고민하는 것 뿐만 아니라, 서로의 코드 스타일을 이해하는데에도 시간이 꽤 많이 소모됐습니다. 거기다 블랙잭의 규칙은 사다리 타기와는 비교도 할 수 없게 복잡해 생각해야할 것도 많았습니다.

그러다보니 이번 미션의 핵심 주제가 Clean Code였지만, 안나와 제가 작성한 코드는 클린 코드와는 거리가 먼 '돌아가는 쓰레기'였습니다. 메소드의 indent는 최대 4개까지 생겼고, 역할과 책임은 제대로 부여되지도 않았습니다. 당연하지만 다른 무엇보다도 제출 기한에 맞춰는게 우선이었기 때문에 어쩔 수 없는 부분이었습니다. 그래도, 리팩토링을 진행하면서 어느 정도 깔끔한 코드가 만들어졌다고 생각했습니다. 언제나 그렇듯 제 생각일 뿐이었지만요. 😢

블랙잭 미션의 리뷰어는 현구막이었습니다. 앞선 미션들에서 현구막에게 리뷰를 받아본 주변 크루들에게 현구막이 엄청 정성스러운 리뷰를 해주신다는 이야기를 들었습니다. 저도 많은 리뷰와 피드백을 주고 받을 생각에 조금 기대하고 코드 리뷰를 요청했습니다. 내심 깔끔하게 리팩토링까지 성공했다고 생각하고 요청한 코드 리뷰였지만, 돌아온 것은 소문대로 엄청나게 정성스러웠던 현구막의 '고봉밥' 리뷰였습니다.

blackjack-01
감사합니다, 현구막!

처음 리뷰를 받고 나서 든 생각은 아쉬움보다도 기쁨이었습니다. 현구막의 리뷰를 보면서 정말 많은 것을 배울 수 있었습니다. 요구사항은 결국 가독성을 위함인 것, DTO 내부에는 로직을 두지 않아야 한다는 점, Enum은 수단일 뿐인 점, Controller에 비즈니스 로직을 두지 않아야 하는 이유, 클린 코드의 기준 등, 정말 많은 부분에서 놓친 점들을 다시 상기할 수 있었습니다.

물론, 리뷰와 피드백을 주고 받는 과정은 조금 힘들었습니다. 제가 MVC 패턴을 제대로 이해하지 못하고 구현하다보니 1단계 미션 merge까지도 현구막이 지적한 부분을 올바르게 리팩토링하지 못했습니다. 그래도 배운 것들을 바탕으로 2단계 미션은 잘해내겠다는 다짐을 가지고 2단계 미션 구현을 시작했습니다.

2단계 - 블랙잭 베팅

2단계 구현에 앞서서 깊게 고민해본 것이 있습니다. "Clean Code란 무엇인가?" 그 동안 저는 이 질문에 명쾌하게 답변할 수 없었습니다. 현구막 역시 제가 클린 코드의 기준을 명확하게 세우지 않았다는 것을 눈치챘는지 저의 클린 코드의 기준이 무엇인지 물어보기도 했습니다. 그래서 꽤 오랜 시간을 고민해봤고, 제가 세운 저의 클린 코드 기준은 다음과 같습니다.

blackjack-02
그래도 가장 우선되는 기준은 "읽기 쉬운 코드"

위의 상세한 기준들은 결국 제가 처음부터 끝까지 추상적으로 가지고 있던 클린 코드의 기준인 "읽기 쉬운 코드"를 위한 기준들입니다. 클린 코드를 추구하는 이유는 결국 다른 개발자들과의 협업을 위함입니다. 내가 쓴 코드를 동료가 읽고, 동료가 쓴 코드를 내가 읽는 상황이 필연적으로 찾아올텐데, 그 코드가 읽기 쉬운 코드가 아니라면 능률이 떨어지고, 같이 일하기 싫어질 수도 있습니다.

그래서 객체지향 생활체조와 같은 여러 코드 컨벤션들이 생기고 디자인 패턴이 탄생한다고 생각합니다. 우리가 수학에서 여러 개념을 통일하자고 약속하듯, 나와 동료의 코드 사이에 약속을 만드는 것입니다. 약속이 생기면 약속만 잘 알고 있어도 이해할 수 있는 범위가 차원이 달라지니까요.

2단계 구현 자체는 그렇게 어렵지 않았습니다. 한 두개 정도의 도메인만 추가됐고, 기존의 코드가 거의 수정되지 않았습니다. 리뷰의 개수에서부터 알 수 있는데, 리뷰와 피드백 과정에서 46개의 대화가 오고 간 1단계와 다르게 이번 단계에서는 10개의 대화에 그쳤습니다.

물론 짧다면 짧을 이 과정에서도 또 배워가는 것이 있었습니다. 앞서서 언급한 클린 코드를 위한 약속의 일종인 디자인 패턴의 중요성과 모든 것을 예측할 수 없으니 현재 상황에 가장 어울리는 구조로 코드를 작성하라는 점을 배울 수 있었습니다.

생각해볼 것들

이번 주 미션에서 학습한 내용에 대한 우아한테크코스의 질문과, 제 답변을 간략하게 정리해봤습니다.

블랙잭을 구현하며 클린 코드를 작성하기 위해 어떠한 것을 하였는가?

사실 부끄럽지만 블랙잭을 구현하는 동안 클린 코드로 작성하기 위해 따로 고민한 부분은 없었던 것 같습니다. 특히 1단계에서는 일단 동작하는 프로그램을 만드려고 바쁘게 코드를 작성하다보니 객체지향 생활체조, 좋은 객체를 위한 7가지 덕목와 같은 컨벤션은 커녕 기본적인 요구사항도 준수하지 못한 코드가 완성되었습니다.

그래도 현구막과 리뷰와 피드백을 주고 받으며 열심히 리팩토링했고, 요구사항도 준수하고 여러 컨벤션을 적용하는데 성공했습니다. 시간을 들여 천천히 구현해보니 제 클린 코드의 기준인 "읽기 쉬운 코드"가 완성된 것입니다. 정작, 완성에 급급했던 1단계 구현 당시에는 왜 진작 이렇게 하지 못했는지 아쉽기도 했습니다.

본인이 정한 클린 코드의 기준은 무엇인가? 그 기준을 지키기 위해 노력한 부분은 무엇인가?

계속 이야기 했지만 제 클린 코드의 기준은 "읽기 쉬운 코드"입니다. 다른 무엇보다도 코드는 읽기 쉬어야 합니다. 함께 협업하는 동료를 위해서가 아니라 나를 위해서라도 읽기 쉬어야 유지보수성도 높아지고, 확장성도 높아집니다. 협업을 비단 다른 개발자와의 협업만 생각하기 쉽지만, 미래의 나와의 협업도 놓쳐서는 안됩니다. 결국, 코드를 유지보수하다보면 먼 미래에 내가 작성한 코드를 내가 읽어야 합니다.

과거의 내가 지금의 나와의 협업을 신경쓰지 않고 일단 돌아가는 코드를 작성하고 만족해버렸다면 그로 인한 손해는 고스란히 제게 돌아옵니다. 따라서 저는 무슨 일이 있어도 읽기 쉬운 코드를 작성하는 것을 최우선 목표로 삼으려고 노력합니다. 메소드 이름이 길어져도 절대 축약해서 작성하지 않는 것부터, 코드를 읽는 것이 책을 읽는 것처럼 자연스럽게 읽히도록 매개변수도 메소드 이름의 일부분으로 활용하기도 했습니다.

아래는 그 예시 코드입니다.

public boolean hasLessCardThan(final Player player) {
    return getHandSize() <= player.getHandSize();
}

public boolean hasMoreCardThan(final Player player) {
    return getHandSize() > player.getHandSize();
}

제 기준에서 이렇게 작성하면 메소드 시그니처만 읽어도 해당 메소드가 무슨 일을 하는지 알기 쉽습니다. 이것이 제 클린 코드의 기준이고, 이를 지키기 위한 노력의 일부분입니다.

피드백을 받으며 리팩토링하는 과정에서 어려움을 느낀 부분이 있는가? 그 문제를 어떠한 방식으로 해결했는가?

처음에는 막연하게 세운 클린 코드의 기준인 "읽기 쉬운 코드"에만 집중하다가 리팩토링 과정에서 조금 어려움을 겪었습니다. 상세한 기준이 없으니 당연히 읽기 쉬우면 전부라는 생각을 하게 됐고, 전체적인 코드의 구조는 잘 신경쓰지 않고 구현했습니다. 피드백을 통해 리팩토링하면서도 코드 부분 부분이 읽기 쉬우니 된 것이라고 생각했습니다. 그러나, 현구막과 대화하면서 진짜 읽기 쉬운 코드는 결국 전체적으로 이해하기 쉬운 코드라는 것을 깨달았습니다.

그래서 2단계를 구현하기 전에 우선 저만의 클린 코드 기준을 상세하게 세우고, 이를 기반으로 구현을 시작했습니다. (자세한 기준은 위의 사진에 있습니다!) 그 덕분인지 확실히 피드백도 1단계에 비해 많이 줄었습니다. 또한, 제대로 학습하지 못하고 그냥 활용했던 개념들이 많았던 탓에 리팩토링 과정이 순탄치 않았던 것도 있었습니다. 그래서 앞으론 어떤 개념을 활용하기 앞서, 해당 개념을 확실히 학습하고 활용하고자 합니다.

해당 미션에서 작성한 본인의 코드가 만족스러운가? 다음 미션에선 어떠한 목표로 코드를 작성할 예정인가?

저는 항상 제 코드에 완전히 만족하지 않습니다. 아무리 잘 짠 코드라고 하더라도 누군가에는 읽기 어려울 수 있습니다. 만약, 미래의 제 동료가 지금의 블랙잭 코드를 이해하기 어렵다고 말한다면 리팩토링 해야 한다고 생각합니다. 그래서 저는 항상 제 코드에 대해 비판적인 자세를 가지고 언제든 바뀔 수 있다고 생각하려고 합니다.

그렇다고 해서 제 코드에 애정이 없는 것은 아닙니다. 언제든 더 나아질 수 있는, 무궁무진한 가능성을 가졌다고 생각하려고 하는 것입니다. 제가 더 성장할 수 있다면, 제 코드도 그럴테니까요!

공부할 개념들

  • 상태 패턴을 비롯한 디자인 패턴
  • 클린 코드의 기준
  • 객체지향 생활체조
  • 함수형 인터페이스
hangillee

Personal blog by hangillee.

Road to good developer.