高质量软件值得其成本吗?

2019 年 5 月 29 日



软件开发项目中一个常见的争论是,是花时间提高软件质量,还是专注于发布更有价值的功能。通常,交付功能的压力主导着讨论,导致许多开发人员抱怨他们没有时间处理架构和代码质量。

贝特里奇标题定律 是一句格言,说任何标题或标题以问号结尾的文章都可以概括为“不”。那些了解我的人不会怀疑我想要颠覆这条定律的愿望。但这篇文章走得更远——它颠覆了问题本身。这个问题假设了质量和成本之间的常见权衡。在这篇文章中,我将解释这种权衡不适用于软件——高质量软件的生产成本实际上更低。

虽然我的大部分文章都是针对专业软件开发人员的,但这篇文章我不会假设读者有任何软件开发机制方面的知识。我希望这篇文章对任何参与思考软件工作的人都有价值,特别是那些作为软件开发团队客户的人,比如商业领袖。

我们习惯于在质量和成本之间进行权衡

正如我在开头提到的,我们都习惯于在质量和成本之间进行权衡。当我更换我的智能手机时,我可以选择处理器更快、屏幕更好、内存更大的更昂贵型号。或者我可以放弃其中一些品质来支付更少的钱。这不是绝对的规则,有时我们可以以更低的价格买到高质量的商品。更多的时候,我们对质量有不同的价值观——有些人并没有真正注意到一个屏幕比另一个屏幕好多少。但这种假设在大多数情况下是正确的,更高的质量通常意味着更高的成本。

软件质量意味着很多东西

如果我要谈论软件的质量,我需要解释一下它是什么。这就是第一个复杂之处——有很多东西可以算作软件的质量。我可以考虑用户界面:它是否能轻松地引导我完成我需要完成的任务,让我更有效率,并消除挫折感?我可以考虑它的可靠性:它是否包含会导致错误和挫折感的缺陷?另一个方面是它的架构:源代码是否被划分为清晰的模块,以便程序员可以轻松地找到并理解他们本周需要处理的代码?

这三个质量示例并非详尽无遗,但足以说明一个重要观点。如果我是一个软件的客户或用户,我不会欣赏我们所说的某些质量。用户可以判断用户界面是否良好。高管可以判断软件是否能让她的员工工作效率更高。用户和客户会注意到缺陷,特别是当它们损坏数据或导致系统暂时无法运行时。但客户和用户无法感知软件的架构。

因此,我将软件质量属性分为**外部**(如用户界面和缺陷)和**内部**(架构)。区别在于用户和客户可以看到是什么使软件产品具有较高的外部质量,但无法区分内部质量的高低。

乍一看,内部质量对客户来说并不重要

既然内部质量不是客户或用户能看到的东西——它重要吗?假设我和丽贝卡编写了一个应用程序来跟踪和预测航班延误。我们的应用程序都具有相同的基本功能,都具有同样优雅的用户界面,并且几乎没有任何缺陷。唯一的区别是,她的内部源代码组织得很整齐,而我的则是一团糟。还有一个区别:我以 6 美元的价格出售我的应用程序,而她以 10 美元的价格出售她的应用程序。

既然客户永远不会看到这段源代码,而且它不会影响应用程序的运行,为什么有人会为丽贝卡的软件多付 4 美元呢?更普遍地说,这应该意味着不值得为更高的内部质量支付更多的钱。

我换一种说法,用成本换取外部质量是有意义的,但用成本换取内部质量是没有意义的。用户可以判断他们是否愿意花更多的钱来获得更好的用户界面,因为他们可以评估用户界面是否足够好,值得额外花钱。但用户看不到软件的内部模块化结构,更不用说判断它是否更好了。为什么要为没有效果的东西多花钱?既然如此——为什么任何软件开发人员都要花时间和精力去提高他们工作的内部质量呢?

内部质量使增强软件变得更容易

那么,为什么软件开发人员会把内部质量作为一个问题呢?程序员的大部分时间都花在修改代码上。即使在一个新系统中,几乎所有的编程都是在现有代码库的上下文中完成的。当我想向软件添加一个新功能时,我的第一个任务是弄清楚这个功能如何融入现有应用程序的流程中。然后我需要改变这个流程,让我的功能适应进去。我经常需要使用应用程序中已经存在的数据,所以我需要了解这些数据代表什么,它们与周围数据的关联,以及我可能需要为我的新功能添加哪些数据。

