희우 위키

HOME ABOUT ME STUDY RSS

자바 예외 패턴 (Exception Patterns in Java)

  • JAVA
  • 업데이트 중인 문서입니다.

우리는 코드를 작성할 때, 인터페이스를 자주 만든다.

그리고, 정상적인 실행 흐름을 정의하고 비정상적인 실행 흐름에 대한 제어를 해주기 위해 예외를 선언한다.

자바에서는 어떻게 예외를 던지는 것이 좋을까? 어떻게 예외를 선언하는 것이 좋을까?

하나씩 정리해보자.

1. 패키지 혹은 기능의 예외 슈퍼클래스를 정의하고, 퍼블릭 인터페이스는 그 예외만을 던지도록 공표하라.

인터페이스에 예외를 많이 선언하는데, 예외는 어떻게 선언(공표)하는 것이 좋을까? (여기서 말하는 인터페이스는 자바의 인터페이스를 의미하는 것이 아니라 어떤 계층 구조의 상위인 고수준 개념을 의미한다.)

  • 기능 혹은 패키지 단위의 예외 계층 구조의 슈퍼클래스(SuperClass)를 정의한다.
  • 모든 인터페이스에 대해서는 예외 계층 구조의 슈퍼클래스만을 던진다고 선언한다.

이유는 아래와 같다.

인터페이스의 변경은 매우 힘들다.

만약, throws절에 새로운 예외가 선언되어야 하는 시그니처가 변경되는 상황을 고려해보자.

모든 클라이언트에 대해 throws절에 새롭게 추가된 예외에 대한 핸들링이 요구되고, 또한 해당 인터페이스의 하위 계층에 속한 모든 기능들에 대하여 변경이 요구된다.

다만, 예외의 슈퍼클래스만을 지정해둔다면 새로운 예외가 정의되고 던져지더라도 인터페이스 변경없이 유연하게 확장이 가능하다.

: org.springframework.beans 패키지에 속한 일부 인터페이스 예외 선언부 예시

내가 주로 사용하는 스프링 프레임워크 프로젝트 내에서 예시를 찾아보았다.

아래는org.springframework.beans 패키지의 일부 인터페이스와 예외 클래스이다.

위에서 언급한 것과 같이 특정 기능에 대한 예외 계층의 슈퍼클래스를 정의해두고, 그 클래스만을 선언하고 있는 모습을 볼 수 있다.

ObjectProvider의 인터페이스 메서드 선언부 일부

BeansException 예외가 던져질 수 있다고 선언되어있다.

스크린샷 2023-06-15 오후 2 54 48

ObjectFactory 인터페이스의 메서드 선언부

마찬가지로, BeansException 예외가 던져질 수 있다고 선언되어있다.

스크린샷 2023-06-15 오후 2 55 24

선언된 BeansException 예외에 대한 클래스 정보

스크린샷 2023-06-15 오후 2 56 01

BeansException의 주석 일부를 읽어보면 다음과 같다.

Abstract superclass for all exceptions thrown in the beans package and subpackages. beans 패키지와 서브패키지 내의 모든 던져지는 예외에 대한 슈퍼클래스(SuperClass)이다.

: Effective Java 3/E 중 인용문

이펙티브 자바에서도 위 내용과 비슷한 가이드라인을 제시하고 있다. 맥락은 약간 다르지만, 아래 내용도 읽어보면 좋을 것 같다.

아래 계층의 예외를 예방하거나 스스로 처리할 수 없고, 그 예외를 상위 계층에 그대로 노출하기 곤란하다면 예외 번역을 사용하라. 이때 예외 연쇄를 이용하면 상위 계층에는 맥락에 어울리는 고수준 예외를 던지면서 근본 원인도 함께 알려주어 오류를 분석하기에 좋다. (아이템 75 중)

상위 계층에서는 저수준 예외를 잡아 자신의 추상화 수준에 맞는 예외로 바꾸어 던져야 한다. 이를 예외 번역(exception translation)이라고 한다. (아이템 73 중)

Effective Java에서는 위의 내용과 약간 반대된다고 생각하는 아이템도 있다.

공통 상위 클래스 하나로 뭉뚱그려 선언하는 일을 삼가자. (아이템 74 중)

나는 조금 다르게 생각한다. 모든 하위 계층의 서브 예외 클래스들을 공표할 수는 없다. 또한 인터페이스 변경은 생각보다 잦다.

Exception, Throwable, RuntimeException 등과 같은 정말 최상단의 슈퍼클래스가 아니고서야 우리가 정의한 패키지 단위의 슈퍼클래스는 분명히 의미가 있다.

그리고, 각 서브 클래스에서 던져지는 세부 예외들은 충분히 슈퍼클래스로 변환도 가능하며 클라이언트 입장에서 슈퍼클래스를 기준으로 핸들링도 가능하다.

2. 특수 사례 패턴(SPECIAL CASE PATTERN)을 적절히 활용하라.

이는 클린코드에서 발췌한 내용을 기반으로 작성된 내용이다.

특수 사례 패턴은 굳이 예외를 던져 애플리케이션 흐름을 중단시킬 이유가 없다면 정상흐름으로 처리할 수 있는 방안을 제시해준다.

public int getTotalAmount(long accountId) {
        int totalAmount = 0;
        try {
            TransactionHistory history = transactionHistoryRepository.findByAccountId(accountId);
            totalAmount += history.getTransactionAmount();
        } catch (EntityNotFoundException ex) {
            // 실행중단.
        }

        return totalAmount;
}

굳이, 실행을 중단시켜야 할 이유가 있는가? TransactionHistory 클래스를 상속 또는 확장한 클래스에서 다음과 같이 메서드를 오버라이딩 해준다면, 비정상흐름을 정상흐름으로 정의할 수 있다.

// Null Object Pattern.
public class NotFoundTransactionHistory extends TransactionHistory {
    @Override
    public int getTransactionAmount() {
        return 0;
    }
}

3. 작성중

요약

  • 예외 또한 확장성을 고려하여 정의해야한다.
    • 주로 package 및 기능 단위로 비정상 흐름을 정의할 예외에 대한 계층구조를 정의하고, 고수준의 인터페이스에는 예외처리의 계층구조의 가장 최상단 SuperClass를 선언한다.
  • 코드의 정상 실행 흐름을 정의하고 코드가 중단되지 않아도 되는 상황에서는 예외를 던지지 말자.
    • 이를 처리하기 위한 특수 사례 패턴(SPECIAL CASE PATTERN)을 기억하자.

참고 내용