11-8.【错误处理】Result 的 map / flatMap 与 try? / try! / try 的组合使用场景对比。

1 阅读3分钟

在 Swift 的防御式编程中,选择 Result 的链式操作还是 try 系列关键字,本质上是在选择**“函数式数据流”还是“命令式错误控制”**。

以下是它们在不同组合场景下的深度对比与应用指南:


1. 核心对比:容器处理 vs. 流程中断

Result 的 map / flatMap

这种方式将错误视为数据的一部分。你是在操作一个“盒子”,如果盒子里是错误,操作会自动跳过。

  • 适用场景:当你有一系列转换逻辑(Pipeline),且希望将最终的“成功或失败状态”作为一个整体传递或存储时。
  • 防御优势:强制链式处理,逻辑闭环,不会中途遗漏错误。

try / try? / try!

这种方式将错误视为流程的中断

  • try:显式捕获。适合需要对不同错误进行精细化补偿(如重试、弹窗)的场景。
  • try? :静默失败。适合“可选性”逻辑,即失败了也没关系,给个 nil 即可。
  • try! :断言失败。仅用于逻辑上绝对不可能出错的场景(如加载包内固定的图片资源)。

2. 组合使用场景对照表

组合方式代码风格典型应用场景防御等级
Result + flatMap函数式异步回调链。例如:先登录 -> 拿 Token -> 请求个人资料。极高(状态不丢失)
try? + 可选链声明式UI 表现层。例如:尝试解析一个可选字段,失败了就不显示,不影响整体。(忽略错误细节)
do-try-catch命令式核心业务逻辑。需要明确知道是网络超时还是权限不足,并给用户反馈。(处理细节)
try!暴力式常量初始化。如:硬编码的正则字符串解析、本地 Bundle 资源读取。(风险点)

3. 场景实例:从 JSON 解析到模型转换

假设我们要完成:String -> Data -> JSON -> Model

场景 A:使用 Result 链式 (Pipeline 模式)

这种方式非常适合作为 SDK 接口,返回给调用方一个最终结果。

Swift

func getModel(from jsonString: String) -> Result<User, Error> {
    return Result { jsonString.data(using: .utf8)! } // 包装为 Result
        .flatMap { data in
            Result { try JSONDecoder().decode(User.self, from: data) }
        }
        .map { user in
            var mutableUser = user
            mutableUser.name = user.name.uppercased() // 成功后的进一步转换
            return mutableUser
        }
}

场景 B:使用 try? 与可选链 (优雅降级模式)

如果你不在乎为什么解析失败,只要结果。

Swift

// 如果任意一步出错,user 就是 nil
let user = try? JSONDecoder().decode(User.self, from: Data(jsonString.utf8))
let greeting = user?.name ?? "Guest" 

场景 C:使用 do-try-catch (严苛防御模式)

在金融或安全模块中,每一步错误都必须记录日志。

Swift

do {
    let data = jsonString.data(using: .utf8)!
    let user = try JSONDecoder().decode(User.self, from: data)
    self.currentUser = user
} catch let error as DecodingError {
    Logger.log("解析错误:字段缺失 (error)")
} catch {
    Logger.log("未知错误:(error)")
}

4. 转换桥梁:何时交叉使用?

在复杂的架构中,你经常需要在这两套体系间“横跳”:

  1. tryResult

    当你调用一个 throws 函数,但需要把结果存进数组或传递给闭包时:

    let result = Result { try throwingFunction() }

  2. Resulttry

    当你手里有一个 Result,但想利用 do-catch 的扁平语法时:

    let value = try result.get()


5. 防御性总结:如何选?

  • Result.flatMap:如果你在写异步流组合多个可能失败的任务,或者希望代码具有高度的数学严谨性
  • try? :如果你在写 UI 逻辑,且失败的后果仅仅是“少显示一个头像”。
  • do-try:如果你在写主业务流程,需要清晰的错误路径和易于调试的堆栈。
  • 绝对避开 try! :除非你在写单元测试,或者初始化那些如果加载失败 App 就该直接崩溃的静态资源。