겸손하기 꾸준하기 건강하기

우테코 6기 프리코스(back-end) 1주차 회고 본문

woowacourse/백엔드 6기 프리코스

우테코 6기 프리코스(back-end) 1주차 회고

seminss 2023. 10. 27. 20:06

 

 

우테코 6기 프리코스를 시작하게 되었다. 😀

좋은 코드를 위해 고민했던 부분들 위주로 작성하고, 리뷰 받은 부분, 소감을 작성하겠다.

 

미션 - 숫자 야구


1일 차 (Oct 19, 2023)

우선 컨셉을 잡고 간단하게 기능 명세서를 작성해 보았다.

컨셉

더보기

컨셉은 그냥 이해하기 편하게 주저리주저리 적어본 거라, 올릴까 고민했지만.. 남겨본다ㅏ..^^

 

 

숫자 야구를 할 수 있는 게임이 있다.
이 게임은 나를 상대하는 안내인과, 상대편, 게임 총관리인이 있다.
상대편, 총관리인의 얼굴은 절대 볼 수 없다.
3자리 숫자를 입력해서 정답을 맞히면 된다.

총관리인은 게임의 소유주이자, 사실상 절대 권력자다.
평소에는 마음이 넓어서, 한 사람도 여러 판을 계속할 수 있도록 허용한다.
안내인이 현재 손님이 한 판 더 하고 싶어 한다는 소식을 들으면, 한 판 더 할 수 있도록 허용한다.
하고 싶어 하지 않을 때는 종료해 주면 된다.
안내인에게 손님이 잘못된 입력을 했다는 소식을 전달받으면, 화가 나서 그 즉시 게임을 완전히 종료해 버린다.

안내인은 유저를 상대하는 사람, 또는 전달자이다.
유저에게 안내를 하거나, 상대편의 숫자를 전달하거나, 결과를 알려준다.
안내인에게는 귀와 입이 달려있어서 귀로 유저의 답변을 듣고, 입으로 안내나 결과를 말해준다.
결과는 기계를 통해 연산하고, 값을 확인해 유저에게 말해주면 된다.
우선은 한 판이 끝날 때까지는 유저를 붙잡아 둔다.
만약 유저가 게임을 다시 하고 싶어 하거나, 잘못된 입력을 하는 등의 이벤트가 발생하면 총관리인에게 전달한다.

볼, 스트라이크, 낫싱을 판단할 수 있는 기계는 따로 있고, 안내인만 사용할 수 있다.

랜덤 한 1자리 수를 뽑아낼 수 있는 기계가 있고, 상대방만 사용할 수 있다.

상대방은 안내인 뒤에 숨어있는, 내가 공략하는 상대다.
랜덤 한 1자리 수를 뽑아낼 수 있는 기계를 사용해, 총 3자리 수를 만든다.
유저가 1판을 시작한다는 이야기를 안내인에게 들으면, 즉시 3자리 수를 만들어 안내인에게 전달한다.
한 게임당 한 판만 3자리 수를 만들면 된다.

머릿속에 대충 그림이 그려졌으니, 이를 바탕으로 기능 명세서를 작성해 보았다.

 

기능 명세서

- [x] 사용자(User)
  - [x] 서로 다른 3자리의 수 
  - [x] 재시작/종료를 구분하는 1과 2 중 하나의 수 입력


- [x] 매니저(Manager)
  - [x] 게임 한 판 관리
    - [x] 안내 문구 출력
    - [x] 컴퓨터 숫자 생성 함수 호출
    - [x] 사용자 입력, 가공 메서드 호출
    - [x] 게임 결과 반환 메서드 호출
  - [x] 사용자의 게임 재시작 여부 체크, 반환


- [x] 총관리자(Game)
  - [x] 게임 매니저 관리


- [x] 상대방(Computer)
  - [x] 서로 다른 랜덤한 수 3개를 조합


- [x] 볼, 스트라이크, 낫싱을 판단하는 어플리케이션


