Communitygithub.com

Kotlin开发模式

一套惯用的Kotlin模式、最佳实践和约定,用于构建健壮、高效且易维护的Kotlin应用程序,涵盖协程、空安全和DSL构建器。在编写或审查Kotlin代码,以及处理惯用结构或空安全问题时使用。

Kotlin开发模式 是什么?

该技能为使用AI编码代理(如Claude Code或Codex)的开发者提供了全面的Kotlin开发指南,涵盖了从基础到高级的七个核心领域:空安全、不可变性、密封类与接口、协程与Flow、扩展函数、DSL构建器以及Gradle Kotlin DSL。它通过丰富的代码示例展示了如何利用Kotlin的类型系统避免空指针异常,如何使用`val`和`copy()`确保数据不可变,以及如何使用密封类构建穷举的层次结构。此外,技能还深入讲解了结构化并发模型,包括`coroutineScope`与`supervisorScope`的使用场景,并提供了Flow响应式流处理的模式。对于API设计和库开发,技能展示了如何使用`@DslMarker`和接收器lambda构建类型安全的领域特定语言。技能适用于所有主流Kotlin平台(JVM、Android、Native、JS),并提供了与AI代理交互的最佳实践,如明确需求、使用简洁的代码片段以及迭代式对话。通过遵循这些模式,开发者可以显著提高代码的可读性、可维护性和正确性。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/kotlin-patterns

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Kotlin 开发模式

用于构建健壮、高效且易维护应用程序的惯用 Kotlin 模式和最佳实践。

使用时机

  • 编写新的 Kotlin 代码时
  • 审查 Kotlin 代码时
  • 重构现有的 Kotlin 代码时
  • 设计 Kotlin 模块或库时
  • 配置 Gradle Kotlin DSL 构建时

工作原理

此技能在七个关键领域强制执行惯用的 Kotlin 约定:利用类型系统和安全调用运算符实现空安全;通过 val 和数据类上的 copy() 实现不可变性;使用密封类和接口实现穷举类型层次结构;通过协程和 Flow 实现结构化并发;使用扩展函数添加行为而无需继承;使用 @DslMarker 和 Lambda 接收器实现类型安全的 DSL 构建器;以及使用 Gradle Kotlin DSL 进行构建配置。

示例

使用 Elvis 运算符实现空安全:

fun getUserEmail(userId: String): String {
    val user = userRepository.findById(userId)
    return user?.email ?: "[email protected]"
}

用于穷举结果的密封类:

sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Failure(val error: AppError) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

使用 async/await 的结构化并发:

suspend fun fetchUserWithPosts(userId: String): UserProfile =
    coroutineScope {
        val user = async { userService.getUser(userId) }
        val posts = async { postService.getUserPosts(userId) }
        UserProfile(user = user.await(), posts = posts.await())
    }

核心原则

1. 空安全

Kotlin 的类型系统区分可空类型和非空类型。请充分利用它。

// 好:默认使用非空类型
fun getUser(id: String): User {
    return userRepository.findById(id)
        ?: throw UserNotFoundException("未找到用户:$id")
}

// 好:安全调用和 Elvis 运算符
fun getUserEmail(userId: String): String {
    val user = userRepository.findById(userId)
    return user?.email ?: "[email protected]"
}

// 坏:强制解包可空类型
fun getUserEmail(userId: String): String {
    val user = userRepository.findById(userId)
    return user!!.email // 如果为 null 则抛出 NPE
}

2. 默认不可变性

优先使用 val 而非 var,优先使用不可变集合而非可变集合。

// 好:不可变数据
data class User(
    val id: String,
    val name: String,
    val email: String,
)

// 好:使用 copy() 进行转换
fun updateEmail(user: User, newEmail: String): User =
    user.copy(email = newEmail)

// 好:不可变集合
val users: List<User> = listOf(user1, user2)
val filtered = users.filter { it.email.isNotBlank() }

