最近在读《人月神话》,这是一本成书于 1975 年的关于软件工程的书,考虑到计算机领域的飞速发展,这本书真的可以算是计算机历史上洪荒时代的作品了,然而虽然时代久远,但是这本书仍然被无数人推荐必读,可谓畅销数十年。既然如此,作为半路出家的软件开发工程师,我当然要拜读一下了。

我手上这本是 32 周年中文纪念版,这本书主要有两个版本,75 年的初版和 95 年的二十周年版,主要的差别是 20 周年版添加了没有银弹等四个章节。这个 32 周年中文纪念版应该就是基于二十周年版的一个翻译版本,32 周年估计是中文出版商的一个宣传噱头。

当然,版本不重要,书的内容是主要的,那么书的主要内容是什么呢?

关于书名

书名《人月神话》中的人指的是人力,月指的是工作时间,主要的意思是人月作为一种衡量软件开发工作量的单位有其误导性,举例来说,1个人可以在10周之内做完的项目,10个人不一定可以在1 周之内完成。其实在书中作者更进一步地指出,单纯地增加开发人力,不仅不能对应地减少项目的开发时间,甚至有可能增加开发的时间,因为更多的开发人员需要更多的交流沟通,这部分的时间成本也是很可观的。当然,这本书不仅仅是论述这一个的观点,从这个观点出发,本书还介绍了很多其他的大型软件工程的规律和最佳实践。接下来,我将一一介绍该书每一章的主要内容,算是我的读书笔记吧。

焦油坑

第一章作者将软件系统开发比作吞噬了恐龙、剑齿虎等史前巨兽的焦油坑,许多大大小小的团队被软件开发的焦油坑所吞噬。

作者首先介绍了变成系统产品的演进,指出程序、编程系统、编程产品、编程系统产品几个概念间的区别,其中只有编程系统产品才是真正可用的面向用户的产物。

然后作者分别介绍了编程的乐趣和苦恼,当然这部分见仁见智,人类的悲欢毕竟并不相通。

人月神话

第二章作者主要介绍了软件开发项目在进度安排上经常出现的问题。也就是书名人月神话的出处。首先,由于我们对项目开发的进度估计过于乐观,我们估计出来的工作量通常会低于实际需要的工作量,尤其是对测试所需时间的安排常常是进度估算失误的重灾区,这应该是很多有经验的开发者会在估时的时候乘以一个固定的系数的原因(1.5 或者 2 甚至 3)。其次人员和时间的关系并非总是反比的关系,如果任务属于可以完全分解的理想状态话,是可能达到人员越多时间越少的反比效果的,但是,任务越复杂也就越难以分解,那任务中的沟通需求就会占用更多的时间,甚至可能导致人员越多,项目所需的时间越长。进而可以得出结论:当进度落后的时候,无脑增加开发人手可能并不能加快开发进度,甚至有可能导致进度更加缓慢。向进度落后的项目中增加人手,只会使进度更加落后,这也被成为 Brooks 法则。

外科手术队伍

基于上一章的结论:更大的队伍不一定能带来更快的开发进度,那么,问题来了,什么样的队伍才是合适的呢,小团队固然高效,但是你不能指望一个 20 人的小团队在合理的时间内去开发一套完整的操作系统吧?作者在这一章里给出了解决方案:将大项目合理地划分成更小的系统,各个外科手术队伍分别开发这些更小的系统。当然外科手术队伍只是一个小团队的方案,具体细节我认为已经不再具有很大的参考价值,但是精干的小型队伍这一理念仍然很有价值。

这里我联想到大企业病的问题,企业规模的扩大必然会导致人浮于事、效率低下的问题,于是如今一些大企业都会将企业的业务分割给各个小团队来负责,一个很著名的例子就是亚马逊的两个披萨的原则,有效地避免了大团队的导致的低效问题。

贵族专制、民主政治和系统设计

接上一章:短小精干的队伍带来了新的问题,如何对大项目进行合理的分割?这依赖于一个很重要的前提:概念的完整性,具体而言是指在项目的开发过程中,应该有一个一以贯之的明确的目标和清晰界定的范围,否则随着人员的变动和时间的变迁,这个项目可能会逐渐演变成四不像(我最近在参与的一个项目就是如此,人员的变动导致了项目功能、技术栈的变迁,项目的周期被极大地拉长)。而如何产生概念的完整性呢?答案是贵族专制,少数人决定一个概念并厘清边界,然后据此分割执行,否则人多嘴杂,必然会导致项目在讨论中变成奇怪的样子。

