목표와 회고

우테코 프론트엔드 5기 - 프리코스 4주 차 후기

Creative_Lee 2022. 11. 22. 16:35

프리코스의 막이 내렸다. 말로 표현할 수 없는 감정이 오간다.
지금 이 감정에 대한 이야기는 글 마지막에 하도록 하겠다.

이번 후기에는 미션으로 성장했던 부분을 설명하고,
마무리로는 앞으로 프로그래밍을 공부하며 내가 가져야 할 태도와 자세에 대해 적어 보겠다.

🔧 3주 차 공통 피드백 수용

4주 차 미션 시작에 앞서 3주 차 공통 피드백을 점검했다.

객체의 상태 접근을 제한한다
객체는 객체스럽게 사용한다. / 데이터를 꺼내지 말고 메시지를 던지도록 구현한다.
필드의 수를 줄이기 위해 노력한다.

피드백의 worst 코드가 내 코드와 정확히 일치해서.. 뜨끔했다..

객체를 분리했다면 getter메서드로 데이터를 꺼낼 것이 아니라, 분리한 이유에 맞는 기능이 구현되어 있어야 한다는 뜻으로 이해했다.

(위 내용은 이번 미션에서 중요한 역할을 했다... 뒤에 이어진다.) 

private 필드의 필요성도 피드백을 통해 알게 되어 따로 공부했다.

 

성공하는 케이스뿐만 아니라 예외에 대한 케이스도 테스트한다.
테스트 코드도 코드다. 리팩터링을 통해 개선해나가야 한다. 
테스트를 위한 코드는 구현 코드에서 분리되어야 한다

테스트 코드 역시 코드라는 말에 한 방 맞은 것 같았다... 

이전 미션까지의 테스트 코드를 돌아보며 메인 코드를 신경 써서 작성하는 것처럼
테스트 코드 역시 가독성과 의미 전달에 신경 써야겠다고 생각했다.


📃 미션 레포지토리 링크

이번 후기는 게임을 구현하는 과정에서의 코드 변화를 중심으로 이어지기 때문에,

지원자분들 외의 분들의 내용 이해를 위해 다리 건너기 미션 repository를 첨부한다.

오징어 게임의 다리 건너기를 생각하면 되겠다.


🙆‍♂️🙅‍♂️  4주 차 미션 -  다리 건너기 게임

4주 차 미션은 다리 건너기 게임을 구현하는 과제였다.
이번 미션에서는 공통 피드백에 해당하는 문제를 직접 맞닥뜨리고
피드백을 적용해 개선하는 과정에서 엄청난 성장을 할 수 있었다.

 

  • 객체는 객체스럽게 사용한다. / 데이터를 꺼내지 말고 메시지를 던지도록 구현한다.
  •  테스트를 통과하기 위해 구현 코드를 변경하거나 테스트에서만 사용되는 로직을 만들지 않는다.
  • 단위 테스트 하기 어려운 코드를 단위 테스트 가능하도록 리팩터링한다. ( + 구체적 예시 )

💫 데이터를 꺼내지 말고 메시지를 던져라!

공통 피드백 중 getter 메서드를 사용하지 않고, 객체에 메시지를 던져 객체가 일하도록 하라는 내용이 있었다.

관련 예시와 자료들을 찾아보며 공부했고, 어느 정도 이해했다고 생각하여 구현을 시작했다.

하지만 어느 순간 피드백 내용의 worst 케이스에 해당하는 코드를 또다시 작성하고 있음을 깨달았다.🤔

이후 해당 코드를 개선하는 과정을 겪으면서,  피드백이 무엇을 말하고자 하는 것인지 온전히 이해할 수 있었다.


⌛ 코드 작성 당시 상황 설명 1

다리에 해당하는 Bridge 클래스를 만들어 다리 생성 기능을 구현한 뒤에 상황이다.

다리를 만들었으니 다리를 건너는 기능을 구현해야 했다.

이를 위해 다리의 방향과 사용자가 입력한 방향을 비교하는 로직이 필요했다.


