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

자동차경주/제훈/step1 #4

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 12 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
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apply plugin: 'eclipse'

group = 'camp.nextstep'
version = '1.0.0'
sourceCompatibility = '1.8'
sourceCompatibility = "11"

repositories {
mavenCentral()
Expand All @@ -17,3 +17,4 @@ dependencies {
test {
useJUnitPlatform()
}
targetCompatibility = JavaVersion.VERSION_11
10 changes: 10 additions & 0 deletions src/main/java/study/racingcar/RacingCarApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package study.racingcar;

import study.racingcar.model.Car;

public class RacingCarApplication {

public static void main(String[] args) {
new Car();
}
}
29 changes: 29 additions & 0 deletions src/main/java/study/racingcar/controller/CarController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package study.racingcar.controller;

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

import static study.racingcar.view.ResultView.printRaceResult;
import static study.racingcar.view.ResultView.requestCarsToMove;

public class CarController {
Copy link
Collaborator

Choose a reason for hiding this comment

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

'CarController' 에서 로직을 직접 구현해서 사용하고 있네요!
현재의 구조에서는 Car객체와 CarController가 서로 바뀐듯처럼 보이네요 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

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

CarController 라는 네이밍은 왜 나온것일까요?
게임기 컨트롤러 처럼 도메인 객체에게 어떠한 일을 하도록 시키는 녀석이 아닐까요?
현재의 구조에서는 Car 객체가 CarController에게 자동차 경주를 하도록 시키는 것으로 보이네요~

Copy link
Collaborator Author

@JoJeHuni JoJeHuni Jul 19, 2023

Choose a reason for hiding this comment

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

MVC 패턴이 컨트롤러 로직 실행 -> 비즈니스 로직 실행 후 결과를 받고 -> 결과 데이터를 모델에 저장 -> 컨트롤러에서 뷰 로직으로 권한을 넘겨준 뒤 뷰는 모델에서 데이터를 참조해 응답하는 것이라는걸 정리해놓고 활용할 때는 반대로 이행하고 있었네요.. 감사합니다!


public static void racingCar(int carNum, int attemptNum) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

racingCar() 라는 메서드는 자동차 경주라고 해석할수도 있지만 사람에 따라 혼란스러울수도 있을것 같아요.
"자동차 경주를 시작한다."의 책임을 맡은 메서드니까 raceStart() 라고 지어보는건 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

메서드에 책임과 연관된 네이밍을 지어보자는 조언 감사합니다!!

List<Integer> positions = new ArrayList<>();
Copy link
Collaborator

Choose a reason for hiding this comment

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

"자동차의 위치"를 나타내는 positions 변수를 사용하셨네요~
그런데 뭔가 이상하게 느껴지지 않나요?
자동차 객체는 존재하지 않는데 "자동차의 위치"만 존재해서 로직을 수행하고 있네요 🤔

이 구조에서 자동차와 관련된 다른 기능이 추가되면 어떻게 될까요?
가령, 자동차의 색깔, 모양도 도메인 로직에 추가된다고 하면
colors, shapes를 추가해서 각각 사용해야하는 구조가 되지 않을까요~?

한번 고민해봅시다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

자동차의 객체는 존재하지 않는데 위치만 존재한다는 말씀에 놀랐네요 고민해가며 수정해보겠습니다!!

Random random = new Random();

addCar(carNum, positions);

for (int i = 0; i < attemptNum; i++) {
requestCarsToMove(carNum, positions, random);
printRaceResult(positions);
}
}

public static void addCar(int carNum, List<Integer> positions) {
for (int i = 0; i < carNum; i++) {
positions.add(i,0);
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/study/racingcar/model/Car.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package study.racingcar.model;

import static study.racingcar.controller.CarController.racingCar;
import static study.racingcar.view.InputView.*;

public class Car {

public Car() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Car 객체가 왜 CarController를 호출해야 하는 것일까요?
RacingCarApplication에서 바로 CarController를 호출해도 되지 않을까요~?

racingCar(numberOfCar(),numberOfAttempt());
}

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

import java.util.Scanner;

import static study.racingcar.controller.CarController.racingCar;

public class InputView {

public static int numberOfCar() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

numberOfCar() 라는 네이밍 보다는
메서드의 책임인 "경주하는 자동차의 갯수을 입력받는다"는 의미로 getCarNumbers() 라고 지어보면 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

좋은 의견 감사합니다 !!!

Scanner scanner = new Scanner(System.in);

System.out.println("자동차 대수는 몇 대 인가요?");
int carNum = scanner.nextInt();

return carNum;
}

public static int numberOfAttempt() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 메서드의 네이밍도 바꿔볼까요?

Scanner scanner = new Scanner(System.in);

System.out.println("시도할 횟수는 몇 회인가요?");
int attemptNum = scanner.nextInt();
System.out.println();

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

import java.util.Collections;
import java.util.List;
import java.util.Random;

public class ResultView {

public static void requestCarsToMove(int carNum, List<Integer> positions, Random random) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

자동차에게 움직이라고 요청하는 도메인 로직이네요!
도메인으로 옮기는게 좋을것같아요~

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

리뷰 받아보니 정말이네요 하면서는 순간 몰랐던 것 같습니다 감사합니다!

for (int j = 0; j < carNum; j++) {
int forward = random.nextInt(10); // 0부터 9
Copy link
Collaborator

Choose a reason for hiding this comment

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

10이라는 숫자는 왜 나온것일까요?
이 코드를 처음보는 사람이 이것을 보고 맥락을 이해할 수 있을까요?
이렇게 단서없이 갑자기 튀어나오는 숫자를 매직 넘버라고 한답니다.

Copy link
Collaborator

Choose a reason for hiding this comment

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

이런경우 매직넘버는 별도의 상수로 관리해서 코드를 읽는 사람에게 단서를 주는것이 좋아요.

private static final int MAX_BOUNDARY_OF_RANDOM_NUMBER= 10;

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

매직넘버는 가급적 지양하자는 조언 감사합니다! 바로 수정하겠습니다

if (forward >= 4) { // 전진하는 조건
Copy link
Collaborator

Choose a reason for hiding this comment

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

if절은 따로 빼서 관리해보면 어떨까요?

int currentValue = positions.get(j);
int newValue = currentValue + 1;
positions.set(j, newValue);
}
}
}

public static void printRaceResult(List<Integer> positions) {
for (int pos : positions) {
if (pos == 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

자동차의 위치가 0인데 출력을 하는 이유는 무엇인가요? 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

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

for (int pos : positions) {
            for (int k = 0; k < pos; k++) {
                System.out.print("ㅡ");
            }
            System.out.println();
        }

이렇게 바꿔야 자동차의 위치에 맞게 출력하는 코드가 되겠네요.

Copy link
Collaborator

Choose a reason for hiding this comment

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

 positions.stream()
                .forEach(pos -> {
                    System.out.println("ㅡ".repeat(pos));
                });

steam api를 사용하면 더 간단하게 사용할 수 있습니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

stream api에 대한 것은 몰랐네요.. 더 간단하게 할 수 있는 방법에 대한 조언도 감사합니다!!

System.out.print("ㅡ");
}
for (int k = 0; k < pos; k++) {
System.out.print("ㅡ");
}
System.out.println();
}
System.out.println();
}
}
36 changes: 36 additions & 0 deletions src/main/java/study/stringcalculator/StringCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package study.stringcalculator;

import java.util.List;

public class StringCalculator {

private StringCalculator() { }
public static int calculate(String text) {
List<String> values = StringParser.parse(text);
int number = Integer.parseInt(values.get(0));

for (int i = 1; i < values.size(); i += 2) {

int operand = Integer.parseInt(values.get(i + 1));
String operator = values.get(i);

number = getNumber(number, operand, operator);
}

return number;
}

private static int getNumber(int number, int operand, String operator) {
if ("+".equals(operator)) return number + operand;
if ("-".equals(operator)) return number - operand;
if ("*".equals(operator)) return number * operand;
if ("/".equals(operator)) {
if (operand == 0) {
throw new IllegalArgumentException();
}
return number / operand;
}

throw new IllegalArgumentException("잘못된 연산자 입니다.");
}
}
14 changes: 14 additions & 0 deletions src/main/java/study/stringcalculator/StringParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package study.stringcalculator;

import java.util.List;

public class StringParser {

private StringParser() { }

static List<String> parse(String input) {
return List.of(input.split(" "));
// 알아둘 것. ArrayList로 만들면 가변 + null값 허용
// List.of 메서드로 만들면 불변 + null값은 허용 X
}
}
13 changes: 0 additions & 13 deletions src/test/java/study/StringTest.java

This file was deleted.

84 changes: 84 additions & 0 deletions src/test/java/study/exam/SetTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package study.exam;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.HashSet;
import java.util.Set;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class SetTest {
Copy link
Collaborator

Choose a reason for hiding this comment

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

저번 요구사항을 반영해서 구체적인 테스트로 바꿔주셨군요 👍

private Set<Integer> numbers;

@BeforeEach
@DisplayName("HashSet에 값을 추가한다.")
void setUp() {
numbers = new HashSet<>();
numbers.add(1);
numbers.add(1);
numbers.add(2);
numbers.add(3);
}

@Test
@DisplayName("집합의 크기를 확인할 수 있다.")
void setSize() {
int result = numbers.size();
assertEquals(result, 3); // Set은 중복을 허용하지 않는다.
}

@Test
@DisplayName("집합에 값이 포함되어있는지 비교할 수 있다.")
void compareContains() {
numbers = new HashSet<>();

numbers.add(1);
numbers.add(1);
numbers.add(2);
numbers.add(3);

assertThat(numbers.contains(1)).isTrue();
assertThat(numbers.contains(2)).isTrue();
assertThat(numbers.contains(3)).isTrue();
}

@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
@DisplayName("ValueSource를 사용해 입력 받은 숫자가 각각 numbers에 포함되는지 확인한다.")
void compareContains(int value) {
numbers = new HashSet<>();

numbers.add(1);
numbers.add(1);
numbers.add(2);
numbers.add(3);

assertThat(numbers.contains(value)).isTrue();
}

@ParameterizedTest
@CsvSource({
"1, true",
"2, true",
"3, true",
"4, false",
"5, false"
})
@DisplayName("CsvSource로 true 값만 비교하는 것이 아닌 false 값도 비교할 수 있다.")
void compareContains(int value, boolean expected) {
numbers = new HashSet<>();

numbers.add(1);
numbers.add(1);
numbers.add(2);
numbers.add(3);

assertThat(numbers.contains(value)).isEqualTo(expected);
}
}
47 changes: 47 additions & 0 deletions src/test/java/study/exam/StringTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package study.exam;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class StringTest {
Copy link
Collaborator

Choose a reason for hiding this comment

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

좋습니다~ 💪

@Test
@DisplayName("abc에서 b를 d로 대체했을 때 같은지 테스트하는 코드")
void replace() {
String actual = "abc".replace("b", "d");
assertThat(actual).isEqualTo("adc");
}
// 요구사항 1
@Test
@DisplayName("특정 문자열로 분리하는 테스트 코드")
public void split() {
String[] values = "1,2".split(",");
assertThat(values).containsExactly("1", "2");
values = "1".split(",");
assertThat(values).contains("1");
}
@Test
@DisplayName("특정 문자열까지 읽어서 비교하는 테스트 코드")
public void subString() {
String input = "(1,2)";
String result = input.substring(1, input.length() - 1); //문자열 인덱스 1부터 input의 길이 - 1 즉 2까지.
assertThat(result).isEqualTo("1,2");
}
@Test
@DisplayName("String의 특정 위치의 문자 가져오기")
public void getIndexValue() {
String values = "abc";
assertEquals('a', values.charAt(0));
}

@Test
@DisplayName("위치 값을 벗어났을 때의 예외")
public void stringIndexOutOfBoundsException() {
String values = "abc";
assertThrows(StringIndexOutOfBoundsException.class, () -> values.charAt(-1));
assertThrows(StringIndexOutOfBoundsException.class, () -> values.charAt(3));
}
}
12 changes: 12 additions & 0 deletions src/test/java/study/racingcar/CarTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package study.racingcar;

import org.junit.jupiter.api.Test;
import study.racingcar.controller.CarController;

public class CarTest {
@Test
public void 자동차_이동_로직을_확인한다() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

테스트가 아직 제대로 작성되지 않았네요!
저번에 리뷰에서 말한것이지만 테스트를 작성했을때 어려웠다면
책임을 나누고 객체들 간의 관계를 정하는 작업을 더 해야한다는 신호가 아닐까요~?

조금씩 좋아지고 있습니다 👍

CarController.racingCar(1, 1);

}
}
34 changes: 34 additions & 0 deletions src/test/java/study/racingcar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
### **요구사항**

초간단 자동차 경주 게임을 구현한다.

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 사용자는 몇 대의 자동차로 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차의 상태를 화면에 출력한다. 어느 시점에 출력할 것인지에 대한 제약은 없다.

---
### **체크리스트**
- [x] 자동차 대수를 입력 받는다.
- [x] 시도할 횟수를 입력 받는다.
- [x] 무작위 값을 구해 4 이상일 때 전진하는 조건을 구현한다.

---
# 프로그래밍 요구 사항

모든 로직에 단위 테스트를 구현한다.
단, UI(System.out, [System.in](http://system.in/)) 로직은 제외
핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.

---
ex.
> 자동차 대수는 몇 대인가요?
>
> 3
>
> 시도할 횟수는 몇 회인가요?
>
> 5
>
> ![img.png](img.png)
Binary file added src/test/java/study/racingcar/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/test/java/study/stringcalculator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### **요구사항**

- 사용자가 입력한 문자열 값에 따라 사칙연산을 수행할 수 있는 계산기를 구현해야 한다.
- 문자열 계산기는 사칙연산의 계산 우선순위가 아닌 입력 값에 따라 계산 순서가 결정된다. 즉, 수학에서는 곱셈, 나눗셈이 덧셈, 뺄셈 보다 먼저 계산해야 하지만 이를 무시한다.
- 예를 들어 "2 + 3 * 4 / 2"와 같은 문자열을 입력할 경우 2 + 3 * 4 / 2 실행 결과인 10을 출력해야 한다.

### **체크리스트**
- [x] 문자열을 입력받는다.
- [x] 문자열을 파싱한다.
- [x] 0으로 나눴을 때 IllegalArgumentException을 터뜨린다.
- [x] 파싱한 문자열로 계산한다.
Loading