[EffectiveJava] item71 - 필요 없는 검사 예외 사용은 피하라
14 Jul 2023검사 예외는 제대로 활용하면 API와 프로그램의 질을 높일 수 있다. 결과를 코드로 반환하거나 비검사 예외를 던지는 것과 달리, 검사 예외는 발생한 문제를 프로그래머가 처리하여 안전성을 높이게끔 해준다.
사용자에게는 불편할 수 있다. 어떤 메서드가 검사 예외를 던질 수 있다고 선언됐다면, 이를 호출하는 코드에서는 catch 블록을 두어 그 예외를 붙잡아 처리하거나 더 바깥으로 던져 문제를 전파해야 한다.
어느 쪽이든 API 사용자에게 부담을 줄 수 있다. 자바 8 부터는 검사 예외를 던지는 메서드는 스트림 안에서 직접 사용할 수도 없어 부담이 커졌다.
검사 예외(Checked Exception) vs 비검사 예외(Unchecked Exception)
만약 API를 제대로 사용해도 발생할 수 있는 예외이거나, 프로그래머가 의미 있는 조치를 취할 수 있는 경우라면 이러한 부담은 받아들일 수 있을 것이다.
그러나 둘 중 어디에도 해당하지 않는다면 비검사 예외를 사용하는 게 좋다.
다음의 예시를 보자.
검사 예외
} catch (TheCheckedException e) {
throw new AssertionError(); /** 일어날 수 없다! */
}
} catch (ThrCheckedException e) {
e.printStackTrace(); /** 우리가 졌다. */
System.exit(1);
}
둘의 코드 둘다 좋은 방식은 아니다.
더 나은 방법이 없다면 비검사 예외를 선택해야 한다.
검사 예외의 단점
검사 예외가 프로그래머에게 지우는 부담은 메서드가 단 하나의 검사 예외만 던질 때가 특히 크다.
이미 다른 검사 예외도 던지는 상황에서 또 다른 검사 예외를 추가하는 경우라면 catch 문 하나 추가하는 선에서 끝이다.
하지만 검사 예외가 단 하나 뿐이라면 오직 그 예외 하나 때문에 API 사용자는 try 블록을 추가해야 하고 스트림에서는 사용하지 못하게 된다.
검사 예외 회픠
위와 같은 상황이라면 검사 예외를 안던지는 방법이 없는지 고민해볼 가치가 있다.
빈 옵셔널
검사 예외를 회피하는 가장 쉬운 방법은 적절한 결과 타입을 담은 옵셔널을 반환하는 것이다.
검사 예외를 던지는 대신 단순히 빈 옵셔널을 반환하면 된다. 이 방식의 단점이라면 예외가 발생한 이유를 알려주는 부가정보를 담을 수가 없다.
반면, 예외를 사용하면 구체적인 예외 타입과 그 타입이 제공하는 메서드를 활용해 부가정보를 제공할 수 있다.
비검사 예외로 변환
또 다른 방법으로, 검사 예외를 던지는 메서드를 2개로 쪼개 비검사 예외로 바꿀 수 있다. 다음의 방법을 보자.
검사 예외를 던지는 메서드 - 리팩터링 전
try {
obj.action(args);
} catch (TheCheckedException e) {
... // 예외 상황에 대처한다.
}
상태 검사 메서드와 비검사 예외를 던지는 메서드 - 리팩터링 후
if (obj.actionPermitted(args)) {
obj.action(args);
} else {
... // 예외 상황에 대처한다.
}
이 리팩터링을 모든 상황에 적용할 수는 없지만 적용할 수만 있다면 더 쓰기 편한 API를 제공할 수 있다.
실패 시 스레드를 중단하길 원한다면 obj.action(args);
한 줄로만 작성해도 무방하다.
만약 이 한 줄짜리 호출 방식이 주로 쓰일 거로 판단되면 리팩터링 하는 편이 바람직 하다. 한편 리팩터링 후의 actionPermitted
는 상태 검사 메서드에 해당하므로 Item69에서 말한 단점도 그대로 적용이 된다.
외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부요인에 의해 상태가 변할 수 있다면 아래와 같은 이유로 이 리팩터링은 적절하지않다.
actionPermitted
와action
호출 사이에 객체의 상태가 변할 수 있다.actionPermitted
와action
메서드의 작업 일부를 중복 수행한다면 성능에서 손해이다.