所有这些都是关于我如何理解现有代码。但软件很容易变得难以理解。逻辑可能会变得混乱,数据可能难以理解,用来指代事物的名称在六个月前对托尼来说可能是有意义的,但对我来说却像他离开公司的理由一样神秘。所有这些都是开发人员所说的**垃圾代码**的形式——当前代码与理想代码之间的区别。

内部质量的主要特征之一是让我更容易弄清楚应用程序是如何工作的,这样我就可以看到如何添加东西。如果软件被很好地划分为独立的模块,我就不必阅读所有 50 万行代码,我可以在几个模块中快速找到几百行代码。如果我们努力做到清晰命名,我就可以快速理解代码的各个部分的功能,而无需费力地理解细节。如果数据能够明智地遵循底层业务的语言和结构,我就可以很容易地理解它与我从客户服务代表那里收到的请求之间的关联。垃圾代码会增加我理解如何进行更改所需的时间,还会增加我犯错的几率。如果我发现了自己的错误,那么我需要更多的时间来理解错误是什么以及如何修复它。如果我没有发现它们,那么我们就会遇到生产缺陷,并且需要更多的时间来修复它们。

我的更改也会影响未来。我可能会看到一个快速的方法来实现这个功能,但这条路线违背了程序的模块化结构,增加了垃圾代码。如果我选择这条路,我今天会更快地完成任务,但会拖慢未来几周和几个月内所有必须处理这段代码的人的速度。一旦团队中的其他成员做出同样的决定,一个易于修改的应用程序就会迅速积累垃圾代码,以至于每一个小的更改都需要花费数周的时间。

客户确实关心新功能的快速推出

在这里,我们看到了为什么内部质量对用户和客户很重要的线索。更好的内部质量使添加新功能变得更容易,因此更快、更便宜。我和丽贝卡现在可能有相同的应用程序,但在接下来的几个月里,丽贝卡的高内部质量让她能够每周添加新功能,而我却只能努力地清除垃圾代码,才能发布一个新功能。我无法与丽贝卡的速度竞争,很快她的软件的功能就比我的丰富得多。然后我所有的客户都删除了我的应用程序,转而使用丽贝卡的应用程序,即使她能够提高价格。

可视化内部质量的影响

内部质量的根本作用是降低未来变更的成本。但是编写好的软件需要一些额外的努力,这在短期内确实会产生一些成本。

一种可视化的方法是使用以下伪图,我在图中绘制了软件的累积功能与其生产时间(以及成本)的关系。对于大多数软件工作来说,曲线看起来像这样。

这就是内部质量差时的情况。最初进展很快,但随着时间的推移,添加新功能变得越来越困难。即使是很小的改动,也需要程序员理解大段的代码,而这些代码很难理解。当他们进行更改时,会出现意外的故障,导致测试时间过长,并且需要修复缺陷。

专注于高内部质量是为了减少生产力的下降。事实上,有些产品的情况正好相反,开发人员可以加快速度,因为新功能可以很容易地通过利用以前的工作来构建。这种情况比较少见,因为它需要一个技术娴熟、纪律严明的团队才能实现。但我们偶尔也会看到这种情况。

这里微妙之处在于,在一段时间内,低内部质量比高内部质量的生产力更高。在这段时间内,质量和成本之间存在某种权衡。当然,问题是:在两条线交叉之前,这段时间有多长?

在这一点上,我们遇到了为什么这是一个伪图的问题。没有办法衡量软件团队交付的功能。这种无法衡量产出,进而无法衡量生产力的现象,使得我们无法用确凿的数字来衡量低内部质量(这也很难衡量)的后果。无法衡量产出在专业工作中很常见——我们如何衡量律师或医生的生产力?

我评估线路交叉点的方法是,征求我认识的技术娴熟的开发人员的意见。答案让很多人感到惊讶。开发人员发现,质量低劣的代码会在几周内显著降低他们的速度。因此,在内部质量和成本之间进行权衡的空间并不大。即使是小型的软件工作也会受益于对良好软件实践的关注,这当然是我从我的经验中可以证明的。

即使是最优秀的团队也会制造垃圾代码

许多非开发人员倾向于认为代码腐化只是在开发团队粗心大意和犯错时才会发生的事情,但即使是最优秀的团队,在工作中也不可避免地会造成一些代码腐化。

