如何阅读代码:一篇关于代码阅读方法的文章翻译
每个人都知道怎么读书。从第一页开始,读者按顺序逐字阅读,时不时停下来思考一下,直到读完最后一页的最后一个字。
那读代码为什么就不一样了呢?
为什么代码不同
我们日常读的中文文章,天生就是为了按顺序阅读而设计的。事实上,除了作者希望读者怎样去吸收内容之外,文字的先后顺序几乎没有任何硬性约束。在撰写文章时,作者可以按照自己喜欢的任意顺序,来组织想要表达的想法。
但代码完全不同:代码是写给计算机运行的,它的排列顺序主要由非人类的规则所决定。作者不可能仅仅因为觉得某行代码能给人类读者带来更好的“前言导引”,就擅自将其挪到文件或函数的开头。
另一个显著差异在于:日常文章总是以“最终成品”的形式呈现给读者的,而代码通常是以 diff(改动差异)的形式被阅读的。
软件工程师日常大部分的时间,都是在阅读既有代码上的微小变动,而不是全新的程序。想象一下,如果阅读一篇文章也像读 diff 一样:读者必须按照作者撰写草稿的演进顺序,一段改动、一句补充地拼凑着读,那将非常容易丧失对全文脉络的整体感知。
第三个原因(作者作为文学与诗歌爱好者,深有体会):代码在结构复杂度上,远超我们平时阅读的自然语言文本。
即便是公认极难读懂的古文诗赋,单看句法结构也比大多数程序简单得多。例如杜甫在《秋兴八首》中著名的倒装名句:
香稻啄馀鹦鹉粒,
碧梧栖老凤凰枝。
读者即便需要费些心思辨析其实际语序是“鹦鹉啄馀香稻粒,凤凰栖老碧梧枝”,其语法依赖也基本局限在这一两句诗的范围之内;而代码中的依赖关系,却能横跨整整一个代码库。经典名著的难度主要在于理解其中描摹的幽微人性,而不在于理清每个字的语法功能。
此外,现代大型代码库在篇幅规模上也极其庞大:《红楼梦》全书约 73 万字,《三国演义》约 60 万字,而如今多数大型现代代码库的代码行数,轻松就能达到甚至超过这个数量级。
人们不擅长阅读代码
正如作者多次强调的,大型计算机程序的复杂程度,已经远远超出了单个人能够完全理解的范围。
因此,在大型程序中阅读代码,必然是一个权衡妥协的过程:你需要决定哪些部分要深入理解、哪些部分可以一扫而过;换句话说,你需要把有限的精力合理分配到代码库的关键点上。
正因如此,大多数人读代码的方式并不理想:
- 有的人像读书一样从头到尾硬啃,结果彻底迷失在执行流程中;
- 有的人只盯着 diff 上的几行改动,忽略了未修改上下文的重要意义;
- 还有的人干脆被复杂性压垮,直接放弃思考,全凭猜测来理解代码。
怎样才能做得更好?
关于阅读代码,作者推崇的一篇参考,是一篇关于如何阅读数学论文的文章,因为数学论文和代码有着高度相似的结构。
该文章介绍了一种被称为“二分扫描”(dyadic scanning)的技巧:不要缓慢地逐字顺序阅读,而是进行多轮扫描。 第一轮先把握整体轮廓与宏观架构,第二轮理解局部子结构,最后才深入探究具体细节。
对于阅读代码而言,作者指出,需要打破原有次序,进行“跳跃式”阅读。
作者通常的习惯是先挑选一条核心链路(例如这次 diff 所引入新功能的正常主路径),沿着调用关系追踪哪些函数调用了哪些其他函数,先对整体流转建立清晰的直觉。
只有对主干流程有了把握之后,作者才会真正仔细关注这些函数内部具体在做些什么。
在实际操作中,作者往往会进行多轮扫描,每一轮顺着不同的脉络展开:
先选定一个特定的函数或一段关键数据,理清它是如何被流转使用的,并顺着多个调用点逐步向外发散(这甚至包括 diff 范围之外的调用方)。
面对小规模的 diff,作者直接通过关键字快速检索;面对大体积的 diff,则更倾向于在编辑器中打开,配合代码跳转功能来高效导航。
在这一过程中,作者会保持高度专注:只盯紧眼前正在追踪的这一条线索,其余所有不相干的模块,通通视为黑盒。
只有当确信自己已经建立起对这次 diff 的宏观认知后,作者才会静下心来,从头到尾仔细通读一遍。
这一阶段通读的目的,主要不再是学习代码结构(因为结构此时应当已经了然于胸),而是去捕捉前几次乱序扫描时可能漏掉的异常代码细节。
如果在此期间确实发现了任何可疑之处,作者会立刻停下来,重新针对可疑点进行专项扫描。
这套方法听起来可能有些繁琐,但实际执行起来往往非常敏捷,因为每一次扫描都目标明确,无需逐行逐句苦思冥想。
AI 时代,我们还需要读代码吗?
现在很多人认为,我们已经不再需要亲自阅读代码了:要么是觉得大模型已经能在无人监督的情况下稳定输出高水准代码,要么是认为可以直接让另一个大模型充当审查者替你读代码。
但在作者看来,这两种设想都站不住脚。
大模型生成代码的实际质量,极大地取决于具体的应用场景。正如作者在《纯粹与不纯粹的软件工程》一文中曾探讨过的,不同的软件领域(比如游戏底层、基础库、数据库引擎等系统)和其他通用领域(比如互联网大厂的分布式业务架构),在工程规范、最佳实践以及审美标准上都有着天壤之别。
如果你只是给自己写个临时脚本或小玩具,不想读代码确实无所谓;
但回顾过去这一年里审阅过的大量 AI 生成代码,作者给出的明确结论是:工程师依然必须亲自阅读代码。
作者在实际工作中,常常能在 AI 生成的代码里捕捉到非常严重的隐患。 这些隐患往往不是显式的语法错误或运行崩溃,代码通常能跑通 AI 所预期的任务,它们更多是对齐层面的偏差。
举一个近期的真实案例:原本只需要在现有逻辑中顺带多传递一个参数的小改动,最终却膨胀成了一个包含三千行复杂改动的巨大 diff。
究其原因,是因为 AI Agent 在阅读上下文时偶然察觉到一个潜在的竞态条件,于是自作主张构建了一整套复杂的防御机制来“修复”它。
但是其实,这个竞态条件在原系统的架构设计中本来就是存在,对系统功能没有任何影响:两部分互不关联的数据即便发生短暂的毫秒级不同步,对最终用户也不会产生任何实际影响。
那么,让大模型替你来做代码评审是否可行?
作者给出的答案同样是否定的,背后的逻辑如出一辙:即便大模型本身不出现显式疏漏,它的工程价值观与技术品味也无法天然契合你或你团队的长期标准。
大模型当然可以作为辅助读代码的得力助手,但你依然必须以严谨的态度审视模型给出的解读; 并且无论何时,都绝不能放弃对底层代码本身的亲自审阅。
