《人月神话》读书笔记

Photo by Janik Butz from Pexels

前言:

我很庆幸读到了这本书,这本神作。或许它不能帮我解决我目前遇到的任何问题,但是可以帮助我拓宽思维,从前人大师的视角看待软件开发。无需更多的言语表达对于这本书的赞美与推崇,这是一本软件开发人员必须多读的书籍。

摘自维基百科介绍:

人月神话:软件项目管理之道》(英语:The Mythical Man-Month: Essays on Software Engineering)是由IBM System/360系统之父佛瑞德·布鲁克斯所著经典文集,全书讲解软件工程项目管理相关课题,被誉为软件领域的圣经

对于人月的解释:

人月(英语:man-month)指的是“一个人要花几个月”才能完成软件开发的单位,比如一个项目 5个人 合作开发需要 2个月 的时间,那么总工作量就是 10人月

另外,人月神话这本书的英文名是The Mythical Man-Month,有些人简称为 MMM

注:

笔者注记将以这种下划线引用添加到正文部分,因为本文是读书笔记加一些反思,一些章节只会挑选一些重要的、启发性的文本加上笔者的概括描述。

原文中比较重要的段落将以加粗等形式标注。

1. 焦油坑

File:La Brea Tar Pits.jpg

图片来源:Wiki Media Commons

史前史中,没有别的场景比巨兽在焦油坑中垂死挣扎的场面更令人震撼。上帝见证着 恐龙、猛犸象、剑齿虎在焦油中挣扎。它们挣扎得越是猛烈,焦油纠缠得越紧,没有任何猛兽足够强壮或具有足够的技巧,能够挣脱束缚,它们最后都沉到了坑底。过去几十年的大型系统开发就犹如这样一个焦油坑,很多大型和强壮的动物在其中剧烈地挣扎。

各种团队,大型的和小型的,庞杂的和精干的,一个接一个淹没在了焦油坑中。表面上看起来好像没有任何一个单独的问题会导致困难,每个都能被解决,但是当它们相互纠缠和累积在一起的时候,团队的行动就会变得越来越慢。

软件开发的另一个难题,是从单一程序到软件系统过程中,所造成复杂度的快速上升,期间并需要包含不同的活动与技能,使得软件开发必须面对多样性的挑战。

焦油坑对应软件,程序员对应各种野兽的比喻很贴切。事实上在实际工作中的感受确实如此,为何精心编制的作品终有一日成了这样的焦油坑?这里无需找其他类似于历史债务、团队水平等借口为自己开脱,程序员终究不是单打独斗的职业。

1.1 认识编程系统产品

本节以程序与系统、产品的差异,解释两者之间的不同,并以3×3的复杂度加以说明。

  1. 图中左上角的是程序(Program)。它本身是完整的,可以由作者在所开发的系 统平台上运行。
  2. 左下角,程序变成编程产品(Programming Product)。这是可以被任何人运行、 测试、修复和扩展的程序。
  3. 右上角,程序变成编程系统(Programming System)中的一个构件单元。它是在功能上能相互协作的程序集合,具有规范的格式,可以进行交互,并可以用来组装和搭建整个系统。
  4. 右下角,编程系统产品(Programming Systems Product)。和以上的所有的情况都不同的是,它的成本高达九倍。然而,只有它才是真正有用的产品,是大多数系统开发的目标。

1.2 职业的乐趣

编程为什么有趣?作为回报,它的从业者期望得到什么样的快乐?

  1. 创建事物的纯粹快乐。如同小孩在玩泥巴时感到愉快一样,成年人喜欢创建事物,特别是自己进行设计。
  2. 于开发对其他人有用的东西。内心深处,我们期望其他人使用我们的劳动成果,并能对他们有所帮助。
  3. 魔术般的力量——将相互啮合的零部件组装在一起,看到它们精妙地运行,得到预先所希望的结果。
  4. 学习的乐趣,来自于这项工作的非重复特性。
  5. 创造的方式如此得灵活,如此得易于精炼和重建,如此得容易实现概念上的设想。程序员,就像诗人一样,几乎仅仅工作在单纯的思考中。

1.3 职业的苦恼

  1. 追求完美。因为计算机也是以这样的方式来变戏法:如果咒语中的一个字符、一个停顿,没有与正确的形式一致,魔术就不会出现。
  2. 由他人来设定目标,供给资源,提供信息。编程人员很少能控制工作环境和工作目标。对于系统编程人员而言,对其他人的依赖是一件非常痛苦的事情
  3. 概念性设计是有趣的,但寻找琐碎的 bug 却只是一项重复性的活动。 伴随着创造性活动的,往往是枯燥沉闷的时间和艰苦的劳动。
  4. 当投入了大量辛苦的劳动,产品在即将完成或者终于完成的时候,却已显得陈旧过时。

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

Photo by olia danilevich from Pexels


2. 人月神话


这部分讲述人力和时间并不呈现线性关系。指出以大量人员和较短的时间,并不能缩短软件的开发进度。一窝蜂的作业方式无助于软件生产,且会制造麻烦,产生出更差的软件。向进度落后的项目追加人力,只会使进度更加落后。因为新进的人员需要时间了解整个项目,而增加额外的沟通消耗。当有N个人必须在这群人之中进行沟通时(无阶级关系),当N增加,其输出M将抵消其效益,甚至倒退(最后几天所完成的进度,远不如刚开始几天所完成的进度。像是发现了许多错误)。

用“人月”来衡量工作规模的大小是危险的,使用人月的前提必须是在人力和工时可以互换的情况之下:当一份工作因具有连续性的限制而不可切分时,就算投入再多的人力,也不会对时程有所影响,生小孩就是需要十个月,多少个母亲一起生都一样,软件工程就是像这样的工作,因为它必须调试,而调试本身就具有连续性的本质。


在众多软件项目中,缺乏合理的时间进度是造成项目滞后的最主要原因,它比其他所 有因素加起来的影响还大。导致这种普遍性灾难的原因是什么呢?

  1. 我们对 估算技术缺乏有效的研究,更加严肃地说,它反映了一种悄无声息,但并不真实的假设——一切都将运作良好。
  2. 我们采用的估算技术 隐含地假设人和月可以互换,错误地将进度与工作量相互 混淆。
  3. 由于对自己的估算缺乏信心,软件经理通常不会有耐心持续地进行估算这项工 作。
  4. 对进度缺少跟踪和监督。其他工程领域中,经过验证的跟踪技术和常规监督程 序,在软件工程中常常被认为是无谓的举动。
  5. 当意识到进度的偏移时,下意识的反应是增加人力。这就像使用 汽油灭火一样,只会使事情更糟。越来越大的火势需要更多的汽油,从而进入了一场注定会导致灾难的循环

2.1 乐观主义

所有的编程人员都是乐观主义者。可能是这种现代魔术特别吸引那些相信美满结局的人;也可能是成百上千琐碎的挫折赶走了大多数人,只剩下了那些习惯上只关注结果的人; 还可能仅仅因为计算机还很年轻,程序员更加年轻,而年轻人总是些乐观主义者——无论是什么样的程序,结果是勿庸置疑的:“这次它肯定会运行。”或者 “我刚刚找出了最后一个错误。

所以系统编程的进度安排背后的第一个假设是:一切都将运作良好,每一项任务仅花费它所“应该”花费的时间

对这种弥漫在编程人员中的乐观主义,理应受到慎重的分析。Dorothy Sayers 在她的 “The Mind of the Maker”一书中,将创造性活动分为三个阶段:构思、实现和交流。书 籍、计算机、或者程序的出现,首先是作为一个构思或模型出现在作者的脑海中,它与时间和空间无关。接着,借助钢笔、墨水和纸,或者电线、硅片和铁氧体,在现实的时间和空间 中实现它们。然后,当某人阅读书本、使用计算机和运行程序的时候,他与作者的思想相互沟通,从而创作过程得以结束。

