Skip to content

Conversation

@sebeeeen
Copy link

🏎️ 자동차 경주 게임

🎯 구현 완료 사항

기능 요구사항 ✅

  • n대의 자동차가 주어진 횟수만큼 전진/정지
  • 자동차 이름 입력 (쉼표 구분, 5자 이하)
  • 무작위 값 4 이상이면 전진
  • 우승자 출력 (단독/공동)
  • 잘못된 입력 시 [ERROR] 발생

프로그래밍 요구사항 ✅

  • indent depth 2 이하
  • 3항 연산자 미사용
  • 함수는 단일 책임
  • Jest 테스트 작성
  • @woowacourse/mission-utils 사용

📂 프로젝트 구조

src/
├── domain/          # 비즈니스 로직
│   ├── Car.js      
│   ├── Cars.js     
│   └── RacingGame.js
├── controller/      # 흐름 제어
│   └── GameController.js
├── validator/       # 입력 검증
│   └── InputValidator.js
└── view/            # 입출력
    ├── InputView.js
    └── OutputView.js

🧪 테스트 결과

테스트 개수 상태
Car 5
Cars 6
RacingGame 5
InputValidator 13
Total 29

💡 주요 구현 내용

1. TDD 방식 적용

  • 테스트 코드 먼저 작성 → 구현 → 리팩토링 순서
  • 각 도메인별 단위 테스트 작성

2. 객체지향 설계

  • 단일 책임 원칙: 각 클래스가 하나의 역할만 수행
  • 도메인 중심: Car, Cars, RacingGame으로 핵심 로직 분리
  • 계층 분리: Domain - Controller - View 구조

3. 코드 품질

  • Private 필드(#) 사용으로 캡슐화
  • 함수를 작게 분리하여 indent depth 최소화
  • 명확한 메서드명으로 가독성 향상

🔍 핵심 로직

Car 클래스

move(randomValue) {
  if (randomValue >= 4) {
    this.#position += 1;
  }
}

Cars 클래스 - 우승자 결정

getWinners() {
  const maxPosition = this.#getMaxPosition();
  return this.#cars.filter(car => car.getPosition() === maxPosition);
}

InputValidator - 검증 분리

static validateCarNames(names) {
  this.#validateNotEmpty(names);
  this.#validateNameLength(names);
  this.#validateNoDuplicate(names);
}

🤔 아쉬운 점

커밋 히스토리

미리 다른 파일에서 전체 구현을 완료한 후, 파일을 하나씩 옮기면서 커밋하는 방식으로 진행했습니다. 그 결과:

문제점:

  • 실제 개발 과정이 커밋에 제대로 반영되지 않음
  • Test → Implementation 순서가 커밋에 명확하게 드러나지 않음
  • 리팩토링 과정이 기록되지 않음

개선 방향:

  • 다음 미션부터는 실제 작업 순서대로 커밋
  • 작은 단위로 자주 커밋하여 개발 과정 기록
  • TDD Red-Green-Refactor 사이클을 커밋에 반영

📝 배운 점

  1. TDD의 중요성: 테스트 먼저 작성하니 요구사항이 명확해지고 리팩토링이 안전해짐
  2. 책임 분리: 각 클래스의 역할을 명확히 하니 코드 이해와 수정이 쉬워짐
  3. Private 필드: 캡슐화로 외부 접근을 차단하여 안전한 객체 설계
  4. 함수 분리: indent depth를 줄이기 위해 함수를 작게 나누니 가독성 향상

- 기능 요구사항 문서화
- 구현할 기능 목록 7개 카테고리로 분류
- 프로젝트 구조 설계
- 프로그래밍 요구사항 정리
- Console API를 활용한 사용자 입력 처리
- 자동차 이름 입력 및 파싱
- 시도 횟수 입력 및 변환
- 쉼표 구분 및 공백 제거 처리
- Console API를 활용한 결과 출력
- 라운드별 자동차 상태 출력
- 자동차 위치를 '-' 문자로 시각화
- 최종 우승자 출력 (단독/공동)
- 게임 전체 흐름 제어
- InputView를 통한 입력 처리
- InputValidator를 통한 유효성 검증
- Random API를 활용한 무작위 값 생성
- OutputView를 통한 결과 출력
- App 클래스로 애플리케이션 시작
- index.js 진입점 생성
- GameController 연동
Copy link

@zzzRYT zzzRYT left a comment

Choose a reason for hiding this comment

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

2주차 고생하셨습니다! 코멘트에 언급하긴 했지만, Controller 부분을 사용하는게 인상깊었습니다. 저도 다음 주차에서는 고려해봐야겠어요!💪

Comment on lines +33 to +66
test('최대 위치를 가진 자동차들을 찾는다 - 단독 우승자', () => {
const cars = new Cars(['pobi', 'woni', 'jun']);

cars.moveAll([5, 4, 3]);
cars.moveAll([6, 3, 2]);

const winners = cars.getWinners();

expect(winners).toHaveLength(1);
expect(winners[0].getName()).toBe('pobi');
});

test('최대 위치를 가진 자동차들을 찾는다 - 공동 우승자', () => {
const cars = new Cars(['pobi', 'woni', 'jun']);

cars.moveAll([5, 6, 3]);
cars.moveAll([6, 5, 2]);

const winners = cars.getWinners();

expect(winners).toHaveLength(2);
expect(winners.map(car => car.getName())).toEqual(expect.arrayContaining(['pobi', 'woni']));
});

test('모든 자동차가 같은 위치면 모두 우승자다', () => {
const cars = new Cars(['pobi', 'woni', 'jun']);

cars.moveAll([3, 3, 3]);

const winners = cars.getWinners();

expect(winners).toHaveLength(3);
});
}); No newline at end of file
Copy link