画蛇添足

这一章主要是告诫系统设计师们,不要过度设计,尤其是在第二个系统(第一个系统完成后开发的第二个系统)中,不要过度自信,保持警觉,避免初始的概念和目标得到充分的体现,而不让一些次要的功能喧宾夺主。(是不是可以说是保持初心?)

贯彻执行

概念的完整性不仅仅要在专制的贵族和系统设计师这一层面上充分传达并理解,在系统的实现人员中,也要充分传达。在不理解业务背景的情况下,开发者也很难写出优秀的代码。作者在这一章介绍了很多方法以达到这一目的,包括规格说明手册、形式化定义(我的理解是 Web 开发中的原型图一样的存在)、会议和大会、多重实现(举例:假如JavaScript只有 V8 一个引擎,那如果引擎实现与 ES 标准不一致时,改标准比改引擎实现要容易得多,但是有多个引擎时,与标准不一致的引擎就必须修改自己的实现,从而保证了标准文档的贯彻执行)、电话日志、产品测试。

当然这些方法在如今可能已经过时了,但是通过种种方式保证软件开发的进度不偏离最初的概念和目标,是实现项目成功的重要保障。实际上在当代的软件开发流程中也有一些措施来实现这一目标,如敏捷开发中每天的站会。

为什么巴比伦塔会失败

我们都知道巴比伦塔的失败是因为上帝给了人类不同的语言导致的沟通失效。那么在大型项目中如何保证沟通的顺畅呢?作者详细介绍了项目工作手册的内容、发布方式、更新方式等等,但是当年他们仍然是纸质文档,后来才进化成微缩胶片,现如今我们有了更方便的字处理工具和文档协作工作,因而这部分的建议就用不上了,但是保持文档的更新和有效获取仍然很重要,否则不同的人对需求的记忆和理解可能出现偏差、进度可能会出现错位等等,导致项目的开发进度被拖慢。

相应的,外科手术队伍也可以有效地降低交流的难度,毕竟人越少,沟通越便捷,这样一来,整个项目通过合理的人力划分和职责限定,就会自上而下形成一个树状结构。作者特地论述了团队中技术负责人和产品负责人的分工与关系,但是我很难将作者的观点映射到当代的开发团队中来,只好略过。

胸有成竹

这一章是介绍对变成效率的量化的,种种量化方法将程序员的生产效率细化到指令/年(不好意思,他们当年是写汇编的,高级语言那个时候还很少)。我觉得这个实际上意义不大, 但是仍然是一个有趣的参考,最终的结论是工作量=常数*指令数量^1.5,也就是说工作时长跟代码数量的指数关系而不是线性关系,随着代码数量的增长,工作时长是指数级上升的。

削足适履

这一章主要是讲程序大小的,包括代码大小和内存占用的大小。代码大小现在应该已经不是软件开发的重要考量了吧?但是这一问题在如今的 App 开发中应该也有,虽然很多公司毫不在乎占用用户几百 M 的存储空间……当然在前端还存在另外一个问题,即过大的代码包会耗费过多的流量,尤其是在早年的移动端开发中,那时候流量很金贵,现在这样的顾虑逐渐变少了,但是一直都存在的顾虑是代码的加载速度,当然这一问题也可以通过分包懒加载来解决了。总而言之,当年的那些经验在前端领域适用的已经不多了,但是在客户端领域可能仍然适用。

内存当然始终是一个问题,但是应该主要考虑不要发生内存泄露的问题,自动的垃圾收集可以解决大部分问题了吧?计算机硬件的进步真的极大地降低了软件工程的难度。

提纲挈领

这一章主要是介绍文档的重要性,对于项目经理来说,文档是很重要的,它包含了项目目标、产品的技术说明、时间、资金预算、工作空间的分配和人员的组织结构。文档的重要性在于首先,明确的书面记录会让分歧更明朗,使混沌的状态变得清晰、明确;其次,文档降低了沟通的负担;最后,文档便于项目经理跟踪项目的进度状态。

未雨绸缪

这对于程序员来说可能是一个十分不幸的消息:系统必然会面对各种变化,你开发的软件必然会在修修补补中变得面目全非,最初的设计必须在各种妥协中打上各种丑陋的补丁。无论是多么良好设计的系统,都会走向混乱,区别只是这个过程的快慢而已。因此,好的设计会让这个过程尽可能地慢,尽可能地不那么痛苦,我们能做的就是眼光尽量放长远,让我们的代码尽可能地具有高可扩展性并且易于维护。而且,在面对不得不进行的重构时,做好心理准备。