// 坏:可变状态
var currentUser: User? = null // 避免可变全局状态
val mutableUsers = mutableListOf<User>() // 除非确实需要,否则避免

3. 表达式体与单表达式函数

使用表达式体编写简洁、易读的函数。

// 好:表达式体
fun isAdult(age: Int): Boolean = age >= 18

fun formatFullName(first: String, last: String): String =
    "$first $last".trim()

fun User.displayName(): String =
    name.ifBlank { email.substringBefore('@') }

// 好:将 when 用作表达式
fun statusMessage(code: Int): String = when (code) {
    200 -> "OK"
    404 -> "Not Found"
    500 -> "Internal Server Error"
    else -> "未知状态码:$code"
}

// 坏:不必要的块体
fun isAdult(age: Int): Boolean {
    return age >= 18
}

4. 使用数据类表示值对象

对于主要保存数据的类型,请使用数据类。

// 好:包含 copy、equals、hashCode、toString 的数据类
data class CreateUserRequest(
    val name: String,
    val email: String,
    val role: Role = Role.USER,
)

// 好:用于类型安全的值类(运行时零开销)
@JvmInline
value class UserId(val value: String) {
    init {
        require(value.isNotBlank()) { "UserId 不能为空" }
    }
}

@JvmInline
value class Email(val value: String) {
    init {
        require('@' in value) { "无效的电子邮件:$value" }
    }
}

fun getUser(id: UserId): User = userRepository.findById(id)

密封类和接口

对受限层次结构建模

// 好:用于穷举 when 的密封类
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Failure(val error: AppError) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

fun <T> Result<T>.getOrNull(): T? = when (this) {
    is Result.Success -> data
    is Result.Failure -> null
    is Result.Loading -> null
}

fun <T> Result<T>.getOrThrow(): T = when (this) {
    is Result.Success -> data
    is Result.Failure -> throw error.toException()
    is Result.Loading -> throw IllegalStateException("仍在加载")
}

用于 API 响应的密封接口

sealed interface ApiError {
    val message: String

    data class NotFound(override val message: String) : ApiError
    data class Unauthorized(override val message: String) : ApiError
    data class Validation(
        override val message: String,
        val field: String,
    ) : ApiError
    data class Internal(
        override val message: String,
        val cause: Throwable? = null,
    ) : ApiError
}

fun ApiError.toStatusCode(): Int = when (this) {
    is ApiError.NotFound -> 404
    is ApiError.Unauthorized -> 401
    is ApiError.Validation -> 422
    is ApiError.Internal -> 500
}

作用域函数

何时使用每个函数

// let:转换可空结果或作用域内结果
val length: Int? = name?.let { it.trim().length }

// apply:配置对象(返回对象本身)
val user = User().apply {
    name = "Alice"
    email = "[email protected]"
}

// also:执行副作用(返回对象本身)
val user = createUser(request).also { logger.info("已创建用户:${it.id}") }

// run:以接收者身份执行代码块(返回结果)
val result = connection.run {
    prepareStatement(sql)
    executeQuery()
}

// with:run 的非扩展形式
val csv = with(StringBuilder()) {
    appendLine("name,email")
    users.forEach { appendLine("${it.name},${it.email}") }
    toString()
}

反模式

// 坏:嵌套作用域函数
user?.let { u ->
    u.address?.let { addr ->
        addr.city?.let { city ->
            println(city) // 难以阅读
        }
    }
}

// 好:改为链式安全调用
val city = user?.address?.city
city?.let { println(it) }

扩展函数

无需继承即可添加功能

// 好:领域特定的扩展
fun String.toSlug(): String =
    lowercase()
        .replace(Regex("[^a-z0-9\\s-]"), "")
        .replace(Regex("\\s+"), "-")
        .trim('-')

fun Instant.toLocalDate(zone: ZoneId = ZoneId.systemDefault()): LocalDate =
    atZone(zone).toLocalDate()

// 好:集合扩展
fun <T> List<T>.second(): T = this[1]

