예외는 예외 상황에서만 써야 하지, 절대로 일상적인 제어 흐름용으로 쓰여서는 안된다. 또한, 이를 프로그래머에게 강요하는 API를 만들어서도 안된다.
try { // 성능이 2배 정도 느리다
int i = 0;
while(true)
range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}
배열의 원소를 순회하는데, 무한루프를 돌다가 배열의 끝에 도달해 예외가 발생하면 끝을 내는 로직으로 작성한 코드이다. 직관적이지 않다는 점 하나만으로도 제어 흐름용으로 예외를 사용하면 안되는 이유는 충분하다.
for (Mountain m : range)
m.climb();
실제 예외를 사용한 쪽이 표준 관용구보다 2배 정도 느리다.
잘 설계된 API는, 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없어야 한다.
특정 상태에서만 호출할 수 있는 상태 의존적 메서드를 제공하는 클래스는, 상태 검사 메서드도 함께 제공한다면 예외를 사용하지 않을 수 있다.
Iterator
인터페이스를 예시로 들어보자. next
는 상태 의존적 메서드, hasNext
는 상태 검사 메서드에 해당한다.
// 두 메서드 덕분에 표준 for 관용구를 사용할 수 있음
for (Iterator<Foo> i = collections.iterator(); i.hasNext(); ) {
Foo foo = i.next();
}
만약 Iterator
가 hasNext
를 제공하지 않았다면 그 일을 다음과 같이 클라이언트가 대신 했어야 할 것이다.
try {
Iterator<Foo> i = collection.iterator();
while(true) {
Foo foo = i.next();
...
} catch (NoSuchElementException e) {
}
이처럼 반복문에 예외를 사용하면 코드가 헷갈리고 성능도 좋지 않으며, 엉뚱한 곳에서 발생한 버그를 본의 아니게 숨기기도 한다.
상태 검사 메서드 이외에, 올바르지 않은 상태일 때 빈 Optional
혹은 null
같은 특수한 값을 반환하는 방법도 존재한다.
💡 언제 무엇을 쓰는 것이 좋을까?
📚 핵심 정리
예외는 예외 상황에서 쓸 의도로 설계되었다. 정상적인 제어 흐름에서 사용해서는 안 되며, 이를 프로그래머에게 강요하는 API를 만들어서도 안 된다.