对于大多数项目,第一个开发的系统并不合用。它可能太慢、太大,而且难以使用,或者三者兼而有之。要解决所有的问题,除了重新开始以外,没有其他的办法——即开发一- 64 ------------------------ Page 77-----------------------个更灵巧或者更好的系统。系统的丢弃和重新设计可以一步完成,也可以一块块地实现。所有大型系统的经验都显示,这是必须完成的步骤2。而且,新的系统概念或新技术会不断出现,所以开发的系统必须被抛弃,但即使是最优秀的项目经理,也不能无所不知地在最开始解决这些问题。因此,管理上的问题不再是“是否构建一个试验性的系统,然后抛弃它?”你必须这样做。现在的问题是“是否预先计划抛弃原型的开发,或者是否将该原型发布给用户?”从这个角度看待问题,答案更加清晰。将原型发布给用户,可以获得时间,但是它的代价高昂——对于用户,使用极度痛苦;对于重新开发的人员,分散了精力;对于产品,影响了声誉,即使最好的再设计也难以挽回名声。因此,为舍弃而计划,无论如何,你一定要这样做。唯一不变的就是变化本身一旦认识到试验性的系统必须被构建和丢弃,具有变更思想的重新设计不可避免,从而直面整个变化现象是非常有用的。第一步是接受这样的事实:变化是与生俱来的,不是不合时宜和令人生厌的异常情况。Cosgrove 很有洞察力地指出,开发人员交付的是用户满意程度,而不仅仅是实际的产品。用户的实际需要和用户感觉会随着程序的构建、测试和使用3而变化 。当然对于硬件产品而言,同样需要满足要求,例如新型汽车或者计算机。但物体的客观存在容纳和阶段化(量子化)了用户对变更的要求。软件产品易于掌握的特性和不可见性,导致它的构建人员面临永恒的需求变更。我从不建议顾客目标和需求的所有变更必须、能够、或者应该整合到设计中。项目开始时建立的基准,肯定会随着开发的进行越来越高,甚至开发不出任何产品。然而,目标上的一些变化无可避免,事先为它们做准备总比假设它们不会出现要好得多。不但目标上的变化不可避免,而且设计策略和技术上的变化也不可避免。抛弃原型概念4本身就是对事实的接受——随着学习的过程更改设计 。- 65 ------------------------ Page 78-----------------------为变更计划系统如何为上述变化设计系统,是个非常著名的问题,在书本上被普遍讨论——可能讨论得比实践还要多得多。它们包括细致的模块化、可扩展的函数、精确完整的模块间接口设计、完备的文档。另外,还可能会采用包括调用队列和表驱动的一些技术。最重要的措施是使用高级语言和自文档技术,以减少变更引起的错误。采用编译时的操作来整合标准声明,在很大程度上帮助了变化的调整。变更的阶段化是一种必要的技术。每个产品都应该有数字版本号,每个版本都应该有自己的日程表和冻结日期,在此之后的变更属于下一个版本的范畴。为变更计划组织架构Cosgrove 主张把所有计划、里程碑、日程安排都当作是尝试性的,以方便进行变化。这似乎有些走极端——现在软件编程小组失败的主要原因是管理控制得太少,而不是太多。不过,他提出了一种卓越的见解。他观察到不愿意为设计书写文档的原因,不仅仅是由于惰性或者时间压力。相反,设计人员通常不愿意提交尝试性的设计决策,再为它们进行辩解。“通过设计文档化,设计人员将自己暴露在每个人的批评之下,他必须能够为他的每个结果进行辩护。如果团队架构因此受到任何形式的威胁,则没有任何东西会被文档化,除非架构是完全受到保护的。为变更组建团队比为变更进行设计更加困难。每个人被分派的工作必须是多样的、富有拓展性的工作,从技术角度而言,整个团队可以灵活地安排。在大型的项目中,项目经理需要有两个和三个顶级程序员作为技术轻骑兵,当工作繁忙最密集的时候,他们能急驰飞奔,解决各种问题。当系统发生变化时,管理结构也需要进行调整。这意味着,只要管理人员和技术人才的天赋允许,老板必须对他们的能力培养给予极大的关注,使管理人员和技术人才具有互换性。这其中的障碍是社会性的,人们必须同顽固的戒心做斗争。首先,管理人员自己常常认为高级人员太“有价值”,而舍不得让他们从事实际的编程工作;其次,管理人员拥有更- 66 ------------------------ Page 79-----------------------高的威信。为了克服这个问题,如Bell Labs 的一些实验室,废除了所有的职位头衔。每个专业人士都是“技术人员中的一员”。而IBM 的另外一些实验室,保持了两条职位晋升线,如图11.1所示。相应的级别在概念上是相同的。管理线 技术线高级程序员 高级程序员开发程序员 顾问程序员项目程序员 程序职员高级准程序员图11.1:IBM 的两条职位晋升线很容易为上述层次建立相互一致的薪水级别。但要建立一致的威信,会困难一些。比如,办公室的大小和布局应该相同。秘书和其他支持也必须相同。从技术线向管理同级调动时,不能伴随着待遇的提升,而且应该以“调动”,而不是“晋升”的名义。相反的调整则应该伴随着待遇的提高,对于传统意识进行补偿是必要的。管理人员需要参与技术课程,高级技术人才需要进行管理培训。项目目标、进展、管理问题必须在高级人员整体中得到共享。只要能力允许,高层人员必须时刻做好技术和情感上的准备,以管理团队或者亲自参与开发工作。这是件工作量很大的任务,但显然很值得!组建外科手术队伍式的软件开发团队,这整个观念是对上述问题的彻底冲击。其结果是当高级人才编程和开发时,不会感到自降身份。这种方法试图清除那些会剥夺创造性乐趣的社会障碍。另外,上述组织架构的设计是为了最小化成员间的接口。同样的,它使系统在最大程度上易于修改。当组织构架必须变化时,为整个“外科手术队伍”重新安排不同的软件开发任务,会变得相对容易一些。这的确是一个长期有效的灵活组织构架解决方案。- 67 ------------------------ Page 80-----------------------前进两步,后退一步在程序发布给顾客使用之后,它不会停止变化。发布后的变更被称为“程序维护”,但是软件的维护过程不同于硬件维护。计算机系统的硬件维护包括了三项活动——替换损坏的器件、清洁和润滑、修改设计上的缺陷。(大多数情况下——但不是全部——变更修复的是实现上、而不是结构上的一些缺陷。对于用户而言,这常常是不可见的。)软件维护不包括清洁、润滑和对损坏器件的修复。它主要包含对设计缺陷的修复。和硬件维护相比,这些软件变更包含了更多的新增功能,它通常是用户能察觉的。对于一个广泛使用的程序,其维护总成本通常是开发成本的40%或更多。令人吃惊的是,该成本受用户数目的严重影响。用户越多,所发现的错误也越多。麻省理工学院核科学实验室的Betty Campbell指出特定版本的软件发布生命期中一个有趣的循环。如图11.2 所示。起初,上一个版本中被发现和修复的bug,在新的版本中仍会出现。新版本中的新功能会产生新的bug。解决了这些问题之后,程序会正常运行几个月。接着,错误率会重新攀升。Campbell 认为这是因为用户的使用到达了新的熟练水平,他们开始运用新的功能。这种高强度的考验查出了新功能中很多不易察觉的问题。5每月发现的bug 数安装后的时间(月)图11.2:出现的bug 数量是发布时间的函数- 68 ------------------------ Page 81-----------------------程序维护中的一个基本问题是——缺陷修复总会以(20-50)%的机率引入新的bug。所以整个过程是前进两步,后退一步。为什么缺陷不能更彻底地被修复?首先,看上去很轻微的错误,似乎仅仅是局部操作上的失败,实际上却是系统级别的问题,通常这不是很明显。修复局部问题的工作量很清晰,并且往往不大。但是,更大范围的修复工作常常会被忽视,除非软件结构很简单,或者文档书写得非常详细。其次,维护人员常常不是编写代码的开发人员,而是一些初级程序员或者新手。作为引入新bug 的一个后果,程序每条语句的维护需要的系统测试比其他编程要多。理论上,在每次修复之后,必须重新运行先前所有的测试用例,从而确保系统不会以更隐蔽的方式被破坏。实际情况中,回归测试必须接近上述理想状况,所以它的成本非常高。显然,使用能消除、至少是能指明副作用的程序设计方法,会在维护成本上有很大的回报。同样,设计实现的人员越少、接口越少,产生的错误也就越少。前进一步,后退一步6Lehman 和Belady 研究了大型操作系统的一系列发布版本的历史 。他们发现模块数量随版本号的增加呈线性增长,但是受到影响的模块以版本号指数的级别增长。所有修改都倾向于破坏系统的架构,增加了系统的混乱程度。用在修复原有设计上瑕疵的工作量越来越少,而早期维护活动本身的漏洞所引起修复工作越来越多。随着时间的推移,系统变得越来越无序,修复工作迟早会失去根基。每一步前进都伴随着一步后退。尽管理论上系统一直可用,但实际上,整个系统已经面目全非,无法再成为下一步进展的基础。而且,机器在变化,配置在变化,用户的需求在变化,所以现实系统不可能永远可用。崭新的、基于原有系统的重新设计是完全必要的。通过对统计模型的研究,关于软件系统,Belady 和Lehman得到了更具普遍意义、为所有经验支持的结论。正如Pascal. C. S. Lewis 所敏锐指出的:这正是历史的关键。使用卓越的能源——构建文明——成立杰出的机构,但是每次总会出现问题。一些致命的缺陷会将自私和残酷的人带到塔尖,接着一切开始滑落,回到到痛苦和堕落。实际上,机器失灵了。看上去,就好像是机器正常启动,跑了几步,然后垮掉了- 69 ------------------------ Page 82-----------------------7。系统软件开发是减少混乱度(减少熵)的过程,所以它本身是处于亚稳态的。软件维护是提高混乱度(增加熵)的过程,即使是最熟练的软件维护工作,也只是放缓了系统退化到非稳态的进程。- 70 ------------------------ Page 83-----------------------干将莫邪(Sharp Tools)巧匠因为他的工具而出名。- 谚语A good workman is known by his tools.- PROVERB就工具而言,即使是现在,很多软件项目仍然像一家五金店。每个骨干人员都仔细地保管自己工作生涯中搜集的一套工具集,这些工具成为个人技能的直观证明。正是如此,每个编程人员也保留着编辑器、排序、内存信息转储、磁盘实用程序等工具。这种方法对软件项目来说是愚蠢的。首先,项目的关键问题是沟通,个性化的工具妨碍——而不是促进沟通。其次,当机器和语言发生变化时,技术也会随之变化,所有工具的生命周期是很短的。毫无疑问,开发和维护公共的通用编程工具的效率更高。不过,仅有通用工具是不够的。专业需要和个人偏好同样需要很多专业工具。所以在前面关于软件开发队伍的讨论中,我建议为每个团队配备一名工具管理人员。这个角色管理所有通用工具,能指导他的客户-老板使用工具。同时,他还能编制老板需要的专业工具。因此,项目经理应该制订一套策略,并为通用工具的开发分配资源。与此同时,他还必须意识到专业工具的需求,对这类工具不能吝啬人力和物力——这种企图的危害非常隐蔽。可能有人会觉得,将所有分散的人员集结起来,形成一个公共的工具小组,会有更高的效率。但实际上却不是这样。项目经理必须考虑、计划、组织的工具到底有哪些呢?首先是计算机设施。它需要硬件和使用安排策略;它需要操作系统,提供服务的方式必须明了;它需要语言,语言的使用方针必须明确;然后是实用程序、调试辅助程序、测试用例生成工具和处理文档的字处理系1统。接下面我们逐一讨论它们 。- 71 ------------------------ Page 84-----------------------目标机器机器支持可以有效地划分成目标机器和辅助机器。目标机器是软件所服务的对象,程序必须在该机器上进行最后测试。辅助机器是那些在开发系统中提供服务的机器。如果是在为原有的机型开发操作系统,则该机器不仅充当目标机器的角色,同时也作为辅助机器。目标机器的类型有哪些?团队开发的监督程序或其他系统核心软件当然需要它们自己的机器。目标机器系统会需要若干操作员和一两个系统编程人员,以保证机器上的标准支持是即时更新和实时可用的。如果还需要其他的机器,那么将是一件很古怪的东西——运行速度不必非常快,但至少要若干兆字节的主存,百兆字节的在线硬盘和终端。字符型终端即可满足要求,但是它必须比15字符/每分的打字机速度要快。大容量内存可以进行进程覆盖(overlay)和功能测试之后的剪裁工作,从而极大地提高生产率。另外,还需要配备调试机器或者软件。这样,在调试过程中,所有类型的程序参数可以被自动计数和测量。例如,内存使用模式是非常强大的诊断措施,能查出程序中不可思议的行为或者性能意外下降的原因。计划安排。当目标机器刚刚被研制,或者当它的第一个操作系统被开发时,机器时间是非常匮乏的,时间的调度安排成了主要问题。目标机器时间需求具有特别的增长曲线。在OS/360 开发中,我们有很好的System/360 仿真器和其他的辅助设施,并根据以前的经验,我们计划出System/360 的使用时间(小时数),向制造商提前预定了机器。不过,起初它们日复一日地处于空闲状态。突然有一天,所有16个系统全部上线,这时资源配给成了严重问题。实际使用情况如图12.1 所示。每个人在同一时间,开始调试自己的第一个组件,然后团队大多数成员持续地进行某些调试工作。我们集中了所有的机器和磁带库,并组建了一个富有经验的专业团队来操作它们。为了最大限度地利用S/360 的时间,我们在任何系统空闲和可能的时间里,以批处理方式运行所有运算任务。我们尝试了每天运行四次(周转时间为两个半小时),而实际要求的周转时间为四小时。我们使用了一台带有终端的1401 辅助机器来进行调度,跟踪成千上万的任务,监督时间周期。- 72 ------------------------ Page 85-----------------------Model40 的每月使用小时数图12.1:目标机器使用的增长曲线但是整个开发队伍实在是过度运转了。在经过了几个月的缓慢周转、相互指责、极度痛苦之后,我们开始把机器时间分配成连续的块。例如,整个从事排序工作的15人小组,会得到系统4 至6 小时的使用时间块,由他们自己决定如何使用。即使没有安排,其他人也不能使用机器资源。这种方式,是一种更好的分配和安排方法。尽管机器的利用程度可能会有些降低(常常不是这样),生产率却提高了。上述小组中的每个人,6 小时中连续10次操作的生产率,