fun <T> List<T>.secondOrNull(): T? = getOrNull(1)

// 好:受限扩展(不污染全局命名空间)
class UserService {
    private fun User.isActive(): Boolean =
        status == Status.ACTIVE && lastLogin.isAfter(Instant.now().minus(30, ChronoUnit.DAYS))

    fun getActiveUsers(): List<User> = userRepository.findAll().filter { it.isActive() }
}

协程

结构化并发

// 好:使用 coroutineScope 进行结构化并发
suspend fun fetchUserWithPosts(userId: String): UserProfile =
    coroutineScope {
        val userDeferred = async { userService.getUser(userId) }
        val postsDeferred = async { postService.getUserPosts(userId) }

        UserProfile(
            user = userDeferred.await(),
            posts = postsDeferred.await(),
        )
    }

// 好:当子任务可能独立失败时使用 supervisorScope
suspend fun fetchDashboard(userId: String): Dashboard =
    supervisorScope {
        val user = async { userService.getUser(userId) }
        val notifications = async { notificationService.getRecent(userId) }
        val recommendations = async { recommendationService.getFor(userId) }

        Dashboard(
            user = user.await(),
            notifications = try {
                notifications.await()
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                emptyList()
            },
            recommendations = try {
                recommendations.await()
            } catch (e: CancellationException) {
                throw e
            } catch (e: Exception) {
                emptyList()
            },
        )
    }

用于响应式流的 Flow

// 好:具有正确错误处理的冷流
fun observeUsers(): Flow<List<User>> = flow {
    while (currentCoroutineContext().isActive) {
        val users = userRepository.findAll()
        emit(users)
        delay(5.seconds)
    }
}.catch { e ->
    logger.error("观察用户时出错", e)
    emit(emptyList())
}

// 好:Flow 操作符
fun searchUsers(query: Flow<String>): Flow<List<User>> =
    query
        .debounce(300.milliseconds)
        .distinctUntilChanged()
        .filter { it.length >= 2 }
        .mapLatest { q -> userRepository.search(q) }
        .catch { emit(emptyList()) }

取消和清理

// 好:尊重取消
suspend fun processItems(items: List<Item>) {
    items.forEach { item ->
        ensureActive() // 在耗时操作前检查取消
        processItem(item)
    }
}

// 好:使用 try/finally 进行清理
suspend fun acquireAndProcess() {
    val resource = acquireResource()
    try {
        resource.process()
    } finally {
        withContext(NonCancellable) {
            resource.release() // 即使取消也要始终释放
        }
    }
}

委托

属性委托

// 惰性初始化
val expensiveData: List<User> by lazy {
    userRepository.findAll()
}

// 可观察属性
var name: String by Delegates.observable("initial") { _, old, new ->
    logger.info("名称从'$old'更改为'$new'")
}

// Map 支持的属性
class Config(private val map: Map<String, Any?>) {
    val host: String by map
    val port: Int by map
    val debug: Boolean by map
}

val config = Config(mapOf("host" to "localhost", "port" to 8080, "debug" to true))

接口委托

// 好:委托接口实现
class LoggingUserRepository(
    private val delegate: UserRepository,
    private val logger: Logger,
) : UserRepository by delegate {
    // 仅覆盖需要添加日志的方法
    override suspend fun findById(id: String): User? {
        logger.info("通过 id 查找用户:$id")
        return delegate.findById(id).also {
            logger.info("找到用户:${it?.name ?: "null"}")
        }
    }
}

DSL 构建器

类型安全的构建器

// 好:使用 @DslMarker 的 DSL
@DslMarker
annotation class HtmlDsl

@HtmlDsl
class HTML {
    private val children = mutableListOf<Element>()

    fun head(init: Head.() -> Unit) {
        children += Head().apply(init)
    }

    fun body(init: Body.() -> Unit) {
        children += Body().apply(init)
    }

    override fun toString(): String = children.joinToString("\n")
}

