Kotlin 2.5 未使用返回值检查器实战:让编译器帮你揪出那些被静默吞掉的关键结果
你有没有遇到过这种场景:代码跑得好好的,测试也通过了,但生产环境就是偶尔出现数据对不上的问题?最后排查了半天,发现罪魁祸首是一个被默默忽略的函数返回值。
来看一段看似无害的代码:
fun processOrder(order: Order) {
val cart = order.cart
// 移除库存不足的商品
cart.items.filter { it.inStock }
// 应用优惠券
cart.total.applyDiscount(order.coupon)
// 保存到数据库
repository.save(cart)
}
问题在哪?filter 不会修改原列表,它返回一个新列表;applyDiscount 如果返回的是新对象而非原地修改,那折扣根本没生效。两行代码的返回值都被静默丢弃了,但编译器一言不发。
这就是 Kotlin 2.5 稳定版的未使用返回值检查器(Unused Return Value Checker)要解决的问题。
这个检查器到底是什么
简单来说:当一个函数返回了有意义的值(非 Unit、非 Nothing),而你调用它后既没有接收返回值,也没有用于条件判断或传递给其他函数,编译器就会发出警告。
这个功能最早在 Kotlin 2.3.0 以实验性身份登场,经过两个版本的打磨,终于在 Kotlin 2.5.0 中正式稳定。
快速上手:两种启用方式
方式一:精准打击 —— 用注解标记
@MustUseReturnValues
fun calculateDiscount(price: Double, rate: Double): Double {
return price * rate
}
fun main() {
calculateDiscount(100.0, 0.8) // ⚠️ 警告:返回值未被使用
}
@MustUseReturnValues 可以标记在函数、类甚至整个文件上:
// 标记整个文件
@file:MustUseReturnValues
package com.example.orders
// 标记整个类
@MustUseReturnValues
class PriceCalculator {
fun computeTotal(items: List<Item>): Double = items.sumOf { it.price }
fun applyTax(total: Double): Double = total * 1.13
}
方式二:全面覆盖 —— 编译器参数
如果你希望项目里所有函数的返回值都被检查,可以在 build.gradle.kts 中开启 full 模式:
kotlin {
compilerOptions {
// "check" 模式:只检查带 @MustUseReturnValues 注解的
// "full" 模式:检查所有非 Unit/Nothing 返回值
freeCompilerArgs.add("-Xreturn-value-checker=full")
}
}
check 模式更温和,只检查你显式标记的函数;full 模式则一视同仁,适合对代码质量要求极高的团队。
实战场景:那些容易被忽略的返回值陷阱
场景一:集合操作的"假修改"
@MustUseReturnValues
fun demo() {
val list = mutableListOf(1, 2, 3)
list.plus(4) // ⚠️ plus 返回新列表,原列表不变
list.filter { it > 1 } // ⚠️ filter 返回新列表
list.map { it * 2 } // ⚠️ map 返回新列表
// 正确写法
val newList = list + 4
val filtered = list.filter { it > 1 }
list.add(4) // ✅ add 返回 Boolean,但 MutableList.add 默认标记为可忽略
}
这是最常见的坑。plus、filter、map 这些函数返回新集合,不会修改原集合。如果忽略了返回值,等于白操作。
场景二:字符串处理的"无效替换"
fun sanitize(input: String): String {
input.trim() // ⚠️ trim 返回新字符串
input.replace("<", "<") // ⚠️ replace 返回新字符串
input.lowercase() // ⚠️ lowercase 返回新字符串
return input // 返回的是原始未处理的字符串!
}
// 正确写法
fun sanitizeFixed(input: String): String =
input.trim().replace("<", "<").lowercase()
Kotlin 的 String 是不可变的,所有修改操作都返回新字符串。忽略返回值意味着你的处理逻辑全部失效。
场景三:Result 和 Either 的静默失败
@MustUseReturnValues
sealed class Result<out T> {
data class Success<T>(val value: T) : Result<T>()
data class Failure(val error: Throwable) : Result<Nothing>()
}
fun fetchUser(id: Long): Result<User> = TODO()
fun processUser(id: Long) {
fetchUser(id) // ⚠️ 返回值未使用,可能是一个 Failure 被静默忽略了
}
// 正确写法
fun processUserFixed(id: Long) {
when (val result = fetchUser(id)) {
is Result.Success -> println("User: ${result.value.name}")
is Result.Failure -> println("Error: ${result.error.message}")
}
}
场景四:数据库操作的返回值
@MustUseReturnValues
interface UserRepository {
fun deleteById(id: Long): Boolean // 返回是否删除成功
fun update(user: User): Int // 返回受影响的行数
}
fun removeUser(id: Long, repo: UserRepository) {
repo.deleteById(id) // ⚠️ 删除可能失败了,但你完全不知道
}
// 正确写法
fun removeUserSafe(id: Long, repo: UserRepository) {
val deleted = repo.deleteById(id)
if (!deleted) {
log.warn("User $id not found for deletion")
}
}
合理抑制警告:不是所有忽略都是 Bug
有些时候,你确实不需要返回值。Kotlin 提供了两种优雅的抑制方式:
方式一:@IgnorableReturnValue —— 函数作者说"可以忽略"
@IgnorableReturnValue
fun MutableList<T>.addIfAbsent(element: T): Boolean {
if (contains(element)) return false
return add(element)
}
fun main() {
val list = mutableListOf(1, 2, 3)
list.addIfAbsent(4) // ✅ 无警告,作者已标记为可忽略
}
方式二:val _ = ... —— 调用者说"我知道,我故意的"
fun computeExpensiveResult(): Result = TODO()
fun main() {
computeExpensiveResult() // ⚠️ 警告
val _ = computeExpensiveResult() // ✅ 显式丢弃,无警告
}
val _ 是 Kotlin 的"未命名变量"语法,表示"我明确知道有返回值,但我不需要它"。这比直接忽略多了一层意图表达,代码审查时一目了然。
在团队中渐进式推广
直接开启 full 模式可能会让老项目瞬间冒出几百个警告。推荐分三步走:
第一步:只标记高风险函数
@MustUseReturnValues
fun validatePayment(payment: Payment): ValidationResult = TODO()
@MustUseReturnValues
fun verifyPermission(user: User, action: Action): Boolean = TODO()
从业务关键函数开始,比如支付验证、权限检查、数据校验等,这些地方忽略返回值往往代价最高。
第二步:标记整个模块或类
@file:MustUseReturnValues
package com.example.finance
// 财务模块里所有函数的返回值都值得被检查
第三步:全量开启 full 模式
当团队习惯了这种检查节奏,再在 CI 中启用 full 模式,把防线拉到最满。
与其他 JVM 语言的对比
| 特性 | Kotlin 2.5 | Java | Scala |
|---|---|---|---|
| 返回值检查 | @MustUseReturnValues | @CheckReturnValue(Error Prone) | @nowarn + 自定义 |
| 抑制方式 | val _ = expr | var _ = expr | val _ = expr |
| 全量模式 | -Xreturn-value-checker=full | 需第三方工具 | 需编译器插件 |
| 标准库集成 | 内置 | 需 Error Prone | 部分内置 |
Kotlin 的优势在于语言级别原生支持,不需要依赖第三方工具,且 val _ 语法比 Java 的 var _ 更简洁(因为 Kotlin 的 _ 是语言特性,不是变量名)。
总结
Kotlin 2.5 稳定的未使用返回值检查器,本质上是把一类常见的运行时 Bug 提前到了编译期。它不会改变你的代码逻辑,但会在你差点犯错时拍一下肩膀:
@MustUseReturnValues—— 标记那些"返回值必须被处理"的函数-Xreturn-value-checker=full—— 全量检查模式,适合严格团队@IgnorableReturnValue—— 告诉检查器"这个可以忽略"val _ = expr—— 调用侧显式丢弃,意图清晰
下次你写出 list.filter { ... } 然后忘记接收结果时,编译器会第一时间提醒你。这小小的警告,可能帮你省掉一个深夜排查 Bug 的夜晚。