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

[2단계 - 자동차 경주 구현] 유콩(김유빈) 미션 제출합니다. #391

Merged
merged 90 commits into from
Feb 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
6f84862
feat: 계산기 생성
her0807 Feb 9, 2022
823ba76
test: String에 관한 테스트 추가
her0807 Feb 9, 2022
2766eaf
test: Set에 관한 테스트 추가
her0807 Feb 9, 2022
7d1fac0
test: 테스트 케이스를 추가함
her0807 Feb 9, 2022
76d2675
test: 중간에 공백이나 null 이 들어올 경우
her0807 Feb 9, 2022
0921832
test: 값이 문자일 경우 테스트
her0807 Feb 9, 2022
a7b6a2c
feat: 값이 Null or 빈값일 경우 예외처리
her0807 Feb 9, 2022
d766f78
feat: 숫자 하나만 들어올 경우 예외 추가
her0807 Feb 9, 2022
ff3031d
feat: , : 구분자 추가
her0807 Feb 9, 2022
3336910
feat: 커스텀 구분자가 있을 경우 기능 추가
her0807 Feb 9, 2022
d25e076
feat: 음수일 경우 예외 추가
her0807 Feb 9, 2022
c31485e
docs: 기능 구현 목록 추가
her0807 Feb 9, 2022
b2e7753
test: 자동차 이름이 공백일 경우 테스트 추가
kyukong Feb 10, 2022
8a04ed9
test: 입력한 자동차 개수가 2개 미만인 경우 테스트 추가
kyukong Feb 10, 2022
9b61954
test: 자동차 이름이 중복될 경우 테스트 추가
kyukong Feb 10, 2022
e9fd026
test: 자동차 이름의 길이가 5글자를 초과하는 경우 테스트 추가
kyukong Feb 10, 2022
c2d5cef
feat: 자동차 이름이 공백인 경우 추가
kyukong Feb 10, 2022
cdb6e31
feat: 자동차 개수가 2개 미만인 경우 추가
kyukong Feb 10, 2022
19f23e9
feat: 자동차 이름이 중복될 경우 추가
kyukong Feb 10, 2022
58a2197
feat: 자동차의 이름이 5글자를 초과하는 경우 추가
kyukong Feb 10, 2022
c75be9b
refactor(Input): 메소드 분리
kyukong Feb 10, 2022
6e15a7a
fix: 자동차 입력받는 메소드 수정
kyukong Feb 10, 2022
ee098a8
fix(InputTest): 자동차 입력 테스트 수정
kyukong Feb 10, 2022
81803ce
refactor: [ERROR] 메시지 추가
kyukong Feb 10, 2022
04800df
docs: README 갱신
kyukong Feb 10, 2022
2909795
test: 시도횟수 공백 테스트 추가
kyukong Feb 10, 2022
03bd0fe
test: 시도횟수 문자 예외 테스트 추가
kyukong Feb 10, 2022
8f08141
test: 시도횟수 음수 테스트 추가
kyukong Feb 10, 2022
7030762
feat: 시도횟수 문자 예외 확인 추가
kyukong Feb 10, 2022
39f2a98
feat: 시도횟수 1미만의 수 확인 추가
kyukong Feb 10, 2022
2610e18
fix(InputTest): 시도횟수 음수 테스트 시 입력값 타입 수정
kyukong Feb 10, 2022
50a31a2
docs: README 갱신
kyukong Feb 10, 2022
be7e5ac
feat: Car 클래스 생성
kyukong Feb 10, 2022
3d7f62f
feat: Cars 클래스 생성
kyukong Feb 10, 2022
b45edd0
feat: Attempt 클래스 생성
kyukong Feb 10, 2022
9501d26
refactor: 데이터 일급객체, 일급컬렉션으로 포장
kyukong Feb 10, 2022
5abeaa3
feat: 레이싱 게임을 진행할 RacingCarGame 클래스 생성
kyukong Feb 10, 2022
81d254f
feat: 자동차 이동 기능 구현
kyukong Feb 10, 2022
10557cf
feat: 자동차 이동 구현 메소드 실행
kyukong Feb 10, 2022
32b0a84
docs: README 갱신
kyukong Feb 10, 2022
2f02b83
test: 현재 횟수와 시도횟수가 동일한지 확인하는 테스트 추가
kyukong Feb 10, 2022
8de85d1
test: 자동차 위치 이동 테스트 추가
kyukong Feb 10, 2022
bb98fd9
feat: 경기 결과 출력
kyukong Feb 10, 2022
d5d875a
docs: README 갱신
kyukong Feb 10, 2022
7593a3f
feat: 최종 우승자 출력
kyukong Feb 10, 2022
f93c576
refactor: 자동차 위치 이동 테스트 수정
kyukong Feb 10, 2022
2a4418d
fix(Input): 자동차 이름 길이 예외처리 수정
kyukong Feb 10, 2022
dfe5502
refactor: 들여쓰기 수정함에 따라 관련 코드 수정
kyukong Feb 10, 2022
a02d824
docs: README 갱신
kyukong Feb 10, 2022
ea1e12a
refactor(Car): 상수 추출과 메서드 분리
her0807 Feb 11, 2022
0bedfd4
refactor(Cars): 상수 추출과 자동차 경주, 우승자 추출 로직 테스트
her0807 Feb 11, 2022
7297c7a
refactor(RacingCarGame): 조금더 직관적인 네이밍으로 수정, 상수 추출
her0807 Feb 11, 2022
ad6a6ca
refactor(Input): 상수 추출, 공백과 null error 분리
her0807 Feb 11, 2022
3c6c3ec
docs: 프로그램 동작 기능 설명 추가
kyukong Feb 13, 2022
bd5f929
docs: 기능 요구사항 추가
kyukong Feb 13, 2022
a26dda5
refactor(Calculator): 메소드 네이밍 수정, int 배열로 변환하는 로직 수정
kyukong Feb 14, 2022
6e59f11
refactor: 상수화 축소
kyukong Feb 14, 2022
195dbab
refactor: 검증 로직 클래스 분리
kyukong Feb 14, 2022
7350989
refactor: 자동차 이름 검증 로직 클래스 분리
kyukong Feb 14, 2022
203af30
refactor: 검증을 Validator 에게 부여하여 기존 InputTest 를 ValidatorTest 로 수정
kyukong Feb 15, 2022
d4ef0a5
refactor: 시도횟수 검증 로직을 domain/Attempt 로 이동
kyukong Feb 15, 2022
51a48f4
refactor: 자동차 검증 로직을 domain/Car 와 domain/Cars 로 이동
kyukong Feb 15, 2022
054c5e4
refactor(CarTest): Car의 drive 테스트 메소드 수정
kyukong Feb 15, 2022
8fe382d
refactor(Car): public 과 private 메소드 위치 조정
kyukong Feb 15, 2022
ee23ac8
refactor(Cars): public 과 private 메소드 위치 조정
kyukong Feb 15, 2022
2b376c2
refactor(Cars): 인스턴스 변수 cars 초기화 위치 수정
kyukong Feb 15, 2022
d2cdbc3
fix(Cars): 생성자 내 생성자 호출 시 this 로 수정
kyukong Feb 15, 2022
b18a5ca
refactor: Car 객체의 toString 메소드 오버라이딩 삭제
kyukong Feb 15, 2022
c2be287
Merge branch 'kyukong' of https://github.com/woowacourse/java-racingc…
kyukong Feb 16, 2022
6d79eba
refactor(Calculator): 입력받은 문자열이 빈값일 경우 문자열 변경
kyukong Feb 16, 2022
4203c14
refactor: view 에서 model 에 접근하지 않도록 수정
kyukong Feb 17, 2022
63556a8
refactor: 각 라운드별 자동차 현재 위치 출력 로직 수정
kyukong Feb 17, 2022
8af5cec
refactor(Car): 자동차 이름, 위치를 입력받는 생성자 추가
kyukong Feb 17, 2022
3847bc1
refactor: 테스트 파일에서 사용하던 private 메소드 삭제
kyukong Feb 17, 2022
ec7b11c
test(CarsTest): 랜덤값 범위 테스트 삭제
kyukong Feb 17, 2022
18d359b
refactor: 객체의 정보를 쉽게 알기 위해 toString() 추가
kyukong Feb 19, 2022
d183a03
refactor(Calculator): 빈값을 치환하는 값 상수화
kyukong Feb 19, 2022
802b5a0
refactor: 빈 값을 포함하여 입력할 경우의 연산 방식 안내
kyukong Feb 19, 2022
fcc2b4f
refactor: 생성자 오버로딩 시 주 생성자를 호출하도록 수정
kyukong Feb 19, 2022
9d1b043
refactor: 직관적인 이름으로 메소드명 변경
kyukong Feb 19, 2022
57c11ce
refactor: 랜덤 값에 대한 테스트 가능하도록 수정
kyukong Feb 19, 2022
8a98f49
refactor: 자동차들의 현재 위치 출력 방식 수정
kyukong Feb 19, 2022
daae066
refactor(Cars): cars 의 값을 재할당하지 못하도록 수정
kyukong Feb 19, 2022
f6f715d
refactor: 각 라운드 결과를 저장하는 객체 생성하여 코드 수정
kyukong Feb 20, 2022
eb20782
refactor(Movable): 메인 메소드 반환값 변경
kyukong Feb 20, 2022
178d770
refactor: 메소드 내에서 파라미터 값이 변경되지 않도록 수정
kyukong Feb 21, 2022
64b3c4d
chore: .gitignore 파일 설정
kyukong Feb 21, 2022
4100c4d
refactor: 생성자 오버로딩 시 호출하는 방향 수정
kyukong Feb 21, 2022
412e0c2
refactor: service 를 controller 로 수정
kyukong Feb 21, 2022
c05f462
refactor: Car 의 이름과 위치를 받는 생성자 삭제
kyukong Feb 21, 2022
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ out/