fun html(init: HTML.() -> Unit): HTML = HTML().apply(init)

// 使用方式
val page = html {
    head { title("我的页面") }
    body {
        h1("欢迎")
        p("你好,世界!")
    }
}

配置 DSL

data class ServerConfig(
    val host: String = "0.0.0.0",
    val port: Int = 8080,
    val ssl: SslConfig? = null,
    val database: DatabaseConfig? = null,
)

data class SslConfig(val certPath: String, val keyPath: String)
data class DatabaseConfig(val url: String, val maxPoolSize: Int = 10)

class ServerConfigBuilder {
    var host: String = "0.0.0.0"
    var port: Int = 8080
    private var ssl: SslConfig? = null
    private var database: DatabaseConfig? = null

    fun ssl(certPath: String, keyPath: String) {
        ssl = SslConfig(certPath, keyPath)
    }

    fun database(url: String, maxPoolSize: Int = 10) {
        database = DatabaseConfig(url, maxPoolSize)
    }

    fun build(): ServerConfig = ServerConfig(host, port, ssl, database)
}

fun serverConfig(init: ServerConfigBuilder.() -> Unit): ServerConfig =
    ServerConfigBuilder().apply(init).build()

// 使用方式
val config = serverConfig {
    host = "0.0.0.0"
    port = 443
    ssl("/certs/cert.pem", "/certs/key.pem")
    database("jdbc:postgresql://localhost:5432/mydb", maxPoolSize = 20)
}

用于惰性求值的序列

// 好:对大型集合进行多次操作时使用序列
val result = users.asSequence()
    .filter { it.isActive }
    .map { it.email }
    .filter { it.endsWith("@company.com") }
    .take(10)
    .toList()

// 好:生成无限序列
val fibonacci: Sequence<Long> = sequence {
    var a = 0L
    var b = 1L
    while (true) {
        yield(a)
        val next = a + b
        a = b
        b = next
    }
}

val first20 = fibonacci.take(20).toList()

Gradle Kotlin DSL

build.gradle.kts 配置

// 检查最新版本:https://kotlinlang.org/docs/releases.html
plugins {
    kotlin("jvm") version "2.3.10"
    kotlin("plugin.serialization") version "2.3.10"
    id("io.ktor.plugin") version "3.4.0"
    id("org.jetbrains.kotlinx.kover") version "0.9.7"
    id("io.gitlab.arturbosch.detekt") version "1.23.8"
}

group = "com.example"
version = "1.0.0"

kotlin {
    jvmToolchain(21)
}

dependencies {
    // Ktor
    implementation("io.ktor:ktor-server-core:3.4.0")
    implementation("io.ktor:ktor-server-netty:3.4.0")
    implementation("io.ktor:ktor-server-content-negotiation:3.4.0")
    implementation("io.ktor:ktor-serialization-kotlinx-json:3.4.0")

    // Exposed
    implementation("org.jetbrains.exposed:exposed-core:1.0.2")
    implementation("org.jetbrains.exposed:exposed-dao:1.0.2")
    implementation("org.jetbrains.exposed:exposed-jdbc:1.0.2")

    // Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")

    // Serialization
}

// ...(原内容已截断)

## 与 AI 代理协作的最佳实践

为了在使用 AI 编码代理(如 Claude Code、Cursor 或 Codex)时获得最佳 Kotlin 代码:

1. **明确你的需求** — 清楚说明你是在编写新代码、重构还是审查。
2. **提供上下文** — 包括相关类型签名、现有代码或你正在使用的框架。
3. **请求特定模式** — 例如“为网络请求使用密封类”或“使用 Flow 进行反应式数据获取”。
4. **迭代完善** — 请求替代方案或解释权衡,以加深理解。
5. **检查类型安全** — 始终要求非空类型并确认安全调用。

记住:AI 代理最多产的 Kotlin 代码将反映此文档中的模式——优先考虑空安全、不可变性和结构化并发。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相关技能