😅 개선 전 코드의 문제점

Bridge 클래스에 저장된 다리 방향

다리의 방향을 꺼내기 위해 getter 메서드를 작성했었다.

 

BridgeGame 클래스의 move 메서드

그리고 BridgeGame 클래스의 move 메서드 내부에서

getter를 사용해 다리의 방향을 그대로 가져온다...

 

이후 현재 위치에 해당하는 다리의 방향이 사용자 방향 입력과 일치하는지 비교한다.

 

위 코드의 문제점은 Bridge 클래스가 아무 기능도 하지 않는다는 것이다.

단지 다리 방향을 저장할 뿐 클래스로 나눈 의미가 없었다.

또한 private 처리한 다리 방향 상태를 그대로 꺼내어 사용하여 private의 의미도 사라졌다.

 

이를 개선하기 위해 다리의 방향과 사용자 방향 입력을 비교하는 로직을 Bridge 클래스 내부로 넣어보았다.


😀 개선 후 코드

Bridge 클래스에 방향값을 전달받아 비교를 수행하는 메서드 생성

Bridge 클래스에 사용자 방향과 현재 위치 값을 전달받아 방향 값의 비교를 수행하는 메서드를 생성했다!

다리 방향 상태 값을 가진 Bridge 클래스에게 메시지를 던져 자신의 상태 값에 관련된 일을 하도록 개선된것이다.

상태 값을 꺼내지 않고도 비교가 가능해졌고, getter 메서드는 더 이상 필요가 없어 삭제했다.😁


⌛ 코드 작성 당시 상황 설명 2

다리를 건넌 후, 게임 상태에 따라 여러 가지 흐름으로 게임을 진행시켜야 했다.

그중 게임을 클리어했다면 게임을 종료 시켜야 했고,

이를 위해 게임의 클리어 여부를 판단하는 로직이 필요했다.


😅 개선 전 코드의 문제점

다리의 길이를 꺼내기 위해 getter 메서드를 작성했었다.

명확한 상황 설명을 위해 뒷 로직은 포함하지 않았습니다.

그리고 BridgeGame 클래스의 isEnd 메서드 내부에서

getter를 사용해 다리의 길이를 그대로 가져온다.

 

이후 지나온 블록 수와 다리 길이를 비교해 모든 블록을 지나왔는지 결과를 낸다.

위 코드 역시 동일한 문제점이 있다.

 

지나온 블록 수와 다리 길이를 비교하는 로직을 Bridge 클래스 내부로 넣어보았다.


😀 개선 후 코드

Bridge 클래스에 지나온 블록 수를 받아 다리길이와 비교하는 메서드 생성

Bridge 클래스에 지나온 블록 수를 받아 다리길이와 비교하는 메서드를 생성했다!

Bridge 클래스가 자신의 상태 값에 대한 로직을 수행하도록 개선되었다.

이 역시 상태 값을 가져오는 getter는 필요하지 않아 삭제했다.😁


💫 테스트 코드 통과만을 위해 구현 코드를 변경하지 않기!

이 피드백 또한 테스트 코드 작성에 난항을 겪으며 이것저것 시도하고 나서야  비로소 이해할 수 있게 되었다.
테스트 편의를 위한 클래스를 생성하여 사용하다가 결국 근본 원인을 찾게 되어 삭제했던 과정을 소개한다.


😅 테스트 편의를 위한  Builder 클래스

우선 결론부터 말하자면 클래스 생성 - 삭제 - 생성 - 삭제,
총 4번 생각의 변화가 있었다...
😥

 


😅 Builder 클래스를 생성한 이유

처음 다리를 생성하는 부분의 코드는 다음과 같았다.

BridgeGame 클래스의 build메서드로 다리 생성

BridgeGame 클래스의 build 매서드 내에서

미션에서 주어진 다리 생성 함수 makeBridge 를 통해 Bridge 클래스를 초기화하여 필드 변수에 저장하는 구조였다.

 

구현 이후 테스트 코드를 작성하려는데, 이게 웬걸... build 메서드는 테스트 자체가 불가능했다!

