错误处理
定义失败、失败的责任人,以及失败后仍能保持安全的部分。这是指导原则,并非核对清单。
类型化、结构化的失败
预期的失败需要声明类型、稳定的判别项以及处理相关的细节。复用项目的错误约定,保留原因链,并将机器标识与显示文本分离。绝不要通过解析消息来进行处理。意外的缺陷(例如穷尽性检查失败)可以保留为普通错误;但封装绝不能将其转变为成功。
捕获、恢复或停止
对于每个失败,在所属的API文档中定义处理和报告的责任人、封装范围以及保证:哪些不变量仍然有效,副作用是缺失、部分完成、已提交还是未知,以及谁负责清理。后台任务同样承担这些义务。
捕获限制的是异常传播,而非修改。只有受影响状态的所有者才能建立安全的继续执行。否则,停止相关的工作,丢弃无效状态或实例,或进行升级。仅记录日志并非恢复。重试需要安全的重复执行、取消能力以及明确的责任人;有限的操作需要预算,而持续的监督需要生命周期所有权和可观测的性能退化。
本地化、可操作、可见的报告
每个最终报告都需要一个可读的摘要、受影响的操以及一个安全的下一步行动。通过接收应用程序的语言环境机制进行本地化(如果存在);其他诊断则使用其现有的输出语言。将敏感的内部细节排除在面向用户的文本之外。
不可信的状态或未知的结果必须通过UI或默认启用的、可访问的日志显示出来:报告影响、不确定性、系统执行的操作以及安全的下一步。持续存在的无效状态需要持续的可见性。如果背后没有相应的保证,绝不要声称成功、回滚或安全重试。报告失败需要一个独立的回退方案。
验证最终状态和可见的结果,而不仅仅是异常是否被捕获。
基础理论
Meyer,《面向对象软件构造》(1997):契约与不变量。Sutter,《Exceptional C++》(1999):异常安全保证。Armstrong,《在有软件错误的情况下构建可靠的分布式系统》(2003):隔离与监督。