규칙 57 - 예외는 예외적 상황에만 사용하라
규칙 58 - 복구 가능 상태에는 점검지정 예외를 사용하고, 프로그래밍 오류에는 실행시점 예외를 이용하라
규칙 59 - 불필요한 점검지정 예외 사용은 피하라


아래 책를 참고하여 학습한 내용을 정리/기록한 포스트입니다. 자세한 내용은 책을 참고하시기 바라며, 문제가 있을 경우 연락 부탁드립니다.


  • 조슈아 블로크, 이병준(옮긴이), Effective Java, 2판, 인사이트, 2015.



57. 예외는 예외적 상황에만 사용하라

예외는 예외적인 상황에만 사용하라. 평상시 제어 흐름(ordinary control flow)에 이용해서는 안된다.

  • 예외는 예외적 상황을 위해 고안된 것이기 때문에, JVM을 구현하는 사람 입장에서 보면 명시적 테스트만큼 빠르게 만들 이유가 별로 없다.
  • 쉽게 이해할 수 있는 표준적인 숙어대로 코딩해야지, 더 좋은 성능을 내 보려고 너무 머리를 많이 굴리면 곤란하다는 것이다.
  • 너무 머리를 많이 굴린 기법일수록 묘한 버그가 생길 가능성이 높고 유지보수 때문에 골치 아플 일도 많은 법.

클라이언트에게 제어 흐름의 일부로 예외를 사용하도록 강요하는 API를 만들지 말아라.

  • 특정한 예측 불가능 조건이 만족될 때만 호출할 수 있는 “상태 종속적(state-dependent)” 메서드를 가진 클래스에는 보통 해당 메서드를 호출해도 되는지를 알기 위한 “상태 검사(state-testing)” 메서드가 별도로 갖춰져 있다.

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

    i.hasNexe()와 같은 상태 검사 메서드가 없을 경우

    try {
    Iterator<Foo> i = collection.iterator();
    while (true) {
    Foo foo = i.next();
    ...
    }
    } catch (NoSuchElementException e) {
    }
  • 상태 검사 메서드를 제공하기 싫다면 부적절한 상태의 객체에 상태 종속적 메서드를 호출하면 null 같은 특이값(distinguished value)이 반환되도록 구현하는 방법도 있다.
    Iterator에서 nullnext()의 정상적 반환값일 수 있기 때문에 사용할 수 없다.

  • 특이값 방식

    외부적인 동기화 메커니즘 없이 병렬적으로 사용될 수 있는 객체거나, 외부적인 요인으로 상태 변화가 일어날 수 있는 객체라면 반드시 특이값 방식으로 구현. (상태 검사 메서드를 호출한 다음 상태 종속적 메서드를 호출하기까지의 시간 동안 객체 상태가 변할 수 있기 때문)

    상태 종속적 메서드가 하는 일을 상태 검사 메서드가 중복해서 하는 바람에 성능이 떨어질까 우려될 경우.

  • 상태 검사 메서드

    다른 모든 조건이 동일하다면 상태 검사 메서드를 두는 편이 대체로 바람직. 가독성 측면에서도 조금 더 낫고, API를 잘못 이용하는 경우도 쉽게 찾아낼 수 있기 때문.

    상태 검사 메서드를 실수로 호출하지 않으면 상태 종속적 메서드에서 예외가 발생하니까 버그가 분명하게 드러난다.

    실수로 특이값 검사를 생략하는 버그는 찾기 어려울 수 있다.(null 체크를 안할 경우?)


58. 복구 가능 상태에는 점검지정 예외를 사용하고, 프로그래밍 오류에는 실행시점 예외를 이용하라

핵심

복구 가능한 상태에는 점검지정 예외를 사용하고, 프로그래밍 오류를 나타내고 싶을 때는 실행지점 예외를 사용하라.

잘 모르겠다면 무점검 예외를 이용하는 편이 나을 것이다(규칙 59).

Java는 세 가지 종류의 throwable을 제공한다.

점검지정 예외(checked exception), 실행시점 예외(runtime exception), 오류(error)

무엇을 사용해야 하는지 딱 떨어지는 기준이 있는 것은 아니지만, 훌륭한 지침으로 삼을 만한 일반 규칙이 있다.

