Kotlinの標準APIを知る【Result編】
Kotlinの標準API Result
良くCoroutines
と一緒に使う記事を見かけます。
が、今回は普通にSever Side Kotlinの中でどんな風に使えそうかを検討してみました。
標準APIのResultはどのようなものか?
処理の成功と失敗(例外)の二値を表現する用途で用いることができます。
public value class Result<out T> @PublishedApi internal constructor(
@PublishedApi
internal val value: Any?
)
上記のようなクラス定義になっており
Result.success(value:T)
で処理に成功した場合に処理で得られた値をvalueに保持
Result.failure(exception: Throwable)
で処理に失敗した場合に送出された例外インスタンスを内包したResult.Failure
という内部クラスをvalueに保持
という形で処理の成功状態 or 失敗状態を表現させます
どう活用していくか
オーソドックスな使い方
runCatching
という関数に例外を送出する可能性のある処理を渡すことで処理に成功した場合はsucessで、失敗した場合はfailureでResultを返してくれます。
val result: Result<Type> = runCatching { 例外が送出される可能性のある処理 }
result
.onSuccess { println("success") }
.onFailure { println("error") }
標準APIのonSuccess
,onFailure
関数で成功だった場合、失敗だった場合の処理を簡潔に書けます(※try-catchやif文による結果の判定等、手続き的な記述を取らなくて済むようになります。)
Exceptionのラッピング
フレームワークや呼び出し先の例外を捕捉して、情報を追加や変換して送出し直したいみたいなケースがあるかと思います。
try {
// 抽象的すぎる例外の送出
} catch (抽象的すぎる例外 e) {
// throw わかりやすく具体化した例外(e)
}
普通に書くと上記みたいな感じになると思います。
これをResultで書きなおしてみると
val result: Result<Type> = runCatching { // 抽象的すぎる例外の送出 }
result.getOrElse { e -> throw わかりやすく具体化した例外(e) }
のようにネストなしで表現できます。
メソッドチェーン(成功したら次の処理をする)
通常のメソッドチェーンはbuilder的なもの等、処理が途中で失敗した場合のことを考慮しないケースで使われることが大半だと思います。ですが、稀に連続する処理で前の処理結果を受け渡しながら成功したら次の処理をするというケースにも出くわします。
このようなケースで愚直にやると
try {
val firstResult = 例外を送出する可能性のある処理_1
val secondResult = 例外を送出する可能性のある処理_2(firstResult)
return val thirdResult = 例外を送出する可能性のある処理_3(secondResult)
} catch (例外 e) {
return エラー時のデフォルト戻り値
}
やや手続き的でちょっと嫌な感じがしてきます。そこでmapCatching
という関数を使った実装で書いてみます。
val result = runCatching { 例外を送出する可能性のある処理_1 }
.mapCatching { 例外を送出する可能性のある処理_2(it) }
.mapCatching { 例外を送出する可能性のある処理_3(it) }
.getOrDefault(エラー時のデフォルト戻り値)
こんな感じで例外を送出する可能性のある処理をmapCatching
でラップすることで処理の結果をResultとしてチェーンしていくことができます。
まとめ
これまで標準的なJavaAPIでは手続き的な記載で冗長になってしまうような部分をResult型での結果処理という設計に切り替えると、宣言的にストレートに書ける可能性があることがわかりました。
みなさんの活用事例も是非コメント等で教えていただけるととても嬉しいです。
ステップアップ
標準APIの現在の実装は例外によるエラー表現しかできませんが、例外を用いないResultの実装を提供してくれているライブラリもあります。
Resultで扱いたいからなんでも処理の失敗を例外で表現するというのは誤った設計に陥ってしまうので、こちらも試してみようと思います。(また、どこかで記事にします。)
Discussion