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
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
# java-calculator
문자열 계산기 미션 저장소

## 짝 프로그래밍 진행 절차
1. 네비게이터, 드라이버 결정
- 1st: 네비게이터 이동민, 드라이버 이찬규
- 2nd: 네비게이터 이찬규, 드라이버 이동민

2. 기능 분석
- 입력기: 사용자 입력을 받는다.
- 출력기: 프로그램 메시지를 출력한다.
- 계산기: 식을 계산한다.
- 파서: 문자열 식을 의미 단위로 나눈다.

3. 테스트 케이스 작성

4. 구현

5. 다른 테스트 케이스 작성

6. 구현

7. 3~6 반복

## 단위 테스트 실습 - 문자열 계산기
- 다음 요구사항을 junit을 활용해 단위 테스트 코드를 추가해 구현한다.
## 요구사항
Expand All @@ -14,8 +35,8 @@
- 문자열을 입력 받은 후(scanner의 nextLine() 메소드 활용) 빈 공백 문자열을 기준으로 문자들을 분리해야 한다.
```java
String value = scanner.nextLine();
String[] values = value.split(" ");
String[] values = value.split(" ");
```
- 문자열을 숫자로 변경하는 방법

`int number = Integer.parseInt("문자열");`
`int number = Integer.parseInt("문자열");`
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apply plugin: 'java'
apply plugin: 'eclipse'

version = '1.0.0'
sourceCompatibility = 1.8
sourceCompatibility = 11

Choose a reason for hiding this comment

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

오 11버전을 사용하셨군요! 이유가 있을까요?


