每个Kotlin开发者应该掌握的最佳实践,第二趴

上一篇文章中,我们介绍了 Kotlin 的一些基础最佳实践,如不可变性、空安全、数据类、协程和密封类。 现在,让我们更进一步。

本文中,我们将探讨 Kotlin 的高级最佳实践,这些实践有助于你为生产级应用编写更简洁、更安全、更高效的代码。

依然,声明一下本文的主旨:本文并非一份系统性的编程教程,而是我多年来编写 Kotlin 代码的一些经验总结。可能你和我会有不同的感受,毕竟每个人踩过的坑不一样,走的路也不尽相同,权当是一位 Kotlin 开发者的心得闲谈吧。

内联高阶函数

高阶函数可能会带来额外开销。当你将函数作为参数传递时,使用 inline 关键字,以避免不必要的对象创建并提高性能。

Kotlin 复制代码
inline fun measureTime(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("Took ${System.currentTimeMillis() - start} ms")
}

不仅如此,这种技巧对于日志打印也尤为重要:

Kotlin 复制代码
inline fun logD(tag: String, content: () -> String) {
    if (Debug) {
        println("$tag: ${content()}")
    }
}

logD("Main") { "User clicked $bird" }

传统的日志打印方式,无论日志是否最终输出,都会产生字符串拼接的开销。

如果采用内联高阶函数的方式,不仅省去了拼接字符串的开销,还能省去高阶函数的额外开销。

善用类型别名

如果函数/类签名冗长且重复,使用类型别名来简化它们:

Kotlin 复制代码
typealias Callback = (Boolean, String?) -> Unit // 简化冗长的 lambda 表达式
fun loginUser(username: String, password: String, callback: Callback) {
    // ...
}

typealias UserOrderMap = Map<String, OrderList> // 简化冗长的泛型集合

这会使你的 API 更加简洁,更易于阅读。

合理使用 const 和 @JvmField

  • 对于编译时常量,使用 const val
  • 当 Java 互操作需要字段访问时,在伴生对象中使用 @JvmField
Kotlin 复制代码
class ApiConfig {
    companion object {
        const val TIMEOUT = 30_000
    }
}

class Blog {
    @JvmField
    val id: Int = 123
}

在 Java 中访问时:

Java 复制代码
System.out.println(ApiConfig.TIMEOUT);
Blog blog = new Blog();
System.out.println(blog.id);

而如果没有加 @JvmField 的话,在 Java 中访问是这样的:

Java 复制代码
// ...
System.out.println(blog.getId());

利用默认参数和命名参数

不要创建多个重载方法,而是使用默认参数:

Kotlin 复制代码
fun connect(host: String, port: Int = 8080, useSSL: Boolean = false) { ... }
connect(host = "example.com")               // 默认 port + no SSL
connect(host = "example.com", useSSL = true) // named argument improves clarity

在 Kotlin 中,非常建议开发者在进行函数调用的时候,加上参数的名字,这在对于函数的参数列表特别长的时候,有助于提高代码的可读性。

试想一下下面这个 Compose 函数(以下为 Jetpack Compose 中的一个典型复杂函数签名,用于演示命名参数的重要性):

Kotlin 复制代码
@Composable
fun BasicTextField(
    state: TextFieldState,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    readOnly: Boolean = false,
    inputTransformation: InputTransformation? = null,
    textStyle: TextStyle = TextStyle.Default,
    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
    onKeyboardAction: KeyboardActionHandler? = null,
    lineLimits: TextFieldLineLimits = TextFieldLineLimits.Default,
    onTextLayout: (Density.(getResult: () -> TextLayoutResult?) -> Unit)? = null,
    interactionSource: MutableInteractionSource? = null,
    cursorBrush: Brush = BasicTextFieldDefaults.CursorBrush,
    outputTransformation: OutputTransformation? = null,
    decorator: TextFieldDecorator? = null,
    scrollState: ScrollState = rememberScrollState(),
)

如果在调用的时候不加参数的名字,那么几乎每次看到这个函数调用,你都要跳转到文档你才能知道具体的功能。

慎用协程的全局作用域

⚠️ 谨慎做法:

Kotlin 复制代码
GlobalScope.launch { fetchData() }

