Skip to content

Conversation

@yerim123456
Copy link

@yerim123456 yerim123456 commented Oct 27, 2025

✅ 기본 조건

입력 조건

  • 사용자가 잘못된 값을 입력한 경우 IllegalArgumentException을 발생 후 종료되어야 함

자동차 이름

  • 경주할 자동차 이름과 경주 차수 2가지 입력 필요
  • 자동차 이름은 , 기준으로 구분
  • 자동차 이름은 1자 이상 5자 이하만 가능함
  • 사용성을 고려해 자동차 이름에 공백이 있는 경우 없는 것으로 처리함

경주 차수

  • 경주 차수는 숫자여야 함
  • 경주 차수는 양의 정수여야 함

동작 조건

  • 경주는 1명이서 할 수 없음
  • 입력된 경주 차수 동안 n대의 자동차는 전진 또는 멈출 수 있음
    • 전진 조건: 0 ~ 9 사이에서 추출한 무작위 값이 4 이상인 경우
  • 각 경주마다 각 자동차의 이동 결과를 출력
  • 우승자는 한 명 이상일 수 있음
    • 우승 조건: 가장 많이 전진한 자동차

출력 조건

  • 자동차 이름은 , 기준으로 구분
  • 전진하는 자동차를 출력할 때마다, 전진한 자동차 이름과 이동거리를 출력 (ex pobi : --)
  • 자동차 경주 게임 완료 후, 누가 우승했는지 출력 (ex 최종 우승자 : pobi)
  • 공동 우승자의 경우 ,를 기준으로 출력 (ex 최종 우승자 : pobi, jun)
  • 우승자 출력 시, 입력 순서로 출력해야 함

✅ 구조 설정 이유

의존성 방향

[Application]
        ↓
[Controller] → [View]
        ↓
 [Service]
        ↓
 [Domain]
        ↑
 [Mapper] → [DTO]

단일 책임 원칙과 의존성 역전 원칙, 유지보수 용의성을 고려하여 설계하고자 했습니다.

  • 단일 책임 원칙에 따라 각 패키지 별 책임을 명시하면 읽는 사람이 설계 의도를 바로 이해할 수 있도록 설정하였습니다.
    - Controller → Service → Domain 의 단방향 의존 구조를 통해, 상위 계층이 하위 계층의 세부 구현에 의존하지 않도록 하였습니다. 즉, Domain은 Controller나 View를 몰라도 되고, 변경에 영향을 받지 않습니다. Mapper는 Domain 객체를 DTO로 변환하는 역할만 수행하며, DTO는 View와 Controller에 전달되는 데이터 구조로 사용됩니다.
  • 이러한 구조를 통해 외부의 변화에도 Domain과 Service는 영향을 받지 않도록 설계되었습니다.

패키지 별 보유 클래스

racingcar
├─ controller      : RacingController
├─ domain          : Car, Cars, MovePolicy, RandomMovePolicy, WinnerCalculator
├─ dto          : CarDto, CarsDto
├─ error           : ErrorMessages
├─ mapper         : CarsMapper
├─ service         : RacingService
├─ util            : RandomGenerator
├─ view            : InputView, OutputView, ViewMessages
└─ Application

Domain에는 이 프로젝트가 해결하고자 하는 본질적인 문제를 표현하는 부분을 담았습니다. Car, Cars 자동차 및 여러 자동차를 관리하는 로직, WinnerCalculator 게임의 결과를 계산하는 핵심 로직, MovePolicy, RandomMovePolicy 전진의 조건을 계산하기 위한 게임의 규칙 로직을 구현해두었습니다.

Service 는 각 도메인 객체를 조합해서 각 로직의 실행 흐름을 구현해 두었습니다. 입력을 받아 Cars 객체로 만드는 initCars 메서드, 입력값의 유효성을 검증하는 validate 메서드들, MovePolicy에 따라 Cars 내부 메서드를 통해 진행하는 경주에서의 자동차 전진 위치 증가 playRound 메서드, WinnerCalculator의 static 메서드를 통해 진행하는 이긴 자동차 리스트 받아오는 getWinners 메서드로 구성되어 있습니다.