- [x] Exception 생성 어플리케이션
  - [x] 3자리 숫자에 대한 입력 Exception 처리
    - [x] 입력이 3자리가 아닌 경우
    - [x] 숫자가 아닌 경우
    - [x] 서로 다른 숫자가 아닌 경우
  - [x] 재시작 여부에 대한 입력 Exception 처리
    - [x] 숫자가 아닌 경우
    - [x] 1,2가 아닌 경우

- [x] 안내 문구 대본
  - [x] 게임 시작 문구 안내
  - [x] 숫자 입력 안내
  - [x] 게임 결과 안내
    - [x] 하나도 없는 경우
    - [x] 3개의 숫자를 모두 맞힐 경우
    - [x] 일부만 맞출 경우
  - [x] 게임 종료 문구 안내

클래스 명과 메서드 명, 변수명 다 마음에 안 들었지만,, 어차피 수정할 거니까!!

초안은 내가 이해하기 쉽게 작성하려고 했던 것 같다.

 

1일 차는 여기서 마무리했다.

 


2일 차 (Oct 20, 2023)

2일 차는 기능 개발 위주로 진행했다.

 

기능 명세서에서 나눠 작성한 클래스별로 개발을 진행했다.

 

안내문구 대본(OutputView), 상대방(Computer), 사용자(User), 예외 판단(ExceptionHandler), 게임 결과 판단(ResultMaker) 까지는  모듈 테스트를 함께 만들며 개발했다.

 

사실 테스트를 직접 짜본 적이 손에 꼽을 정도인 1ㅅ,,, (사실상 없었다)

이번에 어떤 테스트가 필요할까 고민하고 짜본 게 처음이었는데,,

귀찮기는 하지만 생각보다 재밌었다!

 

Mock 객체를 사용해 보면서,

늘 "입력이 있는 테스트는 어떻게 자동화를 하지??"라는 의문을 품어왔던 나로서는,,, 되게 신기하고!!

되게 되게 편하구나 ~~라는 걸 느꼈던 것 같다. 

 

내 코드가 잘 굴러가는지 "자동화"시켜서 확인해 보는 것의 이점을!! 이번에 많이 느꼈다.

(프린트를 찍는 것과는 비교도 안되게 편하다..)

 

아무튼! 그 뒤로,

매니저(Manager)와 총관리자(Game)는 앞전에 개발한 모듈들을 불러와 사용하는 클래스였기 때문에, 따라서 모듈을 통합하는 식으로만 구현을 했고, 따로 테스트를 작성하지 않았다.

바로 우테코 측에서 제공해 준 테스트로 테스트를 해봤다. (사실 이때까지만 해도, 통합 테스트까지는 작성하기 귀찮았었나 보다..)

 

2일 차는 애플리케이션이 잘 돌아가는 걸 확인하고 마무리했다.

 


4일 차 (Oct 22, 2023)

3일 차는 시험공부 이슈로,, 건너뛰고 4일 차부터 리펙토링을 진행했다.

 

열심히 리펙토링만 한 4일 차 ,,,

 

 

1. 클래스명, 메서드명, 변수명을 통일시켜 주었다.

기존의 이름들은 범용적이지 않은 네이밍을 하고 있었고, 어떤 역할을 하는지 명확하지 않은 경우가 있었다.

이러한 부분들이 개발을 하면서도 불편했고, 이후 코드 리뷰를 받을 때도 문제가 될 수 있겠다 생각되었다.

때문에, 대대적인 네이밍 작업을 거쳤다. 정~말 오래 걸렸다... 🥹 (2주 차에는 처음부터 잘 생각하고 네이밍 하자..)

 

2. Manager(현 GameController)의 메서드를 분리해 주었다.

한 메서드에서 각 객체를 불러오고 처리했던 로직들을, 부분 부분 묶어 메서드로 분리해 주었다.

 

3. OutputView의 출력 메시지를 enum으로 선언해 주었다.

