본문 바로가기
우아한테크코스

우아한테크코스(프리코스) 2주차 회고

by 임동무 2022. 11. 8.

프리코스 2주차의 미션은 숫자야구 게임 만들기였다.

https://github.com/woowacourse-precourse/java-baseball

 

GitHub - woowacourse-precourse/java-baseball: 숫자 야구 게임 미션을 진행하는 저장소

숫자 야구 게임 미션을 진행하는 저장소. Contribute to woowacourse-precourse/java-baseball development by creating an account on GitHub.

github.com

 

 

 

기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.
같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고,
그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.

예)
상대방(컴퓨터)의 수가 425일 때
123을 제시한 경우 : 1스트라이크
456을 제시한 경우 : 1볼 1스트라이크
789를 제시한 경우 : 낫싱
위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 서로 다른 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.

사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다.

 

 

1. 테스트 코드

이번 주차를 진행하면서 기존에 인터넷 강의를 보면서 테스트를 따라 작성한 경험이 아닌,

직접 개발하면서 각 기능에 대한 테스트 코드를 작성하는 것은 처음이었다.

 

OT 에서 테스트 코드란 내가 구현한 기능을 즉각적으로 확인할 수 있는 도구라고 하였다. 그래서 형식적으로 테스트 코드를 작성하기보단

1. 기능이 정상적으로 구현이 되었는가?

2. 기능에 어떤 값을 넣으면 오류를 만들 수 있을까?

에 대해서 생각해보면서 테스트 코드를 작성하였다.

 

 

2. 일급 컬렉션

이번 주차도 역시 컬렉션들에 대해서 일급컬렉션을 적용하기 위해서 노력했다.

일급 컬렉션은 불변하도록 정의한 컬렉션 멤버를 소유한다.

그래서 클래스를 생성하면서 생성자에 컬렉션 멤버에 할당할 컬렉션을 넣어 컬렉션 멤버를 초기화시킨다.

이런 일급 컬렉션을 쓴 이유는 생성자에서 예외처리를 할 수 있으며 행위와 상태를 같은 공간에서 관리할 수 있기 때문이다. 

 

컴퓨터의 숫자는 내부 로직에 따라 생성되는 값이기 때문에 예외처리를 할 필요가 없었다.

하지만 사용자의 숫자는 사용자로부터 입력받는 값이기에 어떤 값을 입력받을지 모른다. 그래서 예외처리를 해주어야 한다.

 

내가 생각한 예외처리는 총 3가지였다.

1. 사용자가 문자열의 길이가 3이 아닌 문자열을 입력 - isValidLength(String userNumberInString)

2. 사용자가 숫자가 아닌 값이 포함된 문자열을 입력 - isNumeral(String userNumberInString)

3. 사용자가 중복된 숫자가 포함된 문자열을 입력 - isUniqueNumbers(String userNumberInString)

 

 

이 세가지 예외처리를 각각의 메서드로 정의하고 이 메서드들을 통합하여 예외를 잡는 메서드를 생성하였다.

그리고 이 통합 예외처리 메서드를 사용자의 숫자를 저장하는 일급 컬렉션의 생성자에서 예외사항을 확인한 후에 컬렉션 멤버에 저장되도록 하였다.

    //일급 컬렉션의 생성자
    public UserNumber(String userNumberInString) {
        Exceptions.isValidInputUserNums(userNumberInString);
        this.userNums = convertUserNumbers(userNumberInString);
    }
    
    // 통합 예외처리 메서드
        public static void isValidInputUserNums(String userNumberInString) {
        isValidLength(userNumberInString);
        isNumeral(userNumberInString);
        isUniqueNumbers(userNumberInString);
    }

 

 

또한 이 사용자의 숫자에 대한 기능

1. String 타입의 사용자의 숫자 input 을 List<Integer> 로 바꾸는 기능

2. 사용자의 숫자의 getter

들을 UserNumber 일급 컬렉션에 기능을 정의하여 UserNumber 에 대한 모든 기능을 한 곳에서 관리할 수 있었다. 

