- Published on
우아한테크코스 프리코스 2주차 회고
- Authors
- Name
- ywj9811
우아한테크코스 프리코스 2주차가 끝났다.
두번째 주차를 무사히 끝내고 나서 다른 지원자들의 pr을 보면서 와… 이렇게 나눌 수 있겠구나… 하면서 대단하다고 느끼기도 하고 나도 여러 방향으로 시작할 수 있겠다는 느낌을 받을 수 있었다.
미션의 경우 이번 부터는 첫주차와 다르게 알고리즘 문제가 아닌 미션을 구현하는 내용이었다.
이번 주차를 시작하며
두번째 주차를 알리는 이메일을 받으며 이번 미션을 시작하게 되었다.
이번 주차 미션
java-baseball 이라는 숫자 야구 게임을 구현하는 미션이었다.
친구들한테 보여주니 숫자 야구 게임 훈련소에서 많이 했었다며 군대를 추억하기도 했던 미션이었다ㅋㅋㅋㅋ
🚀 기능 요구 사항
기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다.
- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다.
- 예) 상대방(컴퓨터)의 수가 425일 때
- 123을 제시한 경우 : 1스트라이크
- 456을 제시한 경우 : 1볼 1스트라이크
- 789를 제시한 경우 : 낫싱
- 예) 상대방(컴퓨터)의 수가 425일 때
- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 서로 다른 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다.
- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다.
- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다.
- 사용자가 잘못된 값을 입력할 경우
IllegalArgumentException
을 발생시킨 후 애플리케이션은 종료되어야 한다.
입출력 요구 사항
입력
- 서로 다른 3자리의 수
- 게임이 끝난 경우 재시작/종료를 구분하는 1과 2 중 하나의 수
출력
- 입력한 수에 대한 결과를 볼, 스트라이크 개수로 표시
1볼 1스트라이크
- 하나도 없는 경우
낫싱
- 3개의 숫자를 모두 맞힐 경우
3스트라이크 3개의 숫자를 모두 맞히셨습니다! 게임 종료
- 게임 시작 문구 출력
숫자 야구 게임을 시작합니다.
이렇게 미션이 주어졌고 나의 목표를 정해 보았다.
나의 목표
우선 저번의 피드백을 보면서 새롭게 목표를 잡은 것들이 있었다.
- 위의 기능 요구 사항을 보고 기능을 구현하며 예외 사항을 생각하는 습관을 들여보자 → 완벽한 정답은 없다
- 기능 단위로 커밋을 하자. → 최대한 작은 단위로 쪼개서 커밋하는 것을 우선 해보도록 하자 그러다 보면 이 부분은 함께 묶어도 되지 않을까? 하는 기준을 세울 수 있을 것이라고 하셨다.
- 커밋 메시지를 의미 있게 작성하자. → feat, fix, refactor… 커밋 메시지 컨벤션을 읽어보고 목적에 맞춰서 메시지를 작성해볼 것이다.
- 이름을 통해 의도를 드러내자. → 메소드, 변수, 심지어 반복문의 i 까지 이름을 통해서 목적을 알 수 있도록 좋은 이름을 짓도록 해보자
- 단위 목록을 테스트 코드를 통해 확인하자
- 하나의 메소드가 많은 기능을 가지지 않도록 메소드를 분리하고 그에 맞춰 클래스도 생성하자
이렇게 목표를 가지고 이것들을 맞춰가며 코드를 작성해 보는 한 주였다.
우선 기능 목록을 README.md
에 기능 목록을 작성하고 동시에 생각한 내용들을 정리하며 Think.md
에 작성해 보았다. (자세한 내용은 깃허브에서 볼 수 있다.)
진행을 하며 개인적으로 구현 자체는 그렇게 어렵지는 않았다고 생각이 들기도 했다.
하지만 기능 목록을 작성했고 그에 맞춰서 구현을 하고 동시에 그때 그때 커밋을 하며 의미 있는 커밋 메시지를 작성한다 이런 목표에 맞추기 위해 노력하는 과정이 더 어려웠던 것 같다.
그래도 이후에 커밋 목록을 볼 때 수정을 했는지 기능을 추가했는지 정말 세세하게 정리가 되어있는 모습을 보는데 뿌듯함이 밀려오는 감정을 느낄 수 있었다.
(물론 이 뿌듯함에 중독이 되었는지 커밋이 80개가 넘어가는… 이게 맞는건가 싶기도 했다ㅎㅎ)
그래도 이 부분을 통해 나의 목표 2번과 3번을 나름 만족할 수 있었다.
그리고 가장 어려웠던 부분중 하나가 알맞게 의미 있는 메소드, 변수, 클래스의 이름을 정해주는 것이었다.
의미 있는 이름을 짓는 것은 나름 노력을 하며 리팩토링도 정말 많이 했는데 끝까지 아쉬움이 남아있는 내용 중 하나이다.
이렇게 목표 4번은 조금 아쉬움이 남게 되었다.
기능 목록을 보며 기능을 구현하며 예외를 꾸준히 생각 하였고 그에 맞춰서 예외 처리를 해주었었다.
더 이상의 예외 상황은 생각이 나지 않을 정도로 해결을 해주었고 중간 혹은 앞 뒤에 공백이 들어오는 경우 알아서 공백을 삭제하여 예외로 던지지 않을 수 있도록 처리를 해주기도 하였다.
나름대로 예외를 만드는 과정은 만족을 하며 지나갈 수 있었다.
그리고 이 예외들에 대해서 테스트 코드를 작성하고 모두 확인하는 과정은 가질 수 있었는데, 아쉽게도 나의 기능마다 하나하나 단위 테스트를 진행하고 싶었으나 그 부분은 더 괜찮게 코드를 작성할 수 있지 않았을까 하는 아쉬움이 남기도 했다.
그래서 1번 목표는 나름 만족스럽게 해냈으나 5번 목표는 아쉬움이 남게 되었다.
마지막으로 메소드가 최소한의 기능을 가지고 있도록 구현하며 클래스를 나눠주는 과정을 생각해 보면 메소드를 나눠주며 비슷한 기능의 메소드를 모아 가지고 있는 클래스로 나누어 주었었다.
그래도 뭔가 아쉬움이 남아 있었는데 그것은 패키지로 클래스들을 다시 한번 나눠주면 어떨까 였습니다.
그래서 패키지를 통해서 클래스도 나눠주었다.
그래도 나누는 김에 최대한 MVC 패턴을 맞춰 나눠 보려고 하였으나 그 부분은 성공적이지는 않았다고 느껴졌다.
우선 controller 패키지에 게임을 시작하는 클래스를 만들어 주고 domain이라는 패키지에 입력값을 받아주는 클래스와 랜덤값을 생성하는 클래스를 만들어 주며 service라는 패키지에 결과를 출력해주는 클래스와 규칙을 통해 판단해주는 클래스를 만들어 넣고 마지막으로 exception이라는 패키지에 예외 처리를 담당하는 클래스를 만들어 주었다.
그래도 6번 목표를 최대한 달성하기 위해 노력했고 추가로 패키지로 나누었다는 점에 대해서는 만족하고 있다.
그렇게 우여곡절 끝에 테스트도 여러가지로 시도하고 통과하는 모습을 확인하고 제출을 하게 되었다.
역시 제출하고 초록색 숫자를 보는 순간 기쁘다…
4주를 거치고 나면 내가 제일 좋아하는 색은 초록색이 될 것 같다
테스트 케이스를 작성하고 돌렸을 때 모두 초록색으로 뜨면 기쁘고 제출 결과 초록색이면 또 기쁘고ㅋㅋㅋㅋㅋ
진짜 초록색이 쭈욱 뜨면 기분이 좋아진다… 😂
아쉬운점…
테스트 코드도 모두 통과했고 무사히 제출을 했지만 그래도 아쉬움은 언제나 남게 되는 것 같다.
많은 아쉬움 중 다음에는 고쳐서 아쉬움을 남기지 않고 싶은 부분을 뽑자면
예외 테스트를 조금 더 세세하게 작성해 볼 수 있었다면 어땠을까 하는 생각이 남는다.
그리고 변수 이름에 자료형, 자료 구조 등을 사용하지 마라.
피드백에서 이러한 내용을 보았는데 굉장히 뜨끔하는 내용이었다. 고쳐야겠다..
마치며…
이번 미션부터 알고리즘이 아닌 어떤 기능을 할 수 있도록 만들어 와라 하는 미션이었는데 개인적으로 알고리즘보다 재미있던 과정이 아니었나 싶다.
어떤 기능을 가지고 있어야 하는지 제공이 되고 그에 추가로 넣어줘야 할 것은 무엇이 있을지 고민하고 어떤 내용을 예외로 처리해야 할지 고민하고… 시간도 금방 가고 정말 집중해서 할 수 있는 한 주를 지낸 것 같다.
물론 나도 정말 많은 고민을 하며 기능 목록을 작성하고 기능을 구현하고 예외를 만들었어도 다른 이들의 PR을 보다 보면 와… 진짜 잘하는구나 싶기도 하다.
하지만 지금의 실력 차이는 그 사람들과 나의 경험 차이라고 생각한다.
그런 사람들을 보며 나는 안되겠구나… 하는 자존감 떨어질 수 있는 생각은 하지 않으려 한다.
물론 자연스럽게 그런 생각이 들 수 있겠지만
위에서 말했듯 지금의 실력 차이가 경험 차이라고 생각하며 나도 앞으로 경험을 열심히 쌓아서 저렇게 되어야지 하는 생각으로 동기부여를 받으려고 한다.
이렇게 프리코스 2주차가 지나 3주차가 시작되고 있다.
한 달 동안의 소중한 경험, 이 기간이 지나가면 나는 분명 한단계 성장해 있을 것이다.
성장하는 동시에 최종 합격을 위해 꾸준히 노력할 것이다.
3주차도 화이팅 하며 마치도록 하겠다.
피드백 이후 필요에 의해 추가로 조사한 자료!
@ParameterizedTest
??
테스트를 수행하다 보면 여러 값을 확인하기 위해서 코드를 여러번 작성하는 경우가 있다.
이 때 중복 코드를 제거하기 위해서 @ParameterizedTest라는 어노테이션을 사용하면 좋다.
@CsvSource
??
이는 입력값에 따라서 결과값이 다른 경우에 대한 테스트를 진행할 때 사용하는 것이다.
CSV(Comma Separated Values) 라는 이름에서 알 수 있듯 콤마를 기준으로 CSV를 구분해서 읽는다.
private static final List<Integer> ANSWER = Arrays.asList(1,2,3);
private Refree refree;
@BeforeEach
public setUp() {
refree = new Refree();
}
@ParameterizedTest
@CsvSource({"1,2,3,스트라이크", "7,8,9,낫싱", "1,3,2,2볼 1스트라이크"})
public void compare(int number1, int number2, int number3, String expected) {
String actual = refree.compare(ANSWER, Arrays.asList(number1, number2, number3));
assertThat(actual).isEqualTo(expected);
}
이렇게 “”, “”, 콤마를 기준으로 값이 들어가게 된다.
이외에도 @ValueSource
, @MethodSource
와 같은 어노테이션이 존재한다.
@ValueSource
@Test
void set_contains_test() {
assertThat(numbers.contains(1)).isTrue();
assertThat(numbers.contains(2)).isTrue();
assertThat(numbers.contains(3)).isTrue();
...
}
이런 코드의 경우 중복되는 코드들이 나오게 된다.
→ ParameterizedTest 어노테이션 사용!
@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void set_contains_test(int element) {
assertThat(numbers.contains(element)).isTrue();
}
이렇게 @ValueSource
는 리터럴 값의 배열에 대한 접근을 제공하는 어노테이션이다.
@MethodSource
@DisplayName("스트라이크 테스트")
@ParameterizedTest
@MethodSource("generateData")
public void test1(List<Integer> playerLists, List<Integer> computerLists, int index) {
PlayResult playResult = new PlayResult();
int strikeCount = playResult.countStrike(playerLists, computerLists);
int[] result = {1, 0};
assertThat(strikeCount).isEqualTo(result[index]);
}
@DisplayName("볼 테스트")
@ParameterizedTest
@MethodSource("generateData")
void test_ball(List<Integer> playerLists, List<Integer> computerLists, int index) {
PlayResult playResult = new PlayResult();
int ballCount = playResult.countBall(playerLists, computerLists);
int[] result = {2, 0};
assertThat(ballCount).isEqualTo(result[index]);
}
static Stream<Arguments> generateData() {
return Stream.of(
Arguments.of(Arrays.asList(1, 2, 3), Arrays.asList(1, 3, 2), 0),
Arguments.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), 1)
);
}
이러한 방식으로 사용하는 것으로
generateData() 메소드 → 그저 생성한 이름일 뿐 이름은 다를 수 있다.
를 통해서 테스트할 파라미터를 Stream<Arguments>
형식으로 반환한다.
static Stream<Arguments> generateData() {
return Stream.of(
Arguments.of(Arrays.asList(1, 2, 3), Arrays.asList(1, 3, 2), 0),
Arguments.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), 1)
);
}
이를 통해서 위의 테스트에서는
한번은 Arrays.asList(1, 2, 3), Arrays.asList(1, 3, 2), 0
이렇게 파라미터로 넘어가고
한번은 Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), 1
이렇게 파라미터로 넘어가는 것이다.