TIL
[250304 TIL] Java 계산기 리팩토링
도원좀비
2025. 3. 4. 21:00
기존 코드의 문제점
- exit 체크 중복
- "exit"을 입력하면 종료하는 로직이 여러 메서드에서 반복
- if (input.equalsIgnoreCase("exit")) return input; 이 중복
- 불필요한 Getter와 Setter 존재
- 단순히 값을 저장하는 Getter와 Setter가 많아 불필요한 코드가 증가.
- 메서드를 직접 호출해 값을 사용하는 방식으로 변경
- 중복된 사용자 입력 처리 코드
- FirstInput(), SecondInput(), OperatorInput() 등이 유사한 구조를 가짐.
- 숫자 또는 연산자를 검증하는 코드가 중복
리팩토링 목표
- 중복 제거: "exit" 처리와 사용자 입력 검증을 하나의 메서드로 통합
- 가독성 향상: ArithmeticCalculator와 challenge의 역할을 분리
- 불필요한 Getter와 Setter 제거: 직접 메서드를 호출해 처리
- 유지보수 용이성: 유지보수 시 최소한의 코드 수정으로 변경 가능하도록
리팩토링 과정
1. exit 체크 로직을 메서드로 분리
ArithmeticCalculator.java
private boolean isExitCommand(String input) {
return input.equalsIgnoreCase("exit");
}
(리팩토링 전 코드)
if (input.equalsIgnoreCase("exit")) return input;
(리팩토링 후 코드)
if (isExitCommand(input)) return input;
2. 불필요한 Getter와 Setter 제거
ArithmeticCalculator.java
(리팩토링 전 코드)
private String firstInput;
private String secondInput;
private char operator;
public String getFirstInput() {
return firstInput;
}
public void setFirstInput(Scanner sc) {
System.out.println("첫 번째 숫자를 입력하세요:");
firstInput = sc.nextLine().trim();
}
(리팩토링 후 코드)
public String getFirstInput(Scanner sc) {
System.out.println("첫 번째 숫자를 입력하세요 ('exit' 입력 시 종료): ");
return sc.nextLine().trim();
}
3. 중복된 사용자 입력 처리 코드
ArithmeticCalculator.java
(리팩토링 전 코드)
if (!validateInput(input)) {
continue;
}
(리팩토링 후 코드)
private boolean isValidNumber(String input) {
try {
Double.parseDouble(input);
return true;
} catch (NumberFormatException e) {
return false;
}
}
4. 불필요한 Scanner 사용 최소화
ArithmeticCalculator.java
(리팩토링 전 코드)
private final Scanner sc = new Scanner(System.in);
(리팩토링 후 코드)
public void setFirstInput(Scanner sc) {
리팩토링 후 코드
ArithmeticCalculator.java
package cal;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
//제네릭 T가 Number를 상속
public class ArithmeticCalculator<T extends Number> {
private final List<Double> results = new ArrayList<>(); // 결과 값 저장 리스트
private final Scanner sc = new Scanner(System.in); // 사용자 입력을 위한 Scanner 객체
// 결과 리스트 반환 ( 원본 보호를 위해 복사본을 반환)
public List<Double> getResults() {
return new ArrayList<>(results); // 원본 보호를 위해 복사본 반환
}
// 결과 리스트 설정
public void setResults(List<Double> newResults) {
results.clear();
results.addAll(newResults);
}
// 첫 번째 숫자 입력
public String FirstInput() {
while (true) {
System.out.println("첫 번째 숫자를 입력하세요 ('exit' 입력 시 종료): ");
String input = sc.nextLine().trim();
if (isExitCommand((input))) return input; // exit를 입력받았을 때 반환
if (isValidNumber(input)) return input; // 유효한 숫자인 경우 반환
System.out.println("올바른 숫자를 입력하세요.");
}
}
// 두 번째 숫자 입력
public String SecondInput() {
while (true) {
System.out.println("두 번째 숫자를 입력하세요 ('exit' 입력 시 종료): ");
String input = sc.nextLine().trim();
if (isExitCommand((input))) return input; // exit를 입력받았을 때 반환
if (isValidNumber(input)) return input; // 유효한 숫자인 경우 반환
System.out.println("올바른 숫자를 입력하세요.");
}
}
// 연산자 입력
public char OperatorInput() {
while (true) {
System.out.println("연산자를 입력하세요 (+,-,*,/) 또는 'exit' 입력 시 종료: ");
String input = sc.nextLine().trim();
if (isExitCommand((input))) return 'e'; // exit를 입력받았을 때 반환(char로 받아서 e)
if (isValidOperator(input)) return input.charAt(0); // 유요한 연산자일 경우 반환
System.out.println("올바른 연산자를 입력하세요.");
}
}
// 추가 기능 입력
public String AdditionalInput() {
while (true) {
System.out.println("결과 목록에서 큰 값을 보려면 숫자를 입력하세요.");
System.out.println("결과값의 합계를 보려면 'sum'을 입력하세요.");
System.out.println("첫 번째 결과를 삭제하려면 'remove' 입력 하세요.)");
System.out.println("초기 화면으로 돌아가려면 Enter를 누르세요.");
System.out.print("입력: ");
String input = sc.nextLine().trim();
// 각 조건 유효성 검사
if (input.isBlank() || input.equalsIgnoreCase("sum") || isValidNumber(input) || input.equalsIgnoreCase("remove")) return input;
System.out.println("올바른 숫자 또는 'sum'을 입력하세요.");
}
}
private boolean isExitCommand(String input) {
return input.equalsIgnoreCase("exit");
}
// 숫자 유효성 검사
public boolean isValidNumber(String input) {
try {
Double.parseDouble(input);
return true;
} catch (NumberFormatException e) {
return false;
}
}
// 삭제 유효성 검사
public boolean isValidRemove(String input) {
if (input.equalsIgnoreCase("remove")) {
removeList();
return true;
}
return false;
}
// 연산자 유효성 검사
public boolean isValidOperator(String input) {
return input.length() == 1 && "+-*/".contains(input);
}
// 추가 기능 처리
public void processAdditionalFunctions(String input) {
if (input.equalsIgnoreCase("sum")) {
sumRange(); // 합계 기능 실행
return;
}
if (input.equalsIgnoreCase("remove")) {
isValidRemove(input); // 결과 삭제 기능 실행
return;
}
try {
double filterNum = Double.parseDouble(input);
printBiggerResultsWithForEach(filterNum); // 특정 값보다 큰 값 출력
} catch (NumberFormatException e) {
System.out.println("숫자를 입력하지 않았습니다.");
}
}
// 연산 수행
public double calculate(T num1, char operator, T num2) {
try {
Operation operation = Operation.fromSymbol(String.valueOf(operator));
double result = operation.apply(num1, num2);
results.add(result);
return result;
} catch (IllegalArgumentException e) {
System.out.println("연산 중 오류 발생: " + e.getMessage());
return Double.NaN;
}
}
// 특정 범위의 합계 계산
public void sumRange() {
int start, end;
while (true) {
System.out.println("시작값을 입력하세요: ");
String startInput = sc.nextLine().trim();
// 정수값 유효성 검사
if (isValidNumber(startInput)) {
start = Integer.parseInt(startInput);
break;
}
System.out.println("올바른 숫자를 입력하세요.");
}
while (true) {
System.out.println("끝값을 입력하세요: ");
String endInput = sc.nextLine().trim();
// 정수값 유효성 검사
if (isValidNumber(endInput)) {
end = Integer.parseInt(endInput);
break;
}
System.out.println("올바른 숫자를 입력하세요.");
}
// end값이 start보다 커야 하는 조건 추가
if (start > end) {
System.out.println("시작 인덱스는 끝 인덱스보다 작거나 같아야 합니다.");
return;
}
// 합계를 계산
double sum = sumResults(start, end);
System.out.printf("입력한 범위: [%d, %d]%n해당 범위 내의 결과 값 합계: %.2f%n", start, end, sum);
}
// 결과 합계 기능 매서드
public double sumResults(int start, int end) {
try {
return results.subList(start - 1, end).stream()
.mapToDouble(Double::doubleValue)
.sum();
} catch (IndexOutOfBoundsException e) {
System.out.println("유효하지 않은 인덱스 범위입니다. 범위를 확인하세요.");
return 0.0;
}
}
// 결과 리스트에서 첫 번째 값 삭제
public void removeList() {
if (!results.isEmpty()) {
results.remove(0);
System.out.println("첫 번째 결과값이 삭제되었습니다.");
} else {
System.out.println("삭제할 결과값이 없습니다.");
}
}
public void printBiggerResultsWithForEach(double num) {
results.stream()
.filter(result -> result > num)
.forEach(result -> System.out.println("필터링된 값: " + result));
}
// 연산을 위한 내부 enum 클래스
public enum Operation {
ADD("+", (a, b) -> a.doubleValue() + b.doubleValue()),
SUBTRACT("-", (a, b) -> a.doubleValue() - b.doubleValue()),
MULTIPLY("*", (a, b) -> a.doubleValue() * b.doubleValue()),
DIVIDE("/", (a, b) -> {
if (b.doubleValue() == 0) throw new IllegalArgumentException("0으로 나눌 수 없습니다.");
return a.doubleValue() / b.doubleValue();
});
private final String symbol;
private final Calculator calculator;
Operation(String symbol, Calculator calculator) {
this.symbol = symbol;
this.calculator = calculator;
}
public double apply(Number a, Number b) {
return calculator.calculate(a, b);
}
public static Operation fromSymbol(String symbol) {
for (Operation op : values()) {
if (op.symbol.equals(symbol)) {
return op;
}
}
throw new IllegalArgumentException("유효하지 않은 연산자입니다. (+, -, *, / 만 허용)");
}
@FunctionalInterface
interface Calculator {
double calculate(Number a, Number b);
}
}
}
challenge.java
package cal;
import java.util.Scanner;
public class challenge {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//ArithmeticCalculator.java 상속받아서 변수선언
ArithmeticCalculator<Double> calculator = new ArithmeticCalculator<>();
while (true) {
// 첫 번째 숫자 입력
String firstInput = calculator.FirstInput();
if (firstInput.equalsIgnoreCase("exit")) break;
// 연산자 입력
char operator = calculator.OperatorInput();
if (operator == 'e') break; // 'e'는 exit을 의미
// 두 번째 숫자 입력
String secondInput = calculator.SecondInput();
if (secondInput.equalsIgnoreCase("exit")) break;
// 문자열을 숫자로 변환
double num1 = Double.parseDouble(firstInput);
double num2 = Double.parseDouble(secondInput);
// 연산 수행 및 결과 저장
double result = calculator.calculate(num1, operator, num2);
// 결과 출력
if (!Double.isNaN(result)) {
System.out.println("현재 결과 값: " + result);
System.out.println("전체 결과 내역: " + calculator.getResults());
}
// 추가 기능 입력
String additionalInput = calculator.AdditionalInput();
if (additionalInput.isBlank()) continue; // Enter 입력 시 초기화면
// 추가 기능 수행
calculator.processAdditionalFunctions(additionalInput);
}
// 스캐너 종료
sc.close();
System.out.println("계산기를 종료합니다.");
}
}
리팩토링의 결과
| 기존 코드 | 리팩토링 |
| "exit" 체크 중복 | isExitCommand 메서드로 통합 |
| 숫자 입력 검증 중복 | isValidNumber() 메서드로 통합 |
| Scanner 객체 여러 번 선언 | Scanner를 외부에서 주입 |
| 불필요한 Getter와 Setter 존재 | 불필요한 Setter 제거, 직접 값 반환 |