모눈종이에 사각사각

[프리코스] 2주차 회고 본문

활동기록

[프리코스] 2주차 회고

모눈종이씨 2022. 11. 9. 15:56

0. 시작

본격적인 미션이 시작된 것 같다고 느낀 한 주였다.

지난주인 1주차의 목표는 Git과 미션 사이클에 익숙해지는 것이 목표였다.

2주차는 아래의 메일 본문 내용의 일부에 나와있듯이, 함수를 분리하고, 함수별로 테스트를 작성하는 것에 익숙해지는 것이 목표였다.

2주차 메일 본문 일부

코드를 작성하면서 내가 한 고민사항을 적어보려고 한다.

조금 길어질 수도 있지만, 사고과정 또한 하나의 성장과정이라고 생각해서 기록해두면 좋을 것 같다.

1. 기능 요구사항

2주차 미션은 숫자야구이다.

지난주와 동일하게 진행방식은 기능요구사항, 프로그래밍 요구사항, 과제 요구사항을 다 구현하는 것이었다. 

 

기능을 구현하기 전 기능 목록을 만들고 기능 단위로 커밋을 해야했기에 기능 요구사항을 읽으면서 구현할 기능을 정리했다. 

 

게임 예시를 공책에 적어가면서 필요할 것 같은 기능들을 정리했다.

역시나 한 번에 작성하기는 굉장히 어려운 일이었다.

코드를 설계하다보니 중간중간 빠뜨린게 있어서 "docs(readme): 구현할 기능 목록 추가" 라고 커밋 메시지를 남기며 문서를 수정했다.

처음에 생각할 때는 "이정도면 다 적은거겠지?" 라고 생각했는데, 더 분리해야 했을 때가 있어서 난감했다.

 

그렇게 나온 나의 기능목록!

아직은 미숙하긴 하지만 나름대로 최대한 작게 나눠봤다.

구현할 기능 목록 - docs/README.md

2. 프로그래밍 요구사항

프로그래밍 요구사항의 가장 큰 핵심은 자바 코드 컨벤션 가이드를 준수하며 프로그래밍 하는 것이었다.

 

지난주에도 느꼈지만, 이걸 지키는게 상당히 어렵고 생각할 게 많다는 것을 알게 되었다.

처음에는 자바 코드 컨벤션 가이드를 다 지켜야 한다는 압박감이 있었다.(나 혼자만의...)

 

그러다보니 코드를 짜는게 너무 힘들어졌다. 이렇게 해서는 안되겠다는 생각을 했고,

최소한의 조건만이라도 우선 만족시키며 하나씩 개선해나가자! 라는 목표를 가지게 되었다.

 

indent 깊이 2까지 허용, 3항 연산자 쓰지 않기, 메서드 최대한 작게 만들기, 테스트 코드로 확인하기

이들을 지키기 위해 노력했다.

3. 사용자 입력값을 어떻게 받을까?

입력 받는 것에서부터 막혔다. 어떻게 하지...? 고민하다가 침착하게 안내사항을 다시 읽어보았다.

역시나. 주어진 Console API가 있었다.

안에 readLine 메서드가 있어서 이를 활용하여 해결했다.

4. 사용자의 입력값을 어떻게 저장할까?

처음에는 왼쪽처럼 클래스를 만들었다. 그렇게 되니 문제점이 생겼다.

나중에 컴퓨터가 생성한 값이랑 비교할 때 곤란해진 것이다. 

ArrayList에 들어가있으면 인덱스 값을 증가시키면서 하나씩 비교해주면 되는데 추가적인 과정이 들어갈 것 같았다.

그래서 ArrayList에 저장하는 방향으로 바꾸어주었다.

5. 테스트 코드 이해하기

주어진 테스트 코드를 처음 봤을 때 이게 도대체 무슨 뜻일까...? 하는 생각이 먼저 들었다.

메서드 명에 어떤 테스트인지 적혀 있어서 뭔지 알긴 알겠는데, 안에 로직을 모르니 답답했다.

이럴때는 하나씩 뜯어보는게 답이다 라고 생각해서 파고들었다.

오래 걸리긴 했지만 분석해가며 얻어가는 게 많았다.

테스트 코드의 작동 원리도 알게 되었고, junit과 assertj의 사용법도 조사했다.

 

또한 람다식이 아직 익숙하지 않아 복잡한 식을 보면 이해가 잘 안되었다.

그래서 람다식을  적용하기 전의 코드도 작성해보면서 이해하려 노력했다.

자세한 사항은 [우테코 프리코스 2주차] NsTest 분석[우테코 프리코스 2주차] Assertions 분석 에 포스팅해놓았다.

6. 테스트 코드 작성하기

이번주 목표 중 하나인 테스트코드 작성을 해보았다.

아직 잘 작성하는 것은 아니지만, 기본적으로 테스트 할 수 있도록 작성했다.

정상입력과 비정상 입력일 경우 모두 작성하여 예외 처리도 잘 되었는지 확인했다.

내가 하나하나 입력해서 테스트 하는 것이 아니라 이렇게 테스트 코드를 통해 한번에 테스트를 할 수 있다니..

