Unchecked Exception : RuntimeException 에러
// Bad
try {
int i = 0;
while(true) {
range[i++].foo();
}
} catch (ArrayIndexOUtBoundsException e) {
//
}
// God
for(T m : range){
m.foo();
}
예외는 일상적인 제어 흐름용으로 쓰여선 안되며, 이를 프로그레머에게 강요하는 API 를 만들어서도 안된다.
잘 설계된 API 라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 한다.
특정 상태에서만 호출 할 수 있는 '상태 의존적' 메서드를 재공하는 클래스는 '상태 검사' 메서드도 함께 제공해야 한다. Iterator 의 next 와 hasNext 가 상태의존적 메서드와 검사 메서드에 해당된다. 상태를 검사할 수 있기 때문에 hasNext 와 같은 조건을 걸 수 있다.
문제 상황을 알리는 타입(throwable)으로 검사 예외, 런타임 예외, 에러 이렇게 3 가지를 제공한다.
호출쪽에서 복구하리라 여거지는 상황이라면 검사 예외(Checked Excetpion)를 사용하라. 이것이 검사/비검사 예외를 구분하는 기본 규칙이다.
IllegalArgumentException
:허용하지 않는 값이 인수로 건네졌을 때(null은 따로 NullPointerException
으로 처리)IllegalStateException
:객체가 메서드를 수행하기에 적절하지 않은 상태일 때NullPointerException
: null을 허용하지 않는 메서드에 null 건넸을 때ConcurrentModificationException
:허용하지 않는 동시 수정이 발견됐을 때UnsupportedOperationException
: 호출한 메서드를 지원하지 않을 때상위 계층에서는 저수준의 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야 한다.
예외번역
try{
// sth
} catch(LowerLevelException e){
throw new HigherLevelException();
}
예외를 번역할 때, 저수준의 예외가 디버깅에 도움이 된다면 예외 연쇄를 사용하는 것이 좋다.
try{
// sth
} catch(LowerLevelException e){
throw new HigherLevelException(e);
}
class HigherLevelException extends Exception {
HigherLevelException(Throwable throwable){
super(throwable);
}
}
전역에 GOTO 같은 흐름제어를 위해 Exception 을 사용하는 패턴을 언어와 프레임워크 불문하고 종종 경험했다. 물론 이런 패턴을 도입하는 개발자들의 마음은 이해를 못하는건 아니다. 그러나 이런 GOTO 예외는 저수준의 함수에 있다면 재사용성이 떨어지고 유지보수시 예측가능성을 현저하게 낮춘다. 자신의 프로젝트가 GOTO성 예외에 중독된 프로젝트가 아닌지 돌이켜보자.