[우아한테크코스] 프리코스 2주차 회고!

이호석·2022년 11월 8일
0
post-thumbnail

🚀 숫자 야구

2주차에 주어진 미션은 숫자 야구 미션입니다.
미션에 대한 규칙 및 구현 코드는 다음 저장소에 정리되어 있습니다.
https://github.com/HiiWee/java-baseball

숫자 야구 게임은 신교대에서 정신전력 기간 동기와 메모장으로 몰래 했던 게임이기도 해서 반가웠습니다 ㅎ

🔒 제약 사항

  • 미션은 기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항 세 가지로 구성되어 있다.
  • 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다.
  • 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다.

📌 추가된 요구 사항

  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
    • 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
  • JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
    • 테스트 도구 사용법이 익숙하지 않다면 test/java/study를 참고하여 학습한 후 테스트를 구현한다.

📌 라이브러리

  • camp.nextstep.edu.missionutils에서 제공하는 RandomsConsole API를 사용하여 구현해야 한다.
    • Random 값 추출은 camp.nextstep.edu.missionutils.RandomspickNumberInRange()를 활용한다.
    • 사용자가 입력하는 값은 camp.nextstep.edu.missionutils.ConsolereadLine()을 활용한다.

⚙️ 실행 순서 및 기능 목록 정의

처음 문제를 접했을때는 생각보다 어렵지 않은데? 였습니다. 이 정도면 금방 구현하지 않을까? MVC 패턴을 적용해보자! 마음을 먹고 기능목록을 작성하려는 순간 손이 거짓말처럼 굳어버렸습니다.

어디서 부터 해야할까? 가장 큰 고민이었습니다. 입력을 받는 부분을 먼저 정의해야 하는지, 임의의 3개의 숫자를 만드는 부분부터 목록을 구체화 해야하는지 고민이 됐습니다.

그러던 중 불현듯 정말 구체적인 실행 순서를 정의하면? 기능 목록을 작성하는데도 도움을 얻을 수 있지 않을까? 였습니다.

따라서 숫자야구의 구체적인 기능을 다음과 같이 정리했습니다.

이후 다음과 같이 기능 목록을 정의했습니다.

1. 게임 초기화 및 시작 개요
    - 컴퓨터는 임의의 서로다른 3개의 숫자를 정의한다.
        - [X] 숫자는 1 ~ 9 사이의 랜덤한 3개의 숫자이다.
        - [X] 3개의 숫자는 서로 달라야 한다.
    - 컴퓨터는 초기 시작 메시지를 출력한다.
        - [X] 게임 시작시에 `숫자 야구 게임을 시작합니다.`를 출력한다.
    - 컴퓨터는 입력 메시지를 출력한다.
        - [X] 사용자 입력을 받기 위해 `숫자를 입력해주세요 : `를 출력한다.
    - 컴퓨터는 사용자의 입력을 기다린다.
        - [X] 사용자로부터 입력을 받는다.
        - [X] `camp.nextstep.edu.missionutils`의 제공 라이브러를 이용해 입력을 받는다.
2. 게임 진행중 사용자의 입력을 받은 후
    - 컴퓨터는 사용자의 입력이 올바른지 모든 예외를 확인한다.
        - [X] 3글자를 입력하지 않았다면 `IllegalArgumentException` 예외를 발생시킨다.
        - [X] 숫자가 아닌 입력은 `IllegalArgumentException` 예외를 발생시킨다.
        - [X] 숫자중에 0이 들어가있으면 `IllegalArgumentException` 예외를 발생시킨다.
        - [X] 3개의 숫자중에 동일한 숫자가 존재하면 `IllegalArgumentException` 예외를 발생시킨다.
        - [X] 각 예외에 맞는 메시지를 출력해야 한다.
        - [X] 예외가 발생되고 나면 애플리케이션 종료되어야 한다.
    - 올바른 숫자를 입력하게 되면 컴퓨터는 입력받은 수의 스트라이크, 볼, 낫싱의 경우를 계산한다.
        - [X] 스트라이크의 수를 계산한다.
        - [X] 볼의 수를 계산한다.
    - 컴퓨터는 계산 결과를 가지고 메시지를 만든다.
        - [X] 스트라이크 숫자가 3개라면 정답 메시지를 만든다.
        - [X] 정답이 아니라면 스트라이크 및 볼의 숫자를 가지고 결과 메시지를 만든다.
3. 사용자의 입력이 정답일 경우
    - [X] 정답 메시지를 출력한다.
    - [X] 현재 게임을 종료한다.
    - [X] `게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.` 메시지를 출력한다.
    - 게임 종료후 선택 메시지 출력되고 사용자의 입력 기다림
        - [X] 숫자가 아닌 입력은 `IllegalArgumentException` 예외를 발생시킨다.
        - [X] 1, 2가 아닌 숫자가 입력되면 `IllegalArgumentException` 예외를 발생시킨다.
        - [X] 1이 입력되면 초기 시작 메시지를 제외하고 게임을 다시 시작한다.
        - [X] 2가 입력되면 애플리케이션이 종료된다.
4. 사용자의 입력이 정답이 아닐 경우
    - [X] 결과 메시지를 출력한다.
    - [X] 사용자 입력을 받기 위해 `숫자를 입력해주세요 : `를 출력한다.
    - [X] 사용자의 입력을 다시 받는다.

✔︎ 절차적인 기능 목록의 문제점

적용하고자 하는 패턴은 MVC 패턴입니다. 하지만 기능 목록 자체가 절차적으로 정리되어 있으면서 이를 다시 도메인을 중심으로 변경하려고 하니 하나의 도메인에 해당되는 내용이 이곳저곳에 흩어져 있는 경우가 발생하거나, 중복되는 내용도 존재했습니다.

