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

[자동차 경주] 김민창 4단계 미션 제출합니다. #62

Open
wants to merge 13 commits into
base: idle2534
Choose a base branch
from
Open
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
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
## 움직이는 자동차 [O]
### 기능 요구사항
- 자동차는 이름을 가지고 있다.
- 자동차는 움직일 수 있다.
- 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
### 새로운 프로그래밍 요구사항
- 자동차가 움직이는 기능이 의도대로 동작하는지 테스트한다.
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 Java Style Guide을 원칙으로 한다.
- indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- 3항 연산자를 쓰지 않는다.
- else 예약어를 쓰지 않는다.
- else 예약어를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다.
- 힌트: if문에서 값을 반환하는 방식으로 구현하면 else 예약어를 사용하지 않아도 된다.
- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다.
### 기존 프로그래밍 요구사항
- 메인 메서드는 만들지 않는다.

## 우승 자동차 구하기 [O]
### 기능 요구사항
- n대의 자동차가 참여할 수 있다.
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 구할 수 있다. 우승자는 한 명 이상일 수 있다.
### 새로운 프로그래밍 요구사항
- 우승자를 구하는 기능이 의도대로 동작하는지 테스트한다.

## 게임 실행 [O]
### 기능 요구사항
- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.

Choose a reason for hiding this comment

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

이름은 5자 이하만 가능하다하였는데, 이 부분은 구현이 안 된 것 같아요!

- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 random 값을 구한 후 random 값이 4 이상일 경우 전진하고, 3 이하의 값이면 멈춘다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
<br>**실행 결과**
- 위 요구사항에 따라 3대의 자동차가 5번 움직였을 경우 프로그램을 실행한 결과는 다음과 같다.
```
경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).
neo,brie,brown
시도할 회수는 몇회인가요?
5

실행 결과
neo : -
brie : -
brown : -

neo : --
brie : -
brown : --

neo : ---
brie : --
brown : ---

neo : ----
brie : ---
brown : ----

neo : -----
brie : ----
brown : -----

neo : -----
brie : ----
brown : -----

neo, brown가 최종 우승했습니다.
```

### 새로운 프로그래밍 요구사항
- 메인 메서드를 추가하여 실행 가능한 애플리케이션으로 만든다.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ dependencies {
testImplementation platform('org.assertj:assertj-bom:3.25.1')
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation('org.assertj:assertj-core')
testImplementation("org.mockito:mockito-core:5.14.1")
compileOnly("org.projectlombok:lombok:1.18.34")
annotationProcessor('org.projectlombok:lombok:1.18.34')
}

