자동차 경주 1단계 회고

후니팍·2023년 2월 24일
5
post-thumbnail

요구 사항 목록

  • 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
  • 각 자동차에 이름을 부여할 수 있다.
  • 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
  • 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
  • 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
  • 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
  • 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다.
  • 우승자는 한 명 이상일 수 있다.

첫 설계

나의 방식

우테코 최종 코딩테스트를 준비했을 때 한 번 풀어본 문제였기 때문에 기능 요구 사항을 이해하는데 문제가 없었다. 기능이 어렵지 않아서인지 페어와 나는 프로그램의 실행 순서에 맞게 기능 목록을 작성했다. 지금 생각해보면 프로그램 규모가 작고 머릿속으로 설계할 수 있는 정도의 크기이기 때문에 프로그램 실행 순서대로 코드를 작성할 수 있었던 것 같다.

문제점

하지만, 만약 프로그램 규모가 커져 카트라이더 같은 게임이 되면 같은 방법으로 설계해도 될까? 의문이 들었다. 카트라이더 정도의 규모는 머리로 기능을 순서대로 나열하는 것이 불가능하다. 내 방식대로 설계하면 무조건 놓치는 기능이 생길 것이다.

개선 사항

회고를 작성하는 이 시점에서 내가 생각하는 좋은 방식은 도메인 별 기능 목록 작성이다. 비슷한 기능들을 모아서 도메인의 역할로 미리 정의해주고, 도메인 별로 코드를 작성하는 방식이다. 단위 테스트를 작성할 때도 편할 뿐더러, 기능이 추가되거나 삭제되더라도 기능 목록 수정이 편하겠다는 생각이 들었다. 그리고 놓치는 부분을 인지하고 기능 목록을 계속해서 업데이트해가는 것이 우테코에서 말하는 살아있는 기능 목록을 만드는 것이 아닐까 생각한다. 실제로 두 번째 미션인 "사다리 타기"를 진행할 때, 리뷰어분께서 도메인 별로 정리하는 것이 더 좋을 것 같다는 피드백도 받았다.


구현 방법

  1. Car 객체에 자동차 이름을 저장하는 name과 위치를 저장하는 currentPosition를 필드를 두었다. 아래의 코드를 보면 currentPosition의 자료형이 Position인 것을 확인할 수 있는데, Car에서 현재 위치를 관리하는 것은 자동차의 역할이라기에 무겁다는 느낌이 들어 Position이라는 도메인을 만들어 관리하도록 했다.
private final String name;
private final Position currentPosition;

public Car(String name, int startPoint) {
    validateName(name);
    this.name = name;
    this.currentPosition = new Position(startPoint);
}
  1. 일급 컬렉션 Cars를 만들어 모든 Car를 관리하는 리스트를 객체화시켜 한 번 감싸주었다. 일급 컬렉션으로 감싸준 이유는 링크를 참고하면 된다.
private final List<Car> cars;

public Cars(List<String> carNames) {
    validateDuplicatedNames(carNames);
    validateCarCount(carNames.size());
    this.cars = createCarsByNames(carNames);
}
  1. 컨트롤러에서 뷰에서 입력받은 값을 가지고 CarCars 인스턴스를 생성하도록 했고, 만들어진 Cars 내부에서 자동차가 움직이는 로직을 구현했다. 코드에서 NumberGenerator는 숫자를 발행하는 메서드가 들어있는 인터페이스이다. 전략 패턴을 통해 랜덤 숫자 발행기와 원하는 숫자 발행기를 만들어 테스트가 쉽도록 했다. 전략 패턴에 대한 자세한 내용은 링크를 참고하길 바란다.
// Cars
public List<Car> moveCars(NumberGenerator numberGenerator) {
    cars.forEach(car -> car.move(numberGenerator));
    return Collections.unmodifiableList(cars);
}

