우아한테크코스 5기 - 프리코스 2주차 미션[숫자 야구 게임]

core·2022년 11월 10일
0

우아한테크코스

목록 보기
4/6

서론

1주 차 미션에서 작성했던 준비 운동을 마무리하고 본격적인 미션이 시작되는 것 같다.

이번 2주 차 미션에서는 1주 차에서 학습한 것에 더해 함수를 분리하고, 각 함수별로 테스트를 작성하는 것에 익숙해지는 것을 목표로 하고 있다.

그리고 작성한 함수에 대한 테스트 코드를 작성하여 내가 생각한 기능대로 제대로 동작을 하는지 테스트도 진행을 해아 한다. 자발적으로 작성해 본 적이 없는 내 입장에서는 가치 있는 미션인 것 같다.

2주차 과제를 진행하면서 내가 과제를 진행했던 과정을 정리하겠다.


기능 구현 유의사항

문제 풀기에 앞서 다음과 같은 진행 방식을 유의하면서 기능을 구현해야 한다.

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

🚀 기능 요구 사항

1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임 기능을 구현해야 한다.

  • 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
    • 예를 들어 상대방(컴퓨터)의 수가 425일 때
      • 123을 제시한 경우: 2의 위치가 하나 맞았고, 1과 3은 425에서 존재하지도 않으므로 1스트라이크
        • 456을 제시한 경우: 5의 위치가 서로 다르고 4의 위치가 같으므로 1볼 1스트라이크
        • 789를 제시한 경우: 425에서 해당 숫자가 존재하지 않으므로 낫싱
  • 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 서로 다른 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
  • 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
  • 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
  • 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.
  • 프로그램이 실행될 때 다음과 같은 내용들이 출력되어야 한다.
숫자 야구 게임을 시작합니다.
숫자를 입력해주세요 : 123
1볼 1스트라이크
숫자를 입력해주세요 : 145
1볼
숫자를 입력해주세요 : 671
2볼
숫자를 입력해주세요 : 216
1스트라이크
숫자를 입력해주세요 : 713
3스트라이크
3개의 숫자를 모두 맞히셨습니다! 게임 종료
게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.
1
숫자를 입력해주세요 : 123
1볼
...

🎯 프로그램 요구 사항

  • JDK 11 버전에서 실행 가능해야 한다. JDK 11에서 정상적으로 동작하지 않을 경우 0점 처리한다.
  • 프로그램 실행의 시작점은 Applicationmain()이다.
  • build.gradle 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다.
  • Java 코드 컨벤션 가이드를 준수하며 프로그래밍한다.
  • 프로그램 종료 시 System.exit()를 호출하지 않는다.
  • 프로그램 구현이 완료되면 ApplicationTest의 모든 테스트가 성공해야 한다. 테스트가 실패할 경우 0점 처리한다.
  • 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다.

추가된 요구 사항

  • indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
    • 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
  • 3항 연산자를 쓰지 않는다.
  • 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어야 한다.
  • JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.

라이브러리

  • camp.nextstep.edu.missionutils에서 제공하는 RandomsConsole API를 사용하여 구현해야 한다.

    • Random 값 추출은 camp.nextstep.edu.missionutils.RandomspickNumberInRange()를 활용한다.
    • 사용자가 입력하는 값은 camp.nextstep.edu.missionutils.ConsolereadLine()을 활용한다.

✏️ 과제 진행 요구 사항

  • 기능을 구현하기 전 docs/README.md에 구현할 기능 목록을 정리해 추가한다.

  • Git의 커밋 단위는 앞 단계에서 docs/README.md에 정리한 기능 목록 단위로 추가한다.


진행 과정의 순서

⭐ git commit convention에 대한 공부

프로그램 요구 사항에서 Java 코드 컨벤션의 링크를 들어가보았다. git commit을 할 때 어떤 형태로 작성을 해야 하는지 그에 대한 가이드가 들어있었다.

그 외에도 git comit에 대한 자세한 내용을 알고 싶어서 구글링을 통해 git commit convention에 대해 다양한 가이드를 참조했고, 내가 공부했던 내용들을 바탕으로 벨로그에 정리를 하였다.

⚾ 야구 게임을 위한 필요한 기능 목록들을 고찰

