예외는 진짜 예외 상황에만 사용하라

이진호·2022년 9월 5일
0

Effective Java

목록 보기
1/11
post-thumbnail

Item 69. 예외는 진짜 예외 상황에만 사용하라

다음은 잘못된 코드의 예시입니다.

try {
    int i = 0;
    while(true)
        range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {
}

일단 전혀 직관적이지 않다는 사실 하나만으로도 코드를 이렇게 작성하면 안 되는 이유는 충분합니다.(Item 67).

다음과 같이 표준적인 관용구대로 작성했다면 이해하기 쉬웠을 것입니다.

for (Mountain m: range) 
    m.climb();

예외를 써서 루프를 종료한 이유는, 잘못된 추론을 근거로 성능을 높여보려고 한 것입니다. JVM은 배열에 접근할 때마다 경계를 넘지 않는지 검사를 하는데, 일반적인 반복문도 이 검사를 중복하므로 하나를 생략하기 위해 위와 같이 코드를 짠것입니다. 하지만 이는 세 가지 면에서 잘못된 추론입니다.

  1. 예외는 예외 상황에 쓸 용도로 설계되었으므로 JVM 구현자 입장에서는 명확한 검사만큼 빠르게 만들어야 할 동기가 약하다(최적화에 별로 신경 쓰지 않았을 가능성이 크다).
  2. 코드를 try-catch 블로 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
  3. 배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다. JVM이 알아서 최적화해 없애준다.

예외를 사용한 반복문은 심지어 제대로 동작하지 않을 수도 있습니다. 반복문 안에 버그가 숨어 있다면 흐름 제어에 쓰인 예외가 이 버그를 숨겨 디버깅을 훨씬 어렵게 만들겁니다.(m.climb() 메서드 내부에서 ArrayIndexOutOfBoundsException 발생하는 경우)

예외는 오직 예외 상황에서만 써야 합니다. 절대로 일상적인 제어 흐름용으로 쓰여선 안 됩니다. 표준적이고 쉽게 이해되는 관용구를 사용하고, 성능 개선을 목적으로 과하게 머리를 쓴 기법은 자제해야합니다. 실제로 성능이 좋아지더라도 자바 플랫폼이 꾸준히 개선되고 있으니 최적화로 얻은 상대적인 성능 우위가 오래가지 않을 수 있으며, 숨겨진 미묘한 버그의 폐해와 어려워진 유지보수 문제가 계속 발생하게 됩니다.

이 원칙은 API 설계에도 적용됩니다. 잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 합니다. 특정 상태에서만 호출할 수 있는 '상태 의존적' 메서드를 제공하는 클래스는 '상태 검사' 메서드도 함께 제공해야 합니다(Iterator 인터페이스의 next와 hasNext가 대표적).

for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
    Foo foo = i.next();
    ...
}

만약 hasNext가 없었다면, 아래와 같이 클라이언트에서 예외 처리를 하게 됩니다.(나쁜 예시)

try {
    Iterator<Foo> i = collection.iterator();
    while(true) {
        Foo foo = i.next();
        ...
    }
} catch (NoSuchElementException e) {
}

상태 검사 메서드 대신 사용할 수 있는 선택지로 올바르지 않은 상태일 때 빈 옵셔널(Item 55)을 반환하는 방법도 있습니다. 상태 검사 메서드, 옵셔널, 특정 값 중 하나를 선택하는 지침은 아래와 같습니다.

  1. 외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다면 옵셔널이나 특정 값을 사용한다. 상태 검사 메서드와 상태 의존적인 메서드 호출 사이에 객체의 상태가 변할 수 있기 때문이다.
  2. 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행한다면 옵셔널이나 특정 값을 선택한다.
  3. 다른 모든 경우엔 상태 검사 메서드 방식이 조금 더 낫다고 할 수 있다. 가독성이 살짝 더 좋고, 잘못 사용했을 때 발견하기가 쉽다. 상태 검사 메서드 호출을 깜빡 잊었다면 상태 의존적 메서드가 예외를 던져 버그를 확실히 드러낼 것이다. 반면 특정 값은 검사하지 않고 지나쳐도 발견하기가 어렵다(옵셔널에는 해당하지 않는 문제다).

핵심 정리

예외는 예외 상황에서 쓸 의도로 설계되었다. 정상적인 제어 흐름에서 사용해서는 안 되며, 이를 프로그래머에게 강요하는 API를 만들어서도 안 된다.

출처

0개의 댓글