本文要点
软件开发过程非常复杂,因此很难理解。
这种复杂性导致了许多没有根据的信念和来历不明的直觉。
对开发过程的最新研究挑战了许多人们普遍持有的信念。
某些研究结果没有那么直观,但它们揭示了开发过程中意想不到的力量。
在软件开发中,非技术因素往往胜过技术因素。
最近,我对项目中使用的编程语言与代码质量之间的相关性进行了研究。我很震惊,因为结果和我的期待恰恰相反。一方面,该研究可能存在缺陷,另一方面,软件开发中的许多既定实践和信念起源不明。我们之所这样做,是因为“每个人”都在这样做,或者它们被认为是最佳实践,或者它们是由“福音传教士”(这个名字本身就是一个警告信号)所宣讲的。它们真的有用吗,还是只是“都市传奇”?如果我们看看硬数据(hard data)呢?我查了其他几篇论文,所有的结果都令人惊讶。
考虑到软件系统在我们经济中的重要性,令人惊讶的是,关于开发过程的科学研究是如此之少。其中一个原因可能是软件开发过程非常昂贵,并且通常由那些不愿意让研究人员进入的公司所有,这使得在实际项目上进行实验变得不切实际。最近,像 GitHub 或 GitLab 这样的公共代码库改变了这种情况,它们提供了易于访问的数据。越来越多的研究人员试图深入研究这些数据。
最早的一项基于公共存储库数据的研究发表于 2016 年,题为《大型生态系统研究:编程语言对代码质量的影响》。它试图验证一个被普遍认为是理所当然的信念,即某些编程语言生成的代码质量要高于其他语言生成的。研究人员正在寻找编程语言与缺陷数量和类型之间的关系。在用 17 种语言开发的 729 个 GitHub 项目中,通过对与 bug 相关的提交进行分析,结果确实显示出了预期的相关性。值得注意的是,像 TypeScript、 Clojure、Haskell、Ruby 和 Scala 这样的语言比 C、C++、Objective-C、JavaScript、PHP 和 Python 更容易出错。
一般来说,函数式和静态类型语言比动态类型、脚本或过程式语言更不容易出错。有趣的是,缺陷类型与语言的相关性比缺陷的数量更强。总的来说,这一结果并不令人惊讶,它证实了社区大多数人所相信的事实。这项研究获得了普及,并被广泛引用。需要注意的是,结果是基于统计的,解释统计结果必须要小心。统计上的显著性并不总是具有实际意义,而且正如作者所合理警告的那样,相关性并非因果关系。这项研究的结果并不意味着(尽管很多读者都是这样解释的)如果你把 C 改成 Haskell,代码中的 bug 就会减少。无论如何,这篇论文至少提供了有数据支持的论点。
但这并不是故事的结局。由于科学方法的基石之一是重复,因此有研究团队从 2016 年开始就试图复制这项研究。在纠正了原论文中发现的一些方法上的缺陷之后,研究结果于 2019 年发表在《编程语言对代码质量的影响(重复研究)》一文中。
重复研究还远未成功,大部分来自原始论文中的声明都没有被复制。虽然有些相关性在统计上仍然显著,但从实际的角度来看,它们其实并不显著。换句话说,如果我们看一下数据,会发现选择哪种编程语言似乎并不重要,至少就 bug 的数量而言是如此。不相信吗?让我们看看另一篇论文。
2019 年发表的论文《了解真实世界中Go的并发bug》,重点介绍了使用 Go(一种由谷歌开发的现代编程语言)开发的项目中的并发 bug。Go 是经过专门设计的,它能使并发编程更容易且更不易出错。尽管 Go 提倡使用消息来传递并发性以减少出错的可能性,但它同时提供了消息传递和共享内存同步并发的机制,因此,如果想要比较这两种方法,它就是一个自然而然的选择了。研究人员分析了六个流行的开源 Go 项目中发现的并发 bug,这些项目包括 Docker、Kubernetes 和 gRPC。分析结果甚至让作者也感到困惑:
>“令人惊讶的是,我们的研究表明,与共享内存一样,通过消息传递也很容易产生并发 bug,有时甚至更容易。”
尽管到目前为止,我们所看到的研究表明,编程语言的进步对代码缺陷的影响并不大,但还有另一种解释。
让我们看一看另一项研究——20 世纪 80 年代初进行的经典的慕尼黑出租车试验,虽然这项研究与 IT 无关,但它与道路安全有关,研究人员也遇到了类似的不直观的结果。20 世纪 80 年代,德国汽车制造商开始在汽车上安装首个 ABS(anti-lock braking system,防抱死刹车系统)。由于 ABS 能使汽车在刹车过程中更加稳定,人们自然希望它能提高在道路上的安全性。研究人员想知道能提高多少。他们与一家出租车公司合作,该公司计划在他们的车队中安装 ABS。研究选择了 3000 辆出租车,并从中随机选择一半的汽车在其上安装 ABS。研究人员对这些汽车进行了 3 年的观察。之后,他们比较了 ABS 组和非 ABS 组的事故率。结果十分令人惊讶,几乎没有区别,甚至装有 ABS 的汽车,发生事故的可能性稍微更高一些。
从理论上讲,对 Go 中的 bug 率和并发 bug 的研究应该是有所区别的,但数据显示并非如此。在 ABS 实验中,研究人员收集了更多的数据。首先,这些汽车装有黑匣子,可以收集诸如速度和加速度之类的信息。其次,观察员被分配到司机那里,记录他们在路上的行为。数据显示的画面很清楚。在汽车上安装了 ABS 之后,司机在路上的行为就会改变。注意到他们现在可以更好地控制汽车了,停车距离也就更短了,司机开始更快、更危险地驾驶,急转弯,尾随。
对这一现象的解释是基于心理学的目标风险这一概念的,即人们的行为使得总体风险(即目标风险)处于恒定的水平上。当环境发生变化时,人们会调整自己的行为,从而使风险水平保持不变。在汽车上安装 ABS 可以降低驾驶风险,因此司机为了弥补这一变化,开始更加积极地驾驶。在其他领域也发现了类似的风险补偿。当孩子们穿着防护装备进行体育运动时,会冒更多的身体风险;带有防儿童盖的药瓶会使家长对药物更加粗心;降落伞上开伞绳越好,拉上的越晚。
让我们回到代码质量的研究上来。研究人员在分析什么?向代码存储库的提交。开发人员何时提交代码?当他足够确定代码质量可接受时。换句话说,当提交错误代码的风险处于合理水平时。当开发人员切换到一种不易出错的语言时会发生什么?她很快就会注意到,她现在可以编写更少的测试,花更少的时间来审查代码,并且跳过某些质量检查,也能保持相同的提交低质量代码的风险。就像安装了 ABS 的司机一样,她会调整自己的行为以适应新情况,因此目标风险和以前一样。每个开发人员都有一个代码质量的内部标准,并将提交低于此标准的代码视为目标风险。请注意,目标风险和标准在开发人员中会有所不同,但研究表明,就平均而言,在使用不同语言的开发人员中,目标风险和标准是相同的。
一个很自然的问题是,对于其他能提高代码质量的成熟技术又如何呢?我找了其中两个相关技术点的论文:结对编程和代码审查。它们是否能像通常所宣扬的那样起作用呢?嗯,是也不是,事实证明情况有点复杂。在这两种情况下,都有一些研究在检验这种方法的有效性。
让我们来看看结对编程实验的元分析:《结对编程的有效性:元分析》。它能提高代码质量吗?“分析表明,结对编程对质量有很小的显著积极影响。小的积极影响听起来有点令人失望,但这并不是故事的结局。
>“对证据进行更详细的研究表明,当编程任务的复杂度较低时,结对编程比单独编程要快;而当任务复杂度高时,结对编程产生的代码解决方案质量更高。复杂任务的更高质量需要付出更大的努力,而缩短较简单任务的完成时间则是以显著降低质量为代价的。”
对于代码审查,研究结果通常更加一致,但是在早期缺陷检测方面,其主要的好处并不像我预期的那样。正如微软代码审查实践研究《现代代码审查的期望、结果和挑战》一书的作者所总结的那样:
>“我们的研究表明,虽然发现缺陷仍然是进行审查的主要动机,但是审查对缺陷的关注比预期的要少,而是提供了额外的好处,如知识转移、提高团队意识以及创建问题的替代解决方案。”
一个很自然的问题是,为什么科学研究的结果与我们社区的普遍信念之间存在差异呢?其中一个原因可能是学术界和实践者之间的分歧,使得研究结果对开发人员来说很困难,但这只是故事的一部分。
在 20 世纪 80 年代中期,Fred Brooks 发表了著名的论文《没有银弹:软件工程的本质性与附属性工作》。在介绍中,他将软件项目比作狼人
>“我们熟悉的软件项目都有这样的特点(至少在非技术经理看来是这样的),通常是无辜而直接的,但是它却有可能成为一个错过进度、挥霍预算并且产品有缺陷的怪物。因此,我们听到了迫切需要银弹的呼声,希望能让软件成本像计算机硬件成本那样迅速下降”
他认为,由于软件开发的本质,在软件开发中没有银弹。这本身就是一项内在复杂的努力。在 20 世纪 80 年代,大多数软件都运行在单台只有单核处理器的机器上,互联网还处于早期发展阶段,智能手机也还属于遥远的未来,没有人听说过虚拟化或云计算。Brooks 写的主要是技术复杂性,现在我们更加意识到软件开发中所涉及的社会、心理和商业过程的复杂性。
自从 Brooks 的论文出版以来,这种复杂性也大大增加了。开发团队规模更大了,通常是分布式且具有多文化的,软件系统与商业和社会组织的联系更紧密了。尽管所有这些都有了进步,但软件开发仍然极其复杂,有时甚至处于混乱的边缘。我们必须面对不断变化的需求、不断增加的技术复杂性以及由纠缠不清的技术、商业和社会力量造成的混乱的非线性反馈回路。在这样的环境中发生了什么,我们大脑的自然线路很难搞清楚。IT 界充斥着炒作、神话和宗教战争,这也就不足为奇了。我们拼命想弄明白所有的东西,所以我们的大脑会做它们真正擅长的事情——寻找模式。
有时它们太好了,我们看到了火星表面的通道,随机点的面孔,轮盘赌的规则。一旦我们开始相信某种东西,我们就真的对它上瘾了,每一次对我们信念的确认都会给我们注射多巴胺。我们开始保护我们的信念,结果我们把自己关在回音室里了,我们选择了可以证实我们所珍视的信念的会议、书籍、媒体。随着时间的推移,这些信念固化为一种几乎没人敢挑战的教条。
即使有科学的方法可以让我们以更合理的方式来处理复杂性和我们的偏见,在软件开发这样的复杂过程中,也很难预测一个行为的结果。我们改用更好的编程语言,但代码质量并没有改变,我们引入结对编程或代码审查来提高代码质量,但我们却体验到了更低的质量,或者在一些意想不到的领域受益。但这种复杂性也有好的方面——我们可以找到意想不到的杠杆点。如果我们想提高代码质量,而不是寻找技术解决方案,比如一种新的编程语言或更好的工具,我们可以专注于改进开发文化、提高质量标准或使提交 bug 的风险更大。
从这个角度看,可以发现一些不明显的机会。例如,如果一个团队引入了代码审查,那么它会使开发人员生成的代码对团队其他成员更为可见,从而增加了提交劣质代码的风险。因此,代码审查应该具有提高代码提交质量的作用,不仅仅是通过审查人员来发现 bug 或是否违反标准(上面引用的研究人员正在寻找的),而是可以防止开发人员提交 bug。换句话说,为了提高代码的质量,就应该足以让开发人员相信,他们的代码都会被审查,即使没有人在做审查。
研究的寓意还在于,技术因素离不开心理和文化因素。与其他许多领域一样,基于数据的研究表明,世界并不像我们所认为的那样运转。为了检验我们的信念与现实的符合程度,我们不需要等待研究人员来进行长期的研究。前段时间,我们在某个话题上引起了一场激烈的争论,双方都有很多论点。大约半小时后,有人说“让我们在网上查一下吧”。我们在 30 秒内解决了分歧。科学思维和某些怀疑态度不是只留给科学家的,有时在网上快速查询一下就够了,有时我们需要收集和分析数据,但在许多情况下,这不是火箭科学。但是如何在软件开发实践中引入更多的合理性是一个广泛的话题,也许值得另写一篇文章来讨论。
作者简介
Jacek Sokulski 有 20 多年的软件开发经验。目前在 DOT Systems 担任软件架构师。他的兴趣广泛,涵盖了从分布式系统、软件架构到复杂系统、人工智能、心理学、哲学等领域。他拥有数学博士学位和心理学硕士学位证书。
原文链接:
https://www.infoq.com/articles/search-code-quality/
评论