第32章:自说明代码

第32章:自说明代码:让名称、类型、结构和测试表达做什么,让注释解释原因、约束与非显然取舍,并用同步重放验收说明没有过时。

学习目标

  • 能把一段代码的“做什么”、注释的“为什么”和适用边界写成可检查的说明合同。
  • 能在单行、代码段、数据声明、控制结构、子程序与类型边界中判断何时应该改名、重构或补注释。
  • 能用正常、边界、故障和修复重放验证代码与说明同步,并拒绝会误导读者的过时注释。

为什么需要自说明代码

“少写注释”不是目标,“让读者少猜一步”才是目标。代码首先应该用有意义的名称、类型、结构、接口和测试表达做什么;注释只补充从代码本身无法可靠推出的为什么、约束、历史取舍或适用边界。这样,代码发生变化时,说明也有清楚的更新责任。

本章把一次阅读任务固定为:第二位读者能否预测正常输入、边界输入和故障输入的行为,并指出一个决定为何存在。先固定输入、语言版本、观察窗口和预期,再只改变一处表达。下面的六个操作术语是实验的边界:。它们必须能指向下面的实验状态,而不能只停留在标题。

第32章 自说明代码

把“代码清楚”变成一个因果链:读者任务 → 代码意图 → 注释理由 → 同步验证 → 重放证据。每一段说明都应回答它服务哪个判断、代码哪一部分表达了行为、注释补充了哪个不可见约束,以及改动后怎样知道它没有过时。

一个最小合同可以写成:

说明质量=可预测行为+可解释原因+可复核边界重复代码说明质量 = 可预测行为 + 可解释原因 + 可复核边界 - 重复代码

如果注释只是把 if 翻译成“如果条件成立”,它增加的是阅读噪声;如果它说明“这个阈值来自协议的保留窗口,超过后必须拒绝”,它才为维护者提供了代码本身没有的事实。实验中的“过时注释”故意让这两者分叉,帮助读者找到首个偏离。

32.1 外部文档

外部文档适合说明模块目的、公开接口、输入输出、错误语义、版本边界和使用示例。它不应成为代码与注释不一致时的第三份真相:接口改变后,文档、测试和调用者都必须一起检查。对外文档还要标出适用范围,例如“只接受 UTF-8”或“不会替调用者关闭资源”。

32.2 编程风格作文档

编程风格可以让命名、缩进、错误处理和注释格式稳定,却不能替团队做设计判断。把机械选择交给固定版本的格式化器,把职责、所有权和异常路径留给代码审查。风格规则真正有价值的证据是:它减少无意义 diff,让读者更快找到结构,而不是让每个文件看起来像同一张模板。

32.3 注释或不注释

先问“读者缺少什么信息”,再问“是否写注释”。名称不清楚时先改名,结构复杂时先拆分,重复注释时先删除;只有原因、约束、外部事实、兼容性或非显然算法选择无法由代码推出时,才补理由注释。一个注释如果会在代码改动后继续看似合理却改变结论,就是高风险的同步点。

32.4 高效注释之关键

高效注释短而有判定力:说明动机、保护的约束、被拒绝的替代方案和复核条件。它不描述每个变量当前叫什么,而是记录“为什么不能简化”“为什么必须保留顺序”“这个边界来自哪份协议”。让读者先看代码得到行为,再看注释理解取舍,最后用测试确认两者仍然一致。

注释种类

可以按读者任务分成几类:外部文档回答如何使用,契约注释回答前置与后置条件,决策注释回答为何选择当前方案,警告注释回答什么不能改,测试注释回答如何构造边界。分类的作用是帮助维护者检查缺口,而不是鼓励给每行都贴标签。

高效注释

高效注释把抽象事实落到可验证对象上。例如,normalizeKey 已经表达了“做什么”,注释可以说明“为了兼容旧索引,连字符必须先于大小写折叠处理;改变顺序会让历史键失效”。这种注释有对象、有原因、有约束,也给测试留下了明确的回归点。

