3-21.【函数式编程】比较 try/catch 与 Result<T, Error> 在函数式设计中的差异与使用场景。

7 阅读2分钟

1️⃣ 核心区别

特性try/catchResult<T, Error>
错误处理方式抛出异常,由调用方捕获错误作为值封装在枚举中,沿函数链传递
函数签名通过 throws 声明返回类型 Result<Success, Failure>
函数式组合不方便链式组合,需要嵌套 do/catch支持 map, flatMap, mapError 链式组合
纯函数兼容性异常属于副作用,不是纯函数函数返回值包含错误,保持纯函数特性
错误可控性异常随时抛出,可能中断流程错误显式传递,可选择继续或停止流程
并发安全异常中断线程上下文,需小心可安全在并发流水线中传递,无副作用

2️⃣ 使用对比示例

假设我们有一个字符串转 Int,并进行加法操作。

(1) try/catch 风格

enum ParseError: Error {
    case invalidNumber
}

func parseIntThrow(_ str: String) throws -> Int {
    guard let n = Int(str) else { throw ParseError.invalidNumber }
    return n
}

do {
    let value = try parseIntThrow("123")
    let result = value + 10
    print("Result:", result)
} catch {
    print("Error:", error)
}
  • 优点:语法直观

  • 缺点:

    • 不方便函数组合
    • 链式调用时需要多层 do/catch
    • 异常中断流程 → 不适合函数式流水线

(2) Result<T, Error> 风格

func parseIntResult(_ str: String) -> Result<Int, ParseError> {
    if let n = Int(str) {
        return .success(n)
    } else {
        return .failure(.invalidNumber)
    }
}

// 链式处理
let result = parseIntResult("123")
    .map { $0 + 10 }  // 成功则加 10

switch result {
case .success(let value):
    print("Result:", value)
case .failure(let error):
    print("Error:", error)
}
  • 优点:

    • 链式组合自然 (map, flatMap)
    • 错误显式传递 → 函数式友好
    • 核心逻辑保持纯函数 → 易测试、易复用
  • 缺点:

    • 需要显式处理错误值
    • 对简单抛异常场景稍显冗长

3️⃣ 使用场景建议

场景建议
简单函数调用,错误可直接中断try/catch 更直观
函数式流水线 / 链式组合Result 更适合
并发处理 / 数据流水线Result 可安全传递,不中断线程
纯函数式设计 / 测试驱动Result 优先,保持核心逻辑纯净
多层调用,错误需要统一处理Result 可通过 mapError 统一映射

4️⃣ 核心总结

  1. try/catch

    • 更适合命令式编程
    • 异常中断流程 → 易写、直观
    • 不利于函数式组合
  2. Result<T, Error>

    • 错误显式传递 → 保持函数式纯净
    • 支持链式组合 → map, flatMap, mapError
    • 天然并发安全,可组合数据流水线

⚡ 经验法则:函数式设计中,优先使用 Result 来处理可能失败的操作,核心逻辑保持纯函数;将 try/catch 仅用于外层副作用或必须中断的场景。