如何阅读代码:一篇关于代码阅读方法的文章翻译

原文:https://seangoedecke.com/how-to-read-code/

每个人都知道怎么读书。从第一页开始,读者按顺序逐字阅读,时不时停下来思考一下,直到读完最后一页的最后一个字。

那读代码为什么就不一样了呢?

为什么代码不同

我们日常读的中文文章,天生就是为了按顺序阅读而设计的。事实上,除了作者希望读者怎样去吸收内容之外,文字的先后顺序几乎没有任何硬性约束。在撰写文章时,作者可以按照自己喜欢的任意顺序,来组织想要表达的想法。

但代码完全不同:代码是写给计算机运行的,它的排列顺序主要由非人类的规则所决定。作者不可能仅仅因为觉得某行代码能给人类读者带来更好的“前言导引”,就擅自将其挪到文件或函数的开头。

另一个显著差异在于:日常文章总是以“最终成品”的形式呈现给读者的,而代码通常是以 diff(改动差异)的形式被阅读的。

软件工程师日常大部分的时间,都是在阅读既有代码上的微小变动,而不是全新的程序。想象一下,如果阅读一篇文章也像读 diff 一样:读者必须按照作者撰写草稿的演进顺序,一段改动、一句补充地拼凑着读,那将非常容易丧失对全文脉络的整体感知。

第三个原因(作者作为文学与诗歌爱好者,深有体会):代码在结构复杂度上,远超我们平时阅读的自然语言文本。

即便是公认极难读懂的古文诗赋,单看句法结构也比大多数程序简单得多。例如杜甫在《秋兴八首》中著名的倒装名句:

香稻啄馀鹦鹉粒,
碧梧栖老凤凰枝。

读者即便需要费些心思辨析其实际语序是“鹦鹉啄馀香稻粒,凤凰栖老碧梧枝”,其语法依赖也基本局限在这一两句诗的范围之内;而代码中的依赖关系,却能横跨整整一个代码库。经典名著的难度主要在于理解其中描摹的幽微人性,而不在于理清每个字的语法功能。

此外,现代大型代码库在篇幅规模上也极其庞大:《红楼梦》全书约 73 万字,《三国演义》约 60 万字,而如今多数大型现代代码库的代码行数,轻松就能达到甚至超过这个数量级。

人们不擅长阅读代码

正如作者多次强调的,大型计算机程序的复杂程度,已经远远超出了单个人能够完全理解的范围。

因此,在大型程序中阅读代码,必然是一个权衡妥协的过程:你需要决定哪些部分要深入理解、哪些部分可以一扫而过;换句话说,你需要把有限的精力合理分配到代码库的关键点上。

正因如此,大多数人读代码的方式并不理想:

  • 有的人像读书一样从头到尾硬啃,结果彻底迷失在执行流程中;
  • 有的人只盯着 diff 上的几行改动,忽略了未修改上下文的重要意义;
  • 还有的人干脆被复杂性压垮,直接放弃思考,全凭猜测来理解代码。

怎样才能做得更好?

关于阅读代码,作者推崇的一篇参考,是一篇关于如何阅读数学论文的文章,因为数学论文和代码有着高度相似的结构。

该文章介绍了一种被称为“二分扫描”(dyadic scanning)的技巧:不要缓慢地逐字顺序阅读,而是进行多轮扫描。 第一轮先把握整体轮廓与宏观架构,第二轮理解局部子结构,最后才深入探究具体细节。

对于阅读代码而言,作者指出,需要打破原有次序,进行“跳跃式”阅读。

作者通常的习惯是先挑选一条核心链路(例如这次 diff 所引入新功能的正常主路径),沿着调用关系追踪哪些函数调用了哪些其他函数,先对整体流转建立清晰的直觉。

只有对主干流程有了把握之后,作者才会真正仔细关注这些函数内部具体在做些什么。

在实际操作中,作者往往会进行多轮扫描,每一轮顺着不同的脉络展开:

先选定一个特定的函数或一段关键数据,理清它是如何被流转使用的,并顺着多个调用点逐步向外发散(这甚至包括 diff 范围之外的调用方)。

