Source profileQuality 83/100

affaan-m/ECC/docs/zh-CN/skills/kotlin-patterns/SKILL.md

kotlin-patterns

Use it for engineering tasks; the detail page covers purpose, installation, and practical steps.

Source repository stars
234,327
Declared platforms
0
Static risk flags
1
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

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

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "docs/zh-CN/skills/kotlin-patterns"
    Safe inspection promptEditorial

    Inspect the Agent Skill "kotlin-patterns" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/docs/zh-CN/skills/kotlin-patterns/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      使用时机

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

      编写新的 Kotlin 代码审查 Kotlin 代码重构现有的 Kotlin 代码
    2. 02

      工作原理

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

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

      示例

      Review the “示例” section in the pinned source before continuing.

      Review and apply the “示例” source section.
    4. 04

      核心原则

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

      Kotlin 的类型系统区分可空和不可空类型。充分利用它。优先使用 val 而非 var,优先使用不可变集合而非可变集合。
    5. 05

      1. 空安全

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

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

    Permission review

    Static risk signals and limitations

    Network access

    medium · line 531

    The documentation includes network, browsing, or remote request actions.

    // Check for latest versions: https://kotlinlang.org/docs/releases.html

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score83/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    docs/zh-CN/skills/kotlin-patterns/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Kotlin 开发模式

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

    使用时机

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

    工作原理

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

    示例

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

    fun getUserEmail(userId: String): String {
        val user = userRepository.findById(userId)
        return user?.email ?: "unknown@example.com"
    }
    

    使用密封类处理穷举结果:

    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 的类型系统区分可空和不可空类型。充分利用它。

    // Good: Use non-nullable types by default
    fun getUser(id: String): User {
        return userRepository.findById(id)
            ?: throw UserNotFoundException("User $id not found")
    }
    
    // Good: Safe calls and Elvis operator
    fun getUserEmail(userId: String): String {
        val user = userRepository.findById(userId)
        return user?.email ?: "unknown@example.com"
    }
    
    // Bad: Force-unwrapping nullable types
    fun getUserEmail(userId: String): String {
        val user = userRepository.findById(userId)
        return user!!.email // Throws NPE if null
    }
    

    2. 默认不可变性

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

    // Good: Immutable data
    data class User(
        val id: String,
        val name: String,
        val email: String,
    )
    
    // Good: Transform with copy()
    fun updateEmail(user: User, newEmail: String): User =
        user.copy(email = newEmail)
    
    // Good: Immutable collections
    val users: List<User> = listOf(user1, user2)
    val filtered = users.filter { it.email.isNotBlank() }
    
    // Bad: Mutable state
    var currentUser: User? = null // Avoid mutable global state
    val mutableUsers = mutableListOf<User>() // Avoid unless truly needed
    

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

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

    // Good: Expression body
    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('@') }
    
    // Good: When as expression
    fun statusMessage(code: Int): String = when (code) {
        200 -> "OK"
        404 -> "Not Found"
        500 -> "Internal Server Error"
        else -> "Unknown status: $code"
    }
    
    // Bad: Unnecessary block body
    fun isAdult(age: Int): Boolean {
        return age >= 18
    }
    

    4. 数据类用于值对象

    使用数据类表示主要包含数据的类型。

    // Good: Data class with copy, equals, hashCode, toString
    data class CreateUserRequest(
        val name: String,
        val email: String,
        val role: Role = Role.USER,
    )
    
    // Good: Value class for type safety (zero overhead at runtime)
    @JvmInline
    value class UserId(val value: String) {
        init {
            require(value.isNotBlank()) { "UserId cannot be blank" }
        }
    }
    
    @JvmInline
    value class Email(val value: String) {
        init {
            require('@' in value) { "Invalid email: $value" }
        }
    }
    
    fun getUser(id: UserId): User = userRepository.findById(id)
    

    密封类和接口

    建模受限的层次结构

    // Good: Sealed class for exhaustive 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("Still loading")
    }
    

    用于 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: Transform nullable or scoped result
    val length: Int? = name?.let { it.trim().length }
    
    // apply: Configure an object (returns the object)
    val user = User().apply {
        name = "Alice"
        email = "alice@example.com"
    }
    
    // also: Side effects (returns the object)
    val user = createUser(request).also { logger.info("Created user: ${it.id}") }
    
    // run: Execute a block with receiver (returns result)
    val result = connection.run {
        prepareStatement(sql)
        executeQuery()
    }
    
    // with: Non-extension form of run
    val csv = with(StringBuilder()) {
        appendLine("name,email")
        users.forEach { appendLine("${it.name},${it.email}") }
        toString()
    }
    

    反模式

    // Bad: Nesting scope functions
    user?.let { u ->
        u.address?.let { addr ->
            addr.city?.let { city ->
                println(city) // Hard to read
            }
        }
    }
    
    // Good: Chain safe calls instead
    val city = user?.address?.city
    city?.let { println(it) }
    

    扩展函数

    在不使用继承的情况下添加功能

    // Good: Domain-specific extensions
    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()
    
    // Good: Collection extensions
    fun <T> List<T>.second(): T = this[1]
    
    fun <T> List<T>.secondOrNull(): T? = getOrNull(1)
    
    // Good: Scoped extensions (not polluting global namespace)
    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() }
    }
    

    协程

    结构化并发

    // Good: Structured concurrency with 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(),
            )
        }
    
    // Good: supervisorScope when children can fail independently
    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 用于响应式流

    // Good: Cold flow with proper error handling
    fun observeUsers(): Flow<List<User>> = flow {
        while (currentCoroutineContext().isActive) {
            val users = userRepository.findAll()
            emit(users)
            delay(5.seconds)
        }
    }.catch { e ->
        logger.error("Error observing users", e)
        emit(emptyList())
    }
    
    // Good: Flow operators
    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()) }
    

    取消与清理

    // Good: Respect cancellation
    suspend fun processItems(items: List<Item>) {
        items.forEach { item ->
            ensureActive() // Check cancellation before expensive work
            processItem(item)
        }
    }
    
    // Good: Cleanup with try/finally
    suspend fun acquireAndProcess() {
        val resource = acquireResource()
        try {
            resource.process()
        } finally {
            withContext(NonCancellable) {
                resource.release() // Always release, even on cancellation
            }
        }
    }
    

    委托

    属性委托

    // Lazy initialization
    val expensiveData: List<User> by lazy {
        userRepository.findAll()
    }
    
    // Observable property
    var name: String by Delegates.observable("initial") { _, old, new ->
        logger.info("Name changed from '$old' to '$new'")
    }
    
    // Map-backed properties
    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))
    

    接口委托

    // Good: Delegate interface implementation
    class LoggingUserRepository(
        private val delegate: UserRepository,
        private val logger: Logger,
    ) : UserRepository by delegate {
        // Only override what you need to add logging to
        override suspend fun findById(id: String): User? {
            logger.info("Finding user by id: $id")
            return delegate.findById(id).also {
                logger.info("Found user: ${it?.name ?: "null"}")
            }
        }
    }
    

    DSL 构建器

    类型安全构建器

    // Good: DSL with @DslMarker
    @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)
    
    // Usage
    val page = html {
        head { title("My Page") }
        body {
            h1("Welcome")
            p("Hello, World!")
        }
    }
    

    配置 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()
    
    // Usage
    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)
    }
    

    用于惰性求值的序列

    // Good: Use sequences for large collections with multiple operations
    val result = users.asSequence()
        .filter { it.isActive }
        .map { it.email }
        .filter { it.endsWith("@company.com") }
        .take(10)
        .toList()
    
    // Good: Generate infinite sequences
    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 配置

    // Check for latest versions: 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.0")
        implementation("org.jetbrains.exposed:exposed-dao:1.0.0")
        implementation("org.jetbrains.exposed:exposed-jdbc:1.0.0")
        implementation("org.jetbrains.exposed:exposed-kotlin-datetime:1.0.0")
    
        // Koin
        implementation("io.insert-koin:koin-ktor:4.2.0")
    
        // Coroutines
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
    
        // Testing
        testImplementation("io.kotest:kotest-runner-junit5:6.1.4")
        testImplementation("io.kotest:kotest-assertions-core:6.1.4")
        testImplementation("io.kotest:kotest-property:6.1.4")
        testImplementation("io.mockk:mockk:1.14.9")
        testImplementation("io.ktor:ktor-server-test-host:3.4.0")
        testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
    }
    
    tasks.withType<Test> {
        useJUnitPlatform()
    }
    
    detekt {
        config.setFrom(files("config/detekt/detekt.yml"))
        buildUponDefaultConfig = true
    }
    

    错误处理模式

    用于领域操作的 Result 类型

    // Good: Use Kotlin's Result or a custom sealed class
    suspend fun createUser(request: CreateUserRequest): Result<User> = runCatching {
        require(request.name.isNotBlank()) { "Name cannot be blank" }
        require('@' in request.email) { "Invalid email format" }
    
        val user = User(
            id = UserId(UUID.randomUUID().toString()),
            name = request.name,
            email = Email(request.email),
        )
        userRepository.save(user)
        user
    }
    
    // Good: Chain results
    val displayName = createUser(request)
        .map { it.name }
        .getOrElse { "Unknown" }
    

    require, check, error

    // Good: Preconditions with clear messages
    fun withdraw(account: Account, amount: Money): Account {
        require(amount.value > 0) { "Amount must be positive: $amount" }
        check(account.balance >= amount) { "Insufficient balance: ${account.balance} < $amount" }
    
        return account.copy(balance = account.balance - amount)
    }
    

    集合操作

    惯用的集合处理

    // Good: Chained operations
    val activeAdminEmails: List<String> = users
        .filter { it.role == Role.ADMIN && it.isActive }
        .sortedBy { it.name }
        .map { it.email }
    
    // Good: Grouping and aggregation
    val usersByRole: Map<Role, List<User>> = users.groupBy { it.role }
    
    val oldestByRole: Map<Role, User?> = users.groupBy { it.role }
        .mapValues { (_, users) -> users.minByOrNull { it.createdAt } }
    
    // Good: Associate for map creation
    val usersById: Map<UserId, User> = users.associateBy { it.id }
    
    // Good: Partition for splitting
    val (active, inactive) = users.partition { it.isActive }
    

    快速参考:Kotlin 惯用法

    惯用法描述
    val 优于 var优先使用不可变变量
    data class用于具有 equals/hashCode/copy 的值对象
    sealed class/interface用于受限的类型层次结构
    value class用于零开销的类型安全包装器
    表达式 when穷举模式匹配
    安全调用 ?.空安全的成员访问
    Elvis ?:为可空类型提供默认值
    let/apply/also/run/with用于编写简洁代码的作用域函数
    扩展函数在不使用继承的情况下添加行为
    copy()数据类上的不可变更新
    require/check前置条件断言
    协程 async/await结构化并发执行
    Flow冷响应式流
    sequence惰性求值
    委托 by在不使用继承的情况下重用实现

    应避免的反模式

    // Bad: Force-unwrapping nullable types
    val name = user!!.name
    
    // Bad: Platform type leakage from Java
    fun getLength(s: String) = s.length // Safe
    fun getLength(s: String?) = s?.length ?: 0 // Handle nulls from Java
    
    // Bad: Mutable data classes
    data class MutableUser(var name: String, var email: String)
    
    // Bad: Using exceptions for control flow
    try {
        val user = findUser(id)
    } catch (e: NotFoundException) {
        // Don't use exceptions for expected cases
    }
    
    // Good: Use nullable return or Result
    val user: User? = findUserOrNull(id)
    
    // Bad: Ignoring coroutine scope
    GlobalScope.launch { /* Avoid GlobalScope */ }
    
    // Good: Use structured concurrency
    coroutineScope {
        launch { /* Properly scoped */ }
    }
    
    // Bad: Deeply nested scope functions
    user?.let { u ->
        u.address?.let { a ->
            a.city?.let { c -> process(c) }
        }
    }
    
    // Good: Direct null-safe chain
    user?.address?.city?.let { process(it) }
    

    请记住:Kotlin 代码应简洁但可读。利用类型系统确保安全,优先使用不可变性,并使用协程处理并发。如有疑问,让编译器帮助你。

    Alternatives

    Compare before choosing