干将莫邪

这一章一言以蔽之:磨刀不误砍柴工。好的开发工具和开发环境可以极大地提高开发效率,当然,作者在这一章中介绍具体方法、工具、语言等已经不适用于我们这个时代了。

整体部分

我们将整体的项目分割成更小的部分来开发,但是最终我们仍然需要将这各个部分合并成一个整体对外提供服务,这就带来了新的问题:各个部分可能独立工作良好,但是当合并成整体时反而导致了新的系统级的 bug。这就要求我们在设计系统结构时精心设计,减少各个部分间的耦合,各个模块的独立性越高,系统级的 bug 的可能性就越低。此外作者在本章还介绍了一些测试的方法,这部分我不太了解,而且测试的方法、流程可能也已经过时了,毕竟我们已经有了更好、更方便的版本控制工具和测试流程,但是其中一些理念还是有意义的,如重构过的模块在合进整体的时候仍然需要对系统整体进行测试,而不是仅仅测试重构过的模块。

祸起萧墙

项目的进度很有可能因为各种各样不可避免的因素滞后,这是很可能出现的情况,有的时候甚至可能不知不觉就会落后,难以察觉地落后一天、半天,累积起来整个项目的完成就会遥遥无期。避免这种难以察觉的落后的办法是制定里程碑,将项目的进度安排划分成一个一个阶段并清晰地界定各个阶段需要完成的任务,里程碑边界的清晰、明确十分重要,含糊的里程碑跟没有里程碑没有区别。“其他部分反正会落后”是一种很可怕的想法,是需要项目经理极力去避免的。此外,一线经理不要惧怕向老板反映进度落后的真实情况,同时老板遇到进度落后时也不要越俎代庖代替一线经理做决定,否则一线经理会倾向于不报告真实信息。

另外一面

除了面向开发者的项目文档和说明之外,软件开发也需要编写详尽的面向用户的文档,文档需要包含软件的目的、运行环境、输入输出范围、实现的功能和使用的算法、输入输出的格式、操作指令、选项、运行时间、输出结果的精度和校验方式等等,除此之外,还需要充分的测试用例来说明程序的功能和边界。另外,对于其他开发者而言,需要提供项目开发所需要的文档(类似 Github 上 how to contribute 的部分吧)。

没有银弹

没有银弹是一篇很有名气的文章,这篇文章发表于1986年,同时这也是一个广为流传的软件开发的原则,这一原则是指,在未来的十年内(1986-1996),没有任何技术或管理上的进展,能够独立地许诺在生产率、可靠性或简洁性上去的数量级的提高。作者做出这一论断的前提是:所有软件包括根本任务和次要任务,根本任务是打造构成抽象软件实体的复杂概念结构,次要任务是用编程语言表达这些抽象实体。很多技术或管理上的进展在解决次要任务上发挥了巨大作用,但是在主要任务上却进展寥寥,作者进而分析了造成这种境况的原因,分别从复杂度、一致性、可变性、不可见性论述了他的观点,我在此不做总结了,就我个人的理解而言,软件本质上是需要完成业务需求的,而业务需求则来源于商业决策、社会、文化等等人类文明系统的各个方面,甚至可以说是软件工程是对现实世界的模拟,这个现实的社会系统如此复杂并且变化多端,以至于任何一种架构设计都不可能囊括所有的可能性,所以,需求必然会变更,没有一发银弹可以应对所有的需求。软件开发的难度不在于开发,而在于设计一个完美的软件架构,而这个架构依赖于对业务的熟悉和前瞻性的展望。

作者进而提出了解决核心问题的方法:首先:不开发软件,不开发软件就不会有问题……买一个就好了(我晕,这也算办法?不过虽然这个方法有些无厘头,但是其实预见了微软为代表的薄膜包装软件的兴盛,因为当年的软件大多是客户定制化开发的,而不是售卖通用的软件解决方案);其次,快速地搭建原型(Demo吧?),从而更好地确定用户的需求;第三,增量开发,我觉得这个方法最靠谱,也就是把软件当做是生长的有机体,而不是一个建好就可以的建筑,软件会不断成长,先从简单的核心开始,一边交付一边开发,不断增加新的功能,一边增加新的功能,一边重构已有的部分,其实这跟我们目前的很多项目的开发模式很相似了;最后,培养优秀的设计人员……讲真,我觉得这个也太高远了……不能解决眼前的问题,当然,这是有意义的,培养出优秀的系统设计师(现在应该说是架构师?)是十分重要的,但是,这也只能在实践中培养吧,毕竟纸上谈兵是肯定不行的。

