kotlin-types-value-class について
Kotlin の値クラス、データクラス、型エイリアス、プリミティブの選択を支援するレビュー用スキルです。ドメインモデリングに加え、シリアライズ、Java 相互運用、Compose の安定性、ボクシングコストを確認します。
主な機能
- 01単一フィールド型の選択指針
- 02等価性とシリアライズ変更の確認
- 03Java 相互運用とボクシングの考慮
- 04Compose 安定性のレビュー手順
- 05リファクタリングと性能の確認
活用シーン
- 01プリミティブ中心の Kotlin API のレビュー
- 02単一プロパティのデータクラスの評価
- 03Compose 安定性レポートの調査
- 04API 境界での値クラス変更の評価
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ chrisbanes/ skills --skill kotlin-types-value-classインストール後、Claude Code で/kotlin-types-value-classを入力して読み込まれたか確認します。
詳細
Kotlin の value class と data class
基本原則
ドメイン上の意味を持つ単一フィールドの型には、@JvmInline value class を優先します。複数のフィールドを集約する場合は、データクラスを使用します。
レビュー手順
- 単一プロパティのラッパー、プリミティブ型を多用する API、UI 状態の
@Immutableラッパーを探します。 - 単一の値が実際にドメイン上の区別を表すか判断します。そうでない場合は、プリミティブ型のままにするか、typealias を使用します。
- 型の置き換えによって、等価性、シリアライズ、Java との相互運用性、またはホットパスでのボックス化が変わるか確認します。
- ドメイン上の意味が明確で、契約の変更を許容できる場合にのみ変換します。
- 影響を受けるコンパイルとテストを再実行します。Compose のパフォーマンス改善の場合は、コンパイラーのレポートまたは再コンポジションの根拠を再確認します。
判断フロー
| 状況 | 優先するもの |
|---|---|
単一フィールド + ドメイン上の意味がある(UserId、EmailAddress、Percentage) | @JvmInline value class |
| 単一フィールド + ドメイン上の意味がない(単にまとめるだけ) | Type alias またはプリミティブ型のままにする |
| 複数フィールド | データクラス |
ラップする値を超える独自の equals/hashCode が必要 | データクラス(value class は基底の型に処理を委譲します) |
| ジェネリック型引数として使用する、または実証済みのホットパスで nullable である | データクラスまたはプリミティブ型 |
// GOOD: domain-meaningful single field
@JvmInline value class UserId(val value: String)
@JvmInline value class EmailAddress(val value: String)
@JvmInline value class Percentage(val value: Float)
// BAD: data class wrapping a single domain field
data class UserId(val value: String)
// BAD: value class with no domain meaning
@JvmInline value class Wrapper(val value: String) // just use the String, or a type alias
// BAD: value class needing custom equality
@JvmInline value class CaseInsensitiveString(val value: String)
// value class equals delegates to String equals, which IS case-sensitive
// Use a data class if you need different equality semantics
Compose の安定性に関する手順
Compose のレポートで単一フィールドのラッパーが指摘された場合:
- 基底の型が安定していることを確認します(
String、プリミティブ型、または別の安定した型)。 - 型を区別することだけを目的とするラッパーには、
@Immutableではなく value class を優先します。 - レポートを抑制するためだけに、公開されているシリアライズ/API の契約を変更しないでください。
// Before: primitive value can be mixed up with other strings
data class UiState(val userId: String)
// After: domain type is stable at the Compose boundary
@JvmInline value class UserId(val value: String)
data class UiState(val userId: UserId)
リファクタリング時の確認事項
既存のラッパーを置き換える前に、呼び出し元から見える契約を確認します。
| 確認事項 | 対応 |
|---|---|
| JSON/API 形式が重要 | シリアライズを確認します。@Serializable data class A(val value: String) はオブジェクトとしてエンコードされますが、value class はラップされた値としてエンコードされます。 |
| 独自の等価性またはハッシュ化が必要 | データクラスを維持します。value class の等価性は、ラップされた値に従います。 |
呼び出し元が copy() または分解宣言を使用 | データクラスを維持するか、呼び出し元を意図的に更新します。value class には、データクラスの便利な機能はありません。 |
| Java またはリフレクションを多用するフレームワークとの境界 | 相互運用性を確認します。Java の呼び出し元には基底の型が見えます。ジェネリック型や Any で使用するとボックス化されます。 |
| nullable/ジェネリック/可変長引数のホットパス | 変換前に測定します。これらの使用ではボックス化されます。 |
コンストラクター本体、lateinit、委譲プロパティ、バッキングフィールド | データクラスを維持するか、設計を見直します。value class に格納できるのはコンストラクターの値のみです。 |
根拠を確認してから複数の値をパッキングする
プロファイリングでホットパスの割り当てコストが示されない限り、明確な複数フィールドのデータクラスをビットパッキングに置き換えないでください。必要な場合は、Compose の androidx.compose.ui.util に packFloats、packInts、および対応する unpack* 関数があります。
@JvmInline value class Offset(val packedValue: Long)
fun Offset(x: Float, y: Float): Offset = Offset(packFloats(x, y))
val Offset.x: Float get() = unpackFloat1(packedValue)
val Offset.y: Float get() = unpackFloat2(packedValue)
よくある間違い
| 間違い | 修正 |
|---|---|
| 単一のドメインフィールドをラップするデータクラス | @JvmInline value class に置き換える |
| ドメイン上の意味がない value class(単なるラッパー) | type alias またはプリミティブ型を直接使用する |
| 独自の等価性が必要な value class | 代わりにデータクラスを使用する |
| ホットパスでジェネリック型引数として使われる value class | ボックス化のコストを測定する。影響がある場合はプリミティブ型/データクラスを維持する |
value class にできる型への @Immutable アノテーション | 基底の型が安定している場合は value class に置き換える |
@JvmInline アノテーションを忘れる | 単一フィールドのクラスでは、必ず value class と @JvmInline を併用する |
レビュー時の注意事項
- プロパティがちょうど 1 つのデータクラス
- 異なる値を相互に置き換えられないようにすべき場所で使われている
String、Long、またはInt(例:fun transfer(from: String, to: String, amount: Long)) - 単一フィールドのラッパーに付けられた
@Immutableアノテーション - value class のセマンティクスが必要なドメイン上の区別に type alias を使用している(type alias は型消去され、実行時の保護がありません)
適用しない場合
- 複数のフィールドが必要な型 → データクラス
- カスタムの
equals/hashCodeが必要な型 → データクラス - パフォーマンスが重要なコードで、型を nullable またはジェネリック型として頻繁に使用する場合 → まずオートボクシングのコストを測定してください
- プロジェクトで型安全性の区別が不要な場合 → 型エイリアスまたはプリミティブ型で十分です
- 置き換えによって JSON、Java、リフレクション、またはフレームワークの動作が暗黙的に変わる場合
関連項目
compose-stability-diagnostics— Compose の不安定なパラメーターを診断します。値クラスは解決策の一つです
コメント
Markdown 対応