너무나도 편리하고 효율적인 방법이다.

 

그리고 System.out.println 테스트 코드를 작성할 경우 어떻게 해야 하는지 고민이었다.

반환 값이 없는데 어떻게 작성하지? 고민을 하며 공부했다. 

관련 공부 내용은 System.out.println() 단위 테스트하기 에 작성해두었다.

 

이렇게 작성한 모든 테스트를 다 통과했을 때의 기쁨은 말할 수 없을 정도이다.

(물론 내가 생각하지 못한 다른 예외가 있을 수 있지만...!)

7. 메서드 분리

7.1. main 메서드 분리

 

위의 이미지는 처음에 내가 설계한 main 메서드 안의 코드이다.

굉장히 산만하다. 여러 기능들이 한 군데 모여있는 느낌이었다. 기능별로 구분을 해보았다.

 

좌: 리팩토링 전 // 우: 리팩토링 후

그래서 위와 같이 세 기능으로 구분해보았다.

① 사용자에게 입력을 받아 처리하는 기능

② 정답인지 확인하는 기능

③ 재시작을 결정하는 기능

 

이렇게 메서드로 만듦으로써 main 메서드에서는 게임 시작, 정답 확인, 재시작 가능 여부를 확인만 하면 되고, 나머지는 Game 클래스안의 메서드에서 처리할 수 있게 되었다.

 

 

7.2. Game클래스의 start메서드 분리

Game의 start메서드 안에는 사용자의 입력에 관한 부분이 있다.

Game 클래스는 "게임"에 관련된 건데, 사용자의 입력에 관한 부분이 있다는 것은 기능이 분리 된게 아니라고 생각했다.

여러 기능이 한 메서드 안에 들어가있는 것이라 판단하여 분리하였다.

사용자의 입력은 UserInputNumbers의 getUserInputNumbers 메서드를 통해 받고, 이를 compareNumbers 메서드에서 비교한다.

 

7.3. Game클래스의 canRestart메서드 분리

conRestart 메서드에서도 사용자 입력 관련된 부분이 있어서 위의 사례와 같은 이유로 사용자 입력 관련된 부분은 InputNumbers 클래스로 옮겨주었다.

 

8. static 키워드

8.1. static 변수

테스트 코드를 하나씩 돌렸을 때는 괜찮았는데, 여러 개를 한꺼번에 돌리니까 문제가 발생했다.

왜 그러는지 디버깅을 해보았더니 static 변수로 작성된 isContinue 때문이었다.

isContinue가 한 번 false로 되어서 재시작이 종료 된 후, 다른 테스트케이스로 넘어가면 static이기 때문에 isContinue는 계속 false 상태일 것이다. 매 테스트를 시작할 때 초기화 되지 못한 것이다.

게임을 한 번 시작할 때 초기화 되는게 올바르므로 main의 지역 변수로 변경하였다.

 

 

8.2. static 메서드

사용자의 입력을 얻는 부분을 static 메서드로으로 바꿈으로써 인스턴스를 생성하지 않고도 사용할 수 있도록 하였다.

9. 상수

하드코딩을 하면 특정 값을 변경하고 싶을 때 하나씩 다 변경해야 하는 경우가 생긴다.

이를 막기 위해서 상수를 선언하여 사용하면 선언한 한 곳만 변경하면 되므로 유지보수에도 좋다.

constant 패키지를 만들어 안의 클래스에 상수들을 관리해주었다.

10. 접근제어자

클래스 내부에서만 쓰는 메서드들의 접근제어자를 public에서 private으로 바꾸어주었다.

public class InputNumbers {

    public static List<Integer> getInputNumbers() {
       // 코드 생략
    }

    private static List<Integer> makeList(String userInput) {
        // 코드 생략
    }

    public static String getControlNumber() {
        // 코드 생략
    }

    private static boolean validInputNumber(String numbers) {
        // 코드 생략
    }

    private static void validInputControl(String num) {
        // 코드 생략
    }

    private static boolean isValidDigit(String num) {
        // 코드 생략
    }

    private static boolean duplicateNumber(String numbers) {
        // 코드 생략
    }

    private static int numberCount(String numbers, char num) {
       // 코드 생략
    }
}

 

이렇게 함으로써 외부에서는 getInputNumbers메서드와 getControlNumber메서드로만 접근할 수 있게 된다.

 

11. 소감

제출 완료!

예제 테스트 결과가 잘 나오지 않을까봐 기다리는 동안 조마조마했다. 그래도 잘 통과되어 다행이다.

 

같은 실행 결과를 내지만, 코드를 배치하는 방법은 완전히 다를 수 있다는 사실을 이번 미션을 통해 직접적으로 경험할 수 있었다. 구현도 중요하지만 “설계”가 얼마나 중요한지 알 수 있었다. 또한 테스트 코드를 통해 편리하게 예외 상황을 생각할 수 있었고, 기능이 잘 작동했을 때는 성취감 또한 느낄 수 있었다. 다방면에서 많은 깨달음을 얻을 수 있는 미션이었다.

Comments