Skip to content
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
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,38 @@
# Calculator

## 📌 구현 기능 목록

### 입력받기

"수식을 입력해주세요." ← 사용자에게 문구 출력

입력 받기

### 로직

#### 입력 검사

##### 통과

- 공백은 0
- 연산방향은 왼쪽에서 오른쪽
- (연산자 우선순위)

##### 에러

`IllegalArgumentException`

- 연속된 연산자
- 타입 에러



### 출력

#### 정상 출력

"결과 : ?"

#### 에러 발생

"[ERROR] 잘못된 수식입니다. 입력 형식을 확인해 주세요."
30 changes: 27 additions & 3 deletions src/main/java/calculator/Application.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,35 @@
package calculator;

import java.util.ArrayList;

public class Application {

private static final Console console = new Console();

public static void main(String[] args) {
// TODO: 코드 작성
String inputString = console.readInput();

run(inputString);
}

// 예시 코드
public static int run(String inputString) {
Validator validator = new Validator();
Calculator calculator = new Calculator();
calculator.calculate("");

ArrayList<String> inputArrayList;
int result = 0;

try {
inputArrayList = validator.divideInput(inputString);
result = calculator.calculate(inputArrayList);
} catch (IllegalStateException e) {
// 입력값이 공백값 또는 비어있는 경우
} catch (IllegalArgumentException illegalArgumentException) {
System.out.println(illegalArgumentException.getMessage());
return -1;
}

console.printResult(result);
return result;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Application 클래스가 프로그램의 진입점을 담당하는데, run 까지 넣으면 책임이 조금 과중된다 생각합니다!
ApplicationTest를 통합 테스트로 구현하고 싶어 하신 것 같은데, run 또한 객체이기 때문에 따로 분리하고 main 함수 호출을 통해 테스트를 진행하는 것이 조금 더 좋은 방향이라고 생각해요.
또한, run이 static으로 선언되었고, 다른 클래스를 의존성을 주입받는 것이 아니라 내부에 선언함으로써 유지보수 및 테스트가 어려워진다고 알고있어요.
예를 들어, 현재 calculator가 아닌 연산자 우선순위를 고려한 클래스를 따로 만든다면, 이를 의존성만 바꾸면 되는 것이 아닌 내부 코드를 바꿔야 합니다.

조금 길어졌지만 OOP의 SRP와 OCP에 대해 조금 찾아보시면 현제님이라면 금방 익숙해지실 것 같아요!

}
56 changes: 49 additions & 7 deletions src/main/java/calculator/Calculator.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,55 @@
package calculator;

/*
1주차에만 제공되는 예시 코드입니다.
코드는 그대로 사용해도 되고 수정해도 됩니다.
*/
import java.util.*;

// 연산 책임
public class Calculator {
public int calculate(String input) {
// TODO: 코드 구현
throw new IllegalArgumentException("아직 구현되지 않았습니다.");

private int result = 0;

public int calculate(ArrayList<String> inputArrayList) {

// 단일 원소인 경우 그대로 return
if (isSingleElement(inputArrayList)) {
return Integer.parseInt(inputArrayList.get(0));
}

// 큐 선언
LinkedList<String> stringQueue = toQueue(inputArrayList);

while (stringQueue.size() > 1) {

result = operate(
stringQueue.poll(),
stringQueue.poll(),
stringQueue.poll()
);

stringQueue.addFirst(String.valueOf(result));
}

return result;
}


private boolean isSingleElement(ArrayList<String> inputArrayList) {
return inputArrayList.size() == 1;
}

private LinkedList<String> toQueue(ArrayList<String> inputArrayList) {
return new LinkedList<>(inputArrayList);
}

private int operate(String num1String, String operator, String num2String) {
int num1 = Integer.parseInt(num1String);
int num2 = Integer.parseInt(num2String);

return switch (operator) {
case "+" -> num1 + num2;
case "-" -> num1 - num2;
case "*" -> num1 * num2;
case "/" -> num1 / num2;
default -> throw new IllegalStateException("Unexpected value: " + operator);
};
}
}
26 changes: 26 additions & 0 deletions src/main/java/calculator/Console.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package calculator;

import java.util.Scanner;

// TODO: SINGLETON?
// 입출력 책임
public class Console {
private static final String MESSAGE_INPUT_GUIDE = "수식을 입력해주세요.";
private static final String MESSAGE_OUTPUT_FORMATTED = "결과 : %d";

public static final String MESSAGE_ERROR_FORMAT = "[ERROR] 잘못된 수식입니다. 입력 형식을 확인해 주세요.";
public static final String MESSAGE_ERROR_WRONG_POSITION_OPERATORS = "[ERROR] 잘못된 연산자 위치입니다. 입력 형식을 확인해 주세요.";
public static final String MESSAGE_ERROR_EMPTY_OR_BLANK = "[ERROR] 입력값이 없거나 공백입니다. 입력 형식을 확인해 주세요.";


private static final Scanner scanner = new Scanner(System.in);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Console을 따로 만들어서 입출력을 담당해 책임 분리가 좋은 것 같아요.
Scanner를 싱글톤으로 만들면 어떤 이점이 있나요?


public String readInput() {
System.out.println(MESSAGE_INPUT_GUIDE);
return scanner.nextLine();
}

public void printResult(int result) {
System.out.println(String.format(MESSAGE_OUTPUT_FORMATTED, result));
}
}
63 changes: 63 additions & 0 deletions src/main/java/calculator/Validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package calculator;

import java.util.ArrayList;
import java.util.Set;

// 검사 책임

public class Validator {

// 검사를 위한 배열 생성
private final ArrayList<String> inputArrayList = new ArrayList<>();
// 검사를 위한 연산자 set
private static final Set<Character> OPERATORS = Set.of('+', '-', '*', '/');

// 검사 이후 숫자와 연산자로 쪼개어 배열로 return
public ArrayList<String> divideInput(String input) {
String currentNumber = "";

validateEmptyOrBlank(input);

for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);

if (Character.isDigit(ch)) {
currentNumber += ch;

validateLastNumber(i, input.length() - 1, currentNumber);

} else if (OPERATORS.contains(ch)) {
validateOperatorPosition(currentNumber);

inputArrayList.add(currentNumber);
inputArrayList.add(String.valueOf(ch));

currentNumber = "";

} else { // 숫자나 문자열이 아닌 경우
throw new IllegalArgumentException(Console.MESSAGE_ERROR_FORMAT);
}
}

return inputArrayList;
}

private void validateLastNumber(int position, int length, String currentNumber) {
if (position == length) {
inputArrayList.add(currentNumber);
}
}

private void validateEmptyOrBlank(String input) {
if (input.isBlank()) {
throw new IllegalStateException(Console.MESSAGE_ERROR_EMPTY_OR_BLANK);
}
}

private void validateOperatorPosition(String currentNumber) {
if (currentNumber.isEmpty()) {
throw new IllegalArgumentException(Console.MESSAGE_ERROR_WRONG_POSITION_OPERATORS);
}
}

}
19 changes: 7 additions & 12 deletions src/test/java/calculator/ApplicationTest.java
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
package calculator;

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

