Java

[JAVA] 커스텀 예외의 4가지 Best Practices

에드박 2021. 4. 13. 22:25

 

커스텀 예외를 구현할 때 참고하면 좋을 4가지 Best Practice가 있습니다.

4가지 추천사항들은 여러분의 코드와 API를 더 이해하기 쉽게 만들어주며 작성해야할 문서의 양도 줄여줍니다.

 


1. Always Provide a Benefit (항상 혜택을 제공하라)

자바 표준 예외들에는 포함되어 있는 다양한 장점을 가지는 기능이 있습니다.

 

커스텀 예외의 의도는 자바의 표준 예외들로 표현할 수 없는 정보나 기능을 제공하는 것입니다.

이것은 커스텀 예외를 만들 때 최우선으로 생각해야할 것입니다. 만약 위의 의도가 없다면 JDK가 이미 제공하고 있는 방대한 수의 예외들과 비교했을 때 우리의 커스텀 예외는 어떠한 장점도 제공하지 못하게 됩니다.

 

어떠한 장점도 제공할 수 없는 커스텀 예외를 만드는것 보다 오히려 UnsupportedOperationException 이나 IllegalArgumentException 과 같이 표준 예외들 중 하나를 사용하는 것이 낫습니다. 

자바 개발자들은 표준 예외들을 이미 알고 있고 이런 표준 예외를 사용한다는 것은 우리의 코드와 API를 이해하는데 도움을 줍니다.

 


2. Follow the Naming Convention (네이밍 컨벤션을 따라가라)

JDK가 제공하는 예외 클래스들을 보면 클래스의 이름들이 모두 "Exception"으로 끝난다는 것을 알 수 있습니다. 

일반적인 네이밍 규칙으로 자바 생태계 전체에 적용되는 규칙입니다. 

즉, 여러분이 만들 커스텀 예외도 해당 네이밍 규칙을 따르는 것이 좋습니다.


3. Provide Javadoc Comments for Your Exception Class (예외 클래스에 대한 Javadoc 주석제공)

일부 커스텀 예외는 어떠한 Javadoc 코멘트도 없이 만들어진 경우가 있습니다.

 

기본적으로 API의 모든 클래스, 멤버변수, 생성자들에 대해서는 문서화 하는것이 일반적인 Best Practice 이다.

잘 알겠지만 문서화되지 않은 API들은 사용하기 매우 어렵습니다.

(최고는 클래스, 변수, 메서드들의 네이밍을 문서없이도 이해할 수 있을 정도로 잘 붙인 API이지만 그만큼의 노력이 필요하다!)

 

예외 클래스들은 API에서 외부적으로는 크게 드러나지 않는 부분일 수 있지만 사실상 그렇지 않습니다.

클라이언트와 직접 관련된 메서드가 예외를 던지면 그 예외는 바로 API의 일부가 됩니다.

그렇다는 것은 잘 만들어진 Javadoc이 필요하다는 의미입니다.

 

커스텀 예외의 Javadoc에는 예외가 발생할 수도 있는 상황과 예외의 일반적인 의미를 기술합니다.

해당 문서의 목적은 다른 개발자들이 우리의 API를 이해하도록 하고 예외 상황을 피하도록 돕기 위함 입니다.

 

/**
 * The MyBusinessException wraps all checked standard Java exception and enriches them with a custom error code.
 * You can use this code to retrieve localized error messages and to link to our online documentation.
 * 
 * @author TJanssen
 */
public class MyBusinessException extends Exception { ... }

4. Provider Constructor That Sets the Cause

 

보통 커스텀 예외를 사용할 때 자주 사용하는 형식은 표준 예외를 캐치해서 커스텀 예외로 전환해서 던지는 경우가 많습니다.

자바의 표준 예외 RuntimeException을 캐치해서 커스텀 예외로 전환하는 경우

 

캐치된 예외에는 여러분이 제품에 발생한 오류를 분석하는데 필요한 정보가 포함되어 있습니다.

 

아래의 예제를 보면 NumberFormatException은 에러에 대한 상세 정보를 제공합니다.

MyBusinessException을 생성할 때 cause정보를 설정하지 않으면 이전에 발생시켰던 에러의 정보를 잃을 것입니다.

 

public void wrapException(String input) throws MyBusinessException {
    try {
        // do something
    } catch (NumberFormatException e) {
        throw new MyBusinessException("A message that describes the error.", e, ErrorCode.INVALID_PORT_CONFIGURATION);
    }
}

 

Exception 과 RuntimeException 은 예외의 원인을 기술하고 있는 Throwable을 받을 수 있는 생성자 메서드를 제공합니다.

우리의 커스텀 예외도 이렇게 생성자에서 예외의 원인을 인자로 받는것이 좋습니다. 

발생한 Throwable을 파라미터를 통해 가져올 수 있는 생성자를 최소한 하나를 구현하고 super 생성자에 Throwable를 전달해줘야 한다.

 

public class MyBusinessException extends Exception {
    public MyBusinessException(String message, Throwable cause, ErrorCode code) {
            super(message, cause);
            this.code = code;
        }
        ...
}

 


4가지 Best Practice를 적용시킨 CheckedException

/*
* 이것은 커스텀 CheckedException 입니다.
* 자바의 기본 예외로 표현할 수 없는 예외를 제공하기 위해 만들어졌습니다. 
* */
public class CustomCheckedExceptionEx extends Exception {

    public CustomCheckedExceptionEx() {
    }

    public CustomCheckedExceptionEx(String message) {
        super(message);
    }

    public CustomCheckedExceptionEx(String message, Throwable cause) {
        super(message, cause);
    }

    public CustomCheckedExceptionEx(Throwable cause) {
        super(cause);
    }

    public CustomCheckedExceptionEx(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

class CustomCheckedExceptionTest {
    public static void main(String[] args) throws CustomCheckedExceptionEx {
        try {
            // doSomething
        } catch (Exception e) {
            throw new CustomCheckedExceptionEx("커스텀 체크드 예외 발생!!", e);
        }
    }
}

4가지 Best Practice를 적용시킨 UncheckedException

public class CustomUncheckedException extends RuntimeException {

    public CustomUncheckedException() {
    }

    public CustomUncheckedException(String message) {
        super(message);
    }

    public CustomUncheckedException(String message, Throwable cause) {
        super(message, cause);
    }

    public CustomUncheckedException(Throwable cause) {
        super(cause);
    }

    public CustomUncheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

class CustomUncheckedExceptionTest {
    public static void main(String[] args) {
        try {
            // doSomething
        } catch (RuntimeException e) {
            throw new CustomUncheckedException("커스텀 언체크드 예외 발생!!", e);
        }
    }
}

참고 문헌

- m.blog.naver.com/sthwin/221144722072

- www.notion.so/3565a9689f714638af34125cbb8abbe8

- dzone.com/articles/implementing-custom-exceptions-in-java?fromrel=true

-