编程到底难在哪里?—— 《人月神话》阅读分享

一、焦油坑

前车之覆,后车之鉴。

—— 荷兰谚语

史前史,一头大象悠然自得地在草原上散步。夕阳的余晖洒下,大象觉得有些口渴了,它看到旁边有一个水洼,于是它慢悠悠地走到水洼前饮水。当它喝足准备离开时,突然发现自己被陷住了,这竟是一个蓄有一点水的沥青湖!大象抬起笨重的象腿想要抽身,却发现越是挣扎,越是下陷得厉害。大象预感到了死亡的来临,它的眼中渐渐浮现出惊恐与绝望。

继而它看到了更可怕的事情,几十头狮子见它落单,凶狠地向它冲过来,自己却动弹不得,只能眼睁睁地成为他们的腹中餐。但显然它们都低估了沥青湖的威力,所有的狮子也都陷入其中,无法自拔。焦油紧紧地纠缠着他们,没有谁有足够的技巧能够挣脱束缚。最后,它们都沉入了坑底,形成震惊后人的化石与焦油坑。

软件开发也常常陷入这样的焦油坑中,表面上看起来没有哪个单独的问题是致命的,但当他们纠缠在一起,就形成了拖慢团队进度的焦油。即使是精明的程序员也会陷入其中,难以看清问题的本质。

想要解决问题,我们就要先去了解问题,看一下系统开发的职业乐趣与苦恼。

1.1 开发的乐趣

首先是创造事物的快乐,如同小孩在玩泥巴时感到快乐一样,成年人也喜欢创造事物,特别是经过自己设计的事物。这种快乐是上帝创造世界的折射,一种呈现在每片独特的、崭新的树叶和雪花上的喜悦。

其次,快乐源于开发的东西对他人有用,如马斯洛的需求层次理论中所言,内心深处,我们渴望自己的劳动成果能够对他人有所帮助,这是一种自我价值的实现。

第三,快乐来自于开发的过程非常有趣,每个类、每行代码就像一个精妙的零件,我们生产这些零件,又将其组合加工,编程过程就像在玩乐高拼图一样有趣。

第四,快乐来自于持续学习,编程不同于流水线,我们每天总在面临不同的问题,从不同的问题中可以学到新的事物,类似于在游戏中获取到经验,升级时的快乐。

最后,快乐来自于操作简单,编程本质上是一种纯思维的活动,开发过程中,不需要搭建复杂的环境,只需运用自己的想象,就能建造出自己的 “城堡”。

编程就像是设计一场魔术,通过在键盘上输入正确的咒语,它能打印结果,绘制图形,发出声音,移动支架…它不仅满足了我们内心深处对于创造的渴望,而且唤醒了我们每个人内心的情感。

1.2 开发的苦恼

首先,苦恼来自于追求完美,计算机的魔术虽好,但它需要每个字符、每个停顿都处在正确的位置上,很少有人类活动会要求如此完美。人类对完美本身就不习惯,比如为什么一周是 7 天不是 10 天?为什么一年是 12 个月不是 10 个月?为什么一分钟是 60 秒不是 100 秒?编程最困难的地方就在于要将做事的方式向追求完美调整。

其次,苦恼来自于无法控制工作目标,程序员的工作内容往往是由他人设定的,如产品经理、技术主管、老板或客户,即使遇到他人设定的目标不够合理,程序员也常常无能为力。用管理学的术语来说,个人的权威和他所承担的责任是不匹配的。但每一次任务的完成,都会增加我们的权威。

第三,苦恼来自于对其他人的依赖,在开发过程中,我们不得不使用其他人编写的代码,比如开发环境、三方框架等等。我们期望这些代码能正常工作,但有时他们却存在很多问题,所以程序员不得不花时间去研究和修改,而它们在理想情况下本应该是可靠的、完整的。

