name: handling-errors description: 防止错误处理中的静默失败和上下文丢失。在编写try-catch块、设计错误传播、审查catch块或实现Result模式时使用。
错误处理
铁律
- 永不吞没错误 - 空的catch块隐藏bug
- 永不将错误转换为布尔值 - 丢失所有上下文
- 保留错误上下文 当包装或传播时
- 在处理的层记录一次,而不是每一层
错误消息
每个错误消息回答:发生了什么?为什么?如何恢复?
对于日志(开发者):
logger.error("保存用户失败:30秒后连接超时", {
userId: user.id,
dbHost: config.db.host,
error: error.stack,
});
对于用户:
对于面向用户的错误文案,使用 Skill(ce:writer) 与 UX Writer 角色。关键原则:
- 简洁具体(不是“出了点问题”)
- 可操作(告诉他们下一步做什么)
- 不责备(永远不要说“您输入无效…”)
showError({
title: "上传失败",
message: "文件超过10MB限制。选择一个更小的文件。",
actions: [{ label: "选择文件", onClick: selectFile }],
});
错误类别
| 类型 | 示例 | 处理方式 |
|---|---|---|
| 预期 | 验证错误、未找到、未授权 | 返回Result类型,记录信息日志 |
| 瞬态 | 网络超时、速率限制 | 重试并退避,记录警告日志 |
| 意外 | 空引用、数据库崩溃 | 记录错误日志,显示支持ID |
| 关键 | 认证服务宕机、支付网关离线 | 断路器,警报 |
快速失败与优雅降级
快速失败 对于关键依赖:
await connectToDatabase(); // 失败时抛出异常 - 应用无法在没有它的情况下运行
优雅降级 对于可选功能:
const prefs = await loadPreferences(userId).catch(() => DEFAULT_PREFS);
在正确的层记录日志
// ❌ 在每一层记录日志 = 相同错误记录3次
async function fetchData() {
try { return await fetch(url); }
catch (e) { console.error("获取数据失败:", e); throw e; }
}
// ✅ 在处理的地方记录一次
async function fetchData() {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response;
}
// 顶层记录错误一次
语言特定模式
- TypeScript/React:参见 reference/typescript-react.md 了解错误边界、类型错误、Result模式、UI显示
- Python:参见 reference/python.md 了解EAFP、异常链、上下文管理器
- Go:参见 reference/go.md 了解显式错误返回、使用%w包装、哨兵错误
反模式
| 模式 | 问题 | 修复 |
|---|---|---|
| 空的catch块 | 隐藏错误 | 记录或重新抛出 |
return false 错误时 |
丢失上下文 | 返回Result类型 |
| 通用的“错误”消息 | 难以调试 | 包含内容/原因/上下文 |
| 在每一层记录相同错误 | 日志污染 | 在边界记录一次 |
裸 except: / catch (e) 所有 |
捕获系统信号 | 捕获特定类型 |