첫 주차에 아쉬웠던 점을 개선해서 이번 주차에는 다음 고려 사항들을 신경써서 구현하기로 했다. 테코톡 강연 내용이 방향성을 잡아가는 데 도움이 많이 되었다. :) 구현 과정에서 고려한 사항들을 정리해보면 다음과 같다!

💡 고려 사항

  • 기능 최소로 쪼개고, 기능별 테스트 작성
    • 기능 쪼개는 범위 스스로 감잡기 (중요한 기능부터 먼저 고려할 것)
    • 테스트 코드 작성하면서 스스로 피드백 주기
  • 다른 사람들이 이해하기 쉬운 코드
    • 클린코딩 : 코딩컨벤션 지키기, 메소드명/변수명 가독성 있게 짓기
    • 한 클래스/메소드에 너무 많은 기능 담지 말기
    • 오버 프로그래밍 하지 말기
  • 객체지향적으로 설계하기
    • 책임과 역할 분리, 클래스 간 의존도가 적게 구현하기
    • 메소드 호출한 쪽에서 내부 구현 동작 모르게끔 하기
    • MVC 패턴에 맞게 설계하기

이번 주차에는 위의 고려 사항들을 지키기 위해서 리팩토링을 꽤 많이 했다. 그 과정에서 아예 엎고 새롭게 설계하기도 했고, 클래스 분리/통합 하기도 했다. 이론적으로만 배웠던 OCP, 객체지향 규칙을 실제로 써보는 느낌이었다. 아직 어떤게 더 좋은 설계인지 알듯말듯하지만, 그래도 이전보다는 하나둘씩 배워가는 느낌이다.

일단 MVC 패턴대로 설계했다. 사실 MVC를 처음부터 적용하고자 한건 아니었다. 클래스 분리나 전체 구조를 어떻게 해야할 지 몰라 구글링하다가 MVC를 사용하는게 쉽게 시작할 수 있을 것 같아서 적용하게 됐다.

그리고 요구 사항 파악 후 기능 목록을 써보고, 기능을 최대한 쪼개고 각각 메소드를 만들었다. 기능별 테스트 코드를 작성하면서 진행했다. 그리고 개선할 점이 생각나면 계속 리팩토링 했다.

🧱 설계

MVC 패턴 규칙대로 설계를 시작하는 게, 전체 구조를 잡는 데 도움이 많이 되었다.

MVC 패턴 규칙
  • Model은 Controller와 View에 의존X
  • View는 Model에는 의존 O , Controller는 의존 X
  • View는 Model의 데이터 받을 때는 사용자마다 다르게 보여줘야하는 데이터만 받음
  • Controller는 Model과 View에 의존 O
  • View가 Model의 데이터 받을 때는 Controller에서 받음

img

  • Model
    • Computer : 컴퓨터의 랜덤 숫자 저장
    • Player : 사용자의 입력 숫자 저장
    • Judge : 볼/스트라이크 판별/저장
    • Random : 랜덤 숫자 생성
  • View
    • InputView : 입력 뷰
    • OutputView : 출력 뷰
  • Controller
    • GameController : 모델과 뷰를 컨트롤하여 게임을 진행하는 컨트롤러
  • Vaild
    • PlayerInputNumberValidator : 사용자 입력값 검증

🤔 고민된 점

아래는 크게 고민했던 부분들인데, 나름의 검색과 고민으로 결론을 내었지만 사실 더 좋은 방법이 있을 지 싶은 주제들이다 ✨