第四,设计与开发是有趣的,但维护代码是一项琐碎而重复的工作,伴随着创造性活动的,往往是枯燥沉闷的时间和艰苦的劳动,程序编程也不例外。

最后一个苦恼,有时是一种无奈 —— 当投入了大量辛苦的劳动,产品在即将完成或者终于完成的时候,却已显得陈旧过时。可能是同事或竞争对手已经构思了更好的方案。

现实情况通常比上面所说的要好一些,事实上,当软件开发完成后,很少会为了更好的设想而推倒重来,因为现有的系统已经能满足要求,并体现了回报。

这,就是编程。一个许多人痛苦挣扎的焦油坑以及一种乐趣和苦恼并存的创造性活动。对许多人而言,快乐大于苦恼。本书试图搭建一些桥梁,为通过这样的焦油坑提供一些指导。

二、人月神话

美食的烹调需要时间;片刻等待,更多美味,更多享受。

—— 新奥尔良市安托万餐厅的菜单

在众多软件项目中,缺乏合理的进度安排是项目滞后的最主要原因。导致这种灾难的原因是什么呢?

首先,编程人员都是乐观主义者,总是理想地想象一切都将运作良好,每项任务仅花费它所应该花费的时间。 而实际上,我们在实现过程中总会碰到各种各样的困难,可能是我们构思的缺陷,也可能是程序其他部件带来的错误影响,修复这些错误将占用我们大量的时间。

其次,估算进度时,我们错误地假设人和月可以互换,以人月为单位去衡量开发工作的规模是一个危险和带有欺骗性的神话。就好比无论有多少个母亲,孕育一个生命都需要十个月。人月仅适用于完全可以分解的任务,这在割小麦和收棉花的工作中是可行的。当任务由于次序上的限制不能分解时,人手的添加对进度是没有帮助的。在开发工作中,添加更多的人手,意味着不得不花费时间去培训新人,并且随着开发人员的增多,沟通、交流的时间成本也越大。

第三,系统测试的时间总是被预估得很低。 理论上,bug 的数量应该为 0,但由于我们的乐观主义,实际的 bug 数量比预料得要多得多,系统测试的进度安排常常是编程中最不合理的部分。对于软件的进度安排,作者以自己多年的管理经验总结出如下法则:

  • 1/3 计划
  • 1/6 编码
  • 1/4 构件测试和早期系统测试
  • 1/4 系统测试,所有的构件已完成

与大多数传统进度安排相比,这份进度安排显得计划时间过长,测试更是占用了一半的时间,编码是最容易估算的部分,仅分配了 1/6 的时间。但如果不为测试安排足够的时间简直就是一场灾难,因为 bug 会没有任何预兆,很晚才出现在客户和项目经理面前。

第四,项目进度落后时,人们的第一反应是加派人手。这样的应对方案好比用汽油灭火,只会让火势更大,而更大的火势又需要更多的汽油,从而陷入循环的灾难之中。

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

那么,除去神话色彩的人月应该如何呢?

项目的时间依赖于顺序上的限制,人员的最大数量依赖于独立子任务的数量。从这两个数值可以推演出进度表,该表安排的人员较少,花费的时间较长(唯一的风险是产品可能会过时)。相反,分派较多的人手,计划较短的时间,将无法得到可行的进度安排。

三、外科手术队伍

这些研究表明,效率高和效率低的实施者之间个体差异非常大,经常能够达到数量级的水平。

—— Sackman, Erikson 和 Grant

软件经理很早就认识到优秀程序员和较差的程序员之间生产率的差异,但实际测量出的差异还是令我们所有的人吃惊, Sackman, Erikson 和 Grant 曾对一组具有经验的程序人员进行测量,在该小组中,最好的和最差的表现在生产率上平均为 10:1; 在运行速度和空间上具有 5:1 的惊人差异!并且,数据显示经验和实际的表现没有相互联系。

