mcpField
K
kotlin

kotlin

github.com/margelo开发178595 次安装10 周前更新
代码仓库

关于 kotlin

面向 Kotlin 开发的编码技能,关注类型化状态模型、明确的异步边界和符合惯例的 Android 集成,适用于 Android 代码及 Kotlin Nitro Module。

主要功能

  1. 01类型安全的状态建模
  2. 02明确异步与线程使用方式
  3. 03设计非阻塞属性
  4. 04组织 Android 辅助代码与文件
  5. 05Nitro Module 实现指导

适用场景

  1. 01为 Kotlin API 建模状态
  2. 02选择协程与调度器边界
  3. 03组织 Android 实现文件
  4. 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 约束。

工作流

  1. 编辑前,阅读本地 Kotlin 代码、生成的抽象类和公开 API 的形态。
  2. 实现行为前,先用 Kotlin 类型对有效状态建模。
  3. 选择工作应当同步执行、基于协程执行,还是显式调度。
  4. 确保属性开销低且不会阻塞。
  5. 在 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