最佳注释量

最佳量不是字符数,而是读者完成任务所需的最小额外信息。先删除复述代码的句子,再检查是否仍能解释阈值来源、生命周期、并发假设或异常选择。若一段注释需要解释十几个局部步骤,通常说明函数职责或命名已经超出可理解范围,应先重构。

32.5 注释技术

注释技术要跟着语义边界走:短注释放在稳定的表达旁边,原因和约束放在决定之前,外部事实附上来源或版本,危险的假设放在最接近失效点的位置。注释不是代码的遮罩层;当它妨碍控制流扫描,或者让维护者无法判断哪个事实仍有效,就应该换成命名、类型、测试或更小的接口。

注释单行

单行注释适合标记一个稳定且非显然的局部决定,例如单位转换、协议保留位或故意不采用的快捷路径。不要写“递增计数器”这类同步成本高、信息量低的旁白。审查时把单行注释和它解释的表达式一起移动或删除,避免代码改了而注释仍留在原位置。

注释代码段

代码段注释应该给一个连续动作命名:准备输入、建立快照、提交变更或释放资源。它可以帮助读者建立地图,但不能替代每个分支的后置条件。段落边界一旦改变,注释要和代码一起重审;若段落只是为了容纳过长函数,优先拆成有名字的子程序。

注释数据声明

数据声明的注释要说明单位、所有权、生命周期、合法范围或来源。例如 retryWindowMs 的关键不是“重试窗口”,而是“服务端以毫秒计,零表示禁止重试,最大值受协议上限约束”。能用类型、命名和不变量表达的部分交给代码,注释保留外部契约与不能从类型推出的约束。

注释控制结构

控制结构的注释应解释顺序、早退、状态机转移或反直觉分支为何存在,而不是逐句翻译 ifforreturn。先把复杂条件拆成命名谓词,再写被保护的业务约束。验收时沿成功、边界和失败三条路径检查:注释描述的资源清理、日志或拒绝行为是否真的发生。

注释子程序

子程序注释可以形成调用合同:前置条件、后置条件、返回值含义、错误方式和副作用。它不能把一个“做五件事”的函数包装成清晰函数。如果调用者必须读完整实现才能知道异常是否被吞掉,说明接口或返回类型需要改进;注释应帮助调用者安全使用,而不是为隐藏复杂度辩护。

注释类、文件和程序

类、文件和程序级说明应先描述责任边界、协作对象和不变量,再列出生命周期或配置约束。若文件说明“负责缓存”,却同时创建线程、写磁盘和发送指标,注释暴露出的矛盾应推动拆分职责。高层说明也要有失效条件:架构改变、公共接口改变或外部依赖升级时必须触发复查。

32.6 IEEE标准

标准不是把每条代码都变成表格,而是提供可重复的术语、文档边界和审查期望。IEEE/SWEBOK 的专业知识结构可作为范围与术语核对;具体语言行为仍需回到语言规范与项目合同。把标准中的“应说明”翻译成当前项目的检查项时,要保留对象、责任人、版本和验收证据,不能只贴标准链接。

软件质量保证标准

质量保证要同时看预防、发现和修复:命名与类型减少误读,代码审查发现不一致,测试和重放证明修复没有改变未声明行为。OWASP Code Review Guide 可帮助组织审查关注点,NIST SSDF 可帮助把缺陷修复纳入可追踪过程。本章的专属验收只判断说明是否可预测、可解释、可同步和可重放,不把链接数量当质量。

更多资源

继续学习时,先按缺口选资料:查语言行为看官方规范,查接口设计看项目指南,查安全边界看 CWE 与 OWASP,查版本与范围看出版社资料。每个来源都要写清它核对了哪个事实、哪一版、何时复查;“更多资源”不是把搜索结果堆在章末。

关键点

