로그를 확인하는 도중 일을 수행하는 도중 예상치 못한 예외가 발생하면 상당히 당황스러울 수 있습니다.
이는 메서드가 저수준 예외를 처리하지 않고 상위 레이어로 던질 때 종종 발생하며 다음과 같은 문제가 발생할 수 있습니다..
- 내부 구현 방식을 상위 layer에 드러내 윗 레벨 API를 오염시킬 수 있으며
- 다음 릴리즈에서 구현 방식이 변경될 경우 다른 예외가 발생해 기존 클라이언트 프로그램을 깨지게 할 수 있음
이번 아이템에서는 위 문제를 방지하기 위한 기법들을 소개합니다.
1. 상위 메서드에서 저수준 예외를 번역하자
- 상위 메서드에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야하며 이를 `예외 번역(Exception Translation)`이라고 함
- ex) 데이터베이스 연결을 시도할 때 SQLException이 발생할 수 있음
- 해당 예외는 데이터베이스와 관련된 문제를 나타냄
- 그러나 상위 레이어에서는 이 예외를 더 의미 있는 형태로 전환하여 처리할 수 있음
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.sql.SQLException; | |
public class DBHandler { | |
public void connectToDatabase() throws DatabaseConnectionException { | |
try { | |
// 데이터베이스 연결 시도 | |
// ... | |
} catch (SQLException e) { | |
// SQLException을 DatabaseConnectionException으로 변환하여 상위 레이어로 던짐 | |
throw new DatabaseConnectionException("DB 연결에 실패했했습니다.", e); | |
} | |
} | |
} | |
// 고수준의 예외 클래스 | |
class DatabaseConnectionException extends Exception { | |
public DatabaseConnectionException(String message, Throwable cause) { | |
super(message, cause); | |
} | |
} |
2. 저수준 예외의 내용이 필요할 경우 예외 연쇄를 사용하자
- `예외 연쇄(Exception Handling)`이란 문제의 근본 원인(cause)인 저수준 예외를 고수준 예외에 체이닝하는 방식
- Throwable의 getCause() 메서드와 같은 별도의 접근자 메서드를 통해 필요하면 언제든 저수준 예외를 꺼내 볼 수 있음
- 대부분의 표준 예외는 예외 연쇄용 생성자를 갖추고 있으며 그렇지 않은 예외라도 Throwable의 initCause() 메서드를 통해 `원인`을 직접 못박을 수 있음
- 예외 연쇄는 문제의 원인을 추적하는데 도움을 주며 원인과 고수준 예외의 Stack trace를 적절히 통합해줌
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void main(String[] args) { | |
try { | |
// 고수준 예외 발생 | |
throw new HighLevelException("고수준 예외가 발생했습니다."); | |
} catch (HighLevelException e) { | |
// 저수준 예외 생성 및 연쇄 | |
LowLevelException lowLevelException = new LowLevelException("저수준 예외가 발생했습니다."); | |
lowLevelException.initCause(new Throwable("고수준 예외의 원인입니다.")); | |
// 고수준 예외에 저수준 예외 연쇄 | |
e.initCause(lowLevelException); | |
System.out.println("고수준 예외 메시지: " + e.getMessage()); | |
System.out.println("고수준 예외 원인: " + e.getCause().getMessage()); | |
} | |
} | |
class HighLevelException extends Exception { | |
public HighLevelException(String message) { | |
super(message); | |
} | |
} | |
class LowLevelException extends Exception { | |
public LowLevelException(String message) { | |
super(message); | |
} | |
} |

3. 예외 번역을 남용하지 말자
- 가능하다면 저수준 메서드가 반드시 성공하도록 하여 아래 계층에서는 예외가 발생하지 않도록 하는 것이 최선
- 때로는 상위 계층 메서드의 매개변수 값을 아래 계층 메서드로 건네기 전 Validator를 이용하여 미리 검사하는 것을 권장
- 아래 계층에서의 예외를 피할 수 없다면, 상위 계층에서 해당 예외를 조용히 처리하여 문제를 API 호출자에게 전파하지 않는 차선책도 있음
- 만약 발생한 예외를 단순히 로깅하고 개발자가 디버깅하는 정도로 처리할 수 있다면, 그 방식을 권장
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
try { | |
lowLevelMethod(); | |
} catch (Exception e) { | |
log.error(e.getMessage()); | |
} |
정리
- 아래 계층의 예외를 예방하거나 스스로 처리할 수 없고, 해당 예외를 상위 계층에 그대로 노출하기 곤란하다면 예외 번역을 사용하는 것을 권장
- 이 때 예외 연쇄를 이용하면 상위 계층에는 맥락에 어울리는 고수준 예외를 던지면서 근본 원인도 함께 알려주어 오류 분석에 용이함
- 하지만 예외 번역을 남용하지는 말자
참고
이펙티브 자바
반응형
'JAVA > Effective Java' 카테고리의 다른 글
[아이템 75] 예외의 상세 메시지에 실패 관련 정보를 담으라 (0) | 2024.03.29 |
---|---|
[아이템 74] 메서드가 던지는 모든 예외를 문서화하라 (0) | 2024.03.28 |
[아이템 72] 표준 예외를 사용하라 (0) | 2024.03.25 |
[아이템 71] 필요 없는 검사 예외 사용은 피하라 (0) | 2024.03.25 |
[아이템 70] 복구할 수 있는 상황에는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라 (0) | 2024.03.23 |