Spring boot

[리팩토링 & 예외처리] 인터페이스를 이용한 enum으로 예외처리 정돈하기.

개발중인 감자 2023. 11. 15. 17:24

📌 문제점 

 

기존의 예외 처리들을 하기 위해서는 RuntimeException을 상속받은 예외들을 각각 클래스를 만들어서 처리하였다.

하지만, 이렇게 예외 하나당 파일 하나를 만들면 각각 에러마다 핸들러를 만들어야하고, 

클래스도 많아져서 관리하기 힘들어졌다. 

 

 

📌 enum을 활용하자. 

enum은 열거형으로, 상수들을 정의해서 열거하여 편리하게 사용할 수 있는 클래스이다. 

밑에 처럼 모든 도메인에서 나올 수 있는 예외들을 한 곳에 다 넣었다. 

 

@AllArgsConstructor
@Getter
public enum ExceptionCode {


    INTERNAL_SERVER_ERROR("서버에 오류가 발생했습니다."),
    BAD_REQUEST("잘못된 입력값 입니다."),
    INVALID_REQUEST("잘못된 요청입니다."),
    STARTDATE_IS_LATER_THAN_ENDDATE("출발 일정이 도착 일정보다 늦습니다."),
    NO_SUCH_TRIP("해당하는 Trip이 없습니다."),
    NO_ITINERARY("해당되는 여정이 없습니다."),
    INCORRECT_ITNERARY_ORDER("잘못된 여정 순서입니다."),
    DUPLICATE_ITINERARY_ORDER("여정 순서가 중복됩니다."),
    EMPTY_ITINERARY("수정 할 여정 정보가 없습니다."),
    ILLEGAL_ARGUMENT_DEPARTUREPLACE("출발지를 입력하지 않았습니다."),
    ILLEGAL_ARGUMENT_ARRIVALPLACE("도착지를 입력하지 않았습니다."),
    ILLEGAL_ITINERARY_TYPE("잘 못 된 여정 타입입니다."),
    ITINERARY_ALREADY_DELETED("이미 삭제된 여정입니다.")

    ;

    private final String msg;
}

 

 

🌟 하지만, 

딱 봐도 어디 도메인에서 발생하는지 모르겠고, enum의 값들이 정돈되지 않았다. 

우리는 예외를 보낼 수 있는 ErrorResponseDTO를 따로 선언해서 활용하고 있었다. 

 

다음과 같이 예외처리를 리팩토링하고 싶었다. 

- 개발 중 처리할 custom 예외들은 DefaultException으로 처리한다. 

- 도메인 별로 각각의 Exception 은 DefaultException을 상속받는다. 

- 도메인 별로 발생한 예외들은 도메인 별 예외 코드를 만들어서 처리한다.

- 도메인 별 예외코드는 인터페이스 ExceptionCode를 구현한다. (핵심)

 

 

# 1 일단 모든 예외를 받을 수 있는 기본 DefaultException을 만들자.

@Getter
@NoArgsConstructor
public class DefaultException extends RuntimeException {

    ExceptionCode errorCode;
    String errorMsg;

    public DefaultException(ExceptionCode errorCode, String errorMsg) {
        super(errorCode.getMsg());
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    public DefaultException(ExceptionCode errorCode) {
        super(errorCode.getMsg());
        this.errorCode = errorCode;
        this.errorMsg = errorCode.getMsg();
    }
}

 

 

# 2 DefaultExcpetion을 처리하는 예외 처리 핸들러

@ExceptionHandler(value = {
        DefaultException.class
    })
    public ResponseEntity<ErrorResponseDTO> handleDefaultException(
        DefaultException e,
        HttpServletRequest request
    ) {
        log.error("custom error!!!! status : {} , errorCode : {}, message : {}, url : {}",
            e.getErrorCode().getStatus(),
            e.getErrorCode().getCode(),
            e.getErrorCode().getMsg(),
            request.getRequestURI()
        );

        return new ResponseEntity<>(
                ErrorResponseDTO.error(e.getErrorCode()),
                e.getErrorCode().getStatus()
        );
    }

 

 

# 3 예외처리 코드를 구현할 인터페이스 ExceptionCode 구현

public interface ExceptionCode {
    HttpStatus getStatus();
    String getCode();
    String getMsg();
}

 

 

▶️ TripExcpetionCode

@AllArgsConstructor
@Getter
public enum TripExceptionCode implements ExceptionCode {
    NO_SUCH_TRIP(NOT_FOUND, "NO_SUCH_TRIP", "해당하는 여행 정보가 없습니다."),
    TRIP_ALREADY_DELETED(FORBIDDEN, "TRIP_ALREADY_DELETED", "이미 삭제된 여행 정보입니다."),
    NOT_MATCH_BETWEEN_USER_AND_TRIP(FORBIDDEN, "NOT_MATCH_BETWEEN_USER_AND_TRIP", "로그인 유저와 유저의 여행 정보가 일치하지 않습니다.")
    ;

    private final HttpStatus status;
    private final String code;
    private final String msg;

}

 

 

▶️ 만약 TripException을 구현하고 싶을시, DefaultExcpetion을 상속받으면 된다. 

@Getter
public class TripException extends DefaultException {

    public TripException(ExceptionCode errorCode) {
        super(errorCode);
    }
}

 

 

▶️ 실행 결과

만약 TripId가 맞지 않아 검색결과가 없을 경우

 

 

🌟 해당 방법의 장점

 

열거형 타입으로 예외 처리를 작성하니, 클래스를 하나씩 만들 필요가 없다. 

예상되는 예외가 있다면, 그냥 예외처리 코드에 추가하면 된다. 

확장성, 유지보수에 용이하다.