题记与说明
- 日期:2026-11-06
- 前缀:knowledge-bq_8bedt-4
- 类型:主题研究(先检索多来源精读,再对每条主张做"仍有效 / 需修正 / 已过时"核对,附一个本地最小实验)
- 选读对象:Steve McConnell, *Code Complete, 2nd Edition*(Microsoft Press, 2004)。选第 8 章 Defensive Programming 与第 9 章 The Pseudocode Programming Process——一章讲"代码如何在坏输入与坏状态下自保",一章讲"写一个例程的标准工作流"。两章都属"构建时质量"层面,是全书最有机会被时间检验(也最容易被时代修正)的部分。
- 来源(本次实际精读的 5 个来源):
- Nikola Brežnjak, *Code Complete 2 – Defensive programming*(对 Ch.8 的系统性读书笔记,大量原书引述)— https://nikola-breznjak.com/blog/books/programming/code-complete-2-steve-mcconnell-defensive-programming
- Nikola Brežnjak, *Code Complete 2 – Pseudocode Programming Process*(对 Ch.9 的读书笔记,含 PPP 全步骤与书内 C 代码示例)— https://nikola-breznjak.com/blog/books/programming/code-complete-2-steve-mcconnell-pseudocode-programming-process
- Jeff Atwood (Coding Horror), *Pseudocode or Code?*(2009,对 PPP 的著名批判,含逐字引述 McConnell 的"伪代码五好处")— https://blog.codinghorror.com/pseudocode-or-code/
- Joe Duffy, *The Error Model*(2016,微软 Midori 项目的错误模型复盘:fail-fast/abandonment vs 可恢复错误,是 Ch.8 断言二分法的工业级现代回声)— http://joeduffyblog.com/2016/02/07/the-error-model
- Ousterhout *A Philosophy of Software Design* 的"先写注释"思想的两个整理来源:binary.phile, *Ousterhout Software Design Guide*(2026)— https://www.binaryphile.com/2026/01/09/ousterhout-software-design-guide.html ;Vít Baisa 书摘 — https://vit.baisa.cz/books/a-philosophy-of-software-design
- 未能访问的来源:O'Reilly 官方章节页 https://www.oreilly.com/library/view/code-complete-2nd/0735619670/ch08.html ——
curl -sL仅返回 448 字节的Access Denied拦截页(Akamai edgesuite),原书逐页核对未做成,见"局限"。
一、七维提取
1. 核心问题
一本 2004 年、900 多页的"代码大全",不是每一章都扛得住 20 年。在 2026 年的工程环境(强类型系统、schema 校验库、秒级编译、CI 测试文化、AI 辅助编码)下回看,本笔记回答三个问题:
- 第 8 章的防御式编程:断言、错误处理、异常、隔离栏(barricade)这套分类学,哪些是不变原理(仍有效),哪些已被现代工具升级形式?
- 第 9 章的伪代码编程过程(PPP):在"用精确英语描述函数体、再转成注释、再逐条填代码"这个具体流程明显不合时宜之后,它的内核(先做低层设计、注释承载设计、先想错误处理)还活着吗?
- 由此提炼一个元方法:以后再挑老书章节,按什么标准判断"这章还值得读"?
2. 主要主张
Ch.8 防御式编程——总纲(原书引述,转自来源 1):
Defensive-programming techniques make errors easier to find, easier to fix, and less damaging to production code.
断言 vs 错误处理的二分(本章最核心的一条,原书引述,转自来源 1):
Use error handling code for conditions you expect to occur and assertions for conditions that should never occur.
配套的一组主张:
- 断言源自 Design by Contract:前置条件是"调用方承诺为真",后置条件是"例程承诺结束时为真"(来源 1)。
- 两条并不互斥:"For highly robust code, assert and then handle the error anyway."(先断言,再照常处理错误,双保险,来源 1)。
- 正确性 vs 健壮性的取舍(原书引述,转自来源 1):
__Correctness__ means never returning an inaccurate result; returning no result is better than returning an inaccurate result.
__Robustness__ means always trying to do something that will allow the software to keep operating, even if that leads to results that are inaccurate sometimes.
安全关键系统偏正确性,消费级应用偏健壮性——"Any result whatsoever is usually better than the software shutting down."
- 错误处理的 8 种手段:返回中性值 / 换用下一段有效数据 / 返回上次的结果 / 取最接近的合法值 / 记警告日志 / 返回错误码 / 交给错误处理例程 / 直接关机(来源 1)。
- 异常的克制用法(原书引述,转自来源 1):
Exceptions have an attribute in common with inheritance: used judiciously, they can reduce complexity. Used imprudently, they can make code almost impossible to follow.
细则:只抛"真正异常"的条件;不用异常"甩锅"(能在本地处理就本地处理);避免在构造/析构中抛异常;在正确的抽象层级抛。
- 隔离栏(barricade)(原书引述,转自来源 1):
Defining some parts of the software that works with dirty data and some that work with clean data can be an effective way to relieve the majority of the code of the responsibility for checking for bad data.
脏数据只允许进入边界区域,边界内校验、边界外假设干净——这是本章在现代最容易被直接对应上的一节。
- 多少防御留在生产(来源 1):开发的悖论是"开发时希望错误越吵越好,生产时希望错误越不显眼越好"。四条准则:留检查重要错误的代码;删检查琐碎错误的代码;删导致硬崩溃的代码;留让程序优雅崩溃的代码。
- 防御过头的自我警告:防御代码本身也会出 bug,随手写的防御代码甚至更容易出 bug(来源 1)。
Ch.9 伪代码编程过程——流程主张(转自来源 2、3):
- PPP 五步:设计例程 → 编码 → 检查 → 收尾 → 按需重复。设计阶段的清单包括:确认该例程职责定义清晰、命名("good routine names are one sign of a superior program")、先决定怎么测试、查标准库("the single biggest way to improve both the quality of your code and your productivity is to reuse good code")、先想错误处理、再想效率("in a vast majority of systems, efficiency isn't critical")。
- 核心动作:把伪代码变成注释骨架,然后逐条往下填代码——"Each comment should normally expand to about 2 to 10 lines of code."
- McConnell 给伪代码列的五条好处(Atwood 逐字转引,来源 3):便于评审(不读源码就能评审低层设计)、支持迭代精化("catch high level errors at the highest level")、改动便宜("a few lines of pseudocode are easier to change than a page of code"、错误要在"least-value stage"拦截)、注释工作量最小化、设计文档不再与代码脱节。
- 两个反直觉的主张(原书引述,转自来源 2):
- 晚编译:评审完再编译,否则会得"Just One More Compile"综合症——"The 'Just One More Compile' syndrome leads to hasty, error-prone changes that take more time in the long run."
- 能跑不够:"A working routine isn't enough. If you don't know why it works, study it, discuss it, and experiment with alternative designs until you do."
- McConnell 自己承认 PPP 不是唯一路径:TDD、重构、DbC、甚至"hacking"都是并列备选(来源 2 转述)。
3. 方法
书内的方法:这两章的证据结构是"原则 + 检查表 + 对比示例代码",属于 McConnell 式的实践综合(全书大量引用文献,但这两章本身不提供受控实验数据)。PPP 甚至给出"与 PPP 并列的备选方案"清单,说明作者意图是给流程选项,不是宣称唯一真理。
本笔记的方法:
- 先检索(2 轮 WebSearch),锁定读书笔记、著名批判、现代工业实践三条线;
- 用 webReader/WebFetch 精读 5 个来源,摘录可归属的原文引述;
- 对每条主张做"现代核对":找 2026 年的对应物(schema 校验、error wrapping、fail-fast 文化、Ousterhout 注释先行、AI 辅助编码),逐条判定仍有效 / 需修正 / 已过时;
- 对一个可本地复现的具体警告(断言里别放可执行代码)跑最小实验验证——这正是"精读笔记要动手"与"纯摘抄"的区别。
4. 数据与证据
(a)文字证据:见"主要主张"中的引述,均可归属到来源 1/2/3/4/5 的具体页面。其中来源 3(Coding Horror)逐字转引了 McConnell 的伪代码五好处,来源 4(Joe Duffy)经 WebFetch 提取了 8 段与"bug vs 预期失败"二分直接对应的原话。
(b)本地实验(本次实际运行):验证 Ch.8 中"断言里别放可执行代码"的警告在现代语言是否仍真实(该条的原书精确措辞未能从官方页面核对,此处以我的转述标注;实验只验证其内容成立)。
- 脚本:
cc8-check/assert_pitfall.py(工作目录C:\Users\马鑫悦\.zcode\workspace\draft-6f58b427-fcc9-4060-a418-5a4f773c3b8c),核心内容:
def audit():
print(" [side-effect] audit() executed")
return True
def main():
assert audit(), "audit invariant violated"
print(" main continues")- 命令与输出(Python 3.12.12, Windows):
$ python -I cc8-check/assert_pitfall.py
run 1: normal
[side-effect] audit() executed <- 副作用发生了
main continues
=== exit 0 ===
$ python -IO cc8-check/assert_pitfall.py
run 1: normal
main continues <- audit() 从未执行,校验被静默跳过
=== exit 0 ===- 结论:
-O下断言体(含其中的函数调用)被剥离且无任何报错。若审计逻辑写在 assert 里,优化运行时安全检查直接蒸发。 McConnell 二十年前的 C/C++ 场景警告(NDEBUG剔除断言),在 Python 3.12 上原样成立。
(c)证据面的诚实声明:这两章没有任何可量化的对照实验数据支撑"PPP 降低缺陷率"或"断言减少 X% 故障"——CC2 未给出,我检索到的来源也未给出。Ch.8 的力量来自分类学的正确性(见下面 Midori 的独立再推导),Ch.9 的力量来自流程合理性 + Ousterhout 的间接印证。这是精读判断,不是测量结论。
5. 结论
Ch.8 是"保值章节"的典范——它的核心二分法被工业界在 12 年后用另一套语言独立推导了一遍。Midori 的错误模型(来源 4,Joe Duffy 逐字引述):
Our overall solution was to offer a two-pronged error model. On one hand, you had fail-fast – we called it abandonment – for programming bugs.
A bug is a kind of error the programmer didn't expect.
Proceeding in the face of a bug is dangerous when you're trying to build a robust system. … Because the developer didn't expect this to happen, all bets are off. All data structures reachable by this code are now suspect.
We used exceptions for recoverable errors.
把 Duffy 的"abandonment vs recoverable errors"和 McConnell 的"assertions vs error handling"并排:同一个二分,一个从语言运行时层面推导,一个从工程实践层面归纳,2004 与 2016 在此重合。这不是巧合——"程序员错误必须 fail fast,预期失败必须可恢复"是错误分类学的稳定结构。
Ch.9 的判定是"内核有效、载体过时":
- 过时的载体:用英语逐行描述函数体再手工转码。Atwood 的批判至今有力(来源 3,原话):
code > pseudocode. I find it easier to think about code *in code*.
Comments should be a method of last resort, not something you start with.
- 活着的内核:Ousterhout《A Philosophy of Software Design》第 15 章 "Write the Comments First" 把 PPP 的核心(注释先于代码、注释即设计工具)在 2018/2021 年重新发明了一遍(来源 5,binary.phile 的提炼原话):
Comments are a design tool, not documentation afterthought.
If the interface comment is hard to write, the interface is probably too complex.
Baisa 书摘也记下同一主张:"Delayed comments won't happen at all."(事后补的注释根本不会被写)
- "晚编译防 Just One More Compile"是本章被时代削弱最明显的一条:秒级编译、实时诊断、热重载把"编译压力"几乎清零。
元方法(挑章节的标准):讲不变结构(错误分类学、正确性 vs 健壮性、抽象层级)的章节保值;讲具体载体(伪代码、晚编译、当时的调试工具)的章节贬值。据此扫 CC2 目录:Ch.8、Ch.5/6(设计/类)、Ch.7(例程质量)大概率保值,讲工具链与环境的章节需按年代打折。
6. 局限
- 原书一手文本未核对:O'Reilly 官方章节页返回
Access Denied(curl 实测 448 字节拦截页,见"来源"节),所有"原书引述"均来自二手读书笔记与博客转引,无法核对页码与逐字精度。个别表述(如"断言里别放可执行代码"的原书措辞)只能以转述标注。 - 无量化证据:PPP 与断言实践均无受控实验数据;我未系统检索断言有效性的实证文献(文献里存在正反两方)。
- Coding Horror 批判的年代性:Atwood 的文章写于 2009 年,其"伪代码没用"的结论没有考虑 AI 辅助编码。本笔记提出"PPP 在 AI 时代以 prompt 形式复活"是我自己的推断,无实证支撑,仅作为假说记录。
- 本地实验覆盖面:只验证了 Python 一个语言点(C/C++ 的
NDEBUG行为未在本次环境实测,因未找到/未安装合适的编译链验证路径)。 - 章节选择的主观性:"哪章保值"的判定受我自身经验偏好影响,样本仅为 2 章,不能外推为全书评级。
7. 对我的启发
- 错误处理二分法直接落进自己的项目模板(对应本系列已有的 Python/TS 模板笔记):边界层用 schema 校验(pydantic / zod)当 barricade,边界内一律假设数据干净;内部不变量用
assert表达,且保证 assert 里无副作用(本次实验已证明-O会静默剥离);区分"预期失败"(返回Result/ 抛业务异常)与"程序 bug"(fail fast,宁可崩溃也不带脏状态继续跑)。Duffy 那句 "All data structures reachable by this code are now suspect" 是 fail fast 最好的说服理由。 - 正确性 vs 健壮性取舍表可以当决策工具用:我的数据管道/备份类脚本(如 3-2-1 备份笔记那类)偏正确性——校验失败就停;展示层/UI 类脚本偏健壮性——降级也要出结果。以前这两类取舍是凭感觉,现在有一张明牌。
- 写例程采用"注释先行"的 PPP 变体:先写 docstring + 伪代码式注释骨架(含错误处理与测试思路),再填代码。在 AI 辅助编码下这有了新形态:注释/伪代码骨架就是给 AI 的规格说明,PPP 从"人转译成代码"变成"AI 转译成代码",Atwood 2009 年"精确英语描述函数体不现实"的反对理由,恰好被"转译者从人换成模型"瓦解(推断,待自证)。这也和 Ousterhout "comments as design tool" 完全同构。
- "能跑不够"那条值得贴在显示器旁:"A working routine isn't enough. If you don't know why it works, study it…"——对 vibe coding 尤其重要:AI 生成代码"能跑"的成本趋近于零,"知道为什么能跑"成了新的稀缺品。
- 方法论沉淀:以后精读老书,先做"不变结构 vs 具体载体"二分,再对 3-5 条核心主张找现代对应物,最后挑 1 条能本地动手验证的跑最小实验——本次这个三步法(对应→判定→实验)比通读全书更省也更扎实。
附:逐条核对表(McConnell 原始主张 → 2026 现代对应 → 判定)
| McConnell 主张(Ch.8/9) | 2026 年的对应物 | 判定 |
|---|---|---|
| 断言查"绝不该发生",错误处理查"预期发生" | Midori: abandonment vs recoverable errors(Joe Duffy 2016) | 仍有效,被工业级系统二次确认 |
| 高健壮代码:先断言再处理错误 | fail fast + 结构化错误类型双保险 | 仍有效 |
| 断言里别放可执行代码 | Python -O / C++ NDEBUG 剥除断言体(本地实测成立) | 仍有效,现代语言同样踩坑 |
| 正确性 vs 健壮性取舍 | 安全关键系统 vs 消费应用的 SLO/降级策略 | 仍有效(框架升级了,二分未变) |
| 隔离栏 barricade | 边界 schema 校验(pydantic/zod/JSON Schema)、防腐层 | 仍有效,形式工具化 |
| 异常只抛真异常、在正确抽象层级抛、勿甩锅 | error wrapping(%w/anyhow::Context/异常层级设计) | 仍有效 |
| 调试辅助:开发激进、生产收敛;留"优雅崩溃" | debug build/feature flag、崩溃上报、优雅降级 | 仍有效 |
| 防御过头本身制造缺陷 | 同样成立;且防御代码无测试时更糟 | 仍有效 |
| 伪代码五好处(评审/迭代/改动/注释/文档) | 大部分被类型系统、IDE、测试文化接管 | 形式过时 |
| 伪代码转注释、每条注释展开 2-10 行 | 与 self-documenting code、"注释是最后手段"冲突(Atwood) | 有争议 |
| 先想清楚再写(低层设计前置、注释承载设计) | Ousterhout "Write the Comments First"(2018/2021);AI 时代的 prompt-as-spec(推断) | 内核仍有效 |
| 晚编译防 "Just One More Compile" 综合症 | 秒级编译、实时诊断、热重载 | 基本过时 |
| 先查标准库、复用好代码 | 仍是第一生产力 | 仍有效 |
附:关键引述汇总(均已在正文标明来源)
- "Use error handling code for conditions you expect to occur and assertions for conditions that should never occur." — CC2 Ch.8(转自来源 1)
- "For highly robust code, assert and then handle the error anyway." — CC2 Ch.8(转自来源 1)
- "Correctness means never returning an inaccurate result; returning no result is better than returning an inaccurate result." — CC2 Ch.8(转自来源 1)
- "One of the paradoxes of defensive programming is that during development, you'd like an error to be noticeable… But during production, you'd rather have the error be as unobtrusive as possible, to have the program recover or fail gracefully." — CC2 Ch.8(转自来源 1)
- "Defining some parts of the software that works with dirty data and some that work with clean data can be an effective way to relieve the majority of the code of the responsibility for checking for bad data." — CC2 Ch.8(转自来源 1)
- "Each comment should normally expand to about 2 to 10 lines of code." — CC2 Ch.9(转自来源 2)
- "The 'Just One More Compile' syndrome leads to hasty, error-prone changes that take more time in the long run." — CC2 Ch.9(转自来源 2)
- "A working routine isn't enough. If you don't know why it works, study it, discuss it, and experiment with alternative designs until you do." — CC2 Ch.9(转自来源 2)
- "code > pseudocode. I find it easier to think about code *in code*." / "Comments should be a method of last resort, not something you start with." — Jeff Atwood(来源 3)
- "Our overall solution was to offer a two-pronged error model. On one hand, you had fail-fast – we called it abandonment – for programming bugs." / "Proceeding in the face of a bug is dangerous when you're trying to build a robust system." / "We used exceptions for recoverable errors." — Joe Duffy, *The Error Model*(来源 4)
- "Comments are a design tool, not documentation afterthought." — Ousterhout 思想的整理(来源 5,binary.phile 提炼,非原书逐字)
附:来源链接
- https://nikola-breznjak.com/blog/books/programming/code-complete-2-steve-mcconnell-defensive-programming
- https://nikola-breznjak.com/blog/books/programming/code-complete-2-steve-mcconnell-pseudocode-programming-process
- https://blog.codinghorror.com/pseudocode-or-code/
- http://joeduffyblog.com/2016/02/07/the-error-model
- https://www.binaryphile.com/2026/01/09/ousterhout-software-design-guide.html ;https://vit.baisa.cz/books/a-philosophy-of-software-design
- (未能访问)https://www.oreilly.com/library/view/code-complete-2nd/0735619670/ch08.html —— curl 返回 Access Denied