自说明代码的决策规则是:先明确读者任务;让代码表达做什么;让注释补充为什么、约束和非显然取舍;用正常、边界、故障、修复重放证明两者同步。无法解释收益的注释应删除;无法由代码稳定表达的行为,应优先改名、类型、结构或测试,再决定是否留下说明。

最小可重放实现

下面的伪代码把“注释是否过时”变成维护流程,而不是凭感觉判断:

baseline = freeze(input, version, readerTask)
prediction = readCodeAndComment(baseline)
candidate = changeOneDecision(baseline)
assert replay(candidate) == prediction.expectedOutcome
assert commentExplains(candidate, prediction.reason)
reset()
assert replay(baseline) == baseline.trace

如果 prediction 只能由注释提供,说明代码缺少名称、类型、结构或测试;如果 prediction 与当前代码冲突,必须先修复说明或代码,再重新跑相同输入。实验的目的不是证明注释越少越好,而是让每句话都有一个可以失效、可以复核的责任。

三视图专属因果实验

先预测改变一个名称、一个边界约束或一条注释后,哪一个节点最先变化。再切换正常路径、边界取舍、过时注释和修复重放;每次只改变一个场景,保存状态文字、首个偏离和接受/回退决定。每一步都包含同一个第32章专属视觉实验,读者可以按阶段聚焦,再用“重置实验”返回基线。

分步1 / 3

1. 从读者任务预测代码意图

先写读者要判断的边界,再观察名称、类型和结构是否已经表达做什么;不要急着添加注释。

第32章 · 专属自描述代码实验

让代码表达做什么,让注释解释为什么

先预测首个偏离,再切换一个场景;实验覆盖 19 个目录节点,最后用同一输入重放。

一条注释判断,五个可审查节点注释不能重复代码;它必须让边界与取舍可被复核1读者任务先说要判断什么2代码意图名称与结构表达做什么3注释理由补充为何与边界4同步验证代码与文字一起更新5重放证据第二位读者可复核基线:先固定读者要完成的判断,再决定代码或注释各自承担什么。当前证据:代码表达可检查意图;注释补充原因、约束和非显然取舍。

决策: 待判断:先预测哪个节点会变化,再只切换一个场景。

故障诊断与误区

术语表

名词解释

本章出现的专业名词,用大白话再讲一遍。

说明合同

读者可用来预测行为、边界和维护责任的一组验收条件;它要求输入、版本、观察窗口和结果都可复核。

可检查意图

由名称、类型、结构、接口或测试直接表达的“做什么”;它不是把实现细节换一种说法。

理由注释

说明原因、约束、外部事实或被拒绝的替代方案;它补足代码无法可靠推出的信息。

同步验证

把当前代码、注释、输入和行为轨迹放在一起检查,确认说明没有因实现变化而失真。

读者任务

读者要完成的具体判断,例如确认失败路径是否清理资源,或判断某个边界值是否被拒绝。

重放证据

从干净状态使用同一输入、版本和验收条件重新得到结论的记录,用来证明修复没有依赖偶然状态。

练习与答案

练习

  1. 一段函数名为 applyPolicy,内部同时转换单位、判断过期、写审计日志并返回布尔值。请指出哪些信息应由代码表达,哪些信息应由理由注释表达,并写出一个读者任务。
  1. 代码从“空字符串代表默认值”改成“空字符串必须拒绝”,但原注释没有变化。请写出同步验证的最小证据。
  1. 团队要求“每个函数都必须有注释”。请用本章规则改写这条政策,并给出删除注释的验收条件。

本章小结

自说明代码不是注释数量竞赛,而是信息责任的分配:代码表达做什么,注释解释为什么,测试与重放验证两者同步。判断一条说明是否值得保留,要看它是否绑定读者任务、外部约束或非显然取舍;判断它是否仍然有效,要把正常、边界、故障和修复放在同一条证据链中。

讨论

评论区加载中…