📌 Intro
우테코 프리코스 2주차가 끝났다. 왜 또 나는 바빴던 것일까.... 아쉽다. 그래도 그 안에서도 열심히 임했으니 후회는 없다.
📌 2주차 내용
이번 2주차에서 진행했던 미션은 숫자 야구 게임이다. 숫자 야구 게임은 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리하는 조건을 가지고 있다.
📌 추가된 요구 사항
2주차의 목표는 함수를 분리하고, 각 함수별로 테스트를 작성하는 것에 익숙해지는 것이다.
그리고 추가된 요구사항도 있는데 이전까지 고민하지 않았던 부분들에 대한 내용들이 많아 지키는데 쉽지 않았다.
- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라.
- JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다.
📌 라이브러리
camp.nextstep.edu.missionutils에서 제공하는 Randoms 및 Console API를 사용하여 구현해야 한다.
- Random 값 추출은 camp.nextstep.edu.missionutils.Randoms의 pickNumberInRange()를 활용한다.
- 사용자가 입력하는 값은 camp.nextstep.edu.missionutils.Console의 readLine()을 활용한다.
val computer = mutableListOf()
while (computer.size() < 3) {
val randomNumber = Randoms.pickNumberInRange(1, 9)
if (!computer.contains(randomNumber)) {
computer.add(randomNumber)
}
}
📌 구현 내용
위 링크를 통해 PR을 확인할 수 있다.
README
먼저 README를 작성했는데 여기에는 구현해야 하는 기능을 세분화하고, 발생할 수 있는 예외 정도를 정리했다.
✔ 구현기능 세분화
게임시작 기능숫자를 생성하는 기능
사용자에게 입력을 받는 기능
입력받은 숫자 중 볼의 개수 판단 : countBall → return Int
입력받은 숫자 중 스트라이크의 개수 판단 : countStrike → return Int
볼과 스트라이크의 결과를 출력하는 기능 : printResult
게임 종료를 판단하는 기능 : isGameOver → return Boolean
재시작을 물어보는 기능
예외 처리(잘못된 값을 입력한 경우) : 자리수 2이하 4이상, 음수 입력, 문자입력, 같은 숫자 입력
Application.kt
먼저 Random모듈에 어떤 함수가 있는지 구경하고 싶었다. 다양한 함수가 있었는데 그 중 눈에 띈 것이 pickUniqueNumbersInRange() 함수였다.
public static List<Integer> pickUniqueNumbersInRange(
final int startInclusive,
final int endInclusive,
final int count
) {
validateRange(startInclusive, endInclusive);
validateCount(startInclusive, endInclusive, count);
final List<Integer> numbers = new ArrayList<>();
for (int i = startInclusive; i <= endInclusive; i++) {
numbers.add(i);
}
return shuffle(numbers).subList(0, count);
}
pickUniqueNumbersInRange() 함수는 위와 같이 생겼고 시작 값, 끝 값, 개수를 파라미터로 받아서 랜덤 값이 담겨있는 리스트를 반환해주는 함수다. pickNumberInRange() 함수보다 더 간단하게 구현이 가능할 것 같아서 해당 함수를 사용했다. 하지만 요구사항에 나와있듯이 pickNumberInRange()를 사용해야 했었고 이 부분을 찾는데 오랜 시간을 들였던 것 같다.(요구 사항을 잘 숙지하자..)
사용자로부터 받는 입력에 대해 발생할 수 있는 예외가 많았기 때문에 여러 함수로 분리해두고 하나의 함수에서 한번에 확인하는 방식을 사용했다. 예를 들어 0이나 문자가 입력되면 안되기 때문에 각각 findZero(), findChar() 함수로 분리해두고 checkError()라는 함수로 한번에 확인하는 방식이다.
사용자가 상대방(컴퓨터)의 수를 맞추면 다시 게임을 진행할 것인지 확인해야 하기 때문에 main에서 while문을 사용했고 조건으로 restartFlag이 2가 아닌경우 반복하도록 했다. 초기 상태는 1로 세팅해두고, 랜덤 수를 생성하면 restartFlag를 0으로 바꿔준다. 이는 사용자가 맞추기 전까지는 랜덤수를 새로 생성하지 않고 입력값을 받기 위함이다. 만약 사용자가 맞춘다면 restartFlag에 사용자의 응답을 받고 2는 종료이기 때문에 while문을 탈출하는 것이다.
fun main() {
startGame()
var computerList = listOf<Int>()
var inputNumberList = listOf<Int>()
var restartFlag: Int = 1
while (restartFlag != 2) {
if (restartFlag == 1) {
computerList = makeNumber() // 랜덤수 생성
restartFlag = 0
}
inputNumberList = getInput() // 사용자 입력
val strikes = countStrike(computerList, inputNumberList) // 스트라이크 개수
val balls = countBall(computerList, inputNumberList) - strikes // 볼 개수
printResult(strikes, balls) // 결과 출력
if (isGameOver(strikes)) { // 3스트라이크의 경우
restartFlag = askRestart() // 재시작 여부 확인
}
}
}
📌 Code Review
이번주부터 같은 안드로이드 분들과 코드리뷰를 진행하고 있다. 같은 요구사항이더라도 사람의 생각은 모두 다르기 때문에 정말 다양한 방식으로 구현한 것이 신기했고 모두 깊은 고민의 흔적이 보여서 보고 배워야겠다고 생각했다.
예외를 처리할 때 발생할 수 있는 예외를 함수로 분리해두고 하나의 함수에서 처리한 것이 호평을 받아서 기분이 좋았다.
그리고 요구사항에 코틀린 컨벤션을 지키면서 코딩을 진행하라고 했었고 나름 지킨다고 했지만 어려웠다. 이 부분에 대해서 Ctrl+Alt+L 을 이용하면 코틀린 컨벤션을 처리해준다는 피드백을 받았고 적극 활용하려고 한다.
또 restartFlag를 사용할 때 0, 1, 2 이렇게 값을 사용하는 것 보다 상수로 지정해놓고 사용한다면 가독성이 더 좋아질 것 같다는 피드백을 받았다.
📌 느낀점
2주차 공통 피드백에 나와있는 내용을 보고 내가 정확히 지키고 있던 것이 하나도 없는 것 같이 느껴졌다. 그래도 요구사항을 지키기 위해서는 기능을 어떻게 구현할 것인지, 함수를 어떻게 구분할 것인지 고민을 해야했다. 이런 시간을 보내면서 눈으로는 보이지 않지만 내적인 성장이 이루어졌다고 생각한다.
다른 사람들과 코드 리뷰를 진행하면서 개인마다 15분정도의 시간을 갖고 진행한다. 짧은 시간안에 자신의 코드를 설명해야 하는데 이 때 가독성 좋은 코드가 왜 필요한지 크게 와닿았고, 모두 같은 컨벤션을 지키려고 노력해서 이해하기 수월했던 것 같다. 또 내 코드에 대한 피드백 이외에도 보고 배울점들이 많아서 다음 주차에 적용해서 진행해보면 좋을 것 같다는 생각이 들었다.
이제 2주 밖에 지나지 않았지만 요구사항을 지키는 연습, 코수타, 공통 피드백, 코드리뷰를 통해서 이미 배운 것이 많은 것 같다. 앞으로도 많은 것들을 배우고 내 것으로 만드는데 집중해려고 한다.