Javaで例外の根本原因を見つける方法

1. 前書き

Javaでは、ネストされた例外がエラーの原因の追跡に役立つため、ネストされた例外を処理することは非常に一般的です。
これらの種類の例外に対処する場合、*例外を引き起こした元の問題を知りたい場合があります。そうすれば、アプリケーションはケースごとに異なる応答をすることができます*。 これは、ルート例外を独自にラップするフレームワークを使用する場合に特に便利です。
この短い記事では、プレーンJavaとhttps://commons.apache.org/proper/commons-lang/[Apache Commons Lang]やhttpsなどの外部ライブラリを使用して根本原因の例外を取得する方法を示します。 //github.com/google/guava[Google Guava]。

2. 年齢計算アプリ

私たちのアプリケーションは、ISO形式の_String_として受け取った特定の日付からの人の年齢を示す年齢計算機です。 日付を解析するときに、フォーマットが不適切な日付と将来の日付の2つのエラーケースを処理します。
最初にエラーケースの例外を作成しましょう:
static class InvalidFormatException extends DateParseException {

    InvalidFormatException(String input, Throwable thr) {
        super("Invalid date format: " + input, thr);
    }
}

static class DateOutOfRangeException extends DateParseException {

    DateOutOfRangeException(String date) {
        super("Date out of range: " + date);
    }

}
両方の例外は、コードを少し明確にする共通の親例外から継承します。
static class DateParseException extends RuntimeException {

    DateParseException(String input) {
        super(input);
    }

    DateParseException(String input, Throwable thr) {
        super(input, thr);
    }
}
その後、日付を解析するメソッドで_AgeCalculator_クラスを実装できます。
static class AgeCalculator {

    private static LocalDate parseDate(String birthDateAsString) {
        LocalDate birthDate;
        try {
            birthDate = LocalDate.parse(birthDateAsString);
        } catch (DateTimeParseException ex) {
            throw new InvalidFormatException(birthDateAsString, ex);
        }

        if (birthDate.isAfter(LocalDate.now())) {
            throw new DateOutOfRangeException(birthDateAsString);
        }

        return birthDate;
    }
}
ご覧のとおり、形式が間違っている場合は、_DateTimeParseException_をカスタム_InvalidFormatException._にラップします。
最後に、日付を受け取り、解析してから年齢を計算するパブリックメソッドをクラスに追加しましょう。
public static int calculateAge(String birthDate) {
    if (birthDate == null || birthDate.isEmpty()) {
        throw new IllegalArgumentException();
    }

    try {
        return Period
          .between(parseDate(birthDate), LocalDate.now())
          .getYears();
    } catch (DateParseException ex) {
        throw new CalculationException(ex);
    }
}
示されているように、例外を再度ラップしています。 この場合、これらを作成する必要がある_CalculationException_にラップします。
static class CalculationException extends RuntimeException {

    CalculationException(DateParseException ex) {
        super(ex);
    }
}
これで、日付をISO形式で渡すことで、計算機を使用する準備が整いました。
AgeCalculator.calculateAge("2019-10-01");
そして、計算が失敗した場合、問題が何であるかを知ることは有用でしょうか? どうすればそれを実現できるかをお読みください。

*3. プレーンJava *を使用して根本原因を見つける

根本原因の例外を見つけるために使用する最初の方法は、*ルートに到達するまですべての原因をループするカスタムメソッドを作成することです*。
public static Throwable findCauseUsingPlainJava(Throwable throwable) {
    Objects.requireNonNull(throwable);
    Throwable rootCause = throwable;
    while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
        rootCause = rootCause.getCause();
    }
    return rootCause;
}
*再帰的な原因を処理するときに無限ループを回避するために、ループに余分な条件を追加したことに注意してください。*
_AgeCalculator_に無効な形式を渡すと、根本原因として_DateTimeParseException_を取得します。
try {
    AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
    assertTrue(findCauseUsingPlainJava(ex) instanceof DateTimeParseException);
}
ただし、将来の日付を使用すると、_DateOutOfRangeException_が取得されます。
try {
    AgeCalculator.calculateAge("2020-04-04");
} catch (CalculationException ex) {
    assertTrue(findCauseUsingPlainJava(ex) instanceof DateOutOfRangeException);
}
さらに、このメソッドはネストされていない例外に対しても機能します。
try {
    AgeCalculator.calculateAge(null);
} catch (Exception ex) {
    assertTrue(findCauseUsingPlainJava(ex) instanceof IllegalArgumentException);
}
この場合、_null_を渡したため、_IllegalArgumentException_を取得します。

*4. Apache Commons Lang *を使用して根本原因を見つける

カスタム実装を記述する代わりに、サードパーティのライブラリを使用して根本原因を見つける方法を示します。
Apache Commons Langは_https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/exception/ExceptionUtils.java [ExceptionUtils] _クラスを提供します。例外を処理するユーティリティメソッド。
前の例で_getRootCause()_メソッドを使用します:
try {
    AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
    assertTrue(ExceptionUtils.getRootCause(ex) instanceof DateTimeParseException);
}
以前と同じ根本原因がわかります。 上記のリストにある他の例にも同じ動作が適用されます。

*5. Guava *を使用して根本原因を見つける

最後に試すのは、グアバを使用する方法です。 Apache Commons Langと同様、https://github.com/google/guava/blob/master/guava/src/com/google/common/base/Throwables.java [_Throwables_]クラスと_getRootCause()_を提供します。ユーティリティメソッド。
同じ例を使って試してみましょう。
try {
    AgeCalculator.calculateAge("010102");
} catch (CalculationException ex) {
    assertTrue(Throwables.getRootCause(ex) instanceof DateTimeParseException);
}
動作は他の方法とまったく同じです。

6. 結論

この記事では、アプリケーションでネストされた例外を使用する方法を示し、ユーティリティメソッドを実装して根本原因の例外を見つけました。
また、Apache Commons LangやGoogle Guavaなどのサードパーティライブラリを使用して同じことを行う方法も示しました。
いつものように、例の完全なソースコードは、https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-exceptions [GitHubで]から入手できます。