NoExceptionの紹介
1. 概要
try / catch ブロックは、冗長な、または厄介なコード構造になる場合があります。
この記事では、簡潔で便利な例外ハンドラーを提供するNoExceptionに焦点を当てます。
2. Mavenの依存関係
NoExceptionをpom.xmlに追加しましょう。
<dependency>
<groupId>com.machinezoo.noexception</groupId>
<artifactId>noexception</artifactId>
<version>1.1.0</version>
</dependency>
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)
...
4.2. カスタムロガーの追加
出力をよく見ると、例外がログクラスではなくログクラスとしてログに記録されていることがわかります。
ロガーを提供することで、これを修正できます。
@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. カスタムログメッセージの提供
デフォルトの「CaughtException」とは異なるメッセージを使用したい場合があります。 これを行うには、最初の引数としてロガーを渡し、2番目の引数として文字列メッセージを渡します。
@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()が失敗したときにフォールバック値を挿入するなど、例外をログに記録するだけでは不十分な場合はどうでしょうか。
4.4. デフォルト値の指定
例外は、オプションでラップされた結果を返すことができます。 ターゲットが失敗した場合にデフォルト値を提供するために使用できるように、物事を動かしてみましょう。
@Test
public void
givenDefaultValue_whenDefaultNoException_thenCatchAndLogPrintDefault() {
System.out.println("Result is " + Exceptions
.log(logger, "Something went wrong:")
.get(() -> Integer.parseInt("foobar"))
.orElse(-1));
}
例外が引き続き表示されます。
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シナリオでコードを読みやすくするための優れた方法があります。 別の動作でハンドラーを再利用したい場合はどうなりますか?
NoExceptionのExceptionHandlerクラスを拡張し、例外の種類に応じて次の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)
この例外はログに記録されます。 エラーで試してみましょう:
@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.");
}
そして、エラーがログに記録されるのではなく、 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行のコードで、ケースバイケースで例外処理を簡素化できます。
コードはこのGitHubプロジェクトにあります。