人月神话

一、焦油坑

编程系统产品( Programming Systems Product) 开发的工作量是供个人使用的、独立开发的构件程序的九倍

职业的乐趣

(1)创建事物的快乐;
(2)开发对其他人有用的东西的乐趣;
(3)将可以活动、 相互啮合的零部件组装成类似迷宫的东西, 这个过程所体现出令人神魂颠倒的魅力;
(4)面对不重复的任务,不间断学习的乐趣;
(5)工作在如此易于驾驭的介质上的乐趣——纯粹的思维活动, 其存在、 移动和运转方式完全不同于实际物体;

职业的苦恼

(1)必须追求完美:作者认为学习编程的最困哪部分,是将做事的方式往追求完美的方向调整。
(2)由他人设定目标,供给资源,提供信息:编程人员很少能控制工作环境和工作目标。
(3)概念性设计有趣的,但寻找琐碎的bug却只是一项重复性的活动。
(4)人们通常期望项目在接近结束时,( bug、 工作时间) 能收敛得快一些, 然而软件项目的情况却是越接近完成,收敛得越慢
(5)产品完成时却变得陈旧了。

二、人月神话

美酒的酿造需要年头,美食的烹调需要时间;片刻等待,更多美味,更多享受(延迟满足感)

缺乏合理的时间进度是造成项目滞后的最主要原因:

(1)进行项目安排时,总是假设“一切将运作良好”;
(2)错误地将进度与工作量相互混淆;
(3)由于对自己估算缺乏信心,不会有耐心进行持续地估算这项工作;

乐观主义

(1)创造性活动分为三个阶段:构思、实现、交流
(2)一切正常的概率非常小,甚至接近于无

人月

荒谬的神话:

(1)完全可以分解的任务,并且不需要交流(系统编程中近乎不可能)
(2)无法分解的任务,由于次序上的顺序无法分解(许多软件具有这种特征)
(3)需要沟通的可分解任务,尽量不要增加人手(沟通所增加的负担由两部分组成:培训和互相交流)
(4)关系错综复杂的任务,工作量按照n(n-1)/2递增

系统测试

(1)通常实际出现的缺陷数量比预期的要多得多,因此测试进程的安排常常是编程中最不合理的部分。
(2)软件任务的进程安排:1/3计划、1/6编码、1/4构建测试和早期系统测试、1/4系统测试,所有构件完成

空泛的估算

(1)某项任务的计划程度,可能受限于顾客要求的紧迫程度,因此容易估算出错
(2)重复产生的进度灾难
(3)向进度落后的项目中增加人手,只会使进程更加落后

三、外科手术队伍

问题

(1)小型精炼的团队比大型团队效率要高很多
(2)大型团队在某些时刻开发用时可能比小型团队要短

Mills的建议

外科医生:亲自定义功能和性能的技术说明书,设计程序,编制源代码,测试以及书写技术文档
副手 :外科医生的后备,能完成任何一部分工作,作为设计的思考着、讨论者和评估人员
管理员 :管理控制财务、人员、工作地点等
编辑 :整理外科医生的文档
两个秘书:管理员和编辑各一个秘书
程序职员:负责维护编程产品库中所有团队的技术记录
工具维护人员:维护例如交互式计算机服务之类的书
测试人员:搭建测试平台、测试
语言专家:寻找一种简洁、有效的使用语言的方法来解决棘手的问题

如何运作

(1)所有人都为外科医生服务
(2)观点的不一致由外科医生单方面来统一

团队的扩建

(1)决定设计的人员应是原来的七分之一或者更少
(2)清晰地划分体系结构设计和实现之间的界限

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

获得概念的完整性

(1)系统设计中,概念的完整性应该是最重要的考虑因素
(2)编程系统的目的是使计算机更加容易使用(易用性),易用性实际上需要设计的一致性和概念上的完整性

贵族专制统治和民主政治

解决概念完整性与进度压力的方法:

(1)仔细地区分设计方法和具体实现
(2)组建外科手术队伍

在等待时,实现人员应该做什么?

实现人员对设计人员提出的问题:

