Kotlinでrequireを使って引数とかのAssertionをいい感じに表現する
こういうコードを書いた覚えはないだろうか
例えば商品の代金を計算したいとします。その時の計算処理は一般化し、以下のように記述することができます。
fun calcPurchasePrice(price: Int, quantity: Int): Int {
if (price < 0 || quantity < 0) {
throw IllegalArgumentException("Price and quantity must be positive")
}
return price * quantity
}
ですが、このコードは少し回りくどいです。なぜかというと、if
に続く実装を見なければ、「この判定が何者なのか?」がわからないからです。
そもそもdetekt
のCognitiveComplexMethod
[1]にも見られるように、Kotlinの思想としてif
を多用すること自体がアンチパターンに入っています。
Kotlinを使ってこのコードを表現するのであればもう少し宣言的に書けるのですが、このコードをよりスマートにリファクタするにはどうすればいいでしょうか?
そのコード、require で書けるよ
早速答えになってしまいますが、上記のような場合にはrequire
を使うべきです。
require
を使うことで以下のように書き換えることができます。
fun calcPurchasePrice(price: Int, quantity: Int): Int {
require(price >= 0 && quantity >= 0){
"Price and quantity must be positive"
}
return price * quantity
}
require
は条件がfalse
になった場合IllegalArgumentException
を吐いてくれます。
このコードの良いところはif
がなくなったこともそうですが、もともとのif
で判定しようとしていたことが一目でわかるようになったことです。[2]
リファクタするまではif
の実装を見るまで「引数の判定なのか、それともまた別のなにかの処理なのか?」というのはif
の2文字を見ただけではわかりません。
しかし、require
はそれだけで引数の判定をしているんだなということを雄弁に語ってくれます。
余談ですが、非NULL判定をしなければならない場合はrequireNotNull
を使うことができます。
引数以外をAssertionするときは?
ここまでの話で、引数に関してはrequire
を使ったAssertionを噛ませることでIllegalArgumentException
を吐いてくれるようになりました。
では引数以外の値をAssertionする場合はどうすればいいでしょうか?
例えば以下のように、"環境変数でセール値引きを適用するかをコントロールしている"というシチュエーションを考えます。
fun calcPurchaseSalePrice(price: Double, quantity: Int): Double {
require(price >= 0 && quantity >= 0){
"Price and quantity must be positive"
}
val saleFlg = System.getenv()["SALE_FLG"]
//? saleFlgがnullかどうかの判定を書かないとダメ?
return price * quantity * 0.5
}
またもいきなり答えですが、ここではcheckNotNull
という関数を使うことができます。
fun calcPurchaseSalePrice(price: Double, quantity: Int): Double {
require(price >= 0 && quantity >= 0){
"Price and quantity must be positive"
}
val saleFlg = System.getenv()["SALE_FLG"]
checkNotNull(saleFlg){
"Not found SALE_FLG"
}
return price * quantity * 0.5
}
checkNotNull
のAssertionに引っかかった場合は、IllegalStateException
を吐いてくれます。
おわり
Kotlinの4つの哲学の一つ・簡潔性を語る上で象徴的なrequire
, check
を紹介しました。
表にまとめるとこんな感じ。
チェック対象 | 使う関数 | Assertionエラー時の例外 |
---|---|---|
引数 | require (requireNotNull) | IllegalArgumentException |
引数以外 | check (checkNotNull) | IllegalStateException |
これを知ってから自分が書くコードの可読性が一気に上がったと思います。
是非活用していきましょう!
-
https://detekt.dev/docs/rules/complexity/#cognitivecomplexmethod ↩︎
-
プログラミングの概念っぽく言えば、"表明"がはっきりした。 https://ja.wikipedia.org/wiki/表明 ↩︎
Discussion