计算机编程基于十分容易掌握的介质,编程人员通过非常纯粹的思维活动—— 概念以及灵活的表现形式来开发程序。正由于介质的易于驾驭,我们期待在实现过程中不会 碰到困难,因此造成了乐观主义的弥漫。而我们的构思是有缺陷的,因此总会有 bug。也就 是说,我们的乐观主义并不应该是理所应当的。

2.2 人月

第二个谬误的思考方式是在估计和进度安排中使用的工作量单位:人月。成本的确随开发产品的人数和时间的不同,有着很大的变化,进度却不是如此。因此我认为用人月作为 衡量一项工作的规模是一个危险和带有欺骗性的神话。它暗示着人员数量和时间是可以相互 替换的。

人数和时间的互换仅仅适用于以下情况:某个任务可以分解给参与人员,并且他们之 间不需要相互的交流。这在割小麦或收获棉花的工作中是可行的;而在系统编程中近乎不可能。

当任务由于次序上的限制不能分解时,人手的添加对进度没有帮助。无论多少个母亲,孕育一个生命都需要十个月。由于调试、测试的次序特性,许多软件都具有这种特征。

对于可以分解,但子任务之间需要相互沟通和交流的任务,必须在计划工作中考虑沟 通的工作量。因此,相同人月的前提下,采用增加人手来减少时间得到的最好情况,也比未调整前要差一些

相互之间交流的情况更糟一些。如果任务的每个部分必须分别和其他部分单独协作, 则工作量按照 n(n-1)/2 递增。一对一交流的情况下,三个人的工作量是两个人的三倍,四 个人则是两个人的六倍。而对于需要在三四个人之间召开会议、进行协商、一同解决的问题, 情况会更加恶劣。所增加的用于沟通的工作量可能会完全抵消对原有任务分解所产生的作用

因为软件开发本质上是一项系统工作——错综复杂关系下的一种实践——沟通、交流 的工作量非常大,它很快会消耗任务分解所节省下来的个人时间。从而,添加更多的人手, 实际上是延长了,而不是缩短了时间进度。

2.3 系统测试

在时间进度中,顺序限制所造成的影响,没有哪个部分比单元调试和系统测试所受到的牵涉更彻底。而且,要求的时间依赖于所遇到的错误、缺陷数量以及捕捉它们的程度。理论上,缺陷的数量应该为零。但是,由于我们的乐观主义,通常实际出现的缺陷数量比预料的要多得多。因此,系统测试进度的安排常常是编程中最不合理的部分。

对于软件任务的进度安排,以下是我使用了很多年的经验法则:

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

在许多重要的方面,它与传统的进度安排方法不同:

  1. 分配给计划的时间比寻常的多。即便如此,仍不足以产生详细和稳定的计划规格说 明,也不足以容纳对全新技术的研究和摸索。
  2. 对所完成代码的调试和测试,投入近一半的时间,比平常的安排多很多。
  3. 容易估计的部分,即编码,仅仅分配了六分之一的时间。

通过对传统项目进度安排的研究,我发现很少项目允许为测试分配一半的时间,但大多数项目的测试实际上是花费了进度中一半的时间。它们中的许多项目,在系统测试之前还能保持进度。或者说,除了系统测试,进度基本能保证 。

特别需要指出的是,不为系统测试安排足够的时间简直就是一场灾难。因为延迟发生 在项目快完成的时候。直到项目的发布日期,才有人发现进度上的问题。因此,坏消息没有 任何预兆,很晚才出现在客户和项目经理面前。 另外,此时此刻的延迟具有不寻常的、严重的财务和心理上的反应。在此之前,项目 已经配置了充足的人员,每天的人力成本也已经达到了最大的限度。

更重要的是,当软件用来支持其他的商业活动(计算机硬件到货,新设备、服务上线等等)时,这些活动延误出现 即将发布前,那么将付出相当高的商业代价。 实际上,上述的二次成本远远高于其他开销。因此,在早期进度策划时,允许充分的系统测试时间是非常重要的。


本章小结

有关 Brooks法则 的描述:

在一个时程已经落后的软件项目中增加人手,只会让它更加落后人月神话。根据Brooks法则,增加人员到一个已经延误的项目里,等于是火上加油。除非你可以把工作区分,让新进人员可在不影响他人工作的状况下有所贡献。

把工作切分给更多人做将造成额外的沟通(communication)代价——训练和相互的交流(intercommunication)。欲增加软件项目的人手,总共必须付出的代价可分为三方面:工作重新切分本身所造成的混乱与额外工作量、新进人员的训练、新增加的相互交流。

在这本书的结尾,有一些其他读者的书评,有一句话是;“在阅读的时候,每隔几页不说一句‘对极了!’是很难受的。”,确实是这样(😄),所以我甚至觉得没有必要写读书笔记和自己的评论,摘抄的内容都是我不舍得删减的~


3. 专业化分工——外科手术团队

在计算机领域的会议中,常常听到年轻的软件经理声称他们喜欢由头等人才组成的小 型、精干的队伍,而不是那些几百人的大型团队,这里的“人”当然暗指平庸的程序员。其实我们也经常有相同的看法。 但这种幼稚的观点回避了一个很困难的问题——如何在有意义的时间进度内创建大型的系统?

3.1 问题

软件经理很早就认识到优秀程序员和较差的程序员之间生产率的差异,但实际测量出 的差异还是令我们所有的人吃惊。在他们的一个研究中,Sackman、Erikson 和 Grand 曾对 一组具有经验的程序人员进行测量。在该小组中,最好的和最差的表现在生产率上平均为 10:1;在运行速度和空间上具有 5:1 的惊人差异!简言之,$20,000/年的程序员的生产率可能是$10,000/年程序员的 10 倍。数据显示经验和实际的表现没有相互联系(我怀疑这种现 象是否普遍成立)。

3.2 外科手术团队式的组成

大型项目的每一个部分由一个团队解决,但是该队伍以类似外科手术的方式组建,而并非一拥而上。也就是说,同每个成员截取问题某个部分的做法相反,由一个人来进行问题的分解,其他人 给予他所需要的支持,以提高效率和生产力。

  • 外科医生,首席程序员。他亲自定义功能和性能技术说明书,设计程序, 编制源代码,测试以及书写技术文档。他使用例如 PL/I 的结构化编程语言,拥有对计算机 系统的访问能力;该计算机系统不仅仅能进行测试,还存储程序的各种版本,以允许简单的 文件更新,并对他的文档提供文本编辑能力。首席程序员需要极高的天分、十年的经验和应用数学、业务数据处理或其他方面的大量系统和应用知识。
  • 副手,外科医生的后备,能完成任何一部分工作,但是相对具有较少的经验。他的主要作用是作为设计的思考者、讨论者和评估人员。外科医生试图和他沟通设计,但不受到他建议的限制。副手经常在与其他团队的功能和接口讨论中代表自己的小组。他需要详细 了解所有的代码,研究设计策略的备选方案。显然,他充当外科医生的保险机制。他甚至可能编制代码,但针对代码的任何部分,不承担具体的开发职责。
  • 管理员。外科医生是老板,他必须在人员、加薪等方面具有决定权,但他决不能在这些事务上浪费任何时间。因而,他需要一个控制财务、人员、工作地点安排和机器的专业管理人员,该管理员充当与组织中其他管理机构的接口。Baker 建议仅在项目具有法律、合同、 报表和财务方面的需求时,管理员才具有全职责任。否则,一个管理员可以为两个团队服务。
  • 编辑。外科医生负责产生文档——出于最大清晰度的考虑,他必须书写文档。对内部 描述和外部描述都是如此。而编辑根据外科医生的草稿或者口述的手稿,进行分析和重新组 织,提供各种参考信息和书目,对多个版本进行维护以及监督文档生成的机制。
  • 两个秘书。管理员和编辑每个人需要一个秘书。管理员的秘书负责项目的协作一致和非产品文件。
  • 程序职员。他负责维护编程产品库中所有团队的技术记录。该职员接受秘书性质的培 训,承担机器码文件和可读文件的相关管理责
  • 工具维护人员,保证所有基本服务的可靠性,以及承担团队成员所需要的特殊工具(特 别是交互式计算机服务)的构建、维护和升级责任。工具维护人员常常要开发一些实用程序、编制具有目录的过程库以及宏库。
  • 测试人员,外科医生需要大量合适的测试用例,用来对他所编写的工作片段,以及对 整个工作进行测试。因此,测试人员既是为他的各个功能设计系统测试用例的对头,同时也 是为他的日常调试设计测试数据的助手。他还负责计划测试的步骤和为测试搭建测试平台。
  • 语言专家。语言专家则寻找一种简洁、有效的使用语言的方法来解决复杂、晦涩或者棘手的问题。他通常需要对技术进行一些研究(两到三天)。通常一个语言专家可以为两个到三个外科医生服务任。


