Skip to content

Latest commit

 

History

History
157 lines (101 loc) · 5.21 KB

아이템 73. 추상화 수준에 맞는 예외를 던지라.md

File metadata and controls

157 lines (101 loc) · 5.21 KB

Item.73 추상화 수준에 맞는 예외를 던지라


개발을 하다가 수행하려는 일과 관련 없어 보이는 예외가 튀어나오면 개발자 입장에서는 당황스럽기 마련이다. 메서드가 저수준 예외를 처리하지 않고 바깥으로 전파해버릴 때 종종 일어나는 일이다.

즉, 이러한 일들은 단순히 프로그래머를 당황시키는 데 그치지 않고, 내부 구현 방식을 드러내는 치명적인 문제를 가지고 있기 때문에 상위 레벨의 API를 오염시키게 된다.

또한, 릴리스 과정에서 구현 방식을 바꾸면 다른 예외가 튀어나와 기존 클라이언트 프로그램을 깨지게 할 수 있는 가능성이 생기기도 한다.


상위 계층에서 예외 번역을 하자


이러한 문제를 피하려면 상위 계층에는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꿔 던져야 하는 작업을 수행해야 한다. 이를 예외 번역이라고 부른다.

try {
    ... // 저수준 추상화를 이용한다.
} catch (LowerLevelException e) {
    throw new HigherLevelException(...);
}

자바의 AbstractSequentialList를 통해서 예시를 한번 살펴보도록 하자.


public E get(int index) {
    try {
        return listIterator(index).next();
    } catch(NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: " + index);
    }
}

AbstractSequentialListList에서 인터페이스의 골격 구현을 의미한다.

이 클래스의 get 메서드의 명세를 보면 예외가 발생되었을 때 예외 변환을 해서 보내주는 것을 확인 할 수 있다.


SQLException을 통해서도 한번 살펴보자.

SQLException은 대표적인 체크 예외이다. 중요한 것은 SQLException은 예외를 잡아도 처리해 줄 수가 거의 없다. 그래서 다음과 같이 하는 방법을 추천한다.


try {
    ...
} catch (SQLException e) {
    if(e.getErrorCode()){
        // 처리가 가능한 경우
    } else{
        // 처리가 불가능 한 경우, 에러를 전송
        throw new RuntimeException(e);
    }
}

즉, 처리해 줄 수 있는 것은 처리해주고, 처리가 불가능 한것은 런타임 에러로 포장하여 호출하는 쪽에서 예외를 선언하지 않도록 해주는 것을 권장한다.



저수준 예외가 필요하면 예외 연쇄를 사용하자


예외 연쇄란 문제의 원인인 저수준 예외를 고수준 예외에 실어 보내는 것을 말한다. 별도의 접근자 메서드를 통해 언제든 저수준 예외를 꺼내 볼 수 있다.

하위 계층에서 발생한 예외 정보가 상위 계층 예외를 발생을 시킨 문제를 디버깅 할 때 보통 예외 연쇄를 사용한다고 한다.


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

고수준 예외의 생성자는 예외 연쇄용으로 설계된 상위 클래스의 생성자에 원인을 건네주어 Throwable 생성자로 건네지게 한다.

class HigherLevelException extends Excpetion {
    HigherLevelException(Throwable cause) {
        super(cause);
    }
}

대부분의 표준 예외는 예외 연쇄용 생성자를 갖추고 있다. 만약 예외 연쇄용 생성자를 갖추고 있지 않은 예외라도 ThrowableinitCause 메서드를 이용해 직접 원인을 설정할 수 있다.

간단한 예시를 통해 이를 살펴보자.


public static void main(String[] args) {
    try {
        NumberFormatException ex = new NumberFormatException("Exception");

        ex.initCause(new NullPointException("근본적인 원인"));
        throw ex;
    } catch (NumberFormatException ex) {
        ex.getCause().printStackTrace();
    }
}

즉, 예외 연쇄는 문제의 원인을 프로그램에서 접근할 수 있게 해주며, 원인과 고수준 예외의 스택 추적 정보를 잘 통합해준다.



예외 번역을 남용하지 말자


예외 번역은 예외 전파보다 우수한 방법이지만 그렇다고 남용해서 안된다. 가능하면 저수준 메서드가 반드시 성공하도록 하여 아래 계층에서는 예외가 발생하지 않도록 하는 것이 최선이다.

때론 상위 계층 메서드의 매개변수 값을 넘기기 전에 validator를 통해 미리 검사하는 방법으로 이 목적을 달성할 수 있다.

만약, 아래 계층에서 예외를 피할 수 없다면 상위 계층에서 그 예외를 처리하여 API 호출자에게 전파하지 않는 방법도 있다. 이 경우 로깅을 통해 로그를 기록해두고 프로그래머가 로그를 분석해 추가 조치를 취할 수 있게 하면 된다.



정리


결론적으로 제일 좋은 방법은 당연한 말이지만 에러없는 코딩을 하는 것!!

하위 계층에서 발생한 에러는 하위계층에서 처리하고 어쩔 수 없이 전달해야 하는 경우에는 적절한 예외로 변환해서 보내는 것이 중요!!