Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[item 69] 예외는 진짜 예외 상황에서만 사용하라 #102

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
71 changes: 71 additions & 0 deletions 10장_예외/item69.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
예외는 진짜 예외 상황에서만 사용해야하며 **절대 일상적인 제어 흐름용으로 사용해선 안 된다.**

#### 예외를 잘못 사용한 코드

```
try {
int i = 0;
while(true)
range[i++].climb();
} catch (ArrayIndexOutOfBoundsException e) {

}
```

위 코드는 무한 루프를 돌다가 배열의 끝에 도달해 예외가 발생하면 끝난다.
반복문에서 배열의 크기보다 커졌는지 검사를 하는 부분을 줄이려고 의도했으나, 이는 잘못된 추론이다.

예외는 예외 상황에 쓸 용도로 설계되었으므로 JVM 구현자 입장에서는 명확한 검사만큼 빠르게 만들어야 할 만큼 최적화에 신경쓰지 않았을 확률이 높다.
코드를 try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다. JVM이 알아서 최적화해서 없애준다.
즉, 코드를 헷갈리게 할 뿐만 아니라 성능을 떨어뜨리고 심지어 제대로 동작하지 않을 수도 있다.

- 예외는 오직 예외 상황에서만 사용하라. 절대로 흐름 제어용으로 사용하면 안된다.
- 잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 한다.

#### 예외 관점에서 잘 설계된 API

잘 설계된 API는 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 한다.
특정 상태에서만 사용하는 '상태 의존적' 메소드를 제공하는 클래스는 '상태 검사' 메소드도 함께 제공해야 한다.

예) Iterator 인터페이스의 next(상태 의존적 메소드)와 hasNext(상태 검사 메소드)를 제공한다.

덧붙여, 별도의 상태 검사 메서드 덕분에 다음과 같은 표준 for 관용구를 사용할 수 있다.(for-each도 내부적으로 hasNext를 사용한다.

```
for (Iterator<Foo> i = collections.iterator(); i.hasNext();) {
Foo foo = i.next();
// ...
}
```

Iterator가 hasNext를 제공하지 않았다면 그 일을 클라이언트가 대신해야만 했다.

```
//컬렉션을 이런 식으로 순회하지 말 것!
try {
Iterator<Foo> i = collection.iterator();
while (true) {
Foo foo = i.next();
// ...
}
} catch (NoSuchElementException e) {

}
```

반복문에 예외를 사용하면 장황하고 헷갈리며 속도도 느리고, 엉뚱한 곳에서 발생한 버그를 숨기기도 한다.

#### 상태 검사 메서드 대신 사용 가능한 선택지

올바르지 않은 상태일 때 빈 옵셔널(아이템 55) 혹은 null 같은 특수한 값을 반환하는 것이다.

1. 외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다면 **옵셔널**이나 **특정 값**을 사용한다.
👉 상태 검사 메소드와 상태 의존적 메소드 호출 사이에 객체의 상태가 변할 수 있기 때문이다.
2. 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메소드의 작업 일부를 중복 수행한다면 **옵셔널이나 특정 값**을 사용한다.
3. 다른 경우에는 **상태 검사 메소드**를 사용하는 것이 낫다.
👉 가독성이 살짝 더 좋고, 잘못 사용했을 때 발견하기가 쉽기 때문이다.
만약 상태 검사 메서드 호출을 깜빡 잊었다면 상태 의존적 메서드가 예외를 던져 버그를 확실히 드러낼 것이다.
반면에, 특정 값은 검사하지 않고 지나쳐도 발견하기 어렵다.(옵셔널은 해당 x)

> 예외는 예외 상황에서 쓸 의도로 설계됐다. 정상적인 제어 흐름에서 사용해선 안 되며, 이를 프로그래머에게 강요하는 API를 만들어서도 안 된다.