[Effective Java 스터디 week13] [Item69] 예외는 진짜 예외 상황에만 사용하라

Coen·2023년 1월 25일
1

Effective Java study

목록 보기
12/13
post-thumbnail

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

예외를 완전히 잘못 사용한 예.

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이 알아서 최적화해 없애준다.

실상은 예외를 사용한 쪽이 표준 관용구보다 훨씬 느리다. (조선생님 컴퓨터에서 테스트해보니 2배 차이 났다고 한다.)

심지어 생각한대로 동작하지 않을 수 있다. 반복문 안에 버그가 숨어 있다면 흐름 제어에 쓰인 예외가 이 버그를 숨겨 디버깅을 훨씬 어렵게 할 것이다.

관련 없는 배열을 사용하다가 ArrayIndexOutOfBoundsException 예외를 만나는 경우, 정상적인 반복문 종료 상황으로 오해하고 넘어갈 수 있다.

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

표준적이고 쉽게 이해되는 관용구를 사용하고, 성능 개선을 목적으로 과하게 머리를 쓴 기법은 자제하라.

과하게 영리한 기법에 숨겨진 미묘한 버그의 폐해와 어려워진 유지보수 문제는 계속 이어질 것이다.

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

특정 상태에서만 호출할 수 있는 상태 의존적 메서드를 제공하는 클래스는 상태 검사 메서드도 함께 제공해야 한다.

예를 들면 Iterator 인터페이스의 nexthasNext 가 각각 상태 의존적 메서드와 상태 검사 메서드에 해당된다.

for-each도 내부적으로 hasNext를 사용하는데, 이러한 별도의 상태 검사 메서드 덕분에 표준 for 관용구를 사용할 수 있다.

for (Iterator<Foo> i = collection.iterator(); i.hasNext(); ) {
    Foo foo = i.next();
        ...
}

IteratorhasNext를 제공하지 않았다면 처음의 잘못 사용된 예처럼 클라이언트가 대신 그 일을 해야만 했다.

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

올바르지 않은 상태일 때 빈 옵셔늘 혹은 null 같은 특수한 값을 반환하는 방법이다.

상태 검사 메서드, 옵셔널, 특정 값 중 하나를 선택하는 지침은 아래와 같다.

  1. 외부 동기화 없이 여러 쓰레드가 동시에 접근할 수 있거나 외부 요인으로 상태가 변할 수 있다면 옵셔널이나 특정 값을 사용한다. 상태 검사 메서드와 상태 의존적 메서드 호출 사이에 객체의 상태가 변할 수 있기 때문이다.
  2. 성능이 중요한 상황에서 상태 검사 메서드가 상태 의존적 메서드의 작업 일부를 중복 수행한다면 옵셔널이나 특정 값을 선택한다.
  3. 다른 모든 경우엔 상태 검사 메서드 방식이 조금 더 낫다고 할 수 있다. 가독성이 살짝 더 좋고, 잘못 사용했을 때 발견하기가 쉽다. 상태 검사 메서드 호출을 깜빡 잊었다면 상태 의존적 메서드가 예외를 던져 버그를 확실히 드러낼 것이다. 반면 특정 값은 검사하지 않고 지나쳐도 발견하기가 어렵다.(옵셔널은 해당 안함)

정리
예외는 예외 상황에서 쓸 의도로 설계되었다. 정상적인 제어 흐름에서 사용해서는 안되며, 이를 프로그래머에게 강요하는 API를 만들어서도 안된다.

profile
백엔드 프로그래머

0개의 댓글