重构代码花 1 年!程序员血泪史:千万不要重写代码!
作者 :Roman Luzgin
原文:http://suo.im/6qIUhX
以下为译文:
# 重写代码消耗了12个月!
我们从头开始重写代码浪费的时间。
你能想象在软件行业,12个月的时间没有任何新产品推出,没有任何新版本更新吗?
真的,我不由自主地问自己这个问题:
在这个快速发展的世界里,12月的时间能让我们做多少事情?
“2015年1月20日,星期二,下午5:10,AntiMalware软件终于进入了第一次公测。”
经过几十个小时的不眠不休后,第一个版本的软件说明书终于发布到了网站上,这标志着我们的新旅程的开始。
我在一家为企业和终端用户提供安全软件的小型网络安全公司工作。我们开发的软件保护用户免受恶意软件的侵害。如果用户的电脑被恶意软件感染,我们的软件会帮助他们清理。AntiMalware就是其中一个软件。
第一个测试版收到的反馈令人鼓舞。我们有四个开发人员为这个产品工作,不断地修复Bug, 改进产品功能,推出新版本。
# 第一个稳定版本
经过两个月的纠错、功能改进和编码工作,我们发布了AntiMalware的第一个稳定版本。
看看用户怎么说?
大多数用户的反馈都很好,他们喜欢这个产品。这让我们的团队深受鼓舞,大家卯足了劲地干活,来改进这个产品的核心功能。
# 进入市场
2016—2017。
大风暴来临前的黄金岁月。
AntiMalware软件处于它的最佳期,它成为了我们的旗舰产品。用户纷纷把它推荐给他们的朋友们。所有与安全相关的博客和论坛也都在推荐这个软件。它成了拯救被恶意软件感染的用户的首选软件。
下载、安装、销售,一切都向好的方向发展,用户群在几个月内迅速增长。创始人很高兴,团队也是如此。大家都在想:“我们做到了!像其他大公司一样,我们认为我们创造了自己的成功故事。“
# 新机遇(至少我们这样认为):进入企业市场
后来,公司决定进入企业市场。一个新的企业产品团队成立了。原产品负责人离开了公司,我们的CTO接任成为新的产品负责人(这是灾难的开始,稍后我会解释)。
一些开发人员离开了公司,但没有什么影响。我们把每件事情处理得很好,AntiMalware软件仍然是市场上最好的选择。
# 好日子结束, 麻烦开始
正如我前面所说,我们的CTO成了AntiMalware的产品负责人,他需要处理AntiMalware的方方面面。而且他还是该软件的首席开发人员,负责不间断地发布更新和功能提升。同时,他的职位让他还需要处理公司的其他事务。
当然,一开始都很顺利,我们的情况就像所有软件开发一样,我们不间断地维护和改进我们的软件。
正如我们应该预料到的(显然我们没有),不知何故,软件开发过程开始慢下来。
新的版本更新开始延期了,这种情况持续了一阵子,很快就变成没有版本更新了。这让我很不安,有一天我问CTO:
“这个产品出了什么问题?为什么版本更新要花费那么多时间而且开发进展缓慢?”
他深吸一口气,开始回答:
“我们的代码太复杂,它的结构不好,耦合太紧。架构设计完全错误,用户界面和核心逻辑代码混杂在一起,每当修复一个Bug或作某些改变时,其他部分就会受影响。即使是小的改变也很难做好。每次更新,都会引起新的问题。
一些方法竟然有20个参数,方法体的代码有两页长!你能想象吗?有许多不应该实现的东西不知为何都实现了。
这就是为什么每次更新都要花费很长时间而我们无法推出新功能的原因。每次我们推出一个新版本,我都担心可能会引入新的Bug,而那些现在工作得很好的核心功能则有可能因此无法工作。在这种情况下,发布新版本太冒险了,我们可能会失去我们的用户,我们的软件无人再愿意使用。”
他的回答中提到的一系列问题其实我们都知道。只是,我们期望从他的口中说出来。
我还问了一个问题。负责这个软件的前任首席开发人员为这个软件开发了一年时间,而他都在CTO的管理下,那么CTO为什么允许这样混乱的代码出来呢?
“我不想打击他的积极性,我们必须尽快进入反恶意软件市场,他很擅长这个,所以我才没有制止他这样做。”
CTO这样回答。
也就是说,为了以最快的速度进入市场,我们牺牲了代码质量,这样做也等于破坏了这个产品的未来。
经验教训:
要在第一时间对不好的代码设计说“不”,不要让“面条式代码”毁了你的产品的未来。要确保做出的软件产品有可持续开发性。
# 那么,如何修复这个可怕的代码?
“我们都是程序员,而程序员的心中都驻着个建筑师,当他们到达一个地方的时候,他们想做的第一件事就是把这个地方夷为平地,然后在上面建造一些宏伟的建筑。我们对那些渐进式的更新不感兴趣:如小修小补、改进、种种花草等等。”
- Joel Spolsky,Stackoverflow公司CEO
开发人员总是倾向于抛弃旧代码然后从头开始,他们有这样做的理由。因为他们认为旧代码都是无用而且凌乱的。但是这只是想当然的理由。当我们试图找出背后的真正原因时,我们会发现:
我们可能错了!
旧代码对我们来说可能看起来很凌乱,必须从头重写的原因并不是因为代码本身,而是因为一个重要的,基本的编程法则:
读代码比写代码难。
这解释了代码重用困难的原因,也解释了为什么我们认为旧代码象头发一样凌乱。因为这个原因,当我们阅读另一个开发人员的代码时,我们的潜意识会不断对着我们耳语“扔掉它,重新开始”。
像所有开发人员一样,我们也落入了这个陷阱。只是读一遍我们的凌乱的代码就足够让我们下决心考虑从头重写了。
在一系列的会议之后,即使CTO对重写代码有抵触(他是对的),他最终还是被说服了,我们决定从头重写代码。
然而,重写代码的决定并没有持续太久…
那是一个周末,星期日,我边喝早茶边读一些推送文章。就像我的推送知道该向我展示什么一样,我读到了那篇最著名的关于重写代码的文章,就是Joel Spolsky写的Netscape 的代码重写故事
(https://www.joelonsoftware.com/2000/04/06/things-you-should-never-do-part-i/)。
读完那篇文章后,我立马分享给了AntiMalware开发团队,包括CTO。
然后我们开始了新的讨论。
本来说服CTO作出代码重写的决定就已经很难了。他在读完那篇文章后马上改变了主意,他决定中止代码重写。这让其他团队成员生气了,他们冲我大喊大叫:
“你为什么给他看那篇文章?我们都已经说服他了。这个产品必须从头重写,这是唯一的解决方案。”
我们的第一次重写代码的尝试到此结束了。关于这个话题的讨论也终止了。我们的CTO相信我们可以管理好这个糟糕的代码,并有能力在它之上发布新版本,直到严酷的现实击倒我们为止。
一年没有任何更新…
真的,这不是玩笑。真的一年没有更新了!
“为什么没有更新?“
“自上次更新到现在已经有好几个月过去了。”
每天,我们都得面对这些来自用户的负面评论。作为一家小公司,我们需要管理的产品太多了,而且,我们又进入了企业市场,这些加在一起,使得我们陷入了这样的困境。
把所有这些结合起来,你就会得出这样的结论:我们忘记了我们的用户。
回想一下,我们不想发布新的版本,因为我们不想失去用户。
但事实应该是相反的:如果我们不发布新的更新,我们肯定会失去用户,而我们已经一年半没有给他们任何新版本了。
在被现实打了一巴掌之后,我们决定回头。对我们来说,除了重写代码别无它途。我们做到了。
# 当前
“2018年12月17日,星期一,21:40。测试的电子邮件准备好了,即将发送给我们的内部测试组。”
经过12个月筋疲力尽的工作,代码重写终于完工。我们准备了第一个测试版本说明,就像上次这个产品面市的第一天一样。
我们又回来了…
这个产品的重写版本仍处于测试阶段。测试已经快一个月了。我们正在修复错误,倾听用户的意见,审查用户反馈……一切就像4年前一样……
但是在这12个漫长的月中,我们错过了什么呢?如果不是重写,我们会做出什么新产品?!
许多问题可以在这里提出来。但我知道我们只有重写一条路,我们看不到任何其他的解决方案。
如果你也落入了这个陷阱,开始思考“我是否应该从头开始重写代码”,那么在开始代码重写的第一步之前,就考虑自己提问下面的问题,每个开发人员都应该问问自己:
# 你准备好抛弃关于旧代码的所有知识了吗?
这个问题很重要!请诚实地回答:你真的准备好抛弃所有的知识,所有收集到的错误和修复,年复一年的编码结果吗?抛弃旧代码并从头开始,真的是你所期望的吗?当你从这个角度来审视代码重写的决定,你会发觉很痛苦,不是吗?所有那些试图修补bug的不眠之夜都会在你眼前闪过。相信我,因为我有切身体会。
你必须和很多用户交谈才能找到导致你的软件不能正常工作的问题所在,然后你要在你的软件中定位这个错误,重现这个问题,然后找到解决方法,然后……等等。
# 你能保证你会做的比第一次更好吗?
这点很重要:当你从头开始的时候,没有人能保证你会比第一次做的更好。
因为你选择抛弃关于这个软件的所有知识和已经收集的错误和修复,所以同样的错误很可能再次出现在你的新代码里。
可能代码重写团队已经不是第一个版本的开发团队。所以你实际上没有“更多的经验”。你会犯下旧版本中的大部分的的错误,并带来一些新错误,而这些新错误在旧版本中并不存在。
如果你没有很好地计划重写工作,你可能面临新版本比原始版本更糟的风险。然而,既然作出了重写的决定,你就要承担这个风险,这个风险可能导致你失去你的客户。
# 你准备好将几个月/几年的时间优势拱手送给你的竞争对手吗?
你知道需要多少时间来重写你的软件吗?
代码重写牵扯到大量的精力、计划和准备工作。你必须把每项任务计划好,然而一个接一个地冲刺。你必须确切地知道完成这个痛苦的过程的最后期限。没人知道你会不会错过这个最后期限。有很大的可能你不能准时完成这个过程。
你不得不在数月或数年时间内只能交付旧版本给用户,这将置你于极其危险的境地。你完全无法进行任何战略改变或对市场所需的新功能作出反应,因为你没有任何新代码可以交付。
你的客户可能会抛弃你,因为你除了不断地提供一成不变地旧版本外,无法给他们任何新的东西。
这些你都考虑到了吗?
# 从代码重写中我们学到了什么?
从头开始重写一个系统,本质上就是承认作为一个设计师的失败。它其实是在声明,“我们未能设计一个可维护的系统,因此必须重新从头开始。”
——摘自 Max Kanat-Alexander的 Code Simplicity
像其他设计师一样,我们承认我们未能设计好我们的软件,我们从这个精疲力尽的过程中学到了很多东西。在这里,我分享一些我们从中获得的经验教训。
代码重写是开发人员的一种错觉,大多数情况下它不是解决方案。
当你的代码遇到问题时,准确地诊断问题很重要。像每个开发人员一样,你最初的想法不应该是代码重写。代码重写只是一种错觉。因为你在阅读别人的代码的时候,你会认为如果你从头重写代码,你能做得更好。在这种情况下,请始终牢记那个重要的,基本的编程法则。
在决定重写代码前,考虑代码重构
有针对性的重写对于处理代码库中最严重的错误很有用。如果可以限制范围并解决大部分问题,就不要进行整体重写。例如,软件的加载速度非常慢。但这只影响到项目的一小部分。通过小心地移动代码、重构和更改接口,这个问题可以一次性解决。你不必重写所有代码。
代码重写是一条比预期耗时更长、更困难、更容易失败的路。
告诉大家一个开发人员通常在错过最后期限后才意识到的事实:一切都比想象的要花更长的时间。代码重写成本的估计通常很悲观,然而实际的成本几乎总是比你想象的更高,花费的时间也更长。因为总是会有想不到的复杂问题要解决,这些都会使重写过程变得更加困难和痛苦。最后,你很可能不得不接受失败的结果。
确保重写后的产品能够更好地解决用户的问题,至少相同,不能接受更差。
重写对用户没有直接的影响/好处。因为用户不关心代码,他们只想解决自己的问题,仅此而已。在用户看来,能够解决他们问题的产品就是好产品。否则,他们不会用它。用户不关心你的代码重写决定,所以重写后版本必须至少和旧版本一样有效地解决他们的问题。
保持对现有产品的维护和支持。
在我们的案例中,我们有一年的时间没有向用户提供任何软件更新。这对于我们今天生活的世界来说是太长了。尽管我们的产品依然足够优秀,但是没有更新用户肯定会抱怨。当程序员重写代码时,永远不要停止维护当前正在使用的系统。
在重写过程中,旧的代码仍然需要维护,小的更新和错误修复需要及时提供给用户。否则,你将面临失去用户的风险。
让用户尽快参与设计过程
确保定期向用户展示最新进展,以便他们能够帮助你捕获最严重的错误。尽快与用户见面是很重要的。他们的反馈将帮助您根据他们的需求设计新产品。不要实现任何不必要的功能,这将避免你的代码库过于复杂化。
保持产品团队同步步调一致
一个产品团队不仅仅包括编程队伍,营销、支持、编程、设计……所有团队需要协力工作。通过定期汇报重写进展情况来确保整个团队步调一致。
在我们的案例中,我们遇到了很多这样的问题。例如,营销团队准备产品测试活动时,他们必须准确了解产品方面的情况,以便让客户为即将到来的产品改变做好准备。但是,有时我们在没有通知他们的情况下做了一些更改。这害得他们必须从头开始准备他们的测试活动。记住:不要浪费任何人的时间。
不要对产品作重大更改。
了解你的产品的弱项和强项,这一点很重要。切记不要改变产品的强项,也即用户喜爱的方面。如果用户对用户界面满意,不要对用户界面作大改动。只做最小的更改和小的用户体验改进。当您用重写后的版本替换现有版本时,确保你的用户不会被新的巨大变化所困扰。有许多情况用户放弃了新版本,因为他们找不到以前版本提供的相同的功能。不要让同样的事情发生在你身上。
不要让你的产品只依赖于一个开发者。
在我们的案例中,CTO是负责开发我们软件的首席开发人员。由于他的立场,我们的产品开发进展缓慢。即使是很小的变化也需要几个星期,有时甚至几个月。
我想表达的关键点是保持一直更新,永远不要停止。
版本迁移/更换要循序渐进。
当您确认新版本已经准备好,开始用新版本替换旧版本时。要一步一步,循序渐进。
首先,从一个小型的内部测试组开始,将您的产品发送到该组。收集他们的反馈和崩溃报告,修复错误,迭代新版本,然后重复这个过程,直到你确认你的产品已经准备好公开测试。
进入公开测试后,用户的反馈是你最期待的。你的第一个目标应该是确保您的产品能够解决用户的问题。当你确认新版本提供的功能与旧版本相同或者更好时,就可以进行更换了。这时候开始为新用户发布新版本,并将现有用户迁移到新版本。
以上这些都是我从代码重写过程中吸取的关键经验教训。代码重写几乎永远都不应该是解决方案,重构才是更好的选择。强烈建议采用代码重构循序渐进解决问题。这样做的风险更低,客户也更满意。
# 什么时候重写代码是合适的选择
然而,有时候重写代码也是合适的解决方案。下面我我列出了重写代码的几种情形:
切换到另一种语言或平台:
当一种语言变得如此古老,导致你很难找到开发人员,或者必须花大价钱才能找到时。
现有的代码库变得不可维护(像我们的情形):
如何确认你的代码变得不可维护呢?这个很难,但是如果你发现即使是很小的更改也很难实现,或者新的更新比正常需要花费的时间多得多,或者任何新的更改都会影响到软件的其他部分并导致新的错误,那么你可以确认你的代码变得不可维护了。
有足够的资源可以同时维护现有系统和设计新系统:
重写代码的时候,永远不要停止维护当前正在使用的系统。只要系统在使用中,必须始终对其提供维护。记住,你的个人注意力也是一种必须考虑的资源,如果你打算同时为新系统和旧系统做设计工作,你要考虑是否每天有足够的时间。
开发人员变成了软件开发的瓶颈(像我们的情形):
这不应该出现在重写代码的原因列表中。因为你可以随时在团队中调配开发人员,也可以雇佣新的开发人员来解决瓶颈问题。
然而,就像我们的情形一样,有时你可能需要将它作为代码重写的一个原因。因为我们的软件使用的是旧技术,而CTO是唯一负责开发它的人。我们很难找到一个新的开发人员,因为这个平台年代太久。即使我们能找到一个新人,对我们来说也太昂贵。因此。我还是把它作为代码重写的情形之一,列在这里。
软件的年龄太长(我说的是10-20年或更长时间):
随着时间的推移,一个软件的代码会变得越来越凌乱,维护也会变得越来越昂贵。这是因为为了快速推出修复补丁,初始架构有时会被牺牲掉。而且,懂得旧技术的开发人员越来越少,人员成本也越来越高。同时,很难找到适合旧的应用程序运行的硬件、操作系统和框架。此外,随着业务的发展,旧的系统很可能无法满足新的业务需求。
所以,你必须在旧系统高昂的维护成本,新系统的潜在好处,以及从头重写的成本之间作一个权衡。
如果你的情形符合上述一点或多点,代码重写可能是你能接受的选项。否则,正确的做法是通过一系列简单的步骤改进系统的设计,在不重写代码的情况下处理解决现有系统的复杂性。
从头重写代码可能是你犯的最大错误,但同样地,不重写代码也可能导致相同的结果。我的建议是优先考虑重构而不是重写。
有些开发人员坚信所有系统最终都必须重写。记住这并非总是对的。设计一个不需要抛弃的系统是可能的。总有软件设计师会告诉你,“无论如何,总有一天我们会丢掉所有的东西”。但是,如果软件是从一开始就设计得很好,而且一直有很好的维护,为什么它会被抛弃呢?
马云:未来每年将会向社会输出1000名在阿里工作10年以上的人才
觉得不错,请给个「在看」
分享给你的朋友!