得出的结论很简单:200 个平庸的程序员组成的队伍很可能不如 25 个优秀程序员组成的队伍效率高。

但小而精炼的队伍也有致命的弊端,作者曾在 IBM 公司任职 OS/360 系统的项目经理,OS/360 项目在顶峰时,有超过 1000 人在为它工作 —— 程序员、文档编制人员、操作人员、职员、秘书、管理人员、支持小组等等。从 1963 年到 1966 年,设计、编码和文档工作花费了大约 5000 人年。即使 25 个优秀程序员能达到 200 个普通程序员的水平,也需要 25 年的时间才能完成此项目。一个产品在最初设计的 25 年后才出现,还有人会对他感兴趣吗?

这种进退两难的境地是很残酷的,对于效率和概念完整性来说,最好由少数优秀的人员来设计和开发,而对于大型系统,则需要大量的人手,以使得产品能在时间上满足需求。如何调节这两方面的矛盾呢?

Mills 的建议

Harlan Mills 提供了一个解决方案:大型项目的每一个部分由一个团队解决,但是该队伍并不是一拥而上,而是以类似外科手术队伍的方式组建。

如今的外科手术已经相当成熟,通常来说,一个外科手术队伍中有五种角色:

  • 一位外科医生
  • 一位或多位助手
  • 一位麻醉师
  • 一位器械护士
  • 一位巡回护士

其中,主刀的外科医生负责整台手术的操作部分,他必须经验丰富且能力出色。助手是外科医生的后备,他能完成任何一部分工作,但是相对具有较少的经验。麻醉师、护士为外科医生提供必要的帮助。

在系统开发中也可以采用类似的人员结构:

程序的总架构师担任外科医生的角色,负责程序的整体架构,并输出产品文档。保障程序的概念完整性,在整个团队中具有权威性。

助手需要了解程序的所有代码,负责与架构师讨论程序的结构,但不具有架构师的权威,他充当了外科医生的保险机制。

将程序员按照专业化分工,承担自己擅长领域的代码部分,他们创造了团队最有价值的财富 —— 工作产品。

测试人员负责编写测试用例,为外科医生搭建测试平台,保障程序的可靠性。

外科手术队伍与传统队伍的区别就在于:传统的团队将工作进行划分,每人负责一部分工作的设计和实现。在外科手术团队中,实际设计完全由外科医生完成,其他人负责填充实现细节与提供必要的帮助。

外科手术队伍的好处在于:他减少了沟通的成本,在传统的队伍中大家是平等的,出现观点的差异时,不可避免的需要讨论和妥协,在外科手术队伍中,观点的不一致由外科医生单方面来统一,它保证了程序的概念完整性。

另外,团队中剩余人员职能的专业化分工是高效的关键,只有分工明确,成员之间才可以尽可能地简单交流。

四、画蛇添足

聚沙成塔,集腋成裘

—— 奥维德

在开发第一个系统时,架构师倾向于精炼和简洁,因为他知道自己对正在进行的任务不够了解,所以他会谨慎仔细地工作。对偶尔出现的装饰和润色功能,他通常都会选择暂时搁置一旁,作为“下一个”项目的目标。

第二个系统常常是架构师设计的最危险的系统。它常常被过度设计。在第一个系统设计完成后,架构师对这类系统充满了十足的信心,在设计第二个系统时,先前搁置的功能都被添加进来。最终导致第二个系统虽然功能齐全,但显得粗糙、浪费、不优雅,也许只有一半的功能被常规使用。

当开发第三个、第四个系统时,先前的经验会相互验证,此时的架构师将足够自律,在选择程序的功能时能够做必要的取舍。

想要避免画蛇添足,在开发第二个系统时,架构师就应该学会这些自律。

五、巴比伦塔的教训

大洪水过后,全人类仅剩下诺亚一家,他们因躲在方舟中幸存了下来。诺亚一家人在新土地上重新繁衍生息,成为全人类共同的祖先。心有不安的子孙后代们为了防止大洪水再次发生,提议共同修建一座通天塔,这样,人们以后便不再需要流离失所。


