Skip to content

Latest commit

 

History

History
96 lines (67 loc) · 8.5 KB

File metadata and controls

96 lines (67 loc) · 8.5 KB

item 73 : 주상화 수준에 맞는 예외를 던지라

예외 번역과 예외 연쇄의 중요성

수행하려는 일과 관련 없어 보이는 예외가 튀어나오면 프로그래머는 당황할 수 있다. 특히, 메서드가 저수준 예외를 처리하지 않고 바깥으로 전파해버리면, 내부 구현 방식을 노출하게 되어 윗 레벨 API를 오염시키는 문제가 생긴다. 이는 API 사용자의 혼란을 초래할 뿐만 아니라 유지보수에 상당한 부담을 준다. 더 나아가, 다음 릴리스에서 구현 방식을 변경하면 다른 예외가 발생해 기존 클라이언트 프로그램이 깨질 수 있는 위험이 있다. 이러한 문제를 피하려면 예외를 올바르게 처리하고 번역하는 것이 매우 중요하다.

1. 예외 번역의 필요성

1) 예외 번역이란?

저수준 예외를 상위 계층에 그대로 노출하지 않기 위해 상위 계층에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야 한다. 이를 예외 번역(exception translation)이라고 한다.

예외 번역을 통해 상위 계층에서는 저수준 구현의 구체적인 내용을 숨기고, 고수준 API에 맞는 예외만을 노출할 수 있다. 이렇게 하면 API의 일관성과 안정성을 높일 수 있다.

// 코드 73-1 예외 번역
try {
    // 저수준 추상화를 이용한다.
} catch (LowerLevelException e) {
    // 추상화 수준에 맞게 번역한다.
    throw new HigherLevelException(...);
}

위 예시에서, LowerLevelException은 저수준 구현에서 발생한 예외이며, 이를 상위 계층에 맞는 HigherLevelException으로 번역하여 던진다. 이렇게 하면 상위 계층에서 저수준 구현의 세부 사항을 숨기고, 사용자에게 더 일관된 인터페이스를 제공할 수 있다.

2) 예외 번역 예시

다음은 AbstractSequentialList에서 수행하는 예외 번역의 예이다. AbstractSequentialListList 인터페이스의 골격 구현이며, 이 예에서 수행한 예외 번역은 List<E> 인터페이스의 get 메서드 명세에 명시된 필수사항을 준수한다.

@Override
public E get(int index) {
    ListIterator<E> i = listIterator(index);
    try {
        return i.next();
    } catch (NoSuchElementException e) {
        throw new IndexOutOfBoundsException("인덱스: " + index);
    }
}

위 코드에서 NoSuchElementException은 저수준 예외이며, 이를 상위 계층의 IndexOutOfBoundsException으로 번역하여 던진다. 이렇게 하면 클라이언트는 리스트의 인덱스가 잘못되었음을 직관적으로 이해할 수 있게 된다. 이와 같은 예외 번역을 통해 저수준 예외를 고수준 예외로 변환하여, 클라이언트에게 더 명확하고 직관적인 오류 메시지를 제공할 수 있다.

2. 예외 연쇄의 필요성

1) 예외 연쇄란?

예외를 번역할 때, 저수준 예외가 디버깅에 도움이 된다면 예외 연쇄(exception chaining)를 사용하는 것이 좋다. 예외 연쇄문제의 근본 원인(cause)인 저수준 예외를 고수준 예외에 실어 보내는 방식이다. 이렇게 하면 별도의 접근자 메서드(ThrowablegetCause 메서드)를 통해 언제든지 저수준 예외를 확인할 수 있다.

// 코드 73-2 예외 연쇄
try {
    // 저수준 추상화를 이용한다.
} catch (LowerLevelException cause) {
    // 저수준 예외를 고수준 예외에 실어 보낸다.
    throw new HigherLevelException(cause);
}

예외 연쇄를 사용하면 저수준 예외의 정보를 유지하면서도 상위 계층에서는 보다 추상화된 정보를 제공할 수 있다. 고수준 예외의 생성자는 상위 클래스의 생성자에 이 원인을 전달하여 최종적으로 Throwable 생성자까지 전파된다.

// 코드 73-3 예외 연쇄용 생성자
class HigherLevelException extends Exception {
    HigherLevelException(Throwable cause) {
        super(cause);
    }
}

