Java 예외 처리의 Best Practice들을 정리해 봤다
// X
catch (Throwable) {
// Do something
}
Throwable
모든 예외와 오류의 상위 클래스이다. catch 문에도 사용은 할 수 있지만, 모든 예외 뿐만 아니라 오류까지 잡아버리게 된다. 오류는 JVM이 던지는 것으로 어플리케이션에서 처리할 수 있는 것이 아니다
비슷한 이유로 RuntimeException이나 Exception을 잡을 때도 정말 필요한지 생각해보자
// 검증을 늦게하는 경우
1. 입력을 받는다 (잘못된 엔티티 필드 값)
2. 요청 DTO로 매핑되 컨트롤러로 전달한다
3. 컨트롤러가 서비스로 전달한다
4. 서비스가 DTO를 엔티티로 변환할때 검증한다
// 검증을 빨리하는 경우
1. 입력을 받는다 (잘못된 엔티티 필드 값)
2. 요청 DTO에서 검증한다
Put
메소드의 멱등성을 위해 updatePost()
메소드가 던지는 PostNotFoundException
을 사용해 포스트가 존재하지 않을 시 savePost()
로 분기한다. // 예외로 분기하는 잘못된 코드
@PutMapping("/posts/{postId}")
public ApiResponse<PostDto> updatePost(
@PathVariable Long postId,
@RequestBody final PostRequest postRequest
) {
try {
PostDto post = postService.updatePost(postId, postRequest);
return ApiResponse.response(post);
} catch (PostNotFoundException e) {
PostDto post = postService.savePost(postRequest);
return ApiResponse.response(post);
}
}
// X
catch (SomeException e) {
log.error("some info");
log.error("another info");
}
// O
catch (SomeException e) {
log.error("some info, another info");
}
예외 전파라고도 한다. 자세한 것은 이전에 정리한 적 있다
단 예외 체이닝을 하기전, 감싸고 있는 예외가 새로운 효용을 주는지 생각해보자. 아래 같은 예외 체이닝은 별로 도움이 되지 않는다
public void persistCustomer(Customer c) throws MyPersistenceException {
// persist a Customer
}
public void manageCustomer(Customer c) throws MyBusinessException {
// manage a Customer
try {
persistCustomer(c);
} catch (MyPersistenceException e) {
throw new MyBusinessException(e, e.getCode());
}
}
public void createCustomer(Customer c) throws MyApiException {
// create a Customer
try {
manageCustomer(c);
} catch (MyBusinessException e) {
throw new MyApiException(e, e.getCode());
}
}
try {
service.getSomething(); // 예외를 던지는 메소드
}
finally {
throw new SomeException(); // 예외 체이닝이 끊긴다
}
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
... 무수히 많은 로그들 ...
Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
하나의 예외에 대해서 로그도 남기고 전달도 한다면 여러 로그 메시지가 기록되게 된다. 로그를 보며 디버깅할때 매우 힘들어질 수 있다. 둘 중 하나만 하자
예외를 로깅할것이라면 처리할때 로깅해야한다
빨리 던져라
늦게 잡아라
https://howtodoinjava.com/best-practices/java-exception-handling-best-practices/
https://dzone.com/articles/9-best-practices-to-handle-exceptions-in-java