你永远不应该做的事,第一部分

6 minute read

Stackoverflow 的联合创始人 Joel 的这篇文章,虽然已经过去了20多年,读起来依旧能让人起共鸣。软件开发行业有别与其他行业,在这里每天都有新技术出现,旧技术被淘汰,但有些东西却会一直存在,有些错误20年前的人会犯,20年后的人也依旧会犯,因为人性亘古不变。

以下是翻译正文。


Netscape 6.0 终于要进入其第一个公开测试版了。从来没有一个5.0版本。上一个大版本,即4.0版,几乎是三年前发布的。在互联网世界里,三年是一个非常长的时间。在这段时间里,网景公司无助地坐视他们的市场份额直线下降。

我批评他们在两次发布之间等了这么久,这有点自作聪明。他们不是有意为之的,是吗?

嗯,是的。他们是有意为之的。因为他们犯了一个任何软件公司都可能犯的最糟糕的战略错误:他们决定从头开始重写代码。

网景并不是第一家犯这种错误的公司。Borland 也犯了同样的错误,他们买下了 Arago,并试图把它变成 Windows 版的 dBase,这是一个注定要失败的项目,花了这么长的时间,以至于微软的 Access 吃掉了他们的午餐,然后他们又从头开始重写 Quattro Pro,它的功能之少让人吃惊。微软几乎犯了同样的错误,在一个被称为 “金字塔” 的注定失败的项目中试图从头开始为 Windows 重写 Word,该项目被关闭、扔掉,并被扫到地毯下面。不过,微软很幸运,他们从未停止过在旧代码库上的工作,所以他们有东西可发布,这只是一场财务灾难,而不是一场战略灾难。

我们是程序员。程序员在内心深处是建筑师,当他们到达一个地方时,他们想做的第一件事就是把这个地方推平,然后建造一些宏伟的东西。我们对修补、改进、种植花坛这种渐进式的改造不感兴趣。

有一个微妙的原因导致程序员总是想扔掉代码,重新开始。那就是,他们认为旧的代码是一团糟。而这里有一个有趣的观察:他们很可能是错的。他们之所以认为旧的代码是一团糟,是因为编程的一个基本规律:

读代码比写代码更难。

读代码比写代码更难。

读代码比写代码更难。

(注:重要的事情说三遍)

这就是为什么代码重用是如此困难。这就是为什么你的团队中的每个人都喜欢写一个自己的函数,去将字符串分割成字符串的数组。他们写自己的函数,因为这比弄清旧函数的工作原理更容易、更有趣。(注:深有同感,我参与的项目里负责请求外部HTTP接口的代码就有好几套)

作为这一公理的推论,今天你几乎可以问任何一个程序员关于他们手头上的代码。他们会告诉你:”这是个大杂烩”。”我最想做的就是把它扔掉,重新开始”。

为什么是一团糟?

他们说,”嗯,看看这个函数。它有两页纸那么长! 这些东西都不属于那里! 我不知道这里大部分的API调用是做什么用的。”

在 Borland 新的 Windows 电子表格上市之前,Borland 的创始人 Philippe Kahn 被媒体大量引用,吹嘘 Quattro Pro 将比 Microsoft Excel 好得多,因为它是从头开始写的。所有的源代码都是新的! 说的好像源代码会生锈一样。(注:至今依然有很多人希望用新的某某语言去重写老的关键系统,好吧。。祝他们好运)

认为新代码比旧代码好的想法显然是荒谬的。旧的代码已经被使用。它已经被测试过了。很多错误已经被发现,而且已经被修复。它没有任何问题。它不会因为在你的硬盘上闲置而产生bug。恰恰相反,宝贝! 难道软件就应该像一个老道奇镖车一样,放在车库里就会生锈吗?难道软件就像一只泰迪熊,如果不是用全新的材料制作的,就会有点恶心?

