본문 바로가기
개발&프로그래밍

[JAVA] 효과적인 예외 처리 전략

by 재아군 2024. 11. 16.

[JAVA] 효과적인 예외 처리 전략

Java에서 예외 처리는 프로그램의 안정성과 유지보수성에 큰 영향을 미친다.

이 글에서는 효과적인 예외 처리 전략과 실제 적용 방법에 대해 알아본다.

 

 

 

체크 예외 vs 언체크 예외

 

체크 예외 (Checked Exception)

  • 컴파일 시점에 처리가 강제되는 예외
  • 복구가 가능한 상황에서 사용
  • 예: IOException, SQLException
public void readFile(String path) throws IOException {
    try (FileReader reader = new FileReader(path)) {
        // 파일 읽기 로직
    } catch (IOException e) {
        // 구체적인 에러 처리
        throw new FileProcessingException("파일 처리 중 오류 발생", e);
    }
}

 

언체크 예외 (Unchecked Exception)

  • RuntimeException을 상속
  • 프로그램 오류를 나타내는 경우 사용
  • 예: NullPointerException, IllegalArgumentException
public void processUser(User user) {
    if (user == null) {
        throw new IllegalArgumentException("사용자 정보가 null일 수 없습니다.");
    }
    // 처리 로직
}

 

커스텀 예외 설계

비즈니스 로직에 특화된 예외는 커스텀 예외로 만드는 것이 좋다.

public class BusinessException extends RuntimeException {
    private final ErrorCode errorCode;

    public BusinessException(ErrorCode errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public ErrorCode getErrorCode() {
        return errorCode;
    }
}

// 사용 예시
public class OrderService {
    public void placeOrder(Order order) {
        if (order.getAmount() <= 0) {
            throw new BusinessException(
                ErrorCode.INVALID_ORDER_AMOUNT,
                "주문 금액은 0보다 커야 합니다."
            );
        }
    }
}

 

예외 처리 비용

예외 처리는 성능에 영향을 미칠 수 있으므로 적절한 사용이 중요하다.

 

나쁜 예시

public boolean isNumeric(String str) {
    try {
        Integer.parseInt(str);
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

 

좋은 예시

public boolean isNumeric(String str) {
    if (str == null || str.isEmpty()) {
        return false;
    }
    return str.matches("\\d+");
}

 

로깅 전략

효과적인 로깅은 문제 해결의 핵심이다.

 

로그 레벨 가이드라인

public void processOrder(Order order) {
    logger.debug("주문 처리 시작: {}", order.getId());  // 상세 디버깅용

    try {
        validateOrder(order);
        logger.info("주문 검증 완료: {}", order.getId());  // 중요 비즈니스 이벤트

        processPayment(order);
        logger.info("결제 처리 완료: {}", order.getId());

    } catch (PaymentException e) {
        logger.error("결제 처리 실패: {}", order.getId(), e);  // 심각한 오류
        throw e;
    }
}

 

예외 처리 체인

@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = 
        LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(BusinessException.class)
    public ResponseEntity<ErrorResponse> handleBusinessException(
            BusinessException e) {
        logger.warn("비즈니스 예외 발생: {}", e.getMessage());
        return ResponseEntity
            .badRequest()
            .body(new ErrorResponse(e.getErrorCode(), e.getMessage()));
    }

    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorResponse> handleException(Exception e) {
        logger.error("예상치 못한 오류 발생", e);
        return ResponseEntity
            .internalServerError()
            .body(new ErrorResponse(
                ErrorCode.INTERNAL_SERVER_ERROR, 
                "서버 내부 오류가 발생했습니다."
            ));
    }
}

 

실전 예외 처리 전략

  1. 예외 계층 구조 설계
    • 최상위 비즈니스 예외 정의
    • 세부 예외는 이를 상속
    • 일관된 예외 처리 가능
  2. 예외 전환
    • 하부 기술의 예외를 비즈니스 예외로 변환
    • 추상화 계층 유지
    • 예외 처리 일원화
  3. 리소스 정리
    • try-with-resources 적극 활용
    • 명시적인 리소스 해제
    • 메모리 누수 방지

 

댓글