再论没有银弹

这一章是《没有银弹》这篇文章发表10年后(1996年),作者对针对这一话题的讨论的一些回应与评价,其中也包括对一些新兴技术的讨论,很有意思,其中的很多观点在现今仍然不时在网络上出现。此处不一一列举了,文章网上都可以看到。在这篇文章中,作者对一种新的软件开发趋势寄予了比较高的期望:重用,也就是我们现在说的复用,我觉得这一趋势成就了今天的开源社区。

当然作者仍然坚持认为,子弹没有出现。

《人月神话》的观点:是与非

这一章也是20周年版本新增的内容,20年的发展让初版的一些观点变得过时,但是仍然有不少观点仍然适用,因而作者在这一章里把之前的每一章的主要观点都抽离出来,并对已经过时了的观点做了说明,可以说这一章是整本书的精华了吧,具体内容我就不列了,作者对每一章的总结与我在上面的整理也有很大的不同,我只不过是将自己的理解整理出来,作者的总结则更加细致全面。总而言之,十分推荐。

20年后的《人月神话》

这一章很长很长,算是一篇单独的文章,讨论了很多软件开发相关的技术和管理思想,其中包括:

  1. 概念完整性和结构师的重要性
  2. 开发第二个系统所引起的后果——盲目的功能和频率猜测(我觉得如今火热的AB测可以部分地解决这个问题,当然,今天开发的软件跟当年作者讨论的软件也不一样)
  3. 图形界面的成功(基本上是对图形化桌面的产品设计做了一次分析)
  4. 没有构建舍弃原型——瀑布模型是错的(主要是说软件开发过程中很有可能推倒重来)
  5. 增量开发模型更佳——渐进地精化(我觉得如今互联网公司大多是这种开发模型,敏捷开发也基本上符合这个模型,但是我觉得这个模型对系统架构的扩展性和灵活性要求还是很高的)
  6. 关于信息隐藏,Parnas是正确的,我是错误的(作者原本认为需要让程序员了解软件系统所有的材料和信息,但是Parnas认为模块内部的信息需要被隐藏,而不是暴露出来,现在看来答案已经很清楚了)
  7. 人月到底有多少神话色彩?——Boehm的模型和数据
  8. 人就是一切(或者说,几乎是一切)(简单来说,对项目开发而言,管理比技术重要)
  9. 放弃权力的力量(作者认为充分放权可以充分发挥小团队的主观能动性,从而让整个组织的生产率得到提高)
  10. 最令人惊讶的新事物是什么?数百万的计算机(计算机的普及对人类的影响再怎么夸大也不为过)
  11. 全新的软件产业——塑料薄膜包装的成品软件
  12. 买来开发——使用塑料包装的成品软件包作为构件(作者在这一部分和上一部分主要是介绍了软件产业的新兴潮流,当然这个潮流如今也已经算是作古了)

最后,我的一些感想和收获

这本书真的是够老了,已经40多年了,很难想象一本计算机领域的书能够畅销这么久。但是读完之后我发现畅销是有道理的,虽然书里很多概念、例子、技术都属于上古时期的遗迹了,但是书里介绍的软件开发的困难、管理的思路,仍然可以在今天的软件开发中找到对应的场景,原来我今天遇到的困难早在洪荒时代就已经被讨论并且提出过解决方案,而且今天的一些软件开发的流程、管理方案、团队组织等等都可以看到当年那些解决方案的影子。所以读这本书还是很有收获的,可以理解实际开发过程中遇到的困难和这些困难的应对方法,无论你是在开发团队中担任什么角色,是产品经理还是开发者抑或技术负责人,我都十分推荐这本书。
读完这本书还有一个感受:国内的软件开发行业真的是十分落后,简直是小作坊式的开发,希望每一个老板、项目经理、产品经理、开发者都能读一读软件开发相关的书籍,了解软件开发特有的规律,不要再摸着石头过河了。
当然,这本书毕竟已经是老古董了,读的时候有很多地方会很难理解,建议跳过这些细节,关注那些细节背后的原理和规律,尤其是十分推荐对照着 《人月神话》的观点:是与非 这一章来读,可以起到提纲挈领、高屋建瓴的效果(后悔自己读到后面才知道有这么一章)。