人月银弹神话系列软件(软件银弹复杂度困难抽象)「人月神话 没有银弹」

最近做了一个超大项目重构,其中对项目的管理也产生了非常多的问题,进行了深度项目的复盘之后,再回首去看软件巨著《人月神话》体感更加丰富收获非常多,故产生了此系列文章
本系列文是软件巨著《人月神话》读书笔记,全系列分为三部分,本文主要介绍软件开发没有银弹
没有银弹—软件工程中的根本和次要问题没有银弹: 无论是技术还是管理方法上,没有任何一项技术或者方法可使软件工程的生产力在十年内提高十倍
在所有恐怖民间传说的妖怪中,最可怕的是人狼,因为它们可以完全出乎意料地从熟悉的面孔变成可怕的怪物
为了对付人狼,我们在寻找可以消灭它们的银弹
我们熟悉的软件项目也有类似的特质,平常看似简单的项目,但很可能一转眼就变成一只进度延误、预算超支、产品充满bug的怪兽,所以我们听到了绝望的呼唤,渴望有一种银弹,能够有效降低软件开发的成本,就跟电脑硬件成本能快速下降一样
但是,作者预测从当前开始的十年之内,将不会看到任何银弹,无论是在技术上或管理上,都不会有任何单一的重大突破,能够保证在生产力、可靠度或简洁性上获得改善,甚至,连一个数量级的改善都不会有
计算机硬件发展得太快
从人类文明开始,没有任何其他产业技术的性价比,能在 30 年之内取得 6 个数量级的提高, 也没有任何一个产业可以在性能提高或者降低成本方面取得如此的进步
这些进步来自计算机制造产业的转变,从装配工业转变成流水线工业
软件工程为什么发展那么缓慢呢,效仿亚里士多德, 我将它们分成根本的——软件特性中固有的困难, 次要的——出现在目前生产上的,但并非那些与生俱来的困难
一个相互牵制关联的概念结构,是软件实体必不可少的部分,它包括:数据集合、 数据条目之间的关系、 算法、 功能调用等等
这些要素本身是抽象的, 体现在相同的概念构架中,可以存在不同的表现形式
尽管如此,它仍然是内容丰富和高度精确的
作者认为软件开发中困难的部分是规格化、设计和测试这些概念上的结构,而不是对概念进行表达和对实现逼真程度进行验证
当然, 我们还是会犯一些语法错误, 但是和绝大多数系统中的概念错误相比,它们是微不足道的
如果这是事实,那么软件开发总是非常困难的
天生就没有银弹
作者将软件开发的困难分为两类:本质性(essence): 软件本身在概念(conceptual)建构上存在先天的困难;亦即如何从抽象性问题,发展出具体概念上的解决方案
附属性(accident):将概念上的构思用程序展现出来所遭遇到的困难
软件开发一般是从具象到抽象,然后从抽象到具象,最后从具象到程序实现
假设软件开发的总工作量为10,附属性工作与本质性工作占比为9:1,那么改善附属性工作,将之消除,就可以把软件工作量减轻到1(因为附属性工作变成0),此时我们可以说,软件工作开发的轻松程度提升了一个数量级(因为由10进步到1,差10倍),但这是不可能的
附加性的困难会随着工具和方法的改善而逐渐淡化,反而是本质性的困难最难以解决,因为大部分的活动是发生在人们的脑海里,缺乏有效的辅助工具
依照作者的说法主要有下列几项软件根本复杂度软件根本复杂度,主要由下面四个特性构成:『软件内在特性之复杂性』: 软件要解决的问题,通常是一种人为、抽象化的智能活动,没有哪两个软件是一模一样的,复杂是软件的根本特性
规模上,软件实体可能比任何由人类创造的其他实体要复杂,因为没有任何两个软件部分是相同的(至少是在语句的级别)
如果有相同的情况, 我们会把它们合并成供调用的子函数
在这个方面, 软件系统与计算机、 建筑或者汽车大不相同, 后者往往存在着大量重复的部分
软件系统构建与计算机之上,系统状态非常多
数字计算机本身就比人类建造的大多数东西复杂,计算机拥有大量的状态,这使得构思、描述和测试都非常困难
软件系统的状态又比计算机系统状态多若干个数量级
软件的扩展导致导致复杂度非线性增长,因为软件实体的扩展也不仅仅是相同元素重复添加, 而必须是不同元素实体的添加
大多数情况下, 这些元素以非线性递增的方式交互, 因此整个软件的复杂度以更大的非线性级数增长
数学和物理学中用于解决复杂问题的抽象建模方法在软件工程缺行不通了,原因是软件的复杂度是必要属性,不是次要因素
因此,抽掉复杂度的软件实体描述常常也去掉了一些本质属性
数学和物理学在过去三个世纪取得了巨大的进步, 数学家和物理学家们建立模型以简化复杂的现象, 从模型中抽取出各种特性, 并通过试验来验证这些特性
这些方法之所以可行——是因为模型中忽略的复杂度不是被研究现象的必要属性
当复杂度是本质特性时,这些方法就行不通了上述软件特有的复杂度问题造成了很多经典的软件产品开发问题,在技术上:由于复杂度,团队成员之间的沟通非常困难, 导致了产品瑕疵、 成本超支和进度延迟;由于复杂度, 列举和理解所有可能的状态十分困难, 影响了产品的可靠性;由于函数的复杂度, 函数调用变得困难,导致程序难以使用;由于结构性复杂度,程序难以在不产生副作用的情况下用新函数扩充;由于结构性复杂度,造成很多安全机制状态上的不可见性
复杂度不仅仅导致技术上的困难,还引发了很多管理上的问题
它使全面理解问题变得困难, 从而妨碍了概念上的完整性; 它使所有离散出口难以寻找和控制; 它引起了大量学习和理解上的负担,使开发慢慢演变成了一场灾难
『软件内在特性之一致性』:大型软件开发中,界面、接口常常会不一致,并且随着时间和环境的推移会变得越来越不一致,要维持这样的一致性通常十分困难
物理学家坚信必定存在着某种通用原理, 或者在夸克中,或者在统一场论中
爱因斯坦曾不断地重申自然界一定存在着简化的解释, 因为上帝不是专横武断或反复无常的
但,软件工程师却无法从类似的信念中获得安慰,他必须控制的很多复杂度是随心所欲、毫无规则可言的, 来自若干必须遵循的人为惯例和系统
它们随接口的不同而改变, 随时间的推移而变化, 而且, 这些变化不是必需的, 仅仅由于它们是不同的人——而非上帝设计的结果
『软件内在特性之可变性』:软件构成的因素随时都在变化
软件实体经常会遭受到持续的变更压力
当然,建筑、汽车、计算机也是如此
不过, 工业制造的产品在出厂之后不会经常地发生修改, 它们会被后续模型所取代, 或者必要更改会被整合到具有相同基本设计的后续产品系列
汽车的更改十分罕见, 计算机的现场调整时有发生
然而,它们和软件的现场修改比起来,都要少很多
这一方面是因为软件可以很容易地进行修改,它是纯粹思维活动的产物,可以无限的扩展
日常生活中, 建筑有可能发生变化, 但众所周知, 建筑修改的成本很高, 从而打消了那些想提出修改的人的念头
另外的原因是因为系统中的软件包含了很多功能,而功能是最容易感受变更压力的部分
所有成功的软件都会发生变更
现实工作中,经常发生两种情况
当人们发现软件很有用时, 会在原有应用范围的边界, 或者在超越边界的情况下使用软件
功能扩展的压力主要来自那些喜欢基本功能,又对软件提出了很多新用法的用户们
其次,软件一定是在某种计算机硬件平台上开发,成功软件的生命期通常比当初的计算机硬件平台要长
即使不是更换计算机, 则有可能是换新型号的磁盘、 显示器或者打印机
软件必须与各种新生事物保持一致
简言之,软件产品扎根于文化的母体中,如各种应用、用户、自然及社会规律、计算机硬件等等
后者持续不断地变化着,这些变化无情地强迫着软件随之变化
『软件内在特性之不可见性』: 尚未完成的软件是看不见的,即使利用图表说明,也常无法充分呈现其结构,使得人们在沟通上面临极大的困难
软件是不可见的和无法可视化的
例如,几何抽象是强大的工具
建筑平面图能帮助建筑师和客户一起评估空间布局、 进出的运输流量和各个角度的视觉效果
这样,矛盾变得突出, 忽略的地方变得明显
同样, 机械制图、 化学分子模型尽管是抽象模型, 但都起了相同的作用
总之,都可以通过几何抽象来捕获物理存在的几何特性
软件的客观存在不具有空间的形体特征
因此,没有已有的表达方式,就像陆地海洋有地图、 硅片有膜片图、 计算机有电路图一样
当我们试图用图形来描述软件结构时, 我们发现它不仅仅包含一个, 而是很多相互关联、 重叠在一起的图形
这些图形可能描绘控制流程、 数据流、 依赖关系、 时间序列、 名字空间的相互关系等等
它们通常不是有较少层次的扁平结构
实际上, 在上述结构上建立概念控制的一种方法是强制将关联分割, 直到可以层次化一个或多个图形
除去软件结构上的限制和简化方面的进展,软件仍然保持着无法可视化的固有特性,从而剥夺了一些具有强大功能的概念工具的构造思路
这种缺憾不仅限制了个人的设计过程,也严重地阻碍了相互之间的交流
解决根本困难的一些方法我们必须考虑那些解决软件上根本困难的活动——即,准确地表达复杂概念结构
没有银弹能够解决这些根本困难,但有一些途径去改善他们:『购买和自行开发』
构建软件最可能的彻底解决方案是不开发任何软件
更多专业厂商为工作站和 UNIX 市场提供了很多非常有竞争力的产品,甚至很多工具软件和开发环境软件都可以随时购买使用
这些软件,购买都要比重新开发要低廉一些
即使支付 100,000 美元,购买的软件也仅仅是一个人年的成本
而且软件是立即可用的
至少对于现有的产品、 对于那些专注于该领域开发者的成果而言, 它们是可以立刻投入使用的
并且, 它们往往配备了书写良好的文档,在某种程度上比自行开发的软件维护得更加完备
我相信,这个大众市场将是软件工程领域意义最深远的开发方向
软件成本一直是开发的成本, 而不是复制的成本
所以, 即使只在少数使用者之间实现共享, 也能在很大程度上减少成本
另一种看法是使用软件系统的 n 个拷贝, 将会使开发人员的生产率有效地提高n 倍
这是一个领域和行业范围的提高
『需求精炼和快速原型』
开发软件系统的过程中,最困难的部分是确切地决定搭建什么样的系统
概念性工作中, 没有其他任何一个部分比确定详细的技术需求更加困难, 详细的需求包括了所有的人机界面、 与机器和其他软件系统的接口
需求工作对系统的影响比其他任何一个部分的失误都大,当然纠正需求的困难也比其他任何一个部分要大
软件开发人员为客户所承担的最重要的职能是不断重复地抽取和细化产品的需求
事实上, 客户不知道他们自己需要什么
在尝试和开发一些客户定制的系统之前, 即使他们和软件工程师一起工作,想要完整、精确、正确地抽取现代软件产品的需求——这实际上也是不可能的
因此,现在的技术中最有希望的,并且解决了软件的根本而非次要问题的技术,是开发作为迭代需求过程的一部分——快速原型化系统的方法和工具
软件系统的快速原型对重要的系统界面进行模拟,并演示待开发系统的主要功能
原型不必受到相同硬件速度、 规模或者成本约束的限制
原型通常展示了应用程序的功能主线,但不处理任何如无效输入、 退出清除等异常情况
原型的目的是明确实际的概念结构, 从而客户可以测试一致性和可用性
『增量开发——增长, 而非搭建系统』系统应该能够运行, 即使未完成任何有用功能, 只能正确调用一系列伪子系统
接着, 系统一点一点被充实, 子系统轮流被开发, 或者是在更低的层次调用程序、模块、子系统的占位符(伪程序)等
这种方法迫切地要求自顶向下设计,因为它本身是一种自顶向下增长的软件
增量化开发使逆向跟踪很方便, 并非常容易进行原型开发
每一项新增功能, 以及针对更加复杂数据或情况的新模块, 从已经规划的系统中有机地增长
这种开发模式对士气的推动是令人震惊的
当一个可运行系统——即使是非常简单的系统出现时, 开发人员的热情就迸发了出来
让我们转向自然界,研究一下生物的复杂性,我们会发现它们的复杂程度令我们敬畏
光是大脑本身, 就比任何对它的描述都要复杂, 比任何的模拟仿真都要强大, 它的多样性、 自我保护和自我更新能力异常丰富和有力
其中的秘密就是逐步发育成长,而不是一次性搭建
『卓越的设计人员』,关键的问题是如何提高软件行业的核心,一如既往的是——人员
低劣设计和良好设计之间的区别可能在于设计方法中的完善性, 而良好设计和卓越设计之间的区别肯定不是如此
卓越设计来自卓越的设计人员
软件开发是一个创造性的过程
完备的方法学可以培养和释放创造性的思维,但它无法孕育或激发创造性的过程
尽管很多杰出、实用的软件系统是由很多人共同设计开发,但是那些激动人心、 拥有广大热情爱好者的产品往往是一个或者少数伟大设计师们的思想
因此,尽管我强烈地支持现在的技术转移和开发技能的传授,但我认为我们可以着手的最重要工作是寻求培养卓越设计人员的途径解决次要困难的一些突破『高级语言』 勿庸置疑,软件生产率、可靠性和简洁性上最有力的突破是使用高级语言编程
大多数观察者相信开发生产率至少提高了五倍, 同时可靠性、 简洁性和理解程度也大为提高
抽象程序包含了很多概念上的要素: 操作、 数据类型、 流程和相互通讯, 而具体的机器语言程序则关心位、 寄存器、 条件、 分支、 通道、 磁盘等等
高级语言所达到的抽象程度包含了(抽象)程序所需要的要素, 避免了更低级的元素, 它消除了并不是程序所固有的整个级别的复杂度
高级语言最可能实现的是提供所有编程人员在抽象程序中能想到的要素
可以肯定的是, 我们思考数据结构、 数据类型和操作的速度稳固提高, 不过是以非常缓慢的速度
另外,程序开发方法越来越接近用户的复杂度
『分时』大多数观察者相信分时提高了程序员的生产率和产品的质量,尽管它带来的进步不如高级语言
分时保证了及时性,从而使我们能维持对复杂程度的一个总体把握
批处理编程的较长周转时间意味着不可避免会遗忘一些细枝末节, 如果我们停下编程, 调用编译程序或者执行程序, 思维上的中断使我们不得不重新进行思考, 它在时间上的代价非常高昂
最严重的结果可能是失去对复杂系统的掌握
『统一编程环境』第一个集成开发环境——Unix 和 Interlisp 现在已经得到了广泛应用,并且使生产率提高了 5 倍
它们主要通过提供集成库、统一文件格式、管道和过滤器,解决了共同使用程序的次要困难
这样, 概念性结构理论上的相互调用、 提供输入和互相使用, 在现实中可以非常容易地实现
因为每个新工具可以通过标准格式在任何一个程序中应用,这种突破接着又激发整个工具库的开发
作者当下银弹的希望让我们来讨论一下当今可能作为潜在银弹的最先进的技术进步
它们各自针对什么样的问题?它们是属于必要问题, 或者依然是解决我们剩下的次要困难?它们是提供了创新,还是仅仅是增量改进?『Ada 和其他高级编程语言』近来, 最被吹捧的开发进展之一是编程语言 Ada,一种 80年代的高级语言
Ada 实际上不仅仅反映了语言概念上的突破性进展, 而且蕴涵了鼓励现代设计和模块化概念运用的重要特性
由于 Ada 采用的是抽象数据类型、 层次结构的模块化理念, 所有 Ada 理念可能比语言本身更加先进
然而, Ada 仍然不是消灭软件生产率怪兽的银弹
毕竟, 它只是另一种高级语言, 这类语言出现最大的回报来自出现时的冲击, 它通过使用更加抽象的语句来开发, 降低了机器的次要复杂度
『面向对象编程』提出了抽象数据类型和层次化类型
抽象数据类型的概念是指对象类型应该通过一个名称、 一系列合适的值和操作来定义, 而不是理应被隐藏的存储结构
抽象数据类型的例子是 Ada 包(以及私有类型)和 Modula 的模块
层次化类型,如 Simula-67 的类, 是允许定义可以被后续子类型精化的通用接口
这两个概念是互不相干的——可以只有层次化, 没有数据隐藏; 也可能是只有数据隐藏, 而没有层次化
两种概念都体现了软件开发领域的进步
它们的出现都消除了开发过程中的非本质困难,允许设计人员表达自己设计的内在特性, 而不需要表达大量句法上的内容, 这些内容并没有添加什么新的信息
对于抽象数据类型和层次化类型, 它们都是解决了高级别的次要困难和允许采用较高层次的表现形式来表达设计
『人工智能』使用计算机来解决以前只能通过人类智慧解决的问题
一旦我们了解了它的运行方式, 理解了问题, 就不再认为它是人工智能……不幸的是, 我无法识别这个领域的特定知识体系……绝大多数工作是针对问题域的, 我们需要一些抽象或者创造性来解决上述问题
例如, 我觉得很难去发现图象识别技术能给编程开发实践带来什么样的差异
同样, 语音识别也差不多——软件开发上的困难是决定说什么, 而不是如何说
表达的简化仅仅能提供少量的促进作用
『专家系统』专家系统是包含归纳推论引擎和规则基础的程序,它接收输入数据和假设条件,通过从基础规则推导逻辑结果, 提出结论和建议, 向用户展示前因后果, 并解释最终的结果
推论引擎除了处理推理逻辑以外,通常还包括复杂逻辑或者概率数据和规则如何把它应用在软件开发工作中?可以通过很多途径: 建议接口规则、 制订测试策略、记录各种 bug 产生的频率、提供优化建议等等
但目前这些都是只是解决软件工程的次要问题
在较早实现的用于软件开发的专家顾问系统中,存在着很多困难
在我们假设的例子中, 一个关键的问题是寻找一种方法, 能从软件结构的技术说明中, 自动或者半自动地产生诊断规则
另外, 更加重要也是更加困难的任务是: 寻觅能够清晰表达、 深刻理解为什么的分析专家; 开发有效的技术——抽取专家们所了解的知识, 把它们精炼成基础规则
这项工作的工作量是知识获取工作量的两倍
构建专家系统的必要前提条件是拥有专家
『“自动”编程』从问题的一段陈述说明自动产生解决问题的程序,多数情况下所给出的技术说明本质上是问题的解决方法, 而不是问题自身
『图形化编程』软件非常难以可视化
即使用图形表达出了流程图、 变量范围嵌套情况、 变量交叉引用、 数据流、 层次化数据结构等等, 也只是表达了某个方面, 就像盲人摸象一样
如果我们把很多相关的视图叠加在所产生的图形上, 那么很难再抽取出全局的总体视图
『环境、工具和工作站』这些问题都和基础设施,产品等有关,但是他更多的是为了解决软件工程的次要问题,对主要问题也无法有效解决
小结:软件的复杂体现在它是纯思维的产物,是一个纯抽象的概念
具体语法层面上的实现只是软件开发中的次要问题
除非次要问题能占到开发活动的9/10以上,否则即使全部次要任务的时间缩减到零,也不会带来生产率数量级上的提高
再论没有银弹-对外界对没有银弹讨论的回应这一章是《没有银弹》这篇文章发表10年后(1996年),作者对针对这一话题的讨论的一些回应与评价,其中也包括对一些新兴技术的讨论,很有意思,这里做一些重点的摘录
含糊的表达将会导致误解,作者重新对观点进行解释,在软件开发中,作者称为“ 必要(essence)”的部分是构思这些概念上的结构;我称为“ 次要(accident)”的部分指它的实现过程
作者并不是贬低软件构建中的次要部分
作者认为开发的次要或者表达部分现在已经下降到整个工作的一半或一半以下
由于这部分是现实的问题,所以原则上可以应用测量技术来研究
这样,我的观点也可以通过来更科学和更新的估计来纠正
值得注意的是, 还没有人公开发表或者写信告诉我, 次要部分的任务占据了工作的 9/10
如果开发的次要部分少于整个工作的 9/10,那么即使不占用任何时间(除非出现奇迹), 也不会给生产率带来数量级的提高
环境和次要因素,无论起到多么积极的作用, 仍无法提高生产率
但是在产生负面影响时, 它们会使生产率降低
《没有银弹》认为很多软件开发过程已经消除了以下负面因素: 十分笨拙的机器语言、漫长的批处理周转时间以及无法忍受的内存限制
因为是根本困难所以没有希望?作者解释道,作为本质上的困难, 构思软件概念性的结构本身就有复杂性、 一致性、 可变性及不可见性的特点
不过实际上,每一种困难产生的麻烦都是可以改善的
这些方法可以在现实中取得十分乐观的进展:层次化,通过分层的模块或者对象
增量化,从而系统可以持续地运行关注质量,生产率自然会随着提高?系统化软件开发方法的发展是为了解决质量问题(特别是避免大型的灾难),而不是出于生产率方面的考虑
面向对象编程——这颗铜质子弹可以吗?面向对象技术为什么发展缓慢,《没有银弹》 后的九年中,对面向对象技术的期望稳步增长
为什么增长如此缓慢?James Coggins解释道,问题是OO程序员经历了很多错综复杂混乱的应用,他们所关注的是低层次,而不是高层次的抽象
例如, 他们开发了很多象链表或集合这样的类, 而不是用户接口、 射线束模型或者有限元素模型
不幸的是, C++中帮助程序员避免错误的强类型检查,使得从小型事物中构建大型物体非常困难
资金的先行投入, 收益的后期获得
面向对象技术包含了很多方法学上的进步
面向对象技术的前期投入很多——主要是培训程序员用很新的方法思考, 同时还要把函数打造成通用的类
我认为它的好处是客观实在的, 并非仅仅是推测
面向对象应用在整个开发周期中, 但是真正的获益只有在后续开发、 扩展和维护活动中才能体现出来
往往很多人不愿意持续的投入
重用的情况怎样? 重用是一件说起来容易,做起来难的事情
它同时需要良好的设计和文档
即使我们看到了并不十分常见的优秀设计,但如果没有好的文档,我们也不会看到能重用的构件
对整个重用现象,我变得有些气馁
对于重用,现有理论几乎是整体缺乏
时间证明了使模块能够重用的成本非常高
最后作者再次重申,子弹的本质——形势没有发生改变,复杂性是我们这个行业的属性,而且复杂性是我们主要的限制,目前为止还是没有出现任何银弹
写在最后软件工程可能是人类创造出的最错综复杂的一项思维和智力活动,一方面复杂度是因为软件产品立足解决复杂的现实社会中的问题,另外一方面更是因为计算机只能识别0-1这种简单计算,不擅长进行复杂的问题处理
虽然没有银弹,但我们不要气馁,我们能做的是不断总结前人的优秀成功/失败经验,学习更新的的技术,更好的管理方法,用科学的方法去正确对待软件开发
人月银弹神话系列软件(软件银弹复杂度困难抽象)
(图片来源网络,侵删)

联系我们

在线咨询:点击这里给我发消息