该说明中的功能过于繁多,而对实际情况中的成本考虑比较少
结构师获得了所有创造发明的快乐,剥夺了实现人员的创造力,实现同样是一项高级别的创造性工作
当体系结构的队伍工作缓慢时,很多实现人员只能空闲地坐着等待,所以
(1)必须设定良好定义的时间和空间目标,了解产品运行的平台配置
(2)开始设计模块的边界、表结构、算法以及所有的工具
(3)需要花费一些时间和体系结构师沟通
整个创造性活动包括了三个独立的阶段:
(1)体系结构
(2)设计实现
(3)物理实现

五、画蛇添足

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

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

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

(1)第二个系统是设计师们所设计的最危险的系统;
(2)避免功能上的修饰;
(3)把工作量投入在提高动态内存分配和动态交叉引用的性能上;
(4)为每一个小功能分配一个值:每次改进,功能X不超过m字节的内存和n微秒

六、贯彻执行

(1)文档化的规格说明--手册
(2)形式化定义
(3)直接整合
(4)会议和大会
(5)多重实现:为了如实地遵从手册更新机器所造成的延迟和成本消耗
(6)电话日志
(7)产品测试

七、为什么巴比伦塔会失败

巴比伦塔的管理教训

(1)交流产生组织
(2)交流与组织很重要
(3)没有交流,组织就会分裂而不是争吵

大型编程项目中的交流

(1)非正式途径
(2)会议
(3)工作手册

项目工作手册

(1)他是对项目必须产出的一系列文档进行组织的一种结构(目的、外部规格说明、接口说明、技术标准、内部说明和管理备忘录)
(2)为了回顾的时候发现思路,查看备忘录对产品提出的建议和设计解释说明
(3)项目手册的目的是确保信息能到达所有需要它的人的手中
(4)处理机制:涉及到大规模多人参与的项目的信息索引管理机制,详见p43

大型编程项目的组织架构

(1)产品负责人和技术主管是同一个人
(2)产品负责人作为总指挥,技术主管充当其左右手
(3)技术主管作为总指挥,产品负责人充当其左右手

八、胸有成竹

九、削足适履

(1)作为成本的程序空间
(2)规模控制
(3)空间技能
(4)数据的表现形式是编程的根本

十、提纲挈领

文档来自:技术、周边组织机构、行业传统等若干因素凑在一起;

文档的某些部分包含和表达了一些管理方面的工作

(1)文档的跟踪维护是项目监督和预警的机制
(2)文档本身可以作为检查列表、状态控制、汇报的数据基础

计算机产品的文档

目标,技术说明,进度、时间表,预算,组织机构图,工作空间的分配,报价、预测、价格
大学科系的文档(略)

软件项目的文档

做什么,时间:进度表,资金:预算,地点:工作空间分配,人员:组织图

为什么要有正式的文档?

(1)记录下来减少矛盾;
(2)作为同其他人沟通的渠道;
(3)作为数据基础和检查列表;

十一、未雨绸缪

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

试验性工厂和增大规模

(1)海水淡化先在量产为10000加仑/每天试验,然后再用于2000000加仑/每天;
(2)开发的系统总要被抛弃;

唯一不变的就是变化本身

(1)开发人员交付的是用户满意度,而不是实际的产品,用户的需求和感觉会变化;
(2)事先为它们做准备总比假设它们不会出现要好得多;

为变更计划系统

(1)细致的模块化、可扩展的函数、精确完整的模块间接口设计、完备的文档;
(2)采用一些调用队列和表驱动的一些技术(并不明白这是怎么用的);
(3)最重要的措施是,使用高级语言和自文档技术,以减少变更引起的错误;

为变更计划组织架构

设计人员不愿意为设计书写文档化的原因:懒惰、时间压力、不愿意为此进行辩解;

减少人员的变更

(1)有两三个顶级程序员在最前沿解决各种问题;
(2)减少管理头衔,每个人都是技术人员中的一员;
(3)组建外科手术团队;

前进两步,后退一步

(1)新发布的版本对上一版本的缺陷修复总会以(20-50)%的机率引入新的bug;
(2)轻微的错误,有时候实际上是系统级别的问题;
(3)设计实现的人员越少、接口越少、产生的错误也就越少;

前进一步,后退一步

(1)大型操作系统的所有修改都倾向于破坏系统的架构,增加了系统的混乱程度;
(2)实际上,机器失灵了。看上去,就好像是机器正常启动,跑了几部,然后垮掉了(这句真的很搞笑);
(3)系统开发是减少熵的过程,系统维护是增加熵的过程,即使是最熟练的软件维护工作,也只是放缓了系统退化到非稳态的进程