여기서 getter 는 UserNumber 의 컬렉션 멤버를 반환한다.

이 때, 이 컬렉션 멤버는 불변이여야 한다. 그래서 이를 어떻게 관리해야할 지 고민한 끝에 Collections 의 unmodifiableList 를 이용해서 반환하여 수정이 불가능하도록 즉, 불변 컬렉션으로 반환하였다.

public List<Integer> getUserNums() {
    return Collections.unmodifiableList(this.userNums);
}

 

 

두 번째로 사용한 일급컬렉션은 게임 숫자(컴퓨터의 숫자)에 대한 일급 컬렉션이었다.

사용자의 숫자는 예외사항이 없으므로 (random 값을 추출할 때, 중복된 값을 제외한 3개의 숫자를 추출하기 때문에)

생성자에 예외처리는 하지 않았다.

 

그리고 사용자의 숫자를 게임 숫자에 대해 비교하는 모든 기능을 게임 숫자가 저장된 일급컬렉션에서 정의하였다.

게임의 숫자가 주체가 되며 이와 사용자의 숫자를 비교한 결과를 출력해주는 게임이라고 생각했기에 두 숫자에 대한 비교를 전부 사용자의 숫자를 저장하는 일급 컬렉션이 아닌, 게임 숫자를 저장하는 일급 컬렉션에 정의하였다. 

 

 

여기에 포함된 기능은

1. 스트라이크 판별 및 개수 반환 기능

2. 볼 판별 및 개수 반환 기능

3. 낫싱 판별 기능

3가지 였으며 테스트에서 사용하기 위해서 getter를 만들었고 또한 unmodifiableList 로 불변하도록 설정해주었다.

 

 

3. Constant

이번 주차를 진행하면서 가장 신경을 많이 썼던 부분은

"만약 기능 요구사항이 바뀐다면?" 에 대한 생각에 따라 변화하는 요구사항에 대해서 유연하게 대처할 수 있도록 코드를 짜는 것이었다.

 

이 생각을 계속 하다보니 자연스럽게 과제 제출 전 체크사항의 "모든 원시값과 문자열을 포장했는가?" 에 대한 부분을 계속해서 확인할 수 밖에 없었는데 그 이유는 

 

원시값이나 문자열을 그대로 사용하는 경우

1. 해당 값이 어떤 의미를 갖는 값인지 명확하게 확인하기가 어렵다.

2. 어떤 값을 바꿔야 하는 상황이 닥쳤을 때, 해당 값이 어디에 포함되어 있는지를 찾기가 어렵다. 

등의 문제가 있기 때문이다. 

 

그래서  여러 곳에서 사용하는 멤버 혹은 한 번만 사용되더라도 그 값이 고정된 값이라면 Constant 클래스에 상수로서 정의하였다. 

( ex. 랜덤값 추출 시 시작값과 끝값, 랜덤 숫자 추출 개수, 게임 시작을 알리는 문자열 ...) 

 

이렇게 해서 사용하는 도중, 요구사항의 숫자의 범위가 1~9 였는데 0~9 로 잘못 설정했을 때, 그 상수의 값만 바꿔 쉽게 대처할 수 있었다.

 

 


2주차 총평 :

이번 2주차에서는 1주차보다는 조금 더 수월했다고 느껴졌던 것 같다.

그래도 나름 1주일이라도 해봤다고 코드를 구상하는 단계부터 구현, 커밋, 과제 제출하는 데 까지의 과정이 비교적 순탄하게 진행됐다.

 

OT 때의 힌트가 많은 도움이 되었던 것 같다. 이전에는 기능을 구현하는데 메서드명을 정하는데 한참이 걸리고, 파라미터 이름은 어떻게 해야할 까, 변수명은 이게 적절할 까 등에 대해서 고민하느라 작은 역할을 하는 메서드도 구현하는데 상당한 시간이 걸렸다.

 

