Kotlin编程代码规范指南
# Kotlin编程代码规范指南
本规范参考 Kotlin Coding Conventions (opens new window) 及 Android Kotlin Style Guide (opens new window),结合项目实践精简整理。
# 目录
- 01.规范概述
- 02.命名规范
- 03.代码格式规范
- 04.注释规范
- 05.类与对象设计规范
- 06.函数规范
- 07.表达式与控制流
- 08.空安全
- 09.作用域函数
- 10.协程规范
- 11.集合与序列
- 12.现代Kotlin特性
- 13.工具链与自动化
- 14.常见反模式
- 15.代码审查清单
- 16.常见陷阱速查
# 01.规范概述
# 1.1 为何需要代码规范
疑惑:Kotlin 语法灵活,代码能跑就行,为什么还要花时间制定规范?
答疑:Kotlin 以简洁著称,但“简洁”不等于“随意”。同一行代码可以有多种写法——it 滥用、作用域函数嵌套、!! 断言、随处 GlobalScope——这些看似“方便”的写法在团队协作中会成为维护噩梦。规范的本质是用统一的写法写安全的代码。
# 1.2 核心目标
| 目标 | 说明 |
|---|---|
| 可读性 | 代码像 Kotlin 一样简洁优雅 |
| 一致性 | 整个项目像一个人写的 |
| 安全性 | 从编码层面避免 NPE、协程泄漏、集合误用 |
| 可维护性 | 半年后自己还能快速读懂 |
| 可审查性 | Code Review 聚焦逻辑而非格式 |
# 1.3 要求等级
- 必须(Mandatory):必须采用,违反将在 Code Review 中被驳回
- 推荐(Preferable):理应采用,特殊情况可不采用但需注释说明
- 可选(Optional):团队自行决定
# 1.4 Kotlin版本
代码应针对 Kotlin 1.9+,优先使用稳定特性,不使用实验性 API(除非标记 @OptIn 且经过充分讨论)。
# 02.命名规范
# 2.1 命名总表
| 类型 | 规则 | 示例 |
|---|---|---|
| 包名 | 全小写 + 点分隔 | com.example.myapp.data |
| 文件名 | 大驼峰(同主类名) | UserRepository.kt, StringExt.kt |
| 类/接口/对象 | 大驼峰 | MainActivity, UserService, AppConfig |
| 函数 | 小驼峰(动词开头) | getUser(), fetchData(), isValid() |
| 属性 | 小驼峰(名词) | userName, isActive, maxRetryCount |
| 常量(顶层/companion) | 全大写 + 下划线 | MAX_COUNT, DEFAULT_HOST |
| 枚举常量 | 全大写 + 下划线 | Color.RED, Status.SUCCESS |
| 类型参数 | 单大写字母或大驼峰 + T | T, ItemT, MapT |
| 测试方法 | 反引号允许空格 | fun `should return user when id exists`() |
| 扩展函数/属性 | 小驼峰(同函数) | fun String.isEmail() |
# 2.2 正确与错误示例
// ✅ 正确
const val MAX_RETRY_COUNT = 3
class UserRepository(private val api: UserApi) {
fun getUser(userId: Long): User? { ... }
val isReady: Boolean get() = ...
}
// ✅ 返回布尔值的属性/函数用 is/has 前缀
val isVisible: Boolean get() = visibility == View.VISIBLE
fun hasPermission(perm: Permission): Boolean
// ❌ 错误
const val MaxRetryCount = 3 // 常量非全大写
class userRepository {} // 类名未大驼峰
fun get_user(user_id: Long) {} // 函数名用了下划线
var userName: String = "" // 在 Kotlin 中函数/属性无天然区别 → 属性用名词
fun visible() = ... // 布尔返回值应 isVisible
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2.3 命名反模式
| 反模式 | 示例 | 改进 |
|---|---|---|
| 过度缩写 | usrRepo, btnClk | userRepository, onButtonClick |
| 含义不清 | data, info, tmp | userData, configInfo |
| 否定命名 | isNotEmpty() | isEmpty() 后取反 |
| 拼音命名 | dianHua | phoneNumber |
| 无意义后缀 | UserDataClass, ListImpl | User, UserList |
| 测试函数无空格 | shouldReturnUserWhenIdExists() | `should return user when id exists`() |
# 03.代码格式规范
# 3.1 缩进与空格 【必须】
// ✅ 使用 4 个空格缩进(Kotlin 官方风格)
class UserService(private val repo: UserRepo) {
fun process(id: Long): User? {
val cached = cache[id] // 当可以直接 return 时省略 else
if (cached != null) return cached
return repo.findById(id)?.also {
cache[id] = it
}
}
}
// ✅ 运算符两侧加空格
val result = a + b * c
val ok = (x > 0) && (y < 10)
// ✅ 冒号:类型标注时冒号前无空格、后一个空格
fun greet(name: String): String
// ✅ 冒号:继承/实现时冒号两侧各一个空格
class Dog : Animal(), Barkable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 3.2 大括号与尾逗号 【必须】
// ✅ 左大括号不换行,右大括号独占一行
class UserManager {
fun doWork() {
if (condition) {
// ...
}
}
}
// ✅ 尾逗号(trailing comma):参数/属性列表最后加逗号
// 好处:diff 干净,重排方便
data class UserDto(
val id: Long,
val name: String,
val email: String? = null, // ← 尾逗号
)
fun createUser(
name: String,
email: String,
role: Role = Role.USER, // ← 尾逗号
): User
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3.3 换行与对齐
// ✅ 参数过多时:每个参数独占一行,缩进 4 空格
fun sendNotification(
userId: String,
title: String,
message: String,
type: NotificationType = NotificationType.DEFAULT,
): NotificationResult
// ✅ 链式调用:每个操作符前换行,用 . 开头
val names = users
.filter { it.isActive }
.map { it.name }
.sorted()
.take(10)
// ✅ when 分支过长时换行
return when (status) {
Status.Active -> buildActiveResponse(user)
Status.Inactive,
Status.Pending -> buildDefaultResponse() // 多条件并列
else -> throw IllegalStateException("Unknown status: $status")
}
// 单行长度建议不超过 120 字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3.4 空行与分隔
class UserService(
private val repo: UserRepo,
private val cache: UserCache,
) {
// 同类型的属性放在一起,不同逻辑组之间空一行
private val localCache = LruCache<Long, User>(100)
fun getUser(id: Long): User? {
val cached = cache[id]
if (cached != null && !cached.isExpired()) return cached
return repo.findById(id)?.also {
cache[id] = it
}
}
fun deleteUser(id: Long) {
cache.remove(id)
repo.delete(id)
}
// 不同职责的方法组之间空一行
companion object {
private const val TAG = "UserService"
private const val CACHE_TTL_MS = 60_000L
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 04.注释规范
# 4.1 核心原则
注释解释“为什么”,代码说明“做了什么”。
// ❌ 差的注释:复述代码
count++ // count 加 1
// ✅ 好的注释:解释意图
count++ // 跳过 CSV 文件的标题行
// ✅ 记录决策原因
// 使用重试机制,因为第三方接口在高峰期偶尔超时(2023-06 确认的问题)
suspend fun fetchWithRetry(): Data
// ✅ 标注非显而易见的性能考量
// 用 Sequence 替代 List,上游 10w+ 元素,避免中间集合分配
val result = data.asSequence()
.filter { it.isActive }
.map { it.transform() }
.toList()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 4.2 KDoc类注释
/**
* 用户管理服务,负责用户的 CRUD 和权限管理。
*
* 线程安全性:内部使用 [Mutex] 保护共享状态,所有公开方法均线程安全。
*
* 使用示例:
* ```
* val service = UserService(repo, cache)
* val user = service.getUser(123)
* ```
*
* @property repo 用户数据源
* @property cache 本地缓存
*/
class UserService(
private val repo: UserRepo,
private val cache: UserCache,
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.3 函数注释
/**
* 根据 ID 查询用户信息。
*
* 优先从缓存获取,未命中时查数据库并回填缓存。
*
* @param userId 用户唯一标识,必须 > 0
* @return 用户对象;若不存在则返回 null
*/
fun getUser(userId: Long): User?
/**
* 批量更新用户状态。
*
* @param ids 待更新的用户 ID 集合
* @param newStatus 目标状态
* @throws IllegalArgumentException 当 ids 为空时
*/
fun batchUpdateStatus(ids: Set<Long>, newStatus: UserStatus)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.4 TODO 与 FIXME
// TODO(yc): 添加分页查询支持,预计 v2.0 实现
fun getAllUsers(): List<User>
// FIXME(yc): userId 为 Long.MAX_VALUE 时缓存 key 冲突,需要改用复合 key
fun cacheUser(userId: Long, user: User)
// HACK(yc): 第三方 SDK 的 bug,等待官方修复后移除(issue: #1234)
fun workaroundForSdkBug()
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 05.类与对象设计规范
# 5.1 data class 【必须】
// ✅ 纯数据载体 → data class(自动生成 equals/hashCode/toString/copy/componentN)
data class User(
val id: Long,
val name: String,
val email: String? = null,
)
// ✅ 不可变优先:全部用 val
data class Config(val host: String, val port: Int)
// ❌ 可变 data class 只在确实需要时使用,并写明原因
data class MutableCounter(var count: Int) // 需要时可写
// ❌ 不要用 data class 当"大而全"的对象
// data class 的 copy() 是浅拷贝,嵌套对象会导致意外共享
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 5.2 sealed class 与 sealed interface 【推荐】
// ✅ sealed class:限制子类的封闭层级
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val code: Int, val message: String) : Result<Nothing>()
object Loading : Result<Nothing>()
}
// ✅ when 覆盖所有子类时无需 else
fun <T> handle(result: Result<T>) = when (result) {
is Result.Success -> showData(result.data)
is Result.Error -> showError(result.code, result.message)
is Result.Loading -> showLoading()
// 编译器保证没有遗漏
}
// ✅ Kotlin 1.5+ 可用 sealed interface(更灵活多继承)
sealed interface NetworkState {
data class Available(val latencyMs: Long) : NetworkState
data object Unavailable : NetworkState // data object(Kotlin 1.9+)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 5.3 object 与 companion object 【推荐】
// ✅ 单例用 object(线程安全的懒加载)
object AppConfig {
val baseUrl: String = "https://api.example.com"
const val TIMEOUT_MS = 10_000L
}
// ✅ companion object:类级别的常量和工厂方法
class User private constructor(val id: Long, val name: String) {
companion object {
const val DEFAULT_NAME = "Anonymous"
fun create(name: String): User = User(nextId(), name)
private var currentId = 0L
private fun nextId() = ++currentId
}
}
// ❌ 避免在 companion object 中实例化重量级对象(考虑依赖注入)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 5.4 可见性修饰符 【必须】
// ✅ Kotlin 默认 public,但应显式声明内部可见性
class UserRepository(
private val api: UserApi, // 构造参数私有
private val cache: UserCache,
) {
// 公开 API
suspend fun getUser(id: Long): User? = ...
// 内部辅助方法用 private
private suspend fun refreshCache(id: Long) { ... }
}
// ✅ internal:模块内可见
internal class DatabaseHelper // 不暴露给模块外部
// ❌ 不要用 public 字段直接暴露可变状态
class BadExample {
var status: String = "" // ❌ 直接暴露
}
// ✅ 用 backing property
class GoodExample {
var status: String = ""
private set // ✅ 外部只读
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 5.5 继承与组合 【推荐】
// ✅ 组合优先于继承
class OrderService(
private val paymentService: PaymentService, // 组合
private val notificationService: NotificationService,
)
// ✅ 需要继承时:明确标记 open 和 override
open class Animal {
open fun speak() = println("...")
}
class Dog : Animal() {
override fun speak() = println("Woof!")
}
// ✅ 抽象类用于模板方法
abstract class BaseViewModel {
abstract fun initialState(): State
fun onError(e: Throwable) { // 模板方法
logError(e)
handleError(e) // 子类自定义处理
}
abstract fun handleError(e: Throwable)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 06.函数规范
# 6.1 单表达式函数 【推荐】
// ✅ 简单逻辑:单表达式函数体
fun isAdult(age: Int): Boolean = age >= 18
fun greet(name: String) = "Hello, $name!"
// ✅ 不省略返回类型(公开 API 必须保留)
fun calculateTax(income: Long): Double = // 保留返回类型
when {
income < 36_000 -> 0.0
income < 144_000 -> income * 0.1
else -> income * 0.2
}
// ❌ 过长的单表达式应换成块体
// 经验:超过 3 行的单表达式考虑换块体
fun complex(in: String) = in
.trim()
.lowercase()
.replace(" ", "_")
.take(50)
.plus(".txt") // ← 5 行,建议换成块体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 6.2 参数与默认值 【推荐】
// ✅ 默认参数替代重载
fun connect(host: String, port: Int = 8080, useTls: Boolean = true)
// ✅ 命名参数提高可读性(布尔值参数尤其推荐)
connect("api.example.com", useTls = false) // 调用时语义明确
// ✅ 需要时用 @JvmOverloads 生成 Java 重载版本
class Dialog @JvmOverloads constructor(
val title: String,
val message: String = "",
val positive: String = "确定",
)
// ✅ 参数顺序:必选在前,可选在后
fun search(query: String, page: Int = 1, pageSize: Int = 20)
// ❌ 不要用 Boolean 参数做行为切换 → 拆成两个函数
// fun save(data: Data, sync: Boolean) ❌
fun saveLocal(data: Data) // ✅
fun saveRemote(data: Data) // ✅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 6.3 扩展函数与属性 【推荐】
// ✅ 扩展函数:给既有类添加工具方法
fun String.isEmail(): Boolean =
matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"))
fun String.md5(): String = MessageDigest
.getInstance("MD5")
.digest(toByteArray())
.joinToString("") { "%02x".format(it) }
// ✅ 扩展属性:只读计算属性
val View.isVisible: Boolean
get() = visibility == View.VISIBLE
val Context.screenWidth: Int
get() = resources.displayMetrics.widthPixels
// ❌ 不要滥用扩展
// ① 仅调用一次就别写成扩展
// ② 别用扩展替代成员函数(业务逻辑应放在类内)
// ③ 扩展函数不能 override,不要期望多态行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 6.4 中缀与操作符 【推荐】
// ✅ infix:两个对等对象的操作(DSL 风格)
infix fun Int.toward(to: Int): IntProgression = this towardTo to
// ✅ 操作符重载:数学语义明确时使用
data class Vector(val x: Double, val y: Double) {
operator fun plus(other: Vector) = Vector(x + other.x, y + other.y)
operator fun times(scalar: Double) = Vector(x * scalar, y * scalar)
}
// ❌ 不要为“看起来很酷”而重载操作符
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 6.5 Lambda 与高阶函数 【推荐】
// ✅ 最后一个参数是 lambda 时,花括号写到括号外
users.filter { it.isActive }
.map { it.name }
// ✅ 唯一参数是 lambda 时,省略括号
thread { doWork() }
// ✅ it 的使用:上下文明确时可用;否则显式命名
users.filter { it.age > 18 } // ✅ 上下文清晰
users.map { user -> // ✅ 显式命名更清晰
"${user.firstName} ${user.lastName}"
}
// ✅ 未使用的 lambda 参数用 _ 代替
map.forEach { _, value -> process(value) }
// ✅ 函数引用替代冗余 lambda
users.forEach(::println) // ✅
users.forEach { println(it) } // ⚠ 等价但冗余
// ✅ 带 receiver 的 lambda:type-safe builder
fun buildHtml(block: HtmlBuilder.() -> Unit): String =
HtmlBuilder().apply(block).build()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 07.表达式与控制流
# 7.1 when 表达式 【必须】
// ✅ when 替代冗长 if-else 链
fun getStatusColor(status: Status): Int = when (status) {
Status.Active -> Color.GREEN
Status.Inactive -> Color.GRAY
Status.Error -> Color.RED
}
// ✅ when 无参数(多条件布尔)
fun describe(age: Int) = when {
age < 0 -> throw IllegalArgumentException("invalid age")
age < 13 -> "儿童"
age < 18 -> "青少年"
age < 60 -> "成年人"
else -> "老年人"
}
// ✅ sealed class + when → 编译器保证穷举
fun handle(netState: NetworkState) = when (netState) {
is NetworkState.Available -> showOnline(netState.latencyMs)
is NetworkState.Unavailable -> showOffline()
}
// 编译器不要求 else(因为穷举了所有子类)
// ❌ when 当作命令式用(不推荐)
when (x) { // 忽略了返回值
1 -> println("one")
}
// ✅ 如果只是副作用,考虑用 if 更直接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 7.2 if/try 作为表达式 【推荐】
// ✅ if 作为表达式(无三元运算符)
val user = if (id > 0) getUser(id) else GuestUser
// ✅ try 作为表达式
val result = try {
parse(input)
} catch (e: JsonSyntaxException) {
log.warn("Parse failed", e)
null // 表达式的值
}
// ✅ 多行表达式:最后一行是值
val status = if (score >= 90) {
log.info("Excellent")
"A"
} else if (score >= 60) {
"B"
} else {
"C"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 7.3 Elvis 与提前返回 【推荐】
// ✅ Elvis:提供默认值
val displayName = user.name ?: "Unknown"
// ✅ Elvis + 提前返回(常见卫语句模式)
fun process(user: User?) {
val name = user?.name ?: return // 空则提前返回
val email = user.email ?: error("Email required")
doSomething(name, email)
}
// ✅ Elvis + throw
val config = loadConfig() ?: throw ConfigurationException("Missing config")
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 7.4 解构声明 【可选】
// ✅ data class 自带 componentN,可直接解构
val (name, age) = person
val (id, _, email) = user // _ 忽略不需要的字段
// ✅ 遍历 Map
for ((key, value) in map) {
println("$key -> $value")
}
// ✅ Pair/Triple
val (lat, lng) = location.split(",").let { it[0] to it[1] }
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 7.5 智能类型转换
// ✅ Kotlin 自动智能转换(smart cast)
fun process(obj: Any) {
if (obj is String) {
println(obj.length) // 自动转为 String,可直接访问 .length
}
}
// ✅ when 中的智能转换
fun describe(obj: Any) = when (obj) {
is Int -> "整数: $obj"
is String -> "字符串长度: ${obj.length}"
else -> "未知类型"
}
// ✅ 安全的类型转换:as?
val user = obj as? User // 失败返回 null
user?.let { showProfile(it) }
// ❌ 不要用 as 强转(除非 100% 确定类型)
// val user = obj as User // 类型不匹配抛 ClassCastException
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 08.空安全
# 8.1 安全调用链 【必须】
// ✅ 安全调用 ?. 链式处理可空类型
val city = user?.address?.city?.name
// ✅ 安全调用 + 操作符
val length = user?.name?.length ?: 0
val upper = user?.name?.uppercase()
// ❌ 不要用 if 嵌套替代安全调用链
// if (user != null) { if (user.address != null) { ... } } // ❌
// ✅ val city = user?.address?.city // 一行搞定
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 8.2 Elvis 操作符进阶 【必须】
// ✅ 基础:提供默认值
val name = user.name ?: "Guest"
// ✅ 提前返回(卫语句)
fun validate(user: User?) {
val id = user?.id ?: return
val name = user.name ?: error("name required")
// ... id 和 name 现在都是非空
}
// ✅ 日志 + 默认值
val config = loadCache(key) ?: run {
log.warn("Cache miss for $key")
loadFromNetwork(key)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 8.3 lateinit 与 lazy 【推荐】
// ✅ lateinit:由框架/DI 注入的延迟初始化变量
@Inject lateinit var repository: UserRepository
// ✅ lateinit 检查
if (::repository.isInitialized) {
repository.getUser(1)
}
// ✅ lazy:首次访问时计算且只计算一次(线程安全默认 SYNCHRONIZED)
private val userService by lazy {
UserService(createApi(), createCache())
}
// ❌ lateinit 不要用于基本类型(Int, Boolean 等)——会编译错误
// ❌ 能用构造注入就不要用 lateinit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 8.4 平台类型处理 【必须】
// ✅ Java 互操作:Java 返回的引用默认是“平台类型”(T!)
// 立即声明为可空或非空
val name: String = javaUser.getName() // ✅ 断言非空(风险自负)
val name: String? = javaUser.getName() // ✅ 标记为可空(安全)
val name = javaUser.getName() // ❌ 平台类型泄漏,编译器不检查
// ✅ 对 Java 集合也同理
val list: List<User> = javaRepo.findAll() // ✅ 或 List<User>?
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 8.5 !! 的使用边界 【必须】
// ❌ 代码审查时,每个 !! 都必须有充分理由
// val name = user!!.name // ❌ 无理由 → 驳回
// ✅ 唯一可接受的场景:
// 1. 与 Java 框架交互,框架保证非空
// 2. 测试代码中做断言
// 3. 代码逻辑已经保证非空,但编译器推导不出
fun testUser() {
val user = createTestUser()
assertNotNull(user)
assertEquals("Alice", user!!.name) // 测试中可接受
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 09.作用域函数
# 9.1 函数速查表
| 函数 | 对象引用 | 返回值 | 是否为扩展 | 典型场景 |
|---|---|---|---|---|
let | it | Lambda 结果 | ✅ | 非空执行 / 变量限定作用域 / 类型转换 |
apply | this | 对象本身 | ✅ | 对象初始化 / 配置(Builder 风格) |
run | this | Lambda 结果 | ✅ | 对象配置 + 计算返回值 |
also | it | 对象本身 | ✅ | 附加操作(日志、副作用) |
with | this | Lambda 结果 | ❌(非扩展) | 对同一对象连续调用多个方法 |
# 9.2 选型决策树
你需要做什么?
基于结果做事?──yes──▶ 非空对象?──yes──▶ let
│
└─ 任意对象 ──▶ also
配置/初始化对象?──yes──▶ 需要返回值?──yes──▶ run
│
└─ 返回对象本身 ──▶ apply
对同一对象多个操作?──yes──▶ with
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 9.3 典型场景示例
// ✅ let:非空执行
user?.let { showProfile(it) }
// ✅ let:限定作用域 + 类型转换
val name = (obj as? User)?.let { it.firstName + " " + it.lastName }
// ✅ apply:对象配置
val dialog = AlertDialog.Builder(context).apply {
setTitle("删除确认")
setMessage("确定要删除这条记录吗?")
setPositiveButton("确定") { _, _ -> onConfirm() }
setNegativeButton("取消", null)
}.create()
// ✅ run:对象配置 + 返回计算结果
val validationResult = input.run {
validate() // 调用 input 的方法
toFormattedString() // 最后一行是返回值
}
// ✅ also:附加操作(不影响主流程)
val user = createUser().also {
log.info("User created: ${it.id}")
analytics.track("user_created", mapOf("id" to it.id))
}
// ✅ with:对同一对象的多个调用
with(fontMetrics) {
val lineHeight = ascent + descent
val totalHeight = lineHeight + leading
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 9.4 嵌套与链式规则
// ❌ 不要嵌套作用域函数 —— 它是可读性杀手
user?.let { u ->
repository.save(u).also { saved ->
cache.put(saved.id, saved).apply {
log.info("Cached: ${this.id}") // this 是缓存对象还是 saved?
}
}
}
// ✅ 拆分为小函数或分步执行
user?.let { u ->
val saved = repository.save(u)
cache.put(saved.id, saved)
log.info("Cached: ${saved.id}")
}
// ✅ 链式调用不要超过 2 层作用域函数
val result = data
.filter { it.isValid }
.map { it.transform() }
.apply { log.info("Processed ${size} items") } // ✅ 一层 apply,清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 10.协程规范
# 10.1 结构化并发 【必须】
// ✅ 用 viewModelScope / lifecycleScope(自动随生命周期取消)
class MyViewModel : ViewModel() {
fun loadData() {
viewModelScope.launch { // 当 ViewModel cleared 时自动取消
val data = repository.fetchData()
_state.value = UiState.Success(data)
}
}
}
// ✅ coroutineScope:并行分解任务(一个失败全部取消)
suspend fun fetchAll(): List<Data> = coroutineScope {
val deferredA = async { fetchA() }
val deferredB = async { fetchB() }
deferredA.await() + deferredB.await()
}
// ✅ supervisorScope:子协程独立容错
suspend fun fetchAllSafe(): List<Data> = supervisorScope {
val deferredA = async { fetchA() } // A 失败不影响 B
val deferredB = async { fetchB() }
listOf(
runCatching { deferredA.await() }.getOrNull(),
runCatching { deferredB.await() }.getOrNull(),
).filterNotNull()
}
// ❌ 禁止使用 GlobalScope(生命周期不受控)
// GlobalScope.launch { ... } // 除非你明确知道要全局运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 10.2 调度器选择 【必须】
// ✅ 调度器使用指南
// Dispatchers.Main → UI 更新(Android/Compose)
// Dispatchers.IO → 网络、文件、数据库
// Dispatchers.Default → CPU 密集型(排序、解析、加解密)
// Dispatchers.Unconfined → 几乎不用
class UserRepository(private val api: UserApi, private val db: UserDao) {
suspend fun getUser(id: Long): User {
return withContext(Dispatchers.IO) {
// ① 先查缓存
db.getUser(id)?.let { return@withContext it }
// ② 网络请求
api.fetchUser(id).also { db.insertUser(it) }
}
}
}
// ❌ 在 suspend 函数中做 IO 操作不切换调度器
// 默认运行在调用方的调度器上——可能意外阻塞 Main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 10.3 异常处理 【必须】
// ✅ launch → 异常向上传播(通过 CoroutineExceptionHandler 捕获)
val handler = CoroutineExceptionHandler { _, e ->
log.error("Unhandled coroutine exception", e)
}
scope.launch(handler) {
riskyOperation() // 异常由 handler 处理
}
// ✅ async → 异常在 await() 处抛出
scope.launch {
val deferred = async { fetchData() }
try {
val data = deferred.await()
} catch (e: IOException) {
_error.value = "Network error: ${e.message}"
}
}
// ✅ runCatching(协程友好)
viewModelScope.launch {
runCatching { repository.fetchData() }
.onSuccess { _state.value = it }
.onFailure { _error.value = it.message }
}
// ❌ 不要让异常静默丢失
// try { ... } catch (e: Exception) {} // ← 空的 catch,异常被吞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 10.4 取消与资源释放 【推荐】
// ✅ 协程取消是协作式的:检查 isActive 或调用挂起函数
suspend fun longRunningWork() {
for (i in 0..1000) {
ensureActive() // 检查取消
doStep(i)
}
}
// ✅ 用 finally 释放资源
scope.launch {
val conn = connect()
try {
conn.send(data)
} finally {
conn.close() // 无论如何都会释放
}
}
// ✅ 不可取消的清理代码:withContext(NonCancellable)
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup() // 清理必须执行,即使已被取消
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 10.5 Flow 规范 【推荐】
// ✅ Flow 用于冷数据流
fun observeUser(id: Long): Flow<User> = flow {
while (true) {
emit(repository.getUser(id))
delay(5_000) // 每 5 秒轮询
}
}.flowOn(Dispatchers.IO) // 上游在 IO 执行
.catch { e -> emit(User.EMPTY) } // 异常处理
.distinctUntilChanged() // 去重
// ✅ StateFlow:状态持有(热流)
private val _state = MutableStateFlow(UiState.Loading)
val state: StateFlow<UiState> = _state.asStateFlow() // 只读暴露
// ✅ SharedFlow:事件总线
private val _events = MutableSharedFlow<Event>()
val events: SharedFlow<Event> = _events.asSharedFlow()
// ❌ 不要用 Channel 做事件总线 → 用 SharedFlow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 11.集合与序列
# 11.1 不可变优先 【必须】
// ✅ 默认使用只读集合接口
fun getUsers(): List<User> // 只读视图
fun getConfig(): Map<String, String>
// ✅ 需要可变时显式声明
val mutableItems = mutableListOf<String>()
// ✅ 赋值时用 val,内容可变用 MutableList
private val _items = mutableListOf<Item>() // 内部可变
val items: List<Item> get() = _items // 外部只读
// ❌ 不要用 var + 不可变集合来"模拟"可变集合
// var list = listOf(1, 2); list += 3 → 每次都是新对象
// ✅ val list = mutableListOf(1, 2); list += 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 11.2 函数式链式调用
// ✅ 链式调用:. 开头,每个操作一行
val activeUserNames = users
.filter { it.isActive }
.sortedByDescending { it.lastLogin }
.map { it.name }
.take(10)
// ✅ 空安全组合
val firstEmail = users
.mapNotNull { it.email } // 过滤 null + 类型转换
.firstOrNull()
// ✅ 条件构建:用 buildList/buildMap
val list = buildList {
add("header")
if (condition) add("optional")
addAll(loadDynamicItems())
}
// ❌ 频繁修改用 forEach + mutableList(语义不清)
// users.forEach { if (it.isActive) result.add(it.name) }
// ✅ 用 filter + map 更声明式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 11.3 Sequence 序列 【可选】
// ✅ 大数据量链式操作:用 asSequence 惰性求值
val result = largeList.asSequence() // 10w+ 元素
.filter { it.isValid } // 步骤①
.map { it.transform() } // 步骤②
.take(20) // 步骤③:只处理前 20 条
.toList()
// Sequence:逐元素走完整个链条 ①→②→③,找到 20 个后停止
// List: ① 生成完整中间集合 → ② 再生成完整集合 → ③ 取 20
// ⚠ 小数据量(<1000)直接用 List,Sequence 有开销
// ❌ 对 Sequence 多次迭代 → 每次都会重新执行链条
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 11.4 分组与聚合
// ✅ groupBy:按条件分组
val usersByRole: Map<Role, List<User>> = users.groupBy { it.role }
// ✅ groupingBy + eachCount(省去中间 map)
val roleCounts = users.groupingBy { it.role }.eachCount()
// ✅ 聚合:sumOf, maxOf, minOf, average
val totalScore = users.sumOf { it.score }
val oldest = users.maxByOrNull { it.age }
// ✅ 分区:partition(满足/不满足)
val (active, inactive) = users.partition { it.isActive }
// ✅ 窗口操作(Kotlin 1.2+)
val sliding = list.windowed(size = 3, step = 1) // 滑动窗口
val chunked = list.chunked(3) // 分块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 12.现代Kotlin特性
# 12.1 类型别名(typealias)【可选】
// ✅ 简化复杂类型签名
typealias UserHandler = (User) -> Unit
typealias UserMap = Map<Long, User>
// ✅ 语义化基础类型
typealias UserId = Long // 文档即类型
fun getUser(userId: UserId): User // 比 fun getUser(userId: Long): User 更清晰
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 12.2 委托属性 【推荐】
// ✅ lazy:延迟初始化(线程安全)
private val repository by lazy { UserRepository(api) }
// ✅ observable:属性变化监听
var name: String by Delegates.observable("<init>") { _, old, new ->
println("name changed: $old → $new")
}
// ✅ vetoable:属性变化前可拦截
var age: Int by Delegates.vetoable(0) { _, _, new ->
new in 0..150 // false 则拒绝修改
}
// ✅ map 委托:从 Map 中取值(配置/JSON 反序列化友好)
class UserConfig(map: Map<String, Any>) {
val name: String by map
val age: Int by map
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 12.3 内联类与值类 【推荐】
// ✅ Kotlin 1.5+ @JvmInline value class:零开销包装
@JvmInline
value class UserId(val value: Long)
@JvmInline
value class Password(val value: String) {
init { require(value.length >= 8) }
}
// ✅ 类型安全:不同语义的类型不能直接混用
fun authenticate(userId: UserId, password: Password)
// authenticate(Password("12345678"), UserId(1L)) // ❌ 编译错误!
// ✅ 可以有接口和成员
@JvmInline
value class Email(val address: String) {
val domain: String get() = address.substringAfter("@")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 12.4 内联函数与 reified 【推荐】
// ✅ inline + reified:运行时访问泛型类型信息
inline fun <reified T> Gson.fromJson(json: String): T =
fromJson(json, T::class.java)
// 调用
val user: User = gson.fromJson(jsonString) // 无需传 Class
// ✅ inline 用于高阶函数(减少函数对象分配)
inline fun measureTime(block: () -> Unit): Long {
val start = System.nanoTime()
block()
return System.nanoTime() - start
}
// ❌ 非高阶函数不要 inline(无收益,反而增大字节码)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 12.5 契约 Contracts 【可选】
// ✅ contracts 帮助编译器推导类型
@OptIn(ExperimentalContracts::class)
fun require(condition: Boolean) {
contract { returns() implies condition } // condition 为 true 才能返回
if (!condition) throw IllegalArgumentException()
}
fun test(s: String?) {
require(s != null)
println(s.length) // 编译器知道 s 非空!
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 13.工具链与自动化
# 13.1 ktlint格式化
// ktlint:Kotlin 官方 linter + formatter,零配置
// 安装:brew install ktlint
// 使用:
// ktlint --format → 格式化
// ktlint → 仅检查
// .editorconfig(ktlint 读取的配置)
// [*.kt]
// indent_size = 4
// max_line_length = 120
// insert_final_newline = true
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 13.2 detekt静态分析
# detekt:Kotlin 静态代码分析(复杂度、坏味道、潜在 bug)
# detekt-config.yml
style:
MagicNumber:
active: true
ignoreNumbers: ['-1', '0', '1', '2']
WildcardImport:
active: true
complexity:
TooManyFunctions:
active: true
thresholdInFiles: 15
coroutines:
GlobalCoroutineUsage:
active: true # 检测 GlobalScope 使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 13.3 CI 集成
# GitHub Actions 示例
- name: Kotlin Code Quality
run: |
./gradlew ktlintCheck # 格式检查
./gradlew detekt # 静态分析
./gradlew lint # Android Lint
./gradlew test # 单元测试
1
2
3
4
5
6
7
2
3
4
5
6
7
# 13.4 pre-commit 钩子 【推荐】
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pinterest/ktlint
rev: 1.2.1
hooks:
- id: ktlint
1
2
3
4
5
6
2
3
4
5
6
# 安装
pip install pre-commit
pre-commit install # 每次 commit 前自动检查
pre-commit run --all-files # 手动全量检查
1
2
3
4
2
3
4
# 14.常见反模式
| 反模式 | 问题 | 改进 |
|---|---|---|
!! 断言 | 隐藏 NPE,crash 时无上下文 | 安全调用 ?. / Elvis ?: / requireNotNull() |
GlobalScope | 生命周期不受控,进程级泄漏 | viewModelScope / lifecycleScope / coroutineScope |
data class 默认值过多(>5) | 构造复杂,copy 语义模糊 | Builder 模式或拆分 |
过度使用 var | 可变状态难跟踪 | 优先 val,确需可变用 MutableStateFlow |
| 嵌套作用域函数 | 可读性灾难 | 最多一层,超过则拆分函数 |
it 含义不明 | list.filter { }.map { it.name } 哪个 it? | 嵌套/链式 >1 层时显式命名 |
| 忽略协程异常 | launch { ... } 无 handler,异常被静默丢弃 | 加 CoroutineExceptionHandler 或 supervisorScope |
用 MutableList 对外暴露 | fun getItems(): MutableList<T> 外部可修改 | 返回 List<T> 或 toList() |
| Java 互操作忽略可空 | val s: String = javaMethod() 可能 NPE | 显式标记 String? 或做 null 检查 |
# 15.代码审查清单
每次 Code Review 时,按以下清单逐项检查:
## 命名
- [ ] 类大驼峰,函数/属性小驼峰,常量全大写
- [ ] 无拼音、单字母、含义模糊的命名
- [ ] 布尔返回值函数/属性用 is/has 前缀
- [ ] 测试函数用反引号 + 空格
## 空安全
- [ ] 无 !! 断言(除非有充分理由且注释说明)
- [ ] 可空类型用 ?. 或 ?: 安全处理
- [ ] lateinit/lazy 使用合理
- [ ] Java 互操作返回值显式声明可空性
## 协程
- [ ] 用结构化并发(viewModelScope / lifecycleScope),不用 GlobalScope
- [ ] IO 操作在 Dispatchers.IO
- [ ] 异常有正确捕获(CoroutineExceptionHandler 或 runCatching)
- [ ] 无协程泄漏(launch 在合适作用域)
## 类设计
- [ ] 优先 val 而非 var
- [ ] 数据类用 data class
- [ ] 密封类限定子类(sealed class / sealed interface)
- [ ] 组合优先继承
- [ ] 类成员可见性合理(非 public 尽量 private/internal)
## 函数
- [ ] 单函数不超过 40 行
- [ ] 卫语句减少嵌套
- [ ] 扩展函数语义清晰,无滥用
- [ ] Lambda 中 it 含义明确(>1 层嵌套时显式命名)
## 作用域函数
- [ ] 无嵌套作用域函数(>1 层)
- [ ] 选型正确(apply 配置 / let 非空 / also 副作用)
- [ ] 链式调用不超过 2 层
## 集合
- [ ] 优先只读接口(List / Set / Map)
- [ ] 大数据量链式操作考虑 Sequence
- [ ] 对外暴露不带可变性
## 测试
- [ ] 核心逻辑有单元测试
- [ ] 正常和异常路径都覆盖
- [ ] 协程测试用 runTest / StandardTestDispatcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 16.常见陷阱速查
以下陷阱即使在有经验的开发者中也常见,值得定期回顾。
# 16.1 空安全陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | 平台类型 NPE | val s: String = javaMethod() Java 返回 null 直接 NPE | 显式标记 String? 或马上判断 |
| 2 | !!.let 无意义 | user!!.let { } 还是抛 NPE | user?.let { } |
| 3 | lateinit 未初始化 | 忘记检查 isInitialized | 尽量用构造注入或 lazy |
| 4 | requireNotNull 丢信息 | 抛异常但无业务上下文 | 带上消息:requireNotNull(x) { "id required" } |
# 16.2 协程陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | launch 异常丢失 | 无 handler,异常被静默丢弃 | CoroutineExceptionHandler / supervisorScope |
| 2 | async 未 await | 异常在 deferred 中,不调用 await 永远不会抛出 | runCatching { deferred.await() } |
| 3 | 子协程泄漏 | 父协程取消但子协程未取消 | 用 coroutineScope 保证结构化并发 |
| 4 | withContext 意外嵌套 | withContext(IO) { withContext(Main) { ... } } 多余切换 | 只在外层切换,内部不重复 wrap |
| 5 | runBlocking 在主线程 | Android 上阻塞主线程 → ANR | 测试用 runTest,业务代码禁止 runBlocking |
# 16.3 集合陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | listOf 是只读视图 | 如果原 list 可变,listOf 看到的是快照吗?不,是实时视图 | 需要快照用 toList() |
| 2 | map{} 后用 filter{} → 多余分配 | 先生成完整中间集合再过滤 | 颠倒顺序:先 filter{} 再 map{} |
| 3 | forEach + return | return 是返回到外层函数,不是跳出循环 | 用 forEach + return@forEach 或直接 for |
| 4 | + 操作符产生新集合 | val list = listOf(1); list += 2 是 O(n) 新对象 | 频繁修改用 mutableListOf |
# 16.4 类型与泛型陷阱
| # | 陷阱 | 描述 | 正解 |
|---|---|---|---|
| 1 | data class 的 copy() 浅拷贝 | 嵌套可变对象被共享 | 重写 copy 或避免 data class 包装可变对象 |
| 2 | Nothing 的协变/逆变 | List<Nothing> 是 List<String> 的子类型吗?是 | 理解声明处型变 |
| 3 | 泛型实化只在线联 | 非 inline 函数无法获知泛型具体类型 | 用 reified + inline |
| 4 | sealed class 子类位置 | 子类必须在同一个文件或同一个 package(Kotlin 1.9 已放宽) | 了解版本差异 |
本文档将随 Kotlin 语言演进持续更新,欢迎通过 GitHub issues (opens new window) 反馈问题和建议。
上次更新: 2026/06/17, 11:39:29