软件重写可能是一项非常危险的工作——存在许多非常真实的成本和风险,甚至可能会让最善意的努力付诸东流。然而我们确实重写了!并且经常这么干。虽然明知道会有风险,业务和技术还是一起宣布:“该死的鱼雷,我们正在重写这堆旧代码,这次我们一定会成功的!”

程序员为什么热衷于重写软件?

我想在本文中探讨的问题是“为什么”。考虑到所有的成本和风险,为什么我们还经常选择重写可运行的遗留系统,而不是局部重构呢?根据心理学、社会学和其他领域的分析,我想揭示一些微妙或不那么微妙的力量,这些力量推动着我们每个人走向全面重写的道路。

我想澄清一下。我绝对不认为重写总是错误的选择——重写有很多好的、合理的理由,我们将会在后面的文章中介绍。

创作的乐趣

开发人员喜欢创作的过程。我们天生就是构建者。尽管我们知道这项工作通常需要我们勇敢地冒险进入他人代码的黑暗角落,以消除一些潜在的缺陷或调整一些深奥的计算,但这并不是吸引我们编程的原因。这只是一种必然的不幸。如果有机会,我们还是希望构建自己的系统。我们希望以一种对我们有吸引力的方式,以一种对我们有意义的方式,来制定解决方案。虽然冒着感情用事的危险,但编程是我们的艺术。同样的,画家宁愿从一片空白的画布开始作画,而不愿在另一幅画像中作画。

事实上,不仅是程序员喜欢自己创建的东西,所有人都是这样的。Dan Ariely 在其著作《回报:影响人们动机的隐藏逻辑》(The Hidden Logic That Shapes Our Motivations)一书中,提到了一个有趣的轶事,强调了人类的这种倾向:

早在 20 世纪 40 年代,当大多数女性都在家工作时,一家名为 P. Duff and Sons 的公司就推出了盒装蛋糕粉。家庭主妇们只需加水,在碗里搅拌面糊,将其倒入蛋糕盘中,烘烤半小时,然后调味,他们即可吃到一份美味的甜点。但令人惊讶的是,这种蛋糕粉卖得并不好。原因与味道无关,与流程的复杂性有关——但这不是我们通常认为的复杂性。
Duff 发现,家庭主妇们觉得这些蛋糕不像是她们自己做的;在赋予人们创造力和有意义的所有权方面,她们付出的努力实在太少了。因此,该公司将鸡蛋和奶粉从蛋糕粉中取出。这次,当家庭主妇们添加新鲜的鸡蛋、油和纯牛奶时,她们会觉得自己参与到了制作过程中,并且对最终产品更加满意了。

Ariely 接着描述了一些科学研究,这些研究显示了同样的效果:事实上,我们天生就喜欢享受创作的乐趣,而且我们可能会过分重视自己创作的成果(与我们实际创建出的东西有多好无关)。

我认为这就直接说明了我们作为开发人员对维护遗留系统的感觉。当然,我们能够完全按照某种规定的模式在报表中添加一个新列,或者在屏幕上添加一个按钮,但这并没有给我们带来那种对工作的依恋感或意义。我们想要的是打碎我们自己的鸡蛋,并加入我们自己的牛奶和油。换句话说,我们的大脑可能会自然而然地倾向于重写而不是重构。

维护之苦

从创建过程中寻求乐趣的另一面,是可以避免因繁琐的维护工作而感到沮丧。维护遗留系统总是会带来许多挫败感。随着时间的流逝,软件趋于衰退。业务需求发生了变化,迫使开发人员将原来的厨房改造成前门廊。关键 Deadline 迫使我们偷工减料,并使用 TODO 和复制粘贴的方式交付代码。随着时间的流逝,新老开发人员在系统中开发的东西会在系统架构中形成“沉积层”——“你可以从风格上看出来,这段代码来自 pre-IPO 时期,依赖注入之前”。

程序员为什么热衷于重写软件?

在这样的遗留代码中工作可能令人沮丧。即使只是要修复最简单的 bug,也可能需要我们追踪复杂的执行路径,因为它们会在不同的软件贫民窟里进进出出,在那里杂草丛生,而破烂的窗户尚未修复。将系统的逻辑(或非逻辑!)掌握在我们的脑海中可能是一项艰巨的任务。当我们尽职尽责地接受我们的命运,修复这些缺陷,并添加那些小的改进时,我们一直在思考如何才能以不同的方式来做这件事。我们会想,如果能一把火把这个充满复杂性和技术债的软件堡垒烧掉,从头开始,那该有多好呀。