回到那个两页纸长的函数。是的,我知道,这只是一个显示窗口的简单功能,但它上面已经长出了小毛发之类的东西(注:这里类比代码开始腐败),没有人知道为什么。好吧,我来告诉你为什么:那些是bug fixes。其中一个修复了 Nancy 在试图将这个东西安装在没有 IE 浏览器的电脑上时出现的那个错误。另一个修复了在低内存条件下出现的那个错误。还有一个修复了当文件在软盘上,而用户在中途拔出软盘时出现的那个错误。那个 LoadLibrary 的调用很难看,但它使代码在旧版本的 Windows 95 上工作。

这些错误中的每一个都是经过几周的实际使用才被发现的。程序员可能花了几天时间在测试环境里重现这个 bug 并修复它。它像很多 bug 一样,修复可能只是一行代码,甚至可能只是几个字符,但就这几个字符花费了大量的工作和时间。

当你扔掉代码,从头开始时,你就扔掉了所有的知识,扔掉了所有的那些bugfix,扔掉了多年的工作成果。

你正在丢弃你的市场领导地位。你正在给你的竞争对手送上两三年的时间作为礼物,相信我,这在软件领域是很长的时间。

你正在把自己置于一个极其危险的境地,你在接下来几年内只能使用旧版本的代码,完全无法做出任何战略改变或对市场要求的新功能做出反应,因为你没有可发布的代码。与其这样,你还不如在这段时间内关闭业务。

你正在浪费大量的金钱来重新编写已经写好的代码。

有替代方案吗?人们的共识似乎是,老的网景公司的代码库真的很糟糕。好吧,它可能真的不算好,但是,你知道吗?它在很多现实世界的计算机系统上运行得非常好。

当程序员说他们的代码是一团糟时(他们总是这样),他们指的是三类问题。

首先,架构问题。代码没有被正确分解。负责网络处理的代码里不知道从哪里会弹出了一个对话框;这应该在用户界面代码中处理。这些问题可以通过小心翼翼地移动代码、重构、改变接口,一点点地解决。它们可以由一个程序员小心翼翼地工作,并一次性地检查他的改动,这样就不会有其他人受到干扰。即使是相当重大的架构变化也可以在不丢弃代码的情况下完成。在 Juno 项目中,我们曾经花了几个月的时间来重新架构:只是把东西移来移去,把它们清理干净,创建有意义的基类,并在各模块之间创建清晰的接口。但是我们是在现有的代码基础上小心翼翼地进行的,我们没有引入新的错误,也没有丢弃正在运行的代码。

其次,代码运行效率很低。据说,网景的渲染代码运行很慢。但这只影响到项目的一小部分,你可以优化甚至重写。但你不需要重写整个项目。在优化速度时,1%的工作可以让你得到99%的收获。

第三,代码(可能)很难看。我所做的一个项目实际上有一个数据类型叫做 FuckedString。另一个项目开始时使用下划线作为成员变量的开头,但后来转而使用更标准的 m_。所以一半的函数以”_“开头,一半以 m_开头,看起来很丑。坦率地说,这种事情你可以用 Emacs 的键盘宏5分钟内就能解决,而不是从头开始重写项目。

重要的是要记住,当你从头开始时,绝对没有理由相信你会比第一次做得更好。首先,你甚至可能没有编写第一版代码时相同的编程团队,所以你实际上并没有 “更多的经验”。你只是会再次犯大部分的旧错误,并引入一些原始版本中没有的新问题。(注:我不止一次的见证了某个开发完成上线的功能需要由另外的团队来继续迭代,负责接手的团队由于没有相关的语言经验,不得不使用其他语言重写一遍)

当构建大规模的商业软件时,创建一个新的项目就扔掉一个旧项目的这种老套路是很危险的。如果你在实验性地写代码,当你想到一个更好的算法时,你可能会想把上周写的函数撕掉。这很好。你可能想重构一个类,使其更容易使用。这也是可以的。但是扔掉整个程序是一种危险的愚蠢行为,如果网景公司真的有一些具有软件行业经验的成年人监督,他们可能就不会这般的搬起石头砸自己的脚了。(注:这里Joel调侃当初做出重构决策的人的行业经验就像未成年人一样)

(全文完)