[리팩토링 & 예외처리] 인터페이스를 이용한 enum으로 예외처리 정돈하기.
📌 문제점
기존의 예외 처리들을 하기 위해서는 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가 맞지 않아 검색결과가 없을 경우
🌟 해당 방법의 장점
열거형 타입으로 예외 처리를 작성하니, 클래스를 하나씩 만들 필요가 없다.
예상되는 예외가 있다면, 그냥 예외처리 코드에 추가하면 된다.
확장성, 유지보수에 용이하다.