각 출력문이 전부 "~~~"와 같이 하드코딩 되어있어서, 이 부분을 Enum으로 설정해 불러와 사용해 주었다.

 

 

정리하자면 4일 차에는, 큰 흐름을 변경하지 않는 선에서

클래스 내 메서드 분리를 해주고, 네이밍을 조금 더 범용성 있게 수정해 줬다. (젤 오래 걸림 🙄)

좀 더 가독성을 높이기 위해 디렉터리 구조를 추가해 주고,

이때까지 작성된 코드를 바탕으로 README를 업데이트해 줬다.

더보기
- [x] 사용자 입력 처리 : `UserInputHandler`
  - [x] 입력 받기
  - [x] 정수 List 로 변환
  - [x] 정수로 변환


- [x] 사용자 입력 검증 : `InputValidator`
  - [x] 서로 다른 3자리의 수에 대한 검증
    - [x] 입력이 3자리가 아닌 경우
    - [x] 숫자가 아닌 경우
    - [x] 서로 다른 숫자가 아닌 경우
  - [x] 재시작/종료를 구분 하는 수에 대한 검증
    - [x] 숫자가 아닌 경우
    - [x] 1,2가 아닌 경우


- [x] 게임 관리 : `GameHandler`
  - [x] 게임 초기화
    - [x] 컴퓨터 숫자 생성기로, 랜덤한 3자리 숫자 생성
  - [x] 게임 운영 관리
    - [x] 한 판 진행
      - [x] 사용자 입력이 정답인지 체크
    - [x] 게임 재시작 여부 묻기
    - [x] 재시작을 원하면 다시 게임 한 판
  - [x] 사용자 입력 관리
    - [x] 사용자 입력 검증
    - [x] 사용자 입력 처리
  - [x] 안내 문구 출력 


- [x] 전체 게임 관리 : `BaseballGame`
  - [x] GameHandler 관리
  - [x] IllegalArgumentException이 발생하면 종료


- [x] 컴퓨터 숫자 생성기 : `ComputerNumberGenerator`
  - [x] 서로 다른 랜덤한 수 3개를 조합


- [x] 결과 생성기 : `ResultGenerator`
  - [x] 볼, 스트라이크 갯수 연산 
  - [ ] 볼, 스트라이크 값 설정
    - [ ] 볼, 스트라이크 객체


- [x] 안내 문구 출력 : `OutputView`
  - [x] 게임 시작 문구 안내
  - [x] 숫자 입력 안내
  - [x] 게임 결과 안내
    - [x] 하나도 없는 경우
    - [x] 3개의 숫자를 모두 맞힐 경우
    - [x] 일부만 맞출 경우
  - [x] 게임 종료 안내
  - [x] 게임 재시작 안내


- [x] 출력을 위한 상수 값
  - [x] 에러 메세지 : `ErrorMessage`
  - [x] 게임 안내 메세지 : `GameMessage`

 

그런데 리펙토링을 하면서

 

예외가 터져야 성공인데 예외를 잡고 있었다던가,

실수로 Restart 메시지를 지워버렸다던가..

 

하는 자잘한 버그들이 자꾸 발생했다.

 

물론 잡는데 어렵지도 않았고, 수정도 어려운 버그들은 아니었지만 도대체! 

언제부터 발생했는지? 알 수가 없었다.

 

한 텀, 한 텀 리펙토링을 할 때마다 통합 테스트를 해줘야 할 필요성을 느꼈다.

그리고 어디서 어떻게 문제가 발생할지 모르기 때문에,

최대한 꼼꼼하게! 모든 가능성을 열어두고 테스트를 짜야겠다는 생각을 했다.

 


5일 차 (Oct 23, 2023)

시험 D-1,,,였던 5일 차.

 

 

1. 의존성 주입 (구현체에 의존하고 있었음)

기존에 GameController에서 나머지 객체들을 선언할 때 private static 객체명 객체  = new 객체명 

