11-6.【错误处理】Result<T, Error> 与 throws 在调用方和链式操作上的区别是什么?

5 阅读3分钟

在 Swift 中,Result<T, Error>throws 都可以处理错误,但它们在思维模型代码流向上完全不同。

简单来说:Result 是“数据”,throws 是“行为”。

以下是它们在调用方和链式操作上的核心区别:


1. 调用方的语法体验:显式 vs 隐性

Result (显式处理)

Result 是一个枚举。当你调用一个返回 Result 的函数时,编译器会得到一个

  • 处理方式:你必须通过 switchif case 手动拆解。
  • 特点:强制你在当前位置面对错误,逻辑无法“自动冒泡”。

Swift

let result = fetchData()
switch result {
case .success(let data):
    print("拿到数据: (data)")
case .failure(let error):
    print("处理错误: (error)")
}

Throws (隐性处理/冒泡)

throws 函数在调用时必须加上 try

  • 处理方式:它允许你不做处理(继续向上层抛出),或者用 do-catch 统一捕获。
  • 特点:支持“短路机制”,即一行报错,后续代码自动跳过。

Swift

do {
    let data = try fetchData()
    print("拿到数据: (data)")
} catch {
    print("处理错误: (error)")
}

2. 链式操作的区别:函数式 vs 命令式

这是两者差异最明显的地方。

Result 的链式:转换流 (Transformation)

Result 依赖其内置的函数式方法,如 mapflatMap

  • 逻辑:你是在对一个“容器”进行操作。如果前一步失败,后面的 map 会被自动忽略。
  • 优势:非常适合在不同函数间传递结果(例如作为属性存储或在闭包间传递)。

Swift

let finalResult = fetchData()
    .flatMap { data in parse(data) }     // 如果 fetchData 失败,跳过 parse
    .map { model in model.id }           // 提取 ID
// 此时 finalResult 依然是一个 Result 对象

Throws 的链式:线性流 (Linear Flow)

throws 的链式操作依赖语法层的“顺序执行”。

  • 逻辑:代码看起来就像“一切顺利”的纯线性过程。
  • 优势:代码可读性极高,符合人类阅读习惯。
  • 隐患:如果中间某一步崩溃或逻辑断层,很难一眼看出错误是在哪一层产生的。

Swift

func getID() throws -> Int {
    let data = try fetchData()           // 失败则直接退出函数
    let model = try parse(data)          // 失败则直接退出函数
    return model.id
}

3. 关键特性对比表

特性Result<T, Error>Throws
本质这是一个状态值(枚举)这是一个控制流(类似跳转)
存储性可存储。可以存入数组或属性中不可存储。必须立即执行或冒泡
嵌套容易出现嵌套,除非使用 flatMap结构扁平,代码非常整洁
错误类型可以通过泛型 Result<T, MyError> 强类型化默认是 any Error(Swift 6 开始支持 Typed Throws)
性能 (成功时)略慢(需要包装成枚举)极快(几乎没有额外开销)
性能 (失败时)恒定(枚举分支切换)略慢(涉及错误包装和跳转)

4. 架构建议:如何平衡?

在防御式编程中,我们通常遵循以下准则:

  1. 内部逻辑转换用 throws:在模块内部、或是简单的业务流(A -> B -> C)中,使用 throws 可以让代码最简洁。
  2. 跨异步边界用 Result:在非 Swift Concurrency 的异步回调中,必须用 Result 传递状态。
  3. 状态保存用 Result:如果你需要记录某个操作的最后一次执行结果(例如 lastUploadResult),应使用 Result 属性。

总结一句话

如果你想写出像诗一样简洁的逻辑链路,选 throws;如果你需要精确控制数据在不同时间和空间(线程)的流转,选 Result