大型软件的悲哀
岸上的船儿,如同海上的灯塔,无法移动。
- 荷兰谚语
Een schip op het strand is een baken in zee.
[A ship on the beach is a lighthouse to the sea.]
- DUTCH PROVERB
过去几十年的大型系统开发就犹如一个焦油坑,很多大型和强壮的动物在其中剧烈地挣扎。他们中大多数开发出了可运行的系统——不过,其中只有非常少数的项目满足了目标、时间进度和预算的要求。各种团队,大型的和小型的,庞杂的和精干的,一个接一个淹没在了焦油坑中。表面上看起来好像没有任何一个单独的问题会导致困难,每个都能被解决,但是当它们相互纠缠和累积在一起的时候,团队的行动就会变得越来越慢。对问题的麻烦程度,每个人似乎都会感到惊讶,并且很难看清问题的本质。不过,如果我们想解决问题,就必须试图先去理解它。
编程系统产品
报纸上经常会出现这样的新闻,讲述两个程序员如何在经改造的简陋车库中,编出了超过大型团队工作量的重要程序。接着,每个编程人员准备相信这样的神话,因为他知道自
己能以超过产业化团队的 1000 代码行/年的生产率来开发任何程序。
为什么不是所有的产业化队伍都会被这种专注的二人组合所替代?我们必须看一下产出的是什么。
在图 1.1 的左上部分是程序(Program)。它本身是完整的,可以由作者在所开发的系统平台上运行。它通常是车库中产出的产品,以及作为单个程序员生产率的评估标准。
有两种途径可以使程序转变成更有用的,但是成本更高的东西,它们表现为图中的边界。
水平边界以下,程序变成编程产品(Programming Product)。这是可以被任何人运行、测试、修复和扩展的程序。它可以运行在多种操作系统平台上,供多套数据使用。要成为通用的编程产品,程序必须按照普遍认可的风格来编写,特别是输入的范围和形式必须扩展,以适用于所有可以合理使用的基本算法。接着,对程序进行彻底测试,确保它的稳定性和可靠性,使其值得信赖。这就意味着必须准备、运行和记录详尽的测试用例库,用来检查输入的边界和范围。此外,要将程序提升为程序产品,还需要有完备的文档,每个人都可以加以使用、修复和扩展。经验数据表明,相同功能的编程产品的成本,至少是已经过测试的程序的三倍。
回到图中,垂直边界的右边,程序变成编程系统(Programming System)中的一个构件单元。它是在功能上能相互协作的程序集合,具有规范的格式,可以进行交互,并可以用来组装和搭建整个系统。要成为系统构件,程序必须按照一定的要求编制,使输入和输出在语法和语义上与精确定义的接口一致。同时程序还要符合预先定义的资源限制——内存空间、输入输出设备、计算机时间。最后,程序必须同其它系统构件单元一道,以任何能想象到的组合进行测试。由于测试用例会随着组合不断增加,所以测试的范围非常广。因为一些意想不到的交互会产生许多不易察觉的 bug,测试工作将会非常耗时,因此相同功能的编程系统构件的成本至少是独立程序的三倍。如果系统有大量的组成单元,成本还会更高。
图 1.1 的右下部分代表编程系统产品(Programming Systems Product)。和以上的所有的情况都不同的是,它的成本高达九倍。然而,只有它才是真正有用的产品,是大多数系统开发的目标。
职业的乐趣
编程为什么有趣?作为回报,它的从业者期望得到什么样的快乐?
首先是一种创建事物的纯粹快乐。如同小孩在玩泥巴时感到愉快一样,成年人喜欢创建事物,特别是自己进行设计。 我想这种快乐是上帝创造世界的折射,一种呈现在每片独特、崭新的树叶和雪花上的喜悦。
在我真正学习编程之前,曾玩过一款游戏,它提供了一种和汇编语言很类似的脚本语言,使每位玩家都可以写出自己的游戏。在这个游戏中增加任何功能(无论是否有价值)都是一件让人兴奋的事情,这便是创造者的乐趣,它属于每一位创造者,无论是程序员,画家,雕塑家,原型师还是建筑师。
其次,快乐来自于开发对其他人有用的东西。内心深处,我们期望其他人使用我们的劳动成果,并能对他们有所帮助。
第三是整个过程体现出魔术般的力量——将相互啮合的零部件组装在一起,看到它们精妙地运行,得到预先所希望的结果。
第四是学习的乐趣,来自于这项工作的非重复特性。人们所面临的问题,在某个或其它方面总有些不同。因而解决问题的人可以从中学习新的事物:有时是实践上的,有时是理论上的,或者兼而有之。
最后,乐趣还来自于工作在如此易于驾驭的介质上。程序员,就像诗人一样,几乎仅仅工作在单纯的思考中。程序员凭空地运用自己的想象,来建造自己的“城堡” 。很少有这样的介质——创造的方式如此得灵活,如此得易于精炼和重建,如此得容易实现概念上的设想。(不过我们将会看到,容易驾驭的特性也有它自己的问题)
然而程序毕竟同诗歌不同,它是实实在在的东西;可以移动和运行,能独立产生可见的输出;能打印结果,绘制图形,发出声音,移动支架。神话和传说中的魔术在我们的时代已变成了现实。在键盘上键入正确的咒语,屏幕会活动、变幻,显示出前所未有的或是已经存在的事物。
编程非常有趣,在于它不仅满足了我们内心深处进行创造的渴望,而且还愉悦了每个人内在的情感。
职业的苦恼
然而这个过程并不全都是喜悦。我们只有事先了解一些编程固有的烦恼,这样,当它们真的出现时,才能更加坦然地面对。
首先,必须追求完美。因为计算机也是以这样的方式来变戏法:如果咒语中的一个字符、一个停顿,没有与正确的形式一致,魔术就不会出现。(现实中,很少的人类活动要求完美,所以人类对它本来就不习惯。)实际上,我认为学习编程的最困难部分,是将做事的方式往追求完美的方向调整。
其次,是由他人来设定目标,供给资源,提供信息。编程人员很少能控制工作环境和工作目标。用管理的术语来说,个人的权威和他所承担的责任是不相配的。不过,似乎在所有的领域中,对要完成的工作,很少能提供与责任相一致的正式权威。而现实情况中,实际(相对于正式)的权威来自于每次任务的完成。
对于系统编程人员而言,对其他人的依赖是一件非常痛苦的事情。他依靠其他人的程序,而往往这些程序设计得并不合理,实现拙劣,发布不完整(没有源代码或测试用例),或者文档记录得很糟。所以,系统编程人员不得不花费时间去研究和修改,而它们在理想情况下本应该是可靠完整的。
下一个烦恼——概念性设计是有趣的,但寻找琐碎的 bug 却只是一项重复性的活动。伴随着创造性活动的,往往是枯燥沉闷的时间和艰苦的劳动。程序编制工作也不例外。
另外,人们发现调试和查错往往是线性收敛的,或者更糟糕的是,具有二次方的复杂度。结果,测试一拖再拖,寻找最后一个错误比第一个错误将花费更多的时间。
我们对每一个bug都有解决方案,但没一个方案都有bug。
最后一个苦恼,有时也是一种无奈——当投入了大量辛苦的劳动,产品在即将完成或者终于完成的时候,却已显得陈旧过时。 可能是同事和竞争对手已在追逐新的、更好的构思;也许替代方案不仅仅是在构思,而且已经在安排了。
现实情况比上面所说的通常要好一些。当产品开发完成时,更优秀的新产品通常还不能投入使用,而仅仅是为大家谈论而已。另外,它同样需要数月的开发时间。事实上,只有实际需要时,才会用到最新的设想,因为所实现的系统已经能满足要求,体现了回报。
诚然,产品开发所基于的技术在不断地进步。一旦设计被冻结,在概念上就已经开始陈旧了。不过,实际产品需要一步一步按阶段实现。实现落后与否的判断应根据其它已有的系统,而不是未实现的概念。因此,我们所面临的挑战和任务是在现有的时间和有效的资源范围内,寻找解决实际问题的切实可行方案。
这,就是编程。一个许多人痛苦挣扎的焦油坑以及一种乐趣和苦恼共存的创造性活动。对于许多人而言,其中的乐趣远大于苦恼。
进度的灾难
美酒的酿造需要年头,美食的烹调需要时间;片刻等待,更多美味,更多享受。
- 新奥尔良 Antoine 餐厅的菜单
Good cooking takes time. If you are made to wait, it is to serve you better , and to please you.
- MENU OF RESTAURANT ANTOINE, NEW ORLEANS
在众多软件项目中,缺乏合理的时间进度是造成项目滞后的最主要原因,它比其他所有因素加起来的影响还大。导致这种普遍性灾难的原因是什么呢?
首先,我们对估算技术缺乏有效的研究,更加严肃地说,它反映了一种悄无声息,但并不真实的假设——一切都将运作良好。
第二,我们采用的估算技术隐含地假设人和月可以互换,错误地将进度与工作量相互混淆。
第三,由于对自己的估算缺乏信心,软件经理通常不会有耐心持续地进行估算这项工作。
第四,对进度缺少跟踪和监督。其他工程领域中,经过验证的跟踪技术和常规监督程序,在软件工程中常常被认为是无谓的举动。
第五,当意识到进度的偏移时,下意识(以及传统)的反应是增加人力。这就像使用汽油灭火一样,只会使事情更糟。越来越大的火势需要更多的汽油,从而进入了一场注定会导致灾难的循环。
乐观主义
所有的编程人员都是乐观主义者。可能是这种现代魔术特别吸引那些相信美满结局的人;也可能是成百上千琐碎的挫折赶走了大多数人,只剩下了那些习惯上只关注结果的人;还可能仅仅因为计算机还很年轻,程序员更加年轻,而年轻人总是些乐观主义者——无论是什么样的程序,结果是勿庸置疑的: “这次它肯定会运行。 ”或者“我刚刚找出了最后一个错误。 ”
所以系统编程的进度安排背后的第一个假设是:一切都将运作良好,每一项任务仅花费它所“应该”花费的时间。
对这种弥漫在编程人员中的乐观主义,理应受到慎重的分析。Dorothy Sayers 在她的“The Mind of the Maker”一书中,将创造性活动分为三个阶段:构思、实现和交流。书籍、计算机、或者程序的出现,首先是作为一个构思或模型出现在作者的脑海中,它与时间和空间无关。接着,借助钢笔、墨水和纸,或者电线、硅片和铁氧体,在现实的时间和空间中实现它们。然后,当某人阅读书本、使用计算机和运行程序的时候,他与作者的思想相互沟通,从而创作过程得以结束。
以上 Sayers 的阐述不仅仅可以描绘人类的创造性活动,而且类似于“基督的教义” ,能指导我们的日常工作。对于创造者,只有在实现的过程中,才能发现我们构思的不完整性和不一致性。因此,对于理论家而言,书写、试验以及“工作实现”是非常基本和必要的。
在许多创造性活动中,往往很难掌握活动实施的介质,例如木头切割、油漆、电器安装等。这些介质的物理约束限制了思路的表达,它们同样对实现造成了许多预料之外的困难。
由于物理介质和思路中隐含的不完善性,实际实现起来需要花费大量的时间和汗水。对遇到的大部分实现上的困难,我们总是倾向于去责怪那些物理介质,因为它们不顺应“我们”设定的思路。其实,这只不过是我们的骄傲使判断带上了主观主义色彩。
然而,计算机编程基于十分容易掌握的介质,编程人员通过非常纯粹的思维活动——概念以及灵活的表现形式来开发程序。正由于介质的易于驾驭,我们期待在实现过程中不会碰到困难,因此造成了乐观主义的弥漫。而我们的构思是有缺陷的,因此总会有 bug。也就是说,我们的乐观主义并不应该是理所应当的。
在单个的任务中, “一切都将运转正常”的假设在时间进度上具有可实现性。因为所遇的延迟是一个概率分布曲线, “不会延迟”仅具有有限的概率,所以现实情况可能会像计划安排的那样顺利。然而大型的编程工作,或多或少包含了很多任务,某些任务间还具有前后的次序,从而一切正常的概率变得非常小,甚至接近于无。
人月
第二个谬误的思考方式是在估计和进度安排中使用的工作量单位:人月。成本的确随开发产品的人数和时间的不同,有着很大的变化,进度却不是如此。因此我认为用人月作为衡量一项工作的规模是一个危险和带有欺骗性的神话。它暗示着人员数量和时间是可以相互替换的。
人数和时间的互换仅仅适用于以下情况:某个任务可以分解给参与人员,并且他们之间不需要相互的交流。这在割小麦或收获棉花的工作中是可行的;而在系统编程中近乎不可能。
当任务由于次序上的限制不能分解时,人手的添加对进度没有帮助。无论多少个母亲,孕育一个生命都需要十个月。由于调试、测试的次序特性,许多软件都具有这种特征,对于可以分解,但子任务之间需要相互沟通和交流的任务,必须在计划工作中考虑沟通的工作量。因此,相同人月的前提下,采用增加人手来减少时间得到的最好情况,也比未调整前要差一些。
沟通所增加的负担由两个部分组成,培训和相互的交流。每个成员需要进行技术、项目目标以及总体策略上的培训。这种培训不能分解,因此这部分增加的工作量随人员的数量呈线性变化。
相互之间交流的情况更糟一些。如果任务的每个部分必须分别和其他部分单独协作,则工作量按照 n(n-1)/2 递增。一对一交流的情况下,三个人的工作量是两个人的三倍,四个人则是两个人的六倍。而对于需要在三四个人之间召开会议、 进行协商、一同解决的问题,情况会更加恶劣。所增加的用于沟通的工作量可能会完全抵消对原有任务分解所产生的作用。
因为软件开发本质上是一项系统工作——错综复杂关系下的一种实践——沟通、交流的工作量非常大,它很快会消耗任务分解所节省下来的个人时间。从而,添加更多的人手,实际上是延长了,而不是缩短了时间进度。
队伍
这些研究表明,效率高和效率低的实施者之间具体差别非常大,经常达 到了数量级的水平。
- SACKMAN, ERIKSON 和 GRANT
These studies revealed large individual differences between high and low performers, often by an order of magnitude.
- SACKMAN, ERIKSON, AND GRANT
在计算机领域的论坛中,常常看到软件经理声称他们喜欢由头等人才组成的小型、精干的队伍,而不是那些几百人的大型团队,这里的“人”当然暗指平庸的程序员。其实我也经常有相同的看法。
但这种幼稚的观点回避了一个很困难的问题——如何在有意义的时间进度内创建大型的系统?那么就让我们现在来仔细讨论一下这个问题的每一个方面。
问题
软件经理很早就认识到优秀程序员和较差的程序员之间生产率的差异,但实际测量出的差异还是令我们所有的人吃惊。在他们的一个研究中Sackman、Erikson 和 Grand 曾对一组具有经验的程序人员进行测量。在该小组中,最好的和最差的表现在生产率上平均为10:1;在运行速度和空间上具有 5:1 的惊人差异!
这验证这样的一个观点,需要协作沟通的人员的数量影响着开发成本,因为成本的主要组成部分是相互的沟通和交流,以及更正沟通不当所引起的不良结果(系统调试)。这一点,也暗示系统应该由尽可能少的人员来开发。实际上,绝大多数大型编程系统的经验显示出,一拥而上的开发方法是高成本的、速度缓慢的、不充分的,开发出的是无法在概念上进行集成的产品。
得出的结论很简单:如果一个 200人的项目中,有 25 个最能干和最有开发经验的项目经理,那么开除剩下的 175 名程序员,让项目经理来编程开发。
但是,他们准备花50年还是100年呢?
系统设计
大教堂是艺术史上无与伦比的成就。它的原则既不乏味也不混乱…… 真正达到了风格上的极致,完成这件作品的艺术家们,完全领会和吸收了 以往的成功经验,也完全掌握了他们那个时代的技术,而且在应用的时候 做到了恰如其分,绝不轻率,也绝不花哨。
正是 Jean d ‘Orbais 构思了建筑的整体设计,这个设计得到了后继者的 认同,至少在本质上如此。这也是这个建筑如此和谐统一的原因之一。
- 兰斯大教堂指南1
This great church is an incomparable work of art. There is neither aridity nor confusion in the tenets it sets forth. . . . It is the zenith of a style, the work of artists who had understood and assimilated all their predecessors ’ successes, in complete possession of the techniques of their times, but using them without indiscreet display nor gratuitous feats of skill.
It was Jean d ‘Orbais who undoubtedly conceived the general plan of the building, a plan which was respected, at least in its essential elements, by his successors. This is one of the reasons for the extreme coherence and unity of the edifice.
- REIMS CATHEDRAL GUIDEBOOK
概念一致性
绝大多数欧洲的大教堂中,由不同时代、不同建筑师所建造的各个部分之间,在设计或结构风格上都存在着许多差异。后来的建筑师总是试图在原有建筑师的基础上有所“提高” ,以反映他们在设计风格和个人品味上的改变。所以,在雄伟的哥特式的教堂上,依附着祥和的诺曼第风格十字架,它在显示上帝荣耀的同时,展示了同样属于建筑师的骄傲。
对于计算机系统而言,尽管它们通常没有花费几个世纪的时间来构建,但绝大多数系统体现出的概念差异和不一致性远远超过欧洲的大教堂。这通常并不是因为它由不同的设计师们开发,而是由于设计被分成了由若干人完成的若干任务。
我主张在系统设计中,概念完整性应该是最重要的考虑因素。也就是说为了反映一系列连贯的设计思路,宁可省略一些不规则的特性和改进,也不提倡独立和无法整合的系统,哪怕它们其实包含着许多很好的设计。
为什么巴比伦塔会失败?
现在整个大地都采用一种语言,只包括为数不多的单词。在一次从东方 往西方迁徙的过程中,人们发现了苏美尔地区,并在那里定居下来。接着 他们奔走相告说: “来,让我们制造砖块,并把它们烧好。 ”于是,们 用砖块代替石头,用沥青代替灰泥(建造房屋)。然后,他们又说:“来, 让我们建造一座带有高塔的城市,这个塔将高达云宵,也将让我们声名远
扬,同时,有了这个城市,我们就可以聚居在这里,再也不会分散在广阔 的大地上了。 ”于是上帝决定下来看看人们建造的城市和高塔,看了以后, 他说: “他们只是一个种族,使用一种的语言,如果他们一开始就能建造 城市和高塔,那以后就没有什么难得倒他们了。来,让我们下去,在他们 的语言里制造些混淆,让他们相互之间不能听懂。 ”这样,上帝把人们分 散到世界各地,于是他们不得不停止建造那座城市。(创世纪,11:1-8)
Now the whole earth used only one language, with few words. On the occasion of a migration from the east, men discovered a plain in the land of Shinar , and settled there. Then they said to one another , "Come, let us make bricks, burning them well." So they used bricks for stone, and bitumen for mortar . Then they said,"Come, let us build ourselves a city with a tower whose top shall reach the heavens (thus making a name for ourselves), so that we may not be scattered all over the earth." Then the Lord came down to look at the city and tower which human beings had built. The Lord said, "They are just one people and they all have the same language. If this is what they can do as a beginning, then nothing that they resolve to do will be impossible for them. Come, let us go down, and there make such a babble of their language that they will not understand one another's speech." Thus the Lord dispersed them from there all over the earth, so that they had to stop building the city. (Book of Genesis, 11:1-8).
交流——大型项目的关键。