Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Item 73: 추상화 수준에 맞는 예외를 던지라

## 1. 예외 처리의 중요성

적절한 예외 처리는 프로그램의 가독성, 신뢰성, 유지보수성을 크게 향상

## 2. 추상화 수준에 맞는 예외 처리

저수준 예외를 그대로 전파하지 말고, 상위 레벨에 맞는 예외로 변환

예시:
```java
// 잘못된 예
public void processOrder() throws SQLException {
// 데이터베이스 작업
}

// 올바른 예
public void processOrder() throws OrderProcessingException {
try {
// 데이터베이스 작업
} catch (SQLException e) {
throw new OrderProcessingException("주문 처리 중 오류 발생", e);
}
}
```

## 3. 예외 번역

저수준 예외를 잡아 상위 레벨의 예외로 변환하여 던지는 기법

예시:
```java
try {
// 저수준 작업
} catch (LowerLevelException e) {
throw new HigherLevelException("고수준 작업 실패", e);
}
```

## 4. 예외 연쇄

- 예외를 번역할 때, 저수준 예외가 디버깅에 도움이 된다면 예외 연쇄를 사용하는게 좋다
- 원인이 되는 저수준 예외를 포함하여 상위 레벨 예외를 던진다.

예시:
```java
public class HigherLevelException extends Exception {
public HigherLevelException(String message, Throwable cause) {
super(message, cause);
}
}

// 별도의 접근자 메서드(Throwable의 getCause 메서드)를 통해 필요하면 언제든 저수준 예외를 꺼내볼 수 있다.
class HigherLevelException extends Exception {
HigherLevelException(Throwable cause) {
super(cause);
}
}
```

## 5. 예외 예방과 처리

- 최선: 예외 발생을 예방
- 가능하면 저수준 메서드가 반드시 성공하도록 하여 아래 계층에서는 예외가 발생하지 않도록 하는 것이 최선
- 차선: 적절히 처리하고 로깅
- 아래 계층에서의 예외를 피할 수 없다면, 상위 계층에서 그 예외를 조용히 처리하여 문제를 API 호출자에까지 전파하지 않는 방법

예시:
```java
public void transferMoney(Account from, Account to, BigDecimal amount) {
if (from.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException("잔액 부족");
}
// 송금 로직
}
```

## 6. 결론

상황에 따라 예외를 예방, 처리, 또는 적절히 변환하여 던지는 것이 중요. 이를 통해 API의 추상화 수준을 일관되게 유지하고, 클라이언트에게 더 유용한 정보를 제공 가능


### 추가 사항
- 스프링은 데이터 접근 계층에 대한 일관된 예외 추상화를 제공
- 스프링은 데이터 접근 계층에 대한 수많은 예외를 정리하여 일관된 예외 계층을 제공합니다. 이를 통해 클라이언트는 데이터 접근 계층에서 발생한 예외를 처리할 때, 특정 데이터베이스 기술에 종속되지 않고 일관된 방식으로 처리 가능
- 스프링의 DataAccessException은 고수준 예외로, 다양한 데이터 접근 기술에서 발생하는 저수준 예외들을 추상화
- ex)
```java
- SQLException: JDBC를 사용할 때 발생하는 예외로, 모든 데이터베이스 관련 예외를 포함합니다16.
- HibernateException: Hibernate ORM을 사용할 때 발생하는 예외입니다35.
- JpaSystemException: JPA를 사용할 때 발생할 수 있는 시스템 레벨의 예외입니다.
- MongoException: MongoDB 작업 중 발생하는 예외입니다.
- CassandraException: Cassandra 데이터베이스 작업 중 발생하는 예외입니다.
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Item 75: 예외의 상세 메시지에 실패 관련 정보를 담으라

예외 처리는 프로그램의 신뢰성과 유지보수성을 크게 향상시킬 수 있습니다. 효과적인 예외 처리의 핵심은 상세한 실패 정보를 제공하는 것입니다.

## 예외 메시지의 중요성

예외를 잡지 못해 프로그램이 실패하면 자바 시스템은 예외의 스택 추적 정보를 자동으로 출력한다. (스택 추적정보(stackTrace)는 예외 객체의 toString() 메서드가 반환하는 문자열)
=> 이는 종종 개발자가 문제를 진단할 수 있는 유일한 정보원!
=> 따라서 이 정보에 실패 원인을 가능한 많이 담아야 반환해야한다.

## 상세 메시지에 포함해야 할 정보 => 실패 순간 포착
1. 예외와 관련된 모든 매개변수와 필드 값
2. 실패 상황을 정확히 설명하는 정보

예시:
```java

public class CustomIndexOutOfBoundsException extends IndexOutOfBoundsException {
// 생성자는 범위의 하한, 상한, 그리고 범위를 벗어난 인덱스를 매개변수로 받는다.
public CustomIndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
// 부모 클래스의 생성자를 호출하며, 포맷된 문자열을 전달.
// 이 문자열은 예외의 상세 메시지가 된다.
super(String.format("Index %d out of bounds for range [%d, %d]", index, lowerBound,
upperBound));
}
}
```

## 주의사항
1. 불필요하게 장황한 메시지는 피하자
2. 보안에 민감한 정보(비밀번호, 암호 키 등)는 포함하지 말자
3. 사용자용 오류 메시지와 예외의 상세 메시지를 구분하자


## 효과적인 예외 생성 방법

예외 생성자에서 필요한 모든 정보를 받아 상세 메시지를 미리 생성하는 것이 좋다.

예시:
```java
// BetterIndexOutOfBoundsException은 CustomIndexOutOfBoundsException보다 더 유연하다.
// 예외를 catch한 후에도 getter 메서드들을 통해 관련 정보에 접근할 수 있어, 더 상세한 오류 처리나 로깅이 가능하다.
public class BetterIndexOutOfBoundsException extends IndexOutOfBoundsException {
// 범위의 하한, 상한, 인덱스를 private final 필드로 저장
// 이렇게 하면 나중에 이 정보에 접근 가능
private final int lowerBound;
private final int upperBound;
private final int index;

public BetterIndexOutOfBoundsException(int lowerBound, int upperBound, int index) {
// 부모 클래스의 생성자를 호출하여 상세 메시지를 설정
super(String.format("Index %d out of bounds for range [%d, %d]", index, lowerBound, upperBound));
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.index = index;
}
public int getLowerBound() { return lowerBound; }
public int getUpperBound() { return upperBound; }
public int getIndex() { return index; }
}

```
이러한 접근 방식은 예외가 실패 상황을 더 정확히 포착하고, 문제 해결에 필요한 정보를 제공하는 데 도움