-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
The head ref may contain hidden characters: "\uC790\uB3D9\uCC28\uACBD\uC8FC/\uC81C\uD6C8/Step1"
Changes from 11 commits
99d2be8
9f98a50
8d7e038
548c5a6
6c914e5
e262a8e
5fd4c3f
8535a2b
4f9da27
7102f19
3bf2a25
574c827
d618a59
0171ca0
93eb990
ef48ab2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package study.racingcar; | ||
|
||
import java.util.Random; | ||
import java.util.Scanner; | ||
|
||
public class Car { | ||
|
||
// public static void main(String[] args) { | ||
// new Car(); | ||
// } | ||
private Car() { | ||
numberOfCar(); | ||
} | ||
public static void numberOfCar() { | ||
Scanner scanner = new Scanner(System.in); | ||
System.out.println("자동차 대수는 몇 대 인가요?"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 프로그래밍 요구사항을 지켜보아요! 프로그래밍 요구 사항
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 도메인 객체와 입출력을 담당하는 객체는 분리하는 것이 좋아요. 왜 그럴까요? 도메인 객체의 입장에서는 입/출력은 중요한것이 아닌것이 아닐까요? 객체가 한가지 책임에 집중해야 결과물이 더 좋다고 할 수 있겠죠? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 추가로 책임을 나눠야 하는 이유에 대해서 이야기해보면 이게 무슨 이야기일까요? 다시 한번 Car 객체가 하고 있는 것들을 나열해볼게요. Car 객체가 하는 일 목록
네가지의 일을 하고 있네요. 자 그러면 만약에 프로그래밍 요구 사항이 변경되어서 입력을 받는 일에 변경이 생겼다고 합시다. 그러면 어디를 변경해야 할까요? 혹은 출력과 관련된 요구사항이 바뀌면 어떻죠? 그리고 제일 극악무도한 사실은 도메인 로직이네요! 그러면 Car 객체는 무수한 도메인 로직의 변경사항을 반영해서 변경이 무수한 변경이 일어나겠네요! 혹여나 협업으로 Car 클래스를 관리한다고 하면 상황은 더 끔찍합니다... 이런 맥락에서 객체의 책임을 나눠주어야 한답니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 도메인 객체의 입장에서 중요하지 않는 로직은 다른 객체에 위임하고 중요한 비즈니스 로직을 도메인 객체가 행하게끔 한다.. 저번 코드 리뷰해주신거에 이어 신경써서 만든다는게 말씀해주신대로 하나에 모든 책임을 다 지게끔 만들었네요. 참고해보겠습니다!! 감사합니다 |
||
|
||
int number = scanner.nextInt(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "경주하는 자동차의 갯수" 라는 의미를 전달하는 변수명으로 바꾸는것이 좋을 것 같아요~ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 조언 감사합니다! |
||
|
||
System.out.println("시도할 횟수는 몇 회인가요?"); | ||
int attempt = scanner.nextInt(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 심플하게 attemptNo, attemptNum 정도도 괜찮겠네요 ㅎㅎ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 의미를 전달하는 변수를 사용하는 것이 좋다는 조언 감사합니다! |
||
|
||
System.out.println(); | ||
racingCar(number, attempt); | ||
} | ||
|
||
public static void racingCar(int number, int attempt) { | ||
int[] positions = new int[number]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 배열 말고 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앞으로 개발하실때 특정 값을 저장해야 한다고 했을때 특별한 이유가 있지 않는 이상 |
||
Random random = new Random(); | ||
|
||
for (int i = 0; i < attempt; i++) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 for문은 어떤행위를 하는걸까요? 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for문을 별도의 메서드로 분리하고 메서드에 네이밍을 주면 어떨까요? for (int i = 0; i < attempt; i++) {
requestCarsToMove(number, positions, random);
printRaceResult(positions);
}
private void requestCarsToMove(int number, int[] positions, Random random) {
for (int j = 0; j < number; j++) {
int forward = random.nextInt(10); // 0부터 9
if (forward >= 4) { // 전진하는 조건
positions[j] += 1;
}
}
}
private void printRaceResult(int[] positions) {
for (int pos : positions) {
if (pos == 0) {
System.out.print("ㅡ");
}
for (int k = 0; k < pos; k++) {
System.out.print("ㅡ");
}
System.out.println();
}
System.out.println();
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어떤가요? 그냥 하나의 메서드 내부에 있는것과 둘중에 어떤게 읽는 입장에서 더 읽기 쉬운 코드일까요~? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 하지만 전반적으로 코드가 "객체지향적"이라기 보다는 "절차지향적"인 코드에 가까워보이네요 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 한번 프로그램을 구현해서 "자동차 경주"라는 도메인에 대해 이해도가 올라갔으니, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 행위에 대해 메서드 이름으로 힌트를 주는 것, 객체들에게 책임을 나누는 것 여러 번 되새겨서 제 것으로 만들어보겠습니다 감사합니다!! |
||
for (int j = 0; j < number; j++) { | ||
int forward = random.nextInt(10); // 0부터 9 | ||
if (forward >= 4) { // 전진하는 조건 | ||
positions[j] += 1; | ||
} | ||
} | ||
|
||
for (int pos : positions) { | ||
if (pos == 0) { | ||
System.out.print("ㅡ"); | ||
} | ||
for (int k = 0; k < pos; k++) { | ||
System.out.print("ㅡ"); | ||
} | ||
System.out.println(); | ||
} | ||
System.out.println(); | ||
} | ||
} | ||
} |
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("잘못된 연산자 입니다."); | ||
} | ||
} |
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 | ||
} | ||
} |
This file was deleted.
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} |
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package study.racingcar; | ||
|
||
import org.assertj.core.api.Assertions; | ||
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 study.stringcalculator.StringCalculator; | ||
|
||
import java.io.ByteArrayInputStream; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.PrintStream; | ||
import java.util.HashSet; | ||
import java.util.Scanner; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class CarTest { | ||
|
||
private ByteArrayOutputStream outputStream; | ||
|
||
protected void systemIn(String input) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// 이 메서드를 실행하면 input 값의 바이트 배열을 스트림에 넣어 System.in에 할당해준다. | ||
// 참고한 링크 : https://steadyjay.tistory.com/10 | ||
System.setIn(new ByteArrayInputStream(input.getBytes())); | ||
} | ||
|
||
protected String getOutput() { | ||
// ByteArrayOutputStream의 toString은 기본 문자 집합을 사용하여 버퍼의 내용을 문자열 디코딩 바이트로 변환해줍니다. | ||
return outputStream.toString(); | ||
} | ||
|
||
void test() { | ||
Scanner scanner = new Scanner(System.in); | ||
System.out.println(scanner.nextLine()); | ||
} | ||
@BeforeEach | ||
void setUp() { | ||
outputStream = new ByteArrayOutputStream(); | ||
System.setOut(new PrintStream(outputStream)); | ||
} | ||
|
||
@Test | ||
public void 자동차_수를_입력받는다() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 비즈니스 로직인 자동차 경주와 관련된 테스트는 보이지 않고 입출력에 관련된 테스트만 존재하는것 같네요 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아마도 이렇게 작성하신 이유는 "테스트를 작성하기 어려워서" 이겠죠? 그 이유는 현재 코드가 절차지향적으로 하나의 클래스안에 모든 로직이 혼재 되어있어서 그렇습니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재의 코드를 테스트 하려면
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 하지만 Car 객체의 책임을 나눠서 만들면 어떨까요?
값을 입력받아서 계산한다. 이런식으로 나누어져 있었기때문에 이렇게 각각 테스트하면 됩니다. => 3의 어려움도 해결 이제 제가 하고싶은 이야기가 무엇인지 어느정도 감이 오실까요? 이것이 바로 객체지향과 절차지향의 차이랍니다. 😄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MVC 패턴을 알맞게 적용한 것인지는 모르겠으나 적용 중인데 책임들을 나눠주고보니 테스트코드를 작성하는데 어려움이 느껴지네요.. 자세한 조언 정말 감사합니다!! |
||
String input = "3"; | ||
systemIn(input); | ||
test(); | ||
|
||
Assertions.assertThat(getOutput()).contains("3"); | ||
} | ||
|
||
@Test | ||
public void 시도할_횟수를_입력받는다() { | ||
String input = "5"; | ||
systemIn(input); | ||
test(); | ||
|
||
Assertions.assertThat(getOutput()).contains("5"); | ||
} | ||
} |
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) |
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] 파싱한 문자열로 계산한다. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
필요없는 주석은 삭제해주세요~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Car 객체에서 main함수를 만들고 사용하려고 생성자를 private으로 만드신것 같네요~
Car 객체가
많은 것을 하고 있네요.
Car 객체가 맡은 책임이 너무 많은것이 아닐까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
구조를 이렇게 만들어보면 어떨까요?
main 함수를 가지고 있고 전체 프로그램을 실행시키는 : RacingCarApplication
View --------------------------------
ui 로직중 입력을 담당하는 : InputView
ui 로직중 출력을 담당하는 : ResultView
Controller ----------------------------
도메인 객체들에게 필요한 로직을 요청하는 오케스트레이션 객체 : CarController
model -------------------------------
도메인 객체들 : Car, .. 등등
물론 이 구조가 정답은 아니겠지만 이런식으로 나눠서 만들어보면 객체들의 책임을 나눠서 만들기 편하더라구요~
https://m.blog.naver.com/jhc9639/220967034588
Mvc 패턴에 대해 한번 공부해보시는게 어떨까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
추가로 패키지를 View, controller, model로 만들어서 객체들을 관리하는 것도 좋을 것 같아요~
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MVC패턴 강의 영상을 여러 번 곱씹어 봐야겠네요 너무 도움되는 조언 감사합니다 참고해서 바로 수정해보겠습니다!