TIL

[250304 TIL] Java 계산기 리팩토링

도원좀비 2025. 3. 4. 21:00

기존 코드의 문제점

  1. exit 체크 중복
    • "exit"을 입력하면 종료하는 로직이 여러 메서드에서 반복
    • if (input.equalsIgnoreCase("exit")) return input; 이 중복
  2. 불필요한 Getter와 Setter 존재  
    • 단순히 값을 저장하는 Getter와 Setter가 많아 불필요한 코드가 증가.
    • 메서드를 직접 호출해 값을 사용하는 방식으로 변경
  3. 중복된 사용자 입력 처리 코드
    • 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 제거, 직접 값 반환