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. 1

    报告失败

    只有无法满足方法承诺的情况才抛异常,并选择能表达恢复策略的类型。

  2. 2

    清理资源

    using 或 finally 必须在传播异常前完成确定清理。

  3. 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 exthrow相同;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.ExceptionNullReferenceException等无法表达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);
}
分步1 / 3

切换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
}
分步1 / 3

切换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;
}
分步1 / 3

切换filter false、true、observe-and-pass与filter fault

本章回顾:失败也必须保持可替代、可清理、可诊断

  1. Exception type表达caller action;routine branch用Try/Result,真正contract failure用specific exception。
  2. using/finally保证scope cleanup,不承担durable business commit。
  3. Custom exception只在abstraction需要稳定typed failure时创建,并保留cause chain。
  4. Strong guarantee用prepare-then-commit避免partial state;做不到时诚实声明basic guarantee与compensation。
  5. Exception filter在unwind前筛选handler,优于catch再rethrow。
  6. 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

讨论

评论区加载中…