本章小结

在接受相同的训练、同样都是两年资历的情况下,优秀专业程序员的生产力要比差劲的程序员好上十倍。短小精悍团队是最棒的——尽可能用最少的人。两人团队,其中一人当领导者,这通常是最佳的用人方式。以短小精悍团队开发真正大的系统就太慢了。绝大多数大型软件系统的经验显示,使用一堆人蛮干的方式最耗成本、最慢、最没有效率,做出来的系统在概念上也最不完整。

以一位首席程序员为主、类似于外科手术团队的组织提供了一个良方,既可因少数人的决策而兼顾到产品的整体性,又可因多数人的合作与大幅沟通减少而得到全部人的生产力

我试图去理解作者关于“外科医生团队”的观点并作出总结:在一个开发团队中,成员们应该有各自擅长的事情,分工明确,各司其职,而不是一股脑的冲向电脑前开始编码。

所以本章节原本的标题是“外科手术团队”,我额外强调了专业化分工


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

4.1 概念完整性

设计的一致性让人们赞叹和喜悦。如同旅游指南所述,风格的一致和完整性来自八代拥有自我约束和牺牲精神的建筑师们,他们每一个人牺牲了自己的一些创意,以获得纯粹的设计。同样,这不仅显示了上帝的荣耀,同时也体现了他拯救那些沉醉在自我骄傲中的人们的力量。

对于计算机系统而言,尽管它们通常没有花费几个世纪的时间来构建,但绝大多数系统体现出的概念差异和不一致性远远超过欧洲的大教堂。这通常并不是因为它由不同的设计 师们开发,而是由于设计被分成了由若干人完成的若干任务。

我主张在系统设计中,概念完整性应该是最重要的考虑因素。也就是说为了反映一系列连贯的设计思路,宁可省略一些不规则的特性和改进,也不提倡独立和无法整合的系统, 哪怕它们其实包含着许多很好的设计。

4.2 获得概念的完整性

编程系统(软件)的目的是使计算机更加容易使用。为了做到这一点,计算机装备了语言和各种工具,这些工具实际上也是被调用的程序,受到编程语言的控制。使用这些工具 是有代价的:软件外部描述的规模大小是计算机系统本身说明的十倍。用户会发现寻找一个 特定功能是很容易的,但相应却有太多的选择,要记住太多的选项和格式。

只有当这些功能说明节约下来的时间,比用在学习、记忆和搜索手册上的时间要少时, 易用性才会得到提高。现代编程系统节省的时间的确超过了花费的时间,但是近年来,随着越来越多的功能添加,收益和成本的比率正逐渐地减少。

由于目标是易用性,功能与理解上复杂程度的比值才是系统设计的最终测试标准。单是功能本身或者易于使用都无法成为一个好的设计评判标准。

对于给定级别的功能,能用最简洁和直接的方式来指明事情的系统是最好的。只 有简洁(simplicity)是不够的,Mooers 的 TRAC 语言和 Algol 68 用很多独特的基本 概念达到了所需的简洁特性,但它们并不直白(straightforward)。要表达一件待完成 的事情,常常需要对基本元素进行意料不到的复杂组合。

简洁和直白来自概念的完整性。每个部分必须反映相同的原理、原则和一致的折衷机制。在语法上, 每个部分应使用相同的技巧;在语义上,应具有同样的相似性。因此,易用性实际上需要设计的一致性和概念上的完整性

4.3 贵族专制统治和民主政治

概念的完整性要求设计必须由一个人,或者非常少数互有默契的人员来实现。

而进度压力却要求很多人员来开发系统。有两种方法可以解决这种矛盾。

第一种是仔细地区分设计方法和具体实现。

第二种是前一章节中所讨论的、一种崭新的组建编程开发团队的方法。 对于非常大型的项目,将设计方法、体系结构方面的工作与具体实现相分离是获得概念完整性的强有力方法。

系统的体系结构(Architecture)指的是完整和详细的用户接口说明。对于计算机,它是编程手册;对于编译器,它是语言手册;对于控制程序,它是语言和函数调用手册;对 于整个系统,它是用户要完成自己全部工作所需参考的手册的集合。

因此,系统的架构师,如同建筑的架构师一样,是用户的代理人。架构师的工作,是运用专业技术知识来支持用户的真正利益,而不是维护销售人员所鼓吹的利益。

现在让我们来处理具有浓厚感情色彩的问题——贵族统治和民主政治。结构师难道不是新贵?他们一些智力精英,专门来告诉可怜的实现人员如何工作?是否所有的创造性活动被那些精英单独占有,实现人员仅仅是机器中的齿轮?难道不能遵循民主的理论,从所有的员工中搜集好的创意,以得到更好的产品,而不是将技术说明工作仅限定于少数人?

最后一个问题是最简单的。我当然不认为只有结构师才有好的创意。新的概念经常来自实现者或者用户。然而,我一直试图表达,并且我所有的经验使我确信,系统的概念完整性决定了使用的容易程度。不能与系统基本概念进行整合的良好想法和特色,最好放到一边, 不予考虑。如果出现了很多非常重要但不兼容的构想,就应该抛弃原来的设计,对不同基本概念进行合并,在合并后的系统上重新开始。

至于贵族专制统治的问题,必须回答 “是”或者“否”。就必须只能存在少数的结构师而言,答案是肯定的,他们的工作产物的生命周期比那些实现人员的产物要长,并且结构师一直处在解决用户问题,实现用户利益的核心地位。如果要得到系统概念上的完整性,那 么必须控制这些概念。这实际上是一种无需任何歉意的贵族专制统治。

第二个问题的答案是否定的,因为外部技术说明的编制工作并是比具体设计实现更富有创造性,它只是一项性质不同的创造工作而已。在给定体系结构下的设计实现,同样需要同编制技术说明一样的创造性、同样新的思路和卓越的才华。实际上,产品的成本性能比很大程度上依靠实现人员,就如同易用性很大程度上依赖结构师一样。


本章小结:

  • 在系统设计时,保有概念整体性(conceptual integrity)是最重要的原则。
  • 功能概念复杂度比(这个比值是为了量测使用便利性,对简单和困难的运用都很有效)才是系统设计的最终测试标准。功能多,不见得是好事。
  • 要达成概念整体性,设计必须出自于一个人的想法,或是极少数人的一致决定。
  • 对大型软件项目(小项目也一样)来说,将架构设计独立于实现之外是获取概念整体性强而有利的方法。
  • 如果系统必须保有概念上的整体性,那么就必须有人来控制这些概念,这就是需要专制的原因,无庸置疑。
  • 许多软件架构、实现、实现的工作都是可以同时进行的。(不论硬件或软件设计,都可以同时进行)

或许我在日常的工作中一直觉得有哪里不对劲,为什么微服务的建设工作从接到需求到交付制品,从成果来看差了那么多?好像在实现的过程中总是修修改改,多了一些不在原计划中的东西。看到这里我明白这就是我想要的那个词——概念完整性


