概述
本文讲解如何用 error handling 解决 typed CLI errors 问题。核心思路是:在数据进入业务逻辑之前,用显式约束拒绝不合法的输入,让正常路径保持快速,让失败路径保持可见。
关键设计
Define error classes 是第一个决策,它决定了后续 error handling 管道能假设什么。错误的选择会在高负载下暴露为 typed CLI errors 问题,你以为 error handling 保证的不变量不再成立。在承诺前先测量影响,因为错误选择的代价是事故而非代码评审。
Map to exit codes 的机制在 error handling 中描述简单,但后果会扩散。一个错误的校验会在负载下变成数据问题,你原以为 error handling 保证的不变量不再成立。
实现要点
代码先用 error handling 定义约束,再在边界处调用校验。校验失败时收集所有错误路径,让用户一次看到全部问题,而不是逐个修复。测试用假时钟处理时间逻辑,用桩注入失败路径,断言用户可见的结果。
Print user-friendly msg 是大多数 error handling 实现泄漏性能的地方。一个看起来健康的平均值可能隐藏一个毁掉最慢百分之十用户体验的尾延迟。测量尾延迟,在尾延迟上告警,优化尾延迟,因为平均值会撒谎而尾延迟说出真相。
常见陷阱
- 跳过 error handling 边界检查,让 typed CLI errors 未校验即进入系统。
- 假设 error handling 很快,却不测量尾延迟。
- 让 typed CLI errors 静默失败,而非带指标地显式失败。
- 只测试正常路径,导致 error handling 失败在生产中暴露。
- 跨请求共享可变状态,一个坏输入毒化下一个。
验证方式
编写测试驱动 error handling 到失败条件,断言系统优雅降级而非崩溃。用 node:test 或 vitest 运行,检查校验报告每个违规路径,且正常路径不被校验开销拖慢。这样 typed CLI errors 就是你维护的属性,而非你期望的功能。
迁移与演进
当契约需要变更时,采用扩展-收缩策略:先发布兼容新旧两种形状的版本,待所有生产者都迁移到新形状后,再发布拒绝旧形状的版本。error handling 的版本化能力使这一过程可控,避免破坏性变更导致的生产事故。
Log stack in verbose 的难点不在正常路径,而在 error handling 失败一半时会发生什么。枚举失败模式,为每种情况决定用户看到什么。提前决定的失败是降级,在生产中发现的失败是事故。用混沌测试注入你担心的故障,是在生产前发现接缝的最廉价方式。
可观测性
error handling 的校验决策应当无需调试器即可观测。当 error handling 出错时,运维应看到包含被拒绝输入、拒绝规则和所做决定的结构化日志。静默失败的 error handling 是无法运维的。在决策点添加结构化日志,在拒绝点添加指标,在整个操作周围添加追踪跨度,使“为什么失败”在仪表板中有答案而非在重新部署中。
衡量 error handling 的指标应是用户感受到的——尾延迟、错误率,而非易于绘图的平均值。看起来健康的平均值可能隐藏毁掉最慢百分之十用户体验的尾延迟。在尾延迟上告警,优化尾延迟,因为平均值撒谎而尾延迟说真话。
代码示例
class UsageError extends Error { code = 2; }
class RuntimeError extends Error { code = 1; }
try { await run(); } catch (e) {
console.error(e.message); if (verbose) console.error(e.stack);
process.exit(e.code ?? 1);
}
test('usage error exits 2', () => {
expect(() => run()).toThrow(UsageError);
});