// Car
public void move(NumberGenerator numberGenerator) {
    int randomNumber = numberGenerator.generate();
    if (isMovable(randomNumber)) {
        currentPosition.move();
    }
}
  1. 승자를 가려내는 역할은 WinnerMaker 객체가 담당하도록 했다. 자동차들 중에 가장 위치값이 큰 자동차 하나를 가려낸 후에 그 자동차와 위치값이 같은 자동차들을 반환하도록 했다. 코드를 작성할 당시에는 가장 앞에 있는 자동차를 받아오는 것이 최선이라고 생각했지만 지금은 생각이 다르다. 자동차끼리 비교하는 것이 아닌 위치값끼리 비교하는 로직을 만들 것 같다. 결국 비교해야하는 대상은 위치값이기 때문이다.
public class WinnerMaker {
     public static List<String> getWinnerCarsName(List<Car> cars) {
         Car winner = getWinner(cars);
         return cars.stream()
                 .filter(car -> car.isSamePosition(winner))
                 .map(Car::getName)
                 .collect(Collectors.toUnmodifiableList());
     }

     private static Car getWinner(List<Car> cars) {
         return cars.stream()
                 .max(Car::compareTo)
                 .orElseThrow(() -> new IllegalArgumentException(ErrorConstant.ERROR_PREFIX + "비교할 자동차가 없습니다."));
     }
 }

피드백

올바르지 못한 DTO 사용

나는 결과값 계산에 필요한 자동차 이름자동차의 현재 위치를 DTO를 통해 View에 전달했다. 문제는 Car에서부터 dto를 만들어 view로 전달했다는 것이다. 코드를 작성할 당시 내가 생각한 dto의 역할은 아래와 같다.

  1. 원하는 값만을 도메인에서 쑉쑉 꺼내 view에 전달할 수 있다.
  2. 데이터가 변할 수 있다는 걱정 없이 안정적으로 데이터를 전달할 수 있다.

2가지 장점을 모두 살려 사용했는데, 그렇다면 무엇이 문제일까? 나는 dto 이전에 생각해야할 가장 큰 원칙인 mvc를 무시하고 dto를 사용했다. domain이 마치 view를 인식하고 데이터를 전달하는 듯한 느낌을 준다. domain은 view를 몰라야하는데, dto를 domain에서 사용함으로써 마치 view를 아는 것처럼 되어버렸다. 그로 인해 dto에 대해 더 알아보는 것이 좋을 것 같다는 리뷰를 받았고 더 깊게 공부할 수 있었다. 공부한 후에 controller에서 dto를 생성하도록 로직을 바꾸었고 dto가 dto답게 쓰일 수 있게 되었다.


Repository

처음에 Cars라는 객체 이름 대신 CarRepository라는 이름을 사용했다. DB와 연결되어 쿼리로 원하는 데이터를 들고오는 역할이 Repository의 역할이다. 하지만 자동차의 일급 컬렉션의 역할을 하는 객체 이름을 CarRepository로 만들었으니 언급 받았다. 처음에는 자동차 데이터들을 관리하는 역할이기 때문에 Repository로 이름을 정해야 좋지 않을까 생각했지만, Repository와 일급 컬렉션을 제대로 이해하지 못한 내 불찰이었다. 리뷰어께서 언급해주신 덕분에 교정할 수 있었다.


github


마무리

첫 미션 자신만만하게 풀었지만 다시 돌아보니 부족한 점이 많았다. 1단계 미션의 코드는 부끄러운 정도이다. 우테코 기간동안 부족한 부분들을 이렇게 기록하며 차곡차곡 쌓아가며 내 것으로 만들 예정이다. 1단계에서 미처 해결하지 못한 부분들은 2단계 로그에 업로드할 계획이다.

profile
영차영차

4개의 댓글

comment-user-thumbnail
2023년 2월 24일

역시 내 페어 갱장하네요

답글 달기
comment-user-thumbnail
2023년 2월 24일

많은 고민을 한것이 정말 눈에 보이네요.. 배워갑니다..

답글 달기
comment-user-thumbnail
2023년 2월 26일

도메인별로 기능 목록을 작성하는 거 정말 좋아 보이네요~
저도 다음에 한번 적용해볼게요!

답글 달기
comment-user-thumbnail
2023년 3월 3일

재밌게 읽었습니다 😀

답글 달기