본문 바로가기
코딩 교육 기관/우아한테크캠프 프로

우아한 테크코스 프로 - 프리코스 2주차

by 성건희 2022. 10. 10.
반응형

1주차에 이어 2주차도 진행이 되었는데, 진행하면서 느낀점을 적어보려고 한다.

느낀점

Random 값을 어떻게 테스트해야할까?

나는 자동차에 관련된 부분을 Car 클래스가 담당하도록 하고 이동 거리에 대한 부분은 play() 메서드가 담당하도록 하였다.
움직일 수 있는 조건에 해당하면 거리를 1 증가시키게 해두었다.


거리는 Random 값으로 나온 수가 지정한 값 이상일 경우에만 증가할 수 있도록 하였다.

그러다보니 테스트를 작성할 때 Random 값을 고정할 수 없어 테스트를 돌릴때마다 성공, 실패 되어버리는 문제가 있었다.
'어떻게 하면 Random 값을 테스트할 수 있을까?' 고민하다가 canMove 메서드 안에서 랜덤값을 생성하지 말고
아래와 같이 랜덤한 값을 바깥에서 생성한 뒤 파라미터로 넘기면 어떨까 생각했었다.


하지만 사용하는 입장에서 생각해보면, play 하는 역할만 알면되지, 랜덤값을 생성해서 넘기는 것 까지 사용자가 알아야 하나? 라는 의문이 들었다.
그래서 해당 방법은 포기.

Randoms 가 static 메서드로 되어있어서 mockito 에서 제공하는 MockedStatic 을 이용해서 랜덤 메서드 사용 시 지정한 값을 리턴하게 하면 될 것 같았다.


위와 같이 when 절로 Randoms.pickNumberInRange 메서드를 사용하게 되면 MOVING_FORWARD 값인 4를 무조건 리턴하게 된다.
따라서 해당 테스트 코드는 항상 같은 결과를 반환하게 된다.

예외가 발생한 부분부터 입력을 다시 받는다

미션 요구사항 중 사용자가 잘못된 값을 입력할 경우 예외를 발생시키고, 예외가 발생한 부분부터 입력을 다시받는다 는 요구사항이 있었다.
처음에는 아래와 같이 자동차 이름과 시도할 회수를 각각 입력하는 경우를 반복문으로 돌리며 예외가 발생 시 다시 입력을 받도록 구현하였다.

위와 같이 처리하다 보니 InputView 를 수행하는 부분과 return 타입이 다르고 나머지 흐름은 동일하여 중복되는 듯한 코드가 만들어졌다.
어떻게 중복을 제거할 수 있을지 고민하다가, 템플릿 콜백 패턴을 사용하면 중복을 제거할 수 있을 것 같았다.

조건이 맞을 때까지 반복하며 실행해야 할 로직 (callBack)을 수행하는 역할을 RepeatService 가 담당하도록 하였다.
각 타입은 Cars 와 PlayCount 가 사용되니 제네릭 (T) 으로 구현하여 원하는 타입을 리턴할 수 있도록 만들었다.
수행해야 할 로직은 FunctionalInterface 인 callBack 으로 보내서 수행하므로 각각의 객체가 다른 로직을 수행할 수 있다.
해당 메서드의 사용은 아래와 같이 하면 된다.

중복이 제거되어 좀 더 깔끔한 코드가 되었다.

모든 원시값과 문자열을 포장한다

기존에는 아래와 같이 Car 클래스가 원시값을 속성으로 가지고 있었다.

public class Car {
    private final String name;
    private final int distance;
}

사실 현업에서도 이런 방식으로 사용을 해왔었던지라 별다른 문제가 없다고 생각하며 요구사항을 구현했었는데
코드를 완성하니 Car 생성자에 name 과 distance 가 유효한 값인지 검증하는 로직이 추가로 들어가다보니 코드가 길어지고 가독성이 떨어지는 문제가 있었다. 미션 요구사항 중 모든 원시값과 문자열을 포장하라 는 요구사항이 있어서 해당 속성을 아래와 같이 포장하였다.

public class Car {
    private final Name name;
    private final Distance distance;
}

이렇게 포장하니 유효성 검증을 각각의 포장된 객체안에서 수행하면 되었다.

