[연습] 자동차 경주#514
Conversation
자동차 이름을 부여하는 부분은 자동차쪽으로 수정
0보다 작으면 -> 0 이하면
MIN_FUEL -> MIN_MOVE_FUEL 최소 연료 = MIN_FUEL
CarGameTest -> 이동횟수_등록_실패_테스트
레이스를 이동횟수 만큼 실행 -> 레이스를 1회만 실행하고 이동횟수를 차감하도록
|
|
||
|
|
||
| public void moveOrStop(int fuel) { | ||
| validateFuel(fuel); |
There was a problem hiding this comment.
Fuel 부분은 Randoms 라이브러리를 사용하는데,
검증 책임은 라이브러리를 만든 쪽에 있다고 생각해서
라이브러리를 사용하는 부분은 검증하지 않아도 될 것 같습니다!
| // 추가 기능 구현 | ||
| private void validateName(String name) { | ||
| if (name.length() > MAX_NAME_LENGTH) { | ||
| throw new IllegalArgumentException(Message.CAR_NAME_VALIDATION); |
There was a problem hiding this comment.
저는 예외 메세지는 추후에도 수정할 일이 없고,
여러 곳이 아닌 해당하는 하나의 예외에만 사용되기 때문에
가독성을 위해 상수화하지 않고 있습니다!
다른 분들의 의견은 어떨지 모르겠네요!
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public class CarGame { |
There was a problem hiding this comment.
'CarGame'이라고 네이밍을 하면 어떤 역할을 가지는 객체인지
처음에 알기 힘들 것 같아요!
어떤 책임을 가지는 객체인지 알기 쉽게 네이밍 해주면 좋을 것 같습니다!
추가적으로 CarGame이 너무 많은 역할을 하고 있는 것 같아요.
MVC 패턴을 참고해서 클래스 분리를 하는 것이 좋을 것 같아요!
| } | ||
| } | ||
|
|
||
| public List<Car> getCars() { |
There was a problem hiding this comment.
Collection에 getter를 사용할 때 그냥 Collection 자체를 return하게 되면
외부에서 Collection을 받아서 Collection 내부의 값을 변경할 수도 있으므로
Collections.unmodifiableList(cars)로 외부에서 내부의 값을 변경 못하도록 막아서 반환하는 게 좋을 것 같습니다!
There was a problem hiding this comment.
감사합니다 이 부분은 처음 알았네요!
적용해보겠습니다~!
| for (Car car : cars) { | ||
| car.moveOrStop(getRandomFuel()); | ||
| } | ||
| moveCounts--; |
There was a problem hiding this comment.
시도 횟수를 하나씩 줄여가면서 하는 것보다
for문으로 하나씩 늘려가면서 하는 게 가독성에 더 좋을 것 같습니다!
|
|
||
| public List<String> announceWinners() { | ||
| return cars.stream() | ||
| .filter(car -> car.getCurrentPosition() == cars.stream().map(Car::getCurrentPosition) |
There was a problem hiding this comment.
저도 Car에 Position을 얻기 위해 getter를 사용해서 받아왔었는데,
getter를 사용하는 대신 객체에 메시지를 보내자
이후에 생각나서 찾아보니
이 링크를 참고해서 Car 객체 내부에서 비교를 하도록 하는 것이 좋을 것 같습니다!
| private final InputView inputView; | ||
| private final OutputView outputView; | ||
|
|
||
| public MainController() { |
There was a problem hiding this comment.
클래스 변수로 받을 때 new로 생성 안하고,
생성자로 MainController가 생성될 때
new로 생성하는 이유가 따로 있나요?!
There was a problem hiding this comment.
딱히 이유는 없었던거 같아요.
스프링 프로젝트를 할 때 생성자 주입을 사용하다보니 습관적으로 한거 같아요!
| int moveCounts = inputView.inputMoveCounts(); | ||
| carGame.registerMoveCounts(moveCounts); | ||
| } catch (IllegalArgumentException e) { | ||
| outputView.printError(e.getMessage()); |
There was a problem hiding this comment.
catch에서 Error 메세지를 출력할 때,
저는 그냥 System.out.println으로 출력했는데
OutputView에 에러를 출력하는 기능을 만들어서
OutputView에게 에러 출력 책임을 맡기게 하는 것 잘 배워갑니다!
| private static final String ONLY_NUMBER_REGEX = "^[0-9]+$"; | ||
|
|
||
| public List<String> inputCars() { | ||
| System.out.println(Message.INPUT_CAR_MESSAGE); |
There was a problem hiding this comment.
입력 문구를 출력하는 것도 출력하는 책임이라고 생각해서
출력 처리를 하는 책임을 가진 OutputView에서 처리하는 것이 좋을 것 같습니다!
There was a problem hiding this comment.
흠..저도 프리코스 진행할 때 고민했었는데
저는 View라는 자체가 사용자에게 보여지는 계층이다 보니
입력에 필요한 출력은 어쩔수 없이 InputView에서 담당하는게 맞다고 생각했습니다.
There was a problem hiding this comment.
InputView 클래스 자체가 입력에 관한 책임을 가지기 때문에 저도 입력에 필요한 출력은 InputView에서 담당하는것이 좀 더 낫다고 생각합니다 !
| System.out.println(Message.OUTPUT_RACE_RESULT); | ||
| } | ||
|
|
||
| private void printCar(Car car) { |
There was a problem hiding this comment.
포맷으로 출력하는 System.put.printf를 몰랐었는데
건호님 코드 보고 알게 되었습니다! 감사합니다
|
|
||
| private String getMove(int currentPosition) { | ||
| StringBuilder stringBuilder = new StringBuilder(); | ||
| for (int i = 0; i < currentPosition; i++) { |
| @DisplayName("성공 테스트") | ||
| void 자동차_등록_성공_테스트() { | ||
| //when | ||
| List<String> cars = Arrays.asList("pobi", "woni", "jun"); |
There was a problem hiding this comment.
자바 8이라 List.of를 못 써서 직접 List에 add 했었는데,
Arrays.asList가 있었군요! 배워갑니당
There was a problem hiding this comment.
저도 처음에 List.of를 사용하려고 했는데 자바 8이라 안되서 당황했는데
Arrays.asList는 사용 가능하더라구요..!
|
|
||
| @Test | ||
| @DisplayName("실패시 에러발생") | ||
| void 자동차_등록_실패_테스트() { |
There was a problem hiding this comment.
정확하게 어떤 이유 때문에 실패하는 테스트인지 네이밍해주는 게 좋을 것 같아요!
'자동차_이름이_비었을_경우_테스트_실패()' 이런 식으로
구체적으로 적어주는 게 좋을 것 같습니다!
|
|
||
| @Test | ||
| @DisplayName("이동횟수 등록 실패 테스트") | ||
| void 이동횟수_등록_실패_테스트() { |
There was a problem hiding this comment.
마찬가지로 '이동_횟수가_0이면_등록_실패' 느낌으로
구체적으로 네이밍 해주시면 좋을 것 같습니다.
또, 테스트할 것이 이동 횟수가 0일때, 음수일 때, 숫자가 아닐 때 등등 여러 조건이
있으므로 @ParameterizedTest와 @ValueSource를 사용해서
하나의 메소드에 여러 조건을 추가해서 테스트하는 것이 좋을 것 같습니다!
sh111-coder
left a comment
There was a problem hiding this comment.
코드 잘 봤습니다!!
기능 목록도 가독성 좋게 잘 읽히는 것 같습니다!
전체적으로 하나의 클래스가 여러 책임을 가지는 것 같아서
MVC 패턴으로 클래스가 하나의 책임만을 가지게 클래스를 분리하면 좋을 것 같아요!
drunkenhw
left a comment
There was a problem hiding this comment.
코드 잘봤습니다. 제 실력이 부족해 리뷰에 대해 의문점이나 아닌 것 같은 점이 있으면 말씀해주시면 감사하겠습니다.
| // 추가 기능 구현 | ||
| private void validateName(String name) { | ||
| if (name.length() > MAX_NAME_LENGTH) { | ||
| throw new IllegalArgumentException(Message.CAR_NAME_VALIDATION); |
| for (String carName : cars) { | ||
| this.cars.add(new Car(carName)); | ||
| } |
There was a problem hiding this comment.
| for (String carName : cars) { | |
| this.cars.add(new Car(carName)); | |
| } | |
| this.cars = cars.stream() | |
| .map(Car::new) | |
| .collect(Collectors.toList()); |
이런식으로 map을 사용하는 코드는 어떠신가요?
There was a problem hiding this comment.
이 부분에 대해서는 stream으로 처리할 생각을 못했는데
사용하는 것도 괜찮은거 같습니다.
| } | ||
| } | ||
|
|
||
| public List<Car> getCars() { |
| } | ||
|
|
||
| public boolean checkIsOver() { | ||
| return moveCounts <= 0; |
There was a problem hiding this comment.
0보다 작은 경우라고 표현하는 것보다 확실이 0일 경우 끝난다고 표현하는 것이 좋아보입니다
There was a problem hiding this comment.
확실히 우석님이 말씀하신대로 작성하는게 side effect 가 없을 거 같네요!
감사합니댜!
| if (checkIsOver()) { | ||
| throw new IllegalStateException(Message.GAME_OVER); | ||
| } |
There was a problem hiding this comment.
MainController에서 while (!carGame.checkIsOver()) 하면서 반복하고 있는데 if문으로 한번 더 검증하는 로직은 불필요할 것 같아 보입니다!
| String[] inputCars = input.split(CAR_REGEX); | ||
| return Arrays.stream(inputCars).collect(Collectors.toList()); |
There was a problem hiding this comment.
| String[] inputCars = input.split(CAR_REGEX); | |
| return Arrays.stream(inputCars).collect(Collectors.toList()); | |
| return Arrays.stream(input.split(CAR_REGEX)).collect(Collectors.toList()); |
한번 밖에 쓰이지 않는 변수를 할당하는 것보다 합치는 것이 어떨까요?
| for (String inputCar : inputCars) { | ||
| if (inputCar.length() > Car.MAX_NAME_LENGTH) { | ||
| throw new IllegalArgumentException(Message.CAR_NAME_VALIDATION); | ||
| } | ||
| } |
There was a problem hiding this comment.
자동차의 이름에 대한 유효성 검증은 Car 객체에서 하는 것이 좋아보이고 실제로 Car 클래스에서 하시고 계셔서 없어져도 좋을 코드 같습니다!
There was a problem hiding this comment.
다시보니 검증에 대한 중복이 많네요!
신경써야할 거 같습니다 감사합니다~
| for (Car car : cars) { | ||
| printCar(car); | ||
| } | ||
| printNewLine(); |
There was a problem hiding this comment.
이런식으로 메서드를 만드는 것이 좋아보이네요 배워갑니다!
|
|
||
| @Test | ||
| @DisplayName("성공 테스트") | ||
| void 자동차_등록_성공_테스트() { |
There was a problem hiding this comment.
@DisplayName이 있는데 메서드명을 한글로 하신 이유가 궁금합니다
There was a problem hiding this comment.
처음에 테스트 코드를 작성할 때 한글로 작성하고 계층형 구조로 리팩토링할 때 기존 메서드 명으로 가져갔습니다 !!
|
|
||
|
|
||
| public void moveOrStop(int fuel) { | ||
| validateFuel(fuel); |
| if (fuel >= MIN_MOVE_FUEL) { | ||
| move(); | ||
| } | ||
| } |
There was a problem hiding this comment.
도메인 함수 내부에서 다른 도메인 함수를 호출하는 것 보다
boolean type을 반환하도록 moveOrStop 함수를 변경해 접근해보면 어떤지 제안드립니다 !
| public CarGame() { | ||
| cars = new ArrayList<>(); | ||
| } | ||
|
|
||
| public void registerCar(List<String> cars) { | ||
| validateCars(cars); | ||
| for (String carName : cars) { | ||
| this.cars.add(new Car(carName)); | ||
| } |
There was a problem hiding this comment.
Car 객체들을 registerCar 함수가 아닌, 컨트롤러에서 생성해 생성자 주입 방식을 사용하는 방법을 제안드립니다 !
|
|
||
| private void startRace() { | ||
| outputView.printRaceStart(); | ||
| while (!carGame.checkIsOver()) { |
There was a problem hiding this comment.
"게임을 종료할 수 없는 동안" 이라는 조건보다, "게임이 진행되는 동안"으로 표현한다면 좀 더 직관적일 것 같습니다.
| @@ -0,0 +1,39 @@ | |||
| # 🚗 구현 기능 목록 | |||
| public void registerMoveCounts(int moveCounts) { | ||
| validateMoveCounts(moveCounts); | ||
| this.moveCounts = moveCounts; | ||
| } |
There was a problem hiding this comment.
도메인에서 한번 더 검증을 해줄 필요가 있을까? 라는 생각이 있었는데
결국 계층간의 의존성 없이 개발을 하기 위해서는 확실히 한번 더 검증을 해주는 것이 좋다는 느낌이 드는 것 같습니다 :D
잘 배우고 갑니다 !
🚗 구현 기능 목록
자동차
자동차 경주 게임
입력
출력
동작 순서