不料这件事引起了上帝的注意,上帝开始担心:“如果这件事他们都能共同完成,以后便没有什么难得倒他们了。”上帝决定阻挠他们。他到人间转了一圈之后发现:“现在他们都是同一个种族,都说同一种语言。”于是上帝决定从语言下手,他创造了许多种语言,导致人们互相不能听懂。果然,没过多久,人们便无法继续合作修建通天塔了,不得不散落到世界各地。

——《旧约圣经》,《创世纪》篇。

巴比伦塔计划虽然有充足的人力物力,但由于无法沟通,不得不走向失败的结局。它的教训告诉我们:沟通是合作的必要条件。

大型软件项目中也面临这样的情况,因为左手不知道右手在做什么,所以进度灾难、不合理的功能实现、系统缺陷纷纷出现。随着工作的不断进行,许多小组开始慢慢地修改自己程序的功能、规模和速度,当他们组合在一起时,新的程序就显得与原定目标渐行渐远。

团队之间如何有效的沟通呢?通常有三种途径:

  • 电话、交谈等非正式沟通:这些途径非常方便、有效,并且随时发生在日常生活中
  • 会议沟通:虽然谈到会议总是有些恼人,但会议是最正式的明确需求的途径。如果小组氛围足够和谐,会议沟通将非常有用
  • 工作手册:在项目开始时,就应提纲挈领,准备好项目工作手册。指派专门的人手来维护工作手册,保证它们可以随着项目实时更新。工作手册应保持树状的索引结构,工作人员可以很方便的使用索引来检索他所需要的信息。

巴比伦塔可能是第一个失败的工程,但它不是最后一个。沟通与交流是成功的关键。这项技能需要管理者仔细考虑,相关经验的积累和能力的提高同软件技术本身一样重要。

六、未雨绸缪

不变只是愿望,变化才是永恒。

—— 斯威夫特

化学工程师很早就认识到,在实验室可以进行的反应过程,并不能在工厂中一步到位的实现。一个被称为 “试验性工厂” 的中间步骤是非常必要的,它会为提高产量和在缺乏保护环境下运作提供宝贵经验。

软件系统也面临类似的问题,大多数软件项目都是一开始设计好算法,然后将算法应用到待发布的软件中,接着根据进度安排,将第一次开发的产品发布给客户。

然而,对于大多数项目,第一次开发的系统往往并不好用。它可能太大、太慢,或者难以使用。许多原型设计上的痛点总是在项目落地之后才显现出来。要解决所有的问题,开发出一个更灵巧或者更好的系统,除了重新开始之外,没有其他办法。旧系统的丢弃可以一步完成,也可以一块块地实现。所有大型系统的经验都显示,这是必须完成的步骤。

因此,我们要考虑的问题不是 “是否要构建一个用来试验性的系统,然后抛弃它?”因为这是毋庸置疑的,我们必须这样做。我们需要考虑的问题是 “是否将第一次构建的原型系统发布给用户?”

将原型系统发布给用户,我们可以获得时间,但是它的代价高昂 —— 对于用户,使用起来极度痛苦;对于开发人员,重新开发的同时仍需要维护老系统,分散了精力;对于产品,影响了声誉,即使再好的再设计也难以挽回名声。

因此,为舍弃而计划,我们必须这样做。如《极限编程》一书中所言:拥抱变化,唯一不变的就是变化本身。

一旦意识到试验性的系统必须被丢弃,我们就必须用变更的思想设计软件系统。这在许多书本上被普遍讨论过。包括:

  • 细致的模块化
  • 可拓展的函数
  • 精确完整的模块间接口设计
  • 完备的文档

使用变更的思想设计出来的软件系统具有更好的复用性。它可以极大地减少正式系统开发时的困难。

