In defense of not understanding your codebase

In defense of not understanding your codebase

为“不完全理解代码库”辩护

As a software engineer, how well do you have to understand your own codebase? My guess is that people who work on small codebases with low-turnover teams (say, Redis or games like The Witness) would say “obviously you have to understand it completely, otherwise you can’t do good work”. 作为一名软件工程师,你到底需要多了解自己的代码库?我猜,那些在小型代码库且人员流动率低的团队中工作的人(比如 Redis 或像《见证者》(The Witness) 这样的游戏开发团队)会说:“显然你必须完全理解它,否则你无法做出好的工作。”

I’d also guess that people who work on large codebases with high-turnover teams (say, the Google web search backend or GitHub) would say “obviously you can’t understand it completely, you just have to do the best you can in your local area”. These are two largely different ways of programming with different methods, practices and cultures. However, the first group is over-represented in online discussion about software engineering. I want to defend the second group against the first. In many software engineering environments, there’s nothing wrong with being in a state of partial understanding. In fact, in large systems a partial understanding is the best you can do. 我也猜,那些在大型代码库且人员流动率高的团队中工作的人(比如 Google 网页搜索后端或 GitHub)会说:“显然你不可能完全理解它,你只需要在自己负责的局部区域尽力而为即可。”这是两种截然不同的编程方式,有着不同的方法、实践和文化。然而,第一类群体在关于软件工程的在线讨论中占据了过高的比例。我想为第二类群体辩护。在许多软件工程环境中,处于“部分理解”的状态并没有什么错。事实上,在大型系统中,部分理解已经是你所能做到的极限了。

Against “programming as theory building”

反对“编程即理论构建”

The best articulation of the “you have to understand your codebase” side is Peter Naur’s famous paper Programming as Theory Building. I like this paper, but I think it goes too far in that direction. Naur’s core point is that when programmers work on a program, the code is really just a by-product, and the main product they’re working on is their “theory of the program”. That’s made up of their intuitive sense of what’s happening and why, which can only be partially captured by code or documentation. If they lost the code, they could rewrite the program easily. If they lost their understanding (say, if the team experienced 100% turnover), they would struggle to make sense of the code. “你必须理解你的代码库”这一观点的最佳阐述来自 Peter Naur 的著名论文《编程即理论构建》(Programming as Theory Building)。我喜欢这篇论文,但我认为它在那个方向上走得太远了。Naur 的核心观点是:当程序员编写程序时,代码实际上只是副产品,他们真正构建的主要产品是他们对“程序的理论”。这由他们对程序运行机制及其原因的直觉组成,而这些直觉只能通过代码或文档部分地捕捉。如果他们丢失了代码,他们可以轻松重写程序;但如果他们丢失了理解(例如,如果团队经历了 100% 的人员流失),他们将很难理解这些代码。

So far, so good, but Naur goes further than this. He says that the theory should not be reconstructed from the code. According to Naur, you’re better off scrapping the program entirely and having a new team rebuild it from scratch, building up a new theory in the process: 到目前为止,这都没问题,但 Naur 的观点更进一步。他认为,理论不应该从代码中重新构建。按照 Naur 的说法,你最好彻底废弃该程序,让一个新团队从零开始重建,并在过程中建立一套新的理论:

“reestablishing the theory of a program merely from the documentation, is strictly impossible … [therefore] the existing program text should be discarded and the new-formed programmer team should be given the opportunity to solve the given problem afresh” “仅仅通过文档来重建程序的理论是绝对不可能的……[因此] 应该丢弃现有的程序文本,并让新组建的程序员团队有机会重新解决给定的问题。”

Anyone who’s been an effective software engineer at a large company knows that Naur is dead wrong about this. There are at least two reasons. First, you simply can’t rebuild large software systems from scratch. Sufficiently large systems (if they have users) contain thousands of weird cases and quirks that cannot be reimplemented. Even a team that’s intimately familiar with the system couldn’t do it: there’s just too much stuff to juggle. Successful rewrites always start by carving out the existing codebase into small isolated chunks, then rewriting one chunk at a time. In other words, rewriting a software system involves making a bunch of changes to the old system. If you can’t change the old system, you certainly can’t replace it with a new one. 任何在大公司担任过高效软件工程师的人都知道,Naur 在这一点上大错特错。至少有两个原因。首先,你根本无法从零开始重建大型软件系统。足够大的系统(如果有用户的话)包含成千上万种奇怪的边缘情况和特性,这些是无法重新实现的。即使是一个对系统非常熟悉的团队也做不到:需要处理的事情实在太多了。成功的重写总是从将现有代码库拆分为小的独立块开始,然后一次重写一个块。换句话说,重写软件系统涉及对旧系统进行大量修改。如果你无法修改旧系统,你当然也无法用新系统替换它。