repositories {
mavenCentral()
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/CalculatorApplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import domain.calculator.Calculator;
import domain.calculator.exception.InvalidInputException;
import domain.expression.operator.exception.DivideByZeroException;
import ui.printer.ConsolePrinter;
import ui.printer.Printer;
import ui.receiver.ConsoleReceiver;
import ui.receiver.Receiver;

import java.util.Optional;

public class CalculatorApplication {
private static final boolean CONTINUE_PROGRAM = true;
private static final String EXIT_REQUEST = "exit";

private Printer printer;
private Receiver receiver;
private Calculator calculator;
Comment on lines +15 to +17

Choose a reason for hiding this comment

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

한번 할당 후 변경이 없는 값들은 final을 붙여주는것이 좋습니다!
final에 대해 학습해보세요


public CalculatorApplication() {
printer = new ConsolePrinter();
receiver = new ConsoleReceiver();
calculator = new Calculator();
}

public void run() {
printer.greet();

while (CONTINUE_PROGRAM) {
printer.printWaitingForInputText();
String expression = receiver.receiveExpression();
if (expression.equalsIgnoreCase(EXIT_REQUEST)) {

Choose a reason for hiding this comment

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

String api를 잘 활용하셨네요 👍🏻

break ;
}

Optional<Integer> optionalResult = calculateExpression(expression);

Choose a reason for hiding this comment

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

Optional은 생성 비용이 비싼 객체에요. 또한 비즈니스 로직이 복잡해지는 경우 메소드를 타고 다니면서 Integer와 Optional이 혼용되어 쓰여서 불편함을 낳기도 한답니다.

사용하는 부분이 null 체크용도 뿐이라면 예외를 던지는 방식으로도 충분히 처리할 수 있을 것 같네요.
이펙티브 자바 Item 55 를 참고해보세요.

if (optionalResult.isEmpty()) {
continue ;
}

int result = optionalResult.get();
printer.printResult(result);
}
}

private Optional<Integer> calculateExpression(String expression) {
Integer result;
try {
result = calculator.calculate(expression);
} catch (DivideByZeroException e) {
System.out.println(e.getMessage());
return Optional.empty();
} catch (InvalidInputException e) {
System.out.println(e.getMessage());
return Optional.empty();
}

return Optional.of(result);
}

public static void main(String[] args) {
CalculatorApplication app = new CalculatorApplication();
app.run();
}
}
21 changes: 21 additions & 0 deletions src/main/java/domain/calculator/Calculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package domain.calculator;

import domain.expression.Expression;

public class Calculator {

public int calculate(String strExpression) {
Parser parser = new Parser(strExpression);

int left = Integer.parseInt(parser.nextToken());
Expression expression = Expression.from(left);

while (parser.hasNext()) {
String operator = parser.nextToken();
int right = Integer.parseInt(parser.nextToken());
expression = Expression.of(expression, right, operator);
}

return expression.evaluate();
}
}
55 changes: 55 additions & 0 deletions src/main/java/domain/calculator/ExpressionValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package domain.calculator;

import domain.calculator.exception.InvalidInputException;

import java.util.List;
import java.util.regex.Pattern;

public class ExpressionValidator {

public void validateTokenList(List<String> tokenList) {
long invalidTokenCount = tokenList.stream()
.filter(token -> !isNumber(token) && !isOperator(token))
.count();

if (invalidTokenCount > 0) {
throw new InvalidInputException("올바르지 않은 입력입니다.");
}
}

public void validateTokenSequence(List<String> tokenList) {
throwIfConditionIsTrue(hasNotAnyToken(tokenList));

String firstToken = tokenList.get(0);
throwIfConditionIsTrue(!isNumber(firstToken));

for (int i = 1; i < tokenList.size(); i += 2) {
String operatorToken = tokenList.get(i);
throwIfConditionIsTrue(!isOperator(operatorToken));

if (isNotLastOperation(i, tokenList.size())) {
String rightOperandToken = tokenList.get(i + 1);
throwIfConditionIsTrue(!isNumber(rightOperandToken));
}
}
}

private void throwIfConditionIsTrue(boolean isConditionTrue) {
if (isConditionTrue) {
throw new InvalidInputException("올바르지 않은 입력입니다.");
}
}

private boolean hasNotAnyToken(List<String> tokenList) {
return (tokenList.size() == 0);
}
private boolean isNumber(String token) {
return Pattern.matches("^[0-9]+$", token);
}
private boolean isOperator(String token) {
return Pattern.matches("\\+|-|\\*|/", token);
}
private boolean isNotLastOperation(int index, int tokenListSize) {
return (index + 1 < tokenListSize);
}
}
37 changes: 37 additions & 0 deletions src/main/java/domain/calculator/Parser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package domain.calculator;

import java.util.*;
import java.util.stream.Collectors;

public class Parser {

private final Queue<String> tokenQueue;

public Parser(String expression) {
tokenQueue = new LinkedList<>();
ExpressionValidator validator = new ExpressionValidator();

List<String> tokenList = splitExpressionToList(expression);
validator.validateTokenList(tokenList);
validator.validateTokenSequence(tokenList);
tokenQueue.addAll(tokenList);
Comment on lines +11 to +17

Choose a reason for hiding this comment

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

처음에 생각하신 Parser의 책임은 무엇인가요?
그리고 지금 Parser는 어떤 역할들을 하고있나요?
Parser의 역할이 너무 큰 것 같아요.

}

private List<String> splitExpressionToList(String expression) {
String[] tokens = expression.split(" +");
return filterMeaningfulToken(tokens);
}

private List<String> filterMeaningfulToken(String[] tokens) {
return Arrays.stream(tokens)
.filter(token -> !token.equals(""))
.collect(Collectors.toList());
Comment on lines +26 to +28

Choose a reason for hiding this comment

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

스트림을 잘 활용하시네요 👍🏻

}

public String nextToken() {
return tokenQueue.poll();
}
public boolean hasNext() {
return !tokenQueue.isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package domain.calculator.exception;

public class InvalidInputException extends RuntimeException {

Choose a reason for hiding this comment

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

커스텀 익셉션을 정의하셨네요!
커스텀 익셉션의 장점과 단점은 무엇일까요?


private String message;

public InvalidInputException(String message) {
this.message = message;
}

@Override
public String getMessage() {
return message;
}
}
32 changes: 32 additions & 0 deletions src/main/java/domain/expression/Expression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

package domain.expression;

import domain.expression.operator.Operator;

public class Expression {

private int left;
private int right;
private Operator operator;

private Expression(int left, int right, Operator operator) {
this.left = left;
this.right = right;
this.operator = operator;
}

public static Expression of(int left, int right, String operator) {
return new Expression(left, right, Operator.from(operator));
}
public static Expression of(Expression left, int right, String operator) {
int leftResult = left.evaluate();
return new Expression(leftResult, right, Operator.from(operator));
}
public static Expression from(int left) {
return of(left, 0, "+");
}

public int evaluate() {
return operator.operate(left, right);
}
}
8 changes: 8 additions & 0 deletions src/main/java/domain/expression/operator/Addition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package domain.expression.operator;

public class Addition extends Operator {

public int operate(int left, int right) {
return left + right;
}
}
13 changes: 13 additions & 0 deletions src/main/java/domain/expression/operator/Division.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package domain.expression.operator;

import domain.expression.operator.exception.DivideByZeroException;

public class Division extends Operator {

public int operate(int left, int right) {
if (right == 0) {
throw new DivideByZeroException("0으로 나눌 수 없습니다.");
}
return left / right;
}
}
8 changes: 8 additions & 0 deletions src/main/java/domain/expression/operator/Multiplication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package domain.expression.operator;

public class Multiplication extends Operator {

public int operate(int left, int right) {
return left * right;
}
}
17 changes: 17 additions & 0 deletions src/main/java/domain/expression/operator/Operator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package domain.expression.operator;

public abstract class Operator {

public static Operator from(String operator) {
if (operator.equals("+")) {
return new Addition();
} else if (operator.equals("-")) {
return new Subtraction();
} else if (operator.equals("*")) {
return new Multiplication();
}
return new Division();
Comment on lines +6 to +13

Choose a reason for hiding this comment

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

지금은 연산자가 4개 뿐이지만 연산자와 비례해서 이 메소드의 길이가 증가할것같아요.
if문의 분기를 어떻게 없앨 수 있을까요?
상속이 아닌 인터페이스 구현을 통해 다형성을 활용해보세요.

}

public abstract int operate(int left, int right);
}
8 changes: 8 additions & 0 deletions src/main/java/domain/expression/operator/Subtraction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package domain.expression.operator;

public class Subtraction extends Operator {

public int operate(int left, int right) {
return left - right;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package domain.expression.operator.exception;

public class DivideByZeroException extends RuntimeException {

private String message;

public DivideByZeroException(String message) {
this.message = message;
}

@Override
public String getMessage() {
return message;
}
}
32 changes: 32 additions & 0 deletions src/main/java/ui/printer/ConsolePrinter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package ui.printer;

public class ConsolePrinter implements Printer {
private final String GREET_MESSAGE = "계산기 프로그램입니다.\n" +
"숫자와 사칙연산 기호를 공백으로 구분하여 입력해주세요.\n" +
"프로그램을 종료하시려면 exit을 입력해주세요.";

private final String INPUT_REQUEST_MESSAGE = "식 입력: ";

private final String OUTPUT_MESSAGE = "answer: %d\n";
private final String EXIT_MESSAGE = "프로그램을 종료합니다.";
Comment on lines +4 to +11

Choose a reason for hiding this comment

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

상수처리 👍🏻


@Override
public void greet() {
System.out.println(GREET_MESSAGE);
}

@Override
public void printWaitingForInputText() {
System.out.print(INPUT_REQUEST_MESSAGE);
}

@Override
public void printResult(int result) {
System.out.printf(OUTPUT_MESSAGE, result);
}

@Override
public void printExitMessage() {
System.out.println(EXIT_MESSAGE);
}
}
Loading