kotlin-guide

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Kotlin Guide

Kotlin 开发指南

Applies to: Kotlin 1.9+, JVM 17+, Coroutines, Android, Server-side
适用范围:Kotlin 1.9+、JVM 17+、协程、Android、服务端

Core Principles

核心原则

  1. Null Safety: Leverage the type system to eliminate null pointer exceptions at compile time
  2. Conciseness: Use data classes, scope functions, and destructuring to reduce boilerplate
  3. Coroutines for Concurrency: Structured concurrency with coroutines, not raw threads
  4. Immutability by Default: Prefer
    val
    over
    var
    , immutable collections over mutable
  5. Interop Awareness: Write Kotlin-idiomatic code while maintaining clean Java interop boundaries
  1. 空安全:利用类型系统在编译期消除空指针异常
  2. 简洁性:使用数据类、作用域函数、解构来减少样板代码
  3. 协程实现并发:使用协程实现结构化并发,而非原始线程
  4. 默认不可变:优先使用
    val
    而非
    var
    ,优先使用不可变集合而非可变集合
  5. 互操作性感知:编写符合Kotlin惯用风格的代码,同时保持清晰的Java互操作边界

Guardrails

防护规则

Version & Dependencies

版本与依赖

  • Use Kotlin 1.9+ with Gradle Kotlin DSL (
    build.gradle.kts
    )
  • Target JVM 17+ for server-side, match Android minSdk for mobile
  • Pin Kotlin and kotlinx library versions together (BOM alignment)
  • Run
    ./gradlew dependencies
    to audit transitive dependencies
  • 使用Kotlin 1.9+搭配Gradle Kotlin DSL(
    build.gradle.kts
  • 服务端目标版本为JVM 17+,移动端匹配Android minSdk要求
  • 统一锁定Kotlin与kotlinx库版本(BOM对齐)
  • 执行
    ./gradlew dependencies
    审计传递依赖

Code Style

代码风格

  • Run
    ktlint
    before every commit (format + check)
  • Run
    detekt
    for static analysis (complexity, code smells)
  • Packages: lowercase, no underscores (
    com.example.userservice
    )
  • Classes:
    PascalCase
    | Functions/properties:
    camelCase
    | Constants:
    SCREAMING_SNAKE_CASE
  • Follow Kotlin Coding Conventions
  • 每次提交前执行
    ktlint
    (格式化+检查)
  • 执行
    detekt
    做静态分析(复杂度检查、代码异味排查)
  • 包名:全小写,无下划线(例如
    com.example.userservice
  • 类名:
    PascalCase
    | 函数/属性名:
    camelCase
    | 常量名:
    SCREAMING_SNAKE_CASE
  • 遵循Kotlin官方编码规范

Null Safety

空安全

  • Never use
    !!
    (not-null assertion) in production code
  • Use safe calls (
    ?.
    ), elvis (
    ?:
    ), and smart casts instead
  • Nullable types only at API boundaries (deserialization, Java interop)
  • Prefer
    requireNotNull()
    or
    checkNotNull()
    with meaningful messages over
    !!
  • Use
    ?.let { }
    for nullable transformations, not nested
    if
    checks
kotlin
// BAD: crashes at runtime
val length = name!!.length

// GOOD: safe call with fallback
val length = name?.length ?: 0

// GOOD: explicit contract with clear error
val validName = requireNotNull(name) { "User name must not be null for ID=$id" }
  • 生产代码中严禁使用
    !!
    (非空断言)
  • 优先使用安全调用(
    ?.
    )、Elvis运算符(
    ?:
    )、智能类型转换替代
  • 可空类型仅允许在API边界使用(反序列化、Java互操作场景)
  • 优先使用带明确错误信息的
    requireNotNull()
    checkNotNull()
    替代
    !!
  • 可空类型转换使用
    ?.let { }
    ,而非嵌套
    if
    判断
kotlin
// BAD: 运行时会崩溃
val length = name!!.length

// GOOD: 带兜底逻辑的安全调用
val length = name?.length ?: 0

// GOOD: 明确的契约与清晰的错误提示
val validName = requireNotNull(name) { "ID为$id的用户姓名不能为空" }

Coroutines

协程

  • Always use structured concurrency (
    coroutineScope
    ,
    supervisorScope
    )
  • Never use
    GlobalScope
    in production code
  • Use
    withContext(Dispatchers.IO)
    for blocking I/O operations
  • Always handle
    CancellationException
    correctly (rethrow, never swallow)
  • Set timeouts with
    withTimeout
    or
    withTimeoutOrNull
    for external calls
kotlin
// BAD: unstructured, leaks coroutines
GlobalScope.launch { fetchData() }

// GOOD: structured concurrency, respects parent lifecycle
coroutineScope {
    val user = async { userService.getUser(id) }
    val orders = async { orderService.getOrders(id) }
    UserWithOrders(user.await(), orders.await())
}
  • 始终使用结构化并发(
    coroutineScope
    supervisorScope
  • 生产代码中严禁使用
    GlobalScope
  • 阻塞I/O操作使用
    withContext(Dispatchers.IO)
    执行
  • 必须正确处理
    CancellationException
    (重新抛出,禁止吞掉异常)
  • 外部调用需使用
    withTimeout
    withTimeoutOrNull
    设置超时
kotlin
// BAD: 非结构化并发,会导致协程泄漏
GlobalScope.launch { fetchData() }

// GOOD: 结构化并发,遵循父级生命周期
coroutineScope {
    val user = async { userService.getUser(id) }
    val orders = async { orderService.getOrders(id) }
    UserWithOrders(user.await(), orders.await())
}

Extension Functions

扩展函数

  • Use extension functions to add behavior, not to bypass access control
  • Keep extensions in a dedicated file (
    StringExtensions.kt
    ,
    DateExtensions.kt
    )
  • Do not add extensions to
    Any
    or overly generic types
  • Prefer member functions for core behavior, extensions for utility/convenience
  • Document extensions with
    @receiver
    KDoc tag when purpose is not obvious
  • 使用扩展函数添加额外行为,不得用于绕过访问控制
  • 扩展函数统一放在专用文件中(例如
    StringExtensions.kt
    DateExtensions.kt
  • 禁止为
    Any
    或过于通用的类型添加扩展函数
  • 核心行为优先使用成员函数,扩展函数仅用于工具类/便捷功能
  • 当扩展函数用途不明确时,使用
    @receiver
    KDoc标签标注接收者

Project Structure

项目结构

myproject/
├── app/                          # Application module or main entry
│   └── src/main/kotlin/
├── domain/                       # Business logic, entities, use cases
│   └── src/main/kotlin/
│       └── com/example/domain/
│           ├── model/            # Data classes, sealed classes
│           ├── repository/       # Repository interfaces
│           └── usecase/          # Business operations
├── data/                         # Data layer implementations
│   └── src/main/kotlin/
│       └── com/example/data/
│           ├── repository/       # Repository implementations
│           ├── remote/           # API clients, DTOs
│           └── local/            # Database, DAOs
├── presentation/                 # UI or API controllers
├── build.gradle.kts
├── settings.gradle.kts
└── gradle.properties
  • domain/
    has zero framework dependencies (pure Kotlin)
  • data/
    depends on
    domain/
    , implements repository interfaces
  • presentation/
    depends on
    domain/
    , never imports from
    data/
    directly
  • No circular module dependencies
myproject/
├── app/                          # 应用模块或主入口
│   └── src/main/kotlin/
├── domain/                       # 业务逻辑、实体、用例
│   └── src/main/kotlin/
│       └── com/example/domain/
│           ├── model/            # 数据类、密封类
│           ├── repository/       # 仓库接口
│           └── usecase/          # 业务操作
├── data/                         # 数据层实现
│   └── src/main/kotlin/
│       └── com/example/data/
│           ├── repository/       # 仓库实现
│           ├── remote/           # API客户端、DTO
│           └── local/            # 数据库、DAO
├── presentation/                 # UI或API控制器
├── build.gradle.kts
├── settings.gradle.kts
└── gradle.properties
  • domain/
    无任何框架依赖(纯Kotlin代码)
  • data/
    依赖
    domain/
    ,实现仓库接口
  • presentation/
    依赖
    domain/
    ,禁止直接导入
    data/
    的内容
  • 不允许出现循环模块依赖

Key Patterns

核心模式

Data Classes & Value Classes

数据类与值类

kotlin
data class User(
    val id: UserId,
    val email: Email,
    val name: String,
    val role: Role = Role.VIEWER,
) {
    init {
        require(name.isNotBlank()) { "User name must not be blank" }
    }
}

// Value classes for type-safe IDs (zero runtime overhead)
@JvmInline
value class UserId(val value: String) {
    init { require(value.isNotBlank()) { "UserId must not be blank" } }
}
kotlin
data class User(
    val id: UserId,
    val email: Email,
    val name: String,
    val role: Role = Role.VIEWER,
) {
    init {
        require(name.isNotBlank()) { "用户姓名不能为空" }
    }
}

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

Sealed Classes & Interfaces

密封类与接口

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

sealed class AppError(val message: String) {
    data class NotFound(val resource: String, val id: String) :
        AppError("$resource with ID $id not found")
    data class Validation(val field: String, val reason: String) :
        AppError("Validation failed for $field: $reason")
    data class Unauthorized(val detail: String = "Authentication required") :
        AppError(detail)
}

// Exhaustive when expressions
fun <T> Result<T>.getOrThrow(): T = when (this) {
    is Result.Success -> data
    is Result.Failure -> throw error.toException()
}
kotlin
sealed interface Result<out T> {
    data class Success<T>(val data: T) : Result<T>
    data class Failure(val error: AppError) : Result<Nothing>
}

sealed class AppError(val message: String) {
    data class NotFound(val resource: String, val id: String) :
        AppError("ID为$id$resource不存在")
    data class Validation(val field: String, val reason: String) :
        AppError("$field校验失败: $reason")
    data class Unauthorized(val detail: String = "需要身份认证") :
        AppError(detail)
}

// 穷尽式when表达式
fun <T> Result<T>.getOrThrow(): T = when (this) {
    is Result.Success -> data
    is Result.Failure -> throw error.toException()
}

Scope Functions Quick Reference

作用域函数速查

FunctionContextReturnsUse for
let
it
Lambda resultNullable transforms, scoped vars
run
this
Lambda resultCompute value using object context
with
this
Lambda resultOperate on non-null object
apply
this
Object itselfConfigure/build an object
also
it
Object itselfSide effects (logging, events)
kotlin
// apply: configure and return the object
val request = HttpRequest.Builder().apply {
    url(endpoint)
    header("Authorization", "Bearer $token")
    timeout(Duration.ofSeconds(30))
}.build()

// also: side effects without modifying the chain
val savedUser = userRepository.save(newUser).also { user ->
    logger.info("Created user: ${user.id}")
}
函数上下文返回值适用场景
let
it
Lambda返回值可空类型转换、作用域变量
run
this
Lambda返回值基于对象上下文计算值
with
this
Lambda返回值操作非空对象
apply
this
对象本身配置/构建对象
also
it
对象本身副作用操作(日志、事件)
kotlin
// apply: 配置并返回对象
val request = HttpRequest.Builder().apply {
    url(endpoint)
    header("Authorization", "Bearer $token")
    timeout(Duration.ofSeconds(30))
}.build()

// also: 不修改调用链的副作用操作
val savedUser = userRepository.save(newUser).also { user ->
    logger.info("创建用户: ${user.id}")
}

Testing

测试

Standards

标准

  • Test files:
    *Test.kt
    in
    src/test/kotlin/
    (mirror source package)
  • Use JUnit 5 with
    @Test
    ,
    @Nested
    ,
    @DisplayName
  • Use MockK for mocking (idiomatic Kotlin, supports coroutines)
  • Table-driven style with
    @ParameterizedTest
    and
    @MethodSource
  • Coverage target: >80% for business logic, >60% overall
  • Use
    runTest
    from
    kotlinx-coroutines-test
    for coroutine tests
  • 测试文件:
    *Test.kt
    放在
    src/test/kotlin/
    下(与源码包结构对应)
  • 使用JUnit 5的
    @Test
    @Nested
    @DisplayName
    注解
  • 使用MockK做mock(符合Kotlin惯用风格,支持协程)
  • 表驱动测试使用
    @ParameterizedTest
    @MethodSource
    实现
  • 覆盖率目标:业务逻辑>80%,整体>60%
  • 协程测试使用
    kotlinx-coroutines-test
    提供的
    runTest

Unit Test Pattern

单元测试模式

kotlin
class UserServiceTest {
    private val userRepository = mockk<UserRepository>()
    private val eventBus = mockk<EventBus>(relaxed = true)
    private val service = UserService(userRepository, eventBus)

    @Nested
    @DisplayName("createUser")
    inner class CreateUser {
        @Test
        fun `creates user with valid input`() = runTest {
            coEvery { userRepository.save(any()) } returns mockUser
            val result = service.createUser(validInput)

            assertThat(result).isInstanceOf(Result.Success::class.java)
            coVerify { userRepository.save(any()) }
            coVerify { eventBus.publish(any<UserCreatedEvent>()) }
        }

        @Test
        fun `fails with blank name`() = runTest {
            val result = service.createUser(blankNameInput)

            assertThat(result).isInstanceOf(Result.Failure::class.java)
            coVerify(exactly = 0) { userRepository.save(any()) }
        }
    }
}
kotlin
class UserServiceTest {
    private val userRepository = mockk<UserRepository>()
    private val eventBus = mockk<EventBus>(relaxed = true)
    private val service = UserService(userRepository, eventBus)

    @Nested
    @DisplayName("createUser方法")
    inner class CreateUser {
        @Test
        fun `输入合法时创建用户成功`() = runTest {
            coEvery { userRepository.save(any()) } returns mockUser
            val result = service.createUser(validInput)

            assertThat(result).isInstanceOf(Result.Success::class.java)
            coVerify { userRepository.save(any()) }
            coVerify { eventBus.publish(any<UserCreatedEvent>()) }
        }

        @Test
        fun `姓名为空时创建失败`() = runTest {
            val result = service.createUser(blankNameInput)

            assertThat(result).isInstanceOf(Result.Failure::class.java)
            coVerify(exactly = 0) { userRepository.save(any()) }
        }
    }
}

Parameterized Tests

参数化测试

kotlin
companion object {
    @JvmStatic
    fun emailCases() = listOf(
        Arguments.of("user@example.com", true),
        Arguments.of("invalid-email", false),
        Arguments.of("", false),
    )
}

@ParameterizedTest(name = "email \"{0}\" valid={1}")
@MethodSource("emailCases")
fun `validates email format`(email: String, expected: Boolean) {
    val result = runCatching { Email(email) }
    assertThat(result.isSuccess).isEqualTo(expected)
}
kotlin
companion object {
    @JvmStatic
    fun emailCases() = listOf(
        Arguments.of("user@example.com", true),
        Arguments.of("invalid-email", false),
        Arguments.of("", false),
    )
}

@ParameterizedTest(name = "邮箱\"{0}\"校验结果={1}")
@MethodSource("emailCases")
fun `校验邮箱格式`(email: String, expected: Boolean) {
    val result = runCatching { Email(email) }
    assertThat(result.isSuccess).isEqualTo(expected)
}

Tooling

工具链

Essential Commands

常用命令

bash
./gradlew build                    # Compile + test + check
./gradlew test                     # Run all tests
./gradlew test --tests "*.UserServiceTest"  # Specific test class
./gradlew koverReport              # Coverage report
./gradlew ktlintCheck              # Check formatting
./gradlew ktlintFormat             # Auto-fix formatting
./gradlew detekt                   # Static analysis
./gradlew dependencies             # Dependency tree
bash
./gradlew build                    # 编译 + 测试 + 检查
./gradlew test                     # 运行所有测试
./gradlew test --tests "*.UserServiceTest"  # 运行指定测试类
./gradlew koverReport              # 生成覆盖率报告
./gradlew ktlintCheck              # 检查代码格式
./gradlew ktlintFormat             # 自动修复代码格式
./gradlew detekt                   # 静态分析
./gradlew dependencies             # 生成依赖树

Gradle Kotlin DSL Configuration

Gradle Kotlin DSL配置

kotlin
// build.gradle.kts
plugins {
    kotlin("jvm") version "1.9.22"
    id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
    id("io.gitlab.arturbosch.detekt") version "1.23.4"
    id("org.jetbrains.kotlinx.kover") version "0.7.5"
}

kotlin { jvmToolchain(17) }

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
    testImplementation(kotlin("test"))
    testImplementation("io.mockk:mockk:1.13.9")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
    testImplementation("org.assertj:assertj-core:3.25.1")
}

detekt {
    config.setFrom("detekt.yml")
    buildUponDefaultConfig = true
}

kover {
    verify { rule { minBound(80) } }
}
kotlin
// build.gradle.kts
plugins {
    kotlin("jvm") version "1.9.22"
    id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
    id("io.gitlab.arturbosch.detekt") version "1.23.4"
    id("org.jetbrains.kotlinx.kover") version "0.7.5"
}

kotlin { jvmToolchain(17) }

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
    testImplementation(kotlin("test"))
    testImplementation("io.mockk:mockk:1.13.9")
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")
    testImplementation("org.assertj:assertj-core:3.25.1")
}

detekt {
    config.setFrom("detekt.yml")
    buildUponDefaultConfig = true
}

kover {
    verify { rule { minBound(80) } }
}

Detekt Configuration

Detekt配置

yaml
undefined
yaml
undefined

detekt.yml

detekt.yml

complexity: LongMethod: threshold: 50 CyclomaticComplexMethod: threshold: 10 LargeClass: threshold: 300 LongParameterList: functionThreshold: 5 constructorThreshold: 8 style: ForbiddenComment: values: - "TODO(?!\(#\d+\))" # TODOs require issue reference MagicNumber: ignoreNumbers: ["-1", "0", "1", "2"] MaxLineLength: maxLineLength: 120 potential-bugs: UnsafeCast: active: true
undefined
complexity: LongMethod: threshold: 50 CyclomaticComplexMethod: threshold: 10 LargeClass: threshold: 300 LongParameterList: functionThreshold: 5 constructorThreshold: 8 style: ForbiddenComment: values: - "TODO(?!\(#\d+\))" # TODO必须关联issue编号 MagicNumber: ignoreNumbers: ["-1", "0", "1", "2"] MaxLineLength: maxLineLength: 120 potential-bugs: UnsafeCast: active: true
undefined

References

参考资料

For detailed patterns and examples, see:
  • references/patterns.md -- Coroutine patterns, sealed class hierarchies, DSL builders, Flow patterns
如需查看详细模式与示例,请访问:
  • references/patterns.md -- 协程模式、密封类层级、DSL构建器、Flow模式

External References

外部参考