호출자(caller) 측에서 복구할 것으로 여겨지는 상황에 대해서는 점검지정 예외를 이용해야 한다.

  • 점검지정 예외를 사용할 것인지 무점검 예외(unchecked exception)를 사용할 것인지에 대한 가장 기본적인 규칙

  • 메서드에 선언된 점검지정 예외는 메서드를 호출하면 해당 예외와 관련된 상황이 발생할 수 있음을 API 사용자에게 알리는 구실을 한다.

    API 사용자에게 검점지정 예외를 준다는 것은, 그 상태를 복구할 권한을 준다는 뜻.
    사용자는 그 권한을 무시할 수 있지만 일반적으로 그렇게 하면 곤란하다(규칙 65, 예외를 무시하지 마라).

  • 점검지정 예외는 일반적으로 복구 가능한 상태를 나타내기 때문에, 호출자 측에서 상태를 복구하는 데 이용할 정보를 제공하는 메서드를 갖춰놓는 것이 아주 중요하다.

프로그래밍 오류를 표현할 때는 실행시점 예외를 사용하라.

  • 대부분의 런타임 예외는 선행조건 위반(precondition violation)을 나타낸다.

    클라이언트가 API 명세에 기술된 규악을 지키지 않았다는 뜻. (e.g. ArrayIndexOutOfBoundsException)

사용자 정의 무점검 throwable은 RuntimeException의 하위 클래스로 만들어야 한다.

  • 자바 언어 명세에 강제된 사항은 아니지만, 보통 오류(error)는 JVM이 자원 부족이나 불변식 위한 등, 더 이상 프로그램을 실행할 수 없는 상태에 도달했음을 알리기 위해 사용.

    이 관습은 거의 보편적으로 받아들여지고 있어서, Error의 하위 클래스는 새로 만들지 않는 것이 최선이다.

  • 예외를 만들 경우, 예외 정보 문자열을 파싱하는 것은 위험한 방법이다(규칙 10). 객체가 어떤 형식의 문자열로 변환되는지 상세히 명시하는 클래스는 별로 없다. 따라서 문자열 변환 결과는 구현마다, 그리고 릴리스마다 달라질 수 있기 때문에 예외를 문자열로 나타낸 결과를 파싱하는 코드는 이식성도 없고 깨지기도 쉽다.


59. 불필요한 점검지정 예외 사용은 피하라

점검지정 예외는 프로그래머 하여금 예외적인 상황을 처리하도록 장제함으로써 안정성을 높인다.

아래 경우에 해당하지 않을 경우 무점검 예외를 이용하는 것이 좋다.

  • API를 제대로 이용해도 예외 상황이 벌어지는 것을 막을 수 없을 때
  • API 사용자가 예외상황에 대한 조치를 취할 수 있을 때

API 사용자가 이보다 좋은 코드를 만들 수 없다면, 무점검 예외가 적당하다.

} catch (TheCheckedException e) {
throw new AssertionError();
}
} catch (TheCheckedException e) {
e.printStackTrace();
System.exit(1);
}

메서드가 던지는 예외가 하나뿐일 경우, 점검지정 예외를 없앨 방법이 없을지 고민해보는 것이 좋다.

  • 예외를 던지는 메서드를 둘로 나눠서 첫 번째 메서드가 boolean값을 반환 하도록 만드는 것

    /* as-is */
    try {
    obj.action(args);
    } catch (TheCheckedException e) {
    // 예외적 상황 처리
    }
    /* to-be: 상태 검사 메서드를 거쳐서 무점검 예외 메서드를 호출 */
    if (obj.actionPermitted(args)) {
    obj.action(args);
    } else {
    // 예외적 상황 처리
    }
  • 결과적으로 만들어지는 API는 규칙 57에서 설명한 상태 검사 메서드와 본질적으로 같기 때문에, 동일한 문제를 갖는다.
    그러므로, 외부적인 동기화 수단 없이 병렬적으로 이용될 가능성이 있는 객체거나, 외부에서 그 상태를 바꿀 가능성이 있는 객체라면 위의 리팩토링 기법은 적용할 수 없다.