Choose a reason for hiding this comment

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

위 3개의 테스트의 경우에는 test.each로 한 번에 처리가 가능할 것 같습니다!
우테코에서 알려준 블로그

Copy link

@zzzRYT zzzRYT Oct 28, 2025

Choose a reason for hiding this comment

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

제 코드를 기준으로 생각했을 때, App에서 실행에 대한 역할을 수행하고 있습니다. 저도 App에서 사용하는 여러가지 로직들에 대한 책임을 관리해줄 중간 관리자가 있으면 좋겠다고 생각했는데, 해당 코드를 보고 고민해봐야 겠네요ㅎㅎ 잘봤습니다!😁

Comment on lines +10 to +14
move(randomValue) {
if (randomValue >= 4) {
this.#position += 1;
}
}
Copy link

Choose a reason for hiding this comment

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

제 코드에서도 적용하지 못한 부분이긴한데, 이 코드를 문득 든 생각입니다. 정확하지 않을 수 있으니 참고하세요😅

Car에서는 오로지 움직일 수 있는 역할만 수행한다면, 랜덤값이 4이상 일 때라는 조건을 Game쪽으로 책임을 넘기고 오로지 움직임만을 위한 로직을 만들어도 괜찮을 것 같네요

이렇게 생각한 이유가, 만약 게임에 대한 룰이 바뀌었을 경우, Game에서도 바꿔야겠지만, Car에서도 랜덤값이 4이상으로 바꿔줘야하니깐 책임의 분리가 명확하지 않다는 느낌이랄까요..?😮‍💨

저도 제 코드에 한 번 적용해 봐야겠네요☺️

Copy link

Choose a reason for hiding this comment

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

개인적인 생각이긴하지만, 검증 로직의 경우에 범용적으로 사용되는 검증이 아니라면, 사용되는 부분과 함께 관리되는게 좋다고 생각합니다. 그래야 변경사항이 생겼을 때, 유지보수하기 편하더라구요

예를들어서, validation이라는 폴더에 하위 파일로 carValidation,inputValidation과 같이 나눈다거나, 그냥 사용하는 파일 옆에 같이 붙여둔다던가. 그럴 것 같습니다!😎

Copy link

@lee-eojin lee-eojin left a comment

Choose a reason for hiding this comment

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

안녕하세요! 2주차도 수고하셨습니다! 객체지향 설계 원칙을 잘 적용하신 것 같습니다.
특히 Validator가 Controller와 완전히 분리되어 있는 설계가 인상적이었습니다.
테스트 케이스가 다양해서 재밌게 공부했습니다.

Comment on lines +1 to +4
import App from './App.js';

const app = new App();
await app.run();
app.run();

Choose a reason for hiding this comment

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

App.run()이 async 메서드라서, 원본 템플릿에는 await이 붙어있었던 것 같습니다.
혹시 await을 제거하신 특별한 이유가 있으실까요? 궁금합니다!

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