程序在发布给客户使用之后,并不会停止变化,发布后的变更被称为程序维护。对于一个广泛使用的程序,其维护的成本通常是开发成本的 40% 或更多。该成本受用户数目的影响很大,用户越多,所发现的错误就越多。

程序维护中的一个基本问题是 —— 缺陷修复总会以固定的几率引入新的 bug,几率大概在 20%~50% 之间。所以,整个过程是前进两步,后退一步。

为什么缺陷不能被更彻底地被修复?首先,看上去很微小的错误,似乎仅仅是局部操作上的失败,实际上却可能是系统级别的问题。其次,维护人员通常不是代码的最初编写人员,而是一些后来者,他们并不熟悉代码编写之初的设计意图。

通过对统计模型的研究,关于软件系统,Belady 和 Lehman 得到了更具普遍意义、被所有经验支持的结论:“事物在最初总是最好的。”

七、干将莫邪

巧匠因为他的工具而出名。

—— 谚语

就工具而言,即使是现在,很多软件项目仍然像经营一家五金店。每个骨干人员都仔细地保管自己工作生涯搜集的一套工具集,这些工具成为个人技能的直观证明。

这种方法对软件项目来说是愚蠢的。前文已经提到过,项目的关键问题是沟通,而个性化的工具只会阻碍沟通。并且,当机器和语言发生变化时,技术也会随之变化,所有工具的生命周期都是很短的。毫无疑问,开发和维护公共编程工具的效率更高。

因此,项目经理应该制定一套策略,并为通用工具的开发分配资源。包括:

  • 辅助机器开发:辅助机器的概念和目标机器相对应。目标机器是程序最终的服务对象,辅助机器是在开发系统中提供服务的机器。它是一个仿真装置,用于提供可靠的调试平台
  • 工具库开发:如图片存储、文件操作等工具类,它们可以由工具维护人员一次完成,避免开发过程中重复的工作
  • 文档系统开发:生成详尽且结构良好的文档,方便开发人员查阅

八、祸起萧墙

带来坏消息的人不受欢迎。

—— 索福克勒斯

当一线经理发现自己的队伍出现了计划偏离时,他肯定不会马上赶到老板那里去汇报这个令人沮丧的消息。团队可以弥补进度偏差,他可以想出办法或者重新安排进度以解决问题。

但每个老板都需要知道两种信息:工作计划中的问题和工作状态的数据。出于这个目的,他需要了解开发队伍的真实情况,但得到真相是很困难的。

一线经理的利益和老板的利益在这里出现了冲突。经理担心汇报出了问题,老板会采取行动,这些行动会取代经理的作用,降低经理的威信,从而搞乱其他计划。所以,只要项目经理认为自己可以独立解决问题,就不会选择告诉老板。因此,所有的污垢都被藏在地毯之下。

有两种掀开毯子把污垢暴露出来的办法:

  • 一是减少角色冲突,鼓励状态共享
  • 二是猛地拉开地毯

两种方法并没有优劣之分,他们都应该被采用。

减少角色冲突:老板应当规范自己,不对项目经理可以解决的问题做出反应。他应当清楚自己什么时候需要采取行动,什么时候是在检查状态,保证自己绝不在检查状态的时候采取行动。如果在状态报告的第一个段落结束之前,老板就拿起电话发号施令,这样的做法势必会压制信息的完全公开。当项目经理了解到老板收到状态报告后不会惊慌,并且不会越俎代庖时,他就会逐渐提交真实的结果。

猛地拉开地毯:建立能了解项目真实状态的评审机制是必要的,大型项目中,可能需要每周对某些部分进行评审,大约一个月左右进行整体评审。

对项目的计划和控制是项目早期的预警系统,它们可以防止项目以一次一天的方式落后一年。

九、没有银弹 —— 软件工程中的根本和次要问题