View를 통해 입력과 출력의 형식만 담당하여 InputViewOutputView를 구성하였습니다. 따로 입출력을 위해 필요한 String 리소스는 ViewMessages Enum 클래스로 관리하고 있습니다.

Controller 를 통해 입력 > 서비스 실행 > 출력 프로그램의 전체 흐름을 제어하였습니다. 따라서 RacingController의 run 메서드를 통해 이 프로젝트가 실행되는 흐름을 확인할 수 있습니다.

그 외로 errorutil 패키지를 통해 예외 메세지 관리, 랜덤 수 생성을 진행하고 있습니다. 마지막으로 dip 의존성 역전 원칙 위배를 보완하기 위해 dtomapper를 통해 dtocontrollerview 사이에서 객체 데이터를 주고 받도록 설정하였습니다.


✅ 예외 조건 설정 및 이유
자동차 이름

  • 자동차 이름이 공백이면 예외 발생
    • 이름, ,이름 의 경우 사용자의 실수일 수 있지만 앞선 요구사항에서 ,로 이름을 구분하라는 부분과 자동차 이름은 1자 이상과 5자 이하만 가능하다는 분석에 입각해 공백 시 예외를 발생 시키고자 하였습니다.
  • 자동차 이름은 5자 초과면 예외 발생
    • 요구사항에 기재되어 있음
  • 자동차 이름 중복 입력 시 예외 발생
    • 자동차 이름을 중복으로 내 준 경우, 입력 순서에 따라 다르게 처리가 가능하겠지만 실제 경주에서 같은 차량이 나올 수 없다는 점을 참고하여 예외 사항으로 처리하였습니다. 또한 다양한 경기에선 같은 이름의 선수에 대해 다른 특성을 고려하여 다른 점을 판단하지만 현재 car은 특성이 이름과 위치 뿐이기에 사용자의 입력에 따른 사용자 혼란이 있을 수 있다는 점을 고려하여 중복 입력 시 예외를 발생하도록 설정하였습니다. 또한 Car 클래스에 equalhashCode 메서드를 오버라이드하여 이름이 같다면 같은 객체로 처리하도록 하여 부가적인 문제를 원천 차단하였습니다.
  • 자동차 이름 목록에 이름이 1개일 경우 예외 발생
    • 경주라는 의미를 누군가와 겨루다로 받아들였기에 혼자서는 경주가 불가능하다고 판단하여 이름이 1개일 경우 예외를 발생시키도록 하였습니다.

경주 차수

  • 경주 차수에 공백 입력 시 예외 발생
    • 경주 차수이기에 1회라도 있어야 이 프로젝트의 의미가 있기에 해당 부분을 예외 발생으로 처리하였습니다.
  • 경주 차수에 숫자가 아닌 값 입력 시 예외 발생(NumberFormatException이 발생하는 경우)
    • 경주 차수는 숫자로 입력되어야 하기에 해당 부분 예외 처리하였습니다.
  • 경주 차수에 0 입력 시 예외 발생
    • 맨 위의 예외 발생 이유와 같은 이유입니다.
  • 경주 차수에 음수 입력 시 예외 발생
    • 경주 차수이기에 양의 정수만 가능한데, 음수의 경우 경주를 안하는 것뿐만 아니라 경주를 취소해야 한다는 의미로 받아들여져 한번도 안 한 경주를 취소할 수 없는 상황에 예외 발생으로 처리하였습니다.

✅ 테스트
JUnit 5와 AssertJ를 이용하여 테스트 코드를 작성하였습니다. 작성한 코드가 단위 별로 잘 돌아가는지 단위 테스트를 진행하였으며, 제공된 2개의 테스트 이외의 CarTest, CarsTest, RandomMovePolicyTest, WinnerCalculatorTest, RacingServiceTest 로 20개의 테스트를 진행했습니다.