대부분의 표준 예외는 예외 연쇄용 생성자를 갖추고 있다. 그렇지 않은 예외라도 ThrowableinitCause 메서드를 사용해 원인을 설정할 수 있다. 예외 연쇄는 문제의 원인을 프로그램에서 접근할 수 있게 해주며, 원인과 고수준 예외의 스택 추적 정보를 잘 통합해준다. 이를 통해 디버깅과 문제 해결 과정에서 보다 유용한 정보를 제공할 수 있다.

3. 예외 처리의 최선책과 차선책

1) 예외 번역의 우수성

무턱대고 예외를 전파하는 것보다는 예외 번역이 훨씬 우수하다. 저수준 예외를 상위 계층에 그대로 노출하면, API 사용자는 내부 구현에 대한 불필요한 정보를 접하게 되며, 이는 API 사용성을 떨어뜨릴 수 있다. 특히, 저수준 예외가 의미하는 바가 상위 계층의 추상화와 맞지 않는 경우, 사용자에게 불필요한 혼란을 초래할 수 있다.

예외 번역의 장점: 예외 번역을 통해 상위 계층에서는 맥락에 맞는 고수준 예외를 받으며, 저수준 구현의 세부사항을 알 필요가 없다. 이는 API의 일관성을 유지하는 데도 도움이 된다.

2) 저수준 예외의 예방 및 처리

  • 예외 예방: 가능하다면 저수준 메서드가 반드시 성공하도록 설계하여 아래 계층에서는 예외가 발생하지 않도록 하는 것이 최선이다. 예를 들어, 상위 계층 메서드에서 매개변수 값을 아래 계층 메서드로 전달하기 전에 미리 검사함으로써 예외를 방지할 수 있다. 이렇게 하면 저수준에서 발생할 수 있는 많은 오류를 사전에 차단할 수 있다.
  • 예외 처리: 아래 계층에서의 예외를 피할 수 없다면, 상위 계층에서 그 예외를 조용히 처리하여 문제를 API 호출자에까지 전파하지 않는 방법도 있다. 이 경우, 발생한 예외는 java.util.logging 같은 적절한 로깅 기능을 활용해 기록해두는 것이 좋다. 이렇게 하면 클라이언트 코드와 사용자에게 문제를 전파하지 않으면서도, 프로그래머가 로그를 분석해 추가 조치를 취할 수 있다.

예외를 예방하거나 조용히 처리하는 것은 예외 번역보다 우수한 접근 방식일 수 있다. 예를 들어, 잘못된 입력 값을 조기에 검사하여 예외를 발생시키지 않거나, 발생한 예외를 로깅하고 정상적으로 대체 작업을 수행하는 것이 바람직할 수 있다. 이렇게 하면 사용자 경험을 해치지 않으면서도 문제를 해결할 수 있다.

4. 예외 번역과 예외 연쇄의 핵심 정리

  • 예외 번역: 아래 계층의 예외를 예방하거나 스스로 처리할 수 없고, 그 예외를 상위 계층에 그대로 노출하기 곤란하다면 예외 번역을 사용하라. 예외 번역을 통해 상위 계층에서는 저수준 구현의 세부사항을 알 필요 없이, 자신의 추상화 수준에 맞는 예외만을 받을 수 있다.
  • 예외 연쇄: 예외 연쇄를 이용하면 상위 계층에는 맥락에 어울리는 고수준 예외를 던지면서, 근본 원인도 함께 알려주어 오류 분석에 도움을 줄 수 있다. 이는 특히 디버깅과 문제 원인 파악에 큰 도움이 된다.
  • 최선책: 저수준 예외를 예방하거나 상위 계층에서 미리 처리하는 것이 최선이다. 예외가 발생할 가능성을 사전에 차단함으로써, 시스템의 안정성과 사용성을 높일 수 있다.
  • 차선책: 상위 계층에서 예외를 조용히 처리하고 로깅하여 문제를 전파하지 않는 것도 좋은 방법이다. 이를 통해 사용자에게 불필요한 혼란을 주지 않으면서 문제를 처리할 수 있다.

올바른 예외 처리는 프로그램의 안정성을 높이고, 유지보수 비용을 줄이는 중요한 요소이다. 예외 번역과 예외 연쇄를 적절히 활용하여, 프로그래머와 API 사용자가 모두 이해하기 쉬운 코드를 작성하는 것이 바람직하다. 예외 처리 전략은 코드의 가독성과 안정성, 유지보수성에 중요한 영향을 미치며, 각 상황에 맞는 적절한 예외 처리 방식을 선택하는 것이 중요하다.