Study/우아한 테크 코스 / / 2023. 10. 27. 13:10

[우아한 테크 코스] 프리코스 - 1주차, 숫자 야구 회고

[1일차]

처음에 뭐부터 할지 고민하다 기능 목록을 먼저 정의하라는 요구사항에 따라 기능 목록을 작성했다. 기능 목록을 어떤식으로 나타내야 사용자에게 보기 좋을까? 를 생각했고, 나라면 그림이 제일 위에 있으면 좋겠다 라고 생각해서 다이어그램을 그리기로 했다. 그 전에 꼼꼼하게 진행방식, 미션 제출 방법, 체크리스트 등을 꼼꼼하게 다 읽고 기능 요구 사항을 살펴봤다. 간단해보이지만 단순하게 코딩테스트처럼 문제를 푸는것을 원하지 않기에, 그리고 자바를 통한 개발이 익숙하지 않아 학부때 배운 책을 다시 펴고 이 문제는 어떻게 해결하지? 라는 생각부터 했다. 그 이후에는 어느정도 정리가 돼서
내가 개발할 대략적인 그림을 플로우 차트를 통해서 그리기로 마음을 먹었고, 머메이드의 Docs를 통해 다이어그램을 그리는 방법을 보고 익숙해지는 시간을 충분히 가졌다. 이후 mermaid-js.github.io에서 실시간으로 내가 작성한 다이어그램을 보면서 작성할 수 있었다. 무작정 개발을 시작한 경우가 종종 있는데 이번에 플로우 차트를 통해 객체지향적인 설계에 필요한 고민을 할 수 있었다. 추가로 이론상으로 흐릿하게 알고 있었던 SOLID 원칙을 지키며 개발하기 위해 클래스 다이어그램도 머메이드를 통해 그렸는데 정말 큰 도움이 됐다. SOLID 원칙 블로그를 켜고 어떻게 해야 이 원칙들을 지킬 수 있을까? 를 계속해서 고민했다. 헷갈리는 부분도 있지만 처음으로 객체지향적인 설계를 직접 해본 것 같아서 몸은 피곤했지만 머리는 안개가 걷힌 기분이라서 너무 좋았다. 그렇게 계속해서 클래스 다이어그램을 수정해가며 첫날은 5시간 이상 플로우차트와 다이어그램만 그렸다.
워크플로우를 보며 정말 고심해서 클래스 다이어그램을 만들었고, 기능 목록은 클래스 다이어그램이 있기에 순식간에 만들어졌다.
하지만 3개의 숫자만을 가질 수 있다는 제약에서 시간이 많이 소비되었다. 처음에는 ValidNumberSize와 같은 인터페이스를 만들고 default 메소드를 통해 이를 구현하고, Player 인터페이스가 이를 확장하도록 하려 했지만 default 메서드는 오버라이드를 통해 구현시 변경하여 원래 의도와 다르게 사용되는 경우가 있다는 생각이 들었고, 스터디중인 이펙티브 자바에서도 인터페이스의 default method를 신중히 작성해야 한다는 챕터를 마침 금주에 읽었기에 다른 방법을 생각해봤다.
첫 번째는 유틸리티 클래스인데 이펙티브 자바에서 해당 클래스를 작성하는 방법에 대해서 학습했던 기억이 얼핏 났고, 유틸리티 클래스는 객체지향적이지 않다. 라는 글을 저장 해두었는데 이걸 다시 생각하게 되어서 보류하였다. 두 번째는 클래스를 만들고 이를 Player 인터페이스가 컴포지션을 통해 해결하는 방식을 떠올랐고 이를 통해서 Player의 하위 클래스 생성시 유효성을 검사하는 방법을 진행하였다. 여기서 고민인 점은, Player를 상속받는 모든 클래스에 ValidNumberChecker를 넣어줘야 한다는게 맞는지를 고민했다. 하지만 현재 게임의 규칙이 언제든 변경될 수 있기 때문에 해당 방식이 더 확장성이 있다고 생각하였고, 만약 변경해야 한다면 조금 더 명확하게 Player를 NumberBaseballPlayer로 변경하는게 더 나은 방법인거라 생각이 들었다.

