요약
•
저수준 예외를 잘 처리하고, 고수준에서 조용히 처리하고 로깅을 남겨두자.
•
그렇지 않다면 예외 번역과 연쇄 예외를 잘 활용하자.
예외 번역
•
메서드가 저수준의 예외를 처리하지 않고 바깥으로 전파하는 경우, 종종 수행하려는 일과 관련없어 보이는 예외가 튀어나오게 된다.
•
이는 내부 구현 방식을 드러내어 상위 레벨의 API를 오염시키기 때문에 문제가 된다. 다음 릴리스에서 구현 방식을 바꾸면 다른 예외가 튀어나와 기존 클라이언트 프로그램을 깨지게 할 수도 있다.
•
이러한 문제를 피하려면 상위 계층에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꾸어 던져야한다. 이를 예외 번역(exception translation)이라 한다.
try {
... // 저수준 추상화를 이용한다.
} catch (LowerLevelException e) {
// 추상화 수준에 맞게 번역한다.
throw new HigherLevelException(...);
}
Java
복사
•
아래는 List 인터페이스의 골격 구현 클래스인 AbstractSequentialList의 get 메서드이다.
/**
* 이 리스트 안의 지정한 위치의 원소를 반환한다.
* @throws IndexOutOfBoundsException index가 범위 밖이라면,
* 즉 ({@code index < 0 || index >= size()})이면 발생한다.
*/
public E get(int index) {
ListIterator<E> i = listIterator(index);
try {
return i.next();
} catch (NoSuchElementException e) {
throw new IndexOutOfBoundsException("인덱스: " + index);
}
}
Java
복사
•
이 예외번역은 List<E> 인터페이스의 get 메서드 명세에 명시된 필수사항이다.
예외 연쇄
•
예외 번역을 사용할 때, 저수준 예외가 디버깅에 도움이 된다면 예외 연쇄(exception chaining)를 사용하는 것이 좋다. 예외 연쇄란 근본 원인인 저수준 예외를 고수준 예외에 담아서 보내어, 필요하면 언제든 저수준 예외를 꺼내볼 수 있는 방식이다.
try {
... // 저수준 추상화를 이용한다.
} catch (LowerLevelException cause) {
throw new HigherLevelException(cause);
}
// 예외 연쇄용 생성자
class HigherLevelException extends Exception {
HigherLevelException(Throwalbe cause) {
super(cause);
}
}
Java
복사
•
대부분의 표준 예외는 예외 연쇄용 생성자를 갖추고 있다. 그렇지 않더라도 Throwalbe의 initCause 메서드를 사용해 예외의 원인을 직접 설정할 수 있다.
결론
•
예외를 무턱대고 전파하는 것보다는 예외 번역을 사용하는 것이 더 좋지만, 예외 번역을 남용해서는 안된다.
•
가능하다면 저수준 메서드가 반드시 성공하도록하여, 아래 계층에서는 예외가 발생하지 않도록 하는 것이 최선이다.
•
아래 계층에서의 예외를 피할 수 없다면, 상위 계층에서 해당 예외를 조용히 처리하여 문제를 API 호출자까지 전파하지 않는 방법을 차선책으로 하자. 이런 경우에는 로그를 분석해 추가 조치를 취할 수 있게, 적절한 로깅 기능을 활용해 기록해두는 것이 좋다.