Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[라빈] 자동차 경주 - TDD 미션 리뷰 요청입니다. #68

Merged
merged 46 commits into from
Feb 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6120601
덧셈 기능 구현
giantim Feb 11, 2020
bed34b7
입력된 문자열을 ,로 분리하는 기능 구현
giantim Feb 11, 2020
8d50165
커스텀 구분자에서 계산식만 추출한다.
giantim Feb 11, 2020
30cdf0d
README 수정
giantim Feb 11, 2020
707cb5b
숫자 이외의 값이 입력될 경우 RuntimeException을 throw 한다
giantim Feb 11, 2020
f68bb07
refactor : depth 와 indent 수정
giantim Feb 11, 2020
6444f75
올바르지 않은 커스텀 구분자 문자열 입력 시 예외 출력 기능 구현
giantim Feb 11, 2020
32c8a54
자동차 게임에 대한 README 수정
giantim Feb 11, 2020
d0eafc7
refact : 커스텀 구분자를 입력했을 때 \n -> \\n 으로 입력되는 것을 테스트하고 모든 테스트 케이스를 수정 / …
giantim Feb 11, 2020
7c34259
doc : README 수정
giantim Feb 12, 2020
c024111
feat : 자동차 이름을 입력받고 검증하는 테스트 코드 작성 / 자동차 이름을 검증하고 저장하는 클래스 구현
giantim Feb 12, 2020
0a1ada1
feat :시도 횟수를 검증하고 저장하는 테스트 코드 작성 / 시도 횟수를 검증하고 저장하는 클래스 구현
giantim Feb 12, 2020
622b906
작업을 위해 gitignore 수정
giantim Feb 12, 2020
6c6956a
feat : gitignore 재수정 / Car 객체 구현 / 자동차의 랜덤 숫자에 따라서 진행을 판단하는 테스트 코드 작성
giantim Feb 12, 2020
2117f52
feat : 랜덤 숫자 생성 기능 구현 / 랜덤 숫자를 생성하는 메소드 테스트 코드 작성
giantim Feb 12, 2020
a0be49e
feat : 최종 우승자를 계산하는 메서드 구현 및 테스트 코드 작성
giantim Feb 12, 2020
4534b1f
feat : 자동차 경주 게임 전체 기능 구현 및 테스트 코드 작성
giantim Feb 12, 2020
e74a1f7
refactor : 조건 검사를 메소드로 추출
giantim Feb 12, 2020
c60c5ef
refactor : 조건 검사를 메소드로 추출
giantim Feb 12, 2020
89b6fcb
refactor : DisplayName 추가 / 메소드 이름 수정 / ParameterizedTest 를 이용해서 테스트 …
giantim Feb 12, 2020
9522cc6
refactor : 계산기와 자동차 경주 게임의 메인 함수 패키지 별로 분리 / 실행 결과 출력 추가 / 자동차 경주 게임의…
giantim Feb 12, 2020
feb2efc
refactor : 클래스 이름 수정 / null 또는 빈 문자열 입력시 0을 반환하는 덧셈 기능 구현 및 테스트 코드 작성…
giantim Feb 12, 2020
cf5df4c
refactor : 쉼표구분자로 문자열을 나눠서 숫자들의 합을 출력하는 테스트 코드 작성
giantim Feb 12, 2020
cf53cd0
refactor : 쉼표 또는 콜론 구분자를 이용해 문자열을 나눠서 숫자들의 합을 출력 테스트 추가 / 코드 리팩토링
giantim Feb 12, 2020
7d1faaf
refactor : Matcher 와 Pattern 클래스를 사용해 정규표현식을 사용한 문자열 나누는 API 테스트 코드 작…
giantim Feb 12, 2020
edb4b8f
refactor : 커스텀 구분자가 포함된 문자열 또는 기본 문자열이 입력되었을 때 계산 로직 수정
giantim Feb 12, 2020
3c36d11
refactor : 음수를 전달할 경우 RuntimeException 예외가 발생하는 기능 구현 및 테스트 코드 작성
giantim Feb 12, 2020
a5995e1
refactor : 클래스 변수 제거
giantim Feb 12, 2020
4f46755
refactor : 문자열 계산기 기능 구현
giantim Feb 12, 2020
cbad998
refactor : 사용되는 문자열을 클래스로 분리 / 결과를 출력하는 기능을 StringBuilder 를 사용해서 구현
giantim Feb 12, 2020
adef935
refactor : 사용되는 문자열을 클래스로 분리 / 문자를 더하는 기능을 모두 StringBuilder 를 사용해서 구현
giantim Feb 12, 2020
0e92e0b
refactor : MVC 패턴을 적용하기 위해 디렉토리 구조 변경
giantim Feb 14, 2020
c8e50be
refactor : Car 객체만 테스트 하는 클래스로 이름 변경
giantim Feb 14, 2020
9666a78
refactor : Car 에서 Position 상태를 클래스로 추출
giantim Feb 14, 2020
60c4838
refactor : CarTest -> CarNameTest 로 테스트 클래스 별로 하나의 클래스만 테스트 하도록 변경 / …
giantim Feb 14, 2020
f91e2fe
refactor : CarName -> CarNames 로 일급 컬렉션으로 바꿈
giantim Feb 14, 2020
181b974
refactor : CarNames 에서 메소드 이름, 파라미터 이름 이해하기 쉽게 수정 / CarNamesTest 에서 테…
giantim Feb 14, 2020
c418e3d
refactor : Position 클래스를 테스트 하는 PositionTest 클래스 추가 / Position 클래스의 생…
giantim Feb 14, 2020
7f8e461
refactor : Car 의 Position 을 변경하는 메소드 이름 수정 / Car 의 carName 을 클래스로 추출 …
giantim Feb 14, 2020
441f290
refactor : Car 의 carName 을 CarName 클래스로 변경 / CarName 과 Position 클래스에 …
giantim Feb 14, 2020
38554ad
임시 푸쉬
giantim Feb 14, 2020
52d8e7c
refact : Car 클래스 equals 메소드, hashCode 메소드 오버라이드 수정 / Cars 일급 컬렉션으로 포장
giantim Feb 15, 2020
f2bebb3
refact : RacingLab 클래스 메소드 이름 수정 / RacingLab 클래스 테스트 클래스 추가
giantim Feb 15, 2020
ace6882
refact : RandomNumber 를 클래스로 추출 / RandomNumber 가 범위에 맞게 생성되는지 테스트 / R…
giantim Feb 15, 2020
faf45fb
refact : controller 구현 / viewer 를 input 과 output viewer 로 나눠서 구현 / 테스…
giantim Feb 15, 2020
f6d749a
refact : Car 의 moveCar() -> move() 로 이름 변경 / Car 를 생성할 때 CarName 을 직접…
giantim Feb 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Hue9010 marked this conversation as resolved.
Show resolved Hide resolved
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());
}
}
Comment on lines +34 to +42
Copy link