✅ 推荐做法:

Kotlin 复制代码
viewModelScope.launch { fetchData() }

通过 viewModelScopelifecycleScope 或自定义的 CoroutineScope 使用结构化并发,可确保生命周期管理协程并防止泄漏。

优先使用不可变集合

默认情况下使用 listOfmapOfsetOf 来实现不可变性。仅在需要进行修改时才切换到 mutableListOf

Kotlin 复制代码
val numbers = listOf(1, 2, 3) // immutable
val mutableNumbers = mutableListOf(1, 2, 3) // modifiable

这个思路和优先使用 val 是一样的。不可变集合可提高并发环境中的安全性,同时也能降低阅读代码的难度。

使用 Result 类型进行错误处理

对比返回 null 或不必要地抛出异常,使用 Kotlin 内置的 Result 类型会让代码更加稳定:

Kotlin 复制代码
fun parseInt(str: String): Result<Int> {
    return runCatching { str.toInt() }
}
val number = parseInt("123").getOrElse { 0 } // 发生错误的回退

这使得错误处理更加明确,同时不会因为忘记 try-catch 导致系统崩溃。

这篇文章讨论了 try-catchrunCatching,能够帮助大家更好的理解 Kotlin 中的异常处理。

⚠️ 注意:在协程中使用 runCatching 时,若捕获到 CancellationException,务必重新抛出,否则协程将无法被正常取消,可能导致"僵尸协程"。

reified 泛型函数

Kotlin 支持 inline + reified 泛型,让你在运行时访问类型信息:

Kotlin 复制代码
inline fun <reified T> Gson.fromJson(json: String): T =
    this.fromJson(json, T::class.java)
// Usage
val user: User = gson.fromJson(jsonString)

比手动传递 Class<T> 更简洁、更安全。

有效使用智能类型转换

不要进行显式类型转换,而是依靠 Kotlin 通过 is 检查实现的智能类型转换:

Kotlin 复制代码
fun printLength(obj: Any) {
    if (obj is String) {
        println(obj.length) // 智能类型转换为 String
    }
}

函数式编程的惯用手法

Kotlin 通过 mapfilterflatMapreduce 等函数鼓励使用函数式编程:

Kotlin 复制代码
val evenSquares = (1..10)
    .filter { it % 2 == 0 }
    .map { it * it }

与手动循环相比,这会使代码更加简洁且富有表现力。

在集合上链式调用多个操作(如 map + filter + flatMap)时,会产生中间集合,带来额外内存开销。若操作较复杂或数据量较大,推荐使用 asSequence() 转为惰性序列,以减少中间产物,提升性能:

Kotlin 复制代码
listOf(1,2,3,4,5).asSequence().map { it * 3  }.filter {  it > 2 }.flatMap { (0..it) }.toList()

总结

Kotlin 是一门强大的语言,赋予开发者极大的灵活性。遵循这些高级最佳实践,你编写的代码将更具高性能、安全性与可维护性。

从内联函数到结果类型,再到结构化并发,这些技术将把你的 Kotlin 技能提升到新的高度。

相关推荐
alexhilton1 天前
MVI模式的完整历史、误解和现代Android范式
android·kotlin·android jetpack
pengyu2 天前
【Kotlin 协程修仙录 · 金丹境 · 后阶】 | 异常防火墙:supervisorScope 与异常隔离的终极奥义
android·kotlin
夏沫琅琊2 天前
Jetpack Compose + 原生 PDF API 从零搭一个 Android 工具应用 (一)
android·pdf·kotlin
疏狂难除2 天前
JetBrains IDE插件开发教程(五)——File Header
ide·kotlin
Kapaseker3 天前
Android CLI 完全指南
android·kotlin
Coffeeee4 天前
Android功耗优化,哪些代码会让你的机子发热又耗电
android·架构·kotlin
Android打工仔4 天前
Kotlin 编译器是如何把 suspend 函数变成状态机的?
kotlin
Android打工仔4 天前
一个 suspend 函数,为什么会变成一个普通函数?
kotlin
苦瓜花5 天前
【Kotlin】初入门
android·开发语言·kotlin
Kapaseker5 天前
Rust 是如何处理异常的
rust·kotlin