原文: How to Avoid Brittle Code ,作者:David Rice,译者:GoCD( @goforcd )
遗留代码最常见的问题就是脆弱性。团队如要修改脆弱的代码库,必定伴随着巨大的痛楚。在我们 ThoughtWorks 开发产品的 10 年里,当我们年复一年地尽量保持庞大代码库的延展性时,学到了一些惨痛的教训。我想在本文分享我们从最大挑战中吸取的教训。声明:我写下这些思考,不代表我们已经搞定了所有问题。我们仍然要分担遗留代码的痛苦,和其它团队一样,我们每天都努力让它变得更好一些。
你应该一直渴求更新依赖库和框架。好吧,或许现在已成为共识。但是,10 年前很少有人这样想。有些团队明白,升级是需要完成的、正确的工作,我只是怀疑,他们是否真地优先去做了。它一直需要你认真对待,不要拖到最后,变成技术债务。原因如下:
如果某项任务是痛点,就要经常去做 。对于一直升级所存在的最明显的理由之一,就在于升级会是艰难的。常常存在不可预期的、一系列被破坏的依赖。工作量通常无法知晓。经常做,它就不再是问题。但是,和简单的避免痛苦相比,还有更重要的原因。
升级依赖项的另一个驱动力,在于修复安全漏洞。现在和 10 年前开发软件,有一个最大的不同,我们资源库、框架和应用程序的漏洞报告仿佛从未间断过。修复漏洞,差不多总是涉及到升级某些依赖项。为了快速修复漏洞,升级必须容易操作。
没有定期升级操作的团队,通常将其贴上技术债务的标签。尽管行业比 10 年前更乐意谈起技术债务,但是,说服项目经理偿还技术债务,仍然算得上非常艰难的对话。如果你的团队处于「一直升级所有东东的模式」,你就不用为了升级技术债务而展开沟通。
遗留代码的主要痛点在于,做出修改需要花费多长时间。如果你打算让代码长期运行下去,就需要确保未来修改代码的程序员感到完全地开心。有一种处于优势的方法:极度快速、彻底的单元测试套件。
增加新功能、包括任何代码重构,每个周期大致描述为:编写失败的测试;写代码;显示绿色;搞定。如果你这样做了,你就能一直执行大量的单元测试,有时候是某一套针对性的测试、有时候是整个套件。如果测试不够快,开发周期就不会轻松。写代码的体验不应该是:做了一些修改,而运行测试却需要坐等 10 或 20 分钟。太差劲了。
确保测试套件快速运行,不只是与你的设计和代码有关。诚然,你可以做大量工作来加速测试,比如避免文件、数据库、套接字、海量对象图表生成等。但是还有另外的重要技巧,挑选有助于加速测试的框架和语言。如果你发现自己为了让测试更快、而修改了框架,那么,你需要考虑不同的框架。是的,当我正在开发传统的多页应用程序时,下次就不可能用 Rails 开发了。
还需要考虑应用程序的大小。当某个代码库处于一定规模时,就需要规划好切分方案。这也是让你充分理解某块代码的唯一方法。找到分割项目的切入点,这不同于学术上的工作,你需要投入大量时间折腾代码、研究各种地方、再设计、重构。一直让快速的测试套件迅速地验证你的工作,将使这份工作轻松几个数量级。
实际上,「几个数量级」更像是夸张。如果你需要切分庞大的代码库,并忍受着痛苦的、龟速的单元测试套件,嗯,你可能会被困住。我们正在痛苦地得到教训。因此,尽力确保单元测试要快,并且在开发机器上以单一线程运行。
运行时间长的产品,经历了很多技术领导。某些类型的技术领导,刚一接手,就抱怨现有产品的不足,并马上想开发新的产品。这没错。时髦的技术不总是糟糕的。对于长时间存在的代码库,它需要新的活力,产生足够能量,淘汰不能胜任的地方。我想提两个重要的点。
新接手的技术领导,在和团队一起工作两到三个月之前,不要轻易摒弃任何技术。有太多的情景需要理解。新接手的技术领导需要学会站在团队和代码库的角度考虑。团队和技术领导需要建立信任和节奏。短暂地停留是为了更好的决定。
利用 抽象分支 ,是替换新技术的经典方法(长期分支的荒谬性之外):
组件 X 前面放置一个抽象。
组件 Y 做为 X 的替换品,被引入。
抽象智能地路由到 X 或 Y。
X 逐步被废弃掉。
X 被移除;或许抽象也被移除了。
有很多次,我都看不到这个过程能够顺利走完,因为移除旧组件最后 20% 的工作太难了。你简直想象不到,年复一年地用多种方式做这项工作有多么痛苦。它减缓了所有工作,还让士气消沉。抽象分支属于优秀的模式,他也是我做这种组件替换工作的唯一方式。但是,它需要团队的完全承诺,即,在既定时间内,淘汰旧组件。
仅仅因为我们这里过多地讨论了技术债务,并不能为偿还技术债务提供任何担保。有一点是肯定的,任由技术债务积压下去,只会使其变得无法偿还。「先放一放。我们先做其它紧急需求,它被记下来了,我们回头再搞。」,这话很容易说出口。同时,它可能算得上明智决定。但是,那些所谓的紧急需求永远没有结束的那一天。紧急清单只会越变越长。
状况会恶化下去。据我经验看,当技术债务积压增长过于频繁时,团队将趋向于放弃偿还,团队感到失望,开发人员不能达到流动,业务也获取不到新的价值。关于如何避免不可逾越的 技术债务 ,我做过一些思考。
一个良好的开发团队,不会一而再、再而三地以技术债务为借口。当团队意识到,同种技术债务重复出现时,就必须向前推动,并很快将其融入到日常工作中去。
我的同事 Badri 建议,团队必须就共同承担技术债务达成一致。任何人无权让代码库变得更糟、而让整个团队随后为此买单。
更重要的是,技术领导和产品领导需要彼此信任。双方都不应该玩「我说了算」的把戏。好的技术领导理解业务的优先级,好的产品经理看重能够交付的价值。双方均需要探讨风险、成本和收益。如果你无法交付,你的技术债务就转变成了业务问题,对每个人都没有好处。
显然,为了编写长期存在的代码,一个团队还有大量工作要做:为阅读代码的人写代码,不要自作聪明,经常想想你未来的同事。我乐于听到你的想法。