《人月神话》是一本经典的软件工程的巨作,作者布鲁克斯(FrederickP.Brooks)被誉为“IBMSystem/360之父“,他曾是这一项目的项目经理,后来在设计期担任360操作系统的项目经理。由于这一工作,他与BobEvans和ErichBloch1985年曾获美国国家技术奖。Brooks博士曾经早期担任IBM公司Stretch和Harvest计算机的体系结构设计师。1999年,他还荣获美国计算机领域最高奖图灵奖。由于本书是他领导IBM/360软件开发经验的结晶,内容丰富而生动,成为软件工程方面的经典之作。
在很多方面,管理一个大型的计算机编程项目和其它行业的大型工程很相似。
在很多另外的方面,它又有差别。
全书的中心论点:我相信由于人员的分工,大型编程项目碰到的管理问题和小项目区别很大;我相信关键需要是维持产品自身的概念完整性。并探讨其中的困难和解决方法。
过去几十年的大型系统开发就犹如这样焦油坑,他们中大多数开发出了可运行的系统——不过,其中只有非常少数的项目满足了目标、时间进度和预算的要求,表面上看起来好像没有任何一个单独的问题会导致困难,每个都能被解决,但是当它们相互纠缠和累积在一起的时候,团队的行动就会变得越来越慢。
首先是一种创建事物的纯粹快乐。
其次,快乐来自于开发对其他人有用的东西。
第三是整个过程体现出魔术般的力量——将相互啮合的零部件组装在一起,看到它们精妙地运行,得到预先所希望的结果。
第四是学习的乐趣,来自于这项工作的非重复特性。
最后,乐趣还来自于工作在如此易于驾驭的介质上。
首先,必须追求完美。
其次,是由他人来设定目标,供给资源,提供信息。
概念性设计是有趣的,但寻找琐碎的bug却只是一项重复性的活动。
调试和查错往往是线性收敛的。
产品在即将完成或者终于完成的时候,却已显得陈旧过时。
缺乏合理的时间进度是造成项目滞后的最主要原因。
导致这种普遍性灾难的原因是什么呢?
我们对估算技术缺乏有效的研究,并不真实的假设——一切都将运作良好。
我们采用的估算技术隐含地假设人和月可以互换。
对自己的估算缺乏信心。
对进度缺少跟踪和监督。
增加人力。
所有的编程人员都是乐观主义者。
将创造性活动分为三个阶段:构思、实现和交流。
正由于介质的易于驾驭,我们期待在实现过程中不会碰到困难,因此造成了乐观主义的弥漫。而我们的构思是有缺陷的,因此总会有bug。也就是说,我们的乐观主义并不应该是理所应当的。
成本的确随开发产品的人数和时间的不同,有着很大的变化,进度却不是如此。
它暗示着人员数量和时间是可以相互替换的。
沟通所增加的负担由两个部分组成,培训和相互的交流。
软件开发本质上是一项系统工作——错综复杂关系下的一种实践——沟通、交流的工作量非常大,它很快会消耗任务分解所节省下来的个人时间。从而,添加更多的人手,实际上是延长了,而不是缩短了时间进度。
软件任务进度安排:
1/3计划1/6编码
1/4构件测试和早期系统测试
1/4系统测试,所有的构件已完成
除了系统测试,进度基本能保证。
为了满足顾客期望的日期而造成的不合理进度安排,在软件领域中却比其他的任何工程领域要普遍得多。
Brooks法则:向进度落后的项目中增加人手,只会使进度更加落后。
最好的和最差的表现在生产率上平均为10:1;在运行速度和空间上具有5:1的惊人差异。
数据显示经验和实际的表现没有相互联系。
Mills建议大型项目的每一个部分由一个团队解决,但是该队伍以类似外科手术的方式组建,而并非一拥而上。也就是说,同每个成员截取问题某个部分的做法相反,由一个人来进行问题的分解,其他人给予他所需要的支持,以提高效率和生产力。
外科医生。Mills称之为首席程序员。他亲自定义功能和性能技术说明书,设计程序,编制源代码,测试以及书写技术文档。
副手。较少的经验。经常在与其他团队的功能和接口讨论中代表自己的小组。详细了解所有的代码,研究设计策略的备选方案。
管理员。与组织中其他管理机构的接口。
编辑。书写文档。
两个秘书。管理员和编辑每个人需要一个秘书。
程序职员。他负责维护编程产品库中所有团队的技术记录。
工具维护人员。
测试人员。编写的工作片段,以及对整个工作进行测试。
语言专家。
外科手术队伍可以达到客观的一致性。
团队中剩余人员职能的专业化分工是高效的关键。
扩建过程的成功依赖于这样一个事实,即每个部分的概念完整性得到了彻底的提高。
对于协调的问题,还是需要使用分解的技术。
概念完整性应该是最重要的考虑因素。
编程系统(软件)的目的是使计算机更加容易使用。
目标是易用性,功能与理解上复杂程度的比值才是系统设计的最终测试标准。
对于给定级别的功能,能用最简洁和直接的方式来指明事情的系统是最好的。
概念的完整性要求设计必须由一个人,或者非常少数互有默契的人员来实现。
系统的体系结构指的是完整和详细的用户接口说明。
结构师的工作,是运用专业技术知识来支持用户的真正利益,而不是维护销售人员所鼓吹的利益。
体系结构同实现必须仔细地区分开来。
在说明完成的时候,才雇用编程实现人员。
首先,必须设定良好定义的时间和空间目标,了解产品运行的平台配置。接着,他可以开始设计模块的边界、表结构、算法以及所有的工具。另外,还需要花费一些时间和体系结构师沟通。
结构师必须
牢记是开发人员承担创造性和发明性的实现责任,所以结构师只能建议,而不能支配;
时刻准备着为所指定的说明建议一种实现的方法,同样准备接受其他任何能达到目标的方法;
对上述的建议保持低调和平静;
准备放弃坚持所作的改进建议; 一般开发人员会反对体系结构上的修改建议。通常他是对的——当正在实现产品时,某些特性的修改会造成意料不到的成本开销。
第二个系统是设计师们所设计的最危险的系统。
项目经理如何避免画蛇添足?他必须坚持至少拥有两个系统以上开发经验结构师的决定。同时,保持对特殊诱惑的警觉,他可以不断提出正确的问题,确保原则上的概念和目标在详细设计中得到完整的体现。
手册、或者书面规格说明,是一个非常必要的工具。手册是产品的外部规格说明,它描述和规定了用户所见的每一个细节;同样的,它也是结构师主要的工作产物。
规格说明也不断地被重复准备和修改。
在进度表上应该有带日期的版本信息。
手册不但要描述包括所有界面在内的用户可见的一切,它同时还要避免描述用户看不见的事物。后者是编程实现人员的工作范畴,而实现人员的设计和创造是不应该被限制的。体系结构设计人员必须为自己描述的任何特性准备一种实现方法,但是他不应该试图支配具体的实现过程。
规格说明的风格必须清晰、完整和准确。
形式化定义是精确的,它们倾向于更加完整;差异得更加明显,可以更快地完成。但是形式化定义的缺点是不易理解。
我想将来的规格说明同时包括形式化和记叙性定义两种方式。
必须以一种作为标准,另一种作为辅助描述。
形式化定义可以是一种设计实现。反之,设计实现也可以作为一种形式化定义的方法。
会议是必要的。
周例会和年度大会——这实际上是一种非常有效的方式。
任何人可以提出问题和修改意见,但是建议书通常是以书面形式,在会议之前分发。
重点是创新,而不仅仅是结论。
接着会对详细的变更建议做出决策。
为解决这些堆积起来的问题,我们会举行年度大会,典型的年度大会会持续两周。
充足时间来自新技术的开发日程;而多重实现的同时开发带来了策略上的平等性。
如实地遵从手册更新机器所造成的延迟和成本的消耗,比根据机器调整手册要低。
为什么项目还会失败呢?他们还缺乏些什么?两个方面——交流,以及交流的结果——组织。
团队如何进行相互之间的交流沟通呢?非正式途径、会议、工作手册。
项目工作手册不是独立的一篇文档,它是对项目必须产出的一系列文档进行组织的一种结构。
他发现的不仅仅是思路,而且还有能追溯到最早备忘录的许多文字和章节,这些备忘录对产品提出建议或者解释设计。
控制信息发布。
工作手册的实时更新是非常关键的。
理解的问题可以通过持续的文档维护来解决。
团队组织的目的是减少不必要交流和合作的数量。
减少交流的方法是人力划分和限定职责范围。
产品负责人的角色是什么?他组建团队,划分工作及制订进度表。他要求,并一直要求必要的资源。
产品负责人和技术主管是同一个人。
第二,大型项目中,每个角色都必须全职工作,甚至还要加班。
产品负责人作为总指挥,技术主管充当其左右手。
仅仅通过对编码部分的估计,然后应用上述比率,是无法得到对整个任务的估计的。
构建独立小型程序的数据不适用于编程系统产品。
编译器的复杂度是批处理程序的三倍,操作系统复杂度是编译器的三倍。
对常用编程语句而言。生产率似乎是固定的。
使用适当的高级语言,编程的生产率可以提高5倍。
用功能交换尺寸。
设计人员必须决定用户可选项目的粗细程度。
考虑空间-时间的折衷。
报价、预测、价格:这三个因素互相牵制,决定了项目的成败。
首先,书面记录决策是必要的。
第二,文档能够作为同其他人的沟通渠道。
最后,项目经理的文档可以作为数据基础和检查列表。
一旦认识到试验性的系统必须被构建和丢弃,具有变更思想的重新设计不可避免,从而直面整个变化现象是非常有用的。第一步是接受这样的事实:变化是与生俱来的,不是不合时宜和令人生厌的异常情况。
如何为上述变化设计系统,它们包括细致的模块化、可扩展的函数、精确完整的模块间接口设计、完备的文档。另外,还可能会采用包括调用队列和表驱动的一些技术。
最重要的措施是使用高级语言和自文档技术。
变更的阶段化是一种必要的技术。
保持了两条职位晋升线
管理线:高级准程序员->项目程序员->开发程序员->高级程序员
技术线:高级准程序员->程序职员->顾问程序员->高级程序员
软件维护主要包含对设计缺陷的修复。
对于一个广泛使用的程序,其维护总成本通常是开发成本的40%或更多。该成本受用户数目的严重影响。
缺陷修复总会以(20-50)%的机率引入新的bug。
为什么缺陷不能更彻底地被修复?首先,看上去很轻微的错误,似乎仅仅是局部操作上的失败,实际上却是系统级别的问题,通常这不是很明显。其次,维护人员常常不是编写代码的开发人员,而是一些初级程序员或者新手。
理论上,在每次修复之后,必须重新运行先前所有的测试用例。
实际情况中,回归测试必须接近上述理想状况,所以它的成本非常高。
项目经理应该制订一套策略,并为通用工具的开发分配资源。
这有两个重要的理念。首先是受控,即程序的拷贝属于经理,他可以独立地授权程序的变更。其次是使发布的进展变得正式,以及开发库与集成、发布的正式分离。
关键的工作是产品定义。许许多多的失败完全源于那些产品未精确定义的地方。
测试规格说明。
自顶向下的设计。好的自顶向下设计从几个方面避免了bug。首先,清晰的结构和表达方式更容易对需求和模块功能进行精确的描述。其次,模块分割和模块独立性避免了系统级的bug。另外,细节的隐藏使结构上的缺陷更加容易识别。第四,设计在每个精化步骤的层次上是可以测试的,所以测试可以尽早开始,并且每个步骤的重点可以放在合适的级别上。
结构化编程。
本机调试。
内存转储。
快照。
交互式调试。第一次交互取得的工作进展是后续交互的三倍。
测试用例。
软件系统开发过程中出乎意料的困难部分是系统集成测试。
使用经过调试的构件单元。
搭建充分的测试平台。测试平台可能会有相当于测试对象一半的代码量。
控制变更。
一次添加一个构件。
阶段(量子)化、定期变更。阶段(量子)要么很大,间隔很宽;要么小而频繁。小而频繁的阶段很容易变得不稳定。
制订进度表。里程碑必须是具体的、特定的、可度量的事件,能够进行清晰定义。
每个老板都需要两种信息:需要采取行动的计划方面的问题,用来进行分析的状态数据。
减少角色的冲突。首先老板必须区别行动信息和状态信息,不对项目经理可以解决的问题做出反应,并且决不在检查状态报告的时候做安排。
计划日期是项目经理的工作产物,代表了经协调后的项目整体工作计划,它是合理计划之前的判断。估计日期是最基层经理的工作产物,基层经理对所讨论的工作有着深刻的了解。
文档的重要性:旨在延长软件的生命期、克服惰性和进度的压力。
使用程序
1.目的。
2.环境。
3.范围。
4.实现功能和使用的算法。
5.输入-输出格式。
6.操作指令。
7.选项。
8.运行时间。
9.精度和校验。期望结果的精确程度?如何进行精度的检测?
1.针对遇到的大多数常规数据和程序主要功能进行测试的用例。
2.数量相对较少的合法数据测试用例。
3.数量相对较少的非法数据测试用例。
1.流程图或子系统的结构图。
2.对所用算法的完整描述,或者是对文档中类似描述的引用。
3.对所有文件规划的解释。
4.数据流的概要描述。
5.初始设计中,对已预见修改的讨论。
很少有程序需要一页纸以上的流程图。
流程图被鼓吹的程度远大于它们的实际作用。
每个数据项包含两个文件都需要的所有信息,采用指定的键值来区别,并把它们组合到一个文件中。
我们在程序文档编制的实践中却违反了我们自己的原则。典型的,我们试图维护一份机器可读的程序,以及一系列包含记叙性文字和流程图的文档。
第一个想法是借助那些出于语言的要求而必须存在的语句,来附加尽可能多的“文档”信息。
第二个方法是尽可能地使用空格和一致的格式提高程序的可读性,表现从属和嵌套关系。
第三,以段落注释的形式,向程序中插入必要的记叙性文字。
1.为每次运算使用单独的任务名称。
2.使用包含版本号和能帮助记忆的程序名称。
3.在过程的注释中,包含记叙性的描述文字。
4.尽可能为基本算法提供参考引用,通常它会指向更完备的处理方法
5.显示和算法书籍中的传统算法的关系。a)更改b)定制细化c)重新表达
6.声明所有的变量。
7.用标签标记出初始化的位置。
8.对程序语句进行分组和标记。
9.利用缩进表现结构和分组。
10.在程序列表中,手工添加逻辑箭头。
11.使用行注释来解释任何不很清楚的事情。
12.把多条语句放置在一行,或者把一条语句拆放在若干行。
自文档化方法激发了高级语言的使用,特别是用于在线系统的高级语言——无论是对批处理还是交互式,它都表现出最强的功效和应用的理由。
没有任何技术或管理上的进展,能够独立地许诺十年内使生产率、可靠性或简洁性获得数量级上的进步。
所有软件活动包括根本任务——打造由抽象软件实体构成的复杂概念结构,次要任务——使用编程语言表达这些抽象实体,在空间和时间限制内将它们映射成机器语言。
即使全部次要任务的时间缩减到零,也不会给生产率带来数量级上的提高。
大家熟悉的软件项目具有一些人狼的特性(至少在非技术经理看来),常常看似简单明了的东西,却有可能变成一个落后进度、超出预算、存在大量缺陷的怪物。
这样的畸形并不是由于软件发展得太慢,而是因为计算机硬件发展得太快。
根本的——软件特性中固有的困难,次要的——出现在目前生产上的。
软件开发中困难的部分是规格化、设计和测试这些概念上的结构,而不是对概念进行表达和对实现逼真程度进行验证。
现代软件系统中这些无法规避的内在特性:复杂度、一致性、可变性和不可见性。
没有任何两个软件部分是相同的。
软件实体的扩展也不仅仅是相同元素重复添加,而必须是不同元素实体的添加。
软件的复杂度是必要属性,不是次要因素。
上述软件特有的复杂度问题造成了很多经典的软件产品开发问题。由于复杂度,团队成员之间的沟通非常困难,导致了产品瑕疵、成本超支和进度延迟;由于复杂度,列举和理解所有可能的状态十分困难,影响了产品的可靠性;由于函数的复杂度,函数调用变得困难,导致程序难以使用;由于结构性复杂度,程序难以在不产生副作用的情况下用新函数扩充;由于结构性复杂度,造成很多安全机制状态上的不可见性。
复杂度不仅仅导致技术上的困难,还引发了很多管理上的问题。它使全面理解问题变得困难,从而妨碍了概念上的完整性;它使所有离散出口难以寻找和控制;它引起了大量学习和理解上的负担,使开发慢慢演变成了一场灾难。
他必须控制的很多复杂度是随心所欲、毫无规则可言的,来自若干必须遵循的人为惯例和系统。
系统中的软件包含了很多功能,而功能是最容易感受变更压力的部分。另外的原因是因为软件可以很容易地进行修改——它是纯粹思维活动的产物,可以无限扩展。
软件必须与各种新生事物保持一致。
软件是不可见的和无法可视化的。
软件的客观存在不具有空间的形体特征。
高级语言。
大多数观察者相信开发生产率至少提高了五倍,同时可靠性、简洁性和理解程度也大为提高。
分时。
统一编程环境。提供集成库、统一文件格式、管道和过滤器。
Ada和其他高级编程语言。
面向对象编程。
抽象数据类型的概念是指对象类型应该通过一个名称、一系列合适的值和操作来定义,而不是理应被隐藏的存储结构。
层次化类型,是允许定义可以被后续子类型精化的通用接口。这两个概念是互不相干的——可以只有层次化,没有数据隐藏;也可能是只有数据隐藏,而没有层次化。
人工智能。
专家系统。专家系统是包含归纳推论引擎和规则基础的程序,它接收输入数据和假设条件,通过从基础规则推导逻辑结果,提出结论和建议,向用户展示前因后果,并解释最终的结果。
如何把它应用在软件开发工作中?可以通过很多途径:建议接口规则、制订测试策略、记录各种bug产生的频率、提供优化建议等等。
最优秀和一般的软件工程实践之间的差距是非常大的,可能比其他工程领域中的差距都要大。
“自动”编程。
图形化编程。
程序验证。程序验证不意味着零缺陷的程序。
环境和工具。特定语言的智能化编辑器在现实中还没有得到广泛应用,不过它们最有希望实现的是消除语法错误和简单的语义错误。
工作站。
工作的创造性部分占据了大部分时间,那么那些仅仅是表达概念的活动并不能在很大程度上影响生产率。
购买和自行开发。构建软件最可能的彻底解决方案是不开发任何软件。
需求精炼和快速原型。开发软件系统的过程中,最困难的部分是确切地决定搭建什么样的系统。
软件开发人员为客户所承担的最重要的职能是不断重复地抽取和细化产品的需求。事实上,客户不知道他们自己需要什么。
计划任何软件活动时,要让客户和设计人员之间进行多次广泛的交流沟通,并将其作为系统定义的一部分。这是非常必要的。
原型的目的是明确实际的概念结构,从而客户可以测试一致性和可用性。
增量开发——增长,而非搭建系统。
卓越的设计人员。
低劣设计和良好设计之间的区别可能在于设计方法中的完善性,而良好设计和卓越设计之间的区别肯定不是如此。卓越设计来自卓越的设计人员。软件开发是一个创造性的过程。完备的方法学可以培养和释放创造性的思维,但它无法孕育或激发创造性的过程。
非常卓越的设计者产生的成果更快、更小、更简单、更优雅,实现的代价更少。卓越和一般之间的差异接近于一个数量级。
可以着手的最重要工作是寻求培养卓越设计人员的途径。