跳到正文
Hacker News· swah·· 9 天前AI 评分28

如何阅读代码:为什么代码不能像书一样从头读到尾

How to Read Code

AI 导读

软件工程师大部分时间在读代码改动(diff)而非全新程序,但代码的执行顺序由计算机决定、结构复杂度远超英文文本,因此像读书一样顺序阅读往往失效。作者主张采用源自数学论文阅读的"dyadic scanning":先多轮跳跃式追踪调用链把握整体结构,最后才逐行通读以捕捉遗漏的异常细节。

正文 · AI 翻译

每个人都知道怎么读书。从第一页开始,按顺序逐词阅读1,不时停下来思考,直到读到最后一页的最后一个词。读报纸文章、诗歌或电子邮件都是这样。为什么读代码会有所不同呢?

为什么代码不同

英文文本的设计就是按顺序阅读的。事实上,除了你希望读者如何消费它之外,文本的顺序几乎没有任何约束。在写这篇文章时,我可以按任何我喜欢的顺序来安排我想传达的想法。而代码则是为计算机运行而设计的。因此,顺序主要由非人为因素决定。我不能仅仅因为觉得某行代码能为人类读者提供更好的程序介绍,就把它2移到文件或函数的开头。

另一个重大区别是,英文文本总是作为最终成品被阅读,而代码通常作为差异(diff)被阅读。我们软件工程师大部分时间都在阅读对现有代码的细微改动,而不是全新的程序。想象一下,如果读这篇文章也是这样。你会按照我写作的顺序阅读每一版草稿,这里读到一个改动的句子,那里读到一个新增的句子。很容易就会失去对整体脉络的把握。

第三个原因——我以文学和诗歌爱好者的身份这么说——是代码在结构上比英文文本复杂得多。即使是著名的难懂书籍,在句法上也比大多数计算机程序简单(例如,语法依赖大多局限在一个段落内,而代码依赖可以跨越整个代码库)。它们的主要难点在于理解所讨论的人性细微之处,而不在于理解每个词的语法功能3。大型代码库也更长:《战争与和平》大约有60万个单词,而大多数现代大型代码库有同样多的行数。

人们不擅长阅读代码

正如我多次 说过的,大型计算机程序太复杂了,单个人无法完全理解。因此,在大型程序中阅读代码是一个妥协的过程:决定哪些部分要彻底掌握,哪些部分可以略过;或者将你有限的心智能力分配到整个代码库中。

正因为如此,大多数人阅读代码的能力很差。他们像读书一样从头到尾艰难地读,结果迷失了执行流程。或者他们只是读一遍差异,却忽略了代码中未编辑部分的重要性4。或者他们干脆被复杂性打败,放弃并猜测它的含义。你怎么能做得更好呢?

我如何阅读代码

我读过的关于阅读代码的最佳文章是这篇关于阅读数学论文的文章,它们有相似的结构。作者描述了一种称为“二元扫描”的技巧。你不是缓慢而顺序地阅读,而是进行多次遍历:首先弄清整体结构,然后是子结构,最后才是细节。

对于代码5,这意味着乱序阅读。我喜欢挑一条重要的路径(比如,diff 中引入的功能的正常路径),然后追踪哪些函数调用了哪些其他函数,只是为了感受一下流程。只有在我对此有了很好的感觉之后,我才会密切关注这些函数实际在做什么。

通常我会进行多轮阅读,每轮跟随不同的线索。我会拿一个函数或一块数据,试着弄清楚它是如何被使用的,并在此过程中扩展到多个调用点(包括 diff 之外的调用点)。对于小的 diff,我只需用 ctrl+f 搜索函数名来跳转;对于大的 diff,我会在编辑器中打开它,并用 ctrl+click 来更方便地导航。我尽量只专注于我当前正在看的东西:其他一切都被当作黑盒。

