软件工程中的一些谬误与总结

149 阅读11分钟

人月神话

背景

作者简介(引自维基百科):

小弗雷德雷克·菲利普斯·“弗雷德”·布鲁克斯(英语:Frederick Phillips "Fred" Brooks, Jr,1931年4月19日—2022年11月17日)是一名来自美国北卡罗来纳州达勒姆的软件工程师、学者,曾任IBM系统部主任,主持开发过OS/360等大型电脑计算机)用的操作系统软体

后来,布鲁克斯离开IBM公司,任教于北卡罗来纳大学教堂山分校,担任计算机科学Kenan讲座教授,并著书立说。他所著的《人月神话》一书,被视为是软件工程的重要书籍之一。为1999年图灵奖得主。

本书主要总结了一些软件工程中的谬误与经验,以“保持概念的完整性”作为中心的论点,探讨了其中的困难与解决方案,并在后续章节中探讨了软件工程管理的其他方面

在本篇文章中,我主要记录此书的读后感,在对此做总结的基础上加以延展,以适应四十年后的软件开发的时代背景

人月

💡 一个人六个月能写完的程序六个人一个月能写完吗?

在大部分软件项目中,缺乏合理的进度安排是造成项目滞后的主要原因,但是为什么这种情况出现的如此普遍呢?

  1. 我们对估算技术缺乏有效的研究,或者说这反映了一种普遍的假设:一切都将运作良好
  2. 我们经常假设人和月和互换,错误地将进度和工作量混淆
  3. 对于项目缺少跟踪和进度管理
  4. 当意识到进度的偏移时,传统的反应是增加人力,但是这就像用汽油灭火,只会导致事情更糟

一种普遍的工作量单位是人月,虽然成本随开发产品的开发者人数与时间的不同,有很大的变化,但是进度却不是如此,人数和时间的互换仅仅适用于以下情况:某个任务可以分解给参与人员,**并且他们之间不需要相互的交流,**当任务由于次序上的限制不能分解时,人手的添加对进度没有任何帮助

对于可以分解,但是子任务之间需要沟通和交流的任务,我们必须在计划工作中考虑沟通的工作量

沟通增加的负担由两个部分组成:培训和相互的交流。每个成员都需要进行技术,项目目标以及工作计划的培训,这种培训是不能分解的

相互之间交流的情况更糟一点,如果任务的每个部分都必须与其它部分单独沟通协作,那么工作量将按照n(n-1)/2 沟通工作量量/模块数 的量级递增,具体来说,2个模块的沟通工作量是1,3个模块的沟通工作量是3,4个模块的沟通工作量则是6

当用于沟通的工作量较大时,我们可能会被带到下图的窘境

⚠️ Brooks法则:向进度落后的项目中增加人手,只会使进度更加落后

我们来考虑一个具体的例子,假设我们有一个需要12个人月的任务,分派给3个成员在4个月的时间内完成,并且在每个月的结尾设置了可观测的里程碑A,B,C,D

现在假定两个月之后,第一个里程碑没有达到,此时,我们还有两个月的时间,剩余工作量至少还有9人月,我们面对的解决方案有什么呢?

  1. 假设任务必须完成,且造成延误的原因仅仅是第一部分工作量估计不当,那么目前我们还剩余9人月工作量,时间还有两个月,理论上来说我们需要4.5个开发人员,也就是在原本3个人的基础上增加2个人
  2. 假设任务必须完成,且造成延误的原因是整体任务的估计偏少,那么目前我们还剩余9+n人月工作量,需要4.5+0.5n个人,比原本多1.5+0.5n个人
  3. 重新安排进度
  4. 削减任务

在前两种情况下,由于重复生成的工作量,坚持把不经调整的任务在4个月内完成是很困难的,以第一种情况为例,我们聘请了两个新员工,那么他们至少需要一个老职员的培训,假设培训需要t个月的时间(t<1),那么将会有3t个人月投入到原有进度以外的工作中

那么在第三个月的月末,我们还会剩余4+3t个人月的工作,此时我们还有五个人,一个月的时间,也就是剩余5人月,可以计算出,当t>1/3时,增加人手甚至会减慢项目的进度

我们也可以基于上述假设做扩大计算:

image.png

除此之外,我们还没有考虑任务重新划分,额外系统进行系统测试的工作量,如果需要考虑这些因素,增加人手却会减慢项目进度的可能性会进一步增加

团队架构

❓ 如何在有意义的进度内安排创建大型的系统?

对于一个大型项目来说,从效率和概念的完整性来说,最好由少数人员进行设计和编码,但是对于大型项目,我们需要大量的人手才能使得产品在时间上满足要求,那么如何调和这两方面的矛盾呢?

harlan mills提出了一种创造性的解决方案:每个团队以外科手术的方式组建,而不是一拥而上

也就是说,和团队中每个成员截取问题的每一部分的做法相反,我们由一个人完成问题的分解,其他人给予他所需要的支持以提高生产力

image.png

上述定义的开发团队中有7个专业人士在解决问题,但是系统仅有一到两人思考产生,在客观上达成了一致性,简单的来说,这种架构通过将团队中人员职能分工专业化简化成员之间的交流成本,提高了开发效率

而对于更大的项目,我们可以按照上述结构进行类推,对于各种类型的角色进行按比例扩容

