NoExceptionの紹介

NoExceptionの概要

1. 概要

try/catchブロックは、冗長な、または厄介なコード構造になる場合があります。

この記事では、NoException which provides concise and handy exception handlers.に焦点を当てます

2. メーベン依存

NoExceptionpom.xmlに追加しましょう。


    com.machinezoo.noexception
    noexception
    1.1.0

3. 標準の例外処理

一般的に見られるイディオムから始めましょう。

private static Logger logger = LoggerFactory.getLogger(NoExceptionUnitTest.class);

@Test
public void whenStdExceptionHandling_thenCatchAndLog() {
    try {
        logger.info("Result is " + Integer.parseInt("foobar"));
    } catch (Throwable exception) {
        logger.error("Caught exception:", exception);
    }
}

まず、Loggerを割り当ててから、tryブロックを入力します。 Exceptionがスローされた場合、ログに記録します。

09:29:28.140 [main] ERROR c.b.n.NoExceptionUnitTest
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

4. NoExceptionでの例外の処理

4.1. デフォルトのロギングハンドラ

これをNoExceptionの標準例外ハンドラに置き換えましょう。

@Test
public void whenDefaultNoException_thenCatchAndLog() {
    Exceptions
      .log()
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

このコードにより、上記とほぼ同じ出力が得られます。

09:36:04.461 [main] ERROR c.m.n.Exceptions
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

In its most basic form, NoException provides us with a way to replace try/catch/ exceptions with a single line of code.run()に渡すラムダを実行し、Exceptionがスローされると、ログに記録されます。

4.2. カスタムLoggerの追加

出力をよく見ると、例外がログクラスではなくログクラスとして記録されていることがわかります。

ロガーを提供することで、これを修正できます。

@Test
public void whenDefaultNoException_thenCatchAndLogWithClassName() {
    Exceptions
      .log(logger)
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

次の出力が得られます。

09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest
  - Caught exception
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

4.3. カスタムログメッセージの提供

デフォルトの「キャッチされた例外」とは異なるメッセージを使用したい場合があります。 We can do this by passing a Logger as the first argument and a String message as the second:

@Test
public void whenDefaultNoException_thenCatchAndLogWithMessage() {
    Exceptions
      .log(logger, "Something went wrong:")
      .run(() -> System.out.println("Result is " + Integer.parseInt("foobar")));
}

次の出力が得られます。

09:55:23.724 [main] ERROR c.b.n.NoExceptionUnitTest
  - Something went wrong:
j.l.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

しかし、parseInt()が失敗したときにフォールバック値を挿入するなど、Exceptionsをログに記録するだけでは不十分な場合はどうでしょうか。

4.4. デフォルト値の指定

Exceptionsは、Optionalでラップされた結果を返すことができます。 ターゲットが失敗した場合にデフォルト値を提供するために使用できるように、物事を動かしてみましょう。

@Test
public void
  givenDefaultValue_whenDefaultNoException_thenCatchAndLogPrintDefault() {
    System.out.println("Result is " + Exceptions
      .log(logger, "Something went wrong:")
      .get(() -> Integer.parseInt("foobar"))
      .orElse(-1));
}

Exceptionはまだ表示されています:

12:02:26.388 [main] ERROR c.b.n.NoExceptionUnitTest
  - Caught exception java.lang.NumberFormatException: For input string: "foobar"
at j.l.NumberFormatException.forInputString(NumberFormatException.java:65)
at j.l.Integer.parseInt(Integer.java:580)
...

ただし、コンソールにもメッセージが出力されます。

Result is -1

5. カスタムロギングハンドラーの作成

これまでのところ、単純なtry/catch/logシナリオで繰り返しを回避し、コードを読みやすくするための優れた方法があります。 異なる動作のハンドラーを再利用したい場合はどうしますか?

NoExceptionExceptionHandlerクラスを拡張し、例外の種類に応じて次の2つのいずれかを実行してみましょう。

public class CustomExceptionHandler extends ExceptionHandler {

Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);

    @Override
    public boolean handle(Throwable throwable) {
        if (throwable.getClass().isAssignableFrom(RuntimeException.class)
          || throwable.getClass().isAssignableFrom(Error.class)) {
            return false;
        } else {
            logger.error("Caught Exception", throwable);
            return true;
        }
    }
}

ErrorまたはRuntimeExceptionが表示されたときにfalseを返すことで、ExceptionHandlerに再スローするように指示しています。 他のすべてに対してtrueを返すことにより、例外が処理されたことを示します。

まず、標準の例外を除いてこれを実行します。

@Test
public void givenCustomHandler_whenError_thenRethrowError() {
    CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
    customExceptionHandler.run(() -> "foo".charAt(5));
}

ExceptionHandler:から継承したカスタムハンドラーのrun()メソッドに関数を渡します

18:35:26.374 [main] ERROR c.b.n.CustomExceptionHandler
  - Caught Exception
j.l.StringIndexOutOfBoundsException: String index out of range: 5
at j.l.String.charAt(String.java:658)
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:20)
at c.b.n.CustomExceptionHandling.lambda$main$0(CustomExceptionHandling.java:10)
at c.m.n.ExceptionHandler.run(ExceptionHandler.java:1474)
at c.b.n.CustomExceptionHandling.main(CustomExceptionHandling.java:10)

この例外はログに記録されます。 Errorで試してみましょう:

@Test(expected = Error.class)
public void givenCustomHandler_whenException_thenCatchAndLog() {
    CustomExceptionHandler customExceptionHandler = new CustomExceptionHandler();
    customExceptionHandler.run(() -> throwError());
}

private static void throwError() {
    throw new Error("This is very bad.");
}

そして、Errorがログに記録されるのではなく、main()に再スローされたことがわかります。

Exception in thread "main" java.lang.Error: This is very bad.
at c.b.n.CustomExceptionHandling.throwSomething(CustomExceptionHandling.java:15)
at c.b.n.CustomExceptionHandling.lambda$main$0(CustomExceptionHandling.java:8)
at c.m.n.ExceptionHandler.run(ExceptionHandler.java:1474)
t c.b.n.CustomExceptionHandling.main(CustomExceptionHandling.java:8)

そのため、プロジェクト全体で一貫した例外処理のために使用できる再利用可能なクラスがあります。

6. 結論

NoExceptionを使用すると、1行のコードで、ケースバイケースで例外処理を簡素化できます。

コードはthis GitHub projectにあります。