"변수명/메서드명을 한번만에 완벽하게 이름을 지을 수 없다. 그러니 그 변수 혹은 메서드가 어떤 역할을 하는지 장황하게 적어놓고 그 다음에 차근차근 구현하면서 변수명/메서드명을 최적화해라. 어짜피 지금은 잘 지었다고 생각해도 돌아보면 이름이 마음에 들지 않을것이다." 라는 말을 듣고 처음 구현할 때에는 변수명에 그렇게 까지 신경쓰지 않았다. 대충 그 변수/메서드가 하는 역할에 대해서 일단 장황하게 적어놓았다.

이름 짓는걸 조금 뒤로 미뤄두고 어떤 역할을 하는지 먼저 구현한 다음, 해당 메서드, 변수의 핵심적인 부분을 이용해서 이름을 짓다보니 기능 구현하는데에도 시간이 훨씬 단축됐고 이름 짓는 것 또한 시간이 많이 단축할 수 있었던 것 같다.

 

 

그리고 이번 주차에서 

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

  • Random 값 추출은 camp.nextstep.edu.missionutils.Randoms의 pickNumberInRange()를 활용한다.

는 부분에 대해서 고민을 해보았다.

 

해당 라이브러리를 보면 중복되지 않는 3개의 랜덤 숫자를 추출할 수 있는 메서드가 정의되어 있다.

내가 이해한 바에 따르면 시작값, 끝값, 숫자 개수를 파라미터로 받아서

List<Integer> 에 시작값~끝값 까지의 모든 숫자를 넣어 개수만큼 추출한 List 를 반환해준다. 

 

조금 더 명확한 요구사항을 표현하기 위해서는 위의 문장을 없애고

camp.nextstep.edu.missionutils.Randoms의 pickNumberInRange()를 활용하여 Random 값을 추출한다. 이 외의 메서드는 사용하지 않는다. 라고 표현하거나 이외의 메서드를 아예 정의해놓지 않는 방법도 있었다.

 

하지만 조금 애매한 구석이 있었고 이런 애매함은 의도된 애매함이는 것도 알고 있었기에 선택이 필요했다. 

 

그래서 처음에는 라이브러리를 읽어보았는지, 아니면 그냥 대충 알려주는 방식대로 구현만 했는지 판별하기 위해 이렇게 설계했다고 생각하여 Randoms 의 pickUniqueNumbersInRange 메서드를 사용하였다.

 

하지만 OT 때 요구사항을 자세하게 읽어보라는 말이 계속해서 머리속에 맴돌았다. 그래서 내 생각이 아니라, 요구사항에 조금이라도 더 적합한 방향으로 개발하는 것이 중요하다고 생각했고

'요구사항 대로 pickNumberInRange() 를 이용해서 하나씩 추출하고 중복 제거 등은 내부 로직으로 구현을 해야겠다!" 라고 생각했다.

 

아직까지 어떤 방식이 우아한테크코스에서 의도한 방식인지는 모르겠다.

하지만 나의 모든 선택에 합당한 이유가 있으면 1차적으로는 그걸로 됐다.

 

이유 없이 작성해서 우연히 맞는 것보단 근거가 있지만 틀리는 것이 성장할 수 있는 발판이기 때문이다. 이유가 있다면 근본적으로 어떤 부분을 내가 모르고 있는지, 어떤 부분에서 내가 틀렸는지를 명확하게 알 수 있고 확실하게 바로잡을 수 있다. 

 

우아한테크코스에 합류하면서 최대한 고민하고, 최대한 많이 틀려보면서 조금 더 올바른 방향으로 성장하는 것이 내 목표였다. 지금까지는 많이 틀리고, 스스로에게 질문하면서 잘 성장하면서 따라오고 있다는 생각이 든다. 물론 부족한 부분이 아직도 많지만..

 

다가오는 3주차에도 많이 틀리고, 고민하면서 느릴수도 있지만 올바른 방향으로 성장할 수 있도록 노력해야겠다.

댓글