Choose a reason for hiding this comment

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

            try {
                racingLab = new RacingLab(InputViewer.getRacingLab());
            } catch (IllegalArgumentException e) {
                OutputViewer.printErrorMessage(e.getMessage());
                initializeRacingLab();
            }

이처럼 바꿀 수도 있을 것 같네요.

Copy link
Author

Choose a reason for hiding this comment

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

이 부분에 대해서는 질문이 있어서 수정하지 않고 리뷰 요청합니다. try ~ catch 구문을 이용하여 재귀함수로 메소드를 만든다면 사용자가 계속해서 비정상적인 입력값을 입력했을 때 콜 스택에 메소드 호출이 계속해서 쌓이게 되어서 메모리가 누적되어서 저는 예외를 출력해주는 반복문으로 구현했습니다. 제가 혹시 잘못 생각하거나 놓치는 부분이 있나요? 그리고 재귀함수로 구현하는 것으로 피드백을 주셨는데 이 때의 장점을 알 수 있을까요? 감사합니다!

Copy link

Choose a reason for hiding this comment

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

과제로서 콜 스택이 초과 할 상황에 대해서는 상정하지 않고 간단하게 예외 처리를 해보는 것을 의도하고 말씀드렸던 것이랍니다. 예외 처리가 생각보다 간단하지도 않고 버겁게도 느껴지는데 예외 처리로 단순히 에러 메시지를 출력하는 정도가 아니라 프로그램의 흐름도 정상적인(?) 방향으로 다시 돌려 놓을 수 있다는 걸 경험해보게 하고 싶었네요. 😄

재귀로 제안 드린 이유는 지금 형태에서 가장 간단히 재입력 받을 수 있는 형태가 재귀로 바꾸는 걸로 보여서 제안드린거지 재귀의 장점이 있어 말씀드린 건 아닙니다.

}

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