然而这种观念也会带来一系列的问题

  1. 如何避免结构设计师制定出无法实现,或是代价高昂的技术规格说明,使大家陷入困境?
  2. 如何才能与实现人员就技术说明的琐碎细节充分沟通,以确保设计被正确理解,并精准的整合到产品中?
  3. 这种结构是否意味着编程实现人员的创造性天赋与构思被压制了?

对于第一个问题,面对估计难度过高的问题,结构师有两个选择,削减设计或采用成本更低的实现方式,后者实际上是对开发人员提出挑战,因此如果想要成功,结构师需要记住以下几点

  1. 牢记是开发人员承担设计的实现责任,结构师只能建议而不能支配
  2. 时刻准备为所指定的说明建议一种实现方式
  3. 准备放弃坚持所作的改进建议

第三个问题的答案是否定的,在给定体系下实现其设计同样需要与设计编程技术一样的创造性,实际上,产品的成本性能很大程度上依靠实现人员,就如同易用性很大程度上依赖结构师一样

接下来对于第二个问题,我们试着进行更深入形象的探讨

❓ 一个由1000人开发,10个结构师主导的小组如何保持系统概念上的完整性?

手册或者书面规格说明,是一个非常必要的工具,仅有文档是不够的。手册是产品的外部规格说明,它描述和规定了用户所见的每一个细节;同样地,它也是结构师主要的工作产物

System/360 Principles of Operation的一致完整性仅来自两名作者:Gerry Blaauw和 Andris Padegs。思路是大约10个人的想法,但如果想保持文字和产品之间的一致性,则必须由一个或两个人来完成将其结论转换成书面规格说明的工作。而且,将定义书写成文字,必须对很多最初并不是非常重要的问题进行判断,并得出结论。例如,System/360需要决定在每次操作后,如何设置返回的条件码。其实,对于在整个设计中,保证这些看似琐碎的问题处理原则上的一致性,绝对不是一件无关紧要的事情。

维护问题

💡 事物在最初总是最好的

在程序发布后,并不会停止变化,后续的变更被称为程序维护

对于一个广泛使用的程序,其维护总成本通常是开发成本的40%或更多,而且该成本受用户数目的影响很大,用户越多,所发现的错误也就越多

麻省理工学院核科学实验室的Betty Campbell指出,上一个版本中被发现和修复的bug,在新的版本中仍然会出现,新版本中的薪功能会产生新的bug,在解决了这些问题以后,程序会正常运行几个月,接着,错误率会重新攀升

除此之外,还有一个问题是,bug修复总是会有一定几率引入新的bug,这个问题的出现主要由以下几个原因造成

  1. 部分看上去很微小的工作困难是系统级别的错误,修复局部问题的工作量很清晰且往往不大,但是更大范围的修复工作常常会被忽略
  2. 维护人员常常不是开发人员,而是一些初级程序员

Lehman和 Belady研究了大型操作系统的一系列发布版本的历史。他们发现模块总数量随版本号的增加呈线性增长,但是受到影响的模块数量随版本号的增加呈指数增长。所有修改都倾向于破坏系统的架构,增加了系统的混乱程度(嫡)。用在修复原有设计上瑕疵的工作量越来越少,而早期维护活动本身所引起的漏洞的修复工作越来越多。随着时间的推移,系统变得越来越无序,修复工作迟早会失去根基。每一步前进都伴随着一步后退。尽管系统在理论上一直可用,但实际上,整个系统已经面目全非,无法再成为下一步进展的基础。而且,机器在变化,配置在变化,用户的需求在变化,现实系统不可能永远可用。崭新的、基于原有系统的重新设计是完全必要的。

文档

每个用户都需要一段对程序进行描述的文字,可是大多数文档只会提供很少的总结性内容,就像是描绘了树木,形容了树皮和树叶,却没有一幅森林的图案,作者总结,一份有效的文档需要包含以下内容

  1. 目的:程序的主要功能,开发程序的原因是什么?
  2. 环境:程序运行什么样的机器,硬件配置和操作系统上?
  3. 输入输出格式:输入的有效范围是什么?允许的合法输出范围是什么?
  4. 实现功能和使用的算法
  5. 运行时间:在指定的配置下,解决特定规模所需要的时间
  6. 精度和校验

数据处理的基本原理告诉我们,试图维持不同文档之间的同步关系是一件很麻烦的事情,更合理的方法是:把文档整合到源程序,也就是自文档化

对于我们目前的工作流来说,可以自文档化的地方大致有三个

  1. 代码的注释
  2. 项目或功能模块的readme
  3. git commit message

而这三者基本上都有一套通用的基础解决方案,由于其内容较多,在这里我只简要介绍并给出链接

  1. google js doc,由Google提出的JavaScript编码规范

Google JavaScript Style Guide

  1. clean code,相比于看起来比较枯燥的Google JavaScript style guide,clean code指南更加易于理解,下面给出一篇我的个人记录以及官方指南

clean code

GitHub - ryanmcdermott/clean-code-javascript: :bathtub: Clean Code concepts adapted for JavaScript

  1. git commit message,一个好的git commit message能帮助review人员更好的了解这次commit变更了什么,也能帮助开发人员在出现问题时进行更准确的回退