"원인(cause)을 포함하는 예외(throwable)"를 사용하는 이유 (javadocs, GPT 번역)


첫번째

어떤 예외(throwable)가 원인(cause)을 가지는 이유 중 하나는, 예외를 던지는 클래스가 더 낮은 계층의 추상화 위에 구축되었고, 상위 계층에서의 작업이 하위 계층의 실패로 인해 실패했기 때문입니다. 하위 계층에서 발생한 예외를 그대로 외부로 전파하는 것은 좋지 않은 설계인데, 이는 일반적으로 상위 계층에서 제공하는 추상화와 관련이 없기 때문입니다. 또한, 하위 계층의 예외가 체크 예외(checked exception)인 경우, 이를 그대로 전파하면 상위 계층의 API가 구현 세부 사항에 얽매이게 됩니다.

"랩핑된 예외(wrapped exception)"(즉, 원인을 포함하는 예외)를 던지는 것은 상위 계층이 실패의 세부 정보를 호출자에게 전달하면서도 이러한 단점을 피할 수 있게 해줍니다. 이를 통해 상위 계층의 구현을 변경하더라도 API(특히, 메서드가 던지는 예외의 집합)를 변경하지 않고도 유연성을 유지할 수 있습니다.


즉, 예외의 "원인"을 포함하는 기능은 계층 간 결합도를 낮추고, 상위 계층의 API가 하위 계층의 구현 세부 사항에 종속되지 않도록 보장해주는 역할을 합니다.


두번째

예외가 원인을 가질 수 있는 두 번째 이유는, 예외를 던지는 메서드가 원인을 직접 던질 수 없는 일반 목적의 인터페이스를 준수해야 하기 때문입니다. 예를 들어, 어떤 영속적(persistent) 컬렉션이 Collection 인터페이스를 준수하며, 그 영속성이 java.io를 기반으로 구현된다고 가정합시다.

이때, add 메서드 내부에서 IOException이 발생할 수 있습니다. 하지만 Collection 인터페이스의 요구사항을 준수해야 하므로, 이 IOException을 직접 던질 수 없습니다. 이 문제를 해결하기 위해 IOException을 적절한 Unchecked Exception(체크되지 않은 예외)으로 감싸서 호출자에게 전달할 수 있습니다. (단, 이 영속적 컬렉션의 명세에는 이런 예외를 던질 가능성이 있음을 명시해야 합니다.)


즉, 원인을 포함하는 예외를 사용하는 이유는 다음과 같습니다:

인터페이스 요구사항을 준수하면서도 내부적으로 발생한 예외의 세부 정보를 호출자에게 전달하기 위해, 체크 예외를 래핑하여 적절한 언체크 예외로 변환할 필요가 있습니다. 이는 인터페이스 호환성을 유지하면서 유연성을 확보하는 방법입니다.