然而,对于最终用户,甚至对于企业而言,情况可能就不一样了。当然,遗留系统看起来可能不那么美观,或者可能需要比首选系统更多的点击或延时,但它还是可以工作的。该系统功能正常,相对可靠,并且围绕它的业务流程虽然不是最佳的,但至少能被很好地理解。但是对于开发人员,尤其是那些不是原始创建者的开发人员来说,这个系统代表的不是舒适,而是挫折和约束。我们在里面工作的时间越长,就越想逃离并重写它。

利己主义与技术趋势

除了寻求乐趣或避免挫败感之外,作为开发人员,我们也很清楚什么工作才对自己的职业生涯有实际好处,这通常都不会是维护遗留系统。 2019 年的 Stack Overflow 开发人员调查显示,语言的新颖性与开发人员的收入直接相关。薪资排名前五位的语言,平均年龄只有 15 岁,而排名后五位的语言,平均年龄则为 39 岁。

语言年龄平均工资
Clojure13$90k
F#15$80k
Go8$78k
Scala17$78k
Elixir9$76k
按平均工资排名前 5 的语言


语言年龄平均工资
HTML/CSS25$55k
VBA27$55k
Assembly71$52k
C48$52k
Java25$52k
按平均工资排名后 5 的语言


当然,还有很多其他因素也在起作用,但是在所有条件都相同的情况下,很显然,如果我们不跟上新技术的步伐,那么我们能赚的钱就会更少。对于最近进入就业市场的任何一个开发人员来说,这并不足为奇。招聘人员、简历筛选机器人,甚至其他开发人员都在通过我们的经验寻找最新的热门词汇:微服务、Kubernetes、渐进式 Web 应用程序或其他前沿技术。越新越前沿,越好。其逻辑大概是,与仅像仆人一样从事遗留系统维护工作的人员相比,掌握最新技能的开发人员必然是更有动力也是投入了更多精力的(讽刺的是,从前雇主的角度来看,维护遗留系统可能才是正确的)。

这样的逻辑确实有一些道理。编程工作确实需要高度的探索和实验,因此对学习的热情真的很重要。但是,我认为我们这个行业有一种趋势,有时更倾向于追逐时髦的新技术而非实际需要的技术。当我们选择一种语言、工具或框架时,我们当然希望选择一种最适合这项工作的,但是我们也要考虑这个选择对我们来说意味着什么。我们是使用诸如 Java 之类的成熟但笨拙的语言的人,还是选择使用 Go 或 Kotlin 之类的富有“远见”和“创新性”的人呢?从某种意义上说,我们使用的技术就像我们穿的衣服一样——它们诉说着我们是谁,我们代表了什么,我们的价值是什么。从某种程度上说,技术和时尚是一样的。

当然,现在这个类比还远远不够完美,但看看社会学家 Herbert Blumer 对时尚周期的描述,其中确实有一些相似之处:

精英阶层试图通过明显的标记或标志,比如独特的着装,来使自己与众不同。然而,紧随其后的阶层成员采用这些标志来作为满足他们为获得较高地位而努力的一种手段。它们依次被它们下一阶层的成员所复制。

通过这种方式,精英阶层的显著标志就会在阶层金字塔中逐渐向下过滤。但是,在这个过程中,精英阶层失去了这些独立身份的标志。因此,又会导致设计出新的区别标志,这些标志又被下面的阶层所复制,从而重复了这一循环。

简而言之,无论是服装、音乐还是编程,时尚都与阶级分化相关。精英们不断创新,大众赶上来,所以他们可以与精英联系在一起。然而,一旦他们这样做了,精英就不再是“精英”了,因此他们必须重新创新。大众最终又会追随而来,循环往复,永无止境。

当谈到重写还是重构的问题时,我认为这可能是另一种微妙的力量,它促使作为开发人员的我们朝着全面现代化的选择迈进。维持现有系统的活力,就像穿十年前的衣服一样。当然,它们仍然可以让你保持温暖和干燥,但它们已经破旧了,可能无法向世人展示你想要展示的形象。对于那些不喜欢紧跟技术潮流的人,我们至少要知道,这确实会对我们的职业生涯产生明显的影响。所以,要么保持步调一致,要么减少收入。

直觉错误与思维捷径