1. 클래스 분리 : Judge와 Score

      public class Judge {
      
      private List<Integer> playerNumbers;
      private List<Integer> answer;
      
      private int ball;
      private int strike;
      ...
    ;

원래 위처럼 Judge에 Com, Player의 코드와 ball, strike가 모두 인스턴스 변수로 있었다. 하지만 한 클래스에 많은 인스턴스 변수가 있으면 관련 메소드들도 많아지게 되고 한 클래스의 역할이 많아져 분리하기로 했다. 그래서 Judge는 볼/스트라이크 판별만 하고, Score을 만들어 볼/스트라이크를 저장하는 클래스로 분리했다.

근데 이렇게 되면 Judge에서 Score 내부를 알아야 점수 결과를 낼 수 있어서, 이게 바로 그 의존도가 높아 안 좋은 설계인가 싶었다.. 기능 수정하려면 Controller단에서 두 도메인단 모두 수정해주어야 했다. 테스트 코드 작성할 때도 두 도메인이 섞여야 되서 복잡해지게 됐다..! 그래서 Score은 삭제하고 다시 Judge로만 구현하는 걸로 리팩토링 했다.

2. 예외 처리

예외 처리는 새 Exception 클래스를 만들까 고민했지만, 직접 Exception 을 만드는 대신 IllegalArgumentException에 에러 메세지만 전달해주기로 했다. 직접 Exception 클래스까지 만들어줄 필요성을 크게 못 느끼기도 했고, 오히려 오버프로그래밍이 될까 싶었다. 대신 검증 함수를 따로 분리해 만들었다.

원래 검증 함수들은 Player의 입력과만 관련있기에 Player 클래스 안에 넣어주었다. 하지만 Player 클래스안에 검증용 함수가 너무 많아져서 흐름을 읽기 어려워서 따로 Validator 클래스로 분리하였다.

3. GameController : 메소드 분리 / 메소드명

하나의 메소드에는 하나의 기능만 구현하기

위 규칙을 지키기 위해 메소드를 많이 분리하였다. 하나의 메소드가 5줄을 넘어가지 않도록 구현했고, 메소드명의 기능만을 구현하고자 했다. 근데 이렇게 하면 메소드 수가 많아져서 흐름이 너무 길어질까 고민이었다.

아래는 GameController의 게임 시작 메소드이다. 메인 어플리케이션에서는 이 메소드만 실행해서 전체 프로그램이 진행되게 된다. 이 부분이 다른 사람들이 봐도 잘 이해할 수 있도록 신경 쓴 부분이다. init()만 보더라도 전체 흐름을 이해할 수 있기를 바라면서 메소드로 분리하고, 메소드명을 지었다.

일단 당연히(?) 스스로는 이해가 잘된다… 게임 시작하면 컴퓨터가 답을 셋팅하고, 사용자가 성공할 때까지 숫자를 입력한다. 그리고 성공하면, 재시작 여부를 결정한다. 일부러 init에 내부 구현은 숨기고 따로 메소드로 분리하였다. 최대한 메소드명만 보고도 파악할 수 있게 이름 지었다.

   public void init() {
   createComputerAnswer();

   do {
      loadPlayerNumber();
   } while (!isGameSuccess());

   gameRestartOrStop();
   }

📝 배운 점

1. 테스트 코드

구현한 프로그램이 엄청 크지도 않지만, 테스트 케이스의 중요성을 많이 느꼈다. 메소드별로 테스트 코드를 돌리고 나면, 전체 프로그램에서 오류가 발생했을 때 어디서 발생했는지 파악하기 쉬워지는 것 같다. 그리고 테스트 코드를 쓰면서 스스로의 코드를 피드백할 수 있다는 점이 좋았다.

확실히 그냥 모든 기능 구현 뒤에 프로그램을 실행시켜 에러 원인을 찾는 것보다, 한 기능별로 테스트를 작성하는게 오류 원인도 잘 찾고 안전하게 개발할 수 있는 것 같다. 그리고 스스로의 코드에 확신?도 생기게 된다. 이 메소드까지는 맞으니 다른 부분이 틀렸을 거야 하고 문제 원인을 찾아가는게 빠르다!

2. 다른 사람들이 이해하기 쉬운 코드
  • 스스로 피드백 주기 다른 사람들이 이해하기 쉬운 코드로 만들려면 피드백을 받는 게 가장 좋겠지만, 일단은 스스로 피드백을 주고 있다. 구현하고 다음날 처음부터 흐름을 따라가다 보면 복잡하게 구현된 부분이나, 불분명한 메소드명이 보일때가 있다. 각 메소드별 테스트 코드를 작성하는 것도 메소드명/메소드 분리가 적절히 잘되었는지 판단하는 좋은 도구가 되는 것 같다.
3. A or B ??

답이 있는 것 같으면서도, 깊게 가면 답이 없는 것 같아서 재미있다. 그 적정선을 찾아가는 과정인 것 같다. 좋은 구현을 위해 하나를 중요시하면 다른 걸 놓칠 수 있으니, 적절한 선에서 조정하고, 계속 고민하다보면 스스로의 기준이 생기고.. 이 과정을 시작한 느낌이다. 예를 들어

  • 메소드를 작게 분리하는 게 좋지만, 너무 많은 메소드는 오버 프로그래밍 될 수 있다.
  • 예외 처리도 모든 케이스마다 명확한 예외를 직접 만들어주면 좋겠지만, 그러다보면 프로그램이 커질 때 오히려 복잡해질 수 있다.
  • 문자열은 static 하게 상수화하는 게 좋지만, 또 그러다보면 인스턴스 변수가 너무 많아질 수 있다.

계속 여러 시도해보면서 스스로 적정 기준선을 찾아가는 게 중요한 것 같다.

📚 공부할 것들!

(나중에 스스로 보려고 작성)

  • Collections 자료구조 전체 정리
  • Stream 사용법 공부
  • 클린 코드 자료 공부
  • 테스트 코드 Assertj 공부

개인적으로 좋은 설계를 위해 고민하는 과정이나, 클린코드를 위해 리팩토링하는 과정이 재밌었다. 스프링 프레임워크로 프로젝트할때는 이런 고민을 할 생각도 못했던 것 같다. MVC이더라도 왜 이게 편리한 지? 딱히 못 느꼈다. 근데 이번 주차처럼 이런 고민이라면 진짜 재밌게 일할 수 있겠다싶다..!

그리고 프리코스에서 배워가는 게 많은 것 같아 좋다. 다른 동기분들이 고민하는 내용들이나, 과제의 요구사항들로 생각할게 많아지고, 어떤 걸 앞으로 공부해야 할지 감이 잡히는 듯해서 재밌다 🤓 확실히 이론 공부만 할 때보다 실제로 구현하면서 안좋은 설계도 해보고 리팩토링하는 게 재미있고 배우는 느낌이 든다.