본 글은 우아한 테크코스 3주 차 미션을 수행하면서 기록했던 회고록이다.
지난 2주차 미션에 이어 3주차 미션도 정말 열심히 했다 ㅎ
-> 2주차 미션 회고
3주차 미션
-> 우아한 테크코스 프리코스 3주차 미션 java-lotto
-> 필자가 제출한 코드
3주차 미션에서 주어진 목표는 다음과 같았다.
- 클래스(객체)를 분리하는 연습
- 도메인 로직에 대한 단위 테스트를 작성하는 연습
목표의 1번을 처음 보고 클래스를 잘 분리할 수 있는 방법이 있지 않을까 생각하며 구글링을 해보았고, 다들 하나같이 MVC 패턴에 대해 언급하고 있었다.
예전에 멋사 활동할 때 Django에서 쓰이던 MVT 패턴이 떠올랐고, MVC 패턴의 개념을 이해하는 것은 크게 어렵지 않았다. 하지만 직접 적용해 보는 것은 또 다른 얘기기 때문에 3주 차 미션에서 써보고자 했다.
-> 새로 배운 내용 - MVC 패턴
그리고 새로 주어진 목표를 이루는 것도 중요하지만, 전 주차에서 좋았던 점은 유지하고, 부족했던 점은 개선하는 것 또한 매우매우매우매우 중요하다고 생각했다.
그래서 2주 차 미션을 수행하며 작성했던 회고록과, 우테코 코치님이 공지해 주신 공통 피드백 내용을 같이 보면서 유지 및 개선 점을 정리했다.
이렇게 이것저것 정리하면서 '나만의 미션'을 만들게 되었고, 본 회고록은 이를 최대한 실천해 보고자 나름(?) 노력했던 기록이다.
나만의 미션
나만의 미션은 '전 주차의 유지 및 개선할 내용' 과 '본 주차에서 새로 시도할 내용' 두 가지로 구성했다.
2주 차 유지 및 개선
- TDD 방식 유지하기
- 커밋 메세지의 scope를 패키지 단위로 채우기
- controller, model, view, exception, constants 등
- 최대한 작은 단위로 테스트 코드 작성하기
3주 차의 새로운 시도
- 함수가 한 가지 기능을 하는지 확인하는 기준에 대해 고민해보기
- 기능 범주에 맞추어 클래스 분리에 대해 고민해보기
- MVC 패턴 적용해보고 장단점에 대해 생각해보기
이 외에도 '값을 하드 코딩하지 않는다.', '변수 이름에 자료형은 쓰지 않는다.', '기능 목록을 업데이트 한다.' 등등... 2주 차 미션 공통 피드백 내용은 최대한 다 지키려 했다.
그리고 개발하면서 중간에 새롭게 시도해 본 내용들도 있었는데, 이는 다음과 같다.
개발 중 추가한 미션
- 인터페이스 및 익명 클래스를 통해 반복 코드 줄이기
- 기능 목록 양식 만들기
- 기능 목록 작성 방식 재고
개발 회고
1. 2주 차 유지 및 개선
<TDD 방식 유지>
- 2주 차에 처음으로 시도해 보았던 TDD 방식을 3주 차에도 유지하면서 구현해 보았다.
- red - green - refactor 과정을 다시 한번 시도해 보았고, 이 과정에 좀 더 익숙해질 수 있었다.
- 비록 규모가 있는 실제 개발 프로젝트와는 차이가 있겠지만, 이렇게 계속 시도해보며 과정 자체에 익숙해지면, 후에 분명 다른 이로운 효과도 얻을 수 있을 것이라 기대한다.
<커밋 메세지 scope>
- 2주 차 미션에서 다짐(?)한대로 커밋 메세지의 scope 부분에 클래스 이름이 아닌, 좀 더 큰 범주로 설정하였다.
- 본 3주차 미션에서 시도한 MVC 패턴에 맞추어 model, view, controller, exception 단위로 설정하였다.
- 확실히 중복되는 내용이 사라지고, 길이도 줄어들었다.
- 또한, 깃 사이트의 커밋 내역에서 어떤 파일의 어떤 로직이 수정되었는지 확인할 수 있기 때문에, 개발자의 의도를 드러내기에 충분하겠다고 생각되었다.
- 이후에도 scope 내용을 본 방식과 동일하게 적용할 예정이다.
<최대한 작은 단위의 테스트>
- 2주 차 미션에서는 '기능' 단위로 테스트를 수행했었다.
- Ex) 1볼부터 3스트라이크까지 반환 가능한 모든 경우들을 하나의 테스트 메소드에 모아두고 테스트를 수행했었다.
- 3주차 미션에서는 하나의 기능도 여러가지 경우들에 대해 세세하게 나누어 테스트 메소드를 작성하였다.
- 본 방식대로 개발을 진행해 보니, 확실히 어느 기능에서 문제가 발생하는지 바로바로 피드백을 받을 수 있었다.
- 2주차 미션 때는 큰 기능 단위로 테스트를 실패하니, 어느 구간에서 문제가 발생했는지 추가로 알아볼 필요가 있었는데, 좀 더 작은 단위로 나누어 테스트를 수행해보니 문제 구간을 바로 파악할 수 있었다.
2. 3주 차의 새로운 시도
<메소드 한가지 기능 기준>
-
리팩토링 중 메소드 분리 과정에서 메소드가 한가지 기능을 수행하고 있는지에 대한 기준에 대해 생각해 보았다.
-
기준을 설정하기 전, 먼저 메소드를 분리하는 과정에 대해 생각해 보았고, 그 과정 속에서 메소드가 한 가지 기능을 하는지에 대한 기준을 설정하였다.
-
과정은 다음과 같다.
(1) 우선 메소드를 **간략하게 하나의 문장으로 작성**한다.
- 로또 각각을 당첨 로또와 비교하며 통계에 반영한다.
(2) 문장을 **좀 더 디테일하게 수정**한다.
- 로또 각각에 대해 로또 번호와 당첨 번호를 비교한다. 일치하는 숫자의 개수를 센 후, 일치하는 숫자의 개수에 따라 당첨 통계에 반영한다.
(3) 문장을 **동작 단위**로 나눈다.
- 로또 각각에 대해 로또 번호와 당첨 번호를 비교한다.
- 일치하는 숫자의 개수를 센다.
- 일치하는 숫자의 개수에 따라 당첨 통계에 반영한다.
(3) **동작 단위에서 좀 더 세세하게** 나눌 수 있는지 고민한다.
- 로또 각각에 대해 로또 번호와 당첨 번호를 비교한다.
- 일치하는 숫자의 개수를 센다.
- 일치하는 숫자의 개수에 따라 당첨 통계에 반영한다.
- 해당하는 순위의 통계 값을 1씩 증가시킨다.
- 일치하는 숫자가 5개일 경우 보너스 번호가 일치하는지 여부를 고려한다.
(4) 이렇게 나누었을 때, **두 문장이 붙어있진 않는지** 확인한다.
(5) 메소드 1차 완성
- 로또 각각에 대해 로또 번호와 당첨 번호를 비교한다. : compareOneLotto()
- 일치하는 숫자의 개수를 센다. : countMatchingNumbers()
- 일치하는 숫자의 개수에 따라 당첨 통계에 반영한다. : reflectStatistics()
- 해당하는 순위의 통계 값을 1씩 증가시킨다. : increaseCount()
- 일치하는 숫자가 5개일 경우 보너스 번호가 일치하는지 여부를 고려한다. : checkBonusNumber()
(6) 메소드 **개발 중 한 메소드 내에 여러가지 기능을 포함하게 될 수 있다.** 이를 또 분리한다.
- countMatchingNumbers() : retainAll() 메소드를 활용하여 일치하는 번호만 남기는 과정이 포함된다.
=> 해당 기능 leaveOnlyDuplicated() 분리
-
본 과정을 통해 수행되는데, 여기서 중요한 기준은 다음과 같다.
- 메소드를 문장으로 표현했을 때, 두 문장이 이어진 형태는 아닌지 확인한다.
- Ex) 중복되는 번호만 남겨서 그 개수를 세어 일치하는 숫자의 개수를 구한다.
- indent 깊이가 너무 깊어지거나, 코드 길이가 너무 길어질 경우, 문장을 더 세세하게 나누지 않았을 확률이 높다.
<클래스 분리>
- 먼저 MVC 패턴에 맞추어 클래스를 분리하였다.
- Model
- Lotto : 로또 번호 저장 모델
- WinningLotto : 당첨 로또 저장 모델
- View
- Controller
- LottoGame : Model과 View 및 기능 클래스들을 연결하는 중간 Controller 역할
- LottoAnalyst : 로또 분석 기능
- LottoIssuer : 로또 발행 기능
- 2주 차 미션에서 더 발전시켜 하나의 클래스 내에서 여러 범주의 기능들이 활용되지 않도록, 기능 범주에 맞추어 클래스를 분리하였다.
- 로또 발행 기능 클래스 : LottoIssuer
- 로또 분석 기능 클래스 (당첨 통계 및 수익률) : LottoAnalyst
- 로또 게임 운영 클래스 : LottoGame
- 클래스를 분리해 보니, 확실히 각자 고유의 기능만을 담고 있을 수 있었다.
- 가독성에도 더 도움이 되었고, 로직이 깔끔하게 정리된 느낌이 들었다.
- 이를 통해 유지 보수에도 더 도움이 될 것이라 생각되었고, 테스트도 보다 더 정리되어 기능 정상 동작 여부에 대해 좀 더 효율적으로 피드백 받을 수 있어 매우 좋았다.
- 2주 차 미션에서는 메소드들이 각자의 역할을 수행하는 공장 역할을 하는 느낌이었는데, 3주 차 미션에서 클래스를 분리해보니, 공장이었던 메소드들이 클래스로 묶이면서 하나의 기업이 된 듯한 느낌이었다.
<MVC 패턴>
- 확실히 MVC 패턴에 맞추어 개발을 진행해 보니, 백엔드 개발 방식에 매우 적합한 패턴이라는 생각이 들었다.
- Model이 DB와의 소통을 담당하기 위한 기본적인 로직을 갖추고, View가 사용자와 직접 맞닿아있는 프론트엔드 역할을, Controller가 DB와 프론트엔드 사이에서 로직을 수행하는 서버 역할을 수행하도록 구현하니, 각자의 뚜렷한 역할이 그려져 유지보수에 상당히 도움이 될 수 있을 것이라는 생각이 들었다.
3. 개발 중 추가한 새로운 시도
<인터페이스 및 익명 클래스를 통해 반복 코드 줄이기>
-> [Java] 인터페이스+익명 클래스 활용 반복 코드 줄이기
- 2주 차 미션 당시, 테스트 코드에서 중복되는 코드가 너무 많아 상당히 거슬렸지만 방법을 찾아 적용하기에는 시간이 부족해 아쉬움으로 남았었다.
- 3주 차 역시 테스트 코드에서 중복되는 코드가 다수 발생하였고, "객체 지향"적으로 중복을 줄일 수 없을까 고민해 보았다.
- 테스트 코드의 중복되는 코드가 변수가 아닌 메소드가 달라지다 보니, 단순히 메소드 분리로는 어려움이 예상되었다.
- 메소드 파라미터로 메소드 자체를 전달해야 하기 때문이다.
(Method 객체를 활용하는 방법도 알아보았지만, 보다 객체 지향적인 방법을 적용해 보고 싶었다.)
- 그러던 중, 인터페이스와 익명 클래스를 활용하는 방법에 대해 확인할 수 있었다.
- 덕분에 `LottoExceptionHandler 라는 인터페이스를 생성하고, 각 예외처리 기능 테스트마다 익명 클래스 객체를 생성하여 메소드를 파라미터로 전달하듯이 구현해 낼 수 있었다.
- 지금까지 인터페이스, 추상 클래스에 대해 개념만 모호하게 알고 있었는데 이번 기회에 직접 활용해 보니 진짜 대박이었다.
- 이후 반복되는 코드에 대해 고려할 수 있는 방안이 더 늘어났다!
<기능 목록 양식 만들기>
- 1, 2주 차 미션을 수행하며 기능 목록을 즉흥적으로 작성했었는데, 그러다 보니 놓치는 내용이 있었다.
- 따라서 이번 3주 차 미션을 통해 '나만의 기능 목록 양식'을 만들고, 직접 활용해 보며 부족한 내용을 채워보았다.
- 이를 통해 '기능 목록 양식'을 만들어 낼 수 있었다.
- 기능 전체 목록, 예외 사항 전체 목록 : 분류되지 않은 상태로 기능과 예외 사항 전체를 줄줄이 작성한다.
- 기능 정리, 예외 사항 정리 : 유사한 범주의 내용들을 묶어 기능과 예외 사항들을 정리하고, 이를 토대로 클래스 분리를 수행한다.
- 개발 순서 : 작성된 기능 정리, 예외 사항 정리 내용을 토대로 개발 순서를 결정한다.
- 요구 사항 체크 리스트 : 주어진 요구사항에 대해 체크박스 형태로 정리해둔다. 꼭 개발 전에 작성하며 고려해야 한다. 개발 후 하나씩 체크하며 요구 사항을 만족했는지 확인한다.
<기능 목록 작성 방식>
- 원래는 2주 차 미션에서 작성하던 방식을 좀 더 심화시켜 진행하려 했지만, 공통 피드백 내용과 맞지 않는 부분이 있었다.
- 기능 목록을 재검토 한다.
- 기능 목록을 업데이트 한다.
- 죽은 문서가 아니라 살아있는 문서를 만들기 위해 노력한다.
- 따라서 기능 목록 초안을 지나칠 정도로 자세하게 작성하지 않았고, 살아있는 문서로 만들기 위해 노력하였다.
- 역시나 개발하면서 기능 목록의 많은 내용들이 수정되었고, 피드백에 따르길 잘 했다는 생각이 들었다.
- 또한, 기능 목록 내용에 너무 의존하지 않게 되었다.
- 기능 목록을 처음부터 너무 세세하게 작성하니, 기능 목록의 내용만을 너무 신뢰하여 의존하게 되는 경향이 있었다. 그러다 보니 생각이 기능 목록 안에 갇혀있게 되는 느낌을 받았다.
- 하지만 기능 목록을 계속 수정하면서 '살아있는' 형태로 두니 생각이 뻗칠 수 있는 범위가 더 넓어지는 느낌을 받았다.
- 이후에도 기능 목록을 처음부터 너무 상세히 작성하기 보다, 필요한 기능만을 자세히 작성하여 개발 방안에 대해서는 열어둔 상태로 유지하는 것이 좋을 것 같다.