import org.junit.jupiter.api.Test;


public class ApplicationTest {
Calculator calculator = new Calculator();

@Test
public void 빈_문자열_입력_시_0을_반환한다() {
// 아직 calculate 함수가 구현되지 않은 상태에서 TDD Red Test용 코드
int result = calculator.calculate("");
assertThat(result).isEqualTo(0);
assertThat(Application.run(" ")).isEqualTo(0);
}

@Test
public void 덧셈과_뺄셈만_포함된_수식_계산() {
int result = calculator.calculate("1+2-3");
assertThat(result).isEqualTo(0);
assertThat(Application.run("1+2-3")).isEqualTo(0);
}

@Test
public void 곱셈과_덧셈_포함_수식_계산() {
int result = calculator.calculate("2*3+4");
assertThat(result).isEqualTo(10);
assertThat(Application.run("2*3+4")).isEqualTo(10);

}

@Test
public void 잘못된_수식_입력_시_예외_발생() {
assertThrows(IllegalArgumentException.class, () -> {
calculator.calculate("1++2");
});
assertThat(Application.run("1++2")).isEqualTo(-1);
}

// TODO: 테스트 코드 추가 가능합니다.
Expand Down
53 changes: 53 additions & 0 deletions src/test/java/calculator/CalculatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package calculator;

import org.junit.jupiter.api.Test;

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

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

class CalculatorTest {

Calculator calculator = new Calculator();

@Test
void calculate_test_single() {
ArrayList<String> input = new ArrayList<>(List.of("12"));
int result = calculator.calculate(input);

assertThat(result).isEqualTo(12);
}

@Test
void calculate_test_simple() {
ArrayList<String> input = new ArrayList<>(List.of("1", "+", "2"));
int result = calculator.calculate(input);

assertThat(result).isEqualTo(3);
}

@Test
void calculate_test_divide() {
ArrayList<String> input = new ArrayList<>(List.of("4", "/", "2"));
int result = calculator.calculate(input);

assertThat(result).isEqualTo(2);
}

@Test
void calculate_test_complex() {
ArrayList<String> input = new ArrayList<>(List.of("2", "+", "3", "*", "4"));
int result = calculator.calculate(input);

assertThat(result).isEqualTo(20);
}

@Test
void calculate_test_wrong_operator() {
ArrayList<String> input = new ArrayList<>(List.of("1", "&", "2"));

assertThrows(IllegalStateException.class, () -> calculator.calculate(input));
}
}
55 changes: 55 additions & 0 deletions src/test/java/calculator/ValidatorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package calculator;

import org.junit.jupiter.api.Test;

import java.util.List;

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

class ValidatorTest {

Validator validator = new Validator();

@Test
void split_test() {
String input = "3+5*2";
List<String> result = validator.divideInput(input);

assertThat(result).containsExactly("3", "+", "5", "*", "2");
}

@Test
void exception_test_for_blank() {
String input = " ";
Exception e = assertThrows(IllegalStateException.class, () -> validator.divideInput(input));

assertThat(e.getMessage()).isEqualTo(Console.MESSAGE_ERROR_EMPTY_OR_BLANK);
}

@Test
void exception_test_for_operators() {
String input = "3++2";
Exception e = assertThrows(IllegalArgumentException.class, () -> validator.divideInput(input));

assertThat(e.getMessage()).isEqualTo(Console.MESSAGE_ERROR_WRONG_POSITION_OPERATORS);
}

@Test
void exception_test_for_input_format() {
String input = "5+a";
Exception e = assertThrows(IllegalArgumentException.class, () -> validator.divideInput(input));

assertThat(e.getMessage()).isEqualTo(Console.MESSAGE_ERROR_FORMAT);
}

@Test
void exception_test_for_first_starts_with_operator() {
String input = "+12+3-5";
Exception e = assertThrows(IllegalArgumentException.class, () -> validator.divideInput(input));

assertThat(e.getMessage()).isEqualTo(Console.MESSAGE_ERROR_WRONG_POSITION_OPERATORS);

}

}