추상화 수준에 맞는 예외를 던지라
수행하려는 일과 관련 없어 보이는 예외가 튀어나오면 당황스럽다. 메서드가 저수준 예외를 처리하지 않고 바깥으로 전파해버릴 때 이런 경우가 종종 발생한다. 이러한 예외는 내부 구현 방식을 드러내어 윗 레벨 API를 오염시키기까지 한다.
예외 번역
이러한 문제를 피하기 위해 상위 계층에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야 한다. 이를 '예외 번역' 이라고 한다. 다음은 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 생성자까지 건네지게 한다.
class HigherLevelException extends Exception {
HigherLevelException(Throwable cause) {
super(cause);
}
}
예외 연쇄는 문제의 원인을 프로그램에서 접근할 수 있게 해주며, 원인과 고수준 예외의 스택 추적 정보를 잘 통합해준다.
저수준에서 최대한 처리하자
무턱대고 예외를 전파하는 것보다야 예외 번역이 우수한 방법이지만, 그렇다고 남용해서는 안된다. 가능하다면 저수준 메서드가 반드시 성공하도록 하여 아래 계층에서는 예외가 발생하지 않도록 하는 것이 최선이다. 이를 위해 다음 두 가지 방법을 사용할 수 있다.
- 상위 계층 메서드의 매개변수 값을 아래 계층 메서드로 건네기 전에 미리 검사하자.
- 상위 계층에서 예외를 조용히 처리하여 문제를 API 호출까지 전파하지 않는 방법이 있다면 이를 사용하자.
- 이 경우 발생한 예외는 java.util.logging과 같은 로깅 기능을 활용하여 기록해두면 좋다.