写点什么

投资于质量,不再有技术债务

2014 年 4 月 14 日

一个童话故事

很久以前,有个软件开发团队找到他们的经理。“我们的项目有相当多的技术债务(Technical Debt),我们应该做点什么。”这个团队说。他们展示了一张图(图 1)来说明项目的技术债务。“技术债务关系到项目质量。”他们说。并展示了技术债务各部分的分解,通过静态代码分析,能发现过于复杂的代码、重复的代码和冲突。“我们需要去除技术债务”他们告诉经理。

(点击查看大图)

图 1:SonarQube 技术债务插件的结果报告

但经理困惑了:什么是技术债务?他该额外再增加 $500.000 的预算?为了什么?这关系到质量,但他不知道现在有什么缺陷。客户一直很满意,这些开发人员在说什么?因此经理和团队进行了长时间的讨论。

事实上,技术债务就是在这类讨论中引入的一个比喻。意思是低质量的代码就像是财务负担。债务的总额就是从代码库中把它们清除出去所需要付出的努力。利率是由于低质量代码造成的生产率下降。管理人员通常熟悉财务领域的术语,所以在谈论软件质量时使用这个比喻会更加容易沟通。

这个故事说明,技术债务这个比喻失败了。本以为在和经理沟通技术质量时,它能起到帮助作用,并最终产生回报。然而,现实并不那么容易。这是因为有技术债务并不意味着它必须要偿还。技术债务甚至不一定是件坏事,有时它只是为了按时上市或者达成项目其它目标而妥协的结果。甚至有时候这种情况无法避免,例如类库采用新技术或者进行了升级,导致原来没问题的代码现在产生了技术债务。关于技术债务该如何偿还,并没有简单的定律。事实上,偿还技术债务有多种方式,或者有时候你甚至可以将它作为你的优势 [1]。

技术债务的问题

技术债务的主要问题是它只代表了系统的内部质量。而质量有哪些影响并不明确。特别是,技术债务的经济影响无法简单地用这个比喻来表示。技术债务还很奇怪。如果这些代码不需要修改,那技术债务就完全没关系。但是,一旦要修改这些代码,技术债务就成为代码的所有重要属性。所以,技术债务很可能对项目的成功、外部可见的质量完全没有影响。

但是如果你忽视项目的技术债务,很可能它就在某个地方等着你:如果你需要修改有大量技术债务的代码,成本可能非常高,最终无法执行。开发人员通常知道和害怕这类情形,维护有大量技术债务的代码不仅仅是少了乐趣,而是风险太高,因为 bug 很可能潜入,而评估很容易就被证明是错的。

因此,软件质量可能对软件项目的成功非常重要,而技术债务的比喻是不够的。它能用来表示软件质量,让人们了解软件质量怎么样,但如何处理软件质量才是真正重要的。

质量投资:修复代码的新比喻

也许换一个比喻更能说明对于软件系统的质量,我们该做什么。技术债务只反映修复质量问题所需支付的成本,上述例子中是 $500.000。这个数字本身没有什么用处,它无法说明我们该如何处理这个问题,或者它们如何影响了系统的开发。也许我们该偿还技术债务,也许它完全没有影响。

所以一个更好的比喻也许是质量投资(Quality Investment)。使用质量投资,处理技术债务就有可能获得利润。这样就可以使用财务术语来积极地管理代码质量,可以很容易决定哪些质量问题应该解决,哪些可以暂时接受。

质量投资的想法来源于 SQALE。SQALE 方法 [2][3] 是一种质量模型,它定义了两种类型的成本:修复成本(remediation costs,RC)和非修复成本(non-remediation costs,NRC)。修复成本是指在你的代码库中清除某个质量问题的付出。事实上,修复成本可看作是技术债务。第二类成本更有趣。当这个质量问题未解决时,就会产生非修复成本。例如,开发某个新功能可能需要更长时间。这个额外的付出就是非修复成本。在这个上下文中,非修复成本看起来像是技术债务的利润。但事实上,这些成本应该给予更多考虑,例如不清理低质量代码的额外风险因素。利用 SQALE,你可以选择维持当前的低质量状态,支付非修复成本,或者去提高质量,支付修复成本。

该质量模型的前期实现,例如 SonarQube 的 SQALE 插件,只支持修复成本。然而,这两种成本类型是经济合理地处理质量的关键,也是这个质量模型的创新性所在。

