[우아한테크코스 백엔드 5기] 프리코스 4주차 후기 (다리 건너기)

4주간 진행되었던 프리코스 마지막을 장식할 미션은 바로 다리 건너기 게임입니다.

기능 요구 사항은 아래와 같으며 마지막 요구사항인 만큼 추가된 요구사항들이 정~~말 많았는데요 

그 만큼 새로 느끼고 배우는 점들도 많았고 요구사항들에 맞춰 리팩토링 하는 재미가 쏠쏠했었습니다 ㅎㅎ 

 

4주차 다리 건너기 미션 : https://github.com/SuhHyeonjun/java-bridge
직접 작성한 코드 : https://github.com/woowacourse-precourse/java-bridge/pull/131

 

이번 4주차의 목표 요구사항도 많아 졌고, 이전 미션들에서 받았던 피드백들을 합쳐서 좀 많았습니다

확실히 이렇게 목표 체크리스트를 만들고 나니 과제를 완성한 후 목표를 하나씩 지켰는지 확인하는 과정에서

요구사항을 충족했는지 다시 돌아 볼 수 있었고 하마터면 충족하지못할 뻔 했던 요구사항들을 찾아서 수정했습니다. 🤭


4주차를 통해 느낀점과 배운점

마지막인 만큼 공통 피드백의 양과 요구사항이 많았고, 3주차 미션 피어 리뷰를 통해 깨달은 점들 또한 많았는데요.

그 점들을 하나씩 정리 해보려 합니다.

 

비즈니스 로직과 UI 로직을 분리한다

"비즈니스 로직과 UI 로직을 한 클래스가 담당하지 않도록 한다. 이는 단일 책임의 원칙에도 위배된다."

단일 책임 원칙이란 ?
하나의 객체는 반드시 하나의 동작만의 책임을 갖는다.

비즈니스 로직과 UI로직을 예로 3주차 미션이였던 로또의 PurchaseLotto 클래스를 예를들어보겠습니다.

위 클래스는 로또 구입 개수를 구하는 비즈니스 로직 getLottoAmount()구입한 로또 개수 만큼의 로또 번호를 출력하는 UI로직 printLottos()한 클래스에 선언되어 있습니다.

 

이를 비즈니스 로직과 UI로직을 분리하려면 어떻게 해야할까요?

 

PurchaseLotto 클래스에서는 로또 구입 개수를 구하는 비즈니스 로직 getLottoAmount()만 있고,

OutputView(출력) 클래스를 만들어 UI로직 printLottos()을 분리하고,

PurchaseLotto 클래스에서 구한 purchaseAmount를 getter를 통해 데이터를 전달할 수 있습니다.

 

JAVA가 객체지향언어인 것은 알았지만, 어떤식으로 객체지향의 원칙을 지키며 프로그래밍해야하는지에 대해서는 솔직히 잘 알지 못했었는데요..

이번 피드백을 통해 객체지향 프로그래밍을 하기 위해선 어떤식으로 구현을 하는게 올바른가를 배울 수 있었습니다.

 

 

연관성이 있는 상수는 static final 대신 enum을 활용한다

ErrorMessage 열거형의 일부

이처럼 String 타입으로 서로 연관성이 있는 상수들을 enum을 사용해서 정의하고

어떤 메시지를 담는지, 상수의 이름을 통해 이 메세지의 의도를 알 수 있도록 사용하였습니다. 

 

문자열, 숫자 등의 값을 하드 코딩하지 않는다, final 키워드를 사용해 값의 변경을 막는다.

변경 되지 않을 값들을 상수로 만들고 이름을 부여해 이 변수의 역할이 무엇인지 의도를 드러내야 합니다.

아래 코드는 다리 길이 생성 시 입력 가능한 숫자 범위 ( 3이상 20이하) 인데 bridgeSize < 3 || bridgeSize > 20 으로 숫자 값을 하드 코딩 하지 않고

다음과 같이 숫자를 MIN_NUM = 3 (최소 수), MAX_NUM = 20 (최대 수) 이런식으로 변경 되지 않을 값들을 상수로 만들어 이 변수의 역할이 무엇인지 의도를 드러내었습니다. 

    private static final int MIN_NUM = 3;
    private static final int MAX_NUM = 20;
    
    private static void validateInputSize(int bridgeSize) {
        try {
            if (bridgeSize < MIN_NUM || bridgeSize > MAX_NUM) {
                throw new IllegalArgumentException();
            }
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException(ErrorMessage.BRIDGE_SIZE_INPUT_RANGE.getErrorMessage());
        }
    }

 

