Kotlin1.3 から登場した Result 型。と言っても登場したのは 2018 年の 12 月頃なので、もう半年以上も前の話です。
今回、新しいプロジェクトに携わることになって、SpringBoot や Gradle 含め、Kotlin も 1.3 を利用するようにもろもろバージョンアップされていました。
大きなメジャーバージョンアップじゃない限り、通常は下位バージョン互換が保証されているものが多く、新機能にこだわりがなければ何も意識しないまま過ごしてしまうところですが、コードレビューで runCatching について指摘を受けたので調べてみました。
runCatchingの使いどころ
runCatching は Result 型を返す関数で、以下の 2 つの状態を返すことができます。
Success T
Failure Throwable
公式のサンプルを見てみましょう。
よくある try-catch を使ったエラーハンドリングです。
1 2 3 4 5 6 | try { val data = doSomethingSync() processData(data) } catch(e: Throwable) { showErrorDialog(e) } |
これを runCatching を使うことで以下のように書くことができます。
try-catch の場合は processData() の例外もスローされちゃいますが、runCatching を使うことで doSomethingSync() が成功した時と失敗した時のハンドリングがわかりやすくなりますね。
1 2 3 | runCatching { doSomethingSync() } .onFailure { showErrorDialog(it) } .onSuccess { processData(it) } |
Also note, that the code with try/catch has different semantics, since it also catches exceptions that could have been thrown by processData. Preserving functional-style error-handling semantics using try/catch is quite non-trivial (see Error handling alternative section).
エラーハンドリングと戻り値
runCatching の結果は Result 型になるので、上記サンプルの doSomethingSync() の結果はそのままでは使えません。
よって、シンプルに成功した場合は doSomethingSync() の結果を返し、失敗した場合は特定の Exception を発生させる場合は以下のように書きます。
1 2 3 4 5 6 | return runCatching { doSomethingSync() } .getOrElse { throw Exception("doSomethingSync error.") } |
これは単純な例ですが、これまで以下のように書いている処理が多かったので、多少はシンプルにできるかも。
1 2 3 4 5 6 7 | return try { doSomethingSync() } catch (e: hogeException) { // } catch (e: fugaException) { // } |
runCatching を使った場合はこんな感じ。
1 2 3 4 5 6 7 8 | return runCatching { doSomethingSync() }.let { throw when (it) { is hogeException -> { xxxxException() } is fugaException -> { xxxxException() } } } |
まとめ
今回は実装でよく使うシンプルな例を書いてみましたが、以下の記事だともっと多くの利用例がわかりやすくまとめられています。