본문 바로가기

Effective Java

[이펙티브 자바] 아이템69. 예외는 진짜 예외 상황에만 사용하라


1) 잘못된 코드 

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

 

- 무슨 일을 하는 코드인지 알겠는가? 전혀 직관적이지 않다는 사실 하나만으로도 코드를 이렇게 

  작성하면 안 되는 이유는 충분하다. 

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

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

 

 

2) 개선된 코드.

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

 

 

3) 예외를 사용해 종료하는 반복문의 해악  

 

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

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

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

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

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

 

1. 예외는 예외 상황에 쓸 용도로 설계되었으므로, JVM 구현자 입장에서는 명확한 검사만큼 

    빠르게 만들어야 할 동기가 약하다(최적화에 별로 신경쓰지 않았을 가능성이 크다)

2.  코드를 try~catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.

3. 배열을 순회하는 표준 관용구는 앞서 걱정한 중복 검사를 수행하지 않는다.

    JVM이 알아서 최적화해 없애준다. 

 

- 실상은 예외를 사용한 쪽이 표준 관용구보다 훨씬 느리다.

   내 컴퓨터에서 원소 100개짜리 배열로 테스트해보니 2배 정도 느렸다. 

  

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

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

  반복문 안에 버그가 숨어 있다면 흐름 제어에 쓰인 예외가 이 버그를 숨겨 디버깅을 훨씬 어렵게 할 것이다. 

 

 

 

4) 결론

 

- 이 이야기의 교훈은 간단하다. 예외는 (그 이름이 말해주듯) 오직 예외 상황에서만 써야 한다.

  절대로 일상적인 제어 흐름용으로 쓰여선 안된다.

  더 일반화해 이야기하면 표준적이고 쉽게 이해되는 관용구를 사용하고, 

  성능 개선을 목적으로 과하게 머리를 쓴 기법은 자제하라.

  

- 실제로 성능이 좋아지더라도 자바 플랫폼이 꾸준히 개선되고 있으니 

   최적화로 얻은 상대적인 성능 우위가 오래가지 않을 수 있다.

   반면 과하게 영리한 기법에 숨겨진 미묘한 버그의 폐해와 어려워진 유지보수 문제는

   계속 이어질 것이다. 

 

- 이 원칙은 API 설계에도 적용된다. 잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서

  예외를 사용할 일이 없게 해야 한다. 특정 상태에서만 호출할 수 있는 '상태 의존적' 메서드를 제공하는

  클래스는 '상태 검사' 메서드도 함꼐 제공해야 한다.