resource-management

1 개의 포스트

코드 품질 개선 기법 20편: 이례적인 예외 과대 포장 (새 탭에서 열림)

리소스를 안전하게 해제하기 위해 사용하는 `use` 패턴이나 커스텀 예외 처리 구현 시, 발생한 여러 예외를 하나의 커스텀 예외로 감싸서(wrapping) 던지는 것은 주의해야 합니다. 이러한 '과대 포장'은 호출자가 기대하는 특정 예외 유형을 가려버려 예외 처리 로직을 무력화시키고 디버깅을 어렵게 만듭니다. 따라서 여러 예외가 동시에 발생할 때는 원인이 되는 주요 예외를 우선시하고, 부수적인 예외는 `addSuppressed`를 통해 전달하는 것이 올바른 품질 개선 방향입니다. ### 예외 과대 포장의 부작용 * 리소스 해제 과정에서 발생하는 예외까지 관리하기 위해 `DisposableException` 같은 별도의 예외 클래스로 감싸게 되면, 원래 발생한 구체적인 예외 정보(예: `IOException`)가 추상화되어 버립니다. * 이 경우 호출부에서 특정 예외를 잡기 위해 작성한 `catch(e: IOException)` 문이 작동하지 않게 되어, 의도치 않은 런타임 오류로 이어질 수 있습니다. * 특히 유틸리티 함수나 보조 함수 내부에서 이러한 포장이 일어날 경우, 호출자는 내부 구현을 상세히 알기 전까지는 예외 처리 실패의 원인을 파악하기 매우 어렵습니다. ### `addSuppressed`를 활용한 예외 우선순위 설정 * 한 코드 블록에서 비즈니스 로직과 리소스 해제(dispose) 로직 모두 예외가 발생할 수 있다면, 어떤 예외가 더 중요한지 판단하여 우선순위를 정해야 합니다. * 일반적으로 비즈니스 로직이 실행되는 `block`에서 발생한 예외가 핵심적인 정보를 담고 있으므로 이를 우선적으로 `throw`해야 합니다. * 리소스 해제 시 발생하는 보조적인 예외는 버리지 않고, 주요 예외의 `addSuppressed` 메서드에 추가함으로써 전체적인 예외 맥락을 보존하면서도 타입 시스템을 해치지 않을 수 있습니다. ### 언어별 예외 처리 시 주의사항 * **Kotlin:** `Closeable.use` 확장 함수는 이미 `addSuppressed`를 활용하여 주요 예외를 우선하는 방식으로 구현되어 있으므로, 커스텀 리소스 클래스 제작 시에도 이와 유사한 패턴을 따르는 것이 좋습니다. * **Java:** Checked Exception이 존재하는 Java에서는 예외를 다른 타입으로 감쌀 때 상속 관계를 신중히 고려해야 합니다. * 복구가 불가능한 경우가 아니라면 Checked Exception을 `RuntimeException`으로 함부로 변환하여 던지지 않아야 하며, 부모 예외 타입으로 뭉뚱그려 잡는 과정에서 예외 처리 누락이 발생하지 않도록 주의가 필요합니다. 리소스 해제와 같은 부수적인 작업에서 발생하는 예외가 본래의 실행 목적을 가진 코드의 예외를 덮어쓰지 않도록 설계해야 합니다. 항상 "어떤 예외가 개발자나 시스템에게 더 중요한 정보인가"를 고민하고, 언어에서 제공하는 예외 억제(suppression) 기능을 활용해 예외의 층위를 명확히 관리할 것을 권장합니다.