关于 kotlin
面向 Kotlin 开发的编码技能,关注类型化状态模型、明确的异步边界和符合惯例的 Android 集成,适用于 Android 代码及 Kotlin Nitro Module。
主要功能
- 01类型安全的状态建模
- 02明确异步与线程使用方式
- 03设计非阻塞属性
- 04组织 Android 辅助代码与文件
- 05Nitro Module 实现指导
适用场景
- 01为 Kotlin API 建模状态
- 02选择协程与调度器边界
- 03组织 Android 实现文件
- 04实现 Kotlin Nitro Module
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ margelo/ react-native-skills --skill kotlin安装后在 Claude Code 中输入/kotlin确认已加载。
附带文件agents/openai.yaml
详细介绍
Kotlin
如果 Kotlin 代码需要强类型建模、明确的异步边界和惯用的 Android 集成,请使用此技能。如果代码属于 Nitro Module,请将其与 build-nitro-modules 配合使用,以处理生成的规范、Promise 映射、注解和 HybridObject 约束。
工作流
- 编辑前,阅读本地 Kotlin 代码、生成的抽象类和公开 API 的形态。
- 实现行为前,先用 Kotlin 类型对有效状态建模。
- 选择工作应当同步执行、基于协程执行,还是显式调度。
- 确保属性开销低且不会阻塞。
- 在 API 中明确体现线程切换、Android 服务访问、I/O 和可能失败的操作。
类型安全的 API 设计
- 使用密封接口/类、普通接口或具体类来表示状态变体。不要用一个包含许多可空字段的数据类来编码变体。
- 在字段有效的变体中,将相关值设为非空。如果
barcode和barcodeType必须同时存在,就将它们放在ScannedBarcode上。 - 对同一状态中的真实缺失使用可空类型,而不要用它表示“这个对象可能属于另一种类型”。
- 优先使用编译时类型收窄,而不是让调用方检查空值。反复检查相关字段是否为空,通常意味着模型过于宽松。
// Avoid: related fields are nullable and the valid combinations are implicit.
data class ScannedData(
val position: Point,
val text: String?,
val barcode: String?,
val barcodeType: BarcodeType?,
val face: Rect?,
)
// Prefer: each variant exposes only the fields that are valid for it.
sealed interface ScannedData {
val position: Point
}
data class ScannedText(
override val position: Point,
val text: String,
) : ScannedData
data class ScannedBarcode(
override val position: Point,
val barcode: String,
val barcodeType: BarcodeType,
) : ScannedData
data class ScannedFace(
override val position: Point,
val face: Rect,
) : ScannedData
异步与线程
- 对于自然会挂起的 API,以及已有协程支持的 Android I/O,使用协程。
- 快速、确定性的本地工作保持同步。对于简单的值构造、缓存元数据或纯转换,不要引入协程、调度器或 Promise 处理逻辑。
- 对于不应在调用方线程运行的 CPU 密集型同步工作,使用显式拥有的调度器/作用域或 Nitro
Promise.parallel。 - 除非 Android API 有要求,例如视图/UI 修改或生命周期 API,否则避免使用主调度器。保持主线程代码块简短,并将解析、转换、I/O、会话协商和 CPU 工作移至自有调度器或异步 API。
- 不要在库代码、属性 getter、setter 或面向 JS 的入口中使用
runBlocking。 - 不要通过属性隐藏线程切换、服务查找、阻塞 I/O、权限流程或可能失败的原生操作。
- 如果某个值由特定的 Android 回调/线程发出,优先使用监听器、Flow 或事件 API。在 Nitro 规范中,公开监听器或
Promise<T>方法。 - 将反复使用
launch、withContext、调度器、处理器或执行器进行切换视为架构异味。组件应拥有其所使用的协程作用域/调度器/生命周期,或在公开的异步边界或原生回调边界处一次性切换到该所有者。 - 如果某个工作流在多个嵌套位置于主线程、IO、Default、原生和 JS/Nitro 上下文之间来回切换,请停下来重新设计对象/生命周期/API。过多的切换会掩盖延迟、增加推理执行顺序的难度,并带来未来的性能问题。
- 不要通过
delay、Thread.sleep、Handler.postDelayed、定时器、额外的调度器切换或重复调用同一方法来修复竞态或就绪状态错误。应改为修复所有者调度器/生命周期、状态机、回调/事件、Flow 或异步 API 边界。仅在硬件、OS 服务、远程服务或网络等外部因素存在不确定性时重试,并确保重试次数有限、可取消且具备幂等性。 - 让可变共享状态由一个协程作用域、调度器、锁或 Android 组件生命周期负责。避免混用多种所有权模型,除非边界明确。
- 将
Mutex、synchronized和ReentrantLock视为最后手段,而非默认的监听器或回调处理方式。添加其中任何一种之前,先明确具体的共享可变值、并发调用方,以及为何单一所有者调度器/生命周期、通道/Flow 或不可变快照不足以解决问题。 - 持有锁时,绝不要调用回调、调用 JS/Nitro 或调用未知的用户代码。如有需要,在锁内快照监听器,随后解锁再调用它们。在事件正在发送时移除的监听器,仍可能收到当前事件。
属性
- 使用
val和var表示开销低的内存状态或派生值。 - 对于副作用、I/O、Android 服务调用、权限检查、内存分配、阻塞工作或可能失败的操作,使用方法。
- 如果某个属性需要等待另一个线程,应将其重新设计为异步方法或事件。
// Avoid: blocking work hidden in a getter.
val status: SessionStatus
get() = runBlocking { session.status() }
// Prefer: make the async boundary explicit.
fun getStatus(): Promise<SessionStatus> {
return Promise.async {
session.status()
}
}
Kotlin 风格
- 默认将类声明为
final。Kotlin 类除非标记为open,否则本来就是final;除非有意支持继承,否则保持默认状态。 - 简单值优先使用
data class;拥有资源或管理生命周期时使用普通类。 - 封闭的结果类型优先使用
sealed interface或sealed class。 - 对无效输入和无效状态使用
require、check或特定异常。不要对用户可触发的失败静默地什么也不做。 - 明确处理 Java 互操作。在合适的情况下,先将平台类型转换为 Kotlin 类型,再通过公共 API 暴露。
- 除非处于已通过先前检查、因而不变量显而易见的狭窄边界,否则避免使用
!!。优先使用提前返回、requireNotNull或带类型的状态。 - 在多行控制流中优先使用显式
return语句。不要只为让表达式更简洁,就把return提到多行try/catch、if、when或 lambda 外面;应使用try { return value } catch (...) { return fallback }。 - 在多行 lambda 中,使用带标签的返回语句(例如
return@map value)返回结果。只有像items.map { it.toString() }这样的真正单表达式 lambda 才省略标签;不要写成items.map { return@map it.toString() }。 - 对明确的单表达式 lambda,优先使用内联简写:
formats.map { it.toMLKitFormat() },而不是多行的map { format -> format.toMLKitFormat() }。如果外层或嵌套 lambda 已经使用it,就不要再使用it;在嵌套 lambda 中或需要清晰表达时,应为参数命名。 - 将文件名视为作用域约定。
HybridDataScanner.kt应实现HybridDataScanner;不应同时包含 Android 辅助代码、几何转换、监听器或扩展工具。 - 每个文件只保留一个顶层实现类型。不要在主类型下方放置其他类、接口、枚举、选项适配器、协调器、委托或辅助类型。如果某个辅助项值得定义为类型,就应为它单独创建文件。
- 每个文件只保留一个专注的扩展/转换。不要因为每个辅助项都是扩展,就创建一个包罗万象的扩展文件。应将转换拆分到具名文件中,例如
Barcode+toScannedCode.kt、TargetBarcodeFormat+toMLKitFormat.kt和BarcodeFormat+fromMLKitBarcodeFormat.kt,并在适当情况下使用internal可见性。 - 绝不要在
Hybrid*实现文件或其他主要实现文件中声明扩展fun、val或var,即使它们是私有的、很短,或只在该文件中使用。将每个扩展放入单独的具名Type+operation.kt扩展/转换文件,以保持代码拆分、可维护性和未来差异的清晰。 - 不要仅仅因为 Kotlin 允许,就在主类下方放置私有顶层扩展属性/函数。伴生对象扩展、枚举/平台映射、条码格式转换以及 ML Kit/CameraX 适配器都应放在各自具名文件中。
- 只有在私有顶层辅助项是简短的非扩展函数/属性,且仅用于支持该文件的主类型时,才在同一文件中使用它们。
- 将 Android 预检和平台样板代码从
Hybrid*工厂及实现方法中提取出来。权限检查、清单中的功能/权限验证、PackageManager能力检查、服务可用性检查及类似的准备工作应放在专注的辅助文件中;HybridObject调用处应保持为一两行。 - 不要为这些辅助项创建宽泛的
Utils.kt文件。根据平台类型或领域检查为文件命名,并让每个辅助项只专注于一种行为。 - 将行数作为审查参考:通常只有在满足每文件一个类型、工厂中无辅助代码的规则后,低于约 300 行的文件才算合适。因辅助项、转换或 Android 衔接代码而增加的体量属于设计问题。
- 如果转换只读取单个元素,应在元素类型上定义转换,而不是在
List或数组类型上定义。优先在调用处使用TargetBarcodeFormat.toMLKitFormat()和formats.map { it.toMLKitFormat() },而不是Array<TargetBarcodeFormat>.toMLKitFormats()。 - 不要在
Int、String、Double或Any等宽泛/通用接收者上定义领域转换,即使它们是私有的也不行。优先采用领域类型的伴生对象/工厂方向,例如BarcodeFormat.Companion.fromFormat(format: Int),而不是Int.toBarcodeFormat()。 - 仅当集合具有真正的领域行为时,才添加集合扩展函数,例如跨元素验证、去重、排序、批处理、缓存、非空检查或错误聚合。如果接收者是具体的
Array<SomeDomainType>或List<SomeDomainType>,且函数体主要是map { ... },就将其留在调用方代码中。 - 当 Kotlin 转换器返回或接收 Android 平台的
Int常量时,如果 API 支持,应使用匹配的 AndroidX/Java/Kotlin 注解,例如在函数、返回值或参数上使用 CameraX@ImageCapture.FlashMode样式的注解。放置注解前应检查其目标和保留策略;如果平台 API 只暴露普通Int,则不要盲目添加注解。 - 从带注解的平台
Int转换时,如果注解目标支持参数,应将注解放在format: Int参数上。例如,优先使用BarcodeFormat.Companion.fromFormat(@SomeBarcodeFormat format: Int),而不是未标注类型的原始输入。
Nitro 说明
- Kotlin HybridObject 实现必须继承生成的
Hybrid*Spec类,并添加@Keep和@DoNotStrip。 - 对于挂起或 I/O 工作,使用
Promise.async;对于受 CPU 限制的同步工作,使用Promise.parallel。 - 避免手动创建或传递
Promise<T>实例。优先使用Promise.async、Promise.parallel、Promise.resolved和Promise.rejected;只有对于无法封装为挂起 API 的原生完成、监听器或回调桥接,才使用手动创建的 Promise。 - 对于 Google
Task<T>等通用回调 API,先在单独的扩展文件(如Task+await.kt)中编写通用挂起适配器,然后在Promise.async { task.await() }中调用。不要在公开的 HybridObject 方法中手动连接addOnSuccessListener/addOnFailureListener/addOnCanceledListener。 - 对于
Promise<void>,使用Promise<Unit>。 - 延迟访问
NitroModules.applicationContext,并在其不可用时明确报错。 - 生成的属性是同步的 JS 入口。如果它们需要遵循 Android 线程约束或执行异步工作,请将其重新设计为方法或监听器。
评论
支持 Markdown