Photo by Ann H from Pexels

5. 画蛇添足

如果将制订功能规格说明的责任从开发快速、成本低廉的产品的责任中分离出来,那么有什么样的准则和机制来约束结构师的创造性热情呢? 基本回答是结构师和建筑人员之间彻底、仔细和谐的交流。另外,还有很多值得关注 的、更细致的答案。

5.1 结构师的交互准则和机制

建筑行业的结构设计师使用估算技术来编制预算,该估算技术会由后续的承包商报价 来验证和修正。承包商的报价总会超过预算。接下来,设计师会重新改进他的预算或修订设 计,调整到下一期工程。他也可能会向承包商建议,使用更加便宜的方法来实现设计。

类似的过程也支配着计算机系统和计算机编程系统的结构师。相比之下,他有能在设 计早期从承包商处得到报价的优势,几乎是只要他询问,就能得到答案。他的不利之处常常 是只有一个承包商,后者可以增高或降低前者的估计,来反映对设计的好恶。实际情况中, 尽早交流和持续沟通能使结构师有较好的成本意识,以及使开发人员获得对设计的信心,并且不会混淆各自的责任分工。

面对估算过高的难题,结构师有两个选择:

  • 削减设计

  • 建议成本更低的实现方法——挑战估算的结果

后者是固有的主观感性反应。此时,结构师是在向开发人员的做事方式提出挑战。想要成功,结构师必须

  • 牢记是开发人员承担创造性和发明性的实现责任,所以结构师只能建议,而不能支配
  • 时刻准备着为所指定的说明建议一种实现的方法,同样准备接受其他任何能达到目 标的方法
  • 对上述的建议保持低调和平静
  • 准备放弃坚持所作的改进建议

一般开发人员会反对体系结构上的修改建议。通常他是对的——当正在实现产品时, 某些特性的修改会造成意料不到的成本开销。

5.2 自律——开发第二个系统所带来的后果

在开发第一个系统时,结构师倾向于精炼和简洁。他知道自己对正在进行的任务不够 了解,所以他会谨慎仔细地工作。

在设计第一个项目时,他会面对不断产生的装饰和润色功能。这些功能都被搁置在一边,作为“下一个”项目的内容。第一个项目迟早会结束,而此时的结构师,对这类系统充满了十足的信心,熟练掌握了相应的知识,并且时刻准备开发第二个系统第二个系统是设计师们所设计的最危险的系统。而当他着手第三个或第四个系统时, 先前的经验会相互验证,得到此类系统通用特性的判断,而且系统之间的差异会帮助他识别出经验中不够通用的部分。 一种普遍倾向是过分地设计第二个系统,向系统添加很多修饰功能和想法,它们曾在第一个系统中被小心谨慎地推迟了。

★ 结构师如何避免画蛇添足——开发第二个系统所引起的后果(second-system effect)?是的,他无法跳过二次系统。但他可以有意识关注那些系统的特殊危险,运用特别的自我约束准则,来避免那些功能上的修饰;根据系统基本理念及目的变更,舍弃一些功能。 一个可以开阔结构师眼界的准则是为每个小功能分配一个值:每次改进,功能 x 不超 过 m 字节的内存和 n 微秒。这些值会在一开始作为决策的向导,在物理实现期间充当指南和 对所有人的警示。

★ 项目经理如何避免画蛇添足(second-system effect)?他必须坚持至少拥有两个系统以上开发经验结构师的决定。同时,保持对特殊诱惑的警觉,他可以不断提出正确的问题, 确保原则上的概念和目标在详细设计中得到完整的体现


第二系统效应(英语:The second-system effect)就一个人所做过的设计而言,第二个系统是最危险的系统,一般来说,都倾向于过度设计。

当他做第三或之后的系统时,之前的经验会互相印证,以确认出这类系统的一般性特色,而系统彼此之间的不同处,也会帮助他辨别出属于特殊和非通用的部分。除了做些功能上的修饰之外,第二系统效应还有另外一项特征,那就是倾向于将之前已熟悉的技术发挥到淋漓尽致,但却没有留意到,这项技术早就跟目前项目的基本系统假设有冲突而不再适用

读书笔记部分并没有详细的讲原文中描述的OS/360的例子也摘抄下来,OS/360可称得上是一个最佳的第二系统效应示例。

我相信只要有自己设计过一个微服务系统的开发者都会希望自己的设计能尽量的完美,体现出健壮、高性能、可扩展的特性,从而增加了一些可能用不到的特性、代码风格、设计模式、最佳实践等等,做这些事情的时候需要谨慎的分析是否在过度设计。。。


6. 贯彻执行

假设一个项目经理已经拥有行事规范的结构师和许多编程实现人员,那么他如何确保 每个人听从、理解并实现结构师的决策?对于一个由 1000 人开发的系统,一个 10 个结构师的小组如何保持系统概念上的完整性?在 System/360 硬件设计工作中,我们摸索出来一套实现上述目标的方法,它们对于软件项目同样适用。

6.1 文档化的规格说明——手册

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

随着用户和实现人员反馈的增加,规格说明中难以使用和难以构建实现的地方不断被 指出,规格说明也不断地被重复准备和修改。然而对实现人员而言,修改的阶段化是很重要 的——在进度表上应该有带日期的版本信息。

手册不但要描述包括所有界面在内的用户可见的一切,它同时还要避免描述用户看不见的事物。后者是编程实现人员的工作范畴,而实现人员的设计和创造是不应该被限制的。 体系结构设计人员必须为自己描述的任何特性准备一种实现方法,但是他不应该试图支配具体的实现过程。

规格说明的风格必须清晰、完整和准确。用户常常会单独提到某个定义,所以每条说 明都必须重复所有的基本要素,所以所有文字都要相互一致。这往往使手册读起来枯燥乏味, 但是精确比生动更加重要

6.2 会议和大会

无需多说,会议是必要的。然而,数百人在场的大型磋商会议往往需要大规模和非常正式地召集。因此,我们把会议分成两个级别:周例会年度大会——这实际上是一种非常有效的方式。

周例会是每周半天的会议,由所有的结构师,加上硬件和软件实现人员代表和市场计划人员参与,由首席系统结构师主持。 会议中,任何人可以提出问题和修改意见,但是建议书通常是以书面形式,在会议之前分发。新问题通常会被讨论一些时间。重点是创新,而不仅仅是结论。该小组试图发现解决问题的新方法,然后少数解决方案会被传递给一个和几个结构师,详细地记录到书面的变更建议说明书中。

接着会对详细的变更建议做出决策。这会经历几个反复过程,实现人员和用户会仔细 地进行考虑,正面和负面的意见都会被很好地描述。如果达成了共识,非常好;如果没有, 则由首席结构师来决定。这需要花费时间,最终所发布的结论是正式和果断的。

6.3 产品测试

项目经理最好的朋友就是他每天要面对的敌人——独立的产品测试机构/小组。该小组根据规格说明检查机器和程序,充当麻烦的代言人,查明每一个可能的缺陷和相互矛盾的地方。每个开发机构都需要这样一个独立的技术监督部门,来保证其公正性。

在最后的分析中,用户是独立的监督人员。在残酷的现实使用环境中,每个细微缺陷都将无从遁形。产品-测试小组则是顾客的代理人,专门寻找缺陷。不时地,细心的产品测试人员总会发现一些没有贯彻执行、设计决策没有正确理解或准确实现的地方。出于这方面的原因,设立测试小组是使设计决策得以贯彻执行的必要手段,同样也是需要尽早着手,与设计同时实施的重要环节。


当我开始主动为写系统文档的时候,我就意识到了这件事情的重要性。这种形式化的定义能够整理设计者的思路,精确的描述了系统的“相貌”。在表达的精确和简明性上,提出形式化的定义增强了自己对系统设计的信心和认知。可惜系统文档和产品测试在我目前的工作中并不是一个明确定义的内容。


