EJ3E: 10장 예외 - WordyWeedyHiker/WordyWeedyHiker.github.io GitHub Wiki

10장. 예외

예외를 제대로 활용해서 프로그램의 가독성, 신뢰성, 유지보수성을 높이자. 예외를 효과적으로 활용하는 지침을 다룬다.

아이템 69. 예외는 진짜 예외 상황에서만 사용하라

요약

  • 예외는 예외 상황에서 쓸 의도로 설계되었다.
    • 추론에 의한 최적화 용도로 사용하지 말자. 오히려 성능을 악화시킨다.
    • 실제로 성능이 좋아지더라도 자바 플랫폼이 꾸준히 개선되고 있다.
    • 표준 관용구를 사용하자.
  • 정상적인 제어 흐름에서 사용해서는 안된다.
    • 실제 버그에 의한 예외를 정상적인 종료 상황으로 숨길 수 있다. (심각!!)
  • 이를 프로그래머에게 강요하는 API를 작성해서는 안된다.
    • API가 정상적인 제어 흐름에서 응답 값을 주지 않고, 예외를 발생시키면, 이 API를 사용하는 프로그래머에게 이런 방식을 강요하게 된다. 주의하자.
// 코드 69-1 예외를 완전히 잘 못 사용한 예 - 따라하지 말 것! 
// 잘 못된 추론에 의해 직관적이 않게 예외를 이용해 loop를 종료하는 코드를 작성한 것이다.
try {
    int i = 0;
    while(true)
        range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {

}
// for-each loop
// 다음과 같은 표준적인 관용구로 작성하면 곧바로 이해가 된다.
for (Mountain m : range)
 m.climb();

예외를 써서 루프를 종료한 코드를 작성하기까지 잘못된 추론 과정

다음은 잘못된 추론이다.

  • JVM은 배열에 접근할 때마다 경계를 넘지 않는지 검사하는데,
    • 접근할 때(배역에 index로 접근 : range[i++]), 경계 검사에서 실패하면, Runtime에 ArrayIndexOutOfBoundsException이 발생한다.
  • 일반적인 반복문도 배열 경계에 도달하면 종료한다. 따라서 이 검사를(배열 경계 도달 여부) 반복문에도 명시하면(for-each loop) 같은 일이 중복된다.
    • for-each loop에서는 for문에서 1차 검사를 하고, 통과한 후, for문 block에 배열에 접근하는 코드가 있으면, 중복해서 2차 검사를 한다.

하지만 추론에 의한 최적화는 하지말자.

  • JVM에 입장에서 예외는 명확한 검사 만큼 빠르게 만들어야할 동기가 약하다. 예외는 예외상황에서만 발생하는 말그대로 매우 예외적으로 발생하기 때문이다.
    • 즉 JVM이 최적화에 별로 신경쓰지 않았을 가능성이 크다.
  • try-catch block 안에 코드는 JVM 최적화에 제한이 생긴다.
  • 배열을 순회하는 표준 관용구는 앞서 설명한 중복 검사를 수행하지 않도록 JVM이 알아서 최적화해 없애준다.

API 설계

  • '상태 의존적' 메서드를 제공하는 클래스는 '상태 검사' 메서도 함께 제공해야한다.
    • 상태 의존적 메서드를 사용하는 클라이언트가 상태 검사 메서드를 사용하도록 유도해야한다. 즉, 상태 검사를 하기 위해 예외를 사용할 일이 없도록 메서드로 제공하는 것이다.
  • Iterable interface의 next(),상태 의존적,메서드와 hasNext(),상태 검사,메서드가 해당된다.
    • for-each loop 관용구가 내부적으로 hasNext() 메서드를 이용해서 검사한다.

상태검사 메서드 대신 사용할 선택지

  • 상태검사
  • 옵셔널, Optional (아이템 55), e.g. Optional.empty()
  • 특정 값, e.g. null

상태검사, 옵셔널, 특정 값 중 하나를 선택하는 지침

  • 외부 동기화 없이(synchronized 없이) 여러 쓰레드가 동시에 접근할 수 있거나(multi threading), 외부 요인으로 상태가 변할 수 있다면(concurrent), 옵셔널이나 특정 값을 사용한다.
    • 상태 검사와 상태 의존적 메서드 호출 사이에 객체의 상태가 변할 수 있기 때문이다.
    • Optional은 불변객체(Immutable Object)이기 때문에 Thread safe하다.
  • 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메서드의 일부작업을 중복 수행한다면, 옵셔널이나 특정값을 선택한다.
  • 그외의 경우에는 상태 검사 메서드 방식이 가독성과 버그 발견 측면에서 낫다.
    • 반면 특정 값은 검사하지 않고, 지나쳐도 발견하기 어렵다.
      • 감사없이 지나칠 경우, runtime에 NullPointerException이 발생하는데 상황을 명확하게 설명해주지 않는 예외이다.
      • 실제 버그로 인한 NullPointerException 예외와 구별되지 않는다.

아이템 70. 복구할 수 있는 상황에는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라

요약

JAVA의 문제상황을 알리는 타입 : Throwable

  • 검사 예외
    • Exception
    • 컴파일타임에 throws 또는 try-catch 구문으로 처리해야하는 예외
  • 런타임 예외
    • RuntimeException
  • 에러
    • Error

언제 무엇을 사용해야 하는지 참고할 지침

  • 검사와 비검사 예외를 구분하는 기본 규칙
    • 호출하는 쪽에서 복구하리라 여겨지는 상황이라면 검사 예외를 사용하라
  • 검사 예외를 던지면, 호출자가 catch로 잡아서 처리하거나, 더 바깥으로 전파하도록 강제하게 된다.(컴파일타임)
    • 예외를 잡기만 하고, 처리를 않아하는 것은 좋지 않다.(아이템 77)
  • 비검사 Throwable은 런타임 예외와 에러, 두가지다.
    • 프로그램에서 잡을 필요가 없거나 혹은 통상적으로 잡지 말아야한다.
    • 복구 불가능하거나, 더 실행해봐야 득보다 실이 많은 경우
    • 이 Throwable을 잡지 않은 쓰레드는 적절한 오류 메시지를 내뱉으며 중단된다.

프로그래밍 오류를 나타낼 때는 런타임 예외를 사용하자.

  • 전제조건을 만족하지 못했을 때 발생한다.
  • 전제조건 위배란?
    • 클라이언트가 API의 명세에 기록된 제약을 지키지 못했다는 뜻
    • e.g. ArrayIndexOutOfBoundsException : 배열의 인덱스 제약, 0 ~ '배열크기 - 1'

항상 명확히 구분되지 않는다.

  • 확신하기 어렵다면 아마도 비검사 예외를 선택하는 편이 나을 것이다. (아이템 71)

에러(Error), 보통 JVM이 자원 부족, 불변식 깨짐 등 더이상 수행을 계속할 수 없는 상황을 나타낼때 사용한다.

  • e.g. 자원 부족, OutOfMemoryError
  • Error 클래스 상속해서 하위 클래스 만드는 일은 자제하자.
    • 우리가 만드는 비검사 Throwable은 RuntimeException의 하위 클래스여야한다.
    • Error는 상속하거나 throw문으로 던지는 일도 없어야한다.

Throwable은 직접 사용하지 말자.

  • 자바는 Throwable을 일반적 검사 예외처럼 다루지만, 정상적인 검사 예외보다 나을것이 없고, API 사용자를 헷갈리게 한다.

예외 역시 메서드를 정의할 수 있는 완벽한 객체

  • 예외에 정의된 메서드는 그 예외를 일으킨 상황에 관한 정보를 전달하는데 쓰인다.
    • 이런 정보를 전달하는 메서드가 없다면 오류 메시지를 파싱해야한다. 대단히 나쁜 습관이다. (아이템 12)
  • throwable 클래스는 대부분 오류 메시지 포맷을 상세히 기술하지 않는다.
    • JVM이나 릴리즈에 따라서 오류 메시지 포맷이 계속 달라질 수 있다는 의미이다.
    • 프로그래머가 오류 메시지를 파싱하는 경우, 코드가 깨지기 쉽고 동작하지 않을 수 있다.

검사 예외는 복구할 수 있는 조건일 때 발생하므로

  • 호출자가 예외상황에서 벗어나는데 필요한 정보를 알려주는 접근자 메서드를 예외 클래스에 정의해서 함께 제공하는 것이 중요하다. (아이템 75)

아이템 71. 필요 없는 검사 예외 사용은 피하라

아이템 72. 표준 예외를 사용하라

아이템 73. 추상화 수준에 맞는 예외를 던져라

아이템 74. 메서드가 던지는 모든 예외를 문서화하라

아이템 75. 예외의 상세 메세지에 실패 관련 정보를 담으라

아이템 76. 가능한 실패 원자적으로 만들라

아이템 77. 예외를 무시하지 말라