抛开动机和个人利益不谈,还有另一种强大的力量可以迫使我们相信,对于遗留系统而言,重写是显而易见的最佳决策,即使事实并非如此。那就是我们自己的直觉。

Daniel Kahneman 在关于决策和认知偏见的著作《思考,快与慢》中解释到,尽管我们很容易就所有类型的问题形成意见(并对这些意见充满了信心),但这些问题通常是基于简单的思维捷径,而不是可靠的推理。对于利害关系不大的决策来说,这可能没什么问题,但是对于像重构或重写整个系统(这可能要花费数百甚至数千小时的精力)这样的问题,风险巨大。我们需要确认,问题到底能不能得到解决。

我们使用的一个微妙的思维捷径是替代。当遇到需要深入思考和分析的问题(这是一项艰苦的工作)时,我们通常会把它替换成一个更简单的问题,这样我们就可以更快地解答了。例如,想要知道重写与重构是否值得,我们应该要了解重写的成本是多少,它能为组织带来多少价值,何时实现投资回报(ROI)等。但这太难了!因此,我们会用更容易回答的问题来替代这个问题,比如,“现有系统是否有问题?”或者“ UI 是否过时了?”。答案自然是肯定的。这么做的话,虽然看起来好像解决了原始(困难的)问题,但实际并没有,我们只是将其替换成了更容易解答的问题。

多年来,我一遍又一遍地看到这种辩解。与其停下来试图回答真正困难的问题(重写的净值是否大于重构的净值),我们经常回过头来讨论更简单的替代问题。我们会说“我们要重写,因为遗留系统难以使用”或者“因为没有人能理解它”。是的,这些答案确实相关,但还不够。也许没有人能理解代码是真的,但是一个人要理解这些代码需要多少成本呢?这个成本与从头开始重写整个系统相比,是更大还是更小呢?bug 和技术债也是如此,成本和回报到底是多少?

当然,现在我们生活在一个瞬息万变的世界里——我们没有无限的时间来分析、制定商业案例并发表意见。但是对于一个像重写或重构这样至关重要的问题,我们应该试着放慢速度,确保我们至少回答了真正的问题,而不是回答了一个更容易的替代问题。

“抛弃”的文化

推动我们重写的最后一股力量并非植根于我们自身,而是植根于我们周围的社会。我们生活在一种“抛弃第一”(throw-away)的文化中。我们的食品和饮料装在一次性容器里;购买的产品都是用过多的塑料和纸进行包装的;我们的电器和设备都是为淘汰而设计的,因此它们经常连修都没法修,即使我们想修也不行。换句话说,我们已经习惯了不加思索地丢掉一些东西——不管它是有了瑕疵、破裂了、故障了,还是只是稍微有点旧或有点磨损,我们都会选择扔掉它。

因此,我认为这种“抛弃第一”的态度也有可能影响我们对软件的看法。当一个系统老化时,我们的倾向不是拔出胶带和 WD40 来延长它的使用寿命,而是将它扔掉,出去买(或重写一个)新的型号 。实际上,“遗留”(legacy)一词似乎总能引起开发人员的同情。“真遗憾,你不得不为此工作。”他们会问,“你打算什么时候重写呢?”

程序员为什么热衷于重写软件?

公平地说,软件开发是一个不断改进和创新的行业。我们一直在学习更好、更简洁的做事方式,然后将它们整合到我们的工具、框架和语言中。当他们可以使用 Java 8 的流(stream)和 Lambda 表达式,或更简洁的函数式语言(如 Kotlin 或 Scala)时,没有人会再刻意去使用 Java 7。此外,新的平台和设备不断发布,而我们现有的系统可能无法移植到这些平台和设备上。因此,我并不是说,以旧换新一定是个轻率的选择。

但我确实认为,意识到这种潜在的偏见是有帮助的。当我们过快地放弃一个可能还算可靠的遗留系统(尽管它有些旧或复杂)时,组织可能会付出代价。在接下来的文章中,我将讨论遗留系统现代化的三个不同“R”:“修复”(Repair)、“重用”(Reuse)和“再利用”(Recycle)。与其把我们现有的系统看作是需要敬而远之的东西,我们应该理解并利用好那些目前仍在起作用的组件和元素,并只尝试重写那些需要重写的组件和元素。

原文链接:

http://rewriteorrefactor.com/chapter-3-why-we-rewrite-even-when-we-shouldnt.php