CustomException이란 왜 필요할까?

주효은·2025년 5월 8일
0

GlobalExceptionHandler만으로는 부족했던 이유!

1. 기존 예외 처리 방법

처음 과제를 진행했을 때, 모든 예외를 하나의 GlobalExceptionHandler에서 처리하는 구조로 충분하다고 생각했습니다.
기존에 존재하는 IllegalArgumentException, EntityNotFoundException 등을 활용하면 공통적인 예외 상황을 무리 없이 처리할 수 있었기 때문입니다.

@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<?> handleIllegalArgument(IllegalArgumentException e) {
    return ResponseEntity.badRequest().body(
        ResponseUtil.fail(e.getMessage())
    );
}

@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<?> handleEntityNotFound(EntityNotFoundException e) {
   return ResponseEntity.status(HttpStatus.NOT_FOUND).body(
      ResponseUtil.error(404, e.getMessage())
        );
    }

이렇게 특정 예외마다 HTTP 상태 코드와 메시지를 정의하여 일관된 응답을 제공할 수 있었습니다.


2. 근데 어떤 문제가 있길래..??

예외가 '모호한' 상황을 만나게 되었습니다.

하지만 게시글 수정/삭제 로직을 개발하면서 문제가 발생했습니다.
작성자가 아닌 사용자가 해당 게시글을 수정하거나 삭제하려고 할 때, 기존의 EntityNotFoundException이나 IllegalArgumentException을 사용하자 상황 설명이 애매해졌습니다.

예를 들어, 작성자가 아닌 사용자가 접근할 때 EntityNotFoundException을 던지면 클라이언트 입장에서는 "해당 게시글이 존재하지 않는구나"라고 오해할 수 있습니다.
실제로는 존재하지만 권한이 없는 상태임을 표현할 방법이 없어진 것입니다.

이런 맥락에서 처음엔 RuntimeException을 사용해봤지만,
이 역시 클라이언트가 받아들이기에 예외의 의도나 맥락이 명확하지 않았고,
GlobalExceptionHandler에선 어떤 상황인지 판단할 수 있는 근거도 부족했습니다.


3. CustomException의 도입해보자

그래서 결국 도입한 것이 바로 BusinessException이라는 추상 클래스 기반의 CustomException입니다.

public abstract class BusinessException extends RuntimeException {
    private final HttpStatus status;

    public BusinessException(HttpStatus status, String message) {
        super(message);
        this.status = status;
    }

    public HttpStatus getStatus() {
        return status;
    }
}

이 클래스는 모든 비즈니스 예외의 공통 부모로,

  • 어떤 HTTP 상태로 응답할 것인지 (HttpStatus)
  • 어떤 메시지를 보낼 것인지 (message) 를 명확히 정의할 수 있도록 설계했습니다.

그리고 이 클래스를 상속받아 ForbiddenException처럼 실제 상황에 맞는 구체적인 예외 클래스를 정의했습니다.

public class ForbiddenException extends BusinessException {
    public ForbiddenException(String message) {
        super(HttpStatus.FORBIDDEN, message);
    }
}

4. GlobalExceptionHandler와의 통합

이제 GlobalExceptionHandler에서는 기존의 Exception 처리 방식과 함께
BusinessException 계열의 예외도 따로 처리할 수 있게 되었습니다.

@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<?> handleForbidden(ForbiddenException e) {
    return ResponseEntity.status(HttpStatus.FORBIDDEN)
            .body(ResponseUtil.error(403, e.getMessage()));
}

앞으로는 UnauthorizedException, ConflictException, BadRequestException
도메인 상황에 맞는 예외들을 만들어 가독성 좋은 코드를 작성할 수 있게 되었습니다.


5. 마무리하며: CustomException을 쓴다는 것의 의미

이번 경험을 통해, 단순히 "예외가 발생했다"는 신호 이상의 의미를 전달하는 것이 중요하다는 것을 깨달았습니다. 예외는 개발자와 클라이언트 모두에게 현재 상황을 자세히 설명하고 디버깅 및 유지보수, 그리고 사용자의 경험을 높이는 중요한 과정임을 알게 되었습니다.

GlobalExceptionHandler 하나로 시작했던 저의 예외처리 설계는,
이제는 CustomException을 통해 훨씬 더 명확하고 의미 있는 구조로 개발할 수 있을 것 같습니다!


0개의 댓글