아이템 69: 예외는 진짜 예외 상황에만 사용하라
예외는 오직 예외 상황에서만 쓰여야 하며, 일상적인 제어 흐름용으로 사용되어서는 안 된다.
예외 코드를 사용하면 JVM에서 최적화할 수 있는 여지가 적어진다.
try {
int i = 0;
while(true) range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {} // don't
for (Mountain m : range) m.climb(); // do
잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없어야 한다.
- 특정 상태에서만 호출할 수 있는 상태 의존적 메서드를 제공한다면, 상태 검사 메서드도 함께 제공해야 한다.
- 외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다면 옵셔널이나 특수값(null 등)을 사용한다.
- 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행한다면 옵셔널/특수값을 사용한다.
아이템 70: 복구할 수 있는 상황에는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라
자바에는 클라이언트가 무조건 처리해야 하는 검사 예외(checked exception), 처리하지 않아도 되는 비검사 예외(unchecked exception)이 있다.
예외 사용법
- 호출하는 쪽에서 복구하리라 여겨지는 상황이라면 검사 예외를 사용하라.
- 프로그래밍 오류를 나타낼 때는 런타임 예외를 사용하자.
- 확실하지 않다면 비검사 예외를 사용하자.
- Error는 관습적으로 복구 불가능하고 JVM이 더이상 프로그램을 실행할 수 없을 경우를 나타낸다.
- Exception, RuntimeException, Error를 상속하지 않는 throwable을 만들면 안 된다.
- 검사 예외라면 복구에 필요한 정보를 알려주는 메서드도 제공하자.
아이템 71: 필요 없는 검사 예외 사용은 피하라
꼭 필요한 검사 예외는 프로그램의 안전성을 높여주지만, 남용하면 사용하기 힘든 API가 만들어진다.
예외 상황에서 복구가 불가능하다면 비검사 예외를 던지자.
검사 예외를 회피하는 방법
- 옵셔널을 반환한다.
- 부가 정보를 제공할 수 없지만 간단하게 처리할 수 있다.
- 검사 예외를 던지는 메서드를 2개로 쪼개어, 객체의 상태를 검사하는 메서드를 분리한다.
- 여러 스레드가 동시에 접근하는 상황에서는 적합하지 않다.
옵셔널만으로는 상황 처리에 충분하지 않을 경우에만 검사 예외를 던지자.
아이템 72: 표준 예외를 사용하라
널리 사용되는 표준 예외를 재사용하면 여러 장점이 있다.
- API를 다른 사람이 사용하기 쉬워진다.
- API를 사용한 프로그램도 읽기 쉬워진다.
- 예외 클래스가 적을수록 메모리 사용량도 줄고, 클래스 적재 시간도 감소한다.
대표적인 표준 예외들
IllegalArgumentException
IllegalStateException
NullPointerException
IndexOutOfBoundsException
ConcurrentModificationException
UnsupportedOperationException
유의사항
Exception, RuntimeException, Throwable, Error는 여러 예외를 포괄하는 일종의 추상 클래스 역할을 하므로, 직접 던지지 말자.
아이템 73: 추상화 수준에 맞는 예외를 던지라
- 상위 계층에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야 한다.
- 아래 계층의 예외를 예방하거나 스스로 처리할 수 없고, 그 예외를 상위 계층에 그대로 노출하기 곤란하다면 예외 번역을 사용하자.
try {
// 저수준 추상화를 이용한다.
} catch (LowerLevelException e) {
// 추상화 수준에 맞게 번역한다.
throw new HigherLevelException(...);
}
저수준 예외를 고수준 예외에 실어 보내는 예외 연쇄를 사용하면 유용하다.
아이템 74: 메서드가 던지는 모든 예외를 문서화하라
메서드가 던지는 예외는 메서드를 올바로 사용하는 데 중요한 정보이기 때문에 문서화해야 한다.
검사 예외는 항상 따로따로 선언하고, 각 예외가 발생하는 상황을 자바독의 @throws 태그를 사용해 정확히 문서화하자.
비검사 예외는 메서드 선언의 throws 목록에 넣지 말자.
- 자바독에서는 메서드 선언의 throws에 등장하고 @throws에도 명시한 예외를 시각적으로 구분해주기 때문
한 클래스에 정의된 많은 메서드가 같은 이유로 같은 예외를 던진다면 클래스 설명에 일괄적으로 추가할 수도 있다.
아이템 75: 예외의 상세 메시지에 실패 관련 정보를 담으라
예외 메시지를 남기는 이유는 실패 원인을 추적하기 위해서이다.
따라서 예외가 발생할 때는 실패 메시지에 예외에 관여한 모든 매개변수와 필드의 값을 담아야 한다.
단, 모든 정보를 담으려고 하거나 보안과 관련되어 있는 중요 정보까지 담을 필요는 없다.
아이템 76: 가능한 한 실패 원자적으로 만들라
호출한 메서드가 실패하더라도 해당 객체는 호출 전 상태를 유지하는 것을 실패 원자성이라고 한다.
실패 원자적으로 만드는 방법
- 불변 객체로 설계한다.
- 작업을 수행하기 전에 매개변수의 유효성을 검사한다.
- 실패할 가능성이 있는 모든 코드를 객체의 상태를 바꾸는 코드보다 앞에 배치한다.
- 객체의 임시 복사본에서 작업을 수행한 후에 성공적으로 완료되면 원래 객체와 교체한다.
- 작업 도중에 발생하는 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌린다.
실패 원자성은 항상 보장할 수 있는 것은 아니므로, 비용을 고려해 판단해야 한다.
메서드 명세에 기술한 예외라면 가급적 예외가 발생하더라도 객체의 상태는 변하지 않아야 하고, 상태가 변한다면 API 주석에 해당 사항을 명시해야 한다.
아이템 77: 예외를 무시하지 말라
예외가 선언된 API는 해당 예외가 발생했을 때 조치를 취해야 한다.
try catch문으로 예외를 잡아놓고, 아무 조치를 취하지 않으면 안 된다.
불가피하게 예외를 무시하기로 했다면, 예외 변수의 이름을 변경하고 무시하기로 한 이유를 주석으로 남기자.