피드로 돌아가기
원문 읽기
LINE Engineering
Backend코드 품질 개선 기법 20편: 이례적인 예외 과대 포장
LY Corporation의 Review Committee가 리소스 해제 예외 처리 패턴에서 예외를 감싸는 방식의 문제점을 식별하고 Throwable.addSuppressed를 사용한 개선안을 제시
AI 요약
Context
Kotlin의 Closeable.use 패턴과 유사하게 리소스 해제를 보장하는 Disposable 인터페이스를 구현할 때, 블록 실행 중과 dispose() 호출 중 두 곳에서 예외가 발생할 수 있다.
원래 구현에서는 발생한 예외를 DisposableException으로 감싸서 처리했으나, 이로 인해 호출자가 기대하는 원본 예외 타입의 catch 절이 작동하지 않는 문제가 발생했다.
Technical Solution
- 예외 감싸기의 문제점 식별: 블록에서
IOException이 발생했을 때DisposableException으로 변환하면catch (exception: IOException)절이 실행되지 않음 - Throwable.addSuppressed 도입: 새로운 예외 타입을 만드는 대신
addSuppressed()메서드로 보조 예외를 원본 예외에 첨부하는 방식으로 변경 - 예외 우선순위 결정: 블록에서 발생한 예외를 주 예외로,
dispose()중 발생한 예외를 보조 예외로 명시하여 throw - 구현 수정: try-catch-finally 구조에서
exceptionAtBlock이 null이 아닐 때만exceptionAtDispose를addSuppressed()처리하고, null일 때는exceptionAtDispose자체를 throw
Key Takeaway
다중 예외 발생 시 새로운 래퍼 예외를 만들기보다 addSuppressed()를 사용하여 예외 타입을 유지하면, 호출자의 기존 예외 처리 로직이 정상 작동하면서도 모든 예외 정보를 보존할 수 있다.
실천 포인트
리소스 해제 패턴(Closeable.use, try-with-resources 등)을 Kotlin 확장 함수로 구현할 때, 정리 작업 중 발생한 예외를 주 예외에 addSuppressed()로 첨부하되 주 예외는 그대로 throw해야 호출자의 catch 절이 정상 작동한다.