关于 m13-domain-error
面向 Rust 开发者的错误策略技能,帮助按受众、可恢复性和调试上下文设计错误类型,并选择相应恢复方式。
主要功能
- 01按受众和恢复方式分类错误
- 02区分瞬时与永久错误
- 03提供重试、回退和超时模式
- 04强调错误上下文与安全信息
适用场景
- 01设计应用错误类型
- 02规划服务故障的重试策略
- 03改善错误日志与用户提示
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ actionbook/ rust-skills --skill m13-domain-error安装后在 Claude Code 中输入/m13-domain-error确认已加载。
详细介绍
领域错误策略
第 2 层:设计选择
核心问题
谁需要处理此错误,以及应该如何恢复?
设计错误类型之前:
- 这是面向用户的错误,还是内部错误?
- 是否可以恢复?
- 调试需要哪些上下文?
错误分类
| 错误类型 | 面向对象 | 恢复方式 | 示例 |
|---|---|---|---|
| 面向用户 | 终端用户 | 指引操作 | InvalidEmail、NotFound |
| 内部 | 开发者 | 调试信息 | DatabaseError、ParseError |
| 系统 | 运维/SRE | 监控/告警 | ConnectionTimeout、RateLimited |
| 暂时性 | 自动化流程 | 重试 | NetworkError、ServiceUnavailable |
| 永久性 | 人员 | 调查 | ConfigInvalid、DataCorrupted |
思考提示
设计错误类型之前:
-
谁会看到此错误?
- 终端用户 → 友好的消息,并能指导后续操作
- 开发者 → 详细且便于调试的信息
- 运维人员 → 结构化且可触发告警的信息
-
我们能否恢复?
- 暂时性错误 → 退避后重试
- 可降级错误 → 使用备用值
- 永久性错误 → 快速失败并告警
-
需要哪些上下文?
- 调用链 → anyhow::Context
- 请求 ID → 结构化日志记录
- 输入数据 → 错误负载
向上追溯 ↑
追溯到领域约束(第 3 层):
"How should I handle payment failures?"
↑ Ask: What are the business rules for retries?
↑ Check: domain-fintech (transaction requirements)
↑ Check: SLA (availability requirements)
| 问题 | 追溯到 | 询问 |
|---|---|---|
| 重试策略 | domain-* | 重试可以接受的延迟是多少? |
| 用户体验 | domain-* | 应向用户显示什么消息? |
| 合规性 | domain-* | 审计需要记录哪些内容? |
向下追溯 ↓
追溯到实现(第 1 层):
"Need typed errors"
↓ m06-error-handling: thiserror for library
↓ m04-zero-cost: Error enum design
"Need error context"
↓ m06-error-handling: anyhow::Context
↓ Logging: tracing with fields
"Need retry logic"
↓ m07-concurrency: async retry patterns
↓ Crates: tokio-retry, backoff
快速参考
| 恢复模式 | 适用场景 | 实现方式 |
|---|---|---|
| 重试 | 暂时性故障 | 指数退避 |
| 备用方案 | 降级模式 | 缓存值/默认值 |
| 熔断器 | 级联故障 | failsafe-rs |
| 超时 | 耗时操作 | tokio::time::timeout |
| 舱壁隔离 | 隔离 | 独立线程池 |
错误层级
#[derive(thiserror::Error, Debug)]
pub enum AppError {
// User-facing
#[error("Invalid input: {0}")]
Validation(String),
// Transient (retryable)
#[error("Service temporarily unavailable")]
ServiceUnavailable(#[source] reqwest::Error),
// Internal (log details, show generic)
#[error("Internal error")]
Internal(#[source] anyhow::Error),
}
impl AppError {
pub fn is_retryable(&self) -> bool {
matches!(self, Self::ServiceUnavailable(_))
}
}
重试模式
use tokio_retry::{Retry, strategy::ExponentialBackoff};
async fn with_retry<F, T, E>(f: F) -> Result<T, E>
where
F: Fn() -> impl Future<Output = Result<T, E>>,
E: std::fmt::Debug,
{
let strategy = ExponentialBackoff::from_millis(100)
.max_delay(Duration::from_secs(10))
.take(5);
Retry::spawn(strategy, || f()).await
}
常见错误
| 错误做法 | 错误原因 | 更好的做法 |
|---|---|---|
| 所有错误都使用同一种类型 | 无法采取针对性操作 | 按面向对象分类 |
| 对所有错误都重试 | 浪费资源 | 仅重试暂时性错误 |
| 无限重试 | 自我造成拒绝服务 | 限制重试次数并采用退避策略 |
| 暴露内部错误 | 安全风险 | 使用对用户友好的消息 |
| 缺少上下文 | 难以调试 | 在所有地方使用 .context() |
反模式
| 反模式 | 问题 | 更好的做法 |
|---|---|---|
| 字符串错误 | 缺少结构 | 使用 thiserror 类型 |
| 对可恢复错误使用 panic! | 用户体验差 | 使用带上下文的 Result |
| 忽略错误 | 静默失败 | 记录日志或向上传播 |
| 到处使用 Box | 丢失类型信息 | 使用 thiserror |
| 在正常流程中处理错误 | 影响性能 | 尽早验证 |
相关技能
| 适用场景 | 参阅 |
|---|---|
| 错误处理基础 | m06-error-handling |
| 重试实现 | m07-concurrency |
| 领域建模 | m09-domain |
| 面向用户的 API | domain-* |
评论
支持 Markdown