5.7.7 未使用的返回值检查器

原文链接: https://kotlinlang.org/docs/unused-return-value-checker.html

5.7.7 未使用的返回值检查器

实验性-通用

注意: 此特性计划在未来的 Kotlin 版本中稳定并改进。欢迎你在我们的问题跟踪器 YouTrack 中提供反馈。更多信息请参阅相关的 KEEP 提案。

未使用的返回值检查器可以检测被忽略的结果。这些是从表达式返回、产生了 Unit、Nothing 或 Nothing? 之外的值,并且没有被:

  • 存入变量或属性。
  • 返回或抛出。
  • 作为实参传给其他函数。
  • 用作调用或安全调用中的接收者。
  • 在 if、when、while 等条件中检查。
  • 用作 lambda 的最后一条语句。

该检查器不会对 ++ 和 -- 这类自增运算报告被忽略的结果,也不会对右侧会退出当前函数的布尔短路(例如 condition || return)报告。

你可以用未使用的返回值检查器来发现这样的缺陷:函数调用产生了有意义的结果,但结果被默默丢弃了。这有助于避免意外行为,也让这类问题更容易排查。

下面的例子创建了一个字符串却从未使用,因此检查器会把它报告为被忽略的结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun formatGreeting(name: String): String {
    if (name.isBlank()) return "Hello, anonymous user!"
    if (!name.contains(' ')) {
        // 检查器会警告该结果被忽略:
        // "未使用的 'plus' 返回值。"
        "Hello, " + name.replaceFirstChar(Char::titlecase) + "!"
    }
    val (first, last) = name.split(' ')
    return "Hello, $first! Or should I call you Dr. $last?"
}

配置未使用的返回值检查器

你可以用 -Xreturn-value-checker 编译器选项控制编译器如何报告被忽略的结果。

它有下面几种模式:

  • disable 禁用未使用的返回值检查器(默认)。
  • check 启用检查器,并对来自被标记函数的被忽略结果报告警告。
  • full 启用检查器,把项目中的所有函数都视为已标记,并对被忽略的结果报告警告。

注意: 所有被标记的函数都会按此传播,因此在使用你的代码作为依赖的项目中,只要启用了检查器,被忽略的结果就会被报告。

要在你的项目中使用未使用的返回值检查器,请把该编译器选项添加到构建配置文件中:

Gradle

1
2
3
4
5
6
// build.gradle(.kts)
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xreturn-value-checker=check")
    }
}

Maven

 1
 2
 3
 4
 5
 6
 7
 8
 9
10

<plugin>
    <groupId>org.jetbrains.kotlin</groupId>
    ..
    <configuration>
        <args>
            <arg>-Xreturn-value-checker=check</arg>
        </args>
    </configuration>
</plugin>

标记函数以检查被忽略的结果

当你把 -Xreturn-value-checker 编译器选项设置为 check 时,检查器只会报告来自已标记表达式的被忽略结果,就像 Kotlin 标准库中的大多数函数那样。

要标记你自己的代码,请使用 @MustUseReturnValues 注解。你可以按希望检查器覆盖的范围,把它应用于文件、类或函数。

例如,你可以标记整个文件:

1
2
3
4
5
6
// 标记此文件中的所有函数和类,让检查器报告未使用的返回值
@file:MustUseReturnValues

package my.project

fun someFunction(): String

或者标记某个特定的类:

1
2
3
4
5
6
7
// 标记此类中的所有函数,让检查器报告未使用的返回值
@MustUseReturnValues
class Greeter {
    fun greet(name: String): String = "Hello, $name"
}

fun someFunction(): Int = ...

注意: 你可以把 -Xreturn-value-checker 编译器选项设置为 full,从而对整个项目应用该检查器。使用这个选项时,你不必用 @MustUseReturnValues 标注代码。

抑制对忽略结果的报告

你可以用 @IgnorableReturnValue 注解来抑制对特定函数的报告。对于那些忽略结果很常见也很正常的函数(例如 MutableList.add),可以加上该注解:

1
2
3
4
@IgnorableReturnValue
fun <T> MutableList<T>.addAndIgnoreResult(element: T): Boolean {
    return add(element)
}

你也可以不注解函数本身而抑制警告。做法是用下划线语法(_)把结果赋给一个特殊的匿名变量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 不可忽略返回值的函数
fun computeValue(): Int = 42

fun main() {

    // 警告:结果被忽略
    computeValue()

    // 用一个特殊的未使用变量仅在此调用处抑制警告
    val _ = computeValue()
}

函数重写中的被忽略结果

当你重写一个函数时,重写会继承基类声明上注解所定义的报告规则。当基类声明属于 Kotlin 标准库或其他库依赖时也是如此,因此检查器会对 Any.hashCode() 这类函数的重写报告被忽略的结果。

此外,你不能用另一个要求必须使用其返回值的函数来重写被 @IgnorableReturnValue 标记的函数。不过,在带 @MustUseReturnValues 注解的类或接口中,如果某个重写的结果可以安全忽略,你可以用 @IgnorableReturnValue 标记它:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@MustUseReturnValues
interface Greeter {
    fun greet(name: String): String
}

object SilentGreeter : Greeter {
    @IgnorableReturnValue
    override fun greet(name: String): String = ""
}

fun check(g: Greeter) {
    // 警告:未使用的返回值
    g.greet("John")

    // 没有警告
    SilentGreeter.greet("John")
}

检查高阶函数中未使用的结果

有些高阶函数(例如 let 作用域函数)会返回 lambda 的结果。要检查高阶函数中未使用的 lambda 结果,请在函数的契约中添加实验性的 returnsResultOf() 契约。

警告: Kotlin 契约是实验性的。要启用它,请在声明带契约的函数时加上 @OptIn(ExperimentalContracts::class) 注解。

下面是一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

@OptIn(ExperimentalContracts::class)
inline fun <T, R> T.customLet(block: (T) -> R): R {
    contract {
        returnsResultOf(block)
    }
    return block(this)
}

然后你就可以使用带这种契约的函数(例如 .customLet())来检查 lambda 的结果是否被使用:

1
2
3
4
5
6
7
fun handleNullablePackageName(packageName: String?, builder: StringBuilder) {
    // 检查器不会警告,因为 append() 的返回值可以被忽略
    packageName?.customLet { builder.append(it) }

    // 检查器会警告,因为返回的字符串未被使用
    packageName?.customLet { "kotlin.$it" }
}

警告: returnsResultOf() 契约需要一个单独的编译器选项才能启用。请注意,使用它会产生预发布版本的二进制文件,Kotlin 2.4.0 之前的编译器版本无法读取。

要为你的项目启用它,请在构建文件中添加以下编译器选项:

Gradle

1
2
3
4
5
6
// build.gradle(.kts)
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xallow-returns-result-of")
    }
}

Maven

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14

<build>
    <plugins>
        <plugin>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-maven-plugin</artifactId>
            <configuration>
                <args>
                    <arg>-Xallow-returns-result-of</arg>
                </args>
            </configuration>
        </plugin>
    </plugins>
</build>

与 Java 注解的互操作

有些 Java 库使用类似的机制,但注解不同。未使用的返回值检查器会把以下注解视为等同于使用 @MustUseReturnValues:

它还会把 com.google.errorprone.annotations.CanIgnoreReturnValue 视为等同于使用 @IgnorableReturnValue。