7. 为什么巴比伦塔会失败?

关于巴比伦塔的故事:维基百科 Tower of Babel

7.1 巴比伦塔的管理教训

据《创世纪》记载,巴比伦塔是人类继诺亚方舟之后的第二大工程壮举,但巴比伦塔 同时也是第一个彻底失败的工程。 这个故事在很多方面和不同层次都是非常深刻和富有教育意义的。让我们将它仅仅作 为纯粹的工程项目,来看看有什么值得学习的教训。这个项目到底有多好的先决条件?他们 是否有:

  1. 清晰的目标?是的,尽管幼稚得近乎不可能。而且,项目早在遇到这个基本的限制 之前,就已经失败了。
  2. 人力?非常充足。
  3. 材料?在美索不达米亚有着丰富的泥土和柏油沥青。
  4. 足够的时间?没有任何时间限制的迹象。
  5. 足够的技术?是的,金字塔、锥形的结构本身就是稳定的,可以很好分散压力负载。 对砖石建筑技术,人们有过深刻的研究。同样,项目远在达到技术限制之间,就已经失败了。

那么,既然他们具备了所有的这些条件,为什么项目还会失败呢?他们还缺乏些什么? 两个方面——交流,以及交流的结果——组织。他们无法相互交谈,从而无法合作。当合作无法进行时,工作陷入了停顿。通过史书的字里行间,我们推测交流的缺乏导致了争辩、沮丧和群体猜忌。很快,部落开始分裂——大家选择了孤立,而不是互相争吵。

7.2 大型编程项目中的交流

现在,其实也是这样的情况。因为左手不知道右手在做什么,所以进度灾难、功能的不合理和系统缺陷纷纷出现。

那么,团队如何进行相互之间的交流沟通呢?通过所有可能的途径。

  • 非正式途径 清晰定义小组内部的相互关系和充分利用电话,能鼓励大量的电话沟通,从而达到对 所书写文档的共同理解。
  • 会议 常规项目会议。会议中,团队一个接一个地进行简要的技术陈述。这种方式非常有用, 能澄清成百上千的细小误解。
  • 工作手册。 在项目的开始阶段,应该准备正式的项目工作手册。理所应当,我们专门用一节来讨论它。

7.3 项目工作手册

项目工作手册是什么?。项目工作手册不是独立的一篇文档,它是对项目必须产出的一系列文档进行 组织的一种结构。

项目所有的文档都必须是该结构的一部分。这包括目的、外部规格说明、接口说明、 技术标准、内部说明和管理备忘录。

为什么需要项目工作手册?。技术说明几乎是必不可少的。如果某人就硬件和软件的某部分,去查看一系 列相关的用户手册。他发现的不仅仅是思路,而且还有能追溯到最早备忘录的许多文字和章 节,这些备忘录对产品提出建议或者解释设计。对于技术作者而言,文章的剪裁粘贴与钢笔 一样有用。

基于上述理由,再加上“未来产品”的质量手册将诞生于“今天产品” 的备忘录,所 以正确的文档结构非常重要。事先将项目工作手册设计好,能保证文档的结构本身是规范的, 而不是杂乱无章的。另外,有了文档结构,后来书写的文字就可以放置在合适的章节中。 使用项目手册的第二个原因是控制信息发布。控制信息发布并不是为了限制信息,而是确保信息能到达所有需要它的人的手中。

7.4 大型编程项目的组织架构

如果项目有 n 个工作人员,则有(n 2 - n)/ 2 个相互交流的接口,有将近 2 n 个必须合作的潜在团队。团队组织的目的是减少不必要交流和合作的数量,因此良好的团队组织是解决上述交流问题的关键措施。 减少交流的方法是人力划分(division of labor)限定职责范围(specialization of function)。当使用人力划分和职责限定时,树状管理结构所映出对详细交流的需要会相应减少。

事实上,树状组织架构是作为权力和责任的结构出现。其基本原理——管理角色的非重复性——导致了管理结构是树状的。但是交流的结构并未限制得如此严格,树状结构几乎不能用来描述交流沟通,因为交流是通过网状结构进行的。在很多工程活动领域,树状模拟结构不能很精确地用于描述一般团队、特别工作组、委员会,甚至是矩阵结构组织。 让我们考虑一下树状编程队伍,以及要使它行之有效,每棵子树所必须具备的基本要 素。它们是:

  1. 任务(a mission)
  2. 产品负责人(a producer)
  3. 技术主管和结构师(a technical director or architect)
  4. 进度(a schedule)
  5. 人力的划分(a division of labor)
  6. 各部分之间的接口定义(interface definitions among the parts)

所有这些是非常明显和约定俗成的,除了产品负责人和技术主管之间有一些区别。我 们先分析一下两个角色,然后再考虑它们之间的关系。

产品负责人的角色是什么?他组建团队,划分工作及制订进度表。他要求,并一直要 求必要的资源。这意味着他主要的工作是与团队外部,向上和水平地沟通。他建立团队内部 的沟通和报告方式。最后,他确保进度目标的实现,根据环境的变化调整资源和团队的构架。

那么技术主管的角色是什么?他对设计进行构思,识别系统的子部分,指明从外部看 上去的样子,勾画它的内部结构。他提供整个设计的一致性和概念完整性;他控制系统的复 杂程度。当某个技术问题出现时,他提供问题的解决方案,或者根据需要调整系统设计。用 Al Capp 所喜欢的一句谚语,他是“攻坚小组中的独行侠”(inside-man at the skunk works.)。 他的沟通交流在团队中是首要的。他的工作几乎完全是技术性的。

现在可以看到,这两种角色所需要的技能是非常不同的。这些技能可以按不同的方式 进行组合。产品负责人和技术主管所拥有的特殊技能可以用不同方式组合,组合结果控制和 支配了他们之间的关系。团队的搭建必须根据参与的人员来组织,而不是将人员纯粹地按照 - 45 - 理论进行安排。 存在三种可能的关系,它们都在实践中得到了成功的应用。

7.4.1 产品负责人和技术主管是同一个人

这种方式非常容易应用在很小型的队伍中,可能 是三个或六个开发人员。在大型的项目中则不容易得到应用。原因有两个:

  • 第一,同时具有 管理技能和技术技能的人很难找到。思考者很少,实干家更少,既是思考者又是实干家的太少了。
  • 第二,大型项目中,每个角色都必须全职工作,甚至还要加班。对负责人来说,很难在承担全部管理责任的同时,还能抽出时间进行技术工作。对技术主管来说,很难在保证设 计的概念完整性,没有任何妥协的前提下,担任管理工作。

7.4.2 产品负责人作为总指挥,技术主管充当其左右手

这种方法有一些困难。很难在技术 主管不参与任何管理工作的同时,建立在技术决策上的权威。 显然,产品负责人必须预先声明技术主管的技术权威,在即将出现的绝大部分测试用 例中,他必须支持后者的技术决定。要达到这一点,产品责任人和技术主管必须在基本的技 术理论上具有相似观点;他们必须在主要的技术问题出现之前,私下讨论它们;产品责任人必须对技术主管的技术才能表现出尊重。

这种组合可以使工作很有效。不幸的是它很少被应用。不过,它至少有一个好处,即 项目经理可以使用并不很擅长管理的技术天才来完成工作。

7.4.3 技术主管作为总指挥,产品负责人充当其左右手

这里引用了一个故事来说明此种应用情况,篇幅问题,不做摘录,书籍见:

豆瓣链接

Wikipedia

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


本章小结

第七章以了巴比伦塔的失败向读者介绍,即使拥有充足的技术、人力、时间等资源也是不够的。注意几个章节的描述内容可以总结出如何避免巴比伦塔式的失败:

  • 沟通。关于这一点,本章开始有一个非常有意思的故事没有做摘抄,来自圣经《创世纪》第11章第1-8段,这里我去查阅了一下,非常有阅读价值另外原文第九段是:因为上帝在那里变乱天下人的言语,使众人分散在全地上,所以那城名叫巴别(就是变乱的意思)。
  • 项目工作手册。手册或书面规格是不可或缺的工具,虽然光靠它是不够的。PS:这也是沟通的一部分
  • 组织。团队组织的目的是减少不必要交流和合作的数量,因此良好的团队组织是解决上述交流问题的关键措施。