test {
Expand Down
Empty file removed src/main/java/.gitkeep
Empty file.
19 changes: 19 additions & 0 deletions src/main/java/RacingCarGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import controller.RacingCarController;
import dao.RacingCarDao;
import service.RacingCarService;
import service.RacingCarServiceImpl;
import util.NumberGenerateUtil;
import view.RacingCarView;

public class RacingCarGame {
public static void main(String[] args) {
RacingCarView racingCarView = new RacingCarView();
RacingCarDao racingCarDao = new RacingCarDao();
NumberGenerateUtil numberGenerateUtil = new NumberGenerateUtil();
RacingCarService racingCarService = new RacingCarServiceImpl(racingCarDao, numberGenerateUtil);
RacingCarController game = new RacingCarController(racingCarView, racingCarService);

game.set();
game.run();
}
}
39 changes: 39 additions & 0 deletions src/main/java/controller/RacingCarController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package controller;

import converter.RacingCarConverter;
import lombok.RequiredArgsConstructor;
import service.RacingCarService;
import view.RacingCarView;

@RequiredArgsConstructor
public class RacingCarController {

Choose a reason for hiding this comment

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

민창님이 생각하기에 controller 클래스와 main 클래스는 어떤 차이가 있나요?


final RacingCarView racingCarView;

Choose a reason for hiding this comment

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

객체지향적으로 밖으로 들어날 필요가 없는 변수는 숨기는게 좋습니다.(은닉성)

private으로 숨겨보면 어떨까요?

final RacingCarService racingCarService;

int round;
boolean isGameInit = false;

public void set() {
racingCarService.createRacingCar(racingCarView.inputRacingCarName().stream().map(

Choose a reason for hiding this comment

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

이렇게 괄호안에 너무 긴 stream이 있는건 가독성을 떨어트릴 수 있어요 분리해보는 건 어떨까요?

RacingCarConverter::toRacingCarCreateDto).toList());
round = racingCarView.inputRound();

isGameInit = true;
}

public void run() {
if (!isGameInit)
throw new IllegalStateException("게임이 초기화되지 않았습니다.");

racingCarView.printResultTitle();
for(int currentRound = 0; currentRound < round; ++currentRound){

Choose a reason for hiding this comment

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

controller에서 너무 많은 로직이 있는 것 같아요.

"round 만큼 움직이는 자동차는 움직임을 시도한다" 라는 책임을 안으로 넣어보면 어떨까요?

racingCarService.move();
racingCarView.printResult(racingCarService.getResults());
}

racingCarView.printWinner(racingCarService.getResults());

isGameInit = false;
}
}
27 changes: 27 additions & 0 deletions src/main/java/converter/RacingCarConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package converter;

import domain.RacingCar;
import dto.RacingCarCreateDto;
import dto.RacingCarResultDto;
import java.util.stream.Collectors;

public class RacingCarConverter {
public static RacingCarCreateDto toRacingCarCreateDto(String name) {
return RacingCarCreateDto.builder().name(name).build();
}

public static RacingCarResultDto toRacingCarResultDto(RacingCar racingCar) {

Choose a reason for hiding this comment

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

Converter에서 너무 출력에 대한 책임을 지고 있는 것 같아요.

만약 자동차가 움직일 떄 출력되어야 하는 문자열이 "-" 가 아니라 "*" 로 바뀌었을 떈 Converter가 바뀌는게 맞을까요?

Choose a reason for hiding this comment

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

여기도 너무 괄호안에 로직이 많은 것 같아요.
다른 사람들도 잘 읽을 수 있게 분리하는 건 어떨까요?

return RacingCarResultDto.builder().
name(racingCar.getName())
.resultString(racingCar.getResults().stream().map(RacingCarConverter::resultToString).collect(
Collectors.joining()))
.distance((int) racingCar.getResults().stream().filter(v -> v).count()).build();

Choose a reason for hiding this comment

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

여기서 filter를 이렇게 넣으신 이유가 있을까요?

}

public static String resultToString(Boolean result) {
if (result)
return "-";

return "";
}
}
20 changes: 20 additions & 0 deletions src/main/java/dao/RacingCarDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package dao;

import domain.RacingCar;
import dto.RacingCarCreateDto;
import java.util.ArrayList;
import java.util.List;

public class RacingCarDao {

Choose a reason for hiding this comment

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

Dao를 만들어주셨군요. 민창님이 생각하는 Dao의 정의는 무엇인가요??

private final List<RacingCar> racingCars = new ArrayList<>();

public RacingCar insert(RacingCarCreateDto racingCarCreateDto) {
RacingCar racingCar = new RacingCar(racingCarCreateDto.getName());
racingCars.add(racingCar);
return racingCar;
}

public List<RacingCar> select() {
return racingCars;
}
}
15 changes: 15 additions & 0 deletions src/main/java/domain/RacingCar.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package domain;

import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@RequiredArgsConstructor
public class RacingCar {
private final String name;
private final List<Boolean> results = new ArrayList<>();
}
14 changes: 14 additions & 0 deletions src/main/java/dto/RacingCarCreateDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@Getter
@NoArgsConstructor

Choose a reason for hiding this comment

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

NoArgsCounstructor랑 AllArgsConstructor를 둘 다 붙인 기준이 있으신가요?

@AllArgsConstructor
public class RacingCarCreateDto {
private String name;
}
16 changes: 16 additions & 0 deletions src/main/java/dto/RacingCarResultDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@Getter
@NoArgsConstructor

Choose a reason for hiding this comment

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

얘도!

@AllArgsConstructor
public class RacingCarResultDto {
private String name;
private String resultString;
private int distance;
}
13 changes: 13 additions & 0 deletions src/main/java/service/RacingCarService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package service;

import dto.RacingCarCreateDto;
import dto.RacingCarResultDto;
import java.util.List;

public interface RacingCarService {

Choose a reason for hiding this comment

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

service의 구현체가 하나밖에 없는데 굳이 interface를 만드신 이유가 있을까요??

void createRacingCar(List<RacingCarCreateDto> racingCarCreateDtoList);

void move();

List<RacingCarResultDto> getResults();
}
38 changes: 38 additions & 0 deletions src/main/java/service/RacingCarServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package service;

import converter.RacingCarConverter;
import dao.RacingCarDao;
import domain.RacingCar;
import dto.RacingCarCreateDto;
import dto.RacingCarResultDto;
import java.util.List;
import lombok.RequiredArgsConstructor;
import util.NumberGenerateUtil;

@RequiredArgsConstructor
public class RacingCarServiceImpl implements RacingCarService {
final RacingCarDao racingCarDao;
final NumberGenerateUtil numberGenerateUtil;

@Override
public void createRacingCar(final List<RacingCarCreateDto> racingCarCreateDtoList) {
List<RacingCar> racingCars = racingCarCreateDtoList.stream().map(racingCarDao::insert).toList();

Choose a reason for hiding this comment

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

map에서 다른 side effect를 발생하는 메서드는 지양하는게 좋습니다. 왜 그럴까요?

}

@Override
public void move() {
for (RacingCar racingCar : racingCarDao.select()){
int power = numberGenerateUtil.generateRandomNumber();

if (0 <= power && power <= 9)
racingCar.getResults().add(power >= 4);
else
throw new NumberFormatException("0~9 사이의 Power 값을 필요로 합니다.");
}
}

@Override
public List<RacingCarResultDto> getResults() {
return racingCarDao.select().stream().map(RacingCarConverter::toRacingCarResultDto).toList();
}
}
11 changes: 11 additions & 0 deletions src/main/java/util/NumberGenerateUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package util;

import java.security.SecureRandom;

public class NumberGenerateUtil {

public int generateRandomNumber() {
SecureRandom secureRandom = new SecureRandom();

Choose a reason for hiding this comment

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

SecureRandom 객체를 계속 생성하고 없얘는 군요. 필드로 빼보는 건 어떨까요?

return secureRandom.nextInt(10);
}
}
78 changes: 78 additions & 0 deletions src/main/java/view/RacingCarView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package view;

import dto.RacingCarResultDto;
import java.lang.module.ResolutionException;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;


public class RacingCarView {

Choose a reason for hiding this comment

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

Input과 Output을 합쳐주셨군요.

Input과 Output에 대한 class를 하나로 묶는 것과 분리하는 것엔 어떤 장단점이 있을까요?

public static class RacingCarInputException extends RuntimeException {

public RacingCarInputException() {
super("유효하지 않은 입력입니다.");
}

public RacingCarInputException(final String s) {
super(s);
}
}

public List<String> inputRacingCarName() {
Scanner scanner = new Scanner(System.in);

System.out.println("경주할 자동차 이름을 입력하세요(이름은 쉼표(,)를 기준으로 구분).");
String input = scanner.nextLine().replaceAll(" ", "");

if (Pattern.compile("[!@#$%^&*().?\"{}|<>]").matcher(input).find())
throw new RacingCarInputException("이름에 특수기호를 입력할 수 없습니다.");

List<String> names = List.of(input.split(","));

if (names.size() <= 1)

Choose a reason for hiding this comment

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

여기도 view에서 round처럼 플레이어 수와 관련된 예외처리를 하면 어떤 문제가 있을 수 있을까요?

throw new RacingCarInputException("최소한 두 명 이상의 플레이어가 필요합니다.");


return names;
}

public int inputRound() {
Scanner scanner = new Scanner(System.in);

System.out.println("시도할 회수는 몇회인가요?");

try {
int round = scanner.nextInt();

if (round <= 0)
throw new NumberFormatException("횟수는 1회 이상이여야 합니다.");

Choose a reason for hiding this comment

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

round가 0보다 작은 경우는 NumberFormatException에 속할까요??

Choose a reason for hiding this comment

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

View는 입력,출력과 같은 부분을 처리하는 부분이에요. 이런 곳에 round가 0보다 작은 경우에 대한 예외처리를 하면 어떤 문제가 발생할 수 있을까요?


return round;
} catch (InputMismatchException e) {
throw new RacingCarInputException();
} catch (NumberFormatException e) {
throw new RacingCarInputException(e.getMessage());
}
}

public void printResultTitle() {
System.out.println("\n실행 결과");
}

public void printResult(List<RacingCarResultDto> racingCarResultDtoList) {
for (RacingCarResultDto racingCarResultDto : racingCarResultDtoList) {
System.out.printf("%s : %s%n", racingCarResultDto.getName(), racingCarResultDto.getResultString());
}
System.out.println();
}

public void printWinner(List<RacingCarResultDto> racingCarResultDtoList) {

Choose a reason for hiding this comment

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

변수의 이름에 List같은 자료형과 직접적으로 연관되는 건 피하는게 좋다고 해요. 왜 그럴까요?

(요 부분은 그렇게 중요한 건 아닌데 한번 생각해보면 좋습니다.)

int maxDistance = racingCarResultDtoList.stream().mapToInt(RacingCarResultDto::getDistance).max().orElseThrow();

List<String> winners = racingCarResultDtoList.stream().filter(v -> v.getDistance() == maxDistance).map(RacingCarResultDto::getName).toList();

System.out.println(String.join(", ", winners) + "가 최종 우승했습니다.");
}
}
Empty file removed src/test/java/.gitkeep
Empty file.
Loading