본문 바로가기

Spring

에러메시지를 Enum Class에 담아 코드 개선하기

문제 : 에러 메시지가 코드에 직접 작성됨

  • 다양한 경우의 수의 에러 메시지가 비지니스 코드 내에서 직접 작성되어있기 때문에, 어떤 에러 메시지가 있는지 한눈에 알아보기 힘듦
 @Transactional
    public SignupResponse signup(SignupRequest signupRequest) {
        if (userRepository.existsByEmail(signupRequest.getEmail())) {
            throw new InvalidRequestException("이미 존재하는 이메일입니다.");
        }
 public SigninResponse signin(SigninRequest signinRequest) {
        User user = userRepository.findByEmail(signinRequest.getEmail()).orElseThrow(
                () -> new InvalidRequestException("가입되지 않은 유저입니다."));

        // 로그인 시 이메일과 비밀번호가 일치하지 않을 경우 401을 반환합니다.
        if (!passwordEncoder.matches(signinRequest.getPassword(), user.getPassword())) {
            throw new AuthException("잘못된 비밀번호입니다.");
        }

 

  • 동일한 내용의 에러 메시지가 코드 내에서 여러번 재사용되고 있음
  • 만약 같은 내용이지만 여러 곳에 분포되어 있는 에러 메시지가 추후에 변경된다면 직접 그곳을 찾아다니며 수정해줘야 함
    @Transactional
    public CommentSaveResponse saveComment(AuthUser authUser, long todoId, CommentSaveRequest commentSaveRequest) {
        User user = User.fromAuthUser(authUser);
        Todo todo = todoRepository.findById(todoId).orElseThrow(() ->
                new InvalidRequestException("Todo not found"));
public TodoResponse getTodo(long todoId) {
        Todo todo = todoRepository.findByIdWithUser(todoId)
                .orElseThrow(() -> new InvalidRequestException("Todo not found"));
@Transactional(readOnly = true)
    public List<ManagerResponse> getManagers(long todoId) {
        Todo todo = todoRepository.findById(todoId)
                .orElseThrow(() -> new InvalidRequestException("Todo not found"));

 

에러 메시지를 Enum Class에 담아서 관리하여 개선

에러 객체 분리에 대한 고민

  • ServerException을 좀더 구체적으로 나눌까(관리에러, 날씨 api에러 등..) 고민
    • 구체적으로 나눈다면 추후에 기능이 추가될 때마다 예외 객체들 또한 무수히 많아질 것 같고, 에러를 발생시킨 책임의 주체만 분명히 하고(서버인지, 클라이언트인지) 어차피 구체적인 내용은 에러 메시지를 통하여 전달하면 된다고 생각함
    • 기존에 AuthException(인증, 인가 에러), ServerException(서버에서 발생하는 에러), InvalidRequestException(클라이언트가 잘못된 요청으로 인해 발생하는 에러)를 유지함

 

Enum Class 생성

  • common 패키지 하위에 errorcode를 만들고 에러 메시지를 한번에 관리할 수 있는 ErrorCode  Enum Class를 생성
  • 각각의 Exception Class별로 구분하여 에러 메시지를 배치
public enum ErrorCode {
    // AuthException
    EMAIL_ALREADY_EXISTS("이미 사용 중인 이메일입니다."),
    USER_NOT_FOUND_BY_EMAIL("해당 이메일로 등록된 유저가 없습니다."),
    INVALID_PASSWORD("비밀번호가 잘못되었습니다."),
    AUTH_AND_AUTHUSER_REQUIRED("@Auth와 AuthUser 타입은 함께 사용되어야 합니다."),

    // ServerException
    WEATHER_DATA_FETCH_FAILED("날씨 데이터를 가져오는데 실패했습니다."),
    NO_WEATHER_DATA_FOUND("날씨 데이터가 없습니다."),
    NO_WEATHER_DATA_FOR_TODAY("오늘에 해당하는 날씨 데이터를 찾을 수 없습니다."),
    TOKEN_NOT_FOUND("토큰을 찾을 수 없습니다."),

    // InvalidRequestException
    TODO_NOT_FOUND("해당하는 할일이 없습니다."),
    MANAGER_NOT_FOUND("관리자가 없습니다"),
    NOT_ASSIGNED_TO_SCHEDULE("해당 일정에 등록된 담당자가 아닙니다."),
    CANNOT_REGISTER_AS_SELF("일정 작성자는 본인을 담당자로 등록할 수 없습니다."),
    USER_NOT_FOUND("유저가 존재하지 않습니다."),
    INVALID_SCHEDULE_CREATOR("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다."),
    INVALID_USER_ROLE("유효하지 않은 UserRole입니다."),
    USING_PASSWORD("이미 사용 중인 비밀번호 입니다.");


    private String message;
    private ErrorCode(String message){
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

 

ExceptionClass 수정

기존

public class InvalidRequestException extends RuntimeException {
    public InvalidRequestException(String message) {
        super(message);
    }
}

 

수정 후

public class InvalidRequestException extends RuntimeException {
    private ErrorCode errorCode;
    public InvalidRequestException(ErrorCode errorCode) {
        this.errorCode = errorCode;
    }

    public ErrorCode getErrorCode(){
        return this.errorCode;
    }
}

 

 

GlobalExceptionHandler 수정

기존

@ExceptionHandler(InvalidRequestException.class)
    public ResponseEntity<Map<String, Object>> invalidRequestExceptionException(InvalidRequestException ex) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return getErrorResponse(status, ex.getMessage());
    }

 

수정 후

@ExceptionHandler(InvalidRequestException.class)
    public ResponseEntity<Map<String, Object>> invalidRequestExceptionException(InvalidRequestException ex) {
        HttpStatus status = HttpStatus.BAD_REQUEST;
        return getErrorResponse(status, ex.getErrorCode().getMessage());
    }

 

 

개선점

  • enum을 사용하여 에러 메시지를 한곳에 모아뒀기 때문에, 한눈에 에러 메시지를 확인할 수 있음
  • 추후에 에러 메시지를 유지보수하기 용이해짐 (동일한 에러 메시지를 코드에서 직접 찾아서 변경할 필요 없이 enum 한 곳에서만 변경하면 됨)
  • 중복된 에러 메시지를 일일히 타이핑할일 없이 enum에 있는 메시지를 쓰면 되기 때문에 재사용성이 좋아짐 
  • 비지니스 로직에서 해당 에러 메시지를 봤을 때, 가독성 측면에서 향상됨

 

데이터 비교

개선 전 - 동일한 메시지가 여러 곳에서 사용되어 있고, 직접 서비스 코드 내에서 에러 메시지가 작성되어있어 유지보수에 용이하지 않음

 

개선 후 - Enum Class를 사용하여 에러 메시지를 한곳에서 관리하기 용이해짐