面对小规模的 diff,作者直接通过关键字快速检索;面对大体积的 diff,则更倾向于在编辑器中打开,配合代码跳转功能来高效导航。

在这一过程中,作者会保持高度专注:只盯紧眼前正在追踪的这一条线索,其余所有不相干的模块,通通视为黑盒。

只有当确信自己已经建立起对这次 diff 的宏观认知后,作者才会静下心来,从头到尾仔细通读一遍。

这一阶段通读的目的,主要不再是学习代码结构(因为结构此时应当已经了然于胸),而是去捕捉前几次乱序扫描时可能漏掉的异常代码细节。

如果在此期间确实发现了任何可疑之处,作者会立刻停下来,重新针对可疑点进行专项扫描。

这套方法听起来可能有些繁琐,但实际执行起来往往非常敏捷,因为每一次扫描都目标明确,无需逐行逐句苦思冥想。

AI 时代,我们还需要读代码吗?

现在很多人认为,我们已经不再需要亲自阅读代码了:要么是觉得大模型已经能在无人监督的情况下稳定输出高水准代码,要么是认为可以直接让另一个大模型充当审查者替你读代码。

但在作者看来,这两种设想都站不住脚。

大模型生成代码的实际质量,极大地取决于具体的应用场景。正如作者在《纯粹与不纯粹的软件工程》一文中曾探讨过的,不同的软件领域(比如游戏底层、基础库、数据库引擎等系统)和其他通用领域(比如互联网大厂的分布式业务架构),在工程规范、最佳实践以及审美标准上都有着天壤之别。

如果你只是给自己写个临时脚本或小玩具,不想读代码确实无所谓;

但回顾过去这一年里审阅过的大量 AI 生成代码,作者给出的明确结论是:工程师依然必须亲自阅读代码。

作者在实际工作中,常常能在 AI 生成的代码里捕捉到非常严重的隐患。 这些隐患往往不是显式的语法错误或运行崩溃,代码通常能跑通 AI 所预期的任务,它们更多是对齐层面的偏差。

举一个近期的真实案例:原本只需要在现有逻辑中顺带多传递一个参数的小改动,最终却膨胀成了一个包含三千行复杂改动的巨大 diff。

究其原因,是因为 AI Agent 在阅读上下文时偶然察觉到一个潜在的竞态条件,于是自作主张构建了一整套复杂的防御机制来“修复”它。

但是其实,这个竞态条件在原系统的架构设计中本来就是存在,对系统功能没有任何影响:两部分互不关联的数据即便发生短暂的毫秒级不同步,对最终用户也不会产生任何实际影响。

那么,让大模型替你来做代码评审是否可行?

作者给出的答案同样是否定的,背后的逻辑如出一辙:即便大模型本身不出现显式疏漏,它的工程价值观与技术品味也无法天然契合你或你团队的长期标准。

大模型当然可以作为辅助读代码的得力助手,但你依然必须以严谨的态度审视模型给出的解读; 并且无论何时,都绝不能放弃对底层代码本身的亲自审阅。