문제에서 이번 주차의 미션은 함수를 분리하고 이에 따른 테스트 코드를 작성하는 것을 목표로 한다. 그래서 나는 각 기능을 하나의 함수로 취급해서 각 가능들을 고찰하였다. 즉, 하나의 Application 파일 안에서 코드 작성을 진행했다. 내 코드의 내용이 정답이라는 보장은 없기에 어떤 함수를 만들었고, 무슨 기능인지 간단하게 설명만 하려고 한다.
최종적으로 내가 생각했던 기능들은 다음과 같다.
1️⃣ 게임 시작 공지 기능
2️⃣ 1부터 9까지 3개의 서로 다른 자연수의 컴퓨터 숫자 뽑기 기능
3️⃣ 사용자로부터 3개의 숫자 입력 받기 기능
4️⃣ 사용자로부터 입력된 3개의 숫자의 유효성 검증 기능
5️⃣ 컴퓨터 숫자와 사용자 숫자로부터 '볼'의 개수 뽑기 기능
6️⃣ 컴퓨터 숫자와 사용자 숫자로부터 '스트라이크'의 개수 뽑기 기능
7️⃣ '스트라이크' 및 '볼'의 개수 정보 출력 기능
8️⃣ 3스트라이크가 나올 때까지 3번과 7번을 반복 기능
9️⃣ 3스트라이크가 달성 후 게임 재시작 여부 기능

🏀 각 기능 목록 하나씩 구현 후 commit 진행

목록 당 기능을 구현 후 commit convention에 따라서 commit을 진행했다.

1️⃣ 숫자 야구 게임을 시작합니다.문구 출력을 위한 기능이다. 이 기능에 대한 메서드를 따로 구현을 했고, 메서드 이름을 noticeStartGame으로 설정했다.
2️⃣ createComputerNumbers 메서드를 만들었고, Randoms의 pickNumberInRange메서드를 사용하였다.
3️⃣ inputUserNumbers메서드를 만들었다. 이 메서드에는 숫자를 입력해주세요 : 도 출력이 된다. 처음에는 숫자 입력 출력 문구도 따로 기능으로 뺄 생각을 했지만, 입력 기능에 관련이 있으므로 빼지 않기로 결정했다.
4️⃣ isValidUserNumbers 메서드를 만들었다. 이 메서드는 다음과 같은 내용을 검증한다.

  • 입력한 숫자의 길이가 3인지 검증
  • 각 자리당 숫자의 범위가 1이상 9이하인지 검증
  • 각 자리마다 중복된 숫자가 있는지 검증

5️⃣ getBallNumber 메서드를 만들었고, 3자리의 유효한 사용자 숫자와 컴퓨터 숫자를 비교하여 볼의 개수를 출력한다.
6️⃣ getStrikeNumber메서드를 만들었고, 3자리의 유효한 사용자 숫자와 컴퓨터 숫자를 비교하여 스트라이크의 개수를 출력한다.
7️⃣ printBallStrikeResult를 만들었다. 이 메서드는 들어온 볼의 개수와 스트라이크의 개수 정보를 바탕으로 알맞은 결과를 출력한다. 또한 boolean형의 메서드이므로 3스트라이크가 아니면 true를 반환한다.
8️⃣ 7️⃣번에서 3스트라이크가 나오지 않을 경우 true를 반환하는 것을 이용하여 main함수에서 반복문을 통해서 야구 게임 숫자 반복 입력을 설정했다.
9️⃣ wantRestart를 구현했고, 이 메서드는 boolean형이다. 즉, 사용자가 게임 재시작을 원하면 true를 반환, 게임 종료를 원하면 false를 반환하도록 설정해 main에서 반복문을 통해서 게임을 재시작 하도록 설정하였다.

🔧 전반적인 코드 리펙토링 진행

Java 코드 컨벤션에 따라서 전반적으로 가독성을 높이기 위한 코드 리펙토링을 진행했다.

  • while, if 등의 내용 다음에 수직 빈 줄 쓰기
  • 메서드와 메서드 사이에 수직 빈 줄 쓰기

🔍 기능별 테스트 코드 작성

모든 기능 및 코드 리펙토링을 마치고 전반적으로 각 기능들이 이상이 없는지 그에 따른 테스트 코드를 작성하였다.
테스트 코드에 대한 작성 경험은 별로 많지 않아서 테스트 코드를 쓸 때 주로 어떤 기능을 쓰는지, 또 어떤 방식으로 테스트를 진행하는지 공부하였다.
테스트를 진행하는 방식에는 BDD(Behavior-driven development), TDD(Test-driven development)이 두 가지가 존재한다는 것을 알았다.
TDD는 테스트를 먼저 작성하고 그 테스트를 통과시키는 코드를 작성하는 흐름을 기본으로 하지만, BDD는 시나이오를 기반으로 테스트를 작성한다. 그리고 given, when, then등의 구조로 테스트를 작성한다.
나는 BDD방식을 통해, 즉 given, when, then의 구조로 테스트 코드를 작성하려고 노력했다. 그래야 일반 사람들이 언뜻 봐서도 이해가 될 것 같았기에 가독성 면에서도 좋을 것 같다는 생각이 들었다.
테스트 코드를 작성을 해보면서 힘들었던 점을 꼽아서 얘기하자면 출력 관련 테스트에 대한 코드 작성과 private으로 설정된 메서드를 테스트 코드 파일로 불러오는 일이었다.
나는 이것과 관련하여 구글링을 하는데 많은 시간을 투자했었다.
하지만 결과적으로 테스트 코드를 완성했고, 꽤 유용한 기능들에 대한 노하우를 얻었다는 느낌을 받았다.


