어제 예외처리를 일부로 throw IllegarArgument를 통해 전부 처리를 해주었다.
바로 오늘 전역예외처리 를 진행하려고 하기에 여태껏 예외를 발견하고 처리하는 것을 해왔다고 볼 수 있다.
저번에 도전기능과제에 커스텀예외클래스와 전역예외처리 클래스를 통해 예외처리를 해보아라 라는 명세가 있었는데, 마지막에 있는만큼 나한테는 꽤나 시간이 오래걸리는 작업이었다.
첫번째로 눈에 보이는 예외와 보이지 않는 예외들이 너무 방대해 내가 잘 처리하고 있는지 단계별 잔실수는 없는지 발목을 잡는 요소가 많았다.
두번째로 예외처리를 하는데 커스텀 예외 클래스와 전역처리에 대한 원리를 잘 몰랐다.
세번째로 구글링을 통해 찾아보면 대부분의 사람들이 Enum을 활용하여 생성하는 모습을 보이고 있는데 Enum을 잘 다루지 못했던 내가 보기에는 굉장히 어려워 보였다.
다만 운이 좋게도 예외처리 관련된 강의가 있었고, 예외처리 단계로 넘어가는 날 담당튜너님께서 예외처리에 대한 세션도 다뤄주셨기에 개념과 어떤역할을 하는지 등등 여러모로 이해가 얼추되어 구현할 수 있게 되었다.
@Getter
@Builder
public class ErrorResponse {
private int status; //상태 숫자 코드를 보여줄 필드
private String error; // 에러코드를 보여줄 필드
private String message; // 부연 설명을 위한 메세지 필드
}
튜터님의 세션을 통해 각 회사별로 오류코드를 응답시키는 통일된 틀이 있다는 것을 알게 되었고 접목시키려고 노력하였다.
@Getter
public class CustomException extends RuntimeException{ // RuntimeException 상속
//속성
private final ErrorCode errorCode;
//생성자
public CustomException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
}
문득 생각이 든 것이 커스텀 예외는 왜 런타임을 상속을 받아야될까가 궁금했다.
결론(3가지)
Checked Exception을 피할 수 있음
커스텀 예외를 정의할 때 RuntimeException을 상속받으면 언체크 예외가 됩니다. 이는 호출자가 예외를 처리하도록 강제받지 않기 때문에 코드가 더 간결해질 수 있습니다.
사용자 편의성
개발자가 특정한 상황에서 발생하는 예외를 명확하게 정의할 수 있어, 코드의 가독성을 높이고 예외 처리 로직을 간단하게 유지할 수 있습니다. 불필요한 예외 처리를 피할 수 있는 장점이 있습니다.
비즈니스 로직과의 일관성
대부분의 비즈니스 로직에서 발생하는 예외는 런타임 예외로 간주되므로, 커스텀 예외도 이를 따르는 것이 자연스럽습니다.
다음과 같이 Custom 클래스를 하나만 만들어준 이유는 가독성과 무분별한 파일생성을 막기 위해 만들었다.
왜냐하면 Enum class를 활용해서 내가 커스텀한 예외들을 관리할 것이기 때문이다.
@Getter
public enum ErrorCode {
//관리할 상수화시킬 필드
USER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 유저입니다."),
USER_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다."),
USER_FORBIDDEN(HttpStatus.FORBIDDEN, "해당 유저는 권한이 없습니다."),
EMAIL_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "이미 존재하는 이메일입니다."),
.
.
.
;
//속성
private HttpStatus status;
private String message;
//생성자
ErrorCode(HttpStatus status, String message) {
this.status = status;
this.message = message;
}
}
Enum을 써야하는 이유(중요)
@RestControllerAdvice
public class GlobalExceptionHandler {
//커스텀 예외처리
@ExceptionHandler(CustomException.class)
public ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
ErrorCode errorCode = e.getErrorCode();
ErrorResponse errorResponse = ErrorResponse.builder()
.status(errorCode.getStatus().value())
.error(errorCode.getStatus().name())
.message(errorCode.getMessage())
.build();
return ResponseEntity.status(errorCode.getStatus()).body(errorResponse);
}
// @Valid 실패 처리 (Validation 예외)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidationException(
MethodArgumentNotValidException ex
) {
String message = "유효성 검사에 실패하였습니다.";
if(ex.getBindingResult().getFieldError() != null ) {}
message = ex.getBindingResult().getFieldError().getDefaultMessage();
ErrorResponse response = ErrorResponse.builder()
.status(400)
.error("BAD_REQUEST")
.message(message)
.build();
return ResponseEntity.badRequest().body(response);
}
// 나머지 예외 서버에러 처리
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
ErrorResponse response = ErrorResponse.builder()
.status(500)
.error("INTERNAL_SERVER_ERROR")
.message(ex.getMessage())
.build();
return ResponseEntity.internalServerError().body(response);
}
}
@어노테이션
public ResponseEntity<DTO> handleException(Exception ex) {
Dto dto = Dto.builder()
.필드값들(),
.build();
return ResponseEntity.status.body(response);
}
new IllegalArgumentException()
-> new CustomException(ErrorCode.이넘안 상수코드)
예시
new IllegalArgumentException("존재하지 않는 유저입니다.")
-> new CustomException(ErrorCode.USER_NOT_FOUND))
USER_NOT_FOUND = USER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 유저입니다.")
저번에는 Enum을 사용하지 않고 커스텀 클래스를 하나하나 작성했는데 정말 편리하다는 것을 느꼈고,
초반 과제에서 어거지로 사용해보라는 이유를 조금은 알 수 있었던 파트였지 않았나 싶다.
결국에는 쓰이는 곳이 있고 알고 있어야지만 응용할 수 있는 것이고, 조금이라도 만져본 사람은 얼추
느낌만 알고 있을 지언정 이러한 과정을 통해 지식을 확고하게 다질 수 있게 되는 것 같다.
끝내지 못한 숙제를 할 수 있게된 날이었기에 속이 좀 시원한 날이었다.