From fdc850b7a33829df21d2fa2d35bbf1c293ce1fa2 Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Wed, 24 Sep 2025 19:23:43 +0900 Subject: [PATCH 01/14] =?UTF-8?q?Docs:=20README.md=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 작동 순서대로 기능 목록 작성 테스트 케이스 작성 리펙토링 요소 작성 예정 --- README.md | 222 ++++++++---------------------- src/main/java/racingcar/Car.java | 4 + src/main/java/racingcar/Cars.java | 4 + 3 files changed, 68 insertions(+), 162 deletions(-) create mode 100644 src/main/java/racingcar/Car.java create mode 100644 src/main/java/racingcar/Cars.java diff --git a/README.md b/README.md index a2b6ed1440..6c1bbb9305 100644 --- a/README.md +++ b/README.md @@ -1,164 +1,62 @@ # java-racingcar-precourse -# 미션 - 자동차 경주 -## 🔍 진행 방식 - -- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. -- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. -- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. - -## 📮 미션 제출 방법 - -- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다. - - GitHub을 활용한 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고해 제출한다. -- GitHub에 미션을 제출한 후 [우아한테크코스 지원](https://apply.techcourse.co.kr) 사이트에 접속하여 프리코스 과제를 제출한다. - - 자세한 방법은 [제출 가이드](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 참고 - - **Pull Request만 보내고 지원 플랫폼에서 과제를 제출하지 않으면 최종 제출하지 않은 것으로 처리되니 주의한다.** - -## 🚨 과제 제출 전 체크 리스트 - 0점 방지 - -- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점으로 처리**한다. -- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다. -- **테스트가 실패할 경우 0점으로 처리**되므로, 반드시 확인 후 제출한다. - -### 테스트 실행 가이드 - -- 터미널에서 `java -version`을 실행하여 Java 버전이 17인지 확인한다. - Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 17로 실행되는지 확인한다. -- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고, - Windows 사용자의 경우 `gradlew.bat clean test` 또는 `./gradlew.bat clean test` 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다. - -``` -BUILD SUCCESSFUL in 0s -``` - ---- - -## 🚀 기능 요구 사항 - -초간단 자동차 경주 게임을 구현한다. - -- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. -- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. -- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. -- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. -- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. -- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. -- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. -- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. - -### 입출력 요구 사항 - -#### 입력 - -- 경주 할 자동차 이름(이름은 쉼표(,) 기준으로 구분) - -``` -pobi,woni,jun -``` - -- 시도할 회수 - -``` -5 -``` - -#### 출력 - -- 각 차수별 실행 결과 - -``` -pobi : -- -woni : ---- -jun : --- -``` - -- 단독 우승자 안내 문구 - -``` -최종 우승자 : pobi -``` - -- 공동 우승자 안내 문구 - -``` -최종 우승자 : pobi, jun -``` - -#### 실행 결과 예시 - -``` -경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) -pobi,woni,jun -시도할 회수는 몇회인가요? -5 - -실행 결과 -pobi : - -woni : -jun : - - -pobi : -- -woni : - -jun : -- - -pobi : --- -woni : -- -jun : --- - -pobi : ---- -woni : --- -jun : ---- - -pobi : ----- -woni : ---- -jun : ----- - -최종 우승자 : pobi, jun -``` - ---- - -## 🎯 프로그래밍 요구 사항 - -- JDK 17 버전에서 실행 가능해야 한다. **JDK 17에서 정상적으로 동작하지 않을 경우 0점 처리한다.** -- 프로그램 실행의 시작점은 `Application`의 `main()`이다. -- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다. -- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 준수하며 프로그래밍한다. -- 프로그램 종료 시 `System.exit()`를 호출하지 않는다. -- 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** -- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다. - -### 추가된 요구 사항 - -- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. - - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. -- 3항 연산자를 쓰지 않는다. -- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. -- JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다. - - 테스트 도구 사용법이 익숙하지 않다면 `test/java/study`를 참고하여 학습한 후 테스트를 구현한다. - -### 라이브러리 - -- JDK에서 제공하는 Random 및 Scanner API 대신 `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms` 및 `Console` API를 사용하여 구현해야 한다. - - Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInRange()`를 활용한다. - - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다. - -#### 사용 예시 - -- 0에서 9까지의 정수 중 한 개의 정수 반환 - -```java -Randoms.pickNumberInRange(0,9); -``` - ---- - -## ✏️ 과제 진행 요구 사항 - -- 미션은 [java-racingcar-7](https://github.com/woowacourse-precourse/java-racingcar-7) 저장소를 Fork & Clone해 시작한다. -- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다. -- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가한다. - - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다. -- 과제 진행 및 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고한다. +>스스로 판단한 내용은 `(판단)` 으로 표기한다. + +## 구현 기능 목록 + +### 1. 자동차의 이름을 입력받고 검증한다. +> 검증 조건이 추가되거나 변경될 가능성이 있다. +- [ ] 사용자에게 자동차 이름 입력 메시지 출력 +- [ ] 사용자가 입력한 값 받음 + - [ ] 입력값 받는 동시에 양쪽에 있는 공백 제거 +- [ ] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` + +### 2. 시도할 횟수를 입력받고 검증한다. +> 검증 조건이 추가되거나 변경될 가능성이 있다. +- [ ] 사용자에게 시도할 횟수 입력 메시지 출력 +- [ ] 사용자가 입력한 값 받음 + - [ ] 입력값 받는 동시에 양쪽에 있는 공백 제거 +- [ ] 입력 받은 양수값을 양수로 변경 + - [ ] 양수가 아닌 값 입력 시 예외 발생 + +### 3. 자동차의 이름을 분리하고 검증한다. +> 검증 조건이 추가되거나 변경될 가능성이 있다. +- [ ] 입력된 자동차 이름 문자열을 쉼표(,)를 기준으로 구분 +- [ ] 각 자동차 이름의 양쪽 공백 제거 후 글자 수가 5를 초과하는 경우가 있으면 예외 발생 +- [ ] 각 자동차 이름의 양쪽 공백를 제거한 각 자동차의 이름을 리스트로 저장 + - [ ] 이름이 공백만으로 이루어져 있으면 자동차로 여기지 않음 `(판단)` + +### 4. 자동차를 만들어 이름을 부여한다. +- [ ] 자동차 객체를 저장하는 리스트 생성 + - [ ] 자동차 객체를 생성하면서 각 이름을 저장 + +### 5. 자동차 경주를 진행하면서 과정을 출력한다. +- [ ] 자동차 경주 과정을 출력 + - [ ] 사용자가 입력한 횟수 만큼 각 자동차의 경주 과정을 출력 + - [ ] 각 자동차마다 랜덤 함수를 호출해 값이 4이상인 경우 "-" 를 1개 추가 + +### 6. 우승자를 선정해 출력한다. +- [ ] 자동차 경주 결과에 따른 우승자의 "-" 개수 구하기(즉, "-" 개수의 최댓값 구하기) + - [ ] 최댓값이 존재하지 않으면(자동차가 0개인 경우) 예외 발생 +- [ ] 최댓값을 가진 자동차 객체로만 리스트 생성 +- [ ] 최종 우승자 출력 + - [ ] 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분하여 출력 + + +## 테스트 코드 +### 정상 입력 케이스 +> 검증 조건이 추가되거나 변경될 가능성이 있다. +- [ ] 쉼표 2개 이상 연속으로 들어와도 정상 수행 `(판단)` +- [ ] 자동차이름에 숫자, 기호 허용 `(판단)` +- [ ] 자동차이름 중간 공백도 글자로 인식 -> 글자 수에 반영 `(판단)` + + +### 예외 발생 입력 케이스 +> 검증 조건이 추가되거나 변경될 가능성이 있다. +- [ ] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` +- [ ] 자동차 이름이 5자 초과하는 경우 `(판단)` +- [ ] 이동 횟수 입력이 양수가 아닌 값이 들어오는 경우 `(판단)` +- [ ] 모든 자동차의 이름이 공백만 있는 경우(즉, 자동차의 개수가 0인 경우) `(판단)` + +## 리펙토링 +> 코드가 수정되는 경우 변경 사항을 작성한다. \ No newline at end of file diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java new file mode 100644 index 0000000000..246c8e5abb --- /dev/null +++ b/src/main/java/racingcar/Car.java @@ -0,0 +1,4 @@ +package racingcar; + +public class Car { +} diff --git a/src/main/java/racingcar/Cars.java b/src/main/java/racingcar/Cars.java new file mode 100644 index 0000000000..f0149b565d --- /dev/null +++ b/src/main/java/racingcar/Cars.java @@ -0,0 +1,4 @@ +package racingcar; + +public class Cars { +} From 02a2b3a27f649017e266867e0e37c179f8eb6c0d Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Wed, 24 Sep 2025 19:38:49 +0900 Subject: [PATCH 02/14] =?UTF-8?q?Feat(Application):=20=EB=AA=A8=EB=93=A0?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20Apllication.java=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/Application.java | 72 ++++++++++++++++ src/main/java/racingcar/Car.java | 31 +++++++ src/test/java/racingcar/ApplicationTest.java | 87 +++++++++++++++++++- 3 files changed, 189 insertions(+), 1 deletion(-) diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index a17a52e724..13f1a8f0b9 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,7 +1,79 @@ package racingcar; +import camp.nextstep.edu.missionutils.Console; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 + + System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); + String input1 = Console.readLine().strip(); // 앞뒤 공백 제거 + if (input1 == null || input1.isBlank()) { + throw new IllegalArgumentException(); + } + + System.out.println("시도할 회수는 몇회인가요?"); + String input2 = Console.readLine().strip(); + int count; + try { + count = Integer.parseUnsignedInt(input2); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(); + } + + + String[] carNames = input1.split(","); + List list1 = Stream.of(carNames) + .filter(s -> !s.isBlank()) + .filter(s -> s.length() > 5) + .toList(); + if (!list1.isEmpty()) { + throw new IllegalArgumentException(); + } + + List list2 = Stream.of(carNames).filter(s -> !s.isBlank()).toList(); + List cars = new ArrayList<>(); + for (String s : list2) { + cars.add(new Car(s)); + } + + System.out.println("실행 결과"); + for (int i = 0; i < count; i++) { + for (Car car : cars) { + if (car.pickNumber() >= 4) { + car.go(); + } + } + + for (Car car : cars) { + System.out.println(car.getName() + " : " + car.getRace()); + } + + System.out.println(); + } + + Optional maxCars = cars.stream().max(Comparator.comparing(Car::getRaceCount)); + if (maxCars.isEmpty()) { + throw new IllegalArgumentException(); // 자동차 없음 + } + int max = maxCars.get().getRaceCount(); + + List winners = cars.stream(). + filter(car -> car.getRaceCount() == max) + .toList(); + + System.out.print("최종 우승자 : "); + for (int i = 0; i < winners.size(); i++) { + System.out.print(winners.get(i).getName()); + if (i != winners.size() - 1) { + System.out.print(", "); + } + } } } diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java index 246c8e5abb..2c6ba1117f 100644 --- a/src/main/java/racingcar/Car.java +++ b/src/main/java/racingcar/Car.java @@ -1,4 +1,35 @@ package racingcar; +import camp.nextstep.edu.missionutils.Randoms; + public class Car { + private final String name; + private String race; + private int raceCount; + + public Car(String name) { + this.name = name; + race = ""; + } + + public String getName() { + return name; + } + + public String getRace() { + return race; + } + + public int getRaceCount() { + return raceCount; + } + + public int pickNumber() { + return Randoms.pickNumberInRange(0, 9); + } + + public void go() { + race += "-"; + raceCount++; + } } diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 1d35fc33fe..58f6b9ef59 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -24,13 +24,98 @@ class ApplicationTest extends NsTest { } @Test - void 예외_테스트() { + void 공동우승_테스트() { + assertRandomNumberInRangeTest( + () -> { + run("pobi,경현,woni", "3"); + // 두 명의 진행 상태 확인 + // "최종 우승자 : pobi, woni" 혹은 "최종 우승자 : woni, pobi" 둘 다 허용 + assertThat(output()).contains("pobi : ---", "경현 : ---", "woni : ---", + "최종 우승자 : pobi, 경현, woni"); + }, + MOVING_FORWARD + ); + } + + @Test + void 쉼표_2개_이상_연속_입력_테스트() { + assertRandomNumberInRangeTest( + () -> { + run("pobi,,woni", "2"); + // 두 명의 진행 상태 확인 + // "최종 우승자 : pobi" + assertThat(output()).contains("pobi : --", "woni : -", "최종 우승자 : pobi"); + }, + MOVING_FORWARD, STOP, MOVING_FORWARD + ); + } + + @Test + void 입력값_양쪽_공백_테스트() { + assertRandomNumberInRangeTest( + () -> { + run(" pobi,woni ", "1"); + // 두 명의 진행 상태 확인 + // "최종 우승자 : pobi" + assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi"); + }, + MOVING_FORWARD, STOP + ); + } + + @Test + void 자동차이름_숫자_기호_테스트() { + assertRandomNumberInRangeTest( + () -> { + run("pobi1,$woni", "1"); + // 두 명의 진행 상태 확인 + // "최종 우승자 : pobi" + assertThat(output()).contains("pobi1 : -", "$woni : ", "최종 우승자 : pobi1"); + }, + MOVING_FORWARD, STOP + ); + } + + @Test + void 입력값_공백_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException(" ", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 입력값_null_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException(" ", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 자동차이름_5자_초과_테스트() { assertSimpleTest(() -> assertThatThrownBy(() -> runException("pobi,javaji", "1")) .isInstanceOf(IllegalArgumentException.class) ); } + @Test + void 자동차이름_공백포함_5자_초과_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,kh cho", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 자동차개수_0개_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException(" , , , ", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + @Override public void runMain() { Application.main(new String[]{}); From daeed7f9666f26a339356f95b8f7c08629f9bf45 Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Wed, 24 Sep 2025 19:57:28 +0900 Subject: [PATCH 03/14] =?UTF-8?q?Docs:=20README.md=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EC=99=84=EB=A3=8C=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6c1bbb9305..d0bd778dd4 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,16 @@ ### 1. 자동차의 이름을 입력받고 검증한다. > 검증 조건이 추가되거나 변경될 가능성이 있다. -- [ ] 사용자에게 자동차 이름 입력 메시지 출력 -- [ ] 사용자가 입력한 값 받음 - - [ ] 입력값 받는 동시에 양쪽에 있는 공백 제거 +- [x] 사용자에게 자동차 이름 입력 메시지 출력 +- [x] 사용자가 입력한 값 받음 + - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 - [ ] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` ### 2. 시도할 횟수를 입력받고 검증한다. > 검증 조건이 추가되거나 변경될 가능성이 있다. -- [ ] 사용자에게 시도할 횟수 입력 메시지 출력 -- [ ] 사용자가 입력한 값 받음 - - [ ] 입력값 받는 동시에 양쪽에 있는 공백 제거 +- [x] 사용자에게 시도할 횟수 입력 메시지 출력 +- [x] 사용자가 입력한 값 받음 + - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 - [ ] 입력 받은 양수값을 양수로 변경 - [ ] 양수가 아닌 값 입력 시 예외 발생 From 4ccc7e721efd06dd27562cb7960674cc726558ac Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Wed, 24 Sep 2025 20:01:03 +0900 Subject: [PATCH 04/14] =?UTF-8?q?Feat(Input):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/view/InputView.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/main/java/racingcar/view/InputView.java diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java new file mode 100644 index 0000000000..e1ac8d02b8 --- /dev/null +++ b/src/main/java/racingcar/view/InputView.java @@ -0,0 +1,18 @@ +package racingcar.view; + +import camp.nextstep.edu.missionutils.Console; + +public class InputView { + private static final String CAR_NAME_INPUT_MESSAGE = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; + private static final String NUMBER_INPUT_MESSAGE = "시도할 회수는 몇회인가요?"; + + public String readCarName() { + System.out.println(CAR_NAME_INPUT_MESSAGE); + return Console.readLine().strip(); + } + + public String readNumber() { + System.out.println(NUMBER_INPUT_MESSAGE); + return Console.readLine().strip(); + } +} From 6523c591e5a0d00a3aa7d230e5642882777a8cf9 Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Thu, 25 Sep 2025 10:10:34 +0900 Subject: [PATCH 05/14] =?UTF-8?q?Feat:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=B3=84=EB=A1=9C=20=EB=82=98=EB=88=A0=EC=84=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20README.md=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 54 ++++----- src/main/java/racingcar/Application.java | 73 +----------- .../java/racingcar/ApplicationRunner.java | 15 +++ src/main/java/racingcar/Car.java | 35 ------ src/main/java/racingcar/Cars.java | 4 - .../controller/RacingController.java | 28 +++++ src/main/java/racingcar/domain/Cars.java | 105 ++++++++++++++++++ .../java/racingcar/service/RacingService.java | 40 +++++++ src/main/java/racingcar/util/ParseInt.java | 14 +++ src/main/java/racingcar/util/Validator.java | 29 +++++ src/main/java/racingcar/view/InputView.java | 4 +- src/main/java/racingcar/view/OutputView.java | 19 ++++ src/test/java/racingcar/ApplicationTest.java | 17 +++ 13 files changed, 299 insertions(+), 138 deletions(-) create mode 100644 src/main/java/racingcar/ApplicationRunner.java delete mode 100644 src/main/java/racingcar/Car.java delete mode 100644 src/main/java/racingcar/Cars.java create mode 100644 src/main/java/racingcar/controller/RacingController.java create mode 100644 src/main/java/racingcar/domain/Cars.java create mode 100644 src/main/java/racingcar/service/RacingService.java create mode 100644 src/main/java/racingcar/util/ParseInt.java create mode 100644 src/main/java/racingcar/util/Validator.java create mode 100644 src/main/java/racingcar/view/OutputView.java diff --git a/README.md b/README.md index d0bd778dd4..0733d5bbcc 100644 --- a/README.md +++ b/README.md @@ -9,54 +9,58 @@ - [x] 사용자에게 자동차 이름 입력 메시지 출력 - [x] 사용자가 입력한 값 받음 - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 -- [ ] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` +- [x] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` ### 2. 시도할 횟수를 입력받고 검증한다. > 검증 조건이 추가되거나 변경될 가능성이 있다. - [x] 사용자에게 시도할 횟수 입력 메시지 출력 - [x] 사용자가 입력한 값 받음 - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 -- [ ] 입력 받은 양수값을 양수로 변경 - - [ ] 양수가 아닌 값 입력 시 예외 발생 +- [x] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` +- [x] 입력 받은 양수값을 양수로 변경 + - [x] 양수가 아닌 값 입력 시 예외 발생 +- [x] 시도할 횟수가 정수 최댓값 초과하면 예외 발생 `(판단)` ### 3. 자동차의 이름을 분리하고 검증한다. > 검증 조건이 추가되거나 변경될 가능성이 있다. -- [ ] 입력된 자동차 이름 문자열을 쉼표(,)를 기준으로 구분 -- [ ] 각 자동차 이름의 양쪽 공백 제거 후 글자 수가 5를 초과하는 경우가 있으면 예외 발생 -- [ ] 각 자동차 이름의 양쪽 공백를 제거한 각 자동차의 이름을 리스트로 저장 - - [ ] 이름이 공백만으로 이루어져 있으면 자동차로 여기지 않음 `(판단)` +- [x] 입력된 자동차 이름 문자열을 쉼표(,)를 기준으로 구분 +- [x] 각 자동차 이름의 양쪽 공백 제거 후 글자 수가 5를 초과하는 경우가 있으면 예외 발생 +- [x] 각 자동차 이름의 양쪽 공백를 제거한 각 자동차의 이름을 리스트로 저장 + - [x] 이름이 공백만으로 이루어져 있으면 자동차로 여기지 않음 `(판단)` ### 4. 자동차를 만들어 이름을 부여한다. -- [ ] 자동차 객체를 저장하는 리스트 생성 - - [ ] 자동차 객체를 생성하면서 각 이름을 저장 +- [x] 자동차 객체를 저장하는 리스트 생성 + - [x] 자동차 객체를 생성하면서 각 이름을 저장 ### 5. 자동차 경주를 진행하면서 과정을 출력한다. -- [ ] 자동차 경주 과정을 출력 - - [ ] 사용자가 입력한 횟수 만큼 각 자동차의 경주 과정을 출력 - - [ ] 각 자동차마다 랜덤 함수를 호출해 값이 4이상인 경우 "-" 를 1개 추가 +- [x] 자동차 경주 과정을 출력 + - [x] 사용자가 입력한 횟수 만큼 각 자동차의 경주 과정을 출력 + - [x] 각 자동차마다 랜덤 함수를 호출해 값이 4이상인 경우 "-" 를 1개 추가 ### 6. 우승자를 선정해 출력한다. -- [ ] 자동차 경주 결과에 따른 우승자의 "-" 개수 구하기(즉, "-" 개수의 최댓값 구하기) - - [ ] 최댓값이 존재하지 않으면(자동차가 0개인 경우) 예외 발생 -- [ ] 최댓값을 가진 자동차 객체로만 리스트 생성 -- [ ] 최종 우승자 출력 - - [ ] 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분하여 출력 +- [x] 자동차 경주 결과에 따른 우승자의 "-" 개수 구하기(즉, "-" 개수의 최댓값 구하기) + - [x] 최댓값이 존재하지 않으면(자동차가 0개인 경우) 예외 발생 +- [x] 최댓값을 가진 자동차 객체로만 리스트 생성 +- [x] 최종 우승자 출력 + - [x] 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분하여 출력 ## 테스트 코드 ### 정상 입력 케이스 > 검증 조건이 추가되거나 변경될 가능성이 있다. -- [ ] 쉼표 2개 이상 연속으로 들어와도 정상 수행 `(판단)` -- [ ] 자동차이름에 숫자, 기호 허용 `(판단)` -- [ ] 자동차이름 중간 공백도 글자로 인식 -> 글자 수에 반영 `(판단)` +- [x] 쉼표 2개 이상 연속으로 들어와도 정상 수행 `(판단)` +- [x] 자동차이름에 숫자, 기호 허용 `(판단)` +- [x] 자동차이름 중간 공백도 글자로 인식 -> 글자 수에 반영 `(판단)` ### 예외 발생 입력 케이스 > 검증 조건이 추가되거나 변경될 가능성이 있다. -- [ ] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` -- [ ] 자동차 이름이 5자 초과하는 경우 `(판단)` -- [ ] 이동 횟수 입력이 양수가 아닌 값이 들어오는 경우 `(판단)` -- [ ] 모든 자동차의 이름이 공백만 있는 경우(즉, 자동차의 개수가 0인 경우) `(판단)` +- [x] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` +- [x] 자동차 이름이 5자 초과하는 경우 `(판단)` +- [x] 이동 횟수 입력이 양수가 아닌 값이 들어오는 경우 `(판단)` +- [x] 모든 자동차의 이름이 공백만 있는 경우(즉, 자동차의 개수가 0인 경우) `(판단)` +- [x] 시도할 횟수가 정수 최댓값 초과하면 예외 발생 `(판단)` ## 리펙토링 -> 코드가 수정되는 경우 변경 사항을 작성한다. \ No newline at end of file +> 코드가 수정되는 경우 변경 사항을 작성한다. +- [ ] 예외 메시지 추가 및 관리 \ No newline at end of file diff --git a/src/main/java/racingcar/Application.java b/src/main/java/racingcar/Application.java index 13f1a8f0b9..4d4333dc4d 100644 --- a/src/main/java/racingcar/Application.java +++ b/src/main/java/racingcar/Application.java @@ -1,79 +1,8 @@ package racingcar; -import camp.nextstep.edu.missionutils.Console; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; - public class Application { public static void main(String[] args) { // TODO: 프로그램 구현 - - System.out.println("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"); - String input1 = Console.readLine().strip(); // 앞뒤 공백 제거 - if (input1 == null || input1.isBlank()) { - throw new IllegalArgumentException(); - } - - System.out.println("시도할 회수는 몇회인가요?"); - String input2 = Console.readLine().strip(); - int count; - try { - count = Integer.parseUnsignedInt(input2); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(); - } - - - String[] carNames = input1.split(","); - List list1 = Stream.of(carNames) - .filter(s -> !s.isBlank()) - .filter(s -> s.length() > 5) - .toList(); - if (!list1.isEmpty()) { - throw new IllegalArgumentException(); - } - - List list2 = Stream.of(carNames).filter(s -> !s.isBlank()).toList(); - List cars = new ArrayList<>(); - for (String s : list2) { - cars.add(new Car(s)); - } - - System.out.println("실행 결과"); - for (int i = 0; i < count; i++) { - for (Car car : cars) { - if (car.pickNumber() >= 4) { - car.go(); - } - } - - for (Car car : cars) { - System.out.println(car.getName() + " : " + car.getRace()); - } - - System.out.println(); - } - - Optional maxCars = cars.stream().max(Comparator.comparing(Car::getRaceCount)); - if (maxCars.isEmpty()) { - throw new IllegalArgumentException(); // 자동차 없음 - } - int max = maxCars.get().getRaceCount(); - - List winners = cars.stream(). - filter(car -> car.getRaceCount() == max) - .toList(); - - System.out.print("최종 우승자 : "); - for (int i = 0; i < winners.size(); i++) { - System.out.print(winners.get(i).getName()); - if (i != winners.size() - 1) { - System.out.print(", "); - } - } + ApplicationRunner.run(); } } diff --git a/src/main/java/racingcar/ApplicationRunner.java b/src/main/java/racingcar/ApplicationRunner.java new file mode 100644 index 0000000000..8bae670907 --- /dev/null +++ b/src/main/java/racingcar/ApplicationRunner.java @@ -0,0 +1,15 @@ +package racingcar; + +import racingcar.controller.RacingController; + +public class ApplicationRunner { + + public static void run() { + RacingController controller = new RacingController( + new racingcar.service.RacingService(), + new racingcar.view.InputView(), + new racingcar.view.OutputView() + ); + controller.run(); // IllegalArgumentException 발생 시 main 에서는 잡지 않음 -> 프로그램 종료 + } +} diff --git a/src/main/java/racingcar/Car.java b/src/main/java/racingcar/Car.java deleted file mode 100644 index 2c6ba1117f..0000000000 --- a/src/main/java/racingcar/Car.java +++ /dev/null @@ -1,35 +0,0 @@ -package racingcar; - -import camp.nextstep.edu.missionutils.Randoms; - -public class Car { - private final String name; - private String race; - private int raceCount; - - public Car(String name) { - this.name = name; - race = ""; - } - - public String getName() { - return name; - } - - public String getRace() { - return race; - } - - public int getRaceCount() { - return raceCount; - } - - public int pickNumber() { - return Randoms.pickNumberInRange(0, 9); - } - - public void go() { - race += "-"; - raceCount++; - } -} diff --git a/src/main/java/racingcar/Cars.java b/src/main/java/racingcar/Cars.java deleted file mode 100644 index f0149b565d..0000000000 --- a/src/main/java/racingcar/Cars.java +++ /dev/null @@ -1,4 +0,0 @@ -package racingcar; - -public class Cars { -} diff --git a/src/main/java/racingcar/controller/RacingController.java b/src/main/java/racingcar/controller/RacingController.java new file mode 100644 index 0000000000..597205f4bf --- /dev/null +++ b/src/main/java/racingcar/controller/RacingController.java @@ -0,0 +1,28 @@ +package racingcar.controller; + +import racingcar.domain.Cars; +import racingcar.service.RacingService; +import racingcar.view.InputView; +import racingcar.view.OutputView; + +public class RacingController { + private final RacingService service; + private final InputView inputView; + private final OutputView outputView; + + public RacingController(RacingService service, InputView inputView, OutputView outputView) { + this.service = service; + this.inputView = inputView; + this.outputView = outputView; + } + + public void run() { + String inputCarNames = inputView.readCarNames(); + String inputRaceCount = inputView.readCount(); + int raceCount = service.getRaceCount(inputRaceCount); + Cars cars = service.startRace(inputCarNames, raceCount); + String winners = service.decideWinners(cars); + outputView.printRace(cars, raceCount); + outputView.printWinners(winners); + } +} diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java new file mode 100644 index 0000000000..cac94f5257 --- /dev/null +++ b/src/main/java/racingcar/domain/Cars.java @@ -0,0 +1,105 @@ +package racingcar.domain; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public class Cars { + private final List cars; + private final List races; + + private Cars(List cars) { + this.cars = cars; + races = new ArrayList<>(); + } + + public static Cars of(String[] inputCarNames) { + List cars = Stream.of(inputCarNames) + .filter(s -> !s.isBlank()) + .map(Car::new) + .toList(); + return new Cars(cars); + } + + public void runRace(int raceCount) { + + for (int i = 0; i < raceCount; i++) { + cars.stream() + .filter(car -> car.pickNumber() >= 4) + .forEach(Car::go); + + StringBuilder race = new StringBuilder(); + cars.stream() + .map(Car::getRaceResult) + .forEach(race::append); + + System.out.println(race.toString()); + races.add(race.toString()); + } + } + + public String getRace(int i) { + return races.get(i); + } + + public int getMaxSteps() { + Optional maxCars = cars + .stream() + .max(Comparator.comparing(car -> car.raceCount)); + if (maxCars.isEmpty()) { + throw new IllegalArgumentException(); // 자동차 없음 + } + return maxCars.get().raceCount; + } + + public String getWinners(int maxSteps) { + List winners = getWinnersList(maxSteps); + + StringBuilder winnersResult = new StringBuilder(); + for (int i = 0; i < winners.size(); i++) { + String race = winners.get(i).name; + winnersResult.append(race); + if (i != winners.size() - 1) { + winnersResult.append(", "); + } + } + + return winnersResult.toString(); + } + + private List getWinnersList(int maxSteps) { + return cars.stream() + .filter(car -> car.raceCount == maxSteps) + .toList(); + } + + + public static class Car { + private final String name; + private String race; + private int raceCount; + private static final String STEP = "-"; + + public Car(String name) { + this.name = name; + race = ""; + } + + public String getRaceResult() { + return name + " : " + race + "\n"; + } + + public int pickNumber() { + return Randoms.pickNumberInRange(0, 9); + } + + public void go() { + race += STEP; + raceCount++; + } + } +} diff --git a/src/main/java/racingcar/service/RacingService.java b/src/main/java/racingcar/service/RacingService.java new file mode 100644 index 0000000000..2544cdcb33 --- /dev/null +++ b/src/main/java/racingcar/service/RacingService.java @@ -0,0 +1,40 @@ +package racingcar.service; + +import racingcar.domain.Cars.Car; +import racingcar.domain.Cars; +import racingcar.util.Validator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +import static racingcar.util.ParseInt.parseInt; +import static racingcar.util.Validator.*; + +public class RacingService { + + public Cars startRace(String inputCarNames, int raceCount) { + validateNull(inputCarNames); + + String[] splitCarNames = inputCarNames.split(","); + validateLength(splitCarNames); + Cars cars = Cars.of(splitCarNames); + + cars.runRace(raceCount); + + return cars; + } + + public int getRaceCount(String inputRaceCount) { + validateNull(inputRaceCount); + return parseInt(inputRaceCount); + } + + public String decideWinners(Cars cars) { + int maxSteps = cars.getMaxSteps(); + + return cars.getWinners(maxSteps); + } +} diff --git a/src/main/java/racingcar/util/ParseInt.java b/src/main/java/racingcar/util/ParseInt.java new file mode 100644 index 0000000000..6b5dbcb452 --- /dev/null +++ b/src/main/java/racingcar/util/ParseInt.java @@ -0,0 +1,14 @@ +package racingcar.util; + +public class ParseInt { + + public static int parseInt(String str) { + int integer; + try { + integer = Integer.parseUnsignedInt(str); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(); + } + return integer; + } +} diff --git a/src/main/java/racingcar/util/Validator.java b/src/main/java/racingcar/util/Validator.java new file mode 100644 index 0000000000..7c8be902ba --- /dev/null +++ b/src/main/java/racingcar/util/Validator.java @@ -0,0 +1,29 @@ +package racingcar.util; + +import java.util.List; +import java.util.stream.Stream; + +public class Validator { + + public static void validateNull(String input) { + if (input == null || input.isBlank()) { + throw new IllegalArgumentException(); + } + } + + public static void validateLength(String[] splitCarNames) { + List carNames = Stream.of(splitCarNames) + .filter(s -> !s.isBlank()) + .filter(s -> s.length() > 5) + .toList(); + if (!carNames.isEmpty()) { + throw new IllegalArgumentException(); + } + } + + public static void validateOverflow(String inputRaceCount) { + if (inputRaceCount.compareTo(String.valueOf(Integer.MAX_VALUE)) > 0) { + throw new IllegalArgumentException(); + } + } +} diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java index e1ac8d02b8..87bf9d3be6 100644 --- a/src/main/java/racingcar/view/InputView.java +++ b/src/main/java/racingcar/view/InputView.java @@ -6,12 +6,12 @@ public class InputView { private static final String CAR_NAME_INPUT_MESSAGE = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; private static final String NUMBER_INPUT_MESSAGE = "시도할 회수는 몇회인가요?"; - public String readCarName() { + public String readCarNames() { System.out.println(CAR_NAME_INPUT_MESSAGE); return Console.readLine().strip(); } - public String readNumber() { + public String readCount() { System.out.println(NUMBER_INPUT_MESSAGE); return Console.readLine().strip(); } diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java new file mode 100644 index 0000000000..af61563ddf --- /dev/null +++ b/src/main/java/racingcar/view/OutputView.java @@ -0,0 +1,19 @@ +package racingcar.view; + +import racingcar.domain.Cars; + +public class OutputView { + private static final String WINNERS_MESSAGE = "최종 우승자 : "; + private static final String RESULT_MESSAGE = "실행 결과"; + + public void printRace(Cars cars, int raceCount) { + System.out.println(RESULT_MESSAGE); + for (int i = 0; i < raceCount; i++) { + System.out.println(cars.getRace(i)); + } + } + + public void printWinners(String winners) { + System.out.println(WINNERS_MESSAGE + winners); + } +} diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 58f6b9ef59..2c400da29a 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -11,6 +11,7 @@ class ApplicationTest extends NsTest { private static final int MOVING_FORWARD = 4; private static final int STOP = 3; + private static final String OVERFLOW_INTEGER = String.valueOf(Integer.MAX_VALUE) + "1"; @Test void 기능_테스트() { @@ -116,6 +117,22 @@ class ApplicationTest extends NsTest { ); } + @Test + void 시도할_횟수_입력_오류_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni", "-1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 시도할_횟수_오버플로우_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni", OVERFLOW_INTEGER)) + .isInstanceOf(IllegalArgumentException.class) + ); + } + @Override public void runMain() { Application.main(new String[]{}); From 444214927b8e9c8f255cf1a34dde78c0cf813756 Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Thu, 25 Sep 2025 18:36:39 +0900 Subject: [PATCH 06/14] =?UTF-8?q?Feat(ErrorMessage):=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=ED=86=B5=ED=95=A9=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20enum=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +-- .../java/racingcar/ApplicationRunner.java | 11 ++-- .../java/racingcar/constant/ErrorMessage.java | 19 ++++++ .../controller/RacingController.java | 7 ++- src/main/java/racingcar/domain/Cars.java | 61 +++++++++---------- .../java/racingcar/service/RacingService.java | 38 ++++++------ src/main/java/racingcar/util/ParseInt.java | 4 +- src/main/java/racingcar/util/Split.java | 10 +++ src/main/java/racingcar/util/Validator.java | 16 ++--- src/main/java/racingcar/view/InputView.java | 2 +- src/test/java/racingcar/ApplicationTest.java | 28 ++------- 11 files changed, 114 insertions(+), 92 deletions(-) create mode 100644 src/main/java/racingcar/constant/ErrorMessage.java create mode 100644 src/main/java/racingcar/util/Split.java diff --git a/README.md b/README.md index 0733d5bbcc..b1ee53e9e7 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ - [x] 사용자에게 자동차 이름 입력 메시지 출력 - [x] 사용자가 입력한 값 받음 - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 -- [x] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` +- [x] 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` ### 2. 시도할 횟수를 입력받고 검증한다. > 검증 조건이 추가되거나 변경될 가능성이 있다. - [x] 사용자에게 시도할 횟수 입력 메시지 출력 - [x] 사용자가 입력한 값 받음 - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 -- [x] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` +- [x] 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` - [x] 입력 받은 양수값을 양수로 변경 - [x] 양수가 아닌 값 입력 시 예외 발생 - [x] 시도할 횟수가 정수 최댓값 초과하면 예외 발생 `(판단)` @@ -55,7 +55,7 @@ ### 예외 발생 입력 케이스 > 검증 조건이 추가되거나 변경될 가능성이 있다. -- [x] null 또는 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` +- [x] 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` - [x] 자동차 이름이 5자 초과하는 경우 `(판단)` - [x] 이동 횟수 입력이 양수가 아닌 값이 들어오는 경우 `(판단)` - [x] 모든 자동차의 이름이 공백만 있는 경우(즉, 자동차의 개수가 0인 경우) `(판단)` @@ -63,4 +63,6 @@ ## 리펙토링 > 코드가 수정되는 경우 변경 사항을 작성한다. -- [ ] 예외 메시지 추가 및 관리 \ No newline at end of file +- [x] 예외 메시지 추가 및 관리 +- [ ] 변수명 재확인 +- [x] 자동차 이름 입력값에 문제가 있을 때 시도할 횟수 입력 받지 않고 바로 예외 발생 가능하도록 변경 \ No newline at end of file diff --git a/src/main/java/racingcar/ApplicationRunner.java b/src/main/java/racingcar/ApplicationRunner.java index 8bae670907..343f785383 100644 --- a/src/main/java/racingcar/ApplicationRunner.java +++ b/src/main/java/racingcar/ApplicationRunner.java @@ -1,15 +1,18 @@ package racingcar; import racingcar.controller.RacingController; +import racingcar.service.RacingService; +import racingcar.view.InputView; +import racingcar.view.OutputView; public class ApplicationRunner { public static void run() { RacingController controller = new RacingController( - new racingcar.service.RacingService(), - new racingcar.view.InputView(), - new racingcar.view.OutputView() + new RacingService(), + new InputView(), + new OutputView() ); - controller.run(); // IllegalArgumentException 발생 시 main 에서는 잡지 않음 -> 프로그램 종료 + controller.run(); } } diff --git a/src/main/java/racingcar/constant/ErrorMessage.java b/src/main/java/racingcar/constant/ErrorMessage.java new file mode 100644 index 0000000000..fdf7d37aa6 --- /dev/null +++ b/src/main/java/racingcar/constant/ErrorMessage.java @@ -0,0 +1,19 @@ +package racingcar.constant; + +public enum ErrorMessage { + NO_CAR_ERROR("No cars exist."), + NOT_POSITIVE_NUMBER_ERROR("Enter a positive number."), + ONLY_WHITESPACE_ERROR("Input contains only whitespace."), + MAXIMUM_LENGTH_ERROR("Maximum length of the car name is 5 characters."), + INTEGER_OVERFLOW_ERROR("The input count causes an integer overflow."); + + private final String errorMessage; + + ErrorMessage(String errorMessage) { + this.errorMessage = "[Invalid input] " + errorMessage; + } + + public String getErrorMessage() { + return errorMessage; + } +} diff --git a/src/main/java/racingcar/controller/RacingController.java b/src/main/java/racingcar/controller/RacingController.java index 597205f4bf..832aeb0d8b 100644 --- a/src/main/java/racingcar/controller/RacingController.java +++ b/src/main/java/racingcar/controller/RacingController.java @@ -18,10 +18,13 @@ public RacingController(RacingService service, InputView inputView, OutputView o public void run() { String inputCarNames = inputView.readCarNames(); - String inputRaceCount = inputView.readCount(); + String inputRaceCount = inputView.readRaceCount(); + + Cars cars = service.getCars(inputCarNames); int raceCount = service.getRaceCount(inputRaceCount); - Cars cars = service.startRace(inputCarNames, raceCount); + service.runRace(cars, raceCount); String winners = service.decideWinners(cars); + outputView.printRace(cars, raceCount); outputView.printWinners(winners); } diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java index cac94f5257..277abb3fb8 100644 --- a/src/main/java/racingcar/domain/Cars.java +++ b/src/main/java/racingcar/domain/Cars.java @@ -8,13 +8,15 @@ import java.util.Optional; import java.util.stream.Stream; +import static racingcar.constant.ErrorMessage.*; + public class Cars { private final List cars; private final List races; - private Cars(List cars) { + public Cars(List cars, List races) { this.cars = cars; - races = new ArrayList<>(); + this.races = races; } public static Cars of(String[] inputCarNames) { @@ -22,23 +24,21 @@ public static Cars of(String[] inputCarNames) { .filter(s -> !s.isBlank()) .map(Car::new) .toList(); - return new Cars(cars); + return new Cars(cars, new ArrayList<>()); } public void runRace(int raceCount) { - for (int i = 0; i < raceCount; i++) { cars.stream() .filter(car -> car.pickNumber() >= 4) .forEach(Car::go); - StringBuilder race = new StringBuilder(); + StringBuilder steps = new StringBuilder(); cars.stream() - .map(Car::getRaceResult) - .forEach(race::append); + .map(Car::getStepsResult) + .forEach(steps::append); - System.out.println(race.toString()); - races.add(race.toString()); + races.add(steps.toString()); } } @@ -46,51 +46,50 @@ public String getRace(int i) { return races.get(i); } - public int getMaxSteps() { - Optional maxCars = cars + public int getWinnerSteps() { + Optional winnerCar = cars .stream() .max(Comparator.comparing(car -> car.raceCount)); - if (maxCars.isEmpty()) { - throw new IllegalArgumentException(); // 자동차 없음 + if (winnerCar.isEmpty()) { + throw new IllegalArgumentException(NO_CAR_ERROR.getErrorMessage()); // 자동차 없음 } - return maxCars.get().raceCount; + return winnerCar.get().raceCount; } - public String getWinners(int maxSteps) { - List winners = getWinnersList(maxSteps); + public String getWinners(int winnerSteps) { + List winnersList = getWinnersList(winnerSteps); - StringBuilder winnersResult = new StringBuilder(); - for (int i = 0; i < winners.size(); i++) { - String race = winners.get(i).name; - winnersResult.append(race); - if (i != winners.size() - 1) { - winnersResult.append(", "); + StringBuilder winners = new StringBuilder(); + for (int i = 0; i < winnersList.size(); i++) { + String race = winnersList.get(i).name; + winners.append(race); + if (i != winnersList.size() - 1) { + winners.append(", "); } } - return winnersResult.toString(); + return winners.toString(); } - private List getWinnersList(int maxSteps) { + private List getWinnersList(int winnerSteps) { return cars.stream() - .filter(car -> car.raceCount == maxSteps) + .filter(car -> car.raceCount == winnerSteps) .toList(); } - public static class Car { private final String name; - private String race; + private String steps; private int raceCount; private static final String STEP = "-"; public Car(String name) { this.name = name; - race = ""; + steps = ""; } - public String getRaceResult() { - return name + " : " + race + "\n"; + public String getStepsResult() { + return name + " : " + steps + "\n"; } public int pickNumber() { @@ -98,7 +97,7 @@ public int pickNumber() { } public void go() { - race += STEP; + steps += STEP; raceCount++; } } diff --git a/src/main/java/racingcar/service/RacingService.java b/src/main/java/racingcar/service/RacingService.java index 2544cdcb33..d7dcc2a171 100644 --- a/src/main/java/racingcar/service/RacingService.java +++ b/src/main/java/racingcar/service/RacingService.java @@ -1,40 +1,38 @@ package racingcar.service; -import racingcar.domain.Cars.Car; import racingcar.domain.Cars; -import racingcar.util.Validator; - -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import java.util.stream.Stream; import static racingcar.util.ParseInt.parseInt; +import static racingcar.util.Split.split; import static racingcar.util.Validator.*; public class RacingService { + public Cars getCars(String inputCarNames) { + validateBlank(inputCarNames); - public Cars startRace(String inputCarNames, int raceCount) { - validateNull(inputCarNames); - - String[] splitCarNames = inputCarNames.split(","); + String[] splitCarNames = split(inputCarNames); validateLength(splitCarNames); - Cars cars = Cars.of(splitCarNames); - cars.runRace(raceCount); - - return cars; + return Cars.of(splitCarNames); } public int getRaceCount(String inputRaceCount) { - validateNull(inputRaceCount); - return parseInt(inputRaceCount); + validateBlank(inputRaceCount); + + int raceCount = parseInt(inputRaceCount); + + validateOverflow(inputRaceCount, raceCount); + + return raceCount; + } + + public void runRace(Cars cars, int raceCount) { + cars.runRace(raceCount); } public String decideWinners(Cars cars) { - int maxSteps = cars.getMaxSteps(); + int winnerSteps = cars.getWinnerSteps(); - return cars.getWinners(maxSteps); + return cars.getWinners(winnerSteps); } } diff --git a/src/main/java/racingcar/util/ParseInt.java b/src/main/java/racingcar/util/ParseInt.java index 6b5dbcb452..b2ccc07f0f 100644 --- a/src/main/java/racingcar/util/ParseInt.java +++ b/src/main/java/racingcar/util/ParseInt.java @@ -1,5 +1,7 @@ package racingcar.util; +import static racingcar.constant.ErrorMessage.*; + public class ParseInt { public static int parseInt(String str) { @@ -7,7 +9,7 @@ public static int parseInt(String str) { try { integer = Integer.parseUnsignedInt(str); } catch (NumberFormatException e) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException(NOT_POSITIVE_NUMBER_ERROR.getErrorMessage()); } return integer; } diff --git a/src/main/java/racingcar/util/Split.java b/src/main/java/racingcar/util/Split.java new file mode 100644 index 0000000000..3000776844 --- /dev/null +++ b/src/main/java/racingcar/util/Split.java @@ -0,0 +1,10 @@ +package racingcar.util; + +public class Split { + + private static final String DELIMITER = ","; + + public static String[] split(String str) { + return str.split(DELIMITER); + } +} diff --git a/src/main/java/racingcar/util/Validator.java b/src/main/java/racingcar/util/Validator.java index 7c8be902ba..2dda5bf466 100644 --- a/src/main/java/racingcar/util/Validator.java +++ b/src/main/java/racingcar/util/Validator.java @@ -3,11 +3,13 @@ import java.util.List; import java.util.stream.Stream; +import static racingcar.constant.ErrorMessage.*; + public class Validator { - public static void validateNull(String input) { - if (input == null || input.isBlank()) { - throw new IllegalArgumentException(); + public static void validateBlank(String input) { + if (input.isBlank()) { + throw new IllegalArgumentException(ONLY_WHITESPACE_ERROR.getErrorMessage()); } } @@ -17,13 +19,13 @@ public static void validateLength(String[] splitCarNames) { .filter(s -> s.length() > 5) .toList(); if (!carNames.isEmpty()) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException(MAXIMUM_LENGTH_ERROR.getErrorMessage()); } } - public static void validateOverflow(String inputRaceCount) { - if (inputRaceCount.compareTo(String.valueOf(Integer.MAX_VALUE)) > 0) { - throw new IllegalArgumentException(); + public static void validateOverflow(String inputRaceCount, int raceCount) { + if (!inputRaceCount.equals(String.valueOf(raceCount))) { + throw new IllegalArgumentException(INTEGER_OVERFLOW_ERROR.getErrorMessage()); } } } diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java index 87bf9d3be6..8671ed90b4 100644 --- a/src/main/java/racingcar/view/InputView.java +++ b/src/main/java/racingcar/view/InputView.java @@ -11,7 +11,7 @@ public String readCarNames() { return Console.readLine().strip(); } - public String readCount() { + public String readRaceCount() { System.out.println(NUMBER_INPUT_MESSAGE); return Console.readLine().strip(); } diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 2c400da29a..74ade52c06 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -11,10 +11,10 @@ class ApplicationTest extends NsTest { private static final int MOVING_FORWARD = 4; private static final int STOP = 3; - private static final String OVERFLOW_INTEGER = String.valueOf(Integer.MAX_VALUE) + "1"; + private static final String OVERFLOW_INTEGER = "2147483648"; @Test - void 기능_테스트() { + void 기본_케이스_테스트() { assertRandomNumberInRangeTest( () -> { run("pobi,woni", "1"); @@ -28,11 +28,9 @@ class ApplicationTest extends NsTest { void 공동우승_테스트() { assertRandomNumberInRangeTest( () -> { - run("pobi,경현,woni", "3"); - // 두 명의 진행 상태 확인 - // "최종 우승자 : pobi, woni" 혹은 "최종 우승자 : woni, pobi" 둘 다 허용 - assertThat(output()).contains("pobi : ---", "경현 : ---", "woni : ---", - "최종 우승자 : pobi, 경현, woni"); + run("pobi,woni", "3"); + assertThat(output()).contains("pobi : ---", "woni : ---", + "최종 우승자 : pobi, woni"); }, MOVING_FORWARD ); @@ -43,8 +41,6 @@ class ApplicationTest extends NsTest { assertRandomNumberInRangeTest( () -> { run("pobi,,woni", "2"); - // 두 명의 진행 상태 확인 - // "최종 우승자 : pobi" assertThat(output()).contains("pobi : --", "woni : -", "최종 우승자 : pobi"); }, MOVING_FORWARD, STOP, MOVING_FORWARD @@ -56,8 +52,6 @@ class ApplicationTest extends NsTest { assertRandomNumberInRangeTest( () -> { run(" pobi,woni ", "1"); - // 두 명의 진행 상태 확인 - // "최종 우승자 : pobi" assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi"); }, MOVING_FORWARD, STOP @@ -69,8 +63,6 @@ class ApplicationTest extends NsTest { assertRandomNumberInRangeTest( () -> { run("pobi1,$woni", "1"); - // 두 명의 진행 상태 확인 - // "최종 우승자 : pobi" assertThat(output()).contains("pobi1 : -", "$woni : ", "최종 우승자 : pobi1"); }, MOVING_FORWARD, STOP @@ -85,14 +77,6 @@ class ApplicationTest extends NsTest { ); } - @Test - void 입력값_null_테스트() { - assertSimpleTest(() -> - assertThatThrownBy(() -> runException(" ", "1")) - .isInstanceOf(IllegalArgumentException.class) - ); - } - @Test void 자동차이름_5자_초과_테스트() { assertSimpleTest(() -> @@ -128,7 +112,7 @@ class ApplicationTest extends NsTest { @Test void 시도할_횟수_오버플로우_테스트() { assertSimpleTest(() -> - assertThatThrownBy(() -> runException("pobi,woni", OVERFLOW_INTEGER)) + assertThatThrownBy(() -> runException("pobi,woni", "2147483648")) .isInstanceOf(IllegalArgumentException.class) ); } From 5b5c6817bef4b688cfedd718b1da80f19e1c5f38 Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Fri, 26 Sep 2025 18:00:32 +0900 Subject: [PATCH 07/14] =?UTF-8?q?Feat(Constant):=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=EA=B4=80=EB=A6=AC=20=ED=81=B4=EB=9E=98=EC=8A=A4=20Constant=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 67 +++++++++++-------- .../java/racingcar/constant/Constant.java | 8 +++ .../controller/RacingController.java | 5 +- src/main/java/racingcar/domain/Cars.java | 11 +-- .../java/racingcar/service/RacingService.java | 6 +- src/main/java/racingcar/util/Split.java | 7 +- src/main/java/racingcar/util/Validator.java | 19 ++++-- src/main/java/racingcar/view/OutputView.java | 2 +- src/test/java/racingcar/ApplicationTest.java | 23 ++++++- 9 files changed, 103 insertions(+), 45 deletions(-) create mode 100644 src/main/java/racingcar/constant/Constant.java diff --git a/README.md b/README.md index b1ee53e9e7..018edde6ac 100644 --- a/README.md +++ b/README.md @@ -8,61 +8,72 @@ > 검증 조건이 추가되거나 변경될 가능성이 있다. - [x] 사용자에게 자동차 이름 입력 메시지 출력 - [x] 사용자가 입력한 값 받음 - - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 + - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 `(판단)` - [x] 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` -### 2. 시도할 횟수를 입력받고 검증한다. +### 2. 입력 받은 자동차의 이름을 분리하고 검증한다. +> 검증 조건이 추가되거나 변경될 가능성이 있다. +- [x] 입력된 자동차 이름 문자열을 쉼표(,)를 기준으로 구분해 리스트로 저장 + - [x] 각 자동차 이름의 양쪽 공백 제거 +- [x] 자동차 글자 수가 5를 초과하는 경우가 있으면 예외 발생 +- [x] 모든 자동차의 이름이 공백만으로 이루어져 있으면 예외 발생 `(판단)` + +### 3. 자동차를 만들어 이름을 부여한다. +- [x] 자동차 객체를 저장하는 리스트 생성 + - [x] 자동차 객체를 생성하면서 각 이름을 저장 + - [x] 공백만으로 이루어진 자동차 이름은 제외 + +### 4. 시도할 횟수를 입력받고 검증한다. > 검증 조건이 추가되거나 변경될 가능성이 있다. - [x] 사용자에게 시도할 횟수 입력 메시지 출력 - [x] 사용자가 입력한 값 받음 - - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 + - [x] 입력값 받는 동시에 양쪽에 있는 공백 제거 `(판단)` - [x] 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` - [x] 입력 받은 양수값을 양수로 변경 - [x] 양수가 아닌 값 입력 시 예외 발생 -- [x] 시도할 횟수가 정수 최댓값 초과하면 예외 발생 `(판단)` - -### 3. 자동차의 이름을 분리하고 검증한다. -> 검증 조건이 추가되거나 변경될 가능성이 있다. -- [x] 입력된 자동차 이름 문자열을 쉼표(,)를 기준으로 구분 -- [x] 각 자동차 이름의 양쪽 공백 제거 후 글자 수가 5를 초과하는 경우가 있으면 예외 발생 -- [x] 각 자동차 이름의 양쪽 공백를 제거한 각 자동차의 이름을 리스트로 저장 - - [x] 이름이 공백만으로 이루어져 있으면 자동차로 여기지 않음 `(판단)` +- [x] 양수로 변경한 값이 정수 최댓값 초과하면 예외 발생 `(판단)` -### 4. 자동차를 만들어 이름을 부여한다. -- [x] 자동차 객체를 저장하는 리스트 생성 - - [x] 자동차 객체를 생성하면서 각 이름을 저장 - -### 5. 자동차 경주를 진행하면서 과정을 출력한다. -- [x] 자동차 경주 과정을 출력 - - [x] 사용자가 입력한 횟수 만큼 각 자동차의 경주 과정을 출력 +### 5. 자동차 경주를 진행하면서 과정을 문자열로 저장한다. +- [x] 사용자가 입력한 횟수 만큼 각 자동차의 경주 과정을 문자열로 저장 - [x] 각 자동차마다 랜덤 함수를 호출해 값이 4이상인 경우 "-" 를 1개 추가 -### 6. 우승자를 선정해 출력한다. +### 6. 우승자를 결정해 우승자 목록을 문자열로 저장한다. - [x] 자동차 경주 결과에 따른 우승자의 "-" 개수 구하기(즉, "-" 개수의 최댓값 구하기) - - [x] 최댓값이 존재하지 않으면(자동차가 0개인 경우) 예외 발생 -- [x] 최댓값을 가진 자동차 객체로만 리스트 생성 -- [x] 최종 우승자 출력 +- [x] 최댓값을 가진 자동차 객체로만 리스트 생성(즉, 우승자 리스트 생성) +- [x] 최종 우승자 목록을 문자열로 저장 - [x] 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분하여 출력 + +### 7. 자동차 경주 진행 과정과 우승자 목록을 출력한다. +- [x] 자동차 경주 진행 과정과 우승자 목록을 출력한다. ## 테스트 코드 ### 정상 입력 케이스 > 검증 조건이 추가되거나 변경될 가능성이 있다. +- [x] 기본 케이스 +- [x] 공동 우승 케이스 - [x] 쉼표 2개 이상 연속으로 들어와도 정상 수행 `(판단)` +- [x] 입력값의 양쪽에 공백이 있는 경우 공백 제거하고 정상 수행 `(판단)` - [x] 자동차이름에 숫자, 기호 허용 `(판단)` -- [x] 자동차이름 중간 공백도 글자로 인식 -> 글자 수에 반영 `(판단)` - +- [x] 각 자동차이름 양쪽에 공백이 있는 경우 공백 제거하고 정상 수행 `(판단)` ### 예외 발생 입력 케이스 > 검증 조건이 추가되거나 변경될 가능성이 있다. - [x] 빈 문자열("") 또는 1개 이상의 공백(예: " ") 입력 시 예외 발생 `(판단)` -- [x] 자동차 이름이 5자 초과하는 경우 `(판단)` -- [x] 이동 횟수 입력이 양수가 아닌 값이 들어오는 경우 `(판단)` + - [x] 자동차이름 입력값이 공백만으로 이루어진 경우 `(판단)` + - [x] 시도할횟수 입력값이 공백만으로 이루어진 경우 `(판단)` +- [x] 자동차 이름이 5자 초과하는 경우 +- [x] 자동차이름 중간 공백도 글자로 인식 `(판단)` + - [x] 중간 공백 포함 5자 초과하는 경우 `(판단)` - [x] 모든 자동차의 이름이 공백만 있는 경우(즉, 자동차의 개수가 0인 경우) `(판단)` +- [x] 이동 횟수 입력이 양수가 아닌 값이 들어오는 경우 `(판단)` - [x] 시도할 횟수가 정수 최댓값 초과하면 예외 발생 `(판단)` ## 리펙토링 > 코드가 수정되는 경우 변경 사항을 작성한다. - [x] 예외 메시지 추가 및 관리 -- [ ] 변수명 재확인 -- [x] 자동차 이름 입력값에 문제가 있을 때 시도할 횟수 입력 받지 않고 바로 예외 발생 가능하도록 변경 \ No newline at end of file +- [x] 변수명 재확인 +- [x] 자동차 이름 입력값에 문제가 있을 때 시도할 횟수 입력 받지 않고 바로 예외 발생 가능하도록 변경 +- [x] 로직에 중요한 역할을 하는 상수는 따로 관리(Constant) +- [x] Service 에서 split 메서드 반환값을 배열에서 리스트로 수정 +- [x] validateNoCars 메서드 추가 \ No newline at end of file diff --git a/src/main/java/racingcar/constant/Constant.java b/src/main/java/racingcar/constant/Constant.java new file mode 100644 index 0000000000..82c6b256bb --- /dev/null +++ b/src/main/java/racingcar/constant/Constant.java @@ -0,0 +1,8 @@ +package racingcar.constant; + +public class Constant { + public static final int GO_THRESHOLD = 4; + public static final int NAME_MAX_LENGTH = 5; + public static final int RANDOM_FROM = 0; + public static final int RANDOM_TO = 9; +} diff --git a/src/main/java/racingcar/controller/RacingController.java b/src/main/java/racingcar/controller/RacingController.java index 832aeb0d8b..c4d289e56c 100644 --- a/src/main/java/racingcar/controller/RacingController.java +++ b/src/main/java/racingcar/controller/RacingController.java @@ -18,10 +18,11 @@ public RacingController(RacingService service, InputView inputView, OutputView o public void run() { String inputCarNames = inputView.readCarNames(); - String inputRaceCount = inputView.readRaceCount(); - Cars cars = service.getCars(inputCarNames); + + String inputRaceCount = inputView.readRaceCount(); int raceCount = service.getRaceCount(inputRaceCount); + service.runRace(cars, raceCount); String winners = service.decideWinners(cars); diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java index 277abb3fb8..3eb3b731cd 100644 --- a/src/main/java/racingcar/domain/Cars.java +++ b/src/main/java/racingcar/domain/Cars.java @@ -6,8 +6,8 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.stream.Stream; +import static racingcar.constant.Constant.*; import static racingcar.constant.ErrorMessage.*; public class Cars { @@ -19,8 +19,9 @@ public Cars(List cars, List races) { this.races = races; } - public static Cars of(String[] inputCarNames) { - List cars = Stream.of(inputCarNames) + public static Cars of(List inputCarNames) { + List cars = inputCarNames + .stream() .filter(s -> !s.isBlank()) .map(Car::new) .toList(); @@ -30,7 +31,7 @@ public static Cars of(String[] inputCarNames) { public void runRace(int raceCount) { for (int i = 0; i < raceCount; i++) { cars.stream() - .filter(car -> car.pickNumber() >= 4) + .filter(car -> car.pickNumber() >= GO_THRESHOLD) .forEach(Car::go); StringBuilder steps = new StringBuilder(); @@ -93,7 +94,7 @@ public String getStepsResult() { } public int pickNumber() { - return Randoms.pickNumberInRange(0, 9); + return Randoms.pickNumberInRange(RANDOM_FROM, RANDOM_TO); } public void go() { diff --git a/src/main/java/racingcar/service/RacingService.java b/src/main/java/racingcar/service/RacingService.java index d7dcc2a171..6a0cb6e2e2 100644 --- a/src/main/java/racingcar/service/RacingService.java +++ b/src/main/java/racingcar/service/RacingService.java @@ -2,6 +2,8 @@ import racingcar.domain.Cars; +import java.util.List; + import static racingcar.util.ParseInt.parseInt; import static racingcar.util.Split.split; import static racingcar.util.Validator.*; @@ -10,8 +12,10 @@ public class RacingService { public Cars getCars(String inputCarNames) { validateBlank(inputCarNames); - String[] splitCarNames = split(inputCarNames); + List splitCarNames = split(inputCarNames); + validateLength(splitCarNames); + validateNoCars(splitCarNames); return Cars.of(splitCarNames); } diff --git a/src/main/java/racingcar/util/Split.java b/src/main/java/racingcar/util/Split.java index 3000776844..b54ebfca4d 100644 --- a/src/main/java/racingcar/util/Split.java +++ b/src/main/java/racingcar/util/Split.java @@ -1,10 +1,13 @@ package racingcar.util; +import java.util.List; +import java.util.stream.Stream; + public class Split { private static final String DELIMITER = ","; - public static String[] split(String str) { - return str.split(DELIMITER); + public static List split(String str) { + return Stream.of(str.split(DELIMITER)).map(String::strip).toList(); } } diff --git a/src/main/java/racingcar/util/Validator.java b/src/main/java/racingcar/util/Validator.java index 2dda5bf466..85b65b1d3d 100644 --- a/src/main/java/racingcar/util/Validator.java +++ b/src/main/java/racingcar/util/Validator.java @@ -1,8 +1,8 @@ package racingcar.util; import java.util.List; -import java.util.stream.Stream; +import static racingcar.constant.Constant.NAME_MAX_LENGTH; import static racingcar.constant.ErrorMessage.*; public class Validator { @@ -13,16 +13,27 @@ public static void validateBlank(String input) { } } - public static void validateLength(String[] splitCarNames) { - List carNames = Stream.of(splitCarNames) + public static void validateLength(List splitCarNames) { + List carNames = splitCarNames + .stream() .filter(s -> !s.isBlank()) - .filter(s -> s.length() > 5) + .filter(s -> s.strip().length() > NAME_MAX_LENGTH) .toList(); if (!carNames.isEmpty()) { throw new IllegalArgumentException(MAXIMUM_LENGTH_ERROR.getErrorMessage()); } } + public static void validateNoCars(List splitCarNames) { + List carNames = splitCarNames + .stream() + .filter(s -> !s.isBlank()) + .toList(); + if (carNames.isEmpty()) { + throw new IllegalArgumentException(NO_CAR_ERROR.getErrorMessage()); + } + } + public static void validateOverflow(String inputRaceCount, int raceCount) { if (!inputRaceCount.equals(String.valueOf(raceCount))) { throw new IllegalArgumentException(INTEGER_OVERFLOW_ERROR.getErrorMessage()); diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java index af61563ddf..58efac6d19 100644 --- a/src/main/java/racingcar/view/OutputView.java +++ b/src/main/java/racingcar/view/OutputView.java @@ -16,4 +16,4 @@ public void printRace(Cars cars, int raceCount) { public void printWinners(String winners) { System.out.println(WINNERS_MESSAGE + winners); } -} +} \ No newline at end of file diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 74ade52c06..9ad1182ed8 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -51,7 +51,7 @@ class ApplicationTest extends NsTest { void 입력값_양쪽_공백_테스트() { assertRandomNumberInRangeTest( () -> { - run(" pobi,woni ", "1"); + run(" pobi,woni ", " 1 "); assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi"); }, MOVING_FORWARD, STOP @@ -70,13 +70,32 @@ class ApplicationTest extends NsTest { } @Test - void 입력값_공백_테스트() { + void 각_자동차이름_양쪽_공백__테스트() { + assertRandomNumberInRangeTest( + () -> { + run(" pobi , woni ", "1"); + assertThat(output()).contains("pobi : -", "woni : ", "최종 우승자 : pobi"); + }, + MOVING_FORWARD, STOP + ); + } + + @Test + void 자동차이름_입력값_공백_테스트() { assertSimpleTest(() -> assertThatThrownBy(() -> runException(" ", "1")) .isInstanceOf(IllegalArgumentException.class) ); } + @Test + void 시도할횟수_입력값_공백_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni", " ")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + @Test void 자동차이름_5자_초과_테스트() { assertSimpleTest(() -> From eaaaded1ce16817ac84bd8ca02f6db5a84671b6f Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Fri, 26 Sep 2025 18:29:56 +0900 Subject: [PATCH 08/14] =?UTF-8?q?Docs:=20README.md=20=ED=94=BC=EC=9D=BC=20?= =?UTF-8?q?=EB=82=B4=EC=9A=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 018edde6ac..a46730edd3 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # java-racingcar-precourse - ->스스로 판단한 내용은 `(판단)` 으로 표기한다. +스스로 판단한 내용은 `(판단)` 으로 표기한다. ## 구현 기능 목록 +기능 작동 순서대로 작성 ### 1. 자동차의 이름을 입력받고 검증한다. > 검증 조건이 추가되거나 변경될 가능성이 있다. From 0a154811e9723f0eb1ec24c721f84dd6f6e45d22 Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Sat, 27 Sep 2025 01:13:27 +0900 Subject: [PATCH 09/14] =?UTF-8?q?Docs:=20README.md=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20?= =?UTF-8?q?=EC=9A=94=EC=86=8C=20=EC=B6=94=EA=B0=80(=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a46730edd3..7b926fb202 100644 --- a/README.md +++ b/README.md @@ -76,4 +76,5 @@ - [x] 자동차 이름 입력값에 문제가 있을 때 시도할 횟수 입력 받지 않고 바로 예외 발생 가능하도록 변경 - [x] 로직에 중요한 역할을 하는 상수는 따로 관리(Constant) - [x] Service 에서 split 메서드 반환값을 배열에서 리스트로 수정 -- [x] validateNoCars 메서드 추가 \ No newline at end of file +- [x] validateNoCars 메서드 추가 +- [ ] 주석 추가 \ No newline at end of file From e20b5d3b7c2c7b11b52dded96165a09d1e1757ed Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Sat, 27 Sep 2025 01:44:27 +0900 Subject: [PATCH 10/14] =?UTF-8?q?Feat:=20=EB=AA=A8=EB=93=A0=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EC=97=90=20=EC=A3=BC=EC=84=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/racingcar/ApplicationRunner.java | 1 + .../java/racingcar/constant/Constant.java | 1 + .../java/racingcar/constant/ErrorMessage.java | 1 + .../controller/RacingController.java | 16 +++++++------- src/main/java/racingcar/domain/Cars.java | 11 +++++++++- .../java/racingcar/service/RacingService.java | 22 +++++++++---------- src/main/java/racingcar/util/ParseInt.java | 3 ++- src/main/java/racingcar/util/Split.java | 3 ++- src/main/java/racingcar/util/Validator.java | 5 +++++ src/main/java/racingcar/view/OutputView.java | 2 +- 10 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/main/java/racingcar/ApplicationRunner.java b/src/main/java/racingcar/ApplicationRunner.java index 343f785383..ea2d36735a 100644 --- a/src/main/java/racingcar/ApplicationRunner.java +++ b/src/main/java/racingcar/ApplicationRunner.java @@ -8,6 +8,7 @@ public class ApplicationRunner { public static void run() { + // Controller에 생성자 주입 RacingController controller = new RacingController( new RacingService(), new InputView(), diff --git a/src/main/java/racingcar/constant/Constant.java b/src/main/java/racingcar/constant/Constant.java index 82c6b256bb..0259c9b67a 100644 --- a/src/main/java/racingcar/constant/Constant.java +++ b/src/main/java/racingcar/constant/Constant.java @@ -1,6 +1,7 @@ package racingcar.constant; public class Constant { + // 로직을 구성하는 핵심 상수 관리 public static final int GO_THRESHOLD = 4; public static final int NAME_MAX_LENGTH = 5; public static final int RANDOM_FROM = 0; diff --git a/src/main/java/racingcar/constant/ErrorMessage.java b/src/main/java/racingcar/constant/ErrorMessage.java index fdf7d37aa6..13fbb535de 100644 --- a/src/main/java/racingcar/constant/ErrorMessage.java +++ b/src/main/java/racingcar/constant/ErrorMessage.java @@ -1,6 +1,7 @@ package racingcar.constant; public enum ErrorMessage { + // 에러 메시지 통합 관리 NO_CAR_ERROR("No cars exist."), NOT_POSITIVE_NUMBER_ERROR("Enter a positive number."), ONLY_WHITESPACE_ERROR("Input contains only whitespace."), diff --git a/src/main/java/racingcar/controller/RacingController.java b/src/main/java/racingcar/controller/RacingController.java index c4d289e56c..9aaaae888e 100644 --- a/src/main/java/racingcar/controller/RacingController.java +++ b/src/main/java/racingcar/controller/RacingController.java @@ -17,16 +17,16 @@ public RacingController(RacingService service, InputView inputView, OutputView o } public void run() { - String inputCarNames = inputView.readCarNames(); - Cars cars = service.getCars(inputCarNames); + String inputCarNames = inputView.readCarNames(); // 자동차 이름 입력값 반환 + Cars cars = service.getCars(inputCarNames); // 입력값 검증 + 자동차 리스트 반환 - String inputRaceCount = inputView.readRaceCount(); - int raceCount = service.getRaceCount(inputRaceCount); + String inputRaceCount = inputView.readRaceCount(); // 시도할 횟수 입력값 반환 + int raceCount = service.getRaceCount(inputRaceCount); // 입력값 검증 + 양수로 변환한 값 반 - service.runRace(cars, raceCount); - String winners = service.decideWinners(cars); + service.runRace(cars, raceCount); // 자동차 경주 진행, 경주 진행 과정은 자동차 리스트에 문자열 형태로 저장됨. + String winners = service.decideWinners(cars); // 결과에 따른 우승자 선정 후 반환 - outputView.printRace(cars, raceCount); - outputView.printWinners(winners); + outputView.printRace(cars, raceCount); // 자동차 경주 진행 과정 출력 + outputView.printWinners(winners); // 우승자 출력 } } diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java index 3eb3b731cd..552659fda9 100644 --- a/src/main/java/racingcar/domain/Cars.java +++ b/src/main/java/racingcar/domain/Cars.java @@ -14,11 +14,12 @@ public class Cars { private final List cars; private final List races; - public Cars(List cars, List races) { + private Cars(List cars, List races) { this.cars = cars; this.races = races; } + // 자동차 리스트 생성 및 반환 public static Cars of(List inputCarNames) { List cars = inputCarNames .stream() @@ -28,12 +29,15 @@ public static Cars of(List inputCarNames) { return new Cars(cars, new ArrayList<>()); } + // 자동차 경주 진행 public void runRace(int raceCount) { for (int i = 0; i < raceCount; i++) { cars.stream() .filter(car -> car.pickNumber() >= GO_THRESHOLD) .forEach(Car::go); + // 진행 과정 출력 문자열 저장 + // 성능 최적화를 위해 StringBuilder 사용 StringBuilder steps = new StringBuilder(); cars.stream() .map(Car::getStepsResult) @@ -43,10 +47,12 @@ public void runRace(int raceCount) { } } + // 각 라운드 진행 과정 출력 문자열 반환 -> OutputView 에서 호출 public String getRace(int i) { return races.get(i); } + // 우승자 step 개수 반환 public int getWinnerSteps() { Optional winnerCar = cars .stream() @@ -57,6 +63,7 @@ public int getWinnerSteps() { return winnerCar.get().raceCount; } + // 우승자 출력 문자열 반환 public String getWinners(int winnerSteps) { List winnersList = getWinnersList(winnerSteps); @@ -72,12 +79,14 @@ public String getWinners(int winnerSteps) { return winners.toString(); } + // 우승자 리스트 반환 private List getWinnersList(int winnerSteps) { return cars.stream() .filter(car -> car.raceCount == winnerSteps) .toList(); } + // Cars 에서만 사용하는 Car 클래스 public static class Car { private final String name; private String steps; diff --git a/src/main/java/racingcar/service/RacingService.java b/src/main/java/racingcar/service/RacingService.java index 6a0cb6e2e2..d035de8c6d 100644 --- a/src/main/java/racingcar/service/RacingService.java +++ b/src/main/java/racingcar/service/RacingService.java @@ -10,33 +10,33 @@ public class RacingService { public Cars getCars(String inputCarNames) { - validateBlank(inputCarNames); + validateBlank(inputCarNames); // 입력값이 빈 문자열이 아닌지 검증 - List splitCarNames = split(inputCarNames); + List splitCarNames = split(inputCarNames); // 입력값을 쉼표(,)로 구분해 자동차 이름을 저장하는 리스트로 생성 - validateLength(splitCarNames); - validateNoCars(splitCarNames); + validateLength(splitCarNames); // 각 자동차 이름의 길이가 5를 초과하지 않는지 검증 + validateNoCars(splitCarNames); // 유효한 자동차 이름이 1개 이상인지 검증 - return Cars.of(splitCarNames); + return Cars.of(splitCarNames); // 자동차 리스트 생성 및 반환 } public int getRaceCount(String inputRaceCount) { - validateBlank(inputRaceCount); + validateBlank(inputRaceCount); // 입력값이 빈 문자열이 아닌지 검증 - int raceCount = parseInt(inputRaceCount); + int raceCount = parseInt(inputRaceCount); // 입력값을 양수로 변환, 불가능할 시 예외 발생 - validateOverflow(inputRaceCount, raceCount); + validateOverflow(inputRaceCount, raceCount); // 정수 오버플로우 검증 return raceCount; } public void runRace(Cars cars, int raceCount) { - cars.runRace(raceCount); + cars.runRace(raceCount); // 자동차 경주 진행 } public String decideWinners(Cars cars) { - int winnerSteps = cars.getWinnerSteps(); + int winnerSteps = cars.getWinnerSteps(); // 우승자 step 개수 반환 - return cars.getWinners(winnerSteps); + return cars.getWinners(winnerSteps); // 우승자 출력 문자열 반환 } } diff --git a/src/main/java/racingcar/util/ParseInt.java b/src/main/java/racingcar/util/ParseInt.java index b2ccc07f0f..3c79b85abd 100644 --- a/src/main/java/racingcar/util/ParseInt.java +++ b/src/main/java/racingcar/util/ParseInt.java @@ -7,8 +7,9 @@ public class ParseInt { public static int parseInt(String str) { int integer; try { - integer = Integer.parseUnsignedInt(str); + integer = Integer.parseUnsignedInt(str); // 문자열을 양수로 변환 } catch (NumberFormatException e) { + // 문자열을 양수로 변환할 수 없으면 예외 발생(예. 음수이거나 숫자가 아닌 문자가 포함된 경우) throw new IllegalArgumentException(NOT_POSITIVE_NUMBER_ERROR.getErrorMessage()); } return integer; diff --git a/src/main/java/racingcar/util/Split.java b/src/main/java/racingcar/util/Split.java index b54ebfca4d..4fcc2c78df 100644 --- a/src/main/java/racingcar/util/Split.java +++ b/src/main/java/racingcar/util/Split.java @@ -5,9 +5,10 @@ public class Split { - private static final String DELIMITER = ","; + private static final String DELIMITER = ","; // 구분자 public static List split(String str) { + // 각 자동차 이름의 양쪽 공백을 제거 한 후 이름을 저장하는 리스트 생성 후 반환 return Stream.of(str.split(DELIMITER)).map(String::strip).toList(); } } diff --git a/src/main/java/racingcar/util/Validator.java b/src/main/java/racingcar/util/Validator.java index 85b65b1d3d..18b26eb291 100644 --- a/src/main/java/racingcar/util/Validator.java +++ b/src/main/java/racingcar/util/Validator.java @@ -7,12 +7,14 @@ public class Validator { + // 입력값이 빈 문자열이 아닌지 검증 public static void validateBlank(String input) { if (input.isBlank()) { throw new IllegalArgumentException(ONLY_WHITESPACE_ERROR.getErrorMessage()); } } + // 각 자동차 이름의 길이가 5를 초과하지 않는지 검증 public static void validateLength(List splitCarNames) { List carNames = splitCarNames .stream() @@ -24,6 +26,7 @@ public static void validateLength(List splitCarNames) { } } + // 유효한 자동차 이름이 1개 이상인지 검증 public static void validateNoCars(List splitCarNames) { List carNames = splitCarNames .stream() @@ -34,7 +37,9 @@ public static void validateNoCars(List splitCarNames) { } } + // 정수 오버플로우 검증 public static void validateOverflow(String inputRaceCount, int raceCount) { + // '양수로 변환한 값 != 입력 문자열'이면 오버플로우로 간주 if (!inputRaceCount.equals(String.valueOf(raceCount))) { throw new IllegalArgumentException(INTEGER_OVERFLOW_ERROR.getErrorMessage()); } diff --git a/src/main/java/racingcar/view/OutputView.java b/src/main/java/racingcar/view/OutputView.java index 58efac6d19..fa7df7b9de 100644 --- a/src/main/java/racingcar/view/OutputView.java +++ b/src/main/java/racingcar/view/OutputView.java @@ -9,7 +9,7 @@ public class OutputView { public void printRace(Cars cars, int raceCount) { System.out.println(RESULT_MESSAGE); for (int i = 0; i < raceCount; i++) { - System.out.println(cars.getRace(i)); + System.out.println(cars.getRace(i)); // 매 라운드 진행 과정 출력 } } From 81f4152b70dbe1e45b2432b3820d1c509520cece Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Sat, 27 Sep 2025 01:45:18 +0900 Subject: [PATCH 11/14] =?UTF-8?q?Docs:=20README.md=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20=EC=9A=94=EC=86=8C=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7b926fb202..d716129778 100644 --- a/README.md +++ b/README.md @@ -77,4 +77,4 @@ - [x] 로직에 중요한 역할을 하는 상수는 따로 관리(Constant) - [x] Service 에서 split 메서드 반환값을 배열에서 리스트로 수정 - [x] validateNoCars 메서드 추가 -- [ ] 주석 추가 \ No newline at end of file +- [x] 주석 추가 \ No newline at end of file From f041f590ab8758dd7457553f8b96347caca9b5ed Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Sat, 27 Sep 2025 11:25:10 +0900 Subject: [PATCH 12/14] =?UTF-8?q?Refactor(Cars):=20=EC=9A=B0=EC=8A=B9?= =?UTF-8?q?=EC=9E=90=20=EC=B6=9C=EB=A0=A5=EC=97=90=20StringBuiler=20?= =?UTF-8?q?=EB=8C=80=EC=8B=A0=20String.join=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/domain/Cars.java | 14 +++------ src/main/java/racingcar/view/InputView.java | 18 +++++++++-- src/test/java/racingcar/ApplicationTest.java | 32 +++++++++++++++++--- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java index 552659fda9..2c7d4c9d94 100644 --- a/src/main/java/racingcar/domain/Cars.java +++ b/src/main/java/racingcar/domain/Cars.java @@ -67,16 +67,12 @@ public int getWinnerSteps() { public String getWinners(int winnerSteps) { List winnersList = getWinnersList(winnerSteps); - StringBuilder winners = new StringBuilder(); - for (int i = 0; i < winnersList.size(); i++) { - String race = winnersList.get(i).name; - winners.append(race); - if (i != winnersList.size() - 1) { - winners.append(", "); - } - } + List winners = winnersList + .stream() + .map(car -> car.name) + .toList(); - return winners.toString(); + return String.join(", ", winners); } // 우승자 리스트 반환 diff --git a/src/main/java/racingcar/view/InputView.java b/src/main/java/racingcar/view/InputView.java index 8671ed90b4..d176facb25 100644 --- a/src/main/java/racingcar/view/InputView.java +++ b/src/main/java/racingcar/view/InputView.java @@ -2,17 +2,31 @@ import camp.nextstep.edu.missionutils.Console; +import java.util.NoSuchElementException; + +import static racingcar.constant.ErrorMessage.ONLY_WHITESPACE_ERROR; + public class InputView { private static final String CAR_NAME_INPUT_MESSAGE = "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)"; private static final String NUMBER_INPUT_MESSAGE = "시도할 회수는 몇회인가요?"; public String readCarNames() { System.out.println(CAR_NAME_INPUT_MESSAGE); - return Console.readLine().strip(); + try { + return Console.readLine().strip(); + } catch (NoSuchElementException e) { + // Console이 입력을 못 받는 상황을 IllegalArgumentException 으로 통일 + throw new IllegalArgumentException(ONLY_WHITESPACE_ERROR.getErrorMessage()); + } } public String readRaceCount() { System.out.println(NUMBER_INPUT_MESSAGE); - return Console.readLine().strip(); + try { + return Console.readLine().strip(); + } catch (NoSuchElementException e) { + // Console이 입력을 못 받는 상황을 IllegalArgumentException 으로 통일 + throw new IllegalArgumentException(ONLY_WHITESPACE_ERROR.getErrorMessage()); + } } } diff --git a/src/test/java/racingcar/ApplicationTest.java b/src/test/java/racingcar/ApplicationTest.java index 9ad1182ed8..aad3b6a897 100644 --- a/src/test/java/racingcar/ApplicationTest.java +++ b/src/test/java/racingcar/ApplicationTest.java @@ -81,7 +81,7 @@ class ApplicationTest extends NsTest { } @Test - void 자동차이름_입력값_공백_테스트() { + void 자동차이름_입력값_공백_테스트1() { assertSimpleTest(() -> assertThatThrownBy(() -> runException(" ", "1")) .isInstanceOf(IllegalArgumentException.class) @@ -89,9 +89,33 @@ class ApplicationTest extends NsTest { } @Test - void 시도할횟수_입력값_공백_테스트() { + void 자동차이름_입력값_공백_테스트2() { assertSimpleTest(() -> - assertThatThrownBy(() -> runException("pobi,woni", " ")) + assertThatThrownBy(() -> runException("", "1")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 자동차이름_입력값_공백_테스트3() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 시도할횟수_입력값_공백_테스트1() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni", " ")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Test + void 시도할횟수_입력값_공백_테스트2() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("pobi,woni", "")) .isInstanceOf(IllegalArgumentException.class) ); } @@ -131,7 +155,7 @@ class ApplicationTest extends NsTest { @Test void 시도할_횟수_오버플로우_테스트() { assertSimpleTest(() -> - assertThatThrownBy(() -> runException("pobi,woni", "2147483648")) + assertThatThrownBy(() -> runException("pobi,woni", OVERFLOW_INTEGER)) .isInstanceOf(IllegalArgumentException.class) ); } From d6e40b59acdcc296ff82e4822aaf80ac2a54c0ac Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Sat, 27 Sep 2025 11:26:49 +0900 Subject: [PATCH 13/14] =?UTF-8?q?Docs:=20README.md=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20=EC=9A=94=EC=86=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d716129778..6eb321558d 100644 --- a/README.md +++ b/README.md @@ -77,4 +77,5 @@ - [x] 로직에 중요한 역할을 하는 상수는 따로 관리(Constant) - [x] Service 에서 split 메서드 반환값을 배열에서 리스트로 수정 - [x] validateNoCars 메서드 추가 -- [x] 주석 추가 \ No newline at end of file +- [x] 주석 추가 +- [x] 우승자 출력 문자열을 반환하는 메서드에서 StringBuilder 대신 String.join 메서드 사용 \ No newline at end of file From d14582352de57ed548cfd6d3299ace24a5f8f8a0 Mon Sep 17 00:00:00 2001 From: kyeonghyeon Date: Sat, 27 Sep 2025 13:52:24 +0900 Subject: [PATCH 14/14] =?UTF-8?q?forEach=EB=A5=BC=20forEachOrdered?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/racingcar/domain/Cars.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/racingcar/domain/Cars.java b/src/main/java/racingcar/domain/Cars.java index 2c7d4c9d94..2b782a0a29 100644 --- a/src/main/java/racingcar/domain/Cars.java +++ b/src/main/java/racingcar/domain/Cars.java @@ -34,14 +34,14 @@ public void runRace(int raceCount) { for (int i = 0; i < raceCount; i++) { cars.stream() .filter(car -> car.pickNumber() >= GO_THRESHOLD) - .forEach(Car::go); + .forEachOrdered(Car::go); // 진행 과정 출력 문자열 저장 // 성능 최적화를 위해 StringBuilder 사용 StringBuilder steps = new StringBuilder(); cars.stream() .map(Car::getStepsResult) - .forEach(steps::append); + .forEachOrdered(steps::append); races.add(steps.toString()); }