[Kotlin] isNotXxx()を実装する際のポイント
はじめに
こんにちは、株式会社スマートラウンドのtsukakei1012です!
弊社ではサーバーサイドの言語としてKotlinを利用しており、そこで得られた知見を今回は紹介します!(ちなみに弊社はServer-Side Kotlin Meetupの運営もしています!)
isNotXxx()
メソッドの紹介
Kotlin には、isEmpty()
などの単純な状態チェックを行うメソッドが充実しています。そして、そのなかでもおすすめしたいのがネガティブチェックのための isNotXxx()
系メソッドです。
Kotlin には isEmpty()
や isBlank()
に対応する isNotEmpty()
, isNotBlank()
が標準で用意されています。これらを使うと可読性が上がり、コードレビュー時の理解もスムーズになりますよね。
しかし、プロジェクトによっては独自の状態判定が欲しくなることもあります。例えば、ビジネスロジック上「特定の条件が揃っていれば有効、そうでなければ無効」のような判定が必要なケースです。
そこで本記事では、独自の isNotXxx()
メソッドを実装するときのコツを解説します。
isNotXxx()
メソッドを実装するのか
なぜ isNotEmpty()
を導入する前は、よく下記のような書き方をしていました:
if (!text.isEmpty()) {
// ...
}
もちろんこれでも動作には問題ありませんが、!(否定演算子)を前に付けるスタイルは一瞬考えないと「何をチェックしているか」分かりにくい場合があります。
一方、isNotEmpty()
で書くと、否定演算子を使わずに条件を肯定的に書くことができます:
if (text.isNotEmpty()) {
// ...
}
こちらの方が「この条件分岐は text
が空ではない場合に行う」ということが明確に伝わります。
isNotXxx()
実装の基本ルール
ここが記事の 本題 です。独自実装する際の鉄則として、特に以下の2つをおすすめします。
isXxx()
を反転する形( !isXxx()
)にする
(1) 元の もしすでに isXxx()
というメソッドがあるなら、独自の isNotXxx()
は 必ず isXxx()
を反転したものとするのがベストです。
たとえば、
fun SomeType.isValid(): Boolean {
// 何らかのバリデーション処理
return /* ... */
}
fun SomeType.isNotValid(): Boolean {
return !this.isValid()
}
このように「真偽値を単純に反転するだけ」にすることで、将来 isValid()
の実装が変わっても、isNotValid()
との整合性を維持しやすくなります。
もし isNotValid()
を独自に別の判定ロジックで書いてしまうと、2つのメソッドがズレるリスクが高まります。可読性と保守性の観点で、メソッドの定義はなるべく一貫性を持たせることが重要です。
コラム:Kotlinではどうなっているの?
実は isNotEmpty()
はそうなっていません
@kotlin.internal.InlineOnly
public inline fun CharSequence.isEmpty(): Boolean = length == 0
@kotlin.internal.InlineOnly
public inline fun CharSequence.isNotEmpty(): Boolean = length > 0
その一方、 isNotBlank()
は isBlank()
を反転させる形で実装されています
public fun CharSequence.isBlank(): Boolean = all { it.isWhitespace() }
@kotlin.internal.InlineOnly
public inline fun CharSequence.isNotBlank(): Boolean = !isBlank()
isNotXxxOrYyy()
などは作らない
(2) 「NOTかつ別条件」や「NOTまたは別条件」を1つのメソッド名に盛り込もうとすると、名前がどんどん複雑になりかえって分かりにくくなります。
例えば、
// 悪い例
fun SomeType.isNotValidOrSomethingElse(): Boolean
となってしまうと、「何を判定しているのか」「どの条件が否定されているのか」が一見して分からない という問題が起きがちです。
こういう場合は、「NOT」を一つの概念にだけ適用して、別の条件は別メソッドにするほうが読みやすいです。最終的に呼び出す側のコードで ||
や &&
を使うなど、メソッドの組み合わせで表現しましょう。
コラム:Kotlinではどうなっているの?
KotlinではisNullOrEmpty()
がありますが、 isNotNullOrEmpty()
はありません。
これについては過去、開発チームに要望はありましたが、上の説明と同様の理由で実装しないという結論になりました。
Until now we have been avoiding introducing these functions because of a potentially ambiguous way they can be comprehended: whether isNotNullOrEmpty means isNot(NullOrEmpty) or is(NotNull)OrEmpty
(和訳)これまで、これらの関数は解釈に潜在的な曖昧さがあるため、導入を控えてきました。すなわち、isNotNullOrEmpty が isNot(NullOrEmpty) を意味するのか、あるいは is(NotNull)OrEmpty を意味するのかという問題です。
isNotXxx()
を実装するケース
具体例:自前で ここでは、User
クラスで「利用資格があるかどうか(isEligible()
)を判定するメソッド」を例に挙げます。
data class User(
val id: Int,
val name: String,
val isActive: Boolean
)
fun User.isEligible(): Boolean {
// 例えば名前が空でない & active の場合のみ有効とする
return name.isNotBlank() && isActive
}
fun User.isNotEligible(): Boolean {
// !isEligible() で反転
return !this.isEligible()
}
こうしておくと、呼び出し元コードで
if (user.isEligible()) {
// OK の処理
} else if (user.isNotEligible()) {
// NG の処理
}
のように意図がはっきり分かれるため、可読性が高まります。
まとめ
独自の isNotXxx()
メソッドを実装するときは、
- 実装する際は元の
isXxx()
を反転するだけ(return !isXxx()
) - 複数の条件を詰め込むような
isNotXxxOrYyy
は作らない
という2つの基本ルールを守るだけで、コードの可読性と保守性を大幅に高められます。
「否定形があったほうが読みやすい場合」と「肯定形だけで事足りる場合」をよく見極めて、メソッドを整理していきましょう。
最後に宣伝
スマートラウンド テックブログでは他にDevinやExposedなどの記事があるのでご興味のある方は是非お読みください!
また、この記事を読んでスマートラウンドにご興味を持った方がいらっしゃれば、ぜひ以下の採用ページをご覧ください!
(生成AI関連でもKotlin関連でも大丈夫ですので、カジュアル面談しましょう!)
Discussion