✅ 고민한 점

1. RandomMovePolicy 의 위치

직접 RandomMovePolicy 를 참조하지 않고 MovePolicy를 참조하게 하여 구체 구현체를 domain 단에서 직접 참조하지 않도록 설정해 DIP 규칙을 준수하게 하였습니다. 다만, 현재는 MovePolicy의 다양한 구현이 예측되지 않기에 domain 패키지에 위치시켰습니다.

2. 입력값 유효성 검증을 위한 validate 함수들 위치

입력값에 대한 유효성을 검증한다는 것은 맞지만, 구조에 맞춰서 왜 이 시점에서, 해당 대상에게, 관련 내용을 유효성을 검증해야 하는지에 대해 잘 드러나도록 검사하고자 노력했습니다.

클래스 메서드 검증 대상 유효성 검사 내용
Car validateName 자동차 이름 - null인지 확인, 공백(blank)인지 확인, 글자 수가 5자 이하인지 확인
Cars validateListNotNullAndEmpty 자동차 이름 목록 - 목록이 null인지 확인, 목록이 비어 있는지 확인
  validateInputNameIsMoreThanOne 자동차 이름 목록 - 입력한 자동차 수가 1개 이상인지 확인
  validateUniqueNames 자동차 이름 목록 - 이름 중복 여부 확인
RacingService validateInputNamesIsNotNullAndEmpty 자동차 이름 입력 값 - 자동차 이름 목록이 null인지 확인, 목록이 비어 있는지 확인
  validateInputRoundCountIsPlus 경주 차수 - 입력 값이 0보다 작거나 같은지 확인
InputView parseInt 경주 차수 입력 문자열 - 공백 입력 시 예외 처리, 숫자가 아닌 값 입력 시 예외 처리, NumberFormatException → IllegalArgumentException으로 변환

3. List<Car>이 아닌 Cars 클래스 설정 이유

이번 프로젝트를 진행하면서 일급 컬렉션(First-Class Collection) 개념을 알게 되었습니다. 단순히 Car 객체만 사용하는 것보다, List<Car>를 직접 사용하는 경우 컬렉션이 외부에서 자유롭게 변경될 수 있어 안전성 문제를 고민하게 되었습니다. 특히, 자동차 경주 우승이 1명으로 특정 되지 않을 때, 입력 순서대로 출력해야 하는 조건을 고려해야 했기에 이가 중요히 여겨졌습니다.

따라서 이를 해결하기 위해 Cars 클래스로 컬렉션을 감싸고, 컬렉션 관련 로직(전진, 우승자 계산 등)을 해당 클래스 안에서 처리함으로써 불변성을 유지하고, 보다 안전하고 깔끔한 코드를 구현할 수 있었습니다.

yerim123456 added 25 commits October 27, 2025 23:26
Copy link

@kwonhee1 kwonhee1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

README를 정말 상세하게 작성하셔서 리뷰하기 좋았어요

public List<Car> getCars() {
return cars;
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

순서에 대한 안정성을 위해 Cars 객체를 따로 만든 부분이 좋은 것 같아요

return new Car(name, location + 1);
}
return this;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move 할 때 마다 Car와 Cars 객체를 새로 찍어서 반환한 이유가 긍금합니다

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불변 객체로 만들고 싶다는 마음에 메모리는 많이 들지만 new를 사용해서 코드를 구성했습니다. 이번 요구 사항에 함수형 설계가 중점으로 나와있어서 불변 객체로 진행하여 함수가 항상 예측 가능하게 동작할 수 있도록 하였습니다. 가변 객체가 되면 외부 상태에 따라 객체의 결과가 변경될 가능성이 높다고 배워 그렇게 진행하였습니다. 좋은 질문 감사합니다😊

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

숫자 상수 하나 하나까지 정의해서 사용한거 보니 반성이 많이 되네요

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants