이펙티브 자바 Chapter10. 예외

January 18, 2022 - 5 minute read -
book effective-java

이펙티브 자바 10장에서는
예외를 효과적으로 활용하는 방법에 대해서 소개하고 있다.


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

예외는 일상적인 제어흐름에 사용되면 안된다. 예외의 상황에만 사용해야 한다.
API 설계할 때에도 클라이언트가 정상적인 제어 흐름에 예외를 사용할 일이 없게 해야 한다.

  • 성능 저하의 원인이 될 수 있음
  • 제대로 동작하지 않을 수 있음

상태 검사 메서드, 옵셔널, 특정 값 선택 방법

  • 상태가 변할 수 있다면 옵셔널이나 특정 값 사용
    • 상태 검사 메서드 호출 사이에 상태가 변할 수 있음
  • 성능이 중요한 상황에 메서드 호출이 중복된다면 옵셔널이나 특정 값 사용
  • 나머지 대부분의 경우에는 상태 검사 메소드 방식이 낫다.
    • 가독성 향상, 쉬운 디버깅
    • 상태 검사 메서드 호출이 빠졌다면 확실하게 예외 발생


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

  • 호출하는 쪽에서 복구하리라 여겨진다면 검사 예외를 사용하라
    • 사용자가 예외를 잡기만하고 별다른 조치가 없는 것은 좋지 않음
    • 검사 예외는 호출자가 예외 상황을 벗어나는 데 필요한 정보를 알려주는 메서드도 제공
  • 프로그래밍 오류를 나타낼 때는 런타임 예외 사용
    • 전제조건을 만족하지 못했을 경우 사용 (API 명세에 있는 제약을 지키지 못한 경우)
  • 검사와 비검사 오류를 구분해서 사용
    • 복구가 가능하다면 검사 예외
    • 복구가 불가능하거나 확신이 어렵다면 비검사 예외
  • 직접 구현하는 비검사 throwble 은 모두 RuntimeException 하위 클래스여야 함
    • Error 클래스를 상속해 사용하지 말자


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

  • 검사 예외를 과하게 사용하면 쓰기 불편할 수 있음
    • catch 블록 처리 또는 바깥쪽 전파
    • 스트림 안에서 직접 사용불가
    • 제대로 사용해도 발생할 수 있는 예외, 의미있는 조치를 취할 수 있는 경우 검사 예외 사용

검사 예외를 회피하는 방법

  • 옵셔널 사용
    • 예외가 발생한 예외 타입, 이유 등 부가 정보는 알 수 없음
  • 검사 예외 던지는 메서드를 2개로 나눠 비검사 예외 변경
    • 예외가 던져질지 여부를 boolean 값으로 반환
    • 더 유연해질 수 있음 (반드시 성공이라 판단, 실패시 중단을 원한다면 한줄로 작성 가능)


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

  • Exception, RuntimeException, Throwable, Error 직접 재사용 금지
    • 다른 예외들의 상위 클래스이기 때문에 안정적인 테스트 불가
  • 예외직렬화가 가능해야 함

표준 예외를 재사용의 장점

  • API 가 다른 사람이 익히고 사용하기 쉬워짐
  • 낯선 예외를 사용하지 않아 읽기 쉬움
  • 클래스 수가 적어지므로 메모리 사용량과 클래스 적재 시간 감소

주로 재사용되는 예외

  • IllegalArgumentException
    • 허용되지 않는 값이 인수로 전달 (null일 경우에는 NullPointerException 로 처리)
  • IllegalStateException
    • 객체가 메서드 수행하기에 적절하지 않은 상태
  • NullPointerException
    • null을 허용하지 않는 메서드에 null 제공
  • IndexOutOfBoundsException
    • 인덱스 범위를 넘었을 경우
  • ConcurrentModificationException
    • 허용되지 않는 동시 수정 발견
  • UnsupportedOperationException
    • 호출한 메서드가 지원하지 않을 때
  • ArithmeticException or NumberFormatException
    • 복소수나 유리수를 다루는 객체를 작성하는 경우


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

  • 상위 계층에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞춰 예외를 던져야 함 (exception translation, 예외 번역)
  • 저수준의 예외가 디버깅 도움이 된다면 예외 연쇄(exception chaining) 사용
  • 예외 전파보다 예외 번역이 우수, 그래도 남용하면 좋지 않음
    • 최대한 아래 계층에서 예외가 발생하지 않도록 해야 함
  • 아래 계층의 예외를 피할 수 없다면 상위 계층에서 조용히 처리 (예외를 전파하지 않음)
    • 이 경우 로킹 기능을 활용 추천
try {
    ...
} catch (LowerLevelException cause) {
  throw new HigherLevelException(cause);
}


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

  • 검사 예외는 따로 선언하고, 자바독 @throws로 각 예외를 문서화하자
    • 공통 상위 클래스로 선언하지 말자 (ex. Exception, Throwable)
  • 비검사 예외도 문서화하자
    • 일반적으로 프로그래밍 오류에 사용되지만 문서가 있으면, 오류가 나지 않도록 코딩하게 됨
    • public 메서드라면 필요한 전제조건 문서화
  • 비검사예외는 메서드 선언의 throws 목록에 넣지 말자 (자바독 @throws 태그로만 작성)
    • 검사와 비검사 예외를 확실히 구분
  • 클래스의 많은 메서드들이 같은 이유로 같은 예외를 던진다면 클래스 설명에 추가 (ex. NullPointerException)


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

  • 모든 매개변수와 필드의 값을 실패 메시지에 담아야 함
    • 현상을 보고 무엇을 고쳐야 할지 분석 가능 (ex. 최대, 최소, 인덱스 값 등)
    • 장황할 필요는 없음 (스택 추적에는 파일 명, 줄번호까지 기록되어 있음)
  • 예외는 실패과 관련된 정보를 얻을 수 있는 접근자 메서드를 적절히 제공해야 함
    • 포착한 실패 정보로 예외 상황을 복구
public IndexOutOfBoundException(int lowerBound, int upperBound, int index) {
    super(String.format("최소값: %d, 최댓값: %d, 인덱스: %d", lowerBound, upperBound, index));
    this.lowerBound = lowerBound;
    this.upperBound = upperBound;
    this.index = index;
}


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

  • 메서드가 실패해도 해당 객체는 메서드 호출 전 상태를 유지해야 함 (실패 원자적, failure-atomic)
    • 호출자가 오류 상태를 복구할 수 있음
  • 지키지 못한다면 실패 시 객체 상태를 API 설명에 명시

메서드를 실패 원자적으로 만드는 방법

  • 불변 객체로 만들면 해결 됨
  • 작업 수행에 앞서 매개변수 유효성 검사 (내부 상태 변경전, 잠재적 예외 가능성 거르기)
  • 객체의 임시 복사본에서 작업 수행
    • 작업이 성공하면 원래 객체와 교체
    • 데이터를 임시 자료구조에 저장해 작업하는 게 더 빠른 경우에 좋은 방식
  • 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌리는 방법
    • 내구성을 보장해야 하는 자료구조에 적당
    • 자주 쓰이는 방법은 아님


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

  • catch 블록을 비워두면 예외가 필요 없음
    • 예외는 문제 상황에서 적절한 조치를 취해야 함
  • 예외를 무시하기로 한 경우
    • catch 블록안에 주석으로 이유 남기기
    • 예외 변수명을 ignored로 변경