为什么以及如何我们持续地投入团队的努力来解决技术债务?结果又如何呢?
任何长期维护软件的人都会意识到,随着时间的推移,软件似乎逐渐“腐朽”。防止这种情况的发生,需要付出刻意的努力。本文我将分享一个团队如何成功应对这一挑战,并提供一些实际的操作建议。
所谓的软件腐朽或软件熵主要表现为几个症状:
-
平均故障间隔时间(MTBF)缩短:软件出错的频率增加,发生的故障也越来越多。
-
实施时间(LT)延长:对于价值相似的功能,从实施到审查,再到部署和发布,所需的时间随时间增长而加长。
-
效率降低:价值与投入的比率下降。
-
修复或补救时间(TTR)延长:修复软件缺陷以及确保问题不再发生的时间变长。(参见我在 InfoQ 上关于MTT 指标的文章)
-
首次提交时间(TTFC)延长:这是衡量新人适应代码库效率的多个指标之一。
其根本原因一般包括:
-
外部因素:运行环境、操作系统、依赖组件随时间变化,需要开发者做出相应的调整。
-
内部因素:程序错误、配置偏差、技术债务。
-
混合因素:需求和用户的期望变化速度超过团队用现有代码满足这些需求的能力。
在所有这些原因中,技术债务是开发团队可以主动控制和解决的部分。
技术债务就像是你的代码库的信用卡:容易积累,但摆脱起来却非常困难。
—— Juan Jose Behrend
我不打算在这里重复互联网上已有的信息:
-
Martin Fowler 提出了4 种技术债务类型
-
Gergely Orosz 在 2020 年写过关于实用的中间立场
-
David Pereira 谈论了产品经理的观点
-
Devopedia 有关技术债务历史的页面。
-
维基百科列出了技术债务的原因。
相反,我将分享一个最成功的处理技术债务的案例,并在文末提供一些实用的建议。
故事
多年前,我和一个 12 人的工程师团队共事,他们负责两个大型全栈应用程序。每个应用程序都有超过 18 万行源代码(SLOC),这还不包括代码中的依赖项、注释和空行,但包括了测试代码。
这些代码是几年前为一个定制解决方案构建的“平台化”过程的结果。公司曾经有多个解决同一问题的方案,最终合理地选择了最成熟的一个,将其转化为一个通用平台,并组建了一个由顶尖工程师组成的团队来负责。
这就催生了所谓的“内源”巨型单体架构(也就是共享代码库),大约有 150 多人在这个项目中协作。
那时,我从公司的另一个部门调过来。在技术(Tech)、领域(Domain)和人员(People)这三个方面,我对技术和人员很熟悉,但对业务领域相对较新。
我的挑战从第一天就开始了。我发现自己难以理解代码库,感到非常沮丧。那时我已有 19 年的编程经验,其中最后七年都在专注于这些应用程序所使用的技术。
尽管我的经验似乎比团队中的其他成员都要丰富(至少从纸面上看是这样),但即便是简单的任务也比我预期的要花费更多的时间。这让我感到自己既无能又无助。
幸运的是,原始代码库的一些创造者仍在平台团队中,并给了我总共 2 小时的介绍。这段介绍不仅帮助我理解了代码,更让我了解了项目背后的历史、团队心态以及塑造这些代码的更广泛的背景因素。
你看,领导层并不关心代码的质量,只要按时完成任务就行。为了赶工期,常常有代码质量被牺牲,测试被忽略。我记得墙上甚至挂着这样一个标志:“fuck it, ship it”(随它去吧,赶紧上线),其中“ship”一词被一艘船的图片所替换。
我没有向他人表达我的感受。显然,邀请我加入团队的高级主管(来自我之前所在的部门)可能有他的考虑。也许这是一个考验,看我会如何应对?作为新人,我需要建立自己的信誉,然后才能引导团队进行改变。正如我经常说的:“在尝试改变之前先去理解。”在我的眼中,代码和团队成员是紧密相连的,你无法用技术手段解决文化层面的问题。
我为团队做出的第一个真正贡献,是在墙上挂了一张幽默的图片。这张图片上写着“临时解决方案,你在开玩笑吗?”它受到了团队的欢迎。
如果是现在,我可能会在墙上挂上这样一张图片:
你听说过破窗理论吗?在软件维护的语境中,这意味着:技术债务越多,对新开发的关注就越少。
换句话说,随着时间的推移,技术债务带来的负面效应会逐渐累积。
理念
我发现,并不只有我一个人对技术债务感到沮丧。在连续几次的回顾会议中,技术债务问题一直被提出,直到管理层决定正视这一问题并采取措施。
因此,我们举行了一个研讨会,深入探讨这个问题:了解其发生的原因以及我们如何能够掌控它。在团队的坦诚对话中,我对他们的尊重有了新的提升。事实证明,在压力山大的日常中,他们几乎没有时间去整理这些混乱。这是出乎意料的。我加入时,代码就像是一个摇摇欲坠的建加塔:
他们非常清楚这个问题,但主要难题是缺乏时间和对最佳实践的认识。
来源:Twitter
回想起来,一位开发者曾这样说:
技术债务太多了,我们应该停下所有日常工作,花上六个月时间去解决这个问题。
项目经理(PM)反驳说:
但我们不能这么做。那谁来维护产品,增加新功能呢?在偿还技术债务的同时,我们是否可以将工作分解成更小的部分,与日常任务并行进行,逐步加班完成?
另一位开发者提出:
要我清理代码,我就得有专门的时间,而这个时间不能是原本计划用于修复 bug 或开发新功能的。
还有一位开发者建议:
如果我们能共同协作清理,那就太好了。我们可以一起找出最好的方法,分摊那些机械性的工作。让一个人单独承担清理工作是不公平的。而且,如果只有一个人负责技术债务,我们可能还会制造出更多的债务。
工程经理(EM)提出:
我们需要对这项活动进行时间限定,以免占用本应用于开发新功能和修复 bug 的时间。你们认为花在解决技术债务上的时间应该是多少?
经过一番讨论,大家达成了每周投入一天时间的共识,相当于团队总工作量的 20%。
项目经理(PM)质疑:
你们是说,我们必须用掉我们 20% 的时间,仅仅是为了保持现状?
这之后出现了一段尴尬的沉默,好似在问:“你愿意降低 20% 的薪水吗?”
项目经理(PM)继续说:
我们还有很多待办事项需要完成,所以我们需要一个平衡两种任务的折中方案。那 10% 怎么样?
接下来的事情就成了历史。“技术债务星期五”由此诞生。为什么是星期五?我已不太记得,但这可能与一些人星期五休息有关,实际上,这意味着技术债务并不会确切地占用 10% 的时间。不过,这依然是一个胜利!✌️
这个过程经过几次调整才逐渐完善。但在我离开团队的最后一年里,它保持不变。即使工程经理和项目经理更换了,团队也成功地让新经理适应了这个“传统”。
实施细节
我们每两周举行一次“技术债务清理日”。这些日子不是为了解决具体的问题或任务而设立的。我忘了复制详细的政策,但它大致是这样的:
-
我们将 10% 的时间用于处理技术债务。
-
最重要的原则是尽量不产生技术债务。
-
产生技术债务的代码提交(Pull Request)应该伴随一个解决方案的问题登记。
-
所有技术债务工作都要作为问题记录下来,并标记为“tech-debt”。
-
我们在同一天集中处理技术债务,并尽量减少当天的会议安排。
-
建议(但非强制)在下次团队展示时展示处理结果。
-
在修改代码以解决技术债务时,记得添加或更新相关的测试和文档。
工程师们对技术债务清理日充满期待。团队会提醒管理层,这一天不能用于安排常规的功能开发或错误修复工作。尽管我们顺便修复了一些错误,但主要目的是为了降低未来功能开发的成本,同时提升系统的可维护性和可靠性。
最初,将团队 10% 的时间用于处理技术债务似乎难以说服人,但时间证明了这做法的巨大回报:
-
我们尽可能快地分摊并清偿了这些债务,通常在不到 10 天内完成。
-
由于缺乏固定的问题和任务分配结构,这些日子成了我最喜欢的集体编码时光。我们一起深入探讨代码库,了解其背后的历史。
-
我们发现,一些看似明显的技术债务其实最好保持原状,如果有更好的文档的话。对于那些我们决定不去改动的代码,我们进行了详细记录。
-
渐渐地,我们学会了如何应对这个系统。定期处理技术债务使我们更加注重在一开始就避免产生它。因此,我们能够把技术债务清理日用于更有价值的工作,例如改善测试、代码审查或优化 CI/CD 流程,以减少错误发生或降低其成本。
-
协同处理技术债务使我们能够更快地完成日常工作,因为我们对代码有了更深入的共同理解,而且代码更易于操作。这可能被视为集体编码的积极成果,但没有具体议程也增强了自主性,从而激发了创造力。
-
对代码设计和架构的清晰理解使我们在时间紧迫时做出更明智的决策。我们更清楚地知道处理技术债务需要付出什么,以及为了加快交付速度而从未来借用时间是否值得。
-
管理层逐渐开始欣赏这种做法,因为技术债务不再干扰常规工作,也不会导致令人尴尬的不必要问题。此外,这种自由和信任提升了团队士气。团队被尊重并被视为成熟的团队成员,因此表现也更加成熟。
-
公司中的其他团队也开始尝试实行“技术债务清理日”。
结论
仅仅因为代码能运行,并不代表它就是正确的实现方式。代码的复杂结构很难直观展现给决策者(如项目经理 PM),这使得他们难以抽出时间来解决问题。这样怎么办呢?
来源:Twitter
技术债就像是快餐一样。在别无选择时,它或许是个不错的临时方案,但必须通过不断的努力来减少它带来的负面影响。(这个比喻来自我的指导原则)
从这个角度来看,“代码膨胀”完全有可能成为一个现实问题!😄
技术债可被视为“短期目光的代价”,因为只有片面且短视的思维才会忽略它。关于战术和战略思维的不同,你可以在这里找到更多信息。
《富爸爸穷爸爸》的作者罗伯特·清崎曾经指出:
坏的债务是那种让你损失金钱的债务;好的债务则能帮你赚钱。
对于技术债务也是如此,它分为好的和坏的两种:
-
好的技术债务是一种深思熟虑的权衡,旨在快速获得成果。它像是加速发现的工具,并应尽快还清。
-
坏的技术债务通常与消极态度有关,它是因懒惰而推迟必要工作,而且没有明确的好处。这就像为了一时的满足而借钱买昂贵的鞋子一样!
如果你无法承担其后果,那就不应该产生技术债务。技术债务未偿还的两大原因通常是:
-
**工程能力不足:**指的是低估了创建一个可维护产品所需的工作量。
-
**领导力缺失:**如果工程师需要向管理层证明偿还技术债务的必要性,那么就应该反思管理层的存在意义。
遗憾的是,能力不足的工程师和领导往往相互依存,就像门与门框一样紧密相扣。打破这种共生关系很难,有时候选择离开,加入一个真正重视软件可持续性的团队会是更明智的选择。