즉, 위의 기능 목록은 절차적인 코드를 작성할때는 굉장히 편리한 기능 목록이지만, 객체지향적인 코드를 작성하려는 나에게는 불친절한 기능목록이었습니다. 위의 기능 목록을 전부 변경하는것이 물론 옳겠지만, 개인적으로 불편함을 느끼면서 코딩해보면 다음번엔 이런 부분들을 고민하면서 하지 않을까 싶어서 그대로 사용하기로 했습니당


✔︎ 요구사항을 정확히 살펴보자!

모든 기능 구현을 마치고 최종 ApplicationTest를 돌렸을때 빨간불이 뜨는 새벽 5시의 제 심정을 아시나요..? 으레 그렇듯이 빨간불이길래 저는 정지했습니다. 근데 신호등이 아니니까.. 예.. 초록불이 되진 않았습니다..

문제가 발생한 코드는 다음과 같습니다.

문제가 무엇이었을까? 일단 디버깅을 돌리면서 어디서 문제가 발생하는지 확인했습니다. 저장소에서 하나의 리스트 변수에 컴퓨터 숫자를 담고 있었고, 그런 숫자를 generate하기 위해 camp.nextstep.edu.missionutils.Randoms의 pickNumberInList() 메소드를 활용하고 있었습니다.

하지만, 해당 메소드는 문제가 있는데 바로 테스트 코드에서 문제가 발생합니.
위 그림의 assertRandomNumberInRangeTest() 메소드는 다음과 같이 구현되어 있습니다.

또한 내부적으로 assertRandomTest를 호출하는데 첫번째 인자로
() -> Randoms.pickNumberInRange(anyInt(), anyInt())를 넘기는것을 볼 수 있습니다. assertRandomTest를 다시 살펴보면

다음과 같이 구현되어 있고, 위에서 인자로 받아온 verification을 보면
mock.when(verification).thenReturn(value, ....)과 같이 구현되어 있습니다. 즉, 이미 테스트에서는 pickNumberInRange()로 테스트 해야 정상작동 할 수 있도록 Mock 테스트가 작성되어 있는것입니다. 그러니까 해당 메소드가 pickNumberInList()와 같은 메소드를 사용하면 빈 객체를 반환하게 됩니다.

Mock에 대한 정확한 지식이 없어서 추측하는대로 작성해봤습니다. 조금더 명시적으로 설명해주실 수 있으면 댓글 부탁드려요!!

혹시나 해서 요구사항을 살펴보니 새초롬하게 명시되어 있었습니다.

코치님들이 당부하고 당부했던, 요구사항 꼭 꼼꼼하게 읽으세요!! 의 염려섞인 목소리가 머릿속에서 메아리 치는 순간이었습니다..

❗️ 요구사항은 읽어도 읽어도 끝이없다. 끝까지 놓치지 말자


✔︎ 테스트 코드의 고마움

테스트 코드는 구현과 피드백 사이의 간극을 좁혀준다. 농구공을 던지고 골인지 아닌지 일주일뒤에 결과를 알게된다면 실력이 늘까? 지난주 1주차 코수타 시간에 준 코치님께서 말씀해주신 내용입니다. 이번주 미션을 진행하면서 저말에 여실히 공감할 수 있었습니다.

TDD를 해볼까 했지만, 일단 테스트 코드를 연습하자는 마음으로 하나의 기능을 구현하고 해당 기능의 테스트 코드를 작성하는 순서로 진행했습니다.

숫자 야구 구현체의 패키지 구조는 다음과 같고

테스트 코드의 패키지 또한 구현체와 동일하게 맞춰 주었습니다.

개인적으로 테스트 코드를 작성하면서 가장 큰 도움이 되었던건, 빠른 피드백이었습니다. 코드를 작성하면서 변경 사항이 생기거나, 리팩토링을 하게되는 경우를 살펴보면, 구조적으로 변화가 있어도 그 안에서 흘러가는 데이터의 흐름은 변하면 안됩니다.

테스트 코드는 그 변화의 흐름을 꽉 잡고 있으며 만약 조금이라도 기존 정상 데이터와 다른 흐름을 보이면 빨간불로 경고를 친절하게 날려줍니다. 다만, 몇개의 오류는 테스트 코드로 발견할 수 없었는데, 이를 통해 테스트 코드를 신뢰하기 위해서는 철저하게 작성해야 한다는 점도 배웠습니다.

이번 미션을 진행하면서 이런 테스트 코드의 덕을 많이 보았고, 결과적으로 디버깅에 정말 많은 시간을 쏟을뻔한 경우를 번번히 테스트 코드가 해결해 주었습니다.


✌️

2주차 임에도 코드를 작성할때 고려하는 부분들이 많이 달라졌습니다. 진작에 스스로 적용했더라면 개발 능력이 지금보다 더 좋아지지 않았을까라는 아쉬움도 있지만, 오랜만에 진짜 공부를 하는 기분이 들어서 너무 재밌기도 합니다.

다음 3주차 미션도 다가오는 막연함을 즐기면서 즐겁게 진행하고 싶습니다.

공부해야 할 것

  • MVC 각 계층에서 하는 역할들을 좀 더 명확하게 공부하자
  • 클린코드, 객체지향의 사실과 오해 읽기
  • 객체지향 생활 체조 원칙 9가지 읽기
  • 구글 컨벤션 좀 더 공부
profile
꾸준함이 주는 변화를 믿습니다.

1개의 댓글

comment-user-thumbnail
2022년 11월 10일

저도 요구사항을 꼼꼼히 못보고 놓쳤던게 있어서 공감되네요 ㅠㅠ
잘보고갑니다!

답글 달기