名称:m06-错误处理 描述:“关键:用于错误处理。触发词:Result、Option、Error、?、unwrap、expect、panic、anyhow、thiserror、何时用 panic 对比返回 Result、自定义错误、错误传播、错误处理、Result 用法、什么时候用 panic” 用户可调用:false
错误处理
第 1 层:语言机制
核心问题
这个失败是预期的还是一个错误(bug)?
在选择错误处理策略之前:
- 这在正常操作中会失败吗?
- 谁应该处理这个失败?
- 调用者需要什么上下文信息?
错误 → 设计问题
| 模式 | 不要只是说 | 而是问 |
|---|---|---|
| unwrap 会 panic | “用 ?” | None/Err 在这里实际上可能发生吗? |
| ? 的类型不匹配 | “用 anyhow” | 错误类型设计正确吗? |
| 丢失错误上下文 | “添加 .context()” | 调用者需要知道什么? |
| 错误变体太多 | “用 Box<dyn Error>” | 错误的粒度合适吗? |
思考提示
在处理错误之前:
-
这是什么类型的失败?
- 预期的 → Result<T, E>
- 正常情况下的缺失 → Option<T>
- 错误/不变量违反 → panic!
- 不可恢复的 → panic!
-
谁来处理?
- 调用者 → 用 ? 传播
- 当前函数 → 用 match/if-let 处理
- 用户 → 友好的错误信息
- 程序员 → 带信息的 panic
-
需要什么上下文?
- 错误类型 → thiserror 变体
- 调用链 → anyhow::Context
- 调试信息 → anyhow 或 tracing
向上追溯 ↑
当错误策略不明确时:
"我应该返回 Result 还是 Option?"
↑ 问:缺失/失败是正常的还是异常的?
↑ 检查:m09-领域(领域怎么说?)
↑ 检查:domain-*(错误处理要求)
| 情况 | 追溯到 | 问题 |
|---|---|---|
| 太多 unwrap | m09-领域 | 数据模型正确吗? |
| 错误上下文设计 | m13-领域错误 | 需要什么恢复? |
| 库 vs 应用错误 | m11-生态系统 | 消费者是谁? |
向下追溯 ↓
从设计到实现:
"预期失败,库代码"
↓ 使用:thiserror 用于类型化错误
"预期失败,应用代码"
↓ 使用:anyhow 用于符合人体工学的错误处理
"缺失是正常的(查找、获取、查询)"
↓ 使用:Option<T>
"错误或不变量违反"
↓ 使用:panic!, assert!, unreachable!
"需要带上下文传播"
↓ 使用:.context("发生了什么")
快速参考
| 模式 | 何时使用 | 示例 |
|---|---|---|
Result<T, E> |
可恢复的错误 | fn read() -> Result<String, io::Error> |
Option<T> |
缺失是正常的 | fn find() -> Option<&Item> |
? |
传播错误 | let data = file.read()?; |
unwrap() |
仅用于开发/测试 | config.get("key").unwrap() |
expect() |
不变量成立 | env.get("HOME").expect("HOME set") |
panic! |
不可恢复的 | panic!("critical failure") |
库 vs 应用
| 上下文 | 错误库 | 原因 |
|---|---|---|
| 库 | thiserror |
为消费者提供类型化错误 |
| 应用 | anyhow |
符合人体工学的错误处理 |
| 混合 | 两者 | 在边界用 thiserror,内部用 anyhow |
决策流程图
失败是预期的吗?
├─ 是 → 缺失是唯一的“失败”吗?
│ ├─ 是 → Option<T>
│ └─ 否 → Result<T, E>
│ ├─ 库 → thiserror
│ └─ 应用 → anyhow
└─ 否 → 这是一个错误(bug)吗?
├─ 是 → panic!, assert!
└─ 否 → 考虑是否真的不可恢复
使用 ? → 需要上下文吗?
├─ 是 → .context("message")
└─ 否 → 普通的 ?
常见错误
| 错误 | 原因 | 修复 |
|---|---|---|
unwrap() panic |
未处理的 None/Err | 使用 ? 或 match |
| 类型不匹配 | 不同的错误类型 | 使用 anyhow 或 From |
| 丢失上下文 | 没有上下文的 ? |
添加 .context() |
cannot use ? |
缺少 Result 返回类型 | 返回 Result<(), E> |
反模式
| 反模式 | 为什么不好 | 更好的做法 |
|---|---|---|
到处用 .unwrap() |
在生产环境中 panic | .expect("reason") 或 ? |
| 静默忽略错误 | 隐藏错误 | 处理或传播 |
对预期错误用 panic! |
用户体验差,无法恢复 | Result |
| 到处用 Box<dyn Error> | 丢失类型信息 | thiserror |
相关技能
| 何时 | 参见 |
|---|---|
| 领域错误策略 | m13-领域错误 |
| 包边界 | m11-生态系统 |
| 类型安全的错误 | m05-类型驱动 |
| 心智模型 | m14-心智模型 |