Skip to content

[자동차 경주] 제세형 미션 제출합니다. #72

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

Merged
merged 15 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import domain.Race;
import view.InputView;
import view.ResultView;

public class Application {

private static Race race;
private static int round;
public static void main(String[] args) {
while(true){
if (getValiedNames()){
break; //5글자를 넘는 이름을 입력받는 경우 재시도
}
}
Comment on lines +10 to +14

Choose a reason for hiding this comment

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

재시도 로직 ! 저도 생각도 못해 본 부분이네요 👀

다음 같은 방법이 있을 것 같아요.

  1. 메소드를 세세하게 더 쪼갠다. (이 부분은 작성해보지 못 했지만, 코드가 쉽지 않아질 것 같다는 생각이 드네요.)
  2. do-while문을 사용한다. (이렇게 되면 뎁스가 1이하가 되고, 재시도로직도 잘 작동할 것 같아요!)
Suggested change
while(true){
if (getValiedNames()){
break; //5글자를 넘는 이름을 입력받는 경우 재시도
}
}
String carNames;
do{
carNames = InputView.getCarNames();
} while(!getValiedNames(carNames));

Copy link
Author

Choose a reason for hiding this comment

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

do-while이 있었네요! 맨날 for, while만 쓰고 do-while은 정말 쓰는 케이스가 적어서 생각을 못했습니다 👍

round = InputView.getTryCount();
runRace();
ResultView.printWinners(race.getWinner());
}

private static boolean getValiedNames() {
try{
race = new Race(InputView.getCarNames());
return true;
}catch (IllegalArgumentException e){
System.out.println(e.getMessage());

Choose a reason for hiding this comment

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

세형님은 Application 클래스를 컨트롤러로 사용하신 것 같아요 !

컨트롤러에 시스템 아웃풋과 관련된 로직이 있어, 이 부분은 View로 옮기면 좋을 것 같아요 !

Copy link
Author

Choose a reason for hiding this comment

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

콘솔과 관련된 모든 로직은 View에서만 구현할 수 있게 수정해보겠습니다!

}
return false;
}

private static void runRace(){
System.out.println("domain.Race result");
race.runRace(round);
Comment on lines +30 to +32

Choose a reason for hiding this comment

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

여기도 동일합니다 !

레이싱 결과를 출력하기 위한 View만 존재해야 하는지 등 생각해보면 좋을 것 같아요.

}
}
47 changes: 47 additions & 0 deletions src/main/java/domain/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package domain;

