kotlin-functions について
Kotlin開発者向けに、関数の所属先と、メンバー、トップレベル関数、ファクトリー、協力オブジェクト、拡張関数のどれを選ぶかを判断するスキルです。流暢な構文より、意味上の所有者と互換性を重視します。
主な機能
- 01意味上の所有者の特定
- 02誤解を招く拡張レシーバーの除外
- 03適切な関数形式の選択
- 04実装の移動と呼び出し元の更新
- 05可視性と互換性の確認
- 06変更のコンパイルとテスト
活用シーン
- 01Kotlin拡張関数のレビュー
- 02配置が不適切な生成・解析ロジックのリファクタリング
- 03メンバー関数と協力オブジェクトの選択
- 04関数API変更の互換性確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ chrisbanes/ skills --skill kotlin-functionsインストール後、Claude Code で/kotlin-functionsを入力して読み込まれたか確認します。
詳細
Kotlin 関数の所有者
基本原則
関数は、意味上の所有者として最も適切で小さな単位に配置します。拡張構文が変えるのは呼び出し方であり、所有者ではありません。
プリミティブ型、共通型、ライブラリ所有型への拡張関数は、原則として避けます。誤った所有者を示し、ドメインを汚染し、補完やインポートを煩雑にし、衝突を招くためです。
手順
順番に適用します。
1. 意味上の所有者を特定する
操作と、それを所有する概念を特定します。所有者が明確でない場合は、構文を選ぶ前に作業を中断します。
2. 誤解を招くレシーバーを早い段階で除外する
String、プリミティブ型、コレクション、Flow、フレームワーク、またはサードパーティのレシーバーには、次のすべてを満たすことを求めます。
- 範囲が狭い
private/internalで、一貫性のあるスコープである。 - レシーバーのすべての値に対して有効である。
- ポリシー、状態、I/O、依存関係を持たない。
- レシーバー構文にすることで、明確さが実質的に向上する。
- より適切なプロジェクト所有の配置先がない。
1つでも満たさない場合、そのレシーバーへの拡張関数は禁止です。ステップ3で非拡張関数の形式を選びます。private fun <T> MutableList<T>.swap(...) は、リスト本来の操作であり、ポリシーに依存せず、アルゴリズムの局所的な処理であるため、条件を満たす場合があります。
3. 関数の形式を選ぶ
| 意味 | 優先する形式 |
|---|---|
| プロジェクト所有型に固有の振る舞い | メンバー関数 |
| 型をまたぐ、状態を持たない操作 | トップレベル関数 |
| 生成または解析 | 対象型のファクトリー、または名前付きトップレベル関数 |
| ポリシー、状態、I/O、時計、ロケール、または依存関係を保持する | 注入されるサービス/コラボレーター |
| 型本来の操作で、レシーバー構文のほうが明確かつステップ2の条件をすべて満たす | 拡張関数 |
振る舞いがポリシー、状態、I/O、時計/ロケール、または依存関係を保持する場合に限り、サービス/コラボレーターを使用します。それ以外では、状態を持たない関数に明示的なパラメーターを指定します。
4. 振る舞いと呼び出し元を移動する
実装を移動してから、呼び出し、インポート、関数参照を更新します。明示的な破壊的変更を伴うリリースでない限り、公開エントリーポイントは維持するか、非推奨にします。非公開の移行支援を追加するのは、具体的な利用箇所がある場合に限ります。
// Before: String falsely owns UserId construction.
fun String.toUserId(): UserId = UserId(this)
// After: UserId owns construction.
@JvmInline
value class UserId private constructor(val value: String) {
companion object {
fun parse(raw: String): UserId = UserId(raw)
}
}
val id = UserId.parse(raw)
5. 確認して完了する
どの形式でも、可視性、インポート、衝突、互換性を確認します。拡張関数の場合は、null 許容レシーバー、ジェネリクス、将来のメンバーとの優先順位も確認します。コンパイルとテストを行い、失敗した場合は API の範囲を狭めるか、ステップ1に戻ります。
よくある正当化
| 「でも……」 | 反論 |
|---|---|
| 流れるような構文にできる | 読みやすさだけで所有者が決まるわけではありません。 |
| Kotlin では拡張関数を使う | 慣用的であっても、意味上の適切さは必要です。 |
private/internal である | スコープが有効なのは、すべての条件を満たす場合だけです。 |
| ユーティリティオブジェクトはもっと悪い | トップレベル関数か、対象型のファクトリーを使います。 |
| デフォルトのポリシーは明らかである | タイムゾーンやロケールのデフォルト値もポリシーです。明示したままにします。 |
| すでに PR に含まれている | 既存のコードが所有者の適切さを証明するわけではありません。 |
危険信号
String、数値、コレクション、Flow、またはベンダー型にドメイン上の意味を持たせている。- 拡張関数に時計、ロケール、I/O、ポリシー、または依存関係を隠している。
よくある間違い
| 間違い | 修正 |
|---|---|
Long.toDisplayDate() | タイムゾーン/ロケールのポリシーはフォーマッターが担います。 |
| 拡張関数が解析を隠している | Type.parse(raw) または名前付きパーサーを使います。 |
| ライブラリ型に対する公開拡張関数 | ステップ1〜3に従って再分類します。 |