69. 예외는 진짜 예외 상황에만 사용하라

운이 없다면 언젠가 다음과 같은 코드와 마주칠지도 모른다.

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

무슨 일을 하는 코드인지 알겠는가? 전혀 직관적이지 않다는 사실 하나만으로도 코드를 이렇게 작성하면 안 되는 이유는 충분하다.

이 코드는 배열의 원소를 순회하는데, 아주 끔찍한 방식으로 하고 있다.

무한루프를 돌다가 배열에 끝에 도달에 ArrayIndexOutOfBoundsException이 발생하면 끝을 내는 것이다.

이 코드를 다음과 같이 표준적인 관용구대로 작성했다면 모든 자바 프로그래머가 곧바로 이해했을 것이다.

for(Mountain m : range) {
	m.climb();
}

그런데 예외를 써서 루프를 종료한 이유는 도대체 뭘까?

잘못된 추론을 근거로 성능을 높여보려 한 것이다.

JVM은 배열에 접근할 때마다 경계를 넘지 않는지 검사하는데, 일반적인 반복문도 배열 경계에 도달하면 종료한다.

따라서 이 검사를 반복문에도 명시하면 같은 일이 중복되므로 하나를 생략한 것이다.

하지만 세 가지 면에서 잘못된 추론이다.

  1. 예외는 예외 상황에 쓸 용도로 설계되었으므로 JVM 구현자 입장에서는 명확한 검사만큼 빠르게 만들어야 할 동기가 약하다 (최적화에 별로 신경쓰지 않았을 가능성이 크다)
  2. 코드를 try-catch 블록안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
  3. 배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다. JVM이 알아서 최적화해 없애준다.

실상은 예외를 사용한 쪽이 표준 관용구보다 훨씬 느리다. 내 컴퓨터에서 원소 100개짜리 배열로 테스트해보니 2배 정도 느렸다.

예외를 사용한 반복문의 해악은 코드를 헷갈리게 하고 성능을 떨어뜨리는 데서 끝나지 않는다.

심지어 제대로 동작하지 않을 수도 있다.