本节摘要:注释的价值在于解释代码表达不了的信息——设计动机、算法选择、陷阱与约束。本节划定该写与不该写注释的边界(解释为什么、复杂逻辑、公共接口、陷阱标记该写;复述代码、过时信息、注释掉的代码不该写),介绍文档注释的结构化格式与自动化文档生成,并给出 TODO 与 FIXME 标记的管理纪律。
阅读完本节,你应当能够:
失败形态一:注释坟场。一个函数开头挂着三十行注释,仔细读发现描述的是两年前的版本,函数早已改得面目全非。读者比照注释理解代码,得出完全错误的预期——排查两小时后发现"注释是骗人的"。
失败形态二:噪音刷屏。几乎每行代码后面都跟着注释:i++; // i 加一、count = 0; // 初始化计数器、return result; // 返回结果。这些注释信息量为零,却占据了大量视觉空间,训练读者条件反射地跳过一切注释——于是真正的关键注释也被淹死了。
两种失败指向同一个根源:写注释前没有问"这条注释提供了代码本身提供不了的信息吗"。代码能表达的(这一行在做什么)不需要注释;代码不能表达的(为什么这么做、为什么不用显然的替代方案、这里有什么坑)才需要。这条分界线就是本节的核心。
还有一类历史遗留物要单独点名:注释掉的代码。以前版本控制不普及时,人们用注释"保留"旧实现以备回退。现在版本库记得每一次历史,注释掉的代码只是垃圾:它会随时间腐化(与新代码渐失关联)、误导读者("这是还在用的备选吗")、膨胀文件。正确做法是直接删除,需要时向版本历史找回。
场合一:解释为什么。这是注释的本职。代码只能说"我在做什么",说不了"我为什么这么做"。典型如:
# 这里用链表而非常见动态数组: # 写日志场景下每毫秒都有插入,链表头插代价稳定 log_buffer = LinkedList()
没有这条注释,未来某个"优化者"看到链表会觉得落伍,换成动态数组,然后在写入高峰期收获性能毛刺。
场合二:复杂算法与业务规则。加密算法的位运算、定价规则的例外条款、为兼容历史数据而存在的特判——这些逻辑的正确性依赖背景知识,不注释等于给每个读者出考题。
场合三:公共接口的文档注释。模块、类、公共函数是给外部使用的,使用者不应被迫读实现。文档注释用固定格式说明功能、参数、返回值、可能抛出的异常,各语言都有对应惯例:Python 的文档字符串、Java 系的文档注释风格、脚本的类文档注释格式。示例(概念代码):
def calculate_days_between(start_date, end_date): """计算两个日期之间的天数差。 参数: start_date: 起始日期。 end_date: 结束日期。 返回: 天数差;起始晚于结束则为负数。 异常: 任一参数为空时抛出参数非法异常。 """
文档注释的最大红利是可机器提取——配套工具能把它们汇集成 API 文档,注释写一次,文档自动生成,永远与代码同处一地。
场合四:陷阱与特殊处理标记。TODO(待完成)、FIXME(已知缺陷待修)、HACK(临时方案欠重构)。它们是代码里的"欠条",让技术债务显性可见。
场合五:文件头与版权信息。开源与商业项目的法律要求,通常由工具自动添加。
准确性:注释与代码必须同步。改代码不改注释,注释就从文档变成谎言,而谎言比空白更有害——空白只让读者困惑,谎言让读者自信地犯错。简洁性:注释是旁白不是论文,三行说不清的动机考虑先改代码结构。一致性:全项目统一注释风格(文档注释的标签、TODO 的写法),让注释本身可读。语言统一:团队约定一种语言写注释并坚持,中英混杂最伤扫读。
| 场合 | 该不该写 | 示例 | 常见错误 |
|---|---|---|---|
| 设计动机 | 必写 | 为什么选链表不选数组 | 让读者猜 |
| 复杂算法 | 必写 | 位运算的等价含义 | 假设读者都懂 |
| 公共接口 | 必写 | 文档注释四要素 | 让使用者读实现 |
| 性能特例 | 必写 | 此处为热点才这样写 | 被后人"好心"重构 |
| 代码字面行为 | 不写 | i 加一 | 纯噪音 |
| 过时描述 | 删除 | 描述旧版本的注释 | 误导读者 |
| 注释掉的代码 | 删除 | 历史保留心态 | 版本库可找回 |
💡 关键直觉:注释的 ROI 取决于"未来读者会在哪里卡住"。会在"为什么"上卡住的注释价值最高;在"是什么"上卡住说明命名或结构有问题——修代码,而不是加注释。
TODO 不是免责声明。规范用法包含三要素:事项说明(要做什么)、背景(为什么暂时不做)、责任人或关联任务号(找谁)。// TODO: 处理错误 是不合格的——处理什么错误、为什么现在没处理、谁负责?合格的写法形如 // TODO: 增加网络超时重试,当前版本先快速发布,任务编号 1024,负责人张三。团队要定期(如每次迭代尾声)盘点 TODO 存量:完成的清除、过期的更新、长期滞留的升级为正式技术债务任务。放任不管的 TODO 会繁殖到没人敢信的程度,最终整个标记体系报废。
注释是补充手段,第一手段永远是让代码自己说话:好命名(第 2.2 节)、小函数(第 3.3 节)、常量代替魔术数字(第 3.4 节)。每当你想写"这行代码在做 X"的注释,先试试把 X 变成一个函数名或变量名。经验法则是:能用结构解决的不要用注释解决——结构不会过时,注释会。
⚠️ 常见坑:用注释给长函数分段("第一步校验、第二步计算……")却拒绝拆函数。注释分段是拆分的欠条——每段就该是一个函数,函数名就是那段注释。同理,大段"此函数修改时注意以下八点"的注释,往往说明函数复杂度已超标。
主流生态都有文档提取工具,配置一次持续受益:写完文档注释,构建时自动产出可检索的 API 文档站点。这带来一个隐性约束——文档注释的格式必须严格符合工具的解析规则(标签拼写、缩进层级),格式错位的注释不会被收录,等于白写。团队应提供注释模板(编辑器片段),让四要素(功能、参数、返回、异常)成为肌肉记忆。
下一节把镜头拉远:单个文件之外,整个项目的布局该怎么安排。