はじめに
Kotlinで、関数の引数やデータの状態をチェックして不正であれば例外をスローしたい場面がありますよね。
今まで、何も考えずif文でチェックしてExceptionをthrowする書き方をしていました。
fun hello(user: String) {
if (user.isEmpty()) {
throw IllegalArgumentException("user must not be blank")
}
println("Hello, $user!")
}
// 空文字を渡すとException
hello("") // IllegalArgumentException: user must not be blank
しかし、Kotlinでは標準ライブラリであるPreconditions.ktでこういったチェック処理をよりスマートに書ける関数群が提供されています。
この記事では、Preconditionsの使い方や使い分け、メリットなどをまとめていきます。
require
定義
Exceptionのメッセージをデフォルトにする1.と、
カスタムメッセージを表示する2.のバリエーションがあります。(後述するcheck
も同様です)
require(value: Boolean): Unit
require(value: Boolean, lazyMessage: () -> Any): Unit
使い方
requireは、valueがfalseの場合IllegalArgumentException
をスローします。
冒頭のサンプルコードをrequireを使って書き直すと以下のようになります。
Kotlinらしくスッキリしましたね。
fun hello(user: String) {
require(user.isNotEmpty()) { "user must not be blank" }
println("Hello, $user!")
}
// 空文字を渡すとException
hello("") // IllegalArgumentException: user must not be blank
スローするのがIllegalArgumentException
であることから、サンプルコードのように引数を特定の条件でバリデーションするために使うことが想定されていると思います。
requireNotNull
定義
<T : Any> requireNotNull(value: T?): T
<T : Any> requireNotNull(value: T?, lazyMessage: () -> Any): T
使い方
requireNotNollは、valueがnullかどうかを検証しnullの場合IllegalArgumentException
をスローします。
require
とのもう一つの違いとして、requireNotNull
はvalueがtrueでExceptionが発生しない場合はnullableでない型として元の値を戻り値で返します。
そのためrequireNotNull
以降のロジックはnullでないことが保証された前提で書くことができます。
サンプルコードは以下です。(公式をアレンジ)
fun printRequiredParam(params: Map<String, String?>) {
val required: String = requireNotNull(params["required"]) { "Required value must be non-null" } // returns a non-null value
println(required)
// ...
}
// 引数paramsに"required"に対応するアイテムが無く、nullを返す場合Exception
printRequiredParam(params) // IllegalArgumentException: Required value must be non-null
なるほど、引数に対するデータの存在チェックなどでnullが返る可能性のある時はrequire
よりもrequireNotNull
が適していそうです。
check
定義
check(value: Boolean): Unit
check(value: Boolean, lazyMessage: () -> Any): Unit
使い方
checkは、valueがfalseの場合IllegalStateException
をスローします。
サンプルコードは以下です。(公式をアレンジ)
// someStateは他の処理によって様々な値がセットされる
var someState: String = ""
fun getStateValue(): String {
check(someState.isNotEmpty()) { "State must be non-empty" }
// ...
return someState
}
// someStateがブランクだとException
println(getStateValue()) // IllegalStateException: State must be non-empty
サンプルコードと、スローするのがIllegalStateException
であることから、引数のチェックではなく何らかの状態のバリデーションに使われることが想定されていると思います。
checkNotNull
定義
<T : Any> checkNotNull(value: T?): T
<T : Any> checkNotNull(value: T?, lazyMessage: () -> Any): T
使い方
checkNotNullは、check
のNullチェックバージョンです。
nullableでない戻り値を返すのも、require
とrequireNotNull
の違いと同じです。
サンプルコードは以下です。(公式をアレンジ)
// someStateは他の処理によって様々な値がセットされる
var someState: String? = null
fun getStateValue(): String {
val state = checkNotNull(someState) { "State must be set beforehand" }
// ...
return state
}
// someStateがnullだとException
println(getStateValue()) // IllegalStateException: State must be set beforehand
error
定義
error(message: Any): Nothing
使い方
errorは、これ自体では何の条件もチェックせず与えられたメッセージでIllegalStateException
をスローします。
サンプルコードは以下です。
fun classify(number: Number) {
when (number) {
1 -> println("1")
2 -> println("2")
else -> error("number is out of range")
}
}
// whenで用意されていない条件を渡すとException
classify(3) // IllegalStateException: number is out of range
when式でelseをエラーにしたい時など、require
やcheck
単体で扱いにくいケースに役立ちそうです。
個人的に感じたPreconditionsを使うメリット
シンプルで読みやすい
冒頭のifとthrowsを使ったサンプルコードのような書き方に比べて、Preconditionsを使った書き方は条件分岐が関数に内包されているためシンプルで読みやすいです。
コードの意図が明確になる
読みやすいもう一つの理由は、コードの意図がより明確になるためです。ifとthrowsでやっていることを読むよりも、require
やcheck
という関数名とそのチェック条件を読むほうが「引数をチェックしている」「状態をチェックしている」という書き手の意図をより明示的に理解できるなと思いました。
また、これらの関数を適切な場面で使うことで、自動的にExceptionの種類も適切なものに決まるところもポイントが高いです。
ifとthrowsに比べてより宣言的と言い換えても良いかもしれません。
まとめ
KotlinのPreconditionsの使い方をまとめてみました。
標準ライブラリを使いこなすことは「KotlinをKotlinらしく書く」ことに繋がると思います。
この記事が少しでも役に立てば嬉しいです。