그리고 final에 대해 새롭게 알게된 것이 있는데, List에 final을 선언하여 list 변수의 변경은 불가능하지만,

list 내부에 있는 변수들은 변경이 가능하여 문자열을 계속 추가할 수 있다는 것을 새롭게 알게 되었습니다.

이 처럼 list 변수를 변경하지 않을 것 이라면 상수로 선언하는게 좋을 것 같네요.

final List<String> list = new ArrayList<>();
list.add("Hello");
list.add("World");

 

 

필드(인스턴스 변수)의 수를 줄이기 위해 노력한다

지역변수는 메소드가 호출될 때 메모리에 공간을 할당받고 종료 시 해제되지만 

필드(전역변수)는 프로그램이 실행되어 종료될 때까지 메모리상에 남아있게 됩니다.

또한 필드의 수가 많은 것은 객체의 복잡도를 높이고, 버그 발생 가능성을 높일수 있다고 합니다.

그렇기 때문에 이번 미션에서는 처음 구현할 때 필드를 이용하여 구현한 후 코드가 정상적으로 실행되는지 확인 후,

리팩토링을 통해 필드를 줄이기 위해 노력을 하였는데요.

 

다리 길이를 입력하는 메서드의 경우 처음에는 로또에서도 사용했던 방식과 같이 아래처럼 필드를 사용했었습니다.이런식으로 필드를 사용했던 이유는 다른 클래스에서 이 변수를 사용하는 메서드가 매개변수를 통해 변수를 받지 않고 바로 가져와 사용할 수 있어 편했고 익숙했기 때문이였습니다.

    public static String inputSize;
    
    public int inputBridgeSize() {
        inputSize = readLine();
        System.out.println();
        try {
            validateInputSizeException(inputSize);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return inputBridgeSize();
        }
        return Integer.parseInt(inputSize);
    }

 

하지만 이번 피드백의 "필드의 수를 줄이기 위해 노력한다"를 반영하기 위해 아래처럼 지역변수로 변경하였고,

InputView 클래스의 일부

 

이전의 방식이였다면 inputSize에 inputBridgeSize() 메서드를 대입하지 않고

inputBridgeSize()호출 후 필요한 필드를 가져와 매개변수로 넣는 방식을 사용했었겠지만,

 

BridgeGameController 클래스의 게임 시작 메서드(start)에서 다음과 같이 활용하며

전체 코드들의 필드의 수를 최소한을 줄여 구현할 수 있었습니다.

BridgeGameController 클래스의 일부

 

예외에 대한 케이스도 테스트한다

이 전까지는 테스트 코드를 작성할 때 기능적인 부분에 대해서만 테스트 코드를 작성했었습니다.

하지만 이번 피드백을 통해 프로그램에서 결함이 자주 발생하는 부분 중 하나는 경계값이므로

예외 또한 꼼꼼하게 확인( 테스트 )해야 한다는 것을 느끼게 되었습니다.

 

그래서 이번에는 예외 메서드들에 대한 테스트 코드들을 작성하면서 JUnit5와 한 층더 친해 질 수 있었습니다.

예외 메서드 테스트 코드의 일부

 

반복되는 값 출력은 문자열에 담아서 한번에 출력하도록 한다.

JaeHongDev님의 피어 리뷰를 통해

반복 작업은 시스템의 리소스를 많이 필요로 하는 작업이기에 반복문을 통해 출력은 많은 양의 리소스를 소모한다는 것을 알게 되었습니다.

 

기존 코드는 아래와 같이 반복문을 통해 출력을 하는 방식이였습니다.

    private static void printLottos(List<List<Integer>> lottos) {
        for (int i = 0; i < purchaseAmount; i++) {
            lottos.add(Lotto.createLotto().getNumbers());
            System.out.println(lottos.get(i));
        }
        System.out.println();
    }

 

피드백을 반영하여 수정한 코드는 아래와 같습니다.

결과를 문자열로 저장하여 한번에 출력하는 방법을 통해 리소스의 소모를 줄일 수 있다는 것을 새롭게 알게 되었습니다.

    private static void printLottos(List<List<Integer>> lottos) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < purchaseAmount; i++) {
            lottos.add(Lotto.createLotto().getNumbers());
            stringBuilder.append(lottos.get(i)).append("\n");
        }
        System.out.println(stringBuilder.toString().trim());
    }

 

