Skip to content

Commit

Permalink
[라빈] 자동차 경주 - TDD 미션 리뷰 요청입니다. (#68)
Browse files Browse the repository at this point in the history
* 덧셈 기능 구현

* 입력된 문자열을 ,로 분리하는 기능 구현

* 커스텀 구분자에서 계산식만 추출한다.

* README 수정
구분자를 추출하는 기능 추가
커스텀 구분자를 이용하여 숫자를 추출하는 기능 추가
커스텀 구분자를 추가하는 기능 추가

* 숫자 이외의 값이 입력될 경우 RuntimeException을 throw 한다

* refactor : depth 와 indent 수정

* 올바르지 않은 커스텀 구분자 문자열 입력 시 예외 출력 기능 구현

* 자동차 게임에 대한 README 수정
문자열 덧셈 계산기 구현

* refact : 커스텀 구분자를 입력했을 때 \n -> \\n 으로 입력되는 것을 테스트하고 모든 테스트 케이스를 수정 / Calculate.java 에서 모든 메소드를 \n -> \\n 으로 수정

* doc : README 수정

* feat : 자동차 이름을 입력받고 검증하는 테스트 코드 작성 / 자동차 이름을 검증하고 저장하는 클래스 구현

* feat :시도 횟수를 검증하고 저장하는 테스트 코드 작성 / 시도 횟수를 검증하고 저장하는 클래스 구현

* 작업을 위해 gitignore 수정

* feat : gitignore 재수정 / Car 객체 구현 / 자동차의 랜덤 숫자에 따라서 진행을 판단하는 테스트 코드 작성

* feat : 랜덤 숫자 생성 기능 구현 / 랜덤 숫자를 생성하는 메소드 테스트 코드 작성

* feat : 최종 우승자를 계산하는 메서드 구현 및 테스트 코드 작성

* feat : 자동차 경주 게임 전체 기능 구현 및 테스트 코드 작성

* refactor : 조건 검사를 메소드로 추출

* refactor : 조건 검사를 메소드로 추출

* refactor : DisplayName 추가 / 메소드 이름 수정 / ParameterizedTest 를 이용해서 테스트 케이스 추가

* refactor : 계산기와 자동차 경주 게임의 메인 함수 패키지 별로 분리 / 실행 결과 출력 추가 / 자동차 경주 게임의 우승자 이름을 만드는 기능을 메소드로 추출

* refactor : 클래스 이름 수정 / null 또는 빈 문자열 입력시 0을 반환하는 덧셈 기능 구현 및 테스트 코드 작성 / 한 자리의 숫자를 입력시 그 숫자를 그대로 반환하는 덧셈 기능 구현 및 테스트 코드 작성

* refactor : 쉼표구분자로 문자열을 나눠서 숫자들의 합을 출력하는 테스트 코드 작성

* refactor : 쉼표 또는 콜론 구분자를 이용해 문자열을 나눠서 숫자들의 합을 출력 테스트 추가 / 코드 리팩토링

* refactor : Matcher 와 Pattern 클래스를 사용해 정규표현식을 사용한 문자열 나누는 API 테스트 코드 작성 / 커스텀 구분자로 문자열을 나눠서 숫자들의 합을 반환 테스트 코드 추가(프로덕션 코드 작성 해야함)

* refactor : 커스텀 구분자가 포함된 문자열 또는 기본 문자열이 입력되었을 때 계산 로직 수정

* refactor : 음수를 전달할 경우 RuntimeException 예외가 발생하는 기능 구현 및 테스트 코드 작성

* refactor : 클래스 변수 제거

* refactor : 문자열 계산기 기능 구현

* refactor : 사용되는 문자열을 클래스로 분리 / 결과를 출력하는 기능을 StringBuilder 를 사용해서 구현

* refactor : 사용되는 문자열을 클래스로 분리 / 문자를 더하는 기능을 모두 StringBuilder 를 사용해서 구현

* refactor : MVC 패턴을 적용하기 위해 디렉토리 구조 변경

* refactor : Car 객체만 테스트 하는 클래스로 이름 변경

* refactor : Car 에서 Position 상태를 클래스로 추출

* refactor : CarTest -> CarNameTest 로 테스트 클래스 별로 하나의 클래스만 테스트 하도록 변경 / 컴파일 안되던 문제 수정

* refactor : CarName -> CarNames 로 일급 컬렉션으로 바꿈

* refactor : CarNames 에서 메소드 이름, 파라미터 이름 이해하기 쉽게 수정 / CarNamesTest 에서 테스트 목적에 맞게 DisplayName 수정

* refactor : Position 클래스를 테스트 하는 PositionTest 클래스 추가 / Position 클래스의 생성자 변경

* refactor : Car 의 Position 을 변경하는 메소드 이름 수정 / Car 의 carName 을 클래스로 추출 / CarName 을 테스트 하는 코드 추가

* refactor : Car 의 carName 을 CarName 클래스로 변경 / CarName 과 Position 클래스에 equals 메소드 오버라이드

* 임시 푸쉬

* refact : Car 클래스 equals 메소드, hashCode 메소드 오버라이드 수정 / Cars 일급 컬렉션으로 포장

* refact : RacingLab 클래스 메소드 이름 수정 / RacingLab 클래스 테스트 클래스 추가

* refact : RandomNumber 를 클래스로 추출 / RandomNumber 가 범위에 맞게 생성되는지 테스트 / RacingGame 에서 RacingLab 만큼 게임이 실행되었는지 확인하는 메소드 테스트 추가

* refact : controller 구현 / viewer 를 input 과 output viewer 로 나눠서 구현 / 테스트 코드 정리

* refact : Car 의 moveCar() -> move() 로 이름 변경 / Car 를 생성할 때 CarName 을 직접 받아오도록 변경 / 직관성이 높아지도록 각 클래스마다 상수 변경 / Cars 의 addCar() -> initializeCar() 로 변경 / PositionTest 에서 move 를 할 때 예외가 발생하지 않도록 설계했으므로 테스트 삭제 / CarTest 에 자동차의 위치가 잘 변하는지 테스트 케이스 추가
  • Loading branch information
giantim authored Feb 17, 2020
1 parent c7f9a56 commit c340823
Show file tree
Hide file tree
Showing 23 changed files with 824 additions and 2 deletions.
68 changes: 66 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,69 @@
# java-racingcar
자동차 경주 게임 미션 저장소

## 우아한테크코스 코드리뷰
* [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md)
## 문자열 덧셈 계산기
요구사항
- 쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환 (예: “” => 0, "1,2" => 3, "1,2,3" => 6, “1,2:3” => 6)
- 앞의 기본 구분자(쉼표, 콜론)외에 커스텀 구분자를 지정할 수 있다. 커스텀 구분자는 문자열 앞부분의 “//”와 “\n” 사이에 위치하는 문자를 커스텀 구분자로 사용한다. 예를 들어 “//;\n1;2;3”과 같이 값을 입력할 경우 커스텀 구분자는 세미콜론(;)이며, 결과 값은 6이 반환되어야 한다.
- 문자열 계산기에 숫자 이외의 값 또는 음수를 전달하는 경우 RuntimeException 예외를 throw한다.
- 문자열을 입력 받는다
- 예외사항
- 숫자 또는 슬래쉬로 시작하지 않는 문자열은 RuntimeException 예외를 throw 한다.
- 숫자로 시작하는 문자열의 구분자는 , 또는 : 만 허용된다.
- 커스텀 구분자를 // 와 \n 로 감싸지 않으면 RuntimException 예외를 throw 한다.

프로그래밍 요구사항
- indent(들여쓰기) depth를 2단계에서 1단계로 줄여라.
- depth의 경우 if문을 사용하는 경우 1단계의 depth가 증가한다. if문 안에 while문을 사용한다면 depth가 2단계가 된다.
- 메소드의 크기가 최대 10라인을 넘지 않도록 구현한다.
- method가 한 가지 일만 하도록 최대한 작게 만들어라.
- else를 사용하지 마라.

## 자동차 경주
### 기능 요구사항
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 횟수는 음수이면 안된다.
- 횟수는 양의 정수이어야 한다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 이름에는 쉼표를 제외한 모든 문자가 포함 가능하다.
- 길이가 5 이하인 문자열이면 자동차 이름으로 할 수 있다.
- 공백 또는 쉼표로 시작하는 문자열을 입력하면 안된다.(ex: ,lavine)
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.

### 프로그래밍 요구사항
- 모든 로직에 단위 테스트를 구현한다. 단, UI(System.out, System.in) 로직은 제외
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 참고문서: https://google.github.io/styleguide/javaguide.html 또는 https://myeonguni.tistory.com/1596
- indent(인덴트, 들여쓰기) depth를 3을 넘지 않도록 구현한다. 2까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다.
- else 예약어를 쓰지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.

### 기능 목록 및 commit 로그 요구사항
- 기능을 구현하기 전에 README.md 파일에 구현할 기능 목록을 정리해 추가한다.
- git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.

### 기능 목록
- CarName
- 자동차 이름을 검증하고 저장하는 클래스
- RacingLab
- 시도 횟수를 검증하고 저장하는 클래스
- Output
- 실행 결과를 출력하는 메소드
- 우승자를 출력하는 메소드
- Car
- 이름과 위치를 필드로 갖는다.
- 랜덤 숫자에 따라서 진행을 판단하는 메소드
- Racing
- 입력받은 시도 횟수만큼 반복하는 메소드
- 랜덤 숫자를 생성하는 메소드
- 우승자를 판단하는 메소드
- Application
- 자동차 경주 실행
35 changes: 35 additions & 0 deletions src/main/java/application/calculator/CalculatorApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package application.calculator;

import java.util.Scanner;

public class CalculatorApplication {
private static Scanner scanner = new Scanner(System.in);

private static void runCalculator() {
try {
String userInputText = getUserInputText();
int calculateResult = StringCalculator.splitAndSum(userInputText);
String result = makeResultString(userInputText, calculateResult);
System.out.println(result);
} catch (Exception e) {
System.out.println(e.getMessage());
runCalculator();
}
}

private static String makeResultString(String userInputText, int calculateResult) {
StringBuilder sb = new StringBuilder();
sb.append(userInputText);
sb.append(ConstantForCalculator.ARROW);
sb.append(calculateResult);
return sb.toString();
}

private static String getUserInputText() {
return scanner.nextLine();
}

public static void main(String[] args) {
runCalculator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package application.calculator;

public class ConstantForCalculator {
public final static String ARROW = " => ";
public final static String BIT_OR_OPERATOR = "|";
public final static String DEFAULT_DELIMITER = ",|:";
public final static String REGULAR_EXPRESSION = "//(.)\\\\n(.*)";
}
72 changes: 72 additions & 0 deletions src/main/java/application/calculator/StringCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package application.calculator;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class StringCalculator {
public static int splitAndSum(String text) {
if (isNullOrEmptyText(text)) {
return 0;
}
String delimiter = ConstantForCalculator.DEFAULT_DELIMITER;
String[] splitedText = splitText(text, delimiter);
List<Integer> numbers = new ArrayList<>();
splitText(text, delimiter);
addNumbers(splitedText, numbers);
return calculateNumberSum(numbers);
}

private static String[] splitText(String text, String delimiter) {
Matcher matcher = Pattern.compile(ConstantForCalculator.REGULAR_EXPRESSION).matcher(text);
if (matcher.find()) {
String customDelimiter = matcher.group(1);
delimiter = addCustomDelimiter(delimiter, customDelimiter);
return matcher.group(2).split(delimiter);
}
return text.split(delimiter);
}

private static String addCustomDelimiter(String delimiter, String customDelimiter) {
StringBuilder sb = new StringBuilder();
sb.append(delimiter);
sb.append(ConstantForCalculator.BIT_OR_OPERATOR);
sb.append(customDelimiter);
return sb.toString();
}

private static int calculateNumberSum(List<Integer> numbers) {
int sum = 0;
for (int integer : numbers) {
sum = sum + integer;
}
return sum;
}

private static void addNumbers(String[] splitString, List<Integer> numbers) {
for (String string : splitString) {
int convertNumber = getConvertNumber(string);
throwRuntimeExceptionWhenNegativeNumber(convertNumber);
numbers.add(convertNumber);
}
}

private static void throwRuntimeExceptionWhenNegativeNumber(int number) {
if (number < 0) {
throw new RuntimeException("음수를 입력하였습니다.");
}
}

private static int getConvertNumber(String string) {
try {
return Integer.parseInt(string);
} catch (Exception e) {
throw new RuntimeException("잘못된 수를 입력하였습니다.");
}
}

private static boolean isNullOrEmptyText(String text) {
return text == null || text.isEmpty();
}
}
9 changes: 9 additions & 0 deletions src/main/java/application/racing/RacingApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package application.racing;

import application.racing.controller.RacingGameController;

public class RacingApplication {
public static void main(String[] args) {
RacingGameController.run();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package application.racing.controller;

import application.racing.domain.Cars;
import application.racing.domain.RacingGame;
import application.racing.domain.RacingLab;
import application.racing.view.InputViewer;
import application.racing.view.OutputViewer;

public class RacingGameController {
private static Cars cars;
private static RacingLab racingLab;
private static RacingGame racingGame;

public static void run() {
initializeCars();
initializeRacingLab();
race();
OutputViewer.printWinner(cars.findWinner());
}

private static void initializeCars() {
boolean flag = false;
while (!flag) {
try {
cars = new Cars(InputViewer.getCarsName());
flag = true;
} catch (IllegalArgumentException e) {
OutputViewer.printErrorMessage(e.getMessage());
}
}
}

private static void initializeRacingLab() {
boolean flag = false;
while (!flag) {
try {
racingLab = new RacingLab(InputViewer.getRacingLab());
flag = true;
} catch (IllegalArgumentException e) {
OutputViewer.printErrorMessage(e.getMessage());
}
}
}

private static void race() {
racingGame = new RacingGame();
OutputViewer.printRacingResultMessage();
while (!racingGame.isEnd(racingLab)) {
racingGame.raceOneLab(cars);
OutputViewer.printPositionDuringRace(cars);
}
}
}
46 changes: 46 additions & 0 deletions src/main/java/application/racing/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package application.racing.domain;

public class Car {
private CarName carName;
private Position position;

public Car(CarName carName) {
this.carName = carName;
this.position = new Position();
}

public void move(int number) {
this.position.move(number);
}

public boolean isMaxPosition(int maxPosition) {
return this.getPosition() == maxPosition;
}

public int getPosition() {
return this.position.getPosition();
}

@Override
public String toString() {
return this.carName.toString();
}

@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || !(o instanceof Car)) {
return false;
}
Car compareCar = (Car) o;
return compareCar.carName.equals(this.carName)
&& compareCar.position.equals(this.position);
}

@Override
public int hashCode() {
return 31 * (this.carName.hashCode() + this.position.hashCode());
}
}
55 changes: 55 additions & 0 deletions src/main/java/application/racing/domain/CarName.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package application.racing.domain;

public class CarName {
private final static String BLANK = " ";
private final static int MAX_NAME_LENGTH = 5;

private String carName;

public CarName(String carName) {
validateCarNameFormat(carName);
validateCarNameContainBlank(carName);
validateCarNameLength(carName);
this.carName = carName;
}

private void validateCarNameFormat(String carName) {
if (carName == null || carName.isEmpty()) {
throw new IllegalArgumentException("이름을 잘못 입력하였습니다.");
}
}

private void validateCarNameContainBlank(String carName) {
if (carName.contains(BLANK)) {
throw new IllegalArgumentException("공백을 포함한 이름을 입력하였습니다.");
}
}

private void validateCarNameLength(String carName) {
if (carName.length() > MAX_NAME_LENGTH) {
throw new IllegalArgumentException("5글자 초과의 자동차 이름을 입력하였습니다.");
}
}

@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || !(o instanceof CarName)) {
return false;
}
CarName compareCarName = (CarName) o;
return compareCarName.carName.equals(this.carName);
}

@Override
public int hashCode() {
return this.carName.hashCode() * 31;
}

@Override
public String toString() {
return this.carName;
}
}
Loading

0 comments on commit c340823

Please sign in to comment.