mermaid를 활용한 플로우차트
클래스 다이어그램



[2일차]

2일차는 직접 숫자야구 코드를 작성하면서 이론으로만 알았던, 혹은 알았지만 잊고 있었던 부분에 대해서 잘 알게되었다. 인터페이스에 아무런 키워드 없이 List<Integer> numbers를 생성하면 public static final로 생성되는건 알았지만, private이나 protected로 하지 못하는 부분에 대해서는 생각해본 적이 없고, 시도하려는 생각도 안했는데 이번에 Player를 생성하고 User와 Computer를 상속하게 하면서 numbers라는 숫자형 리스트와 정적팩토리 create 메서드를 추상화하여 사용하려 했는데 interface로 선언하니 이 부분이 막혔었다. 결국 abstract class나 class를 만들어 상속하게 하는 방법 중 추상 클래스를 사용하였다. 더 좋은 설계 방법을 계속해서 생각해내고, 어떻게 해결해야 할지를 고민하게 되는 시간이었다. 그렇게 우선적으로 틀을 잡고 코드를 구현하였는데, 무작정 코딩부터 하려고 하면 할수록점점 미궁속으로 들어가고 있었지만 재정비해서 노트에 클래스 별 자세한 역할을 정리하였고, 그 역할에 맞도록 코드를 개발했더니 순식간에 코드 구현을 했었던 것 같다. 여기서도 쉬운 문제라고 생각하면서도 SOLID와 MVC패턴을 지키면서 객체지향적인 코드를 구현하려니 많이 어려웠다. 이론으로 알고 있다고 생각했지만 실제 설계한것과 코드로 나타내는것은 다르다고 느꼈고 이런 와중에 많은 시행착오를 겪었다. 하지만 묻혀가던 지식들을 하나씩 꺼내서 내 코드를 변화 시키는 과정은 무척이나 즐겁고 행복한 경험이었다. 2일차는 그렇게 설계를 조금씩 바꾸며 숫자 야구 게임의 테스트 통과를 목표로 진행하였다. 밤 12시가 되고 나서야 테스트 코드를 통과하였고 이는 무척 행복했다. 왜냐하면 오늘 목표인 테스트 통과를 완료했기 때문이다.



[3일차]

