mcpField
K
kotlin-types-value-class

kotlin-types-value-class

github.com/chrisbanes开发1.1k844 次安装1 天前更新
代码仓库

关于 kotlin-types-value-class

用于审查 Kotlin 值类、数据类、类型别名和基本类型选择的技能。它兼顾领域建模,并检查序列化、Java 互操作、Compose 稳定性与装箱成本。

主要功能

  1. 01单字段类型的选择指导
  2. 02检查相等性与序列化变化
  3. 03Java 互操作与装箱考量
  4. 04Compose 稳定性审查步骤
  5. 05重构与性能检查

适用场景

  1. 01审查基本类型密集的 Kotlin API
  2. 02评估仅含一个属性的数据类
  3. 03排查 Compose 稳定性报告
  4. 04评估 API 边界上的值类变更

安装

复制下面的命令,在终端中运行。

$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。Data class 用于聚合多个字段。

审查流程

  1. 查找单属性包装类型、基本类型繁多的 API,以及 UI 状态中的 @Immutable 包装类型。
  2. 判断单个值是否代表真正的领域区别。如果不是,则保留基本类型或使用类型别名。
  3. 检查替换类型是否会改变相等性、序列化、Java 互操作性或热路径装箱行为。
  4. 只有在领域含义明确且契约变更可接受时才进行转换。
  5. 重新运行受影响的编译器检查和测试;对于 Compose 性能相关工作,重新检查编译器报告或重组证据。

决策流程

情况优先选择
单字段 + 有领域含义(UserId、EmailAddress、Percentage)@JvmInline value class
单字段 + 没有领域含义(仅用于分组)类型别名或保留基本类型
多个字段Data class
需要自定义 equals/hashCode,且行为超出包装值本身Data class(value class 会委托给底层类型)
在已证实的热路径中用作泛型类型实参或可空类型Data class 或基本类型
// 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 报告指出某个单字段包装类型时:

  1. 确认底层类型是稳定的(String、基本类型或其他稳定类型)。
  2. 对于唯一作用是区分类型的包装类型,优先使用 value class,而不是添加 @Immutable。
  3. 不要仅为消除报告而更改公开的序列化/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 会编码为包装值。
需要自定义相等性或哈希保留 data class。Value class 的相等性遵循包装值。
调用方使用 copy() 或解构保留 data class,或有计划地更新调用方。Value class 不提供 data class 的便捷功能。
Java 或大量使用反射的框架边界验证互操作性。Java 调用方看到的是底层类型;泛型/Any 用法会装箱。
可空类型/泛型/可变参数热路径转换前先测量;这些用法会装箱。
构造函数体、lateinit、委托属性、幕后字段保留 data class 或重新设计;value class 仅存储构造函数的值。

有证据后再打包多个值

除非性能分析显示热路径存在分配开销,否则不要将清晰的多字段 data 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)

常见错误

错误修正方法
用 data class 包装单个领域字段替换为 @JvmInline value class
value class 没有领域含义(仅作为包装类型)使用类型别名或直接使用基本类型
value class 需要自定义相等性改用 data class
热路径中将 value class 用作泛型类型实参测量装箱开销;如果影响明显,则保留基本类型/data class
对可以使用 value class 的类型添加 @Immutable 注解当底层类型稳定时,替换为 value class
忘记 @JvmInline 注解单字段类始终应将 value class 与 @JvmInline 配对使用

审查期间的危险信号

  • 只有一个属性的 data class
  • 在不同值不可互换的场景中使用 String、Long 或 Int(例如,fun transfer(from: String, to: String, amount: Long))
  • 单字段包装类型上有 @Immutable 注解
  • 使用类型别名来区分领域类型,而实际需要 value class 语义(类型别名会被擦除,不提供运行时保护)

不适用的情况

  • 类型需要多个字段 → 使用 data class
  • 类型需要自定义 equals/hashCode → 使用 data class
  • 在性能关键代码中,类型经常作为可空类型或泛型使用 → 先测量自动装箱的开销
  • 项目不需要区分类型安全 → 类型别名或基本类型就足够了
  • 替换会悄然改变 JSON、Java、反射或框架行为

相关内容

评论

支持 Markdown