public class Car {

Choose a reason for hiding this comment

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

a6025cd 커밋을 보니, setLocation() 메소드 삭제하셨더라구요.

커밋 메시지까지 보니 삭제하신 의도가 좋았습니다 👍

private String name;
private int location;

public Car(String name) throws IllegalArgumentException {
this.setName(name);
this.location = 0;
}

//private 속성들에 대한 getter 및 setter
public String getName() {
return name;
}
public void setName(String name) throws IllegalArgumentException {
if(name.length()>5){
throw new IllegalArgumentException("name is must be less than 5 characters");
}
this.name = name;
}
public int getLocation() {
return location;
}

//랜덤으로 speed 값을 생성하는 대신, speed를 파라미터로 입력받아 자동차를 이동
public void move(int speed) throws IllegalArgumentException {
if(speed > 9 || speed < 0){
throw new IllegalArgumentException("speed is must be between 0 and 9");
Comment on lines +28 to +29

Choose a reason for hiding this comment

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

속도 예외 처리 굳굳

}
this.location = calculateNextLocation(speed);

}

//speed값을 기준으로 다음 위치 계산
private int calculateNextLocation(int speed){
if(speed >= 4){
return this.location+1;
}
return this.location;
}
Comment on lines +36 to +41

Choose a reason for hiding this comment

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

뎁스를 줄이기 위해 메소드로 추출한 부분 좋습니다 👍






Comment on lines +42 to +46

Choose a reason for hiding this comment

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

개행 한 번 만 확인해주세요 !

}
80 changes: 80 additions & 0 deletions src/main/java/domain/Race.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package domain;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class Race {
final private List<Car> cars;
private List<Car> winners;

//domain.Race 생성자 : ","를 이용해 구분하여 레이스에 참가하는 차량의 이름을 입력
public Race(String nameOfCars) throws IllegalArgumentException {
String[] carNames = nameOfCars.split(",");

cars = new ArrayList<>();
for (String carName : carNames) {
cars.add(new Car(carName));
}
}

//getter
public List<Car> getWinner() {
return winners;
}

//레이스 실행
public void runRace(int round){
//주어진 횟수동안 이동
for(int i=0; i<round; i++){
moveAllCars();
}

//승자 구하기
checkWinners(calcMaxLocation());
}

//모든 차량 이동
private void moveAllCars(){
for(Car car: cars){
car.move(new Random().nextInt(10));
printCarProgress(car);
}
System.out.println();
}

private void printCarProgress(Car car){
Comment on lines +42 to +46

Choose a reason for hiding this comment

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

차량을 한단계씩 움직이면서 자동차의 진행 상황을 표시하는 것을 view 패키지 의존 없이 구현하는 방법을 찾지 못했습니다..

다양한 방법이 있겠지만, 레이싱을 한 번 수행한 후, 컨트롤러가 레이싱 현재 상황을 게터로 얻고, 뷰로 전달해서 출력하는 방법도 있을 것 같아요.

이를 위해서는 현재 컨트롤러에 있는 코드와 Race에 있는 코드를 많이 수정할 필요가 있지만, 하나를 배워간다는 점에서 좋은 의의가 있을 것 같아요 👍

Copy link
Author

Choose a reason for hiding this comment

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

한번 머리좀 싸매야 할 것 같습니다 😢
Race 클래스 안에 단계를 한단계씩 진행시키는 메서드를 만들고, 컨트롤러에서 해당 메서드를 호출하면서 현재 상황을 출력하는 메서드를 View에 구현해봐야 할것 같아요.

System.out.print(car.getName() + " : ");
for(int i = 0; i < car.getLocation(); i++){
System.out.print("-");
}
System.out.println();
}

//최대 거리 구하기
private int calcMaxLocation(){
int maxLocation = -1;
for(Car car:cars){
maxLocation = Math.max(maxLocation, car.getLocation());
}

return maxLocation;
}

//승자 구하기
private void checkWinners(int maxLocation){
winners = new ArrayList<>();

for(Car car: cars){
addWinners(car, maxLocation);
}
}

//승자 추가하기
private void addWinners(Car car, int maxLocation){
if(car.getLocation() == maxLocation){
winners.add(car);
}
}
Comment on lines +64 to +78

Choose a reason for hiding this comment

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

checkWinners이라는 메소드명과 다르게 void를 반환하고 있습니다 !
아래 작성된 addWinners의 메소드의 if 조건이 check에 더 가까운 느낌도 있습니다.

addWinners 메소드를 checkWinners로 변경 후 boolean을 반환하는 방식으로 변경하고, checkWinners 메소드를 addWinners로 변경 후 리스트에 넣는 방식을 사용한다면 메소드 명과 반환형을 일치시킬 수 있을 거 같다는 생각이 듭니다 !

Suggested change
//승자 구하기
private void checkWinners(int maxLocation){
winners = new ArrayList<>();
for(Car car: cars){
addWinners(car, maxLocation);
}
}
//승자 추가하기
private void addWinners(Car car, int maxLocation){
if(car.getLocation() == maxLocation){
winners.add(car);
}
}
private void addWinners(Car car, int maxLocation){
if (checkWinners(car, maxLocation)) {
winners.add(car);
}
}
private boolean checkWinners(Car car, int maxLocation){
return car.getLocation() == maxLocation;
}

Copy link
Author

Choose a reason for hiding this comment

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

메서드 명명이 제일 어려운 것 같아요 😢
메소드명과 필요인자만 보고서도 메서드가 하는 일과 반환형을 추론할 수 있도록 작성해보겠습니다!


}
18 changes: 18 additions & 0 deletions src/main/java/view/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package view;

import java.util.Scanner;

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

Choose a reason for hiding this comment

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

fianl staticstatic final가 큰 차이는 없지만, 자바에서는 static final를 공식 컨벤션으로 사용한다고 하네요 !

Copy link
Author

Choose a reason for hiding this comment

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

확인했습니다 👀


public static String getCarNames(){

Choose a reason for hiding this comment

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

뷰 관련 메소드를 static 메소드로 선언하신 이유가 궁금합니다 !

Copy link
Author

Choose a reason for hiding this comment

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

4단계 - 리펙터링 힌트에서 InputView.getCarNames(); 로 별도 인스턴스 생성 없이 클래스에서 바로 호출하기도 했었고, 처음 코드를 작성할 때 Application 클래스 안에서 작성해놓고 나중에 View로 옮기면서 생각없이 그냥 그대로 놔둔거기도 합니다..

그런데 조금 찾아보니 객체 지향에서 정적 메서드가 갖는 단점이 몇가지 있는 것 같아요..! Mocking을 통한 테스트가 불가능 하다는 점과 다형성을 사용할 수 없는 단점이 있는 것 같습니다. 😨

굳이 static을 사용하지 않고도 Application 안에서 지역변수나 필드로 인스턴스화 하여 메서드 호출을 해도 문제가 없기 때문에 static을 사용하지 않는 쪽으로 수정하는게 좋을 것 같기도 합니다 👀

System.out.println("Enter names of cars which join the race (Names are separated by commas)");
return scanner.nextLine();
}

