View
예외는 예외 상황에서 쓸 의도로 설계되었다. 정상적인 제어 흐름에서 사용해서는 안되며, 이를 강요하는 API도 만들면 안 된다.
예외를 제어 흐름용으로 사용하지 말자
위 코드는 무한루프를 돌다가 배열의 끝에 도달해 ArrayIndexOutOfBoundsException이 발생하면 배열 순회를 끝낸다. 제어 흐름용으로 예외를 사용한 케이스이다.
// 예외를 완전히 잘못 사용한 예
try {
int i = 0;
while(true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
}
위 코드는 직관적이지 않아 무슨 일을 하는지 한눈에 알아보기 어렵다. 또한, 다른 문제점들도 존재한다.
예외를 제어 흐름용으로 사용하면 표준 관용구보다 훨씬 느리다.
이와 같은 코드를 작성한 이유는 잘못된 추론에서 나온 것이다. 잘못된 추론은 다음과 같다.
- 예외를 통한 검사가 표준 관용구보다 빠를 것이다.
- trty-catch 블록 안에 넣어도 JVM이 적용할 수 있는 최적화가 제한되지 않는다.
- 배열을 순회하는 표준 관용구는 배열 경계 검사를 수행한다.
하지만 실상은 다음과 같다.
- 예외는 예외 상황에만 쓸 용도로 만들어졌으므로 JVM 구현자 입장에서는 빠르게 만들어야 할 동기가 약하다. 즉, 예외를 통한 검사가 표준 관용구보다 빠르지 않을 수 있다.
- trty-catch 블록 안에 넣어도 JVM이 적용할 수 있는 최적화가 제한된다.
- 배열을 순회하는 표준 관용구의 배열 경계 검사는 JVM이 알아서 최적화해 이를 중복 수행하지 않는다.
즉, 예외를 사용해 제어 흐름을 하는 것이 반복문 표준 관용구를 사용하는 것보다 빠를 거라는 추론에서 위와 같은 코드를 작성한 것이다. 하지만 실상은 예외를 사용한 쪽이 표준 관용구보다 훨씬 느리다.
위 코드를 표준 관용구대로 작성했다면 직관적으로 이해되고 성능 저하도 없었을 것이다.
// 표준 관용구
for (Mountain m : range) {
m.climb();
}
예외가 제 역할을 못할 수 있다.
예외를 사용한 반복문은 성능 저하에서 끝나지 않고 프로그램이 제대로 동작하지 않게 만들 수 있다. 반복문 안에 버그가 숨어 있다면 흐름 제어에 쓴 예외가 버그를 숨겨 디버깅이 훨씬 어려워질 수 있다.
예를 들어, 반복문의 몸체에서 호출한 메서드가 내부에서 관련 없는 배열을 사용하다가 ArrayIndexOutOfBoundsException을 일으켰다면, 예외를 사용한 반복문은 정상적으로 반복문 종료 상황으로 인식하고 정상 종료 처리되는 시킨다. 하지만 표준 관용구였다면 스택 추적 정보를 남기고 즉각 스레드를 종료시켰을 것이다.
이처럼 예외는 오직 예외 상황에서만 사용해야 한다. 절대 일상적인 제어 흐름용으로 쓰여선 안 된다.
이러한 원칙은 API 설계에도 적용된다.
예외 관점에서 잘 설계된 API란?
- 잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 한다.
- '상태 의존적' 메서드를 제공하는 클래스는 '상태 검사' 메서드도 함께 제공해야 한다.
Iterator 인터페이스의 next() 메서드(상태 의존적 메서드)와 hasNext() 메서드(상태 검사 메서드)가 그 예이다.
for(Iterator<Foo> i = collection.iterator(); i.hasNext();) {
Foo foo = i.next();
...
}
만약, Iterator가 상태 검사 메서드를 제공하지 않았다면, 앞선 예제와 같은 코드를 짜야했을 것이다.
// 이런식으로 순회하지 말자!!!!
try{
Iterator<Foo> i = collection.iterator();
while(true) {
Foo foo = i.next();
...
}
} catch(NoSuchElementException e) {
}
앞서 말했 듯, 이러한 형태의 코드는 느리고, 엉뚱한 곳에서 발생한 버그를 숨기기도 한다. 절대 사용하지 말자.
상태 검사 메서드의 대체 방안
상태 검사 메서드를 제공하는 대신 Optional 혹은 null 같은 특수한 값을 반환하는 것이 대체 방안이 될 수 있다. 상태 검사 메서드, Optional, 특정 값 중 하나를 선택하는 몇 가지 지침이 있는데, 그 지침은 다음과 같다.
- 외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 가변 상태라면 Optional이나 특정 값을 사용하자. (상태 검사 메서드와 상태 의존적 메서드 사이의 객체가 변화할 수도 있기 때문에)
- 성능이 중요한 상황이라면 상태 검사 메서드가 상태 의존적 메서드의 작업을 일부 중복 수행한다면 Option이나 특정 값을 선택한다.
- 다른 모든 경우엔 상태 검사 메서드 방식이 조금 더 났다.
핵심 정리
- 예외는 예외 상황에서만 사용하도록 설계되었으므로 정상적인 제어 흐름에서 사용하지 말자!
'BackEnd > 이펙티브 자바' 카테고리의 다른 글
[이펙티브 자바] Item71 - 필요 없는 검사 예외 사용은 피하라 (0) | 2021.09.24 |
---|---|
[이펙티브 자바] Item70 - 복구할 수 있는 상황에는 검사 예외를, 프로그래밍 오류에는 런타임 예외를 사용하라 (0) | 2021.09.13 |
[이펙티브 자바] Item68 - 일반적으로 통용되는 명명 규칙을 따르라 (0) | 2021.09.06 |
[이펙티브 자바] Item67 - 최적화는 신중히 하라 (0) | 2021.08.30 |
[이펙티브 자바] Item66 - 네이티브 메서드는 신중히 사용하라 (0) | 2021.08.29 |