[우아한테크코스] 프리코스 4주차 회고

이호석·2022년 11월 22일
0
post-thumbnail

🚀 다리 건너기

4주차에 주어진 미션은 다리 건너기 미션입니다.
미션에 대한 규칙 및 구현 코드는 다음 저장소에 정리되어 있습니다.
https://github.com/HiiWee/java-bridge/tree/HiiWee

🔒 제약 사항

  • 미션은 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 세 가지로 구성되어 있다.
  • 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
  • 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.

📌 기존 요구 사항

📌 추가된 요구 사항

📌 클래스 요구 사항

  • InputView 클래스
    제공된 InputView 클래스를 활용해 구현해야 한다.
    InputView의 패키지는 변경할 수 있다.
    InputView의 메서드의 시그니처(인자, 이름)와 반환 타입은 변경할 수 있다.
    사용자 값 입력을 위해 필요한 메서드를 추가할 수 있다.

  • OutputView 클래스
    제공된 OutputView 클래스를 활용해 구현해야 한다.
    OutputView의 패키지는 변경할 수 있다.
    OutputView의 메서드의 이름은 변경할 수 없고, 인자와 반환 타입은 필요에 따라 추가하거나 변경할 수 있다.
    값 출력을 위해 필요한 메서드를 추가할 수 있다.

  • BridgeGame 클래스
    제공된 BridgeGame 클래스를 활용해 구현해야 한다.
    BridgeGame에 필드(인스턴스 변수)를 추가할 수 있다.
    BridgeGame의 패키지는 변경할 수 있다.
    BridgeGame의 메서드의 이름은 변경할 수 없고, 인자와 반환 타입은 필요에 따라 추가하거나 변경할 수 있다.
    게임 진행을 위해 필요한 메서드를 추가 하거나 변경할 수 있다.

  • BridgeMaker 클래스
    제공된 BridgeMaker 클래스를 활용해 구현해야 한다.
    BridgeMaker의 필드(인스턴스 변수)를 변경할 수 없다.
    BridgeMaker의 메서드의 시그니처(인자, 이름)와 반환 타입은 변경할 수 없다.

  • BridgeRandomNumberGenerator 클래스
    Random 값 추출은 제공된 bridge.BridgeRandomNumberGenerator의 generate()를 활용한다.
    BridgeRandomNumberGenerator, BridgeNumberGenerator 클래스의 코드는 변경할 수 없다.

🚀 미션을 시작하면서

❗️ 새로운 목표

1. 클래스(객체)를 분리하는 연습
2. 리팩토링

지난주차 미션에서 객체를 분리하는 요구사항이 불만족스러웠던건지? 혹은 정말 중요해서 한번 더 강조하고 싶으셨는지 동일한 목표와 리팩토링이라는 새로운 목표를 주었습니다.

구현 순서에 대한 고민

시간이 지날수록, 미션의 복잡도와 요구사항이 커질수록 들었던 고민이었습니다.
구현 순서에 대한 고민은 이번 미션에서 많은 시간을 잡아먹게한 요인이기도 합니다.

초기에는 사용자의 입력부분부터 구현하며 해당 입력에 맞는 도메인 로직들을 결정하고 구현했습니다. 하지만, 도메인 로직이 사용자의 입력에 따라 결정되는것이 맞나? 라는 생각이 들었고 메인이 되는 도메인 로직을 구현하고, 사용자의 입력을 그에 맞게 변환하는게 좋겠다는 생각으로 구현하기 시작했습니다.

많은 시간이 들었던건 도메인 로직에 맞게 사용자로 부터 받아온 입력을 변환해 저장하거나, 사용하게 되는데 이 부분에서 입력과 로직의 차이로 인한 비효율적인 상황이 발생하지 않을까 고민이 많았습니다.

예를들어 일급컬렉션을 적용하는 부분이 있었는데

List<String>타입을 Blocks라는 일급 컬렉션 객체를 적용했고, String 타입을 상태를 가진 Enum(BlockStatus)를 적용하여 저장소에 저장하기로 결정했었습니다.