如果你正在提高质量,解决质量问题,那么支付的是修复成本。当问题清除后,团队避免了相应的非修复成本。因此,只有当非修复成本大于修复成本时,才值得去提高质量。因为只有这样,质量投资才能产生利润。这可以简单地用下面的利润公式来描述:

复制代码
利润 = 非修复成本 – 修复成本

这个公式给出了一个合理的、符合经济原则的指导方针,用于判断质量问题是否需要修复,以及何时修复。

正如之前提到的,代码的质量只有当它将来需要修改时,才会变得重要,因为只有这时候团队才会因为低质量代码而慢下来。所以,正确的投资分析必须评估代码将来修改的可能性,以及这两种类型的成本。因此,质量投资的比喻可以通过三个评估来实现。让我们看一个例子以便更深入地了解这一点。

假设我们有一个系统,它包括三个模块:客户、订单和发票。客户管理是个非常老的模块,已经不再开发了。因此,这个模块不适于质量投资:仅当代码被修改时才会产生修复成本,在这个例子中,修改的可能性为 0%。因此支付任何修复成本都将导致损失。

然而,我们知道在接下来的迭代中,对订单流程进行了大量修改。根据经验,发票管理也必须进行一些修改。像这样的粗略估计通常是足够的,而且也很容易达成一致。经验告诉我们,详细的评估绝大多数都是假装精确。

接下来的步骤是要评估订单模块和发票模块的质量问题。订单管理的测试覆盖率非常低,客户管理的代码非常复杂,也就是说方法和类有大量的代码,并且有很多复杂的循环。面对众多选择,我们评估了修复成本和非修复成本。非修复成本的评估也应考虑模块修改的可能性。在下一个迭代中,如果代码质量相同,订单管理估计要投入 20 天。而如果测试覆盖率能提高,那么估计只需投入 13 天。因此,非修复成本是 7 天。这非常高,因为质量确实太差了,同时也因为有大量代码要修改。

  • 订单管理:很低的测试覆盖率,修复成本:5 天,非修复成本:7 天
  • 发票模块:很高的复杂度,修复成本:5 天,非修复成本:4 天

这些评估表明订单模块的质量投资产生了 2 天(=7 天 -5 天)的利润。相反,发票模块的质量投资没有利润,甚至亏损。由此可见,投资于当前的订单系统是有价值的,因为根据团队的评估,提高测试覆盖率就有利润。对于发票模块,情况并不明确。就现在来说,其质量投资没有利润。然而,发票模块很可能在未来的几个迭代中也需要修改。这样你就能从发票模块的质量投资中得到利润。

根据给出的评估和计算出的利润,也可以为每个质量投资推算出投资回报(RoI)。投资回报表示相应的成本产生了多少利润。因此,投资回报率等于利润除以修复成本。订单模块质量投资的投资回报率大约是 40%(=2 天 /5 天)。经理们通常会寻找机会去获取比较高的投资回报率。使用这些财务术语,你能与他们沟通代码质量提高后的收益。当然,就像软件开发的几乎所有其它事情一样,这些数字都是估计值。然而,这显示了团队不只是基于自己的原因寻求提高质量,更重要的是由于经济原因。

质量投资这个比喻,让你能与管理者进行各种类型的讨论。除了技术债务的成本,你还可以与经理讨论节省时间和投入。对于经理来说,这意味着双赢局面:节省了预算,开发人员也很高兴,因为他们可以提高代码质量。同样,管理者可以考虑投资是否合适,或者一个快速但质量较低的解决方案也是足够的,甚至是必要的,因为要按时上市。

最后,质量投资是比较了众多评估的结果:什么是最经济的决定?但是,它回避了一个问题,为什么在软件开发中,很少使用投资这种想法。

另一个童话

现在,让我们再重头开始讲这个童话故事,这次采用质量投资的方式。