3일차는 코드 리펙토링에 많이 심혈을 기울이려 애를 썼다. 자기 전 다짐한 목표는 진짜 mvc 패턴을 구현하는것, 그리고 메서드 하나에 다 몰려있는 기능들을 쪼개는 것이었다. 하지만 코드를 다시보니 가독성은 둘째치고 비효율적인 코드가 눈에 들어왔고, 기능들을 쪼개기 전에 이런 부분부터 해결하고자 애를 썼다. 그렇게 시간을 들여서 버그를 수정하고  가장 문제인것은 어디서 뭘 할건지를 정하는게 우선순위였다. 현재는 중구난방 여기저기서 view를 보고 있었고, 스파게티처럼 구현된 부분이 많았다. 우선적으로 mvc 패턴을 적용시키고, 상수를 빼서 따로 상수 클래스를 만들고(enum을 사용할지 상수 클래스를 사용할지 고민 되었지만 다음 미션에 사용하기로 마음을 먹었다.) 메서드 추출 및 분리, 가독성을 위해 while(true) 이후 return하던 나쁜 코드를 do while로 변경해 조금 더 가독성있게 변경하는 등... 객체지향적으로 수정하기 위해 많은 노력을 했었다. 클래스는 항상 불변으로 만들어야 멀티쓰레드 환경에서 장점을 갖고, 다양한 상황에서의 버그가 일어날 수 있는 여지가 적다고 이펙티브 자바에서 계속해서 강조했기 때문에 불변객체를 만들고, 이걸 어떻게 해결해야 할지를 고민했다. 사실 불변 객체를 만드는 것 자체는 쉬웠지만, 객체지향적으로 getter와 setter는 최대한 사용하지 않으려 했기 때문에 클래스 하나를 리펙토링을 한번 하는데 많은 시간이 걸렸다. 게임을 컨트롤 하는 클래스에 Computer와 User를 컴포지션으로 넣어보기도 하고, 게임을 시작하는 GameStarter와 게임 종료를 판단하는 GameTerminator에 넣어보기도 했다. 그러면서 객체의 생성 주기에 따라 해당 객체를 멤버 변수로 갖게할지, 매개변수로 받아 사용할지를 고민했다. 그렇게 클래스를 변경하면서 한번씩 테스트코드를 통해 어떤 부분에서 문제가 생기는지 한번에 알 수 있었다. 추가로 했던 부분은 내가 짠 코드를 보는데 Input을 model이 하기도 하고, controller가 하기도 하는 이상한 코드가 만들어졌다. 결국 다시 모델에 있던 컨트롤러를 사용하는 코드를 하나씩 컨트롤러 밖으로 빼고, 컨트롤러에서 모델과 뷰를 중간에서 조정하는 역할을 하게 만들고, 모델은 비즈니스 로직과 데이터를 처리, 뷰는 사용자의 입출력을 담당하도록 만들었다. 하지만 여전히 뷰에서 모델의 로직을 사용하고 있었기 때문에 해당 부분을 내일 처리하고, 코드를 더 리펙토링해야 할 것 같다. 코드에 맞게 1일차에 설계했던 잘못된 다이어그램도 변경해야 하지만 보면 거의 10분마다 변경되는 프로젝트 구조와 남은 시간들을 생각하면 추후에 한번에 변경해야 될 것 같다고 느꼈다. 언젠가는 설계한 그대로 쭉 수정하지 않는 멋진 설계 능력도 갖고 싶다.

리펙토링



[4일차]

4일차는 가장 먼저 관심사 분리를 진행하였다. 전날에 자기 전 mvc 패턴에 대해 곰곰히 생각을 하게 됐는데, 무작정 Controller, Model, View로 나눈다고 개발을 했지만 정확히 언제 어떻게 쓰이는지에 대해 알 수 없었다. 하지만 정확히 mvc에 대해 다시 짚고 넘어가고, 해당 패턴이 가진 장단점은 알지는 이걸 왜 사용하는지는 몰랐다. 3일차까지 작성했던 코드에서는 계층간의 분리가 잘 안되어 있었다. 그래서 View에서 Model 객체를 사용하고, Model이 View를 호출하는 순환 참조가 발생하였기 때문이다. 그리고 게임이 시작되는 컨트롤러에서 게임 종료를 담당하는 new 생성자를 통해 모델을 호출하였는데 중간 계층인 게임의 흐름을 담당하는 모델에서 의존성 주입을 받아 해당 함수를 호출하게 만들고, 컨트롤러에서는 흐름을 담당하는 모델만 바라보도록 수정하여 결합도를 낮췄다. 사용자의 입력을 담당하는 InputView에서는 입력의 책임만 갖도록 분리, 추후 view를 웹 등으로 변경하려면 분리해서, 현재는 View가 콘솔이지만 나중에는 Web, App 등 다양한 화면에서 사용할 수 있도록 하였다. 또 유틸리티 클래스에서 숫자에 관한 모든 검증, 변환을 하였는데 InputView에서 입력의 책임을 갖도록 하고, 사용자의 입력을 검증, 변환하는 Convertor와 Validator로 캡슐화시켜 새로 생성하고  Convertor와 Validator를 InputView와 OutputView에서 주입받아 사용하도록 하였다. 이 과정에서 이론으로 알고있던 부분을 코드로 변환하여 읽기 힘들던 코드를 눈에 띄게 한번에 파악할 수 있었다. 관심사의 분리의 장점을 직접 체감했고, 객체지향적인 코드가 왜 유지보수가 쉬워지고 버그가 발생하기 힘든지 그리고 확장에 대해 열려 있어야 하고, 수정에 대해서는 닫혀 있어야 한다. 라는 OCP를 진정으로 이해하게 됐다. 이런 과정속에서 테스트를 계속해서 반복하였는데, 테스트코드의 중요성도 같이 깨달았던 것 같다. 

