kotlin-resultを使ったエラーハンドリング
Kotlinには検査例外が無い。もし、API呼び出しなどでのエラーにいくつか種類を定義して、エラーの種類毎の処理を網羅的に実装できているかをコンパイル時に検査したい場合はどうしたら良いか。kotlin-resultが使える。いわゆるResultモナドをkotlinで使えるようにするライブラリ。sealed classと組み合わせて使うとこれができる。
まずエラークラスを作る。sealed classとそのサブクラスで定義する。
code:ApiError.kt
package net.example
sealed class ApiError {
class DogError(
val name: String
) : ApiError()
class CatError(
val age: Int
) : ApiError()
class DukeError(
val exception: Exception
) : ApiError()
}
APIでエラーを返す例。エラークラス毎に異なるインスタンス変数を含めることができる。
code: Api.kt
package net.example
import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import net.example.ApiError
class Api {
fun animal(): Result<Unit, ApiError> {
try {
// do something
if (dog) {
return Err(ApiError.DogError(name = "wanwano"))
} else if (cat) {
return Err(ApiError.CatError(age = 222))
} else {
return Ok(Unit)
}
} catch(e: Exception) {
// 例外を包むエラークラスを自分で定義すればErrに包める。
return Err(ApiError.DukeError(exception = e))
}
}
}
APIが Err を返した場合は mapBoth の failure が実行される。 ApiError はsealed classなので、 when で ApiError のサブクラスについて網羅的に分岐を書かないとコンパイル時に警告が出る。これで安心して眠れる。なお、一部のサブクラスについてのみ分岐処理を書きたい場合は、 else を書けば良い。
code:client.kt
package net.example
import com.github.michaelbull.result.mapBoth
import net.example.Api
import net.example.ApiError
api.animal().mapBoth(
success = {
// do something
},
failure = { error ->
when(error) {
is ApiError.DogError -> {
error.name
// do something
}
is ApiError.CatError -> {
error.age
// do something
}
is ApiError.DukeError -> {
error.exception
// do something
}
}
}
)