Java/Effective Java

[Effective Java] 아이템 69. 예외는 진짜 예외 상황에서만 사용하라

메성 2020. 2. 6. 01:00
반응형

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

예외는 오직 예외 상황에서만 써야한다. 절대 일상적인 제어 흐름용으로 사용해선 안 된다.

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

}

위 코드는 무한루프를 돌다가 배열의 끝에 도달하게 되면 ArrayIndexOutOfBoundsException 이 발생하면 끝을 내버리는 코드다.

 

 

왜 예외를 써서 루프를 종료한 거지? 세 가지 잘못된 추론을 살펴볼 수 있다.

  1. 예외를 통한 명확한 검사가 빠를 것이다.
  2. 코드를 try-catch 블록 안에 넣으면 JVM이 적용할 수 있는 최적화가 제한된다.
    • JVM은 배열에 접근할 때마다 경계를 넘지않는지 매번 검사하므로. 이 부분을 제한한다는 추론으로 보인다.
  3. 배열을 순회하는 표준 관용구 는 JVM이 알아서 최적화해 중복 검사를 진행하지 않는다.
    • 여기서 말한 중복검사는 JVM에서의 배열 경계 검사와 반복문에서 배열 경계 검사를 말한다.

이런 추론으로 예외를 사용하지 않고 반복문을 사용하면 성능이 안 좋아 진다는 잘못된 추론이 발생한 것인데, 실제로는 예외를 사용한 쪽이 표준 관용구 보다 훨씬 느리다.

//표준 관용구
for(Mountain m : range) {
  m.climb();
}

 

예외를 사용한 반복문의 오류

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

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

  • Ex. 반복문의 몸체에서 호출한 메서드 내부에서 반복문과 관련 없는 배열을 사용하다가 ArrayInexOutOfBoundsException을 일으킬 시,
    • 표준 관용구였다면 이 버그는 예외를 잡지 않고 스택 추적 정보를 남긴 후 해당 스레드를 즉각 종료했을 것이다.
    • 그러나 예외를 통해 제어 흐름을 사용했다면, 버그 때문에 발생한 엉뚱한 예외를 정상적인 반복문에 의한 종료 상황으로 오해하고 넘어갈 것이다.

결과적으로, 예외는 오직 예외 상황에서만 사용해야하고, 절대로 일상적인 제어 흐름용으로는 사용해선 안 된다.

 

 

예외 관점에서 잘 설계된 API란,

잘 설계된 API는 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 한다.

즉, '상태 의존적' 메소드를 제공하는 클래스는 '상태 검사' 메소드도 함께 제공해야 한다.

  • Ex. Iterator 인터페이스의 next(상태 의존적 메소드)와 hasNext(상태 검사 메소드)를 제공한다.
//Iterator 표준 관용구 사용
for(Iterator<Foo> i = collection.iterator(); i.hashNext();) {
  Foo foo = i.next();
  ...
}

 

만약 Iterator가 hasNext를 제공하지 않았다면?

try{
  Iterator<Foo> i = collection.iterator();
  while(true) {
    Foo foo = i.next();
    ...
  }
} catch(NoSuchElementException e) {

}

이 코드는 우리가 잘못되었다고 판단했던 예외를 사용한 반복문의 오류와 비슷하다

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

 

 

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

  1. 외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다옵셔널이나 특정 값을 사용한다.
    • 상태 검사 메소드와 상태 의존적 메소드 호출 사이에 객체의 상태가 변할 수 있기 때문이다.
  2. 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메소드의 작업 일부를 중복 수행한다면 옵셔널이나 특정 값을 사용한다.
  3. 그 외 다른 경우에는 상태 검사 메소드 를 사용하는 것이 낫다.
반응형