很久以前,有个软件开发团队找到他们的经理。“我们相信,已经找到一种方法来提升我们的开发。”他们说,“我们进行了一些评估,刚开始时,下一个迭代中订单处理要做的修改非常多。然后,如果我们投资 5 天用于提高订单管理的测试覆盖率,我们估计下一个迭代能节省 7 天,所以我们可以少投入 2 天。”“太棒了!”经理说,“就这样做!这是有价值的投入。”所以这个团队最终提高了大家都关心的模块质量。“另外还有一个,”这个团队说,“如果我们另外再投资 5 天用来提高发票系统的质量,我们觉得我们将能节省 4 天。虽然这有点损失,但我们相信在下一个迭代中我们就能赚回来。”经理回答:“好的,但是,在这个迭代中,我们真的要推出尽可能多的功能,这额外的两天非常有用。客户现在不是很高兴,我要让他们看到印象深刻的东西。否则可能就没有下次迭代了……”所以他们得以继续提高订单管理的代码质量,并开心地过着日子。

这说明了质量投资作为一个比喻为什么这么有效:可以用它来评估涉及质量的每个决定的利润。也很容易判断出某些质量投资可能比其它方面的关注,例如实现新功能,具有更高的优先级。如果没有这个比喻,所有这些都是不可能的。当然,只有当团队有质量很高的评估历史数据时这些才有可能。但即使没有,使用这些术语去思考也有利于得出更经济的决定。

CodeQ Invest

CodeQ Invest[4] 是一款支持质量投资的工具。它是一个 Web 应用,能够自动计算包括单个类到根包在内的各级别的代码修改概率。源代码控制系统用于计算合适的数字。它基于这样一种思想,过去经常修改的代码,将来它们修改的可能性要比那些过去没修改过的代码更大。你可以定义这个计算要涵盖的天数。这些天内的每次提交都将用于计算,计算每个修改的文件,代码修改的比例。此外,你能选择不同的方法来计算过去代码发生的变化:

  • 过去 n 天内,所有提交的权重相同
  • 过去 n 天内,所有提交的权重逐渐增加(越新的修改,权重越高)
  • 过去 n 次提交的权重相同

除了自动计算修改概率,你也可以人工评估某些代码的修改可能性。对于进入维护阶段的系统,也就是说不会增加新功能的系统,概率计算是非常有用的。它同时也给出了代码库中的有趣视角和代码中可能的热点。人工评估非常适合于开发新功能时,特别是一些过去未触及的代码必须要修改的时候。

CodeQ Invest 还有很多其它功能。它计算你代码库中的最佳投资。你提供预算,也就是投资系统质量的小时数,CodeQ Invest 将建议哪些地方的代码应该提升。团队应该为它进行质量配置。该配置从该团队的视角来定义代码质量。不同的团队可能会给出不同的配置。配置包括许多质量需求。每个需求描述代码质量的一个度量角度。CodeQ Invest 当前允许你对不同的指标设置阀值。可以使用 SonarQube(前身是 Sonar)[5] 对它们进行度量。SonarQube 是非常流行的代码质量管理工具,用于分析代码得到指标值。此外,对每一个质量需求,团队必须评估修复成本和非修复成本。团队只在低层级评估成本,例如,将 100 行代码的测试覆盖率提高 10% 需要花费多长时间?基于该测试覆盖率的代码,实现功能有什么影响?该工具为你提供更高层次的抽象与合计。示例如图 2。

CodeQ Invest 只是一个工具,用于进一步帮助你使用新比喻和新方法来处理代码质量。基础部分是各种评估,它可以以传统的方式完成,即评估故事、质量提升以及在特定故事上进行质量投资的影响。或者你可以使用 CodeQ Invest 并进行设置,正如你所看到的,设置某些质量特性影响的数量。CodeQ Invest 将使用这些基本数据,得到投资计划的相关建议。

(点击查看大图)

图2:CodeQ Invest 的项目视图。显示了一个可缩放的树对应你代码库中的投资机会。在右侧你能看到几种预算的投资回报分布(例如16 或者32 小时)和一个基于给定投资生成的质量投资计划。

总结

尽管技术债务能够帮助沟通软件系统的质量,但对于如何处理这个质量,它并没有太大帮助。这将导致很多问题,例如低质量的代码得不到维护。到最后,主要的关注点是应该提升系统哪一部分的质量,哪些地方是没问题的,不需要修改。而这就是质量投资能够帮助的:每一项提升都根据经济产出来判断,也就是说它是否比保持代码不变更便宜,还是应该投资于质量?这给了一个很好的指导方针,哪些代码的质量应该提升,什么情况下代码质量应优先于实现新功能。

链接

[1] Managing Technical Debt