이 과정에서 사용자의 다리 길이 입력을 받아서, List<String> → List<BlockStatus> → Blocks라는 일급 컬렉션 객체가 되기까지의 과정이 비효율적이지 않을까란 고민이 있었고, 실제 코드들을 미리 생각하거나, 도식화 하면서 검증하려 그것들을 어떻게 관리할지 고민하는 시간이 많았습니다.

결국 고민끝에 완성하고나니 내가 구현한 설계에 맞춰서 코드를 작성하는것도 능력이라는 생각이 들었습니다. 코드를 작성하지 않고 생각만으로는 왈가왈부하는것은 도움이 되지 않았으며, 결국 일단 구현하고 해당 구조를 조금더 효율적으로 혹은 아예 구조를 변경하는 리팩토링의 과정에 조금더 시간을 썼다면 더 만족스러운 코드를 만들 수 있지 않았을까라는 아쉬움이 남았습니다.

객체는 객체답게

로또 미션을 절반쯤 했을 때 객체에 메시지를 전달하는 것의 의미를 깨달아 그제야 적용하고자 했던 지난 미션과는 달리, 이번 미션에서는 처음부터 확실하게 객체에 메시지를 던져주자고 결심했습니다.

가장 먼저 각 객체를 파악하고 이들 사이의 관계가 무엇이 있을까를 파악하려 했습니다. 최초에는 BridgeMaker를 통해 정의된 List 블록을 그대로 사용하려 했지만, 조금 더 객체답게 사용하기 위해 Blocks라는 일급 컬렉션을 생성해 사용하기로 했습니다.

이를 통해 생성된 다리의 블록(칸)들을 비즈니스에 종속적인 자료구조로 이용할 수 있었고, 혹여나 발생될 수 있는 값의 변동도 막을 수 있었습니다.
임의로 생성된 다리의 칸들과는 별개로 현재 다리 건너기의 진행 상태를 알 수 있는 경우도 필요하기에 이를 일급 컬렉션을 통해 정의했습니다. (CrossStatuese)

이때쯤에 랜덤으로 정의된 다리의 '상태'와 현재 다리 건너기의 진행 '상태'에 대한 고민이 있었습니다.
이들은 어찌 됐든 하나의 상태를 나타내고 연관성이 있는 상수들이었고, 만약 후에 위, 아래뿐만 아니라 여러 가지의 다리를 건널 수 있는 방법이 추가될 수 있었고, 현재 진행 상태 또한 변경될 수 있는 여지가 있으므로 enum을 활용해 조금 더 유연하게 대처하고자 했습니다. 더하여 enum을 사용하게 되면 단순히 자료형일 때와 달리 해당 이름을 통해서도 의미를 전달할 수 있는 이점이 있었습니다.

(다리 각 칸들의 정답 위치를 저장)

(다리 각 칸들에 대해 사용자들이 선택한 결과들을 저장)

객체에 메시지를 보내게 되며 생긴 이점도 많았습니다.
로또 미션에서는 클래스의 분리를 조금 더 해야 하지 않았느냐는 아쉬움이 있었고, 다리 건너기에서는 그런 아쉬움과 요구사항을 만족하고자 조금 더 책임에 맞는 클래스들을 생성하게 됐습니다.
예를 들어 Blocks라는 랜덤 정의된 다리의 상태를 나타내는 일급 컬렉션과, CrossStatuses라는 현재 다리 건너기의 진행 상태를 나타내는 일급 컬렉션을 Bride라는 다리를 나타내는 클래스가 사용하게 됩니다.

랜덤 칸과 사용자의 건너기 상태를 저장하는 다리 클래스

다리에는 다리 중 한 칸을 건너겠다는 메시지, 혹은 현재까지 움직인 결과, 재사용을 위한 진행 상태 초기화 같은 메시지들이 전달됩니다. 그리고 Bridge 클래스는 각각 받아온 메시지들을 알맞은 일급 컬렉션에 전달해 실질적인 계산 로직들은 일급 컬렉션이 능동적으로 수행하게 하여 각 객체의 책임을 분리하고 메시지를 통해 상호작용할 수 있게 됐습니다.