在未来的十年内,无论是在技术还是管理方法上,都看不出有任何突破性的进步,能够保证在十年内大幅度地提高软件的生产率、可靠性和简洁性。

在古老的西方传说中,月圆之夜,受感染的人狼将会由人变狼。人狼出没,凶恶残暴,唯一能杀死他们的武器便是银制子弹。

在软件开发行业,我们一直渴求一颗消灭软件复杂度的银弹。但我们看看近十年来的情况,没有发现银弹的踪迹。分析软件的本身特性也会发现,软件不大可能有任何的发明创新 —— 能够像计算机硬件工业中的电子器件、晶体管、大规模集成一样 —— 大幅度地提高软件的生产率、可靠性和简洁程度。我们甚至不能期望每两年有两倍的增长。

所有软件活动都可分为两种:

  • 根本任务:打造构成抽象软件实体的复杂概念结构
  • 次要任务:使用编程语言表达这些抽象实体,在空间和时间的限制下将它们映射成机器语言

当一种高级语言出现,简洁的语法使编码过程更加容易;当编译器大幅更新,使程序构建部署更加方便;当电脑内存扩大,使程序运行速度越来越快。这些工作都在解决软件工程中的次要任务。产品本身的复杂性和可变性始终无法被简化。

软件的根本性困难包括以下几点:

  • 复杂性:没有哪两个软件是一模一样的,复杂是软件的根本特性
  • 一致性:大型软件开发中,界面、接口常常会不一致,并且随着时间的推移会变得越来越不一致
  • 易变性:软件构成的因素随时都在变化
  • 不可见性:程序是看不见的,即使用图示方法,也难以充分表现其结构,使得人们沟通面临极大的困难

没有银弹能够解决这些根本困难,但有一些途径去改善他们:

  • 买来装配:软件的建造尽量复用现有的组件,避免从头做起
  • 快速雏形:采用渐进式的增量开发模型,先产生雏形,再逐步演进,不断成长为大型软件,软件雏形快速运行起来那一刻,给开发人员带来的激励是无与伦比的。而不是采用线性的瀑布模型:计划 → 编码 → 单元测试 → 系统测试 → 现场支持
  • 人,就是一切:良好的方法可以改善人的创造过程,但人的原本创造力才是软件开发的根本

简言之,软件的复杂体现在它是纯思维的产物,是一个纯抽象的概念。具体语法层面上的实现只是软件开发中的次要问题。除非次要问题能占到开发活动的 9/10 以上,否则即使全部次要任务的时间缩减到零,也不会带来生产率数量级上的提高。

笔者手记

《人月神话》是一本引人深思的书,它介绍的软件系统管理流程不仅适用于软件开发,同样适用于其他工程领域。如律师、医生、社会学家、心理学家等等,都为此书提出过宝贵的意见。全书中没有一行代码,看起来这本书只是恰好讲了一下软件开发而已。

职业的特性,是开发人员很少去思考的问题。Brooks 告诉我们,我们喜爱开发是因为它给我们带来了“创造的快乐”,我们产生烦恼是因为“过于追求完美”。了解了编程的这些特性,在开发中遇到奇葩问题时,会释怀不少。

开发人员安排应像外科手术队伍,这样的观点让人眼前一亮。软件开发的任务是无法用人月分解的,团结一致、分工明确的开发才能保证程序的概念完整性,细想起来非常合理。

编程到底难在哪里?《人月神话》告诉我们,语言实现、环境搭建都只是软件的次要问题,编程的根本困难是捋清程序逻辑,考验的是设计者的思维能力。所以我们常说,算法和数据结构才是程序的核

由于此书被多次再版,并且许多名家评论过此书,所以《人月神话》的开篇有一篇又一篇的序,结束时还有厚厚的书评。实际上《人月神话》原书只是薄薄的一本小册子,只需要一下午的时间就可读完,本书被封为软件管理的“神书”,非常推荐大家花点时间阅读一下本书。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值