코드 개선 (refactoring)/Java

유효성 검사는 어디에서 해야 될까?

가지코딩 2025. 4. 18. 20:44

계산기 과제를 진행하던 중, 생각하게 된 주제이다.

 

level1: 클래스 없이 계산기 구현

level2: 클래스를 활용한 계산기 구현 - Calculator 클래스 분리

 

<참고>

 

'내일배움캠프(Spring 7기)/CH 2 계산기 과제' 카테고리의 글 목록

gajicoding 님의 블로그 입니다.

gajicoding.tistory.com

 


두 상황의 차이점을 잘 비교해보자


Level 1을 구현 후 피드백 받은 코드이다.

 

피드백 전

  • switch 문 내부에서 예외 발생
    • 실행 전 상태로 충분히 판단 가능한 예외를 굳이 실행에 맡김 (늦은 판단)
try {
    // Scanner로 num1, num2, symbol을 입력받은 후
    
    result = switch (symbol) {
        case '+' -> num1 + num2;
        case '-' -> num1 - num2;
        case '*' -> num1 * num2;
        case '/' -> num1 / num2;
        default -> throw new Exception("사칙연산(+, -, *, /) 기호를 입력해야 한다.");
    };
    System.out.println("계산 결과: " + result);
} catch(ArithmeticException e) {
    System.out.println("잘못된 입력: " + "0으로 나눌 수 없다." + "\n");
} (Exception e) {
    System.out.println("잘못된 입력: " + e.getMessage() + "\n");
}

 

피드백 후

  • switch 전에 예외 발생 가능 조건이 이미 존재 → 미리 체크하는 것이 더 좋음
  • 예외를 예측 가능하면 먼저 던져라 !!!
try {
    // Scanner로 num1, num2, symbol을 입력받은 후
    
    if(num2 == 0 && symbol == '/') {
        throw new ArithmeticException("0으로 나눌 수 없다.");
    }

    result = switch (symbol) {
        case '+' -> num1 + num2;
        case '-' -> num1 - num2;
        case '*' -> num1 * num2;
        case '/' -> num1 / num2;
        default -> throw new Exception("사칙연산(+, -, *, /) 기호를 입력해야 한다.");
    };
    System.out.println("계산 결과: " + result);
} catch (Exception e) {
    System.out.println("잘못된 입력: " + e.getMessage() + "\n");
}

Level 2 (클래스 분리)를 구현 후 피드백 받은 코드이다.

피드백 전

  • 유효성 검사 후 calculate() 메서드 실행
// App 클래스
if(nums[1] == 0 && symbol == '/') {
    System.out.println("잘못된 입력: " + "0으로 나눌 수 없다." + "\n");
    continue;
}

res = calculator.calculate(nums[0], nums[1], symbol);
// Calulator 클래스의 calculate 메서드
public ... calculate(int num1, int num2, char symbol) {
    int res;
    try {
        res = switch (symbol) {
            case '+' -> num1 + num2;
            case '-' -> num1 - num2;
            case '*' -> num1 * num2;
            case '/' -> num1 / num2;
            default -> throw new Exception("사칙연산(+, -, *, /) 기호를 입력해야 한다.");
        };
        ...
    } catch(Exception e) {
        System.out.println("잘못된 입력: " + e.getMessage() + "\n");
    }
    ...
}

 

 

피드백 후

  • 계산 책임은 Calculator에 있으므로, 예외도 내부에서 발생시켜야 한다.
  • App은 여러 계산기 중 하나일 뿐 → "이 계산기가 어떤 예외를 던질 수 있는지"만 알고 있으면 된다.
  • 역할 분리 원칙: 책임 있는 쪽에서 검증과 예외 발생까지 담당해야 재사용성이 높아진다.
// Main 클래스
res = calculator.calculate(nums, symbol);
// Calulator 클래스의 calculate 메서드
public ... calculate(int num1, int num2, char symbol) {
    int res;
    
    if(nums[1] == 0 && symbol == '/'){
        throw new ArithmeticException("0으로 나눌 수 없다.");
    }
        
    try {
        res = switch (symbol) {
            case '+' -> num1 + num2;
            case '-' -> num1 - num2;
            case '*' -> num1 * num2;
            case '/' -> num1 / num2;
            default -> throw new Exception("사칙연산(+, -, *, /) 기호를 입력해야 한다.");
        };
        ...
    } catch(Exception e) {
        System.out.println("잘못된 입력: " + e.getMessage() + "\n");
    }
    ...
}

정리

  • 입력값에 대한 검증은 입력을 받는 곳에서 한다
    • 사용자 입력, 외부 데이터 등은 가능한 빨리 유효성 체크
  • 로직 실행 중 발생할 수 있는 예외는 해당 로직 내부에서 처리하거나 던진다
    • ex: 계산, 변환, 파일 처리 등
  • 공통적인 검증이 필요할 경우, 유틸리티 클래스에 분리한다
    • 여러 곳에서 재사용 가능하도록

 

'코드 개선 (refactoring) > Java' 카테고리의 다른 글

getItems.add(...) vs addItem(...)  (0) 2025.04.21
Optional 제대로 사용하기  (1) 2025.04.19