이런 식으로 선언을 하고 있어서, 생성자를 통해 외부에서 주입받을 수 있도록 수정해 줬다.

 

2. GameController에서 InputProcessor 분리

Input은 게임의 핵심 로직에서 약간의 거리가 있다 판단되어 두 객체를 분리해 주었다.

 

3. error 메시지 enum 만들기

모든 Exception이 IligalArgumentException으로 터지기 때문에 각각의 에러가 전부 같은 에러 메시지를 남기고 있었다. 따라서 에러 메시지를 지정해 주면 좋을 것이라 생각해 ErrorMessage도 enum으로 생성해 불러올 수 있도록 했다.

 

4. ResultFormatter 만들기

현재 OutputView는 다음과 같이 구성되어 있다.

public class OutputView {
    public void printStartMessage(){
        System.out.print(GAME_START_MESSAGE.getMessage());
    }
    public void printInputMessage(){
        System.out.print(PROMPT_FOR_NUMBER.getMessage());
    }
    public void printEndMessage(){
        System.out.print(GAME_SUCCESS_MESSAGE.formatMessage(GAME_DIGIT.getValue()));
    }
    public void printRestartMessage(){
        System.out.print(
                RESTART_CHOICE_MESSAGE.formatMessage(RESTART_CHOICE.getValue(),END_CHOICE.getValue()));
    }
    public void printResultMessage(GameResult gameResult){
        System.out.println(
                ResultFormatter.formatResult(gameResult.strike(),gameResult.ball()));
    } /** 여기 **/
}

그러나 기존 printResultMessage 메서드 내부에는, 아래 로직이 같이 존재했었다. 

    public static String formatResult(int strike, int ball){
        if (strike== GAME_DIGIT.getValue()){
            return strike+ STRIKE.getMessage();
        }
        if (strike==0 && ball>0){
            return ball+ BALL.getMessage();
        }
        if (strike>0 && ball==0){
            return strike+ STRIKE.getMessage();
        }
        if (strike>0 && ball>0){
            return ball+ BALL.getMessage()+ " "+ strike+ STRIKE.getMessage();
        }
        return NOTING.getMessage();
    }

 

역할을 분리해 주는 것이 좋을 것이라 판단되어, ResultFormater이라는 객체를 따로 정의해 분리했다.

 

사실, 리펙토링 후에도 view와 format 부분이 완전히 분리되었다고는 생각되지 않았는데, 결국은 view를 호출함으로써 formatting을 하는 로직이었기 때문이다. 이 부분은 계속 고민이 되었지만, 다른 방법이 생각나지 않아서 패스했었다.

그리고 추후 1주 차 미션이 끝나고 코드 리뷰를 하면서 더 좋은 방법을 찾게 되었다! (하단 코드 리뷰 부분 참조)

 

5. strike, ball을 담은 DTO로 게임 결과 저장하기

기존에는 List <Integer> 형태로 strike, ball 값을 주고받고 있었다. 그렇게 하니. get(0),. get(1) 이런 식으로 stike ball을 가져와야 했었고, 의미가 명확하지 않다 생각되었다. 그리하여 GameResult DTO를 정의해 사용하게 되었다.

 

이때, 자바 16부터 도입된 recored에 대해 알게 되었다. GameResult class에서 strike와 ball을 final로 선언하고, getter() 메서드만 선언했더니, IntellJ IDE에서 record 선언을 권장하길래,, 사용해 보게 되었다!

record는 모든 필드가 final이고, 이러한 필드에 대한 getter, equals(), hashCode(), toString() 메서드들이 자동으로 생성되는 새로운 형태의 클래스이다! 

 

기존 class를 사용하면 다음과 같이 선언하게 된다.

public class GameResult {
    private final int strike;
    private final int ball;

    public GameResult(int strike, int ball) {
        this.strike = strike;
        this.ball = ball;
    }

    public int getStrike() {
        return strike;
    }

    public int getBall() {
        return ball;
    }
}

