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 11 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
51 changes: 51 additions & 0 deletions src/main/java/study/racingcar/Car.java
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) {
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

Choose a reason for hiding this comment

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

Car 객체에서 main함수를 만들고 사용하려고 생성자를 private으로 만드신것 같네요~

Car 객체가

  1. main 함수도 실행하고
  2. 도메인 로직도 실행하고
  3. 입력도 받고
  4. 출력도 하고

많은 것을 하고 있네요.

Car 객체가 맡은 책임이 너무 많은것이 아닐까요?

Copy link
Collaborator

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 패턴에 대해 한번 공부해보시는게 어떨까요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

추가로 패키지를 View, controller, model로 만들어서 객체들을 관리하는 것도 좋을 것 같아요~

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

MVC패턴 강의 영상을 여러 번 곱씹어 봐야겠네요 너무 도움되는 조언 감사합니다 참고해서 바로 수정해보겠습니다!

// new Car();
// }
private Car() {
numberOfCar();
}
public static void numberOfCar() {
Scanner scanner = new Scanner(System.in);
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.

프로그래밍 요구사항을 지켜보아요!

프로그래밍 요구 사항

  • 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
  • UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.

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

Choose a reason for hiding this comment

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

추가로 책임을 나눠야 하는 이유에 대해서 이야기해보면
객체가 많은 책임을 맡고 있다는 것은
다르게 이야기하면 "변경해야 할 이유가 많다는 것"이에요.

이게 무슨 이야기일까요?

다시 한번 Car 객체가 하고 있는 것들을 나열해볼게요.

Car 객체가 하는 일 목록

  1. main 함수도 실행
  2. 도메인 로직도 실행
  3. 입력도 받고
  4. 출력도 하고

네가지의 일을 하고 있네요.

자 그러면 만약에 프로그래밍 요구 사항이 변경되어서

입력을 받는 일에 변경이 생겼다고 합시다.

그러면 어디를 변경해야 할까요?
Car 객체를 변경해야 겠지요?

혹은 출력과 관련된 요구사항이 바뀌면 어떻죠?
이번에도 Car 객체를 변경해야 겠네요?

그리고 제일 극악무도한 사실은 도메인 로직이네요!
소프트웨어는 생명체와 같아서 변경은 밥먹듯이 일어납니다.
그중 도메인 로직은 가장 변경이 많이 생기는 지점이기도 하지요.

그러면 Car 객체는 무수한 도메인 로직의 변경사항을 반영해서 변경이 무수한 변경이 일어나겠네요!

혹여나 협업으로 Car 클래스를 관리한다고 하면 상황은 더 끔찍합니다...

이런 맥락에서 객체의 책임을 나눠주어야 한답니다.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

도메인 객체의 입장에서 중요하지 않는 로직은 다른 객체에 위임하고 중요한 비즈니스 로직을 도메인 객체가 행하게끔 한다.. 저번 코드 리뷰해주신거에 이어 신경써서 만든다는게 말씀해주신대로 하나에 모든 책임을 다 지게끔 만들었네요. 참고해보겠습니다!! 감사합니다


int number = scanner.nextInt();
Copy link
Collaborator

Choose a reason for hiding this comment

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

number는 무슨 숫자를 의미하는 걸까요?
처음 코드를 읽는 사람이 이 변수명만을 읽고 무슨 변수인지 알 수 있을까요? 🤔

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.

조언 감사합니다!


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

Choose a reason for hiding this comment

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

attempt는 시도의 정보를 담은 객체를 생각나게 하는 네이밍이네요~

numberOfAttempt 이런식으로 "시도하는 횟수"라는 의미를 전달하는 변수로 바꿔보는 것이 어떨까요?

Copy link

Choose a reason for hiding this comment

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

심플하게 attemptNo, attemptNum 정도도 괜찮겠네요 ㅎㅎ

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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];
Copy link
Collaborator

Choose a reason for hiding this comment

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

배열 말고 List를 사용해보면 어떨까요?

Copy link
Collaborator

Choose a reason for hiding this comment

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

앞으로 개발하실때 특정 값을 저장해야 한다고 했을때 특별한 이유가 있지 않는 이상
자바 내장 Collection API에서 제공하는 자료구조를 사용하시는게 좋답니다 💪

Random random = new Random();

for (int i = 0; i < attempt; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

이 for문은 어떤행위를 하는걸까요? 🤔
처음 코드를 읽은 사람이 이 for문을 읽고 무엇을 하는것인지 한눈에 알 수 있을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The 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();
    }

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

Choose a reason for hiding this comment

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

하지만 전반적으로 코드가 "객체지향적"이라기 보다는 "절차지향적"인 코드에 가까워보이네요 🤔
그래서 메서드로 나눠도 코드에 찝찝한 느낌이 남아있어요.

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 < 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();
}
}
}
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));
}
}
62 changes: 62 additions & 0 deletions src/test/java/study/racingcar/CarTest.java
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) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

프로그래밍 요구사항을 지켜보아요!

프로그래밍 요구 사항

모든 로직에 단위 테스트를 구현한다.
단, UI(System.out, System.in) 로직은 제외

// 이 메서드를 실행하면 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 자동차_수를_입력받는다() {
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

Choose a reason for hiding this comment

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

아마도 이렇게 작성하신 이유는 "테스트를 작성하기 어려워서" 이겠죠?

그 이유는 현재 코드가 절차지향적으로 하나의 클래스안에 모든 로직이 혼재 되어있어서 그렇습니다.
테스트를 해야하는데 필요한 것들이 너무 많은 것이죠.

Copy link
Collaborator

Choose a reason for hiding this comment

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

현재의 코드를 테스트 하려면

  1. 입력을 받는다.
  2. 입력받은것을 Car 객체한테 넘겨준다.
  3. Car 객체가 내부적으로 비즈니스 로직을 수행 (내부에서 어떤일이 일어나는지 테스트 코드로 확인하기가 힘듬)
  4. 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 객체의 책임을 나눠서 만들면 어떨까요?

StringCalcualtor를 만들었을때를 생각해봅시다.

StringCalcualtor는 파라미터로 매개변수로 값을 입력받아서 동작하기 때문에 입출력과 관련된것은 생각할필요가 없었죠?
-> Car 객체의 1,4의 어려움 해결

StringCalcualtor의 책임을 나눠서 만들었기 때문에 테스트하기가 간단함

값을 입력받아서 계산한다.
=> 1. 입력받은 값을 파싱한다.(StringParser) 2. 파싱한 값으로 계산한다.(StringCalculator)

이런식으로 나누어져 있었기때문에
파싱하는 책임을 테스트, 계산하는 책임을 테스트

이렇게 각각 테스트하면 됩니다. => 3의 어려움도 해결

이제 제가 하고싶은 이야기가 무엇인지 어느정도 감이 오실까요?

이것이 바로 객체지향과 절차지향의 차이랍니다. 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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");
}
}
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