2주차 진행 후 받은 공통 피드백

🔔 README.md를 상세히 작성한다

미션 저장소의 README.md는 소스코드에 앞서 해당 프로젝트가 어떠한 프로젝트인지 마크다운으로 작성하여 소개하는 문서이다. 해당 프로젝트가 어떠한 프로젝트이며, 어떤 기능을 담고 있는지 기술하기 위해서 마크다운문법을 검색해서 학습해보고 적용해 본다.

🔔 기능 목록을 재검토한다

기능 목록을 클래스 설계와 구현, 함수(메서드) 설계와 구현과 같이 너무 상세하게 작성하지 않는다. 클래스 이름, 함수(메서드) 시그니처와 반환값은 언제든지 변경될 수 있기 때문이다. 너무 세세한 부분까지 정리하기보다 구현해야 할 기능 목록을 정리하는 데 집중한다. 정상적인 경우도 중요하지만, 예외적인 상황도 기능 목록에 정리한다. 특히 예외 상황은 시작 단계에서 모두 찾기 힘들기 때문에 기능을 구현하면서 계속해서 추가해 나간다.

🔔 기능 목록을 업데이트한다

README.md 파일에 작성하는 기능 목록은 기능 구현을 하면서 변경될 수 있다. 시작할 때 모든 기능 목록을 완벽하게 정리해야 한다는 부담을 가지기보다 기능을 구현하면서 문서를 계속 업데이트한다. 죽은 문서가 아니라 살아있는 문서를 만들기 위해 노력한다.

🔔 값을 하드 코딩하지 않는다

문자열, 숫자 등의 값을 하드 코딩하지 마라. 상수(static final)를 만들고 이름을 부여해 이 변수의 역할이 무엇인지 의도를 드러내라. 구글에서 "java 상수"와 같은 키워드로 검색해 상수 구현 방법을 학습하고 적용해 본다.

🔔 구현 순서도 코딩 컨벤션이다

클래스는 상수, 멤버 변수, 생성자, 메서드 순으로 작성한다.

class A {
    상수(static final) 또는 클래스 변수

    인스턴스 변수

    생성자
    
    메서드
}

🔔 변수 이름에 자료형은 사용하지 않는다

변수 이름에 자료형, 자료 구조 등을 사용하지 마라.

String carNameList = Console.readLine();
String[] arrayString = carNameList.split(",");

🔔 한 함수가 한 가지 기능만 담당하게 한다

함수 길이가 길어진다면 한 함수에서 여러 일을 하려고 하는 경우일 가능성이 높다. 아래와 같이 한 함수에서 안내 문구 출력, 사용자 입력, 유효값 검증 등 여러 일을 하고 있다면 이를 적절하게 분리한다.

🔔 함수가 한 가지 기능을 하는지 확인하는 기준을 세운다

만약 여러 함수에서 중복되어 사용되는 코드가 있다면 함수 분리를 고민해 본다. 또한, 함수의 길이를 15라인을 넘어가지 않도록 구현하며 함수를 분리하는 의식적인 연습을 할 수 있다.

🔔 테스트를 작성하는 이유에 대해 본인의 경험을 토대로 정리해본다

단지 기능을 점검하기 위한 목적으로 테스트를 작성하는 것은 아니다. 테스트를 작성하는 과정을 통해서 나의 코드에 대해 빠르게 피드백을 받을 수 있을 뿐만 아니라 학습 도구로도 활용할 수 있다. 이런 경험을 통해 테스트에 대해 어떤 유용함을 느꼈는지 알아본다.

🔔 처음부터 큰 단위의 테스트를 만들지 않는다

테스트의 중요한 목적 중 하나는 내가 작성하는 코드에 대해 빠르게 피드백을 받는 것이다. 시작부터 큰 단위의 테스트를 만들게 된다면 작성한 코드에 대한 피드백을 받기까지 많은 시간이 걸린다. 그래서 문제를 작게 나누고, 그 중 핵심 기능에 가까운 부분부터 작게 테스트를 만들어 나간다.

큰 단위의 테스트

  • 숫자 야구 게임을 시작해서 사용자가 숫자를 입력하면, 컴퓨터 숫자와 비교하여 그 결과를 알려준다.

작은 단위의 테스트

  • 사용자의 숫자가 컴퓨터의 숫자와 하나도 일치하지 않으면 낫싱을 출력한다.
  • 사용자의 숫자가 컴퓨터의 숫자와 1개는 일치하고, 위치가 다르면 1볼을 출력한다.
profile
코어의 서버 탐험기

1개의 댓글

comment-user-thumbnail
2022년 11월 14일

잘보고갑니다!

답글 달기