과거였다면 순수한 List 타입을 이용해 해당 리스트 내에서 알맞은 값을 찾거나 계산하는 로직이 Bridge 클래스에 모두 포함되어 하나의 클래스가 너무 큰 책임을 갖게 코드를 만들었다면, 일급 컬렉션 및 객체와의 상호작용을 고민한 뒤부터는 해당 코드들이 많은 개선이 이루어지게 됐습니다.

이러한 도메인 로직들이 각 도메인에 종속되게 되면서 실제 서비스 로직은 정말 input과 output 데이터를 알맞은 곳에 보내주고, 반환하는 서비스적인 흐름만을 담당하게 되어 한눈에 봐도 가독성이 좋아짐을 느꼈습니다.

서비스 로직

리팩토링

우테코를 진행하면서 느낀 건 처음부터 완벽한 코드를 작성하려 하면 끝까지 완성하기가 너무 어렵다는 느낌이었습니다.

따라서 만약 내가 100퍼센트의 구현력을 가지고 있다면, 초기 코드에서는 70퍼센트의 역량을 보여주면서 일단 동작까지 가능하게 만드는 게 중요하고 만약 동작이 정상적이라면 그 후에 100퍼센트의 구현력을 위한 리팩토링이 조금 더 효율적이라고 생각하고 있습니다.

지난 과제들을 진행하면서 이런 리팩토링의 중요성은 여실히 느끼게 되었고, 볼 때마다 개선의 여지가 보이는 코드들을 많이 찾을 수 있었습니다.
또한 리팩토링의 검증을 단위테스트로 하라는 학습 목표를 보며 내가 그래도 잘 해오고 있었다는 생각이 들어 기분이 좋기도 했습니다 ㅎㅎ

리팩토링에 대한 막막함보다는 언제 리팩토링을 해야 하느냐는 고민이 많았습니다. 예를 들어 구현해야 하는 특정 기능이 존재하고 해당 기능은 A, B, C의 상황으로 나뉘어 작성할 수 있게 된다면, A를 구현하고 리팩토링하고 검증하고, B를 구현하고 리팩토링하고 검증하는 방식으로 하나의 기능에 대해 여러 단계를 나누어 리팩토링 해야 하는지 혹은 특정 기능에 대한 A, B, C를 전부 구현하고 리팩토링 해야 하는지에서의 고민이 많았습니다.

미션을 진행 초기에는 단순히 아쉬운 점이 발견되면 즉시 리팩토링을 진행했습니다. 이는 내가 생각한 고민을 즉시 반영할 수 있다는 장점이 있었지만, 기능을 구현하는데 몰입이 조금 떨어지는 것 같았습니다.

개인적으로 위에서 제시한 두 상황은 어느 정도의 trade-off라고 판단해 결국 나만의 기준이 필요하다고 느꼈습니다.

과제를 진행하면서 효율적이라고 느껴졌던 리팩토링은 하나의 기능이 완성된 이후였습니다.
기능이 완성되기 전에 진행한 리팩토링들은 실제 기능을 구현했을 때, 예상과는 다르게 동작하는 경우도 있었습니다.
많은 변경 사항이 또다시 발생하는 경우도 있었고, 결국 도돌이표처럼 다시 리팩토링하는 경험을 겪으며 확실한 피드백을 받을 수 있는 상황일 때 리팩토링 해야 한다고 느꼈습니다.

마치면서

4주였지만 정말 짧게 느껴진 프리코스 였습니다.
남은 시간들은 학업과 프로젝트 그리고 프리코스 연습을 하면서 최종 결과를 기다려보려고 합니다

될 수 있으면 꼭 최종코테까지 볼 수 있으면 좋겠습니다!

profile
꾸준함이 주는 변화를 믿습니다.

1개의 댓글

comment-user-thumbnail
2022년 11월 28일

좋은 결과 있을거야 ♥ 호석이의 노력과 진심이라면 앞으로도 무엇이든 해낼 수 있을거야 !! 응원하구 존경해 ♥ ♥

답글 달기