이번 계산기 프로젝트에서 발생한 문제와 그 해결 방법을 정리하려고 한다.
문제 상황
ArithmeticCalculator 객체를 매번 새로 생성할 때, 계산 결과가 results 리스트에 저장되지 않는 문제가 발생했다. 이는 매번 객체가 새로 생성되면서, 결과가 저장되지 않고 사라지는 상황이었다.
public void start() {
while (true) {
...
ArithmeticCalculator<Number> arithmeticCalculator = new ArithmeticCalculator<>(firstNumber, secondNumber, operation);
Number answer = arithmeticCalculator.calculate();
...
}
}
public class ArithmeticCalculator<T extends Number> {
private final T firstNumber;
private final T secondNumber;
private final Operation<T> operation;
private final List<T> results = new ArrayList<>();
public ArithmeticCalculator(T firstNumber, T secondNumber, Operation<T> operation) {
this.firstNumber = firstNumber;
this.secondNumber = secondNumber;
this.operation = operation;
}
public T calculate() {
T answer = operation.operate(firstNumber, secondNumber);
results.add(answer);
return answer;
}
}
매번 객체가 새로 생성되기 때문에 results 리스트도 초기화되며, 계산 결과가 제대로 저장되지 않는 현상이 발생했다.
해결 방법
private final ArithmeticCalculator<Number> arithmeticCalculator;
public CalculatorMachine(ArithmeticCalculator<Number> arithmeticCalculator) {
this.arithmeticCalculator = arithmeticCalculator;
}
public void start() {
while (true) {
// 첫 번째 숫자와 두 번째 숫자 및 연산자 입력 받는 로직
final String firstNumberStr = repeat(this::inputFirstNumber);
final String secondNumberStr = repeat(this::inputSecondNumber);
final String operatorStr = repeat(this::inputOperator);
// Number 형태로 변환시켜주는 로직
final Number firstNumber = parseNumber(firstNumberStr);
final Number secondNumber = parseNumber(secondNumberStr);
// 입력 값들을 받고 계산하는 로직
final Number answer = arithmeticCalculator.calculate(firstNumber, secondNumber, operatorStr);
Output.printCalculationResult(answer);
// 저장된 연산 결과들 중 Scanner로 입력받은 값보다 큰 결과값 들을 출력 하는 로직
final List<Number> filteredResults = arithmeticCalculator.getResultsGreaterThanInputNumbers(firstNumber,
secondNumber);
Output.printFilteredResults(filteredResults);
// 총 계산 결과를 출력하는 로직
final List<Number> history = arithmeticCalculator.getHistory();
Output.printAllHistory(history);
// 다시 계산할건지 물어보는 로직
final String ended = inputEnded();
if (ended.equalsIgnoreCase("exit")) {
break;
}
}
}
package level3.calculator;
import java.util.ArrayList;
import java.util.List;
import level3.operation.Operation;
public class ArithmeticCalculator<T extends Number> {
private final List<T> results = new ArrayList<>();
public T calculate(T firstNumber, T secondNumber, Operation<T> operation) {
T answer = operation.operate(firstNumber, secondNumber);
results.add(answer);
return answer;
}
}
ArithmeticCalculator 객체를 매번 새로 생성하지 않고, 프로그램 실행 내내 같은 인스턴스를 유지하도록 수정하여 문제를 해결했다. 이를 통해 results 리스트에 모든 결과가 정상적으로 저장되도록 하였다.
Operation 처리 고민
public class CalculatorMachine {
...
Operation<Number> operation = OperatorType.getOperation(operator);
...
}
또한, 처음에 CalculatorMachine 클래스에서 연산자 타입을 찾는 방식에 대한 고민이 있었다. 사람의 입력을 받는 계산기의 역할을 생각했을 때, 연산자 타입을 ArithmeticCalculator에서 처리하는 것이 더 자연스러울 것이라고 판단하여, 연산자 타입을 CalculatorMachine에서 넘겨주는 대신 ArithmeticCalculator에서 직접 처리하도록 수정하였다.
입출력 처리 및 가독성 개선
처음에는 CalculatorMachine 클래스에서 입력, 출력, 예외 처리를 모두 담당하고 있었는데, 이로 인해 코드의 가독성이 떨어지고 복잡해지는 문제가 발생했다.
특히, 사용자의 입력을 처리하는 부분에서 많은 중복과 불필요한 코드가 생겨났다. 예를 들어, 숫자와 연산자를 입력받고 이를 처리하는 로직이 여러 곳에 분산되어 있어 유지보수가 어려워졌다.
이러한 문제를 해결하기 위해, 입력과 출력을 담당하는 별도의 Input과 Output 클래스를 만들었다.
- Input 클래스는 사용자의 입력을 처리하는 역할을 한다.
- 숫자나 연산자를 입력받고, 잘못된 입력에 대한 예외 처리도 담당한다.
- 이를 통해 CalculatorMachine 클래스가 너무 많은 책임을 가지는 것을 방지하고, 입력 처리 로직을 한 곳에 집중시켜 코드의 가독성을 높였다.
- Output 클래스는 계산 결과를 출력하는 역할을 맡았다.
- 단순히 결과를 출력하는 것뿐만 아니라, results 리스트에 저장된 과거 계산 결과를 필터링하여 특정 조건에 맞는 값만 출력하거나, 전체 기록을 출력하는 등의 기능도 추가하였다.
- 이렇게 함으로써 출력과 관련된 로직도 한 곳에 집중되었고, 프로그램의 구조가 더 명확해졌다.
예시로, 사용자의 입력을 받는 부분은 Input 클래스로 이동하여 다음과 같이 처리된다
// 첫 번째 숫자를 입력 받는 로직
public static String readFirstNumber() {
final String input = scanner.next();
// 숫자 형식인지 확인하는 메서드
isValidNumber(input);
return input;
}
// 두 번째 숫자를 입력 받는 로직
public static String readSecondNumber() {
final String input = scanner.next();
// 숫자 형식인지 확인하는 메서드
isValidNumber(input);
return input;
}
// 연산자를 입력받는 로직
public static String readOperator() {
final String operator = scanner.next();
// 올바른 연산자인지 확인하는 메서드
isValidOperator(operator);
return operator;
}
그리고 계산 결과를 출력하는 부분은 Output 클래스로 이동하여 다음과 같이 처리된다:
// 첫 번째 숫자 입력 요구 메세지
public static void printFirstNumber() {
System.out.println("첫 번째 숫자를 입력해주세요.");
}
// 두 번째 숫자 입력 요구 메세지
public static void printSecondNumber() {
System.out.println("두 번째 숫자를 입력해주세요.");
}
// 연산자 입력 요구 메세지
public static void printOperator() {
System.out.println("연산자 (+, -, *, /)를 입력해주세요.");
}
이렇게 입력과 출력 기능을 각각 Input과 Output 클래스로 분리하여 코드의 가독성을 높이고, CalculatorMachine 클래스가 너무 많은 책임을 가지지 않도록 개선하였다.
예외 처리 개선
처음에는 AppException이라는 커스텀 예외를 Exception 클래스를 상속받아 처리하려 했지만, try-catch 문을 반복 사용하면서 코드가 지저분해지는 문제가 발생했다. 강의에서 들은 UncheckedException과 CheckedException의 개념을 참고하여, 숫자 2개 입력하는 것과 연산자 입력 오류는 컴파일 시점이 아니라 실행 중에 발생하는 오류 이므로 RuntimeException을 상속받는 방식으로 변경하여 예외 처리를 간소화하였다.
'회고' 카테고리의 다른 글
일정 관리 서비스 회고 및 리팩토링 (0) | 2024.10.07 |
---|---|
일정 관리 과제 회고 (1) | 2024.10.03 |
숫자 야구 게임 전체 회고 (2) | 2024.09.24 |
숫자 야구 게임 과제 Lv4 회고 (1) | 2024.09.19 |
숫자 야구 게임 Lv2 Lv3 회고 (0) | 2024.09.18 |