一旦我确信自己理解了 diff,我才会坐下来从头到尾仔细阅读它。这次阅读的目的与其说是了解结构——到这时我应该已经知道了——不如说是为了捕捉之前乱序阅读时没有注意到的任何奇怪的代码片段。如果我确实看到任何不寻常的地方,我就会回去继续做多轮阅读。

这听起来可能很慢。但实际上每一轮都很快,因为我并不是在费力地琢磨每一行代码。

AI 不能直接帮你做这件事吗?

现在很多人说,你不再需要阅读代码了:要么是因为 LLM 现在能可靠地生成高质量代码而无需监督,要么是因为你可以直接让一个审查 LLM 替你阅读代码。我认为这两种想法都是错误的。

显然,LLM 代码的质量取决于上下文。正如我在Pure and impure software engineering中所写,一些软件领域(游戏开发、库、数据库等工具)与其他软件领域(比如大公司的分布式系统)有着截然不同的工程标准、实践和价值观。如果你只是为自己做一个工具,如果你不想读代码,你大概可以不读。但在今年读了一堆 AI 生成的代码之后,我可以肯定地说,你仍然必须读它。

我经常在 AI 生成的代码中发现巨大的错误。这些不是 bug——代码通常做了 AI 想让它做的事——而更多是对齐问题。举一个最近的例子,对现有代码做一个小改动以传入一个额外的值,结果膨胀成了一个复杂的三千行 diff,因为 agent 注意到一个竞态条件,并构建了一套复杂的机制来“修复”它。事实上,这个竞态条件在设计上就是无害的:两份不相关的数据可能会短暂不同步,但不会对客户造成影响。

LLM 能直接替你阅读代码吗?不能,原因相同:即使它们不犯错误,它们的技术价值观也不会与你的或你公司的价值观一致。你仍然可以使用 LLM 来帮助你阅读代码,但你必须仔细阅读和审查 LLM 的输出,而且你也应该仔细阅读代码本身。


  1. 事实上,这并不是许多人在实践中的阅读方式。由于注意力不集中而跳过单词甚至整段,或者读完整本书却没有花时间思考,这些都很常见。但至少在理论上,每个人都同意这才是你应该阅读的方式。

    ↩
  2. 当然,你有时可以做到其中一些(在某些编程语言中比其他语言更容易做到)。但主要约束是计算机。代码能够编译(或运行时没有语法错误)比代码可读性更重要。

    ↩
  3. 我无意低估对伟大文学作品进行句法解析的重要性,我认为这既困难又是一项被低估的技能。例如,我通读了《哈姆雷特》的第一场,直到看到:“Sit down awhile / And let us once again assail your ears / That are so fortified against our story / What we have two nights seen.” 即使是这样一个句子,也需要花功夫才能确定“That”指的是“ears”,而“What”指的是“story”(或者等价地说,它前面隐含了一个“with”)。正如一个函数可以有许多分散的调用者一样,文本中的一个句子可以改变许多其他句子的意义。

    ↩
  4. 在之前的一篇文章中,我把这称为大多数工程师在代码审查中犯的主要错误。

    ↩
  5. 这里我谈论的是有意义的 diff:涉及几百行或更多内容的改动。琐碎的 diff 可以端到端地读完。

    ↩

如果你喜欢这篇文章,可以考虑订阅我的新文章邮件更新,或者在 Hacker News 上分享它。

这是一篇相关文章的预览,它与本文共享标签。

我看到工程师在代码审查中犯的错误

在过去两年里,代码审查变得重要得多。现在使用 LLM 生成代码很容易,但审查代码仍然同样困难。许多软件工程师现在花在审查自己 AI 工具输出上的时间与审查同事代码的时间一样多(甚至更多)。

我认为很多工程师没有正确地进行代码审查。当然,进行代码审查有很多不同的方式,所以这在很大程度上是我工程品味的体现。
继续阅读...


来源:Hacker News · seangoedecke.com