Second, abandoned systems are revived all the time. In a tech company with hundreds of millions of lines of code and thousands of engineers, it’s not uncommon for a codebase to have nobody left who’s familiar with it. All it takes is a few people to quit at the wrong time, or for a codebase to be unmaintained for a year. Not only have I seen other teams do this, I have personally taken ownership of abandoned codebases, figured them out, and gotten to a point where I could effectively work with them. It takes time, but building a new theory of the codebase is possible. You start by understanding one flow end-to-end, then slowly branch out from there, making careful changes as you go. 其次,被废弃的系统经常会被重新启用。在一家拥有数亿行代码和数千名工程师的科技公司里,代码库中没有熟悉它的人的情况并不罕见。只需要几个人在错误的时间离职,或者代码库一年无人维护,就会发生这种情况。我不仅见过其他团队这样做,我自己也曾接手过被废弃的代码库,弄清楚它们,并最终能够有效地使用它们。这需要时间,但为代码库构建一套新理论是可能的。你从理解一个端到端的流程开始,然后慢慢向外扩展,并在过程中进行谨慎的修改。

In sufficiently large codebases, everyone operates with an incorrect theory of the program. The defining feature of modern software systems is that they’re just way too big for anyone (or even a whole team) to keep in their head: nobody understands it all. To be effective, you have to figure out a way to work with a merely partially-correct theory. This is why I keep going on about taking a position and confidence. If you’re not sure about something, you can’t just sit back and wait for someone with a perfect understanding to come and give you the answer. If you’re a competent engineer, that person is you. You have to grit your teeth, make your most educated guess, and then deal with the consequences. 在足够大的代码库中,每个人都在使用一套“不正确的程序理论”进行工作。现代软件系统的定义特征是它们太大了,任何个人(甚至整个团队)都无法将其完全装进脑子里:没有人能完全理解它。为了保持高效,你必须找到一种方法,在仅有部分正确理论的情况下工作。这就是我一直强调“立场”和“自信”的原因。如果你对某事不确定,你不能只是坐着等待一个拥有完美理解力的人来给你答案。如果你是一名合格的工程师,那个人就是你自己。你必须咬紧牙关,做出你最有根据的猜测,然后承担后果。

To be generous to Naur, it’s possible that in 1985 the average size of a program was several orders of magnitude smaller than today, and that when Naur writes about “large programs” he’s not talking about tens of millions of lines of code. Naur’s first example of a large program is a 200,000 line industrial monitoring program, and his second example is a compiler. In 1987, the first version of the compiler GCC was about a hundred thousand lines of code; in 2015 GCC was over fourteen million lines. I can believe that rewriting one or two hundred thousand lines of code is relatively straightforward, particularly if you get to reuse existing tests. Not so for one or two million. 对 Naur 宽容一点来说,1985 年程序的平均规模可能比今天小几个数量级,当 Naur 谈论“大型程序”时,他指的并不是数千万行代码。Naur 举的第一个大型程序例子是一个 20 万行的工业监控程序,第二个例子是一个编译器。1987 年,GCC 编译器的第一个版本大约有 10 万行代码;而到了 2015 年,GCC 已经超过了 1400 万行。我可以相信重写 10 万或 20 万行代码相对简单,特别是如果你能复用现有的测试的话。但对于 100 万或 200 万行代码来说,情况就完全不同了。

Theory building is one tradeoff among many

理论构建只是众多权衡中的一种

LLMs are often cited as a tool that’s bad because it impedes the ordinary process of theory-building. I think this is overly simplistic. Like many software tools, LLMs are a double-edged sword: they make it harder to construct a detailed mental theory of the software, but they allow you to build a partial theory quickly and they can help you leverage that partial theory more effectively. This is a complex tradeoff that I’m still thinking about. 大语言模型 (LLM) 常被认为是一种糟糕的工具,因为它阻碍了常规的理论构建过程。我认为这种看法过于简单化了。像许多软件工具一样,LLM 是一把双刃剑:它们确实让你更难构建关于软件的详细心理理论,但它们能让你快速构建部分理论,并帮助你更有效地利用这些部分理论。这是一个我仍在思考的复杂权衡。

Setting LLMs aside, I’m confident that it’s silly to say that anything that interferes with your theory of the software must be bad. Here is a partial list of other things that make it harder to maintain a theory: 抛开 LLM 不谈,我确信说“任何干扰你软件理论的东西都是坏的”是愚蠢的。以下是其他一些让维护理论变得更困难的事情的部分列表:

  • Other people being allowed to write code in your codebase
  • 允许其他人向你的代码库中编写代码
  • Having to implement legally-required features like accessibility and data protection
  • 必须实现法律要求的特性,如无障碍访问和数据保护
  • Allowing your colleagues to quit their jobs or move between teams
  • 允许你的同事离职或在团队间调动
  • Having to…
  • 必须……