개발/Effective Java

[Effective Java] item 71. 필요 없는 검사 예외 사용은 피하라

nova_dev 2021. 4. 25. 23:00
반응형

[Effective Java] item 71. 필요 없는 검사 예외 사용은 피하라

검사 예외의 장점

  • 검사 예외를 싫어하는 자바 프로그래머가 많지만 제대로 활용하면 API와 프로그램의 질을 높일 수 있다.
  • 결과를 코드로 반환하거나 비검사 예외를 던지는 것과 달리, 검사 예외는 발생한 문제를 프로그래머가 처리하여 안전성을 높이게끔 해준다.

검사 예외의 사용

  • 물론, 검사 예외를 과하게 사용하면 오히려 쓰기 불편한 API가 된다.
  • 어떤 메서드가 검사 예외를 던질 수 있다고 선언됐다면, 이를 호출하는 코드에서는 catch 블록을 두어 그 예외를 붙잡아 처리하거나 더 바깥으로 던져 문제를 전파해야만 한다.
  • 검사 예외를 던지는 메서드는 스트림 안에서 직접 사용할 수 없기 때문에 자바 8부터는 부담이 더욱 커졌다.

비검사 예외

  • API를 제대로 사용해도 발생할 수 있는 예외이거나, 프로그래머가 의미 있는 조치를 취할 수 있는 경우라면 이 정도 부담쯤은 받아들일 수 있을 것이다.
  • 그러나 둘 중 어디에도 해당하지 않는다면 비검사 예외를 사용하는 게 좋다.
  • 검사 예외와 비검사 예외 중 어느 것을 선택해야 할지는 프로그래머가 그 예외를 어떻게 다룰지 생각해보면 알 수 있다.

다음과 같이 하는 것이 최선인가?

} catch (TheCheckedException e) {
    throw new AssertionError(); // 일어날 수 없다!
}

아니면 다음 방식은 어떤가?

} catch (TheCheckedException e){
    e.printStackTrace(); // 이런, 우리가 졌다.
    System.exit(1);
}

더 나은 방법이 없다면 비검사 예외를 선택해야 한다.

검사 예외가 부담되는 경우

검사 예외가 프로그래머에게 지우는 부담은 메서드가 단 하나의 검사 예외만 던질 때가 특히 크다. 이미 다른 검사 예외도 던지는 상황에서 또 다른 검사 예외를 추가하는 경우라면 기껏해야 catch 문 하나 추가하는 선에서 끝이다.

하지만 검사 예외가 단 하나뿐이라면 오직 그 예외 때문에 API 사용자는 try 블록을 추가해야 하고 스트림에서 직접 사용하지 못하게 된다. 그러니 이런 상황이라면 검사 예외를 안 던지는 방법이 없는지 고민해볼 가치가 있다.

검사 예외를 회피하는 가장 쉬운 방법은 적절한 결과 타입을 담은 옵셔널을 반환하는 것이다. 검사 예외를 던지는 대신 단순히 빈 옵셔널을 반환하면 된다. 이 방법의 단점이라면 예외갑 라생한 이유를 알려주는 부가 정보를 담을 수 없다는 것이다.

반면, 예외를 사용하면 구체적인 예외 타입과 그 타입이 제공하는 메서드들을 활용해 부가 정보를 제공할 수 있다.

검사 예외를 던지는 메서드를 2개로 쪼개서 비검사 예외로 변환하기

또 다른 방법으로, 검사 예외를 던지는 메서드를 2개로 쪼개 비검사 예외로 바꿀 수 있다.

이 방식에서 첫 번째 메서드는 예외가 던져질지 여부를 boolean 값으로 반환한다. 다음의 예를 보자

검사 예외를 던지는 메서드 - 리팩터링 전
try {
    obj.action(args);
} catch (TheCheckedException e) {
    ... // 예외 상황에 대처한다.
}

리팩터링하면 다음처럼 된다.

상태 검사 메서드와 비검사 예외를 던지는 메서드 - 리팩터링 후
if (obj.actionPermitted(args)) {
    obj.action(args);
} else {
    ... // 예외 상황에 대처한다.
}

이 리팩터링을 모든 상황에 적용할 수는 없다. 그래도 적용할 수만 있다면 더 쓰기 편한 API를 제공할 수 있다. 리팩터링 후의 API가 딱히 더 아름답진 않지만, 더 유연한 것은 확실하다. 프로그래머가 이 메서드를 성공하리란 걸 안다거나, 실패 시 스레드를 중단하길 원한다면 다음처럼 한 줄로 작성해도 무방하다.

obj.action(args);

이 한줄 짜리 호출 방식이 주로 쓰일 거로 판단되면 리팩터링하는 편이 바람직하다. 한편, actionPermitted는 상태 검사 메서드에 해당하므로 아이템 69에서 말한 단점도 그대로 적용되니 주의해야 한다. 즉, 외부 동기화 없이 여러 쓰레드가 동시에 접근할 수 있거나 외부 요인에 의해 상태가 변할 수 있다면 이 리팩터링은 적절하지 않다. actionPermitted와 action 호출 사이의 객체의 상태가 변할 수 있기 때문이다.

또한 actionPermitted가 action 메서드의 작업 일부를 중복 수행한다면 성능에서 손해이니, 역시 이 리팩터링이 적절하지 않을 수 있다.

핵심 정리

꼭 필요한 곳에만 사용한다면 검사 예외는 프로그램의 안전성을 높여주지만, 남용하면 쓰기 고통스러운 API를 낳는다. API 호출자가 예외 상황에서 복구할 방법이 없다면 비검사 예외를 던지자. 복구가 가능하고 호출자가 그 처리를 해주길 바란다면, 우선 옵셔널을 반환해도 될지 고민하자. 옵셔널만으로는 상황을 처리하기에 충분한 정보를 제공할 수 없을 때만 검사 예외를 던지자

추가 자료

  • 검사 예외
    • 개발자가 명시해야 하는 부분, Exception으로 어플리케이션 수행 중에 일어날 법한 예외를 검사하고 대비하라는 목적으로 사용한다.
    • 과도하게 예외 검출을 하면 시스템의 성능이 떨어진다.
  • 비검사 예외
    • Error는 시스템적인 예외를 의미하고 개발자가 예외(try-catch)를 잡지 말라고 하며 심각한 상황에서 발생하는 예외이다.
    • 시스템은 치명적이므로 일어나서는 안되지만 만약 발생하면 해결하기 위해 로그를 남기도록 한다.
    • Exception을 상속받았지만 RuntimeException은 Unchecked로 비검사 예외이다. (헷갈리지 말자!)

참고 자료

반응형