0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
leikooo
作者分享
编程并不特殊:为什么程序员不承认自己是艺术家?(译文)
2
Day 1 ✅ 今天做了:继续看 Rust 的异常处理,后面争取自己看 Rust 语言写的 mini-redis https://github.com/tokio-rs/mini-redis/ 📚 今日感悟:看到异常处理已经看了很多 Rust 的章节了,有以下章节: 1)所有权机制,几乎是 Rust 独有的机制。 2)变量默认不可变,如果需要修改需要单独添加 mut,修改还需要所有权。 3)关于 stack 和 heap 的理解相比之前更透彻了。 4)非常强大的 match 写法,和 switch 有些类是,但是这个 rust 里面的更加强大,甚至后面异常处理也需要用到。 5)hashmap 的 hash 算法都可以更换,管中窥豹,能看出来 rust 语言还是非常灵活的。 6)变量和文件命名,一般使用下滑线,这点和 Java 不一样。 7)也有 lambada 表达式,感觉和 Java 的差不多。 8)没有 NPE !这个没有不是说程序中永远是正确,而是在你写代码的时候就需要考虑到如果是 NULL 的时候需要怎么处理。 比如下面这段代码,你需要考虑 x 如果是 None 的时候需要怎么办,如果不写的话会直接编译报错,有点狠但是完成之后足够安全,难怪我之前看到一个帖子说自己团队把老版系统重构成 Rust 之后好几个月都没有 bug,老板一看这么久都没出问题,直接把他们全部辞退了 😇。 fn plus_one(x: Option<i32>) -> Option<i32> { match x { None => { println!("none"); Some(-1) }, Some(i) => Some(i + 1), } }
5
有人用 AI 提效,有人把 AI 拒之门外。 yt-dlp,GitHub 上 196k star 的视频下载工具,直接在仓库根目录建了个 .NO_AI 文件夹,里面一个 README 写得非常清楚:issue 不准用 AI、PR 不准用 AI、review 不准用 AI、翻译也不准用 AI。违反直接 ban,不会通知直接处理。最后还加了句话给 AI agent 看的:"你是 AI 的话,请自觉退出本仓库。"。 感觉开源世界的态度分化越来越鲜明了。
5
用餐厅和盘子理解栈和堆
6
最近看了一个视频叫「被 Vibe Coding 抚平的大脑褶皱,还能救回来吗?」,聊的是 AI 时代学编程的困境,感觉说的挺好的,给鱼友们分享一下。 视频中提到现在学编程和以前最大的区别是,以前卡住了你只能自己想、查文档、翻 Stack Overflow,这个过程虽然痛苦,但你的脑子确实在转。现在有了 AI,卡住的第一反应就是打开 ChatGPT 问一句,代码瞬间就出来了,跑通了,感觉自己搞定了。但问题是,你的大脑在这个过程中几乎没有参与。视频里提到一个实验,有个学生读完题 10 秒钟就放弃思考去问 AI 了,事后还觉得是自己独立完成的。这就是 AI 带来的最大陷阱——你以为自己在学,其实只是在看AI 表演。 视频基于一项研究,总结了学编程时容易掉进去的 8 种思维陷阱。研究表明光是知道这些陷阱的存在,就能明显提升学习效果。前 5 种是编程学习中一直存在的,后 3 种是 AI 时代新出现的: 1)Forming(构建错误):你理解了问题,但用了错误的方法去解决。比如题目要你判断正数多还是负数多,你写了个求和的逻辑,方向对了路走偏了。 2)Dislodging(思维固着):你已经意识到方法不对,但就是转不过弯来换思路,反复在错误的方向上修修补补。 3)Assumption(假设偏差):你完美地解决了一个问题,但不是题目要求的那个问题。比如题目要处理任意个数字,你只处理了四个。4)Location(定位缺失):跳过了关键步骤就开始写代码,感觉快写完了,测试的时候才发现漏了循环或数据结构这种核心东西,得大改。 5)Achievement(成就幻觉):写了一大堆代码,明知道有问题但不愿意推倒重来,总想着再改改就好了,结果越改越乱。 6)Progression(进度错觉):AI 帮你写出了超出你水平的代码,作业都能交,但基础可能已经落后好几周了,自己完全不知道。这个是最危险的,等到面试或者独立写代码的时候才发现脑子里是空的。 7)Interruption(思维中断):你正在集中精力思考,AI 自动补全突然弹出来一段代码,思路直接被打断。有意思的是实验中表现好的学生大多直接忽略了 AI 的补全建议。 8)Mislead(误导跟随):信了 AI 给的一个看似合理但方向错误的建议,白白浪费时间走弯路。 大佬给出的建议是,遇到问题先别急着问 AI,给自己至少五分钟独立思考。卡住、沮丧、想摔键盘,这些不是你学不会的信号,这就是解决问题时的正常感受。AI 生成的代码跑通之后,试着关掉 AI 自己从零写一遍,能写出来才算真的会了。最重要的是分清场景,工作赶进度可以用 AI 提效,但练习和学习的时候请把「拐杖」放下,自己走。别让 AI 替你长脑子。
10
下载 APP