人月神话 读书笔记 03

发布时间 2023-05-19 20:01:35作者: 一个小虎牙

第9章 削足适履
9.1 程序有多大?除了运行时间以外,它所占据的空间也是主要开销。

当系统设计者认为对用户而言,常驻程序内存的形式比加法器、磁盘等更加有用时,他会将硬件实现中的一部分移到内存上。相反的,其他的做法是非常不负责任的。

由于规模是软件系统产品用户成本中如此大的一个组成部分,开发人员必须设置规模的目标,控制规模,考虑减小规模的方法,就像硬件开发人员会设立元器件数量目标,控制元器件的数量,想出一些减少零件的方法。同任何开销一样,规模本身不是坏事,但不必要的规模是不可取的。

9.2 对项目经理而言,规模控制既是技术工作的一部分,也是管理工作的一部分。

他必须研究用户和他们的应用,以设置将开发系统的规模。接着,把这些系统划分成若干部分,并设定每个部分的规模目标。

首先,仅对核心程序设定规模目标是不够的,必须把所有的方面都编入预算。

在为每个单元设立核心规模的同时,我们没有同时设置访问的目标。

项目规模本身很大,缺乏管理和沟通,以至于每个团队成员认为自己是争取小红花的学生,而不是构建系统软件产品的人员。为了满足目标,每个人都在局部优化自己的程序,很少会有人停下来,考虑一下对客户的整体影响。

9.3 空间预算的多少和控制并不能使程序规模减小,为实现这一目标,它还需要一些创造性和技能。

其中的一个技巧是用功能交换尺寸:

在内存大小一定的情况下进行系统设计时,会出现另外一个基本问题。内存受限的后果是即使最小的功能模块,它的适用范围也难以得到推广。临时空间的尺寸,以及每次磁盘访问的程序数量是很关键的决策,因为性能是规模的非线性函数。

第二个技能是考虑空间——时间的折衷:

对于给定的功能,空间越多,速度越快。这一点在很大的范围内都适用。也正是这一点使空间预算成为可能。

项目经理可以做两件事来帮助他的团队取得良好的空间——时间折衷。一是确保他们在编程技能上得到培训,而不仅仅是依赖他们自己掌握的知识和先前的经验。另外一种方法是认识到编程需要技术积累,需要开发很多公共单元构件库。库中的每个组件需要有两个版本,运行速度较快和短小精炼的。

9.4 精炼、充分和快速的程序,往往是战略性突破的结果,而不仅仅技巧上的提高。这种突破常常是一种新型算法。

更普遍的是,战略上突破常来自于数据或表的重新表达。实际上,数据的表现形式是编程的根本。

第10章 提纲挈领
在一片文件的汪洋中,少数文档形成了关键的枢纽,每件项目管理的工作都围绕着它们运转。它们是经理们的主要个人工具。

10.1 计算机产品的文档

目标:

定义待满足的目标和需要,定义迫切需要的资源、约束和优先级。

技术说明:

计算机手册和性能规格说明。它是在计划新产品时第一个产生,并且最后完成的文档。

进度、时间表:

预算:预算不仅仅是约束。对管理人员来说,它还是最有用的文档之一。预算的存在会迫使技术决策的制订,否则,技术决策很容易被忽略。更重要的是,它促使和澄清了策略上的一些决定。

组织机构图:

报价、预测、价格:这三个因素互相牵制,决定了项目的成败。

 

10.3 软件项目的文档

做什么:目标。定义了待完成的目标、迫切需要的资源、约束和优先级。

做什么:产品技术说明。以建议书开始,以用户手册和内部文档结束。速度和空间说明是关键的部分。

时间:进度表

资金:预算

地点:工作空间分配

人员:组织图。它与接口说明是相互依存的,如果系统设计能自由地变化,则项目组织架构必须为变化做准备。

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

首先,书面记录决策是必要的。

第二,文档能够作为同其他人的沟通渠道。

最后,项目经理的文档可以作为数据基础和检查列表。

只有一小部分管理人员的时间——可能只有20%——用来从自己头脑外部获取信息。其他的工作是沟通:倾听、报告、讲授、规劝、讨论、鼓励。不过,对于基于数据的部分,少数关键的文档是至关重要的,它们可以满足绝大多数需要。