그러나 record를 사용하면 다음과 같이 선언 가능하다.

package baseball.dto;

public record GameResult(int strike, int ball) {
}

record는 특수한 상황에서 사용하는 객체이기 때문에 상황에 맞다면 적절하게 사용하면 편리할 것 같다!

아무튼, GameResult DTO를 선언함으로써 Strike와 Ball의 상태 유지를 하기 더 편리해졌다!

 

 

5일 차에 통합 테스트를 구현하려고 했으나, Controller에서 선언받아 사용하는 객체가 많아 쉽지 않아 보였다.

찾아보니, 이런 부분을 쉽게 테스트를 하려면 인터페이스를 구현하면 된다더라.

지금 구현하는 java-baseball 같은 경우에 인터페이스까지 써서 코드를 늘려야 할 필요까지 있을까?라는 생각이 들어 굳이 하지 않았다. 대신 우테코 측에서 제공하는 Application 테스트+기능별 모듈테스트만 실행하면서 통과하면 다음 기능 구현,, 하는 식으로 진행했다.

 

2주 차에 설계를 할 때는 테스트하기 쉽도록 구현하는 법도 생각을 해보면 좋을 것 같다는 생각이 들었다!

 


7일 차 (Oct 25, 2023)

전공 시험에 시달리다 온 7일 차였다. (´•̥̥̥‸•̥̥̥`)

 

나름 꽤 구조를 잘 짜뒀다고 생각했었는데, 제출 당일에 다시 코드를 살펴보니,

막상 제출하기에는 너무 부족한 코드라는 생각이 들었다.

 

"내가 한 모듈화가 꼭 필요한 모듈화일까.."

"정적 변수로 입출력, 상수 구현해 둔 것들 과연 맞는 선택일까..? 코드가 너무 길어지는데 ㅜ_ㅜ"

 

그리하여 마지막날까지.. 다시 리펙토링을 거치게 되었다.

 

1. 문자열, 상수들 enum으로 선언한 것 static import를 해줬다.

기존에 enum으로 선언한 값들을 한번 호출하려고 할 때마다 ErrorMessage.DUPLICATE_NUMBER.getMessage(); 이런 식으로 호출하니까 정말 코드가 너~무 길어지고 가독성이 안 좋은 거다..

constant로 선언을 한 게 잘못된 걸까 고민을 막 하고 있었는데,, 갑자기 번뜩! static import..?라는 키워드가 생각이 났다.

그래서 찾아보니까 enum도 static import 가 되더라 |┐∵|┘

그래서 적용해 줬다. (근데 그래도 너무 길다 싶어서, enum을 사용하는 게 맞는 선택인지 고민되었지만..! 리뷰하면서 보니 다들 enum 사용하시더라! 잘한 선택이었다 :))

 

2. 제출날이 되어서야 눈에 들어온 입력 값 분리의 필요성..

"같은 입력을 받는다고 해서 같은 동작을 하는 게 아니다."라는 생각이 7일 차가 되어서야 들었다🥺

사용자 숫자 3자리 입력과, 재시작 숫자 1,2 입력받는 부분이 아예 다른 기능을 위한 것인데, 같이 묶어 처리를 하고 있었던 것이다.

 

분리가 필요해 보여 Input을 처리하는 객체인 InputValidator, InputConverter, InputProcessor 세 가지를 전부 분리했다.

 

그랬더니, Input 관련 객체가 무려 6개..?! 가 생겨버렸다.

심지어 1. 분리한 두 로직에서 중복되는 코드가 많아진다. 2.Controller에서 주입받아야 할 객체가 많아진다. 는 문제들이 생겨버렸다.. 일단 2번 문제는 Controller에 빌더 패턴을 적용해서 임시 조치를 했었다.

 

1번 문제에서는 제네릭을 고려했다. Guess와 Choice의 처리 로직이 대부분 비슷하고, 반환 타입만 달랐기 때문이다. (Inteter, List <Integer>) 그렇게 제네릭 타입을 적용을 해봤는데, GameController에서 오류가 났다.

살펴보니, 제네릭 타입을 선언할 때는 선언부에 타입을 명시를 해줘야 하는데, 나는 GuessInput과 ChoiceInput을 모두 GameController에서 사용하고 있었고, 타입 명시를 할 수가 없었기 때문에 오류가 나고 있었다. (한 클래스에서 두 타입을 모두 반환받아야 하기 때문에 선언할 때 서로 다르게 타입 명시가 불가능하다.)

 

따라서 제네릭을 사용하려면 결국은 GameController도 분리를 해줘야 했다 ㅋ.ㅋ.ㅋ.ㅋ....

 

그래서 결과적으로 다시 롤백시켜 버렸다. 사용자 숫자 3자리 입력과, 재시작 숫자 1,2 입력받는 부분을 그냥 하나의 InputProcessor 내에서 처리했다. 어떻게든 방법을 찾고 싶었지만 ㅠ.ㅠ 시간이 없어 급하게 마무리를 했다...

 

설계부터 꼬여서 고치기가 쉽지 않았던 문제라고 생각한다. (여기서 또다시 느껴지는 설계의 중요성.. 😶‍🌫️)

코드 리뷰를 하면서 다른 분들 코드를 살펴보니 다들 잘 나눠주셨더라...ㅎㅎㅎ

 

 

7일 차에 와서 든 생각은 ,, 정말 설계가 생각했던 것보다 더더더더더 중요하다는 것이었다!

설계할 때 내가 한 설계가 정말 기능이 잘 나눠졌는지. 그래서 한 클래스가 하나의 책임만 지는지!

내가 한 책임만 진다고 생각을 했지만, 그게 정말 진짜인지.. 고민을 두 번 세 번 해보는 자세가 필요하다고 느꼈다.

 

ISP 인터페이스 분리 원칙에 의하면, 인터페이스 여러 개가 범용 인터페이스 여러 개보다 낫다.

코드가 길어질까 고민하지 말고 일단 기능별로 잘 쪼갤 수 있도록 설계를 하고! 코드가 길어질까 고민된다면 중복되는 코드가 있지는 않은지.. 검토해서 코드를 재사용하는 방향으로 고려해 보면 좋을 것 같다.

 

 

최종적으로 업데이트된 README는 다음과 같다.

## 🌠 기능 목록 설계


- [x] 전체 게임 관리 : `BaseballGame`
  - [x] GameController 관리
  - [x] IllegalArgumentException이 발생하면 종료


- [x] 게임 관리 : `GameController`
  - [x] 게임 초기화
  - [x] 게임 운영 관리
    - [x] 한 판 진행
      - [x] 사용자 입력이 정답인지 체크
    - [x] 게임 재시작 여부 묻기
    - [x] 재시작을 원하면 다시 게임 한 판


- [x] 컴퓨터 숫자 생성기 : `ComputerNumberGenerator`
  - [x] 서로 다른 랜덤한 수 3개를 조합


- [x] 결과 생성기 : `ResultGenerator`
  - [x] 볼, 스트라이크 갯수 연산 
  - [x] 볼, 스트라이크 값 객체에 저장


- [x] 사용자 입력 관리 : `InputProcessor`
  - [x] 사용자 입력 검증 : `InputValidator`
    - [x] 입력 값이 없는 경우
    - [x] 입력이 정수가 아닌 경우
    - [x] 입력이 3자리가 아닌 경우
    - [x] 입력에 중복된 수가 있는 경우
    - [x] 입력이 1,2 가 아닌 경우
  - [x] 사용자 입력 처리 : `InputConverter`
    - [x] 문자열 -> 정수 변환
    - [x] 문자열 -> 정수 리스트 변환


- [x] 출력문 관리 : `OutputView`
  - [x] 게임 시작 문구 안내
  - [x] 숫자 입력 안내
  - [x] 게임 결과(스트라이크, 볼, 낫싱) 안내
  - [x] 게임 종료 안내
  - [x] 게임 재시작 안내

코드 리뷰 (Oct 26, 2023)

새로운 2주 차 미션이 나오는 날!

미션이 3시에 나오기 때문에 그전까지 커뮤니티에서는 코드 리뷰가 엄청 활발하게 이뤄졌다,,

 

25일에서 26으로 넘어가는 새벽..! (26일 9시 시험이 있었음) 디스코드에 잠깐 접속을 했는데, 다들 코드 리뷰를 벌써부터 활발하게 하고 계셨다.😵

 

사실 프리코스를 시작하면서 코드리뷰 하는 시간이 제일 기대가 되었기 때문에, 두근 세근.. 떨리는 마음으로 다른 분들이 짜신 코드를 구경했다.

 

와.. 그런데 너무 잘 짜신 분들이 많고. 나도 꽤 열심히 했다고 생각했는데, 진짜 모든 열정을 쏟아부으신 분들이 계셔서 너무 놀랐다.

갑자기 내 코드가 너무 부끄러워지고.. 이렇게 잘하시는 분들한테 내가 리뷰를 요청드리기도, 받기도 죄송하더라..ㅠㅠ

 

그래서 리뷰 글을 올리려다가 소심하게 댓글로 한 분한테 부탁을 드렸고 너무 감사하게도 리뷰를 꼼꼼하게 달아주셨다 🥹 진짜 정말 소중한 리뷰여따.. 그리고 같은 학교 친구랑도 상호 코드 리뷰 해줬는데 정말 도움이 많이 됐다!

1주차 리뷰받았던 사항

  1. 중복 예외 처리 - 자료 구조 Set을 사용해보면 어떨까요?
  2. 재시작 옵션을 굳이 int형으로 변환할 필요가 있었을까요?
  3. 중괄호 생략하는 습관이 있었는데, 고치면 좋을 것 같다. 컨벤션.
  4. 변수 네이밍
  5. 길어지는 조건식은 메서드로 빼자

아무래도 일주일간 한 프로젝트를 구현한 것이다 보니, 의도치 않게 토끼굴에 빠져버리는 경향이 있었다. (지엽적인 것에 몰두)

리뷰를 받은 덕분에 오히려 중요한! 너무 당연하게 생각해서 놓쳤던 문제들을 챙길 수 있었고, 내 코드에 대해 객관적으로 살필 수 있었다고 생각한다.

인상 깊었던 부분

리뷰를 받고, 잘 짜신 분들 코드 구경 다니면서 내가 고민되었던 분들, 어떻게 구현하셨는지도 참고했다.

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        //... strike, ball 판단해서 포맷팅 하는 로직
        return sb.toString();
    }

특히 인상 깊었던 부분은 5일 차에서 고민했던 ResultFormatter 부분!

GameResult 객체에서 toString을 오버라이드 해서 가져오니 역할과 책임이 명확하게 분리가 되어 너무 좋은 코드였다고 생각한다.

 

        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException(e.getMessage());

그리고 Exception을 가장 바깥에서 잡아서 print 해준 코드!

나는 InputValidator에서 Exception을 받아 바로 error 메시지를 날려줬었는데, InputValidator에서는 예외만 날려주고, 

BaseballGame에서 받아 sout(error)을 해줬으면 역할 구분이 더 잘 됐을 것 같다..! 2주 차부터는 나도 이렇게 해야지. ㅎㅎ

 

    public void addStrike() {
        this.strike = this.strike + 1;
    }

그리고 GameResult 값을 변경할 때 이런 식으로 객체 내부에 연산 로직 함수를 선언해서 값을 변경하는 것도 좋아 보였다.

나는 strike, ball 지역변수를 선언해서 연산 후 GameResult 객체를 생성하는 식으로 구현했었다.


 

다른 분들의 코드를 보면서 내 코드가 너무 부끄럽고, 다음 미션 하기가 너무 두려웠는데,,

코치 분께서 어제의 나보나 오늘의 내가 나으면 되고, 내일의 내가 오늘의 나보다 나으면 된다. 내 경쟁자는 어제의 나다!!!!

라고 말씀해 주셔서,  내 페이스 대로 미션을 계속 진행하면 될 것 같다는 용기를 얻을 수 있었다!!!! 

 

이제 2주 차 시작!!! 1주 차에 배운 내용을 바탕으로 2주차도 최선을 다해보자!

 

더보기

정말 즐겁게 미션을 했습니다.

사실 java-baseball은 작년 프리코스 미션이었기 때문에, 개인적으로 과제를 풀어본 경험이 있었습니다. 
당시에는 코드를 어떻게 짜야하는지 몰라서, 클래스 하나에 모든 기능을 넣어 구현했었습니다. 
로직이 이리저리 엉켜있던 탓에, 모듈화를 하고 싶어도 거의 불가능했습니다.

이번에는 객체 지향적으로 프로젝트를 구성해 보려고 했습니다.
프로젝트의 요구사항을 꼼꼼하게 확인하고, 나름의 체계로 설계 후 개발을 했습니다.
설계대로 개발을 진행하자, 모듈화 하려고 했을 때 메서드 분리, 클래스 분리 등의 작업이 수월했습니다.
제가 설계한 대로 딱 들어맞고, 톱니바퀴가 굴러가듯 각 모듈이 조립되는 것을 보며 희열을 느꼈습니다.

이번 과제 미션에서는 써보고 싶었던 문법들, 패턴들을 최대한 적용을 해보려고 했고, 리펙토링 하는데 많은 시간을 투자했습니다.

사실 '제네릭'이라는 개념은 자바 문법을 공부할 때마다 늘 이해가 가지 않던 문법이었고, 몰라서 못 썼던 개념이었습니다.

이번 과제 미션에서, 세 자리 숫자를 입력받는 부분과, 재시작 여부를 입력받는 부분이 서로 다른 기능이라 판단했습니다. 그러나 두 기능의 처리 로직은 동일하고, 반환 타입만 달랐습니다. 때문에 '객체를 나누면서도, 중복된 코드를 줄이고, 필요한 부분은 재활용할 수 있는 방법이 없을까' 찾아보게 되었습니다. 그리고 이럴 때 사용하는 것이 제네릭이라는 것을 알게 되었습니다.

이런 식으로 코드 개선을 위해 계속 코드를 검토하면서, 자바 언어의 특성에 대해서도 깊게 고민해 볼 수 있었습니다. 1주 차에 느낀 점들을 바탕으로, 2주 차에는 인터페이스, 제네릭 등의 자바 문법을 좀 더 고려해서 더 좋은 설계를 해보고 싶습니다.

더불어, 리펙토링 할 때마다 코드를 수정하면서 테스트가 실행되지 않는 경우가 종종 생겼는데, 이때마다 모듈 테스트, 통합 테스트, 그리고 통합테스트의 통합 테스트.. 와 같은 식의 촘촘한 테스트 작성이 중요하다는 걸 체감했습니다.

이에, 다음 주차 목표를 잡아봤습니다.

1. 역할&책임 분리해서 설계하고, 자바 문법 적극 활용하기!
2. 테스트는 모듈 단위부터 잘 짜기

순수 자바로만 프로젝트를 만들 기회가 많지 않기 때문에 이번 1주 차에서 너무 즐겁게 문제를 풀었고, 2주 차도 정말 기대가 됩니다.  2주 차에는 1주 차를 하면서 느꼈던 부족한 점들, 공부해 보고 싶은 부분들을 더 챙겨서 더 좋은 설계, 더 좋은 코드를 작성해 보고 싶습니다!

 

 

https://github.com/seminss/java-baseball-6/tree/seminss

 

GitHub - seminss/java-baseball-6

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

github.com