size 값을 전달하여 메서드를 실행시키면 접근할 수 없는 필드 변수에 생성된 다리를 저장하기 때문에
테스트 할 방법이 없었다.


나는 테스트 문제를 해결함과 동시에 다리를 짓는 건축자라는 표현이 있으면

코드도 부드럽게 읽히지 않을까 하는 생각으로 Builder 클래스를 만들었다. 


😅 Builder 클래스로 상황이 개선되었다는 착각

Builder 클래스

위와 같이 Builder 클래스를 만들고, 
메서드로 생성 로직을 한 번 더 묶어서 Bridge 인스턴스를 return 하게 했다. 

BridgeGame 클래스의 build 메서드 변경 사항

BridgeGame 클래스의 build 메서드는
내부에서 Builder 클래스를 초기화하고 buildBridge 메서드를 실행하도록 변경했다.

(이때 여전히 build 메서드는 테스트 불가능하다는 사실을 알지 못했다...😥)

 

테스트 작성 가능..?

위 코드처럼 원래 할 수 없던 BridgeGame클래스의 build 메서드 테스트를

Builder 클래스로 대신하게 되면서, 해결이 됐다고 생각했다.

 

하지만 곧 근본적인 문제를 맞닥뜨렸다.


😅 Builder 클래스를 삭제하게 된 이유

BridgeGame클래스 내 move 메서드

문제는 위 코드처럼 사용자의 방향 입력을 받아 움직이는 기능을 만들고 나서 발생했다.

기능 구현 이후 테스트 코드를 작성하는데.. 이게 웬걸!! move 메서드 또한 테스트 자체가 불가능했다!

 

BridgeGame 클래스 내의 모든 기능 메서드를 테스트할 수 없는 근본적인 원인은 따로 있던 것이다...

테스트 불가능

원인은 위 테스트 코드를 보면 알 수 있다.

다리 건너기 게임은 BridgeGame 클래스의 build 메서드로 다리를 만들면서 시작하는데...

다리를 만드는 과정에 제공된 Random 유틸 함수가 사용된다.

BridgeGame 클래스 내부에서 Random 함수를 통해 다리를 생성하는 구조 자체만으로

다리와 연관된 모든 메서드를 테스트할 수 없게 되는 것이다.

 

원인을 알게 된 후 테스트 코드 작성이 주 목적이 되어 버린 Builder 클래스를 삭제했고, 코드를 리팩터링 했다.


💫 단위 테스트하기 어려운 코드를 가능하도록 리팩터링 하기!

😃 근본적인 원인 해결

결국 근본적인 원인은 Random 함수였고,

다리 성성을 BridgeGame 클래스 외부에서 해야한다는 결론이 나왔다.

GameController 클래스에서 다리생성
전달 받은 다리를 필드에 할당

사용자 입력을 전달받아 명령을 내려주는 GameController 클래스에서 다리를 생성하고,
BridgeGame 클래스의 인스턴스를 만들 때 생성된 다리를 전달하여 초기화하도록 개선하였다.


😃 테스트 가능해진 메서드

move 메서드 테스트 가능
retry 메서드 테스트 가능

 

개선 이후 Random 함수와 BridgeGame 클래스가 분리되었고,
원하는 값으로 다리 생성이 가능해졌다. 이로 인해 메서드의 테스트도 가능하게 됐다.

해냈다....이도현...


🔔 프리코스를 마무리 하며...

 

마침내 결승선을 통과했다.

4주간의 마라톤을 끝내고 나니 성취감에 뿌듯하면서도, 끝이 났다는 게 아쉽기도 하다.

짦은 시간 동안에도 충분히 많은 성장을 할 수 있다는 것을 느낄 수 있는 시간이었다.


하지만 나는 더 성장해야 한다. 아니 평생 성장해야 한다.
이번 프리코스 과정에서 온전히 학습에만 집중한 것처럼 같은 자세로 열심히 배워간다면,

 

나는 분명 훌륭한 개발자가 되어있을 것이다.

 

고생했다 도현아!