### VS Code ###
.vscode/

### mac ###
.DS_Store
35 changes: 22 additions & 13 deletions src/main/java/calculator/Calculator.java
Original file line number Diff line number Diff line change
@@ -1,44 +1,53 @@
package calculator;

import calculator.view.Output;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Calculator {
private static final int WRONG_VALUE_RESULT = 0;
private static final int CUSTOM_DELIMITER_PART = 1;
private static final int NUMBER_PART = 2;
public static final String EMPTY_VALUE = "0";

public int splitAndSum(final String text) {
String calculatorText = replaceEmpty(text);
return sum(getSplitNumbers(calculatorText));
}

public int splitAndSum(String text) {
private String replaceEmpty(final String text) {
if (isEmpty(text)) {
return WRONG_VALUE_RESULT;
Output.emptyValueOfSum();
return EMPTY_VALUE;
}
return sum(getSplitNumbers(text));
return text;
}

private boolean isEmpty(String text) {
private boolean isEmpty(final String text) {
return text == null || text.trim().isEmpty();
}

private int[] getSplitNumbers(String text) {
private int[] getSplitNumbers(final String text) {
String delimiter = ",|:";
String splitText = text;

Matcher m = Pattern.compile("//(.)\n(.*)").matcher(text);
if (m.find()) {
delimiter += "|" + m.group(CUSTOM_DELIMITER_PART);
text = m.group(NUMBER_PART);
splitText = m.group(NUMBER_PART);
}
return stringToIntArray(text.split(delimiter));
return stringToIntArray(splitText.split(delimiter));
}

private int[] stringToIntArray(String[] strings) {
private int[] stringToIntArray(final String[] strings) {
return Arrays.stream(strings)
.filter(this::isValidNumber)
.map(Integer::parseInt)
.mapToInt(Integer::intValue).toArray();
}

private boolean isValidNumber(String string) {
private boolean isValidNumber(final String string) {
if (isEmpty(string)) {
return false;
}
Expand All @@ -48,11 +57,11 @@ private boolean isValidNumber(String string) {
return true;
}

private boolean isNumber(String string) {
private boolean isNumber(final String string) {
return string.matches("[+-]?\\d*?(\\.\\d+)?");
}

private int sum(int[] numbers) {
private int sum(final int[] numbers) {
if (numbers.length == 1) {
return numbers[0];
}
Expand All @@ -64,7 +73,7 @@ private int sum(int[] numbers) {
return result;
}

private void checkNegative(int number) {
private void checkNegative(final int number) {
if (number < 0) {
throw new RuntimeException("자연수를 입력해주세요.");
}
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/calculator/view/Output.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package calculator.view;

public class Output {
private static void info(final String message) {
System.out.println("[INFO] " + message);
}

public static void emptyValueOfSum() {
info("빈 값은 0으로 바뀌어 계산됩니다.");
}
}
2 changes: 1 addition & 1 deletion src/main/java/racingcar/Application.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package racingcar;

import racingcar.service.RacingCarGame;
import racingcar.controller.RacingCarGame;

public class Application {
public static void main(String[] args) {
Expand Down
61 changes: 61 additions & 0 deletions src/main/java/racingcar/controller/RacingCarGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package racingcar.controller;

import racingcar.domain.Attempt;
import racingcar.domain.Cars;
import racingcar.domain.RoundResult;
import racingcar.view.Input;
import racingcar.view.Output;

import java.util.List;

public class RacingCarGame {
private Cars cars;
private Attempt attempt;
private final RoundResult roundResult = new RoundResult();

private final Input input;

public RacingCarGame() {
input = new Input();

Choose a reason for hiding this comment

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

정답은 없지만, 내부에서 선언하여 같은 값을 사용한다면 위의 RoundResult처럼 같이 선언과 동시에 초기화하거나 객체 생성자에서 초기화하는 일관된 형태를 띄면 좋을 것 같습니다.

}

public void run() {
init();
round();
win();
}

@Override
public String toString() {

Choose a reason for hiding this comment

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

메소드 위치의 절대적으로 옳은 위치는 없지만, toString이 단순히 값을 편히 보기위한 편의기능이라면 아래의 private메소드들이 이 객체의 역할을 더 드러내는 것으로 보여 프라이빗과 투스트링의 위치를 수정하는건 어떨까요?

return "- cars\n" + cars + "- " + attempt;
}

private void init() {
cars = createCars(input.carName());
attempt = createAttempt(input.attempt());
}

private Cars createCars(final String carName) {

Choose a reason for hiding this comment

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

요 부분은 취향차이긴 하지만, 저는 new Cars(carName) 자체만으로도 createCars라는 의미를 충분히 전달하는 것 같습니다.

return new Cars(carName);
}

private Attempt createAttempt(final String attempt) {

Choose a reason for hiding this comment

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

ditto

return new Attempt(attempt);
}

private void round() {
Output.resultTitle();

int nowAttempt = 0;
while (!attempt.isSame(nowAttempt++)) {
cars.play(roundResult);
roundResult.roundEnd();
}
Output.result(roundResult);
}

private void win() {
List<String> winners = cars.findWinners();
Output.showResult(winners);
}
}
36 changes: 19 additions & 17 deletions src/main/java/racingcar/domain/Attempt.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,45 @@
public class Attempt {
private final int attempt;

public Attempt(int attempt) {
public Attempt(final String attempt) {
this(stringToInt(attempt));
}

public Attempt(final int attempt) {
if (isNegative(attempt)) {
throw new IllegalArgumentException("시도횟수는 1이상의 수를 입력해주세요.");
}
this.attempt = attempt;
}

public Attempt(String attempt) {
checkValid(attempt);
this.attempt = Integer.parseInt(attempt);
}

public boolean isSame(int nowAttempt) {
public boolean isSame(final int nowAttempt) {
return attempt == nowAttempt;
}

private void checkValid(String attempt) {
@Override
public String toString() {
return "attempt : " + attempt;
}

private static int stringToInt(final String text) {
checkValid(text);
return Integer.parseInt(text);
}

private static void checkValid(final String attempt) {
if (attempt.isBlank()) {
throw new IllegalArgumentException("반복 횟수는 %s일 수 없습니다.");
}
if (!isNumber(attempt)) {
throw new IllegalArgumentException("시도횟수는 숫자를 입력해주세요.");
}
if (isNegative(attempt)) {
throw new IllegalArgumentException("시도횟수는 1이상의 수를 입력해주세요.");
}
}

private boolean isNumber(String string) {
private static boolean isNumber(final String string) {
return string.matches("[+-]?\\d*(\\.\\d+)?");
}

private boolean isNegative(int number) {
private static boolean isNegative(final int number) {
return number <= 0;
}

private boolean isNegative(String string) {
return Integer.parseInt(string) <= 0;
}
}
29 changes: 17 additions & 12 deletions src/main/java/racingcar/domain/Car.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,17 @@ public class Car implements Comparable<Car> {
private final String name;
private int position = 0;

public Car(String name) {
checkValid(name);
public Car(final String name) {
checkValidName(name);

Choose a reason for hiding this comment

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

생성자 오버로딩을 사용할 때는 부생성자들이 항상 마지막엔 주 생성자를 호출하는 형태가 일반적인데요.

그 이유에 대해 고민해보면 좋을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

중복되는 코드를 줄일 수 있고 객체 정보가 수정될 경우 모든 생성자를 전부 수정해야 하기 때문에 생성자를 타고 들어가는 형태를 사용하는 것 같습니다. 수정했습니다!

Choose a reason for hiding this comment

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

잘 수정해주신 것 같습니다. 조금 더 이어서, 두 생성자 중 현재는 2번째 생성자가 1번 생성자를 호출하고 있는데, 이 방향이 맞을까요?

Copy link
Author

Choose a reason for hiding this comment

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

호출하는 방향을 바꾸었으나 왜 방향을 바꾸는 것이 좋은지는 모르겠습니다.🥲 파라미터의 개수가 많은 생성자가 더 적은 생성자를 호출 후 이후에 나머지 값을 대입하는 방식은 중요 변수를 대입하는 과정을 놓칠 수 있기 때문인가요??

Choose a reason for hiding this comment

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

제가 말씀드린 이유는 각 파라미터에 대한 일관된 검증을 하기 위함인데요. String name만 가지고 생성하는 생성자의 경우 Position 에 대한 Validation 을 수행하지 않기에 발생할 수 있는 이슈들을 제거하기 위해 호출의 방향을 변경하는 것이 좋을 것 같다고 생각했습니다.

Copy link
Author

Choose a reason for hiding this comment

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

name 만으로 생성자를 호출할 경우 검증이 필요없는 0이 position 에 대입되므로 검증이 필요 없을 거라 생각했습니다. 혹시나 다른 개발자가 초깃값을 수정할 가능성이 있기에 잘못된 객체 생성을 막기 위해서는 검증이 포함되는 구조로 바꾸는 것이 좋겠군요. 테스트를 위한 생성자를 삭제하면서 해당 생성자는 삭제했지만 다음 미션때는 그 부분을 고려하도록 하겠습니다. 감사합니다!

this.name = name;
}

public void drive(boolean directing) {
if (directing) {
public void drive(final Movable movable) {
if (movable.isMoving()) {
move();
}
}

public boolean isSamePosition(Car other) {
return this.position == other.position;
}

public String getName() {
return name;
}
Expand All @@ -29,17 +25,26 @@ public int getPosition() {
return position;
}

public boolean isSamePosition(final Car other) {
return this.position == other.position;
}

@Override
public int compareTo(Car car) {
public int compareTo(final Car car) {
return this.position - car.position;
}

private void checkValid(String names) {
@Override
public String toString() {
return "name : " + name + ", position : " + position;

Choose a reason for hiding this comment

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

2단계 요구사항에서 domain 패키지의 객체는 view 패키지 객체에 의존하지 않는다. 요게 있는 것으로 알고 있어요.

이 요구사항이 의미하는 바를 조금 더 깊게 고민해보면 좋을 것 같아요. 단순히 패키지 의존만을 의미하는 것인지, 아니면 도메인 패키지가 뷰의 로직을 알면 안된다인지 생각해보면 좋을 것 같아요.

}

private void checkValidName(final String names) {
checkBlank(names);
checkNameLength(names);
}

private void checkBlank(String name) {
private void checkBlank(final String name) {
String text = "자동차 이름은 %s일 수 없습니다.";
if (name == null) {
throw new NullPointerException(String.format(text, "null"));
Expand All @@ -49,7 +54,7 @@ private void checkBlank(String name) {
}
}

private void checkNameLength(String name) {
private void checkNameLength(final String name) {
if (!(name.trim().length() <= CAR_LENGTH_LIMIT)) {
throw new IllegalArgumentException("자동차의 이름은 " + CAR_LENGTH_LIMIT + "글자를 초과할 수 없습니다.");
}
Expand Down
Loading