图片来源:维基百科

10. 提纲挈领

技术、周边组织机构、行业传统等若干因素凑在一起,定义了项目必须准备的一些文书工作。对于一个刚从技术人员中任命的项目经理来说,这简直是一件彻头彻尾令人生厌的事情,而且是毫无必要和令人分心的,充满了被吞没的威胁。但是,在实际工作中,大多数情况都是这样的。

慢慢的,他逐渐认识到这些文档的某些部分包含和表达了一些管理方面的工作。每份文档的准备工作是集中考虑,并使各种讨论意见明朗化的主要时刻。如果不这样,项目往往会处于无休止的混乱状态。文档的跟踪维护是项目监督和预警的机制。文档本身可以作为检查列表、状态控制,也可以作为汇报的数据基础。

10.1 软件项目的文档

在许多软件项目中,开发人员从商讨结构的会议开始,然后开始书写代码。不论项目的规模如何小,项目经理聪明的做法都是:立刻正式生成若干文档作为自己的数据基础,哪怕这些迷你文档非常简单。接着,他会和其他管理人员一样要求各种文档。

  • 内容:目标。定义了待完成的目标、迫切需要的资源、约束和优先级。
  • 内容:产品技术说明。以建议书开始,以用户手册和内部文档结束。速度和空间说 明是关键的部分。
  • 时间:进度表
  • 资金:预算
  • 地点:工作空间分配
  • 人员:组织图。

10.2 为什么要有正式的文档?

首先,书面记录决策是必要的。只有记录下来,分歧才会明朗,矛盾才会突出。书写 这项活动需要上百次的细小决定,正是由于它们的存在,人们才能从令人迷惑的现象中得到 清晰、确定的策略。

第二,文档能够作为同其他人的沟通渠道。项目经理常常会不断发现,许多理应被普遍认同的策略,完全不为团队的一些成员所知。正因为项目经理的基本职责是使每个人都向着相同的方向前进,所以他的主要工作是沟通,而不是做出决定。这些文档能极大地减轻他 的负担。

最后,项目经理的文档可以作为数据基础和检查列表。通过周期性的回顾,他能清楚项目所处的状态,以及哪些需要重点进行更改和调整。

项目经理的任务是制订计划,并根据计划实现。但是只有书面计划是精确和可以沟通的。计划中包括了时间、地点、人物、做什么、资金。这些少量的关键文档封装了一些项目经理的工作。如果一开始就认识到它们的普遍性和重要性,那么就可以将文档作为工具友好地利用起来,而不会让它成为令人厌烦的繁重任务。通过遵循文档开展工作,项目经理能更清晰和快速地设定自己的方向。

13. 整体部分

和古老的神话里一样,现代神话里也总有一些爱吹嘘的人:“我可以编写控制航空货运、 拦截弹道导弹、管理银行账户、控制生产线的系统。”对这些人,回答很简单,“我也可以, 任何人都可以,但是其他人成功了吗?”

如何开发一个可以运行的系统?如何测试系统?如何将经过测试的一系列构件集成到 已测试过、可以依赖的系统?对这些问题,我们以前或多或少地提到了一些方法,现在就来更加系统地考虑一下。

13.1 剔除Bug的设计

13.1.1 防范 bug 的定义

系统各个组成部分的开发者都会做出一些假设,而这些假设之间的 不匹配,是大多数致命和难以察觉的 bug 的主要来源。第 4、5、6 章所讨论的获取概念完整性的途径,就是直接面对这些问题。简言之,产品的概念完整性在使它易于使用的同时,也使开发更容易进行以及 bug 更不容易产生。

上述方法所意味的详尽体系结构设计正是出于这个目的。Bell 实验室安全监控系统项 目的 V.A.Vyssotsky 提出,“关键的工作是产品定义。许许多多的失败完全源于那些产品未精确定义的地方。”细致的功能定义、详细的规格说明、规范化的功能描述说明以及这些方法的实施,大大减少了系统中必须查找的 bug 数量。

13.1.2 测试规格说明

在编写任何代码之前,规格说明必须提交给测试小组,以详细地检查说明的完整性和明确性。如同 Vyssotsky 所述,开发人员自己不会完成这项工作:“他们不会告诉你他们不懂。相反,他们乐于自己摸索出解决问题和澄清疑惑的办法。”

13.1.3 自顶向下的设计

在 1971 年的一篇论文中,Niklaus Wirth 把一种被很多最优秀的编程人员所使用的设计流程 形式化。他将程序开发划分成体系结构设计、设计实现和物理编码实现,每个步骤可以使用自顶向下的方法很好地实现。

简言之,Wirth 的流程将设计看成一系列精化步骤。开始是勾画出能得到主要结果的, 但比较粗略的任务定义和大概的解决方案。然后,对该定义和方案进行细致的检查,以判断结果与期望之间的差距。同时,将上述步骤的解决方案,在更细的步骤中进行分解,每一项任务定义的精化变成了解决方案中算法的精化,后者还可能伴随着数据表达方式的精化。 在这个过程中,当识别出解决方案或者数据的模块时,对这些模块的进一步细化可以 和其他的工作独立,而模块的大小程度决定了程序的适用性和可变化的程度。 Wirth 主张在每个步骤中,尽可能使用级别较高的表达方法来表现概念和隐藏细节,除非有必要进行进一步的细化。

13.1.4 结构化编程。

另外一系列减少 bug 数量的新方法很大程度上来自 Dijkstra 。Bohm 和Jacopini 的为其提供了理论证明 。 基本上,该方法所设计程序的控制结构,仅包含语句形式的循环结构,例如 DO WHILE, 以及 IF...THEN...ELSE 的条件判断结构,而具体的条件部分在 IF...THEN...ELSE 后的花括号中描述。

此外,关于完全避免 GO TO 语句的说法显得有些教条主义,而且似乎有些 吹毛求疵关键的地方和构建无 bug 程序的核心,是把系统的结构作为控制结构来考虑,而不是独立的跳转语句。这种思考方法是我们在程序设计发展史上向前迈出的一大步。

14. 祸起萧墙

当人们听到某个项目的进度发生了灾难性偏离时,可能会认为项目一定是遭受了一系列重大灾难。然而,通常灾祸来自白蚁的肆虐,而不是龙卷风的侵袭。同样,项目进度经常以一种难以察觉,但是残酷无情的方式慢慢落后。实际上,重大灾害是比较容易处理的,它往往和重大的压力、彻底的重组、新技术的出现有关,整个项目组通常可以应付自如。

但是一天一天的进度落后是难以识别、不容易防范和难以弥补的。昨天,某个关键人员生病了,无法召开某个会议。今天,由于雷击打坏了公司的供电变压器,所有机器无法启动。明天,因为工厂磁盘供货延迟了一周,磁盘例程的测试无法进行。下雪、应急任务、私 人问题、同顾客的紧急会议、管理人员检查——这个列表可以不断地延长。每件事都只会将某项活动延迟半天或者一天,但是整个进度开始落后了,尽管每次只有一点点。

14.1 里程碑还是沉重的负担?

如何根据一个严格的进度表来控制项目?第一个步骤是制订进度表。进度表上的每一件事,被称为“里程碑”,它们都有一个日期。选择日期是一个估计技术上的问题, 在前面已经讨论过,它在很大程度上依赖以往的经验。

里程碑的选择只有一个原则,那就是,里程碑必须是具体的、特定的、可度量的事件,能够进行清晰定义。以下是一些反面的例子,例如编码,在代码编写时间达到一半的时候就已经“90%完成”了;调试在大多时候都是“99%完成”的;“计划完毕”是任何人只要愿意,就可以声明的事件。

