티스토리 뷰

자바에는 언어 차원에서 제공하는 수많은 예외들이 있다

언어 자체에서 제공되므로 잘 작성되어 있고 네이밍도 적절하며 문서화도 잘 되어있다

그런데도 불구하고 구글링을 하다 보면 커스텀 예외를 사용하는 경우들이 적지 않게 있다

 

어설픈 경우 다음과 같은 형태를 가질 것이고 이런 경우에는 커스텀한 이름을 붙인 것 외에 아무런 장점이 없다

토이 프로젝트를 진행하던 당시에는 이런 예외를 사용했으나 이제 와서 생각해 보면 어설프다고 밖에 할 수 없다

public class TokenException extends AuthenticationException {

  public TokenException(String message) {
    super(message);
  }

  public TokenException(String msg, Throwable cause) {
    super(msg, cause);
  }
}

 

조금 더 나은 형태는 다음과 같다

Exception, RuntimeException의 변수로는 부족해 추가 정보를 넣기 위해 커스텀해 사용할 것이다

public class TokenException extends AuthenticationException {

  @Getter
  private String code;

  public TokenException(String message) {
    super(message);
  }

  public TokenException(String msg, Throwable cause) {
    super(msg, cause);
  }
}

 

커스텀 예외를 만들지 않아도 된다고 생각하는 이유는 Throwable의 detailMessage를 쓰면 된다고 생각하기 때문이다

개발자들이 보기 친절한 예외 메세지를 적어둘 수도 있지만 예외 코드를 던지는 방법으로도 활용 가능하다

Throwable.detailMessage

 

개인적인 경험으로 커스텀 예외들은 문서화 작업을 늘리는 주범이다

다양한 상황에서 공통적으로 사용할 수 있도록 예외를 사용하고 있었다면 하지 않아도 될 

수많은 작업들이 커스텀 예외 때문에 너무 많이 발생했고 이는 문서화 노가다로 이어진다

 

예외 처리는 ExceptionResolver, ExceptionHandler를 통해 전역적으로 처리할 수 있는데

detailMessage에 있는 예외 코드를 꺼내 공통된 처리를 해줄 수 있을 것이다

예외 코드 외에 어떤 상황에서 터지는 예외인지 등 부가 정보는 인터페이스 문서에 정의해 두면 깔끔해진다

그래서 나는 표준 예외 + 인터페이스 문서 조합을 선호하는 편이다

 

표준 예외만 사용하고 싶더라도 서비스를 만들다 보면 커스텀 예외를 채택할 수 밖에 없는 상황들이 있다

1. 컨벤션으로 정해져있거나

2. 백엔드만 고쳐서 해결되는 상황이 아니거나

3. 별에 별 부가 정보를 다 때려 박는 경우 거나

 

위와 같은 이유로 예외 처리 또한 프로젝트 설계 단계부터 치밀하게 고민해 만들어야 한다

커스텀 예외가 무조건 나쁜 것만은 아니지만 표준 예외보다 더 좋을 순 없다고 생각한다

커스텀 예외를 한번 만들기 시작하면 이후엔 우후죽순 생겨날지 모른다

대규모 프로젝트의 경우 비즈니스 코드 익히기도 어려운 상황에 수 많은 비지니스 예외까지 확인해야 한다

 

모든 예외는 생성자를 타고 들어가 보면 Throwable.fillInStackTrace()를 호출하게 된다

fillInStackTrace()를 호출하는 생성자는 아래 사진 외에도 더 있으니 Throwable을 찾아가 보자

Throwable constructor

 

fillInStackTrace()가 느린 이유는 synchronized 키워드가 걸려있기 때문이다

Throwable.fillInStackTrace()

 

커스텀 예외를 사용해야 하는 경우에 대해 다음 글을 읽어보면 좋을 것 같다

물론 CustomException을 만들고 fillInStackTrace()를 재정의하지 않아도 대부분의 경우 아무 문제없을 것이다

성능이 매우 중요해서 메모리 사용량, 동시성을 어떻게든 쥐어짜고 싶을 때 사용해 볼 법하다

이왕 커스텀 예외를 만드는 김에 성능 향상도 노려보자

 

Java Exception 생성 비용은 비싸다. : NHN Cloud Meetup

Java Exception 생성 비용은 비싸다.

meetup.nhncloud.com

 