项目经理的任务是制订计划,并根据计划实现。但是只有书面计划是精确和可以沟通的。计划中包括了时间、地点、人物、做什么、资金。这些少量的关键文档封装了一些项目经理的工作。

第11章 未雨绸缪
不变只是愿望,变化才是永恒。——SWIFT。

普遍的做法是,选择一种方法,试试看;如果失败了,没关系,再试试别的。不管怎么样,重要的是先去尝试。——富兰克林 D. 罗斯福。

11.1 化学工程师很早就认识到,在实验室可以进行的反应过程,并不能在工厂中一步实现。一个被称为“实验性工厂(pilot planet)”的中间步骤是非常必要的。软件系统的构建人员也面临类似的问题。

对于大多数项目,第一个开发的系统并不合用。要解决所有的问题,除了重新开始以外,没有其他的办法——即开发一个更灵巧或者更好的系统。系统的丢弃和重新设计可以一步完成,也可以一块块地实现。所有大型系统的经验都显示,这是必须完成的步骤。

因此,管理上的问题不再是“是否构建一个试验性的系统,然后抛弃它?”。现在的问题是“是否预先计划抛弃原型的开发,或者是否将该原型发布给用户?”。将原型发布给用户,可以获得时间,但是它的代价高昂——对于用户,使用极度痛苦;对于重新开发的人员,分散了精力;对于产品,影响了声誉,即使最好的再设计也难以挽回名声。因此,为舍弃而计划,无论如何,你一定要这样做。

11.2 一旦认识到试验性的系统必须被构建和丢弃,具有变更思想的重新设计不可避免,从而直面整个变化现象是非常有用的。

我从不建议顾客目标和需求的所有变更必须、能够、或者应该整合到设计中。项目开始时建立的基准,肯定会随着开发的进行越来越高,甚至开发不出任何产品。抛弃原型概念本身就是对事实的接受——随着学习的过程更改设计。

11.3 如何为上述变化设计系统,是个非常著名的问题。它们包括细致的模块化、可扩展的函数、精确完整的模块间接口设计、完备的文档。另外,还可能会采用包括调用队列和表驱动的一些技术。

最重要的措施是使用高级语言和自文档技术,以减少变更引起的错误。采用编译时的操作来整合标准声明,在很大程度上帮助了变化的调整。

变更的阶段化是一种必要的技术。每个产品都应该有数字版本号,每个版本都应该有自己的日程表和冻结日期,在此之后的变更属于下一个版本的范畴。

程序员不愿意为设计书写文档的原因,不仅仅是由于惰性。更多的是源于设计人员的踌躇——要为自己尝试性的设计决策进行辩解。

11.4 当系统发生变化时,管理结构也需要进行调整。这意味着,只要管理人员和技术人才的天赋允许,老板必须对他们的能力培养给予极大的关注,使管理人员和技术人才具有互换性。

这其中的障碍是社会性的,人们必须同顽固的戒心做斗争。首先,管理人员自己常常认为高级人员太“有价值”,而舍不得让他们从事实际的编程工作;其次,管理人员拥有更高的威信。为了克服这个问题,如Bell Labs的一些实验室,废除了所有的职位头衔。每个专业人士都是“技术人员中的一员”。而IBM的另外一些实验室,保持了两条职位晋升线,

管理人员需要参与技术课程,高级技术人才需要进行管理培训。项目目标、进展、管理问题必须在高级人员整体中得到共享。只要能力允许,高层人员必须时刻做好技术和情感上的准备,以管理团队或者亲自参与开发工作。这是件工作量很大的任务,但显然很值得!

组建外科手术队伍式的软件开发团队,这整个观念是对上述问题的彻底冲击。其结果是当高级人才编程和开发时,不会感到自降身份。这种方法试图清除那些会剥夺创造性乐趣的社会障碍。

上述组织架构的设计是为了最小化成员间的接口。同样的,它使系统在最大程度上易于修改。当组织构架必须变化时,为整个“外科手术队伍”重新安排不同的软件开发任务,会变得相对容易一些。

11.5 在程序发布给顾客使用之后,它不会停止变化。发布后的变更被称为“程序维护”,但是软件的维护过程不同于硬件维护。

