코드 69-1 예외를 완전히 잘못 사용한 예 - 따라 하지 말 것!
try {
int i = 0;
while(true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
}
위 코드의 문제점
- 직관적이지 않다.
- 예외를 써서 루프를 종료하는 이상한 방식으로 구현
다음과 같이 표준 관융구대로 작성했다면 누구나 쉽게 이해했을 것이다.
코드 - 배열을 순회하는 표준 관용구
for(Mountain m : range)
m.climb();
예외를 써서 루프를 종료한 이유? (코드 69-1)
- 잘못된 추론을 근거로 성능을 높여보려 한 것
- JVM은 배열에 접근할 때마다 경계를 넘지 않는지 검사
- 일반적인 반복문도 배열 경계에 도달하면 종료(경계를 넘지 않는지 검사)
-> 따라서 이 검사를 반복문에도 명시하면 같은 일이 중복 되리라 판단하여 하나를 생략한 것이다.
잘못된 추론인 이유
- 예외는 예외 상황에 쓸 용도로 설계되었으므로 명확한 검사만큼 빠르게 만들어야 할 동기가 약하다.(즉, 예외처리는 최적화에 신경 쓰지 않았을 가능성이 크다.)
- 코드를 try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
- 배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다. JVM이 알아서 최적화해 없애준다.
추론과는 다르게 예외를 사용한 쪽이 표준 관용구보다 훨씬 느리다.
잘못된 예외 사용으로 인한 문제점
- 코드를 헷갈리게 한다.
- 오히려 성능을 떨어뜨릴 가능성이 크다.
- 코드 자체가 제대로 동작하지 않을 수 있다.
- 예를 들어 코드 69-1 에서 내부와 관련없는 배열을 사용하다가 ArrayIndexOutOfBoundsException을 일으켰을 경우, 정상적인 반복문 종료 상황으로 오해하고 넘어갈 것이다.
교훈
- 예외는 오직 예외 상황에서만 써야 한다. 절대로 일상적인 제어 흐름용으로 쓰여선 안 된다.
- 섵부른 최적화를 하지말아야 한다. 당장 성능이 더 좋을 수 있어도 자바가 계속 업그레이드 되며 성능 우위는 영원하지 않을 것이다.
- 미묘한 버그로 인해 유지보수 문제는 계속 이어질 것이다.
- 잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 한다.
- 특정 상태에서만 호출할 수 있는 '상태 의존적 메서드'를 제공하는 클래스는 '상태 검사 메서드'도 함께 제공해야 한다.
- Ex. Iterator 인터페이스의 next : '상태 의존적 메서드', hasNext : '상태 검사 메서드'
- 만약 hasNext가 제공되지 않는다면, 코드 69-1과 같이 예외를 통해 종료시켜야 할 것이다.
- '상태 검사 메서드' 외의 선택지도 있다. '빈 옵셔널' 혹은 'null과 같은 특정 값'을 통해 반환하는 방법이다.
'상태 검사 메서드', '옵셔널', '특정 값'의 선택 기준
'옵셔널'이나 '특정 값' 선택
- '상태 검사 메서드'와 '상태 의존적 메서드' 호출 사이에 객체의 상태가 변할 수 있는 경우
- 여러 스레드가 동시 접근, 외부 요인으로 상태 변할 수 있는 경우
- 성능이 중요한 상황에서 '상태 검사 메서드'가 '상태 의존적 메서드'의 작업 일부를 중복 수행
그 외 모든 경우에는 '상태 검사 메서드' 방식 사용