IllegalArgumentException과 IllegalStateException 차이

이번에는 IllegalArgumentException만 발생시키는 것이 아닌 경우에 따라 명확한 유형을 처리해야 한다는 요구사항이 있었습니다.

IllegalArgumentException과IllegalStateException은 둘 다 RuntimeExcpetion을 상속 받았으며 차이점으로는 다음과 같습니다.

예외 설명 예시
IllegalArgumentException 부정한 인수, 또는 부적절한 인수를
메서드에 건네준 것을 나타내기 위해서 발생 됩니다
- 입력 제한 길이가 3글잔데 3글자를 초과한 경우
- 양수를 넣어야하는데 음수를 넣은 경우
IllegalStateException 부정 또는 부적절한 때에
메서드가 불려 간 것을 나타냅니다.
- 찾아야하는 결과 값이 없는데 메서드가 호출 된 경우

위 차이점을 바탕으로 앞으로 예외 처리 시 적절한 유형으로 처리하도록 해야겠습니다.

 

요구사항을 정말 꼼꼼히 읽자 !

로컬에서 어플리케이션을 실행했을 때 예외 처리도 잘되었고, 테스트 코드또한 성공했었습니다.

그래서 제출 시간이 되어 풀리퀘스트를 보내고, 우테코 사이트에 과제를 제출하는데

당연히 성공할 줄 알았던 테스트가 아래 처럼 실행에 실패했다고 나오더라구요 .. 😭

테스트 오류도 아니고 실패라니 무엇인지 문제인지 몰라 기존의 메인 테스트 빼고 모든 테스트를 삭제도 해보고,

요구사항을 다시 읽어보면서 무엇이 부족한지 찾아보며 계속 커밋을 하며 이유를 찾아갔었는데요.

예제 테스트 에러를 찾기 위한 발악의 흔적..

이유는 다름이 아닌 기존에 제공된 BridgeMaker클래스의 패키지를 변경했기 때문이였습니다. 허허

MVC 패턴을 적용하기 위해 BridgeMaker 클래스를 domain에 넣었는데 이게 문제가 되었더라구요..

클래스의 요구사항에는 패키지를 변경할 수 없다는 말이 없어 패키지를 변경했는데, 프로그래밍 요구 사항을 자세히 보니

BridgeMaker 클래스의 요구사항에는 패키지에 대한 언급이 없기 때문에 이동하면 안됐습니다 !

이런 함정이 있었다니 .. 요구 사항을 정말 꼼꼼히 읽어야 겠어요 😂


마지막 프리코스를 마무리 하며

Java의 J도 모르던 제가 우테코에 지원하기 위해 자바의 정석을 사서 급하게 공부하던게 엊그제 같은데

4주간의 프리코스를 무사히 마무리하며 엄청한 성장을 이뤘다는게 정말 뿌듯하고 신기하네요 😀

테스트 결과

혼자 독학할 때는 그저 책을 통해 따라만 했었고 어떻게 공부해야 할 지 몰랐었지만,

우테코의 매주마다 추가되는 요구사항과 피드백을 통해 찾아보고 공부하고 테스트 해보면서 정말 많은 것들을 얻어 갈 수 있었습니다.

프리코스만 해도 이정도의 성장을 이뤄낼 수 있었는데 5기로 들어가게 되면 정말 얼마나 큰 성장을 할 수 있을까요...

프리코스 과제를 하면서 우테코의 간절함은 더욱더 커져만 가네요 🙏🏻🙏🏻

 

앞으로 최종 코딩테스트가 남았는데 4주치의 과제와 지원서를 바탕으로 최종 코테 응시 여부 정해지기에

아직 최종 코딩테스트를 칠 수 있을지는 모르겠지만 남은 시간 동안 지금까지 해왔던 과제들을 복습하고,

아직 정리하지 못한 내용들을 하나씩 정리해 가려 합니다. ( 정리할 내용들이 산더미네요 ... )

 

좋은 소식이 있다면 다음 최종 코딩테스트 후기로 다시 찾아 오겠습니다 !

5기 지원자 분들 4주간 모두 고생 많으셨고 좋은 결과 있길 바랍니다. 👍🏻