Backend/Java

[Effective Java : Item 69] 예외는 진짜 예외 상황에만 사용하라

김세진 2024. 3. 29. 13:52
반응형

 

 

 

 

 

개요

 

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배가량 차이) 그리고 만약 의도했던 배열이 아닌 다른 부분에서 ArrayIndexOutOfBoundsException이 발생했다면, 그 버그는 예외를 잡지 못하고 정상적인 반복문 종료 상황으로 오해하고 넘어갈 것이기 때문에 디버깅도 훨씬 어려워진다.

 

 

교훈

 

정리하자면, 예외는 오직 예외 상황에서만 써야 하고, 절대로 일상적인 제어 흐름용으로 쓰여선 안 된다. 표준적이고 쉽게 이해되는 관용구로 작성하고, 성능 개선을 위한 독특한 기법은 자제해야 한다. 실제로 성능이 좋아지더라도, 자바 플랫폼은 꾸준히 개선되고 있으므로 나중에도 유효한 성능 차이가 있음을 보장할 수 없고, 숨겨진 버그가 존재할 수 있어 유지보수에 어려움이 생기게 된다.

 

위 원칙은 API 설계에도 적용된다. 잘 설계된 API라면 클라이언트가 정상적인 제어 흐름에서 예외를 사용할 일이 없게 해야 한다. 이에 따라 '상태 의존적' 메서드를 제공하는 클래스는 '상태 검사' 메서드도 함께 제공해야 한다. Iterator 인터페이스의 next()와 hasNext()가 각각 상태 의존적 메서드와 상태 검사 메서드에 해당한다. 만약 Iterator가 hasNext()를 제공하지 않았다면, 클라이언트는 개요에서 보았던 대로 예외를 활용해야 할 수밖에 없었을 것이다.

 

이외에도 상태 검사 메서드를 제공하는 대신, 빈 옵셔널 혹은 특정 값(null 등)을 반환하는 방법도 존재한다. 세 가지 선택지 중 어느 것이 적절한 지 상황 별로 정리하자면 다음과 같다.

 

빈 옵셔널 혹은 특정 값

- 외부 동기화 없이 여러 스레드가 동시에 접근할 수 있거나, 외부 요인으로 상태가 변할 수 있는 경우 사용. 상태 검사 메서드와 상태 의존적 메서드 호출 사이에 객체의 상태가 변할 수 있기 때문

- 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행하는 경우 사용

 

상태 검사 메서드

- 위에서 언급된 경우 외 모든 상황에서 조금 더 낫다.

- 가독성이 좋고, 잘못 사용했을 때 상태 의존적 메서드가 예외를 던져 버그를 발견하기 쉽다.

 

 

정리

 

1. 예외는 예외 상황에서 쓸 의도로 설계되었으므로, 정상적인 제어 흐름에서는 사용해서는 안 된다.

2. 이를 프로그래머에게 강요하는 API를 만들어서도 안 된다.

 

 

 

 

반응형