こういうコードを書いた覚えはないだろうか
例えば商品の代金を計算したいとする。その時の計算処理は一般化し、以下のように記述することができる。
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 |
バックエンドでは入力値をバリデーションする機会が必ずある。その場合必然的にif
を多用する必要があるが、それをrequire
に置き換えることができる。これを知ってから自分が書くコードの可読性が一気に上がったと思う。是非活用したいきたい。
*1:https://detekt.dev/docs/rules/complexity/##cognitivecomplexmethod
*2:プログラミングの概念っぽく言えば、"表明"がはっきりした。 https://ja.wikipedia.org/wiki/%E8%A1%A8%E6%98%8E