十二、干将莫邪

巧匠因为他的工具而出名。(新鲜!)

目标机器

(1)软件所服务的对象,程序必须在该机器上进行最后测试;
(2)计划安排好使用目标机器;

辅助机器和数据服务

(1)仿真装置:用于新机器初现之前的调试;
(2)仿真机器无法精确地达到与新型机器一致的实现;
(3)编译器和汇编平台:也需要运行在可靠的辅助平台上;
(4)维护程序库;
(5)编程工具;
(6)文旦系统:在所有工具中,最能节省劳动力的,可能是运行在可靠平台上的、计算机化的文本编辑系统;
(7)性能仿真装置(并没有用过);

高级语言和交互式编程

(1)此处作者建议使用高级语言编程
(2)多个级别上的数据和程序的共享和保护,可延伸的库管理,以及协助终端用户开发的设施;

十三、整体部分

和古老神话里一样,现代神话里也总有一些爱吹嘘的人
我能召唤遥远的精灵。
那又怎么样,我也可以,谁都可以,问题是你真的召唤的时候,它们会来吗?

剔除bug的设计

(1)各个组成部分的开发者都会做出一些假设,而这些假设之间的不匹配,是大多数致命和难以察觉的bug的主要来源。
(2)编码之前测试规格说明就要交给测试小组;
(3)采用自顶向下的设计;

构建单元调试,四个步骤

本机调试——>内存转储——>快照——>交互式调试
系统集成调试(系统调试花费的时间会比预料的更长;需要一种完备系统化和可计划的方法来降低它的困难程度;)

使用经过调试的构件单元

(1)除了构件上的bug之外,还存在系统bug,越早将各个部分合拢,系统bug出现得越早;
(2)另一种观念,使用系统的各个部分进行相互测试,避免了大量测试辅助平台的搭建工作;
(3)但经验显示,使用完好的、经过调试的构件,能比搭建测试平台和进行全面的构件单元测试节省更多的时间; 搭

建充分的测试平台

(1)伪构件的形式
(2)微缩文件
(3)微缩文件的特例,伪文件

控制变更

(1)一次添加一个构件,对系统进行回归测试
(2)阶段(量子)化、定期变更,不要采用小而频繁的变更

十四、祸起萧墙

带来坏消息的人不受欢迎

里程碑还是沉重的负担?

(1)里程碑要有明显边界和没有歧义
(2)里程碑要清晰,切实的完成
(3)政府的承包商的两项有趣的研究结果显示:
  1.如果在某项活动开始之前就着手估计,并且每两周进行一次仔细的修订。这样,随着开始时间的临近,无论最后情况会变得如何的糟糕,它都不会有太大的变化。
  2.活动期间,对时间长短的过高估计,会随着活动的进行持续下降。
  3.过低估计在活动中不会有太大的变化,一直到计划的结束日期之前大约三周左右。

“其他的部分反正会落后”

(1)必须关注每一天的滞后,它们是大灾祸的基本组成元素;
(2)使用PERT图

地毯的下面

老板很难得到项目进展状况的真相;
掀开毯子的方法:
(1)减少角色冲突和鼓励状态共享:不对项目经理可以解决的问题做出反应,并且决不再检查报告的时候做安排;
(2)猛地拉开地毯:使用PERT图;

十五、另外一面

不了解,就无法真正拥有。 ————歌德

需要什么样的文档

使用程序

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

验证程序

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

修改程序

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

流程图

流程图被鼓吹的过于重要;

自文档化的程序

数据处理的基本原理告诉我们,试图把信息放在不同的文件中,并努力维持它们之间的同步,是一件非常费力不讨好的事情。
解决方案是,把文档整合到源代码:
(1)在代码中添加标签、声明语句、符号名称等;
(2)提高程序的可读性,表现从属和嵌套关系;
(3)段落注释的形式,向程序中插入必要的记述性文字;

一些技巧