생성자 주입을 통해 관심사 분리



[5일차]

5일차는 테스트 케이스 작성에 치중했다. 테스트 케이스를 작성한 경험이 드물었고 필요성을 몰랐지만, 이번에 야구 게임을 개발하고, 리펙토링을 하며 그 중요성을 깨달았다. 단위 테스트를 하기로 마음을 먹었고, 중요한 로직이라고 생각되는 게임 판정, 사용자 입력값 유효성 검증, 변환자(convertor) 검증의 테스트 코드를 작성했다. 처음에는 어떻게 해야 할 지 고민이 있었는데 단순하게 given, when, then으로 나누고 테스트에 필요한 객체 생성 및 과정 준비(given), 테스트를 할 대상 로직 실행(when), 결과값 검증(then) 으로 일관된 테스트를 진행했다. 특히 어려웠던 부분은 테스트의 범위를 어디까지 할지가 관건이었는데 처음 실패 케이스까지 작성하기 때문에 어려움이 많았다. 하지만 다른 사람들의 테스트를 보고 나만의 규칙을 정하였는데 1.한눈에 파악하기 어려운 로직을 테스트하자 2.실패하는 모든 경우를 테스트하자, 였다. 이를 기반으로 테스트 코드를 작성했고 Junit5의 사용법을 배워 테스트 코드를 작성하는 내가 신기했고, 테스트 코드를 코드 작성 후 마지막 검토에 하는게 아닌, 해당하는 메서드나 클래스 작성 후 바로 테스트하면 더 좋았겠다. 라는 생각이 들었다.



[6일차]

6일차는 개발하며 처음과는 너무나 동떨어진 docs를 수정하였다. 클래스 다이어그램을 다시 설계하는데 꽤나 시간이 소요됐고, 완성된 모습을 보자 한눈에 잘 들어오는 것 같아 기분이 좋았다. 아쉬운 부분은 개발에만 신경쓰느라 docs를 완전히 잊고 있었다는게 너무 아까웠다. 다음에는 처음부터 잘 설계해서 docs를 어느정도 잘 잡아두고 그에 맞게 하나씩 기능을 구현한다면 더 쉬운 미션이 될 것 같았다. 6일차까지 진행하며 어려운점도 많았고, 몰랐던 걸 알아가는 과정에서 얻는 성취감도 너무나 즐거웠다. 문제로 나왔던 숫자야구는 정말 간단한 문제였지만 더 좋은 코드로 만들고 더 좋은 설계를 하려는게 1주차 미션의 나의 목표였는데 잘 성취한 것 같아서 기분이 좋다. 다른 사람들이 볼 때 내 코드를 어떻게 볼지도 궁금하다. 개발을 하면서 가장 많이 단기간에 몰입한 시간이었고 얻은게 정말 많은 한주간의 미션이었다.

 

[최종 코드]

https://github.com/devMtn30/java-baseball-6/tree/devmtn30/

 

GitHub - devMtn30/java-baseball-6

Contribute to devMtn30/java-baseball-6 development by creating an account on GitHub.

github.com

 

[코드 리뷰]

많은 분들이 코드 리뷰를 해주셨는데 정말 도움이 많이 됐다!

테스트 코드 작성시에 진짜 도움이 많이 될 것 같은 리뷰!!!(꼼꼼하게 봐주시고 여러개를 달아주셨다 ㅜㅜ), 그리고 리뷰해서 주신 분들의 코드를 보고 세상은 넓고, 고수는 많다... 나는 항상 코린이다!  라는 생각으로 초심을 잃지 말아야겠다라는 생각이 들었다.

 

assertThatIllegalArgcumentException을 활용하자!
언더바 대신 DisplayName을 활용하고 카멜케이스로 테스트 작성하자!
맛좋은 스트림을 활용하자!
당근과 채찍 🔍 감사합니다.

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유