计算机系统的硬件维护包括了三项活动——替换损坏的器件、清洁和润滑、修改设计上的缺陷。

软件维护不包括清洁、润滑和对损坏器件的修复。它主要包含对设计缺陷的修复。对于一个广泛使用的程序,其维护总成本通常是开发成本的40%或更多。令人吃惊的是,该成本受用户数目的严重影响。用户越多,所发现的错误也越多。

程序维护中的一个基本问题是——缺陷修复总会以(20-50)%的机率引入新的bug。所以整个过程是前进两步,后退一步。

为什么缺陷不能更彻底地被修复?

首先,看上去很轻微的错误,似乎仅仅是局部操作上的失败,实际上却是系统级别的问题。修复局部问题的工作量很清晰,并且往往不大。但是,更大范围的修复工作常常会被忽视,除非软件结构很简单,或者文档书写得非常详细

其次,维护人员常常不是编写代码的开发人员,而是一些初级程序员或者新手。作为引入新bug的一个后果,程序每条语句的维护需要的系统测试比其他编程要多。理论上,在每次修复之后,必须重新运行先前所有的测试用例,从而确保系统不会以更隐蔽的方式被破坏,所以它的成本非常高。

11.6 Lehman和Belady研究了大型操作系统的一系列发布版本的历史。他们发现模块数量随版本号的增加呈线性增长,但是受到影响的模块以版本号指数的级别增长。所有修改都倾向于破坏系统的架构,增加了系统的混乱程度。用在修复原有设计上瑕疵的工作量越来越少,而早期维护活动本身的漏洞所引起修复工作越来越多。随着时间的推移,系统变得越来越无序,修复工作迟早会失去根基。每一步前进都伴随着一步后退。

实际上,整个系统已经面目全非,无法再成为下一步进展的基础。而且,机器在变化,配置在变化,用户的需求在变化,所以现实系统不可能永远可用。崭新的、基于原有系统的重新设计是完全必要的。

系统软件开发是减少混乱度(减少熵)的过程,所以它本身是处于亚稳态的。软件维护是提高混乱度(增加熵)的过程,即使是最熟练的软件维护工作,也只是放缓了系统退化到非稳态的进程。

第12章 干将莫邪
就工具而言,即使是现在,很多软件项目仍然像一家五金店。每个骨干人员都仔细地保管自己工作生涯中搜集的一套工具集,这些工具成为个人技能的直观证明。正是如此,每个编程人员也保留着编辑器、排序、内存信息转储、磁盘实用程序等工具。

这种方法对软件项目来说是愚蠢的:

首先,项目的关键问题是沟通,个性化的工具妨碍——而不是促进沟通。

其次,当机器和语言发生变化时,技术也会随之变化,所有工具的生命周期是很短的。毫无疑问,开发和维护公共的通用编程工具的效率更高。

不过,仅有通用工具是不够的。专业需要和个人偏好同样需要很多专业工具。所以在前面关于软件开发队伍的讨论中,我建议为每个团队配备一名工具管理人员。这个角色管理所有通用工具,能指导他的客户——老板使用工具。同时,他还能编制老板需要的专业工具。

项目经理必须考虑、计划、组织的工具到底有哪些呢?

首先是计算机设施。它需要硬件和使用安排策略;它需要操作系统,提供服务的方式必须明了;它需要语言,语言的使用方针必须明确;然后是实用程序、调试辅助程序、测试用例生成工具和处理文档的字处理系统。

12.1 机器支持可以有效地划分成目标机器和辅助机器。目标机器是软件所服务的对象,程序必须在该机器上进行最后测试。辅助机器是那些在开发系统中提供服务的机器。

目标机器的类型有哪些?

团队开发的监督程序或其他系统核心软件当然需要它们自己的机器。目标机器系统会需要若干操作员和一两个系统编程人员,以保证机器上的标准支持是即时更新和实时可用的。

另外,还需要配备调试机器或者软件。这样,在调试过程中,所有类型的程序参数可以被自动计数和测量。

计划安排:

目标机器时间需求具有特别的增长曲线。当目标机器刚刚被研制,或者当它的第一个操作系统被开发时,机器时间是非常匮乏的,时间的调度安排成了主要问题。