Car 클래스에서는 비즈니스 로직만 가지게 되어 코드가 좀 더 깔끔해졌다.
name 나 distance 의 기능이 필요하면 메시지를 호출하는 방식으로 구현하여 객체지향으로 작성하려고 노력했다.

예외 처리 중복 제거

유효성 체크에 실패하면 예외를 발생시키는데, 예외의 메시지는 [ERROR] 로 시작해야하는 요구사항이 있었다.
따라서 아래와 같이 구현했다.

위 코드를 보다보니 다음과 같은 문제가 있었다.

  1. '[ERROR]' 메시지를 매번 작성해야한다.
  2. 개발자의 실수로 '[ERROR]' 메시지를 작성하지 않으면 테스트가 실패한다.

고민하다가 RacingCarIllegalArgumentException 라는 예외를 만들어서 처리하기로 했다.

위와 같이 처리하면 '[ERROR]' 메시지를 작성하지 않아도 알아서 추가가 된다.
예외의 이름을 고민 했었는데, 처음에는 RacingCarException 으로 하려고 했으나.. 예외는 IllegalArgumentException 을 발생시켜야 한다는 요구사항이 있었다.
이 코드를 처음보는 개발자가 'RacingCarException 을 발생시켰는데 왜 IllegalArgumentException 가 발생하지?' 라고 혼동이 올 수 있을 것 같았다.
고민하다가 결국 RacingCarIllegalArgumentException 으로 정하긴했는데 영 찝찝하긴 하다..

일급콜렉션을 활용해 구현한다

구현 요구사항 중 일급콜렉션을 활용해 구현해야하는 조건이 있었다.
경주할 자동차의 리스트를 List<Car> cars; 로 가지고 있었는데, 이것을 일급콜렉션으로 만들어보았다.

내가 생각한 일급콜렉션을 사용하였을 때 장점은 아래와 같았다.

  1. List<Car> cars; 보다는 Cars cars; 가 좀 더 코드를 읽기에 자연스러웠다.
  2. 자동차 리스트를 생성할 때 조건들을 검증하며 유효한 값임을 보장할 수 있다.
  3. 리스트의 기능을 사용할 때 일급콜렉션 안에서 구현하고 사용하면 된다.
    예를 들면 자동차 리스트를 경주시킬 때 Cars 클래스의 race 메서드를 사용하면 된다

일급콜렉션으로 사용하면서 단점도 있었는데,
예를 들어 리스트를 전체 조회할 경우 getter 를 만들어주어야 한다는 게 불편했다.

사실 여기서 고민이었던게 리스트를 전체 조회하는 getter 를 만들어도 되는걸까 싶었다.
getter 로 List 를 가져와서 add 등 남발할 수 있을것 같았기 때문에 이러면 일급콜렉션으로 분리한 의미가 없을 것 같았기 때문이다.

OutputView 클래스의 1턴의 실행 결과를 출력하는 printRaceOneTurnResult 메서드를 살펴보자.


1턴의 실행 결과를 나타내는 부분이 기존에는 getter 를 통해서 가져왔었는데, getter 를 사용하지 않도록 수정했다.

정리

2차 미션을 수행할 때는 1차 미션보다 각 역할에 따라 어떤 객체가 책임을 맡는게 좋을까에 대한 고민이 많았었던 것 같다.
원시값과 문자열을 포장하고 일급콜렉션을 활용하는게 익숙하지 않았는데, 적용하고 나니 확실히 각각의 객체가 자신의 일만 담당하면 되어 보기에 깔끔해지는 장점이 있는 것 같다.
추가로 사용자의 잘못된 값을 입력할 때 예외 처리 후 다시 입력받는 부분을 구현하는데 고민을 많이했었는데, 템플릿 콜백 패턴을 통해 중복을 제거하는 방법을 선택했었다. 이외에 다른 방법은 어떤게 있을 지 궁금하다.
진행한 프리코스에 대한 피드백이 없다는게 조금은 아쉽긴했지만 지원자가 많아 어쩔 수 없는 것 같다 😭
짧은 시간이었지만 극단적으로 메서드를 분리하는 작업을 많이 배웠고, 이를 토대로 현업에서 적용해보면서 실력을 더욱 키울 수 있을 것 같았다.
나를 다시 되돌아보는 이런 좋은 기회를 제공해주어서 너무 감사하다.

참고

반응형

댓글