然而,具体的里程碑是百分之百的事件。“结构师和实现人员签字认可的规格说明”, “100%源代码编制完成,纸带打孔完成并输入到磁盘库”,“测试通过了所有的测试用例”。 这些切实的里程碑澄清了那些划分得比较模糊的阶段——计划、编码、调试。

里程碑有明显边界和没有歧义,比它容易被老板核实更为重要。如果里程碑定义得非常明确,以致于无法自欺欺人时,很少有人会就里程碑的进展弄虚作假。但是如果里程碑很模糊,老板就常常会得到一份与实际情况不符的报告。毕竟,没有人愿意承受坏消息。这种 做法只是为了起到缓和的作用,并没有任何蓄意的欺骗。

好的里程碑对团队来说实际上是一项服务,可以用来向项目经理提出合理要求的一项 服务,而不确切的里程碑是难以处理的负担。当里程碑没有正确反映损失的时间,并对人们形成误导,以致事态无法挽回的时候,它会彻底碾碎小组的士气。慢性进度偏离同样也是士 气杀手。

14.2 其他的部分反正会落后

进度落后了一天,那又怎么样呢?谁会关心一天的滞后?我们可以跟上进度。何况, 和我们有关的其他部分已经落后了。

棒球队队长知道,进取这种心理素质,是很多优秀队员和团队不可缺少的。它表现为 “要求跑得更快”,“要求移动得更加迅速”,“更加努力尝试”。对软件开发队伍,进取同样是非常必要的。进取提供了缓冲和储备,使开发队伍能够处理常规的异常事件,可以预计和防止小的灾祸。而对任务进行计算和对工作量进行度量,会对进取超前会造成一些消极的影响——这时,人们往往会比较乐观地放缓工作节奏。就这一点来说,它们是令人扫兴的事情。 不过,如同我们看到的,必须关心每一天的滞后,它们是大灾祸的基本组成元素

**并不是每一天的滞后都等于灾难。尽管会如上文所述,事先估计会给工作进度的超前带来影响,但对活动的一些计算和考虑还是必要的。那么,如何判断哪些偏离是关键的呢? 只有采用 PERT 或者关键路径技术才能判断。它显示谁需要什么样的东西,谁位于关键路径 上,他的工作滞后会影响最终的完成日期。另外,它还指出一个任务在成为关键路径时,可以落后的时间。

Photo by Matthias Groeneveld from Pexels


找不到合适的图片来表达千里之堤,溃于蚁穴的感觉。


15. 另外一面(The Other Face)

计算机程序是从人传递到机器的一些信息。为了将人的意图清晰地传达给不会说话的机器,程序采用了严格的语法和严谨的定义。

但是书面的计算机程序还有其他的呈现面貌:向用户诉说自己的“故事”。即使是完全开发给自己使用的程序,这种沟通仍然是必要的。因为记忆衰退的规律会使用户-作者失去对程序的了解,于是他不得不重拾自己劳动的各个细节。

公共应用程序的用户在时间和空间上都远离它们的作者,因此对这类程序,文档的重要性更是不言而喻!对软件编程产品来说,程序向用户所呈现的面貌和提供给机器识别的内容同样重要。

面对那些文档“简约”的程序,我们中的大多数人都不免曾经暗骂那些远在他方的匿 名作者。因此,一些人试图向新人慢慢地灌输文档的重要性:旨在延长软件的生命期、克服惰性和进度的压力。但是,很多次尝试都失败了,我想很可能是由于我们使用了错误的方法。

15.1 需要什么样的文档

不同用户需要不同级别的文档。某些用户仅仅偶尔使用程序,有些用户必须依赖程序, 还有一些用户必须根据环境和目的的变动对程序进行修改。

15.1.1 使用程序

每个用户都需要一段对程序进行描述的文字。可是大多数文档只提供了很 少的总结性内容,无法达到用户要求,就像是描绘了树木,形容了树叶,但却没有一副森林 的图案。为了得到一份有用的文字描述,就必须放慢脚步,稳妥地进行。

  1. 目的。主要的功能是什么?开发程序的原因是什么?
  2. 环境。程序运行在什么样的机器、硬件配置和操作系统上?
  3. 范围。输入的有效范围是什么?允许显示的合法范围是什么?
  4. 实现功能和使用的算法。精确地阐述它做了什么。
  5. 输入-输出格式。必须是确切和完整的。
  6. 操作指令。包括控制台及输出内容中正常和异常结束的行为。
  7. 选项。用户的功能选项有哪些?如何在选项之间进行挑选?
  8. 运行时间。在指定的配置下,解决特定规模问题所需要的时间?
  9. 精度和校验。期望结果的精确程度?如何进行精度的检测?

一般来说,三、四页纸常常就可以容纳以上所有的信息。不过往往需要特别注意的是表达的简洁和精确。由于它包含了和软件相关的基本决策,所以这份文档的绝大部分需要在 程序编制之前书写。

15.1.2 验证程序

除了程序的使用方法,还必须附带一些程序正确运行的证明,即测试用例。 每一份发布的程序拷贝应该包括一些可以例行运行的小测试用例,为用户提供信心——他拥有了一份可信赖的拷贝,并且正确地安装到了机器上。

然后,需要得到更加全面的测试用例,在程序修改之后,进行常规运行。这些用例可 以根据输入数据的范围划分成三个部分。

  1. 针对遇到的大多数常规数据和程序主要功能进行测试的用例。它们是测试用例的主 要组成部分。
  2. 数量相对较少的合法数据测试用例,对输入数据范围边界进行检查,确保最大可能 值、最小可能值和其他有效特殊数据可以正常工作。
  3. 数量相对较少的非法数据测试用例,在边界外检查数据范围边界,确保无效的输入 能有正确的数据诊断提示。

15.1.3 修改程序

调整程序或者修复程序需要更多的信息。显然,这要求了解全部的细节, 并且这些细节已经记录在注释良好的列表中。和一般用户一样,修改者迫切需要一份清晰明 了的概述,不过这一次是关于系统的内部结构。那么这份概述的组成部分是什么呢?

  1. 流程图或子系统的结构图,对此以下有更详细的论述。
  2. 对所用算法的完整描述,或者是对文档中类似描述的引用。
  3. 对所有文件规划的解释。
  4. 数据流的概要描述——从磁盘或者磁带中,获取数据或程序处理的序列——以及在 每个处理过程完成的操作。
  5. 初始设计中,对已预见修改的讨论;特性、功能回调的位置以及出口;原作者对可能会扩充的地方以及可能处理方案的一些意见。另外,对隐藏缺陷的观察也同样很有价值。

16. 没有银弹-软件工程中的根本和次要问题

所有软件活动包括根本任务——打造由抽象软件实体构成的复杂概念结构,次要任务 ——使用编程语言表达这些抽象实体,在空间和时间限制内将它们映射成机器语言。软件生 产率在近年内取得的巨大进步来自对后天障碍的突破,例如硬件的限制、笨拙的编程语言、 机器时间的缺乏等等。这些障碍使次要任务实施起来异常艰难,相对必要任务而言,软件工程师在次要任务上花费了多少时间和精力?除非它占了所有工作的 9/10,否则即使全部次 要任务的时间缩减到零,也不会给生产率带来数量级上的提高。

因此,现在是关注软件任务中的必要活动的时候了,也就是那些和构造异常复杂的抽象概念结构有关的部分。我建议:

  • 仔细地进行市场调研,避免开发已上市的产品。
  • 在获取和制订软件需求时,将快速原型开发作为迭代计划的一部分。
  • 有机地更新软件,随着系统的运行、使用和测试,逐渐添加越来越多的功能。
  • 不断挑选和培养杰出的概念设计人员。

16.1 介绍