我们开始把机器时间分配成连续的块。例如,整个从事排序工作的15人小组,会得到系统4至6小时的使用时间块,由他们自己决定如何使用。即使没有安排,其他人也不能使用机器资源。这种方式,是一种更好的分配和安排方法。尽管机器的利用程度可能会有些降低(常常不是这样),生产率却提高了。

12.2 仿真装置。如果目标机器是新产品,则需要一个目标机器的逻辑仿真装置。这样,在生产出新机器之前,就有辅助的调试平台可供使用。即使在新机器出现之后,仿真装置仍然可以提供可靠的调试平台。

可靠并不等于精确。在某些方面,仿真机器肯定无法精确地达到与新型机器一致的实现。但是至少在一段时间内,它的实现是稳定的,新硬件就不会。

实验室研制和试制的模型产品和早期硬件不会像定义的那样运行,不会稳定工作,甚至每天都不会一样。。所以,一套运行在稳定平台上的可靠仿真装置,提供了远大于我们所期望的功用。

编译器和汇编平台。出于同样的原因,编译器和汇编软件需要运行在可靠的辅助平台上,为目标机器编译目标代码。接着,可以在仿真器上立刻开始后续的调试。

高级语言的编程开发中,在目标机器上开始全面测试目标代码之前,编译器可以在辅助机器上完成很多目标代码的调试和测试工作。

程序库和管理。在OS/360开发中,一个非常成功的重要辅助机器应用是维护程序库。

这个库实际上划分成不同访问规则下的子库。

首先,每个组或者编程人员分配了一个区域,用来存放他的程序拷贝、测试用例以及单元测试需要的测试辅助例程和数据。在这个开发库(playpen)中,不存在任何限制开发人员的规定。他可以自由处置自己的程序,他是它们的拥有者。

当开发人员准备将软件单元集成到更大的部分时,他向集成经理提交一份拷贝,后者将拷贝放置在系统集成子库中。此时,原作者不可以再改变代码,除非得到了集成经理的批准。当系统合并在一起时,集成经理开始进行所有的系统测试工作,识别和修补bug。

有时,系统的一个版本可能会被广泛应用,它被提升到当前版本子库。此时,这个拷贝是不可更改的,除非有重大缺陷。该版本可以用于所有新模块的集成和测试。

这有两个重要的理念。首先是受控,即程序的拷贝属于经理,他可以独立地授权程序的变更。其次是使发布的进展变得正式,以及开发库(playpen)与集成、发布的正式分离。

编程工具。随着调试技术的出现,旧方法的使用减少了,但并没有消失。因此,还是需要内存转储、源文件编辑、快照转储、甚至跟踪等工具。

文档系统。在所有的工具中,最能节省劳动力的,可能是运行在可靠平台上的、计算机化的文本编辑系统。

性能仿真装置。最好有一个。正如我们将在下章讨论到的,彻底地开发一个。使用相同的自顶向下设计方法,来实现性能仿真器、逻辑仿真装置和产品。

11.3 高级语言。使用高级语言的主要原因是生产率和调试速度。我们在前面已讨论过生产率的问题(第8章)。调试上的改进来自下列事实——存在更少的bug,而且更容易查找。

bug更少的原因,是因为它避免在错误面前暴露所有级别的工作,这样不但会造成语法上的错误,还会产生语义上的问题,如不当使用寄存器等。编译器的诊断机制可以帮助找出这些类似的错误,更重要的是,它非常容易插入调试的快照。

系统编程需要什么样的高级语言呢?

现在可供合理选择的语言是PL/I6。它提供完整的功能集;它与操作系统环境相吻合;它有各种各样的编译器,一些是交互式的,一些速度很快,一些诊断性很好,另一些能产生优化程度很高的代码。我自己觉得使用APL来解决算法更快一些,然后,将它们翻译成某个系统环境下的PL/I语言。

交互式编程。在那些系统编程所关注的方面,Multics(以及后续系统,IBM的TSS)和其他交互式计算机系统在概念上有很大的不同:多个级别上数据和程序的共享和保护,可延伸的库管理,以及协助终端用户共同开发的设施。