Chapter 5: Exception Practices (Items 45-50)
完整覆盖第3版Item 45-50:method contract failure、using/finally、完整自定义异常、strong guarantee、exception filter与filter side effects。
学习目标
- 能区分routine branch、argument/state contract failure与dependency fault,设计让caller可采取动作的exception surface
- 能实现using/finally cleanup、complete custom exception与strong exception guarantee,并复现每个fault point后的state
- 能推导exception filter的search-before-unwind顺序,判断filter condition与有限side effect是否保留原始诊断语义
机制总览
异常传播与状态保证实验
- 1
报告失败
只有无法满足方法承诺的情况才抛异常,并选择能表达恢复策略的类型。
- 2
清理资源
using 或 finally 必须在传播异常前完成确定清理。
- 3
保持状态
优先强异常保证:失败后对象保持调用前可用状态。
章级决策实验
异常传播与状态保证实验
沿失败路径检查报告方式、资源清理、状态恢复和诊断上下文。
选择推理阶段
当前阶段 · 报告失败
只有无法满足方法承诺的情况才抛异常,并选择能表达恢复策略的类型。
可核验证据
异常类型矩阵、调用方处理策略与边界测试。
异常是方法契约的一部分:调用方需要知道什么算失败、失败后对象仍满足什么不变量,以及哪里能取得证据。
失效—证据矩阵
异常传播与状态保证实验
报告失败
典型失效
用返回值吞掉严重失败,或把普通分支全部异常化。
核验证据
异常类型矩阵、调用方处理策略与边界测试。
清理资源
典型失效
清理代码只在成功路径执行,原始异常又被二次异常覆盖。
核验证据
故障注入、Dispose 次数与异常链。
保持状态
典型失效
先修改共享状态再执行可能失败的步骤,留下半提交。
核验证据
前后快照、不变量断言与补偿日志。
为什么Exception是Method Contract的一部分
Exception不是“发生错误时打印的字符串”,而是method无法完成承诺时交给caller的typed control transfer。Type、properties、cause chain、cleanup与post-failure state共同决定caller能否retry、translate、return response或terminate。
先预测:任何invalid input都该抛exception;catch后throw ex与throw相同;filter中写business state只要返回false就无影响。三个判断都错误。
Failure Taxonomy(Item 45)
Item 45: Use Exceptions to Report Method Contract Failures
Public method因invalid argument、invalid object state或dependency fault无法完成定义功能时,抛最specific、稳定且已文档化的exception。Routine lookup miss、parse attempt等预期分支可设计Try/Result,避免用exception当常规loop control。不要主动抛System.Exception、NullReferenceException等无法表达caller action的类型。
public Money Withdraw(Money amount)
{
if (amount <= Money.Zero) throw new ArgumentOutOfRangeException(nameof(amount));
if (amount > Balance) throw new InvalidOperationException("Insufficient balance.");
return ApplyWithdrawal(amount);
}切换argument、state、routine miss与dependency fault
Cleanup、Custom Type与State Guarantee(Items 46-48)
Item 46: Utilize using and try/finally for Resource Cleanup
using/await using把Dispose绑定到lexical scope,try/finally适合归还pool lease、恢复临时状态等非IDisposable cleanup。Finally必须短、幂等且避免抛出新exception遮蔽primary fault;business commit不能伪装成cleanup。
Item 47: Create Complete Application-Specific Exception Classes
只有caller确实需要按domain type捕获或读取structured data时才创建custom exception。提供message、inner exception及有用readonly properties;现代.NET应用通常不需要旧式formatter serialization constructor,但跨process contract应使用独立error DTO,不序列化任意exception object。
Item 48: Prefer the Strong Exception Guarantee
Strong guarantee要求operation失败后observable state与调用前完全相同。常用做法是先validate/prepare到temporary state,全部成功后单点commit;若external side effect无法rollback,则明确basic guarantee、idempotency key与compensation,不虚构transaction。
public void ReplaceLines(IEnumerable<Line> candidate)
{
var prepared = candidate.Select(Validate).ToList();
_lines = prepared; // publish only after every validation succeeds
}切换cleanup、custom type与strong/basic guarantee
Filter Search、Unwind与Observation(Items 49-50)
Item 49: Prefer Exception Filters to catch and re-throw
catch (HttpRequestException ex) when (ex.StatusCode == ...)只在condition匹配时选择handler;不匹配会继续寻找outer handler,而不是先catch再rethrow。Filter在stack unwind前参与search,debugger保留原始throw context,也避免throw ex重置stack。
Item 50: Leverage Side Effects in Exception Filters
原书指出filter可在返回false前执行有限观察,例如记录尚未unwind时可见的diagnostic context,然后让原exception继续传播。Side effect必须best-effort、non-throwing、bounded且不能改变business state;filter抛出的exception会被丢弃并按false处理,因此审计/支付等关键写入绝不能放这里。
try { RunOperation(); }
catch (Exception ex) when (Observe(ex)) { }
static bool Observe(Exception ex)
{
Diagnostics.TryRecord(ex);
return false;
}切换filter false、true、observe-and-pass与filter fault
本章回顾:失败也必须保持可替代、可清理、可诊断
- Exception type表达caller action;routine branch用Try/Result,真正contract failure用specific exception。
- using/finally保证scope cleanup,不承担durable business commit。
- Custom exception只在abstraction需要稳定typed failure时创建,并保留cause chain。
- Strong guarantee用prepare-then-commit避免partial state;做不到时诚实声明basic guarantee与compensation。
- Exception filter在unwind前筛选handler,优于catch再rethrow。
- Filter side effect只做有界best-effort observation,不能改变业务正确性。
练习
问题 1:库存查询无结果、负数量和数据库超时应分别怎样表达?
问题 2:批量替换先修改前半集合再验证后半,如何达到strong guarantee?
问题 3:何时可在exception filter里执行side effect,怎样验收?
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- contract failure
- strong exception guarantee
- complete custom exception
- search-before-unwind
- observational side effect