반응형

1. 개요

 수행하려는 일과 관련 없어 보이는 예외가 튀어나올 수 있을까? 있다. 바로 체인된 메서드가 저수준의 예외를 처리하지 않고 바깥으로 던져버릴때이다.

 

예외를 찾아서...

 

 예외 발생의 원인을 찾기 위해 내부 구현을 모조리 뒤져야한다는 것이다. 자연스레 결합도가 높아질것이다.

 만약 buy() 메서드에서 클라이언트에게 정확한 예외 메시지를 주고싶다는 등의 이유로 이 예외를 잡아 처리한다면 어떨까? 내부 구현을 분석한 후 적절한 예외 메시지를 던져야할것이다. 

 최종적으로 IllegalStatusException에 대한 catch 문을 추가하고 "상품 유효성 검사에 실패했습니다. 관리자에게 문의해주세요" 라는 예외를 던지도록 예외를 전환했다. 하지만 이것도 문제가된다.

 

2. checkStock() 에서 발생한 IllegalStatusException

 며칠 뒤 buy() 메서드 호출과 함께 어디선가 IllegalStatusException이 발생했다. 클라이언트에게는 "상품 유효성 검사에 실패했다"라는 예외 메시지 던져주고 있다. 그런데 확인해보니 이번 예외는 productValidation()가 아닌 checkStock() 에서 발생했고, 재고 관련 인스턴스를 생성할 때 발생했다.

 

재고 관련 문제가 발생했는데 "상품 유효성 검사에 실패했다"는 에러 메시지는 사용자와 개발자 모두를 혼란에 빠지게했다.

결론은 예외를 무작정 던져도 문제이지만, 무작정 던진 걸 상위 수준에서 무작정 catch 해서 처리하는 것도 문제라는 것이다.

 

자, 그럼 어떻게 했어야할까? productValidation()에서 예외를 잡아 본인의 추상화 수준에 맞는 예외로 전환해서 던졌어야한다. ProductValidationException과 같이 말이다. 예외를 밖으로 던지는 것 자체가 책임을 전가하는 것이기에 결합도 측면에서도 좋지 않다.

 

try{
    // 제품 유효성 체크 로직
}catch(IllegalStatusException e){
    throw new ProductValidationException("제품 유효성 체크에 실패하였습니다. 관리자에게 문의해주세요!")
}

 

이처럼 자신의 추상화 수준에 맞는 예외 클래스로 바꿔 던지는 것을 예외 번역(exception translation) 또는 예외 전환 이라고 한다.

 

3.  예외 체이닝

예외를 전환하려 했더니, 저수준 예외가 디버깅에 도움이 될것같아 예외를 살리고 싶다면 어떨까? 문제의 근본 원인인 저수준 예외를 고수준 예외에 실어보내는 방법을 사용해야 한다. 이를 예외 체이닝(exception chaining)이라고 한다.

 

try{
    // 제품 유효성 체크 로직
}catch(IllegalStatusException e){
    throw new ProductValidationException(e) // 예외 체이닝
}

 

class ProductValidationException extends Exception{
    ProductValidationException(Throwable cause){
        super(cause);
    }
}

 

대부분의 표준 예외는 예외 체이닝 생성자를 갖추고 있다. 예외 연쇄는 문제의 원인을 프로그램에서 접근(getCause 메서드)할 수 있게 해주며, 원인과 고수준 예외의 스택 추적 정보를 통합해준다.

 

정리

아래 계층의 예외를 예방하거나 스스로 처리할 수 없고, 그 예외를 상위 계층에 그대로 노출하기 곤란하다면 예외 전환을 사용하자. 무턱대고 예외를 전파하는 것보다 예외 전환이 우수한 방법이다. 하지만 남용해서는 안된다. 가능하다면 저수준 메서드가 반드시 성공하도록 해야한다. 예를들어 전달하는 하위 메서드에 매개변수를 전달하기 전에 매개변수에 대한 체크를 하는 방법이 있을것이다.

반응형

+ Recent posts