[2] SQALE

[3] Technical Debt Evaluation (SQALE) Installation and Usage

[4] CodeQ Invest

[5] SonarQube

关于作者

Eberhard Wolff是一位自由架构师和顾问。他也是 ADESSO AG 公司的技术顾问委员会负责人。他的兴趣包括敏捷技术和现代软件架构。

Felix Müller作为一名 IT 顾问工作在柏林的 codecentric AG 公司。他是一名富有激情的软件开发人员。他在开发领域的主要兴趣包括 Web 开发、自动化测试、持续交付、代码质量管理和敏捷实践。

原文英文链接: No More Technical Debt - Invest in Quality

2014 年 4 月 14 日 23:002265

评论

发布
暂无评论
发现更多内容

程序开发中的持续集成、持续交付、持续部署

石云升

持续集成 持续交付 持续部署 自动化部署

netty案例,netty4.1中级拓展篇九《Netty集群部署实现跨服务端通信的落地方案》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1高级应用篇三,手写RPC框架第三章《RPC中间件》

小傅哥

Netty 小傅哥

世界正在重塑 加密货币将扮演什么角色

CECBC区块链专委会

数字货币 加密货币

做职场里的“超级英雄”,需要怎样的盔甲与工具?

脑极体

netty案例,netty4.1中级拓展篇十二《Netty流量整形数据流速率控制分析与实战》

小傅哥

Netty 小傅哥

netty案例,netty4.1中级拓展篇十三《Netty基于SSL实现信息传输过程中双向加密验证》

小傅哥

Netty 小傅哥

netty案例,netty4.1高级应用篇一,手写RPC框架第一章《自定义配置xml》

小傅哥

Java Netty

JDK8 日期 API 使用

HeGuang

JDK1.8

netty案例,netty4.1中级拓展篇八《Netty心跳服务与断线重连》

小傅哥

Netty 小傅哥

netty案例,netty4.1中级拓展篇十《Netty接收发送多种协议消息类型的通信处理方案》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1源码分析篇一《NioEventLoopGroup源码分析》

小傅哥

Netty 小傅哥

netty案例,netty4.1中级拓展篇五《基于Netty搭建WebSocket,模仿微信聊天页面》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1源码分析篇三《Netty服务端初始化过程以及反射工厂的作用》

小傅哥

Java Netty 小傅哥

大数据技术思想入门(二):分布式存储集群特点

抖码算法

Java 大数据 hadoop 分布式

Week10--课后作业

Geek_165f3d

netty案例,netty4.1中级拓展篇六《SpringBoot+Netty+Elasticsearch收集日志信息数据存储》

小傅哥

Java Netty

netty案例,netty4.1源码分析篇二《ServerBootstrap配置与绑定启动》

小傅哥

Java Netty 小傅哥

数字化背景下的经济社会发展的新特征 新趋势

CECBC区块链专委会

区块链 人工智能 大数据

spring事务的这10种坑,你稍不注意可能就会踩中

简爱W

netty案例,netty4.1源码分析篇五《一行简单的writeAndFlush都做了哪些事》

小傅哥

Java Netty 小傅哥

书摘之《堂吉诃德》—— 谁不曾想过仗剑走天涯?

小匚

读书笔记

netty案例,netty4.1中级拓展篇七《Netty请求响应同步通信》

小傅哥

Java Netty 小傅哥

netty案例,netty4.1中级拓展篇十一《Netty基于ChunkedStream数据流切块传输》

小傅哥

Java Netty 小傅哥

大龄程序员的自我介绍 v 0.1

escray

学习 面试 自我介绍 面试现场

netty案例,netty4.1源码分析篇四《ByteBuf的数据结构在使用方式中的剖析》

小傅哥

Java Netty 小傅哥

8锁问题

HeGuang

synchronized

netty案例,netty4.1高级应用篇二,手写RPC框架第二章《netty通信》

小傅哥

Netty 小傅哥

netty案例,netty4.1源码分析篇六《Netty异步架构监听类Promise源码分析》

小傅哥

Netty 小傅哥

Week10---课后总结

Geek_165f3d

区块链的共识机制有哪些好处优势?

CECBC区块链专委会

区块链 分布式 金融

低代码的认知误区与落地实践

低代码的认知误区与落地实践

投资于质量,不再有技术债务-InfoQ