我喜欢用我和我们一位最优秀的 technical team lead 聊天时的一个故事来说明这一点。他刚刚完成了一个被广泛认为非常成功的项目。客户对交付的系统感到满意,无论是在其功能方面,还是在其构建时间和成本方面。我们的人对参与该项目的经历持积极态度。这位技术主管总体上很高兴,但他承认系统的架构不是很好。我当时的反应是“怎么会这样——你可是我们最优秀的架构师之一啊?” 他的回答是任何经验丰富的软件架构师都熟悉的:“我们做出了正确的决定,但直到现在我们才明白应该如何构建它”。

许多人,包括软件行业中不少人,都把构建软件比作建造大教堂或摩天大楼——毕竟,我们为什么要用“架构师”来称呼高级程序员呢?但构建软件存在于一个物理世界所未知的不确定性世界中。软件的客户只是粗略地知道他们需要产品中的哪些功能,并且随着软件的构建而了解更多——特别是在早期版本发布给他们的用户之后。软件开发的基石——语言、库和平台——每隔几年就会发生重大变化。这相当于在现实世界中,客户通常会在建筑物建成并投入使用一半后增加新的楼层并更改楼层平面图,而混凝土的基本属性每隔一年就会发生变化。

鉴于这种变化水平,软件项目总是在创造新的东西。我们几乎从未发现自己在处理一个以前已经解决过的、很好理解的问题。自然而然地,我们在构建解决方案的过程中对问题有了最多的了解,因此我经常听到团队说,他们在花费一年左右的时间构建软件之后,才真正最了解软件的架构应该是什么。即使是最优秀的团队,他们的软件中也会有代码腐化。

区别在于,最优秀的团队不仅创建的代码腐化要少得多,而且还去除了足够多的代码腐化,从而能够继续快速添加功能。他们花时间创建自动化测试,以便能够快速发现问题并减少消除错误的时间。他们经常进行重构,以便在代码腐化积累到足以妨碍工作之前将其清除。持续集成最大限度地减少了由于团队成员目标不一致而导致的代码腐化积累。一个常见的比喻是,这就像清理厨房里的工作台面和设备。做饭的时候,你不可能不把东西弄脏,但如果你不快速清理,污垢就会变干,更难清除,而且所有脏东西都会妨碍下一道菜的烹饪。

高质量软件的生产成本更低

总结所有这些

  • 忽视内部质量会导致代码腐化迅速积累
  • 这种代码腐化会减慢功能开发速度
  • 即使是一个伟大的团队也会产生代码腐化,但通过保持较高的内部质量,能够将其控制在可控范围内
  • 较高的内部质量可最大限度地减少代码腐化,使团队能够以更少的精力、时间和成本添加功能。

遗憾的是,软件开发人员通常不善于解释这种情况。我曾无数次与开发团队交谈,他们说“他们(管理层)不允许我们编写高质量的代码,因为它太耗时”。开发人员经常通过证明适当专业性的必要性来证明对质量的关注是合理的。但这种道德上的论点意味着这种质量是有代价的——这注定了他们的论点是站不住脚的。令人恼火的是,由此产生的代码腐化既增加了开发人员的工作量,也浪费了客户的资金。在考虑内部质量时,我强调我们应该只从经济角度来考虑它。较高的内部质量降低了未来功能的成本,这意味着花时间编写好的代码实际上降低了成本。

这就是为什么本文开头的这个问题没有抓住重点的原因。高质量内部软件的“成本”是负的。成本和质量之间通常的权衡,我们在生活中大多数决定中都习惯了这种权衡,但在软件的内部质量方面却说不通。(对于外部质量来说,例如精心设计的用户体验,这是说得通的。)因为成本和内部质量之间的关系是一种不寻常的、反直觉的关系,所以通常很难理解。但理解这一点对于以最高效率开发软件至关重要。


衡量低内部质量的影响

(添加时间:2024 年 1 月 29 日)

正如我上面提到的,我们并不知道如何衡量内部质量或生产力,因此很难获得关于内部质量重要性的量化证据。然而,近年来,人们一直在加大这方面的努力。

我偶然发现的一篇特别有趣的论文是 Adam Tornhill 和 Markus Borg 的这项研究。他们使用其专有工具 CodeScene 来确定 39 个专有代码库中文件的健康状况。他们发现,解决低质量代码中的问题所花费的时间是高质量代码的两倍多,而且低质量代码的缺陷密度是高质量代码的 15 倍。

重大修订

2019 年 5 月 29 日:发布