위 글에는 예외 생성 비용에 대한 링크가 달려있는데 오래전 글이라 그런지 링크가 죽어있었다

자료를 찾아보던 중 plumbr에서 벤치마크를 한 게 있어 첨부한다

객체를 생성하는데 0.029ns, 예외를 생성하는데 8.675ns 걸렸다

8.675ns가 fillInStackTrace()를 재정의하지 않아도 아무런 문제가 없을 것이라 생각한 이유다

인간이 인식할 수 있는 단위가 아니다

https://plumbr.io/blog/java/throwing-exceptions-slow-and-ugly

 

비판적으로 생각해 보면 예외 생성마다 8.675 - 0.029 = 8.646ns를 줄일 수 있으면

트래픽이 엄청나게 쏟아지는 프로젝트에서는 의미가 있지 않을까?

또한 위 벤치마크는 exceptional() 메서드 자체에서 RuntimeException을 직접 던져버리니

스택을 깊이 채울 필요도 없어 8.675ns가 나왔고 실제 사용 사례에서는 얼마나 깊은 스택을 채워야 할지 모른다

Nhn 글이 2015년에 작성된 점을 감안하면 훨씬 더 빨라졌을 테지만 대략적으로 살펴보자

1~5ms를 최대로 잡아 생각하고 객체 생성 비용인 0.029ns를 빼보면 결과는 다음과 같다

매 작업마다 어림 잡아 5ms를 아낄 수도 있는 것이다

0.999999971ms ~ 4.999999971ms

 

서비스 운영 환경에서 예외가 터졌을 때를 생각해 보면 세밀한 정보가 나오는 것은 당연히 좋지만

때로 너무 깊은 Stack Trace까지 찍혀 보이지 않아도 될 것 같다는 생각을 해본 적이 있을 것이다

그 생각을 실현시켜 보자, Throwable에 정의된 fillInStackTrace()를 재정의하면 된다

 

장점은 예외가 터졌을 때 길고 긴 trace 전체가 아닌 핵심적인 부분만 볼 수 있다는 점 (그렇게 만들어 둔 경우에만)

new Object() 보단 느리겠지만 거의 근접한 수준으로 빠르게 예외를 생성할 수 있다는 점이 있다

 

단점은 세밀한 정보를 얻을 수 없다는 점이다

다음 사진과 같이 단순하게 this만 반환하게 해 놨을 경우 어떤 클래스 몇 번째 라인에서 터졌는지도 나오지 않는다

따라서 프로덕션에서는 this만 반환하는 것보다는 조금 더 손을 써 예외가 터진 장소까지는 찍도록 하자

 

nhn 글에서는 다음 사진과 같이 예외를 캐시 해버리는 수준까지 사용한다고 하는데

예외를 사용하는 문맥이 달라지지 않는다면 저 방식으로 효율적으로 사용할 수도 있겠다

상황에 따라 같은 예외여도 다른 에러코드를 내려줘야 한다면

상황마다 상수가 생기는 것이니 관리 포인트가 늘어나 저렇게 사용하긴 어려울 것 같다

예를 들어 문자열 입력값에 대한 검증 후 터트리는 예외여도 응답이 다를 필요가 있다

ex) 회원 가입 페이지 / 게시글 작성 페이지 등과 같이 어떤 로직에서 발생한 것인지에 따라 i18n 응답이 달라야 하는 경우

이 부분은 서비스에 따라 다르게 적용하면 되겠다

https://meetup.nhncloud.com/posts/47

 

결론으로 이미 잘 작성된 표준 예외를 사용하자

자바의 예외 생성 비용을 고려해 서비스 성능을 극한으로 쥐어짜고 싶다면

커스텀 예외를 만들어 fillInStackTrace() 메서드를 stack depth 전체가 아닌 일부만 쌓도록 재정의하자

상황에 따라 달라지지 않는 예외라면 상수로 만들어두어 매번 생성하지 않아도 되게 하자

'Java > Effective Java' 카테고리의 다른 글

[Item71] Checked Exception 즐겨보기  (0) 2023.01.30
[Item70] Checked Exception 금지  (0) 2022.11.01
[Item69] 예외 기반 반복  (2) 2022.09.25
[Item68] 컨벤션 따르기  (1) 2022.09.11
[Item67] 최적화는 나중에  (0) 2022.08.28
댓글
링크
글 보관함
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Total
Today
Yesterday