Skip to content

错误处理 Result

源:Kotlin standard library - Result

Result<T> 是 Kotlin 标准库提供的一个内联类(Inline Class),用于封装一个可能成功或失败的操作结果。它提供了一种函数式的错误处理风格,旨在取代传统的、且容易导致代码流中断的 try-catch 块。

核心理念

Result 的设计初衷是将“异常”视为一种“值”,通过链式调用来处理成功或失败的分支,使代码逻辑更加线性、平滑。

基础用法

runCatching

这是构建 Result 对象的入口。它会执行代码块,如果成功则封装返回值,如果抛出异常则捕获并封装异常。

kotlin
fun readConfig(path: String): Result<String> = runCatching {
    // 这里的代码可能会抛出 IOException
    if (path.isEmpty()) throw IllegalArgumentException("Path is empty")
    "content"
}

获取结果

你可以根据业务需求选择不同的方式来“解包” Result。

kotlin
// 成功返回 T,失败返回 null
val content: String? = result.getOrNull()
kotlin
// 失败时返回默认值
val content = result.getOrDefault("default config")
kotlin
// 失败时执行 Lambda 计算默认值
val content = result.getOrElse { e -> 
    logger.warn("Read failed: $e")
    "fallback config" 
}
kotlin
// 成功返回 T,失败重新抛出异常
val content = result.getOrThrow()

链式操作 (Functional Chaining)

Result 最大的威力在于支持类似集合的操作符,允许你构建处理流水线。

变换 (map / mapCatching)

kotlin
val result = runCatching { fetchData() }
    .map { data -> 
        // 仅在成功时执行,将 T 转换为 R
        parseData(data) 
    }
    .mapCatching { parsed ->
        // 如果转换过程也可能抛出异常,使用 mapCatching
        validate(parsed)
    }

恢复 (recover / recoverCatching)

类似 try-catch 中的 catch 块,但在流中处理。

kotlin
val finalResult = operation()
    .recover { e ->
        // 捕获异常,尝试恢复或返回备用值
        if (e is TimeoutException) retry() else throw e
    }

副作用 (onSuccess / onFailure)

用于执行日志记录、UI 更新等不改变结果值的操作。

kotlin
remoteCall()
    .onSuccess { response -> 
        println("Success: $response") 
        saveToCache(response)
    }
    .onFailure { exception -> 
        println("Error: $exception") 
        reportError(exception)
    }

进阶:折叠 (Fold)

当你需要将 Result 最终转换为单一值(无论成功还是失败)时,fold 是最通用的操作符。这在 UI 状态映射中非常有用。

kotlin
val uiState = repository.getData()
    .fold(
        onSuccess = { data -> UiState.Content(data) },
        onFailure = { error -> UiState.Error(error.message) }
    )

原理与性能

Result 是一个 Inline Class (Value Class)

kotlin
@JvmInline
value class Result<out T>(internal val value: Any?) : Serializable

这意味着在运行时,Result 对象通常不会被分配堆内存,而是直接以内联的方式传递(除非被装箱)。这使得它极其轻量高效。

关键限制与最佳实践

返回类型限制

由于 JVM 的签名冲突问题,Kotlin 编译器曾在很长一段时间内禁止将 Result 作为函数的直接返回类型(除非开启特定编译器参数)。虽然现在放宽了限制,但在公共 API 设计中仍需谨慎。

  1. 边界使用:推荐仅在系统的“不安全边界”(如 IO 操作、网络请求、第三方库调用)使用 runCatching 将异常转换为 Result
  2. 避免过度包装:不要在核心业务逻辑中层层传递 Result。一旦解包并处理了错误,就应该恢复为领域模型或抛出业务异常。
  3. 协程异常:在协程中,异常通常用于结构化并发的取消机制。虽然 runCatching 可以捕获协程中的异常,但必须小心不要捕获了 CancellationException,否则可能导致协程无法被正确取消。
kotlin
// ⚠️ 协程中使用的注意事项
runCatching {
    suspendingCall()
}.onFailure { e ->
    if (e is CancellationException) throw e // 必须重新抛出!
    handleError(e)
}