수행하려던 일과 관련 없는 예외가 튀어나온다면 당황스러울 것이다. 메서드가 저수준 예외(구체화 단계의 예외)를 처리하지 않고 바깥으로 던져버리면 내부 구현 방식을 드러내어 윗 레벨 API 를 오염시킨다. 다음 릴리스에서 구현 방식을 바꾸면 다른 예외가 튀어나와서 기존 클라이언트 프로그램을 깨지게 할 수도 있는 것이다.
이 문제를 피하기 위해서는 상위 계층에서 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야 한다. 이를 예외 번역(exception translation)
이라고 한다.
try{
...
} catch(LowerLevelException e){
// 추상화 수준에 맞춘다
throw new HigherLevelException(...);
}
다음은 AbstractSequentialList
에서 수행하는 예외 번역의 예를 보자.
public E get(int index){
ListIterator<E> i = listIterator(index);
try{
return i.next();
}catch(NoSuchElementException e){
throw new IndexOutOfBoundsException("인덱스 : " + index);
}
}
저수준 예외가 디버깅에 도움이 된다면 예외 연쇄
를 사용하는 것이 좋다. 예외 연쇄
란 문제의 근본 원인인 저수준 예외를 고수준 예외에 실어 보내는 방식이다. 그러면 별도의 접근자 메서드(Throwable의 getCause())를 통해 필요하면 언제든지 저수준 예외를 꺼낼 수 있다.
try{
... // 저수준 추상화를 이용한다.
}catch(LowerLevelException cause){
// 저수준 예외를 고수준 예외에 실어 보낸다.
throw new HigherLevelException(cause);
}
고수준 예외의 생성자는 상위 클래스의 생성자에 원인
을 건네주어 최종적으로 Throwable(Throwable) 생성자까지 건네지게 한다.
class HigherLevelException extends Exception{
HigherLevelException(Throwable cause){
super(cause);
}
}
대부분의 표준 예외는 연쇄용 생성자를 가지고 있다. 예외 연쇄
는 문제의 원인을 프로그램에서 잡근할 수 있게 해주고, 원인과 고수준 예외의 스택 추적 정보를 잘 추적해준다.
무턱대고 예외를 전파하는 것보다 예외 번역이 우수한 방법이지만, 그렇다고 남용하면 곤란하다. 가능하다면 저수준 메서드가 반드시 성공하도록 하여 아래 계층에서는 예외가 발생하지 않도록 하는 것이 최선이다.
<정리>
아래 계층의 예외를 예방하거나 스스로 처리할 수 없고, 그 예외를 상위 계층에 그대로 노출하기 곤란하다면 예외 번역을 사용하라. 이때 예외 연쇄를 사용하면 상위 계층에는 맥락에 어울리는 고수준 예외를 던지면서 근본 원인도 알려줄 수 있어 오류를 분석하기에 좋다.