在所有恐怖民间传说的妖怪中,最可怕的是人狼,因为它们可以完全出乎意料地从熟悉的面孔变成可怕的怪物。为了对付人狼,我们在寻找可以消灭它们的银弹。

大家熟悉的软件项目具有一些人狼的特性(至少在非技术经理看来),常常看似简单明了的东西,却有可能变成一个落后进度、超出预算、存在大量缺陷的怪物。因此,我们听到了近乎绝望的寻求银弹的呼唤,寻求一种可以使软件成本像计算机硬件成本一样降低的尚方宝剑。

但是,我们看看近十年来的情况,没有银弹的踪迹。没有任何技术或管理上的进展, 能够独立地许诺在生产率、可靠性或简洁性上取得数量级的提高。本章中,我们试图通过分析软件问题的本质和很多候选银弹的特征,来探索其原因。

不过,怀疑论者并不是悲观主义者。尽管我们没有看见令人惊异的突破,并认为这种银弹实际上是与软件的内在特性相悖,不过还是出现了一些令人振奋的革新。这些方法的规 范化、持续地开拓、发展和传播确实是可以在将来使生产率产生数量级上的提高。虽然没有通天大道,但是路就在脚下。

解决管理灾难的第一步是将大块的“巨无霸理论”替换成“微生物理论”,它的每一步——希望的诞生,本身就是对一蹴而就型解决方案的冲击。它告诉工作者进步是逐步取得的, 伴随着辛勤的劳动,对规范化过程应进行持续不懈的努力。由此,诞生了现在的软件工程。

16.2 根本困难

不仅仅是在目力所及的范围内,没有发现银弹,而且软件的特性本身也导致了不大可 能有任何的发明创新——能够像计算机硬件工业中的微电子器件、晶体管、大规模集成一样 ——提高软件的生产率、可靠性和简洁程度。我们甚至不能期望每两年有一倍的增长。

首先,我们必须看到这样的畸形并不是由于软件发展得太慢,而是因为计算机硬件发 展得太快。从人类文明开始,没有任何其他产业技术的性价比,能在 30 年之内取得 6 个数 量级的提高,也没有任何一个产业可以在性能提高或者降低成本方面取得如此的进步。这些进步来自计算机制造产业的转变,从装配工业转变成流水线工业。

我认为软件开发中困难的部分是规格化、设计和测试这些概念上的结构,而不是对概念进行表达和对实现逼真程度进行验证。当然,我们还是会犯一些语法错误,但是和绝大多 数系统中的概念错误相比,它们是微不足道的。

如果这是事实,那么软件开发总是非常困难的。天生就没有银弹。

让我们来考虑现代软件系统中这些无法规避的内在特性:复杂度、一致性、可变性和不可见性

16.2.1 复杂度

规模上,软件实体可能比任何由人类创造的其他实体要复杂,因为没有任何 两个软件部分是相同的(至少是在语句的级别)。如果有相同的情况,我们会把它们合并成 供调用的子函数。在这个方面,软件系统与计算机、建筑或者汽车大不相同,后者往往存在 着大量重复的部分。

同样,软件实体的扩展也不仅仅是相同元素重复添加,而必须是不同元素实体的添加。 大多数情况下,这些元素以非线性递增的方式交互,因此整个软件的复杂度以更大的非线性 级数增长。 软件的复杂度是必要属性,不是次要因素。因此,抽掉复杂度的软件实体描述常常也 去掉了一些本质属性。

由于复杂度,团队成员之间的沟通非常困难,导致了产品瑕疵、成本超支和进度延迟;由于复杂度,列举和理解所有可能的状态十分困难,影响了产品的可靠性;由于函数的复杂度,函数调用变得困难, 导致程序难以使用;由于结构性复杂度,程序难以在不产生副作用的情况下用新函数扩充; 由于结构性复杂度,造成很多安全机制状态上的不可见性。

复杂度不仅仅导致技术上的困难,还引发了很多管理上的问题。它使全面理解问题变得困难,从而妨碍了概念上的完整性;它使所有离散出口难以寻找和控制;它引起了大量学习和理解上的负担,使开发慢慢演变成了一场灾难。

16.2.2 一致性

并不是只有软件工程师才面对复杂问题。物理学家甚至在非常“基础”的级 别上,面对异常复杂的事物。不过,物理学家坚信必定存在着某种通用原理,或者在夸克中, 或者在统一场论中。爱因斯坦曾不断地重申自然界一定存在着简化的解释,因为上帝不是专 横武断或反复无常的。

软件工程师却无法从类似的信念中获得安慰,他必须控制的很多复杂度是随心所欲、 毫无规则可言的,来自若干必须遵循的人为惯例和系统。它们随接口的不同而改变,随时间的推移而变化,而且,这些变化不是必需的,仅仅由于它们是不同的人,而非上帝——设计的结果。 某些情况下,因为是开发最新的软件,所以它必须遵循各种接口。另一些情况下,软 件的开发目标就是兼容性。在上述的所有情况中,很多复杂性来自保持与其他接口的一致, 对软件的任何再设计,都无法简化这些复杂特性。

16.2.3 可变性

软件实体经常会遭受到持续的变更压力。当然,建筑、汽车、计算机也是如 此。不过,工业制造的产品在出厂之后不会经常地发生修改,它们会被后续模型所取代,或者必要更改会被整合到具有相同基本设计的后续产品系列。汽车的更改十分罕见,计算机的现场调整时有发生。然而,它们和软件的现场修改比起来,都要少很多。

其中部分的原因是因为系统中的软件包含了很多功能,而功能是最容易感受变更压力的部分。另外的原因是因为软件可以很容易地进行修改——它是纯粹思维活动的产物,可以无限扩展。日常生活中,建筑有可能发生变化,但众所周知,建筑修改的成本很高,从而打 消了那些想提出修改的人的念头。

简言之,软件产品扎根于文化的母体中,如各种应用、用户、自然及社会规律、计算 机硬件等等。后者持续不断地变化着,这些变化无情地强迫着软件随之变化。

16.2.4 不可见性

软件是不可见的和无法可视化的。例如,几何抽象是强大的工具。建筑平 面图能帮助建筑师和客户一起评估空间布局、进出的运输流量和各个角度的视觉效果。这样, 矛盾变得突出,忽略的地方变得明显。同样,机械制图、化学分子模型尽管是抽象模型,但都起了相同的作用。总之,都可以通过几何抽象来捕获物理存在的几何特性。

软件的客观存在不具有空间的形体特征。因此,没有已有的表达方式,就像陆地海洋 有地图、硅片有膜片图、计算机有电路图一样。当我们试图用图形来描述软件结构时,我们 发现它不仅仅包含一个,而是很多相互关联、重叠在一起的图形。这些图形可能描绘控制流 程、数据流、依赖关系、时间序列、名字空间的相互关系等等。它们通常不是有较少层次的 扁平结构。实际上,在上述结构上建立概念控制的一种方法是强制将关联分割,直到可以层次化一个或多个图形。

除去软件结构上的限制和简化方面的进展,软件仍然保持着无法可视化的固有特性, 从而剥夺了一些具有强大功能的概念工具的构造思路。这种缺憾不仅限制了个人的设计过 程,也严重地阻碍了相互之间的交流。

读后感

从17章到后面大多是一些对于书籍内容的议论,这里没有摘抄。

全书细读几遍依然回味无穷,久久不能平息,再随手翻到前面看一看。。。

前面几章的内容在摘抄的时候我还想每章之后做一些总结和感想,但是越往后读越沉浸在书中描述的场景中,不由得把书中的场景代入到我目前的工作中,好像我现在面对的一切困难、烦恼都是不那么重要了。相对的,我更希望默默的读下去,细细体会这其中的思想与带给我的启发。

原本打算写在这里的读后感,等多经历一点事情再写吧,生怕现在不够高的视角写下了错误、片面的评论。

posted @ 2021-06-23 21:04  夜色微光  阅读(2496)  评论(1编辑  收藏  举报