(1)为每次运算使用单独的任务名称。 维护一份日志, 记录程序运行的目的、 时间和结果。如果名称由一个助记符(这里是 QLT)和数字后缀组成,那么后缀可以作为运算编号,把列表和日志联系在一起。这种技术要求为每次运算准备新的任务卡, 不过这项工作可以采用“重复进行公共信息的批处理”来完成。
(2)使用包含版本号和能帮助记忆的程序名称。 即, 假设程序将会有很多版本。 例子中使用的是 1967 年的最低一位数字。
(3)在过程( PROCEDURE)的注释中,包含记叙性的描述文字。
(4)尽可能为基本算法提供参考引用,通常它会指向更完备的处理方法。这样,既节省了空间,同时还允许那些有经验的读者能非常自信地略过这一段内容。
(5)显示和算法书籍中的传统算法的关系。
  a) 更改 b) 定制细化 c) 重新表达
(6)声明所有的变量。采用助记符,并使用注释把 DECLARE 转化成完整的说明。注意,声明已经包含了名称和结构性描述, 需要增加的仅仅是对目的的解释。 通过这种方式, 可以避免在不同的处理中重复名称和结构性的描述。
(7)用标签标记出初始化的位置。
(8)对程序语句进行分组和标记,以显示与设计文档中语句单元的一致性。
(9)利用缩进表现结构和分组。
(10)在程序列表中, 手工添加逻辑箭头。 它们对调试和变更非常有帮助。 它们还可以补充在页面右边的空白处(注释区域),成为机器可读文字的一部分。
(11)使用行注释来解释任何不很清楚的事情。 如果采用了上述技术, 那么注释的长度和数量都将小于传统惯例。
(12)把多条语句放置在一行, 或者把一条语句拆放在若干行, 以吻合逻辑思维, 表示和其他算法描述一致。

为什么不?

在一些情况下会增加源代码的大小;

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

没有任何技术或管理上的进展,能够独立地许诺十年内使生产率、可靠性或简洁性获得数量级上的进步。

是否一定那么困难呢?————根本困难

计算机硬件发展得太快,软件发展跟不上;

根本的————软件特性中固有的困难

(1)软件实体必不可少的部分:数据集合、 数据条目之间的关系、 算法、 功能调用等等;
(2)作者认为软件开发中困难的部分是规格化、设计和测试这些概念上的结构,而不是对概念进行表达和对实现逼真程度进行验证;
(3)复杂度:由于复杂度,团队成员之间的沟通非常困难;列举和理解所有可能的状态十分困难;使全面理解问题变得困难,从而妨碍了概念上的完整性;使所有离散出口难以寻找和控制;引起了大量学习和理解上的负担,使开发慢慢演变成了一场灾难。
(4)一致性:软件工程是面对的是没有一致性的代码;
(5)可变性:软件工程有时需要很大量的修改;
(6)不可见性:软件无法形象的表现出来,这种缺陷不仅限制了个人的设计过程,也严重地阻碍了相互之间的交流。

次要的————出现在目前生产上的,但并非那些与生俱来的困难

(1)高级语言:软件生产率、可靠性和简洁性上最有力的突破;
(2)抽象程序包含了很多概念上的要素:操作、数据类型、流程和相互通讯,而具体的机器语言程序则关心位、寄存器、条件、分支、 通道、 磁盘等等。
(3)分时:提高了程序员的生产率和产品的质量
(4)统一编程环境:它们主要通过提供集成库、统一文件格式、管道和过滤器

银弹的希望

(1)Ada和其他高级编程语言
(2)面向对象编程
(3)专家系统
(4)“自动”编程
(5)图形化编程
(6)程序验证
(7)更好的环境和工具
(8)高运算速度的工作站

针对概念上根本问题的颇具前途的方法

(1)也许购买软件比自行开发要快捷有效的多,学会使用软件比会编写软件要有用的多;
(2)软件系统的快速原型对重要的系统界面进行模拟,并演示待开发系统的主要功能;
(3)增量的方式一点一点搭建系统,一个原因是雏形可以极大的推动士气;
(4)卓越的开发人员和一般之间的差异接近于一个数量级

十七、再论《没有银弹》

那些想看到完美方案的人,其实在心底里就认为它们以前不存在,以后也不可能出现。 ————亚历山大 波普

创造性活动包括

(1)概念性结构的形式规格化;
(2)使用现实的介质来实现;
(3)在实际的使用中,与用户交互 ;

现实问题

  《没有银弹》无可争辩地指出,如果开发的次要部分少于整个工作的9/10,那么即使不占用任何时间(除非出现奇迹),也不会给生产率带来数量级的提高。因此, 必须着手解决开发的根本问题。

posted @ 2018-03-01 16:25  小禾先生  阅读(261)  评论(0编辑  收藏  举报