public static int getTryCount(){
System.out.println("Enter rounds of race");
return scanner.nextInt();
}

}
17 changes: 17 additions & 0 deletions src/main/java/view/ResultView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package view;

import domain.Car;

import java.util.ArrayList;
import java.util.List;

public class ResultView {
public static void printWinners(List<Car> winners) {
List<String> winnerNames = new ArrayList<>();
for (Car winner : winners) {
winnerNames.add(winner.getName());
}
Comment on lines +10 to +13

Choose a reason for hiding this comment

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

winners에 있는 Car의 name을 게터로 얻어서 출력을 한다면, view에서 리스트에 넣는 로직을 수행하지 않아도 될 것 같아요 !

String result = String.join(", ", winnerNames) + " is(are) winners";
System.out.println(result);
}
}
58 changes: 58 additions & 0 deletions src/test/java/TestCar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import domain.Car;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Nested;

import java.util.Random;

import static org.assertj.core.api.Assertions.*;

@DisplayName("움직이는 자동차 테스트")
public class TestCar {

@Nested
@DisplayName("움직이는 자동차 기능테스트")
class TestCarFeatures{

@Test
@DisplayName("이동 테스트")
void testMove(){
//given
final Car testCar = new Car("test");

//when
//자동차 이동시키기
int testSpeed = new Random().nextInt(10);
testCar.move(testSpeed);

//then
if(testSpeed >= 4){
assertThat(1).isEqualTo(testCar.getLocation());
return;
}
assertThat(0).isEqualTo(testCar.getLocation());
}
}

@Nested
@DisplayName("움직이는 자동차 예외테스트")
class TestCarExceptions{

@Test
@DisplayName("이동 예외 테스트")
void testMoveException(){
//given
final Car testCar = new Car("test");

//when
assertThatThrownBy(()-> testCar.move(-1)).hasMessage("speed is must be between 0 and 9");
assertThatThrownBy(()-> testCar.move(10)).hasMessage("speed is must be between 0 and 9");
}

@Test
@DisplayName("차량 생성 예외 테스트")
void testCarCreationException(){
assertThatThrownBy(()-> new Car("123456")).hasMessage("name is must be less than 5 characters");
}
}
}
40 changes: 40 additions & 0 deletions src/test/java/TestRace.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import domain.Race;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Nested;

import static org.assertj.core.api.Assertions.*;

@DisplayName("자동차 경주 테스트")
public class TestRace {

@Nested
@DisplayName("자동차 경주 기능 테스트")
class TestRaceFeatures{

@Test
@DisplayName("우승 자동차 구하기 테스트")
void testRace(){

Choose a reason for hiding this comment

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

테스트 코드에서는 내가 작성한 코드가 요구 사항을 만족 시켰는지, 코드를 수정한 이후 요구사항을 만족하고 있는지 확인하는 용도로도 사용할 수 있어요. 이 테스트 코드에서 요구 사항을 만족 시켰는 지 확인할 수 있는 방법은 조금 부족하다고 생각이 듭니다.

코드에서는 최소 거리를 이동한 사람이 우승하도록 작성하였지만, 주어진 요구사항은 최대 거리를 이동한 사람이 우승하는 것이라면 이 테스트에서는 이를 검증하기 힘들 것 같아요.

어떻게 하면 이를 입증할 수 있을까요?? 게터를 이용해서 레이싱에 참여하는 자동차에 move함수를 호출해서 임의로 이동 거리를 설정하는 방법도 있을 것 같아요 !

//given
final Race testRace = new Race("test1,test2,test3");

//when
//5회 동안 경주
testRace.runRace(5);

//then
assertThat(testRace.getWinner().get(0)).isNotNull();
}
}

@Nested
@DisplayName("자동차 경주 예외 테스트")
class TestRaceExceptions{

@Test
@DisplayName("경주 생성 예외 테스트")
void testRaceCreationException(){
assertThatThrownBy(() -> new Race("1234,12345,123456")).hasMessage("name is must be less than 5 characters");
}
}
}