지난번 사이드 프로젝트를 할 때 커스텀한 예외 클래스를 따로 개별적으로 관리하여 한 눈에 보기 힘들고 중복되는 코드가 많아 가독성이 떨어진다는 느낌을 받았다. 그래서 이번에는 enum 으로 예외 코드를 관리해보기로 하였다.
자바의 Enum 타입이란 ?
공식 문서에 따르면 자바에서 Enum 타입은 고정된 상수들의 집합을 나타내는 특별한 데이터 타입으로, 변수는 미리 정의된 값들 중 하나와 같아야 한다. 또한, 상수들은 쉼표로 구분되며 대문자로 표기한다.
public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY 
}
또한 상수에 대한 의미있는 값을 미리 부여하여 활용할 수 있는데,
이렇게 하면 Enum 상수가 단순한 식별자를 넘어서 특정한 의미나 행동을 직접적으로 나타낼 수 있게 된다.
public enum TrafficLight {
    RED("Stop"),
    YELLOW("Caution"),
    GREEN("Go");
    private final String action;
    TrafficLight(String action) {
        this.action = action;
    }
    public String getAction() {
        return action;
    }
}- 각 색상에 대한 행동 출력하기
public class TrafficLightTest {
    public static void main(String[] args) {
        // 각 신호등 색상에 대한 행동 출력
        for (TrafficLight light : TrafficLight.values()) {
            System.out.println(light + ": " + light.getAction());
        }
    }
}
예외 처리 without ExceptionCode
예외 코드를 사용하지 않았을 때는 RuntimeException 을 상속받은 BizException 을 만들어 모든 custom 한 예외 클래스에서 상속을 받았었다. 또한, 각 예외 클래스에 message 와 StatusCode 를 정해두었다.
간략한 예시는 아래와 같다.
- BizException
public abstract class BizException extends RuntimeException {
    public BizException(String message) {
        super(message);
    }
    public BizException(String message, Throwable cause) {
        super(message, cause);
    }
    public abstract int getStatusCode();
}- UserNotFoundException
public class UserNotFoundException extends BizException {
    private static final String MESSAGE = "존재하지 않는 회원입니다.";
    public UserNotFoundException() {
        super(MESSAGE);
    }
    public UserNotFoundException(Throwable cause) {
        super(MESSAGE, cause);
    }
    @Override
    public String getMessage() {
        return MESSAGE;
    }
    @Override
    public int getStatusCode() {
        return 404;
    }
}- Test
    @Test
    public void UserNotFoundTest() {
        UserNotFoundException exception = assertThrows(UserNotFoundException.class, () -> {
            userServiceImpl.findByUserId(1L);
        });
        assertEquals("존재하지 않는 회원입니다.", exception.getMessage());
    }
예외 처리 with ExceptionCode
Enum 타입으로 ExceptionCode를 정의하여 도메인별로 서비스에서 정의한 code 로 구분하도록 한다.
예시는 아래와 같다.
- ExceptionCode
@AllArgsConstructor @Getter
public enum ExceptionCode {
    USER_NOT_FOUND(NOT_FOUND,"USER_01", "존재하지 않는 회원입니다.");
    
    // 도메인별로 코드를 구분
    NOTICE_NOT_FOUND(NOT_FOUND,"NOTICE_01", "존재하지 않는 게시글입니다.");
    private final HttpStatus status; // HTTP 상태 코드
    private final String code; // 우리가 정의한 코드
    private final String message; // 응답 메시지
}- UserNotFoundException
@AllArgsConstructor @Getter
public class UserNotFoundException extends RuntimeException{
    private final ExceptionCode exceptionCode;
}- Test
    @Test
    public void UserNotFoundTest() {
        UserNotFoundException exception = assertThrows(UserNotFoundException.class, () -> {
            userServiceImpl.findByUserId(1L);
        });
        // Exception 코드의 메시지를 가져오도록 변경
        assertEquals("존재하지 않는 회원입니다.", exception.getExceptionCode().getMessage());
    }
정리
그러면 무엇을 쓰는 게 좋을까?
ExceptionCode 를 관리하게 되면 커스텀한 모든 예외를 한 눈에 볼 수 있고 도메인별로 코드를 관리할 수 있게 된다는 장점이 있으며 중복 코드를 줄일 수 있다. 그렇지만 예외가 추가되거나 제거해야하는 경우에는 ExceptionCode 와 커스텀한 예외 클래스 두 가지를 수정해야한다는 단점이 있다.
어떤 방법이 더 적합한지는 프로젝트의 규모와 작업 방식에 따라 달라지기 때문에 팀원과의 논의가 우선시되어야 할 것 같다.
※ ExceptionCode 로 관리할 시 장/단점
1. 장점
- 일관성 있는 예외 처리 가능
- 중앙 집중 관리로 가독성 향상
- 중복 코드 감소
2. 단점
- 예외 수정/삭제 시 두 곳을 수정해야하므로 유지보수 비용 증가
- 일관성있는 처리로 유연성 제한이 될 수 있음.
참고
https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
https://www.simplilearn.com/tutorials/java-tutorial/enum-in-java
https://dev-monkey-dugi.tistory.com/142
'Web' 카테고리의 다른 글
| [Spring] QueryDsl 로 동적 쿼리 짜기 with JPA (0) | 2024.01.07 | 
|---|---|
| [JPA] 양방향 순환 참조 (0) | 2023.12.24 | 
| [Spring] actuator 를 통한 shutdown endpoint 생성 (0) | 2023.12.10 | 
| [Spring] RestTemplate 과 WebClient (0) | 2023.12.03 | 
| [Spring] Swagger 에 JWT 설정하기 (0) | 2023.11.26 | 

