前言
我本人是在一个不大不小的创业公司工作,公司致力于提供标准化、高质量产品服务多家客户,我们的开发资源(研发)十分有限且以个位数的状况已经维持了数年,短时间内我们没有扩充研发团队规模的计划,这个短时间在我看来可以是以年为单位。
我的职位是后端开发,工作内容包括但不限于:架构设计、业务代码实现、解决疑难的技术问题和及时响应客户反馈的问题,基本上你可以把我看作是一个扳手,哪里不好拧我就去拧哪里;当然我还有另外一个title,当需要有人来承担责任时,我又变成了一个后端负责人的角色。
如上所述,我的岗位职责要求我还需要考虑代码之外的事情,比如说要对业务负责;本着对业务负责的态度,我会仔细审视产品侧推过来的每个需求,有时候会为一个小的改动点与产品发生激烈的争执。从24年开始,随着客户的进入、功能越做越深、业务逻辑越来越复杂,项目代码无论是在复杂度上还是体量上都急剧增加,每一次核心功能的改动都让我有一种如履薄冰的感觉,我心中隐隐出现了一个模糊的声音:系统复杂度与日俱增,研发资源有限,这样的系统会走向一个怎样的终点呢?
这个声音从25年开始日渐清晰和强烈,我尝试尽可能的降低系统复杂度提升的速度,从需求侧过滤掉对现有核心逻辑冲击非常大、会显著增加系统复杂度的需求,这样做的一个直接后果就是给项目经理和产品造成很大的压力也导致我们之间关系比较紧张;后来我也意识到在业务驱动的系统内,技术人员既当裁判又当运动员的这种方式本省就存在巨大的隐患:技术人员不是直接面对企业和用户,无法获取真实的诉求;如果全部按照技术人员来决定一个需求做还是不做,我相信大部分情况下都是不做或者说不想做的。所以我尝试以一种更高维的视角来理解系统的复杂度,试图在复杂度与需求之间取得一种平衡。
说来惭愧,作为一个非科班出生但是从业7年的开发者,从来没有系统的了解过软件工程;但总归是亡羊补牢犹未为晚,我开始阅读关于软件工程复杂性的书籍和论文,把这些书籍和论文按照时间线的进行排列,最早的可能就是1987年美国佛瑞德·布鲁克斯教授撰写的:No Silver Bullet—Essence and Accidents of Software Engineering,这篇论文中的一些观点太过悲观以现在的视角来看已经不合事宜,但是作为软件工程领域的重磅之作仍能给我们带来深刻的启发,在这里分享给大家。
以下是对原文的翻译:
摘要
所有软件的构建都涉及基本任务和偶然任务,前者是建立构成抽象软件实体的复杂概念结构,后者是用编程语言表示这些抽象实体,并在空间和速度的限制下将这些实体映射到机器语言上。过去,软件生产率的大幅提升大多来自于消除了使偶然任务异常困难的人为障碍,如严重的硬件限制、笨拙的编程语言、机器时间的缺乏等。与基本任务相比,软件工程师现在所做的工作中有多少仍然是偶然性的?除非占全部工作的 9/10 以上,否则将所有偶然活动的时间缩减为零不会带来数量级的改进。
因此,现在似乎到了解决软件任务的关键部分的时候了,这些部分涉及到形成非常复杂的抽象概念结构。我建议:
- 利用大众市场,避免构建买得到的东西。
- 在确定软件需求时,将快速原型开发作为计划迭代的一部分。
- 有机地发展软件,随着系统的运行、使用和测试,为其添加越来越多的功能。
- 发掘和培养新一代优秀的概念设计师。
前言
在我们民间传说中充满恶梦的所有怪物中,没有比狼人更令人恐惧的了,因为他们会出其不意地从熟悉的人变成恐怖的怪物。对于狼人,我们需要的是能让他们安息的银弹。
我们熟悉的软件项目就有这样的特点(至少在非技术经理看来是这样),通常是天真无邪、直截了当的,但也有可能变成一个进度延期、预算泡汤、产品有缺陷的怪物。因此,我们听到了绝望的呼声,希望找到一种银弹,能让软件成本像计算机硬件成本一样迅速下降。
但是,当我们把目光投向将来,我们并没有看到什么灵丹妙药。无论是在技术还是在管理技巧方面,都没有任何一项发展本身能在生产率、可靠性和简便性方面带来哪怕一个数量级的改进。在本章中,我们将对软件问题的性质和所提出的 "灵丹妙药 "的特性进行研究,试图找出其中的原因。 不过,怀疑并非悲观。尽管我们看不到任何惊人的突破,甚至认为这不符合软件的本质,但许多令人鼓舞的创新正在进行之中。只要我们坚持不懈地努力开发、传播和利用这些创新,就一定能取得巨大的进步。世上没有坦途,但确有可行之路。
人类能克服疾病的第一步,就是以细菌说(germ theory)淘汰了恶魔说(demon theory)和体液说(humours theory),正是这一步,带给了人类希望,粉碎了所有奇迹式的冀望,告诉人们进步是要靠按部就班、不辞劳苦而来,得在清洁卫生方面持续不断地投入心血,养成良好习惯,才是正道。今天的软件工程也是如此。
一定要困难重重吗?- 本质困难
现在不仅没有灵丹妙药,软件的本质也决定了不可能有任何发明能像电子技术、晶体管和大规模集成技术对计算机硬件的影响那样,对软件的生产率、可靠性和简易性产生影响。我们不能指望每两年就能看到两倍的增长。
首先,我们必须看到,反常现象并不是软件进步如此缓慢,而是计算机硬件进步如此之快。自人类文明诞生以来,没有任何其他技术能在 30 年内实现六个数量级的性价比提升。在其他任何技术中,人们都无法选择从性能提高或成本降低中获取收益。这些进步源于计算机制造从组装工业向流程工业的转变。
其次,为了了解我们可以期待软件技术取得怎样的进步,让我们来研究一下它的困难。按照亚里士多德的说法,我把它们分为本质--软件本质中固有的困难--和偶然--软件生产过程中出现的、但并非固有的困难。
我将在下一节讨论偶然,首先让我们考虑本质。
软件实体的本质是由相互关联的概念构成的:数据集、数据项之间的关系、算法和函数调用。这种本质是抽象的,因为概念结构在许多不同的表述中都是相同的。然而,它又是高度精确和丰富详尽的。
我认为,构建软件的难点在于对这一概念结构的规范、设计和测试,而不是表述它和测试表述的保真度。当然,我们仍然会犯语法错误,但与大多数系统中的概念性错误相比,这些都是小毛病。 如果这是真的,那么构建软件将永远是困难的。从本质上讲,没有什么灵丹妙药。
让我们来看看现代软件系统这一不可还原本质的固有特性:复杂性、一致性、可变性和不可见性。
复杂性
就其规模而言,软件实体可能比人类构造的任何其他实体都要复杂,因为没有两个部分是相同的(至少在语句级以上)。如果有,我们就把两个相似的部分合二为一,成为一个开放或封闭的子程序。在这一点上,软件系统与计算机、建筑或汽车有着很大的不同,在这些系统中,重复的元素比比皆是。
数字计算机本身比人们制造的大多数东西都要复杂;它们有非常多的状态。这使得构思、描述和测试它们变得困难。软件系统的状态数量要比计算机多得多。 同样,软件实体的扩展并不仅仅是相同元素在更大尺寸上的重复;它必然是不同元素数量的增加。在大多数情况下,这些元素以某种非线性方式相互影响,整体复杂性的增加远远超过线性增加。
软件的复杂性是本质属性,而非偶然属性。因此,对软件实体的描述如果抽象掉其复杂性,往往就会抽象掉其本质。三个世纪以来,数学和物理科学通过构建复杂现象的简化模型,从模型中推导出属性,并通过实验验证这些属性,取得了长足的进步。这种方法之所以行之有效,是因为模型中忽略的复杂性并非现象的本质属性。当复杂性是本质时,这种方法就行不通了。
软件产品开发中的许多经典问题都源于这种基本的复杂性及其随规模的非线性增长。复杂性带来了团队成员之间沟通的困难,从而导致产品缺陷、成本超支和进度延误。复杂性带来了枚举程序所有可能状态的困难,更不用说理解这些状态了。函数的复杂性带来了调用这些函数的困难,从而使程序难以使用。结构的复杂性带来了在不产生副作用的情况下将程序扩展到新函数的困难。结构的复杂性带来了不可视化的状态,这些状态构成了安全陷阱门。
复杂性不仅带来技术问题,也带来管理问题。这种复杂性使概述变得困难,从而妨碍了概念的完整性。它使我们难以找到和控制所有的枝节。它造成了巨大的学习和理解负担,使人员流动成为一场灾难。
一致性
面对复杂性的不只是软件人员。即使在 “基本”粒子层面,物理学也要处理极其复杂的对象。然而,物理学家坚信,无论是在夸克还是在统一场理论中,都能找到统一的原理,并以此为信念继续努力。爱因斯坦反复论证说,大自然一定有简化的解释,因为上帝不是在掷筛子。
没有这样的一个信念能让软件工程师感到宽慰。他们必须掌握的诸多复杂性,大多是人为强加的任意复杂性,这些复杂性毫无逻辑与缘由,是由软件工程师所对接的众多人类机构和系统强行施加的。这些机构和系统各不相同,且随时间不断变化,它们之间的差异并非出于必要,而仅仅是因为它们是由不同的人设计的,而非由上帝创造。
在许多情况下,软件之所以必须进行适配(此处“confirm”结合语境译为“适配”更合适,原词有“确认、符合”之意,这里强调软件与场景或接口的匹配),是因为它是最近才进入该领域的。在其他情况下,软件之所以必须遵循(“conform”译为“遵循、符合”)相关规范,则是因为它被视为最易于适配的。但无论如何,大量的复杂性都源于与其他接口的适配;仅靠对软件本身进行任何重新设计,都无法消除这种复杂性。
可变性
软件实体不断承受着要求其变更的压力。当然,建筑物、汽车和计算机亦是如此。不过,制成品在制造完成后很少会进行改动;它们通常会被后续型号所取代,或者将必要的改动纳入同一基本设计的后续序列号版本中。汽车召回事件其实相当罕见,计算机的现场改动也略少一些。但这两者的改动频率都远低于已部署软件的修改频率。
部分原因在于,系统中的软件体现了其功能,而功能正是最易感受到变更压力的部分。另一部分原因在于,软件更容易修改——它纯粹是思想的产物,具有无限的可塑性。建筑物实际上也会进行改动,但所有人都能理解改动的成本高昂,这在一定程度上抑制了改动者的随意性。
所有成功的软件都会被修改。这背后有两个过程在起作用。当人们发现一款软件产品很有用时,就会在原始应用领域的边缘或超出该领域的全新场景中尝试使用它。要求扩展功能的压力主要来自那些喜欢基本功能并为其发明新用途的用户。
其次,成功的软件还会在其首次适配的机器载体正常生命周期结束后继续存在。即使没有新计算机问世,至少也会有新磁盘、新显示器、新打印机出现;而软件必须适应这些新的应用载体。
简而言之,软件产品嵌入在一个由应用、用户、法规和机器载体构成的文化矩阵中。这些因素都在不断变化,它们的变迁不可避免地迫使软件产品做出相应的改变。
不可见性
软件无形且难以直观呈现。几何抽象是强大的工具。建筑物的平面图有助于建筑师和客户评估空间布局、人流走向和视野景观。其中的矛盾之处会显而易见,疏漏之处也能被及时发现。机械零件的图纸和分子的棍状模型,尽管都是抽象形式,却也发挥着同样的作用。几何现实通过几何抽象得以捕捉和呈现。
而软件的现实本质并不天然地嵌入于空间之中。因此,它不像土地有地图、硅芯片有电路图、计算机有连接示意图那样,具备现成的几何表现形式。一旦我们尝试绘制软件结构图,就会发现它并非由单一图形构成,而是由多个一般有向图叠加而成。这些不同的图形可能分别代表控制流、数据流、依赖模式、时间顺序、命名空间关系等。这些图形通常甚至不是平面的,更不用说层次化的了。实际上,要实现对这种结构的概念性掌控,其中一种方法就是强制切断某些连接,直到其中一个或多个图形变为层次化结构。
尽管在限制和简化软件结构方面已取得一定进展,但软件本质上仍然难以直观呈现,从而剥夺了思维中一些最强大的概念工具。这种缺失不仅阻碍了个人内部的设计过程,还严重妨碍了人与人之间的交流沟通。
过往突破解决的是偶然性困难
若我们审视过去软件技术领域最具成效的三大进展,便会发现,每一项进展都针对软件构建过程中的不同主要难题发起了攻势,但这些难题均为偶然性而非本质性的。同时,我们也能洞察到,对每一项攻势进行外推时所存在的自然局限性。
高级语言。毋庸置疑,在提升软件生产效率、可靠性和简化程度方面,高级语言的逐步采用堪称最具影响力的举措。多数观察者认为,这一发展至少使生产效率提升了五倍,同时在可靠性、简洁性和可理解性方面也带来了相应提升。高级语言究竟实现了什么?它使程序摆脱了诸多偶发性复杂度。抽象程序由概念性构造组成,包括操作、数据类型、序列和通信。而具体的机器程序则涉及比特、寄存器、条件、分支、通道、磁盘等。高级语言在多大程度上体现了抽象程序中所需的构造,并避免了所有更低层次的构造,它就在多大程度上消除了整整一层原本就不属于程序本质的复杂度。
高级语言所能做到的极致,就是提供程序员在抽象程序中所设想的全部构造。诚然,我们在思考数据结构、数据类型和操作方面的成熟度正在稳步提升,但提升的速度却在不断放缓。而且,语言的发展正日益接近用户的成熟度水平。
此外,在某个节点上,高级语言的精细化会成为一种负担,它非但不会减轻,反而会增加那些很少使用深奥构造的用户的智力任务。
分时系统。尽管分时系统所带来的提升不如高级语言显著,但多数观察者仍认为,它在提升程序员生产效率和产品质量方面发挥了重要作用。
分时系统针对的是截然不同的难题。分时系统保留了即时性,从而使我们能够保持对复杂性的整体把握。批处理编程的缓慢周转意味着,当我们停止编程并调用编译和执行时,不可避免地会忘记之前思考的细节,甚至可能忘记核心要点。这种意识的中断在时间上代价高昂,因为我们必须重新梳理思路。最严重的影响可能是,对复杂系统中正在发生的所有事情的掌控力逐渐减弱。
与机器语言的复杂性一样,缓慢周转也是软件过程中的偶然性而非本质性难题。分时系统的贡献存在直接限制。其主要效果是缩短系统响应时间。当响应时间趋近于零时,在某个点上会达到人类可感知的阈值,大约为100毫秒。超过这个阈值,便无法期待更多益处。
统一的编程环境。Unix和Interlisp作为首批得到广泛应用的集成编程环境,被认为通过整体提升使生产效率得到了倍数级的增长。为何如此?
它们通过提供集成库、统一文件格式以及堆栈和过滤器,解决了程序协同使用过程中的偶然性难题。因此,那些在原则上本就可以相互调用、馈送和使用的概念性结构,在实践中也确实能够轻松实现。
这一突破反过来又刺激了整个工具集的开发,因为借助标准格式,每一项新工具都可以应用于任何程序。
由于这些成功,编程环境成为了当今软件工程研究的重点。我们将在下一节中探讨它们的潜力和局限性。
“银弹”之盼
现在,让我们来审视那些常被吹捧为潜在“银弹”的技术发展。它们针对的是哪些问题?这些问题究竟是本质性的,还是我们偶然性难题的遗留?它们带来的是革命性的进步,还是渐进式的改进?
Ada及其他高级语言的发展。近期备受推崇的一项发展是编程语言Ada,这是一款20世纪80年代的通用高级语言。Ada确实不仅反映了语言概念上的进化改进,还融入了鼓励现代设计和模块化概念的功能。或许,Ada的理念相较于Ada语言本身更具进步性,因为它体现的是模块化、抽象数据类型和层次化结构的理念。Ada或许过于丰富,这是对其设计提出要求过程中自然产生的结果。这并非致命问题,因为子集工作词汇可以解决学习难题,而硬件的进步将为我们提供廉价的MIPS(每秒百万条指令),以支付编译成本。提升软件系统的结构化水平,确实是我们投入资金所换来的增加MIPS的一个极佳用途。20世纪60年代,操作系统因占用内存和计算周期而饱受诟病,但事实证明,它们是利用过去硬件激增所带来的部分MIPS和廉价内存字节的绝佳形式。
尽管如此,Ada并不会成为消灭软件生产效率难题的“银弹”。毕竟,它只是另一种高级语言,而这类语言带来的最大回报源于首次从机器的偶发性复杂度向更具抽象性的逐步解决方案表述的转变。一旦这些偶发性问题被消除,剩余的问题将更小,消除它们的回报也肯定会更低。
我预测,十年后,当评估Ada的有效性时,人们会发现它确实带来了实质性的改变,但这并非因为任何特定的语言特性,也并非因为所有特性的综合作用。新的Ada环境也不会成为改进的原因。Ada的最大贡献将在于,转向使用它促使程序员接受了现代软件设计技术的培训。
面向对象编程。许多软件技术的研究者对面向对象编程寄予的希望,超过了对当下任何其他技术潮流的期望。我也是其中之一。达特茅斯学院的马克·谢尔曼(Mark Sherman)指出,我们必须谨慎区分这一名称下的两个独立概念:抽象数据类型和层次化类型(也称为类)。抽象数据类型的概念是,一个对象的类型应通过名称、一组适当值和一组适当操作来定义,而非通过其存储结构来定义,存储结构应被隐藏。Ada包(带私有类型)或Modula的模块就是例子。
层次化类型,如Simula-67中的类,允许定义通用接口,这些接口可以通过提供从属类型来进一步细化。这两个概念是正交的——可能存在没有隐藏的层次结构,也可能存在没有层次结构的隐藏。这两个概念都代表了构建软件技术方面的真正进步。
它们各自从过程中消除了又一个偶发性难题,使设计者能够表达设计的本质,而无需表达大量不添加新信息内容的语法材料。对于抽象类型和层次化类型,结果都是消除了更高层次的偶发性难题,并允许更高层次的设计表达。
尽管如此,这类进步最多只能消除设计表达中的所有偶发性难题。设计本身的复杂性是本质性的;这类攻击对此毫无改变。只有当今天编程语言中剩余的类型规范这一不必要的底层细节本身就占据了设计程序产品所涉及工作的十分之九时,面向对象编程才能带来数量级的提升。对此,我表示怀疑。
人工智能。许多人期待人工智能领域的进步能带来革命性的突破,从而在软件生产效率和质量上实现数量级的提升。而我并不这么认为。要明白其中缘由,我们必须剖析“人工智能”的含义,并探究其应用方式。
帕纳斯(Parnas)对术语的混乱进行了澄清:
当下,人工智能有两种截然不同的定义被广泛使用。AI-1:利用计算机解决以往只能通过运用人类智能才能解决的问题。AI-2:运用一套特定的编程技术,即启发式或基于规则的编程。这种方法通过研究人类专家来确定他们在解决问题时所使用的启发式方法或经验法则。程序的设计旨在模仿人类解决问题的方式。
第一个定义的内涵具有可变性。如今,某事物可能符合AI-1的定义,但一旦我们了解了程序的工作原理并理解了问题,我们就不会再将其视为人工智能。遗憾的是,我无法确定这一领域独有的技术体系。大部分工作都是针对特定问题的,而且需要一定的抽象能力或创造力才能明白如何将其迁移应用。
我完全赞同这一批评。用于语音识别的技术与用于图像识别的技术似乎鲜有共通之处,而这两者又都与专家系统中使用的技术不同。例如,我很难看出图像识别技术将如何对编程实践产生显著影响。语音识别技术亦是如此。构建软件的难点在于决定要表达什么,而非如何表达。任何表达上的便利所带来的提升都只是微乎其微的。
专家系统技术,即AI-2,值得单独设立一个章节来探讨。
专家系统。在人工智能领域中,技术最为先进且应用最为广泛的部分,当属构建专家系统的技术。众多软件科学家正致力于将此技术应用于软件开发环境。那么,这一概念究竟是什么?其前景又如何呢?
专家系统是一个程序,它包含一个通用推理引擎和一个规则库,旨在接收输入数据和假设,并通过从规则库中推导出的推理来探索逻辑后果,进而得出结论和提供建议,同时还能通过向用户追溯其推理过程来解释其结果。推理引擎通常不仅能处理纯粹的确定性逻辑,还能处理模糊或概率性的数据和规则。
与采用编程算法解决相同问题相比,此类系统具有一些明显的优势:
- 推理引擎技术是以与应用无关的方式开发的,随后可应用于多种场景。因此,人们可以在推理引擎上投入更多精力。事实上,这项技术已经相当成熟。
- 应用中特定材料的可变部分以统一的方式编码在规则库中,并提供了用于开发、更改、测试和记录规则库的工具。这使应用本身的复杂性得到了很大程度的规范。
爱德华·费根鲍姆(Edward Feigenbaum)表示,此类系统的强大之处并非源于日益复杂的推理机制,而是源于日益丰富的知识库,这些知识库更准确地反映了现实世界。我认为,这项技术带来的最重要进步是将应用复杂性与程序本身相分离。
那么,如何将这一技术应用于软件开发任务呢?这可以通过多种方式实现:提供界面规则建议、就测试策略提供咨询、记忆特定类型错误的频率、提供优化提示等。
以一个假想的测试顾问为例。在最基本的形式下,诊断专家系统非常类似于飞行员的检查清单,从根本上为可能出现的困难原因提供建议。随着规则库的发展,这些建议会变得更加具体,能够更全面地考虑所报告的故障症状。我们可以想象一个调试助手,它最初提供非常泛化的建议,但随着规则库中体现的系统结构越来越多,它生成的假设和推荐的测试会变得越来越具体。此类专家系统与传统系统最显著的不同之处可能在于,其规则库应按照相应软件产品的层次化模块方式进行模块化,这样,随着产品的模块化修改,诊断规则库也可以进行模块化修改。
生成诊断规则所需的工作,实际上也是为模块和系统生成测试用例集时必须完成的工作。如果以一种适当通用的方式来完成这项工作,采用统一的规则结构并利用良好的推理引擎,那么它实际上可能会减少生成启动测试用例的总工作量,同时也有助于产品的终身维护和修改测试。同样,我们可以设想其他顾问——可能有很多,而且对于软件开发任务的其他部分来说,可能都是简单的顾问。
在为程序开发者早日实现有用的专家顾问方面,仍存在许多困难。我们假想场景中的一个关键部分,是开发出从程序结构规范到自动或半自动生成诊断规则的简便方法。更为困难且重要的是知识获取这一双重任务:找到善于表达、具有自我分析能力的专家,他们知道为何要这样做;以及开发出高效的技术,用于提取他们所知道的知识并将其提炼到规则库中。构建专家系统的基本先决条件是拥有一位专家。
专家系统最为强大的贡献,无疑在于将最优秀程序员的经验和积累的智慧,提供给经验不足的程序员使用。这一贡献绝非微不足道。最佳软件工程实践与平均实践水平之间的差距非常大——或许比其他任何工程学科中的差距都要大。因此,一款能够传播优秀实践的工具将具有重要意义。
“自动化”编程。近40年来,人们一直在预测并撰写关于“自动化编程”的文章,即根据问题规范的陈述生成解决问题的程序。如今,有些人又大书特书,仿佛他们期待这项技术能带来下一次突破。
帕纳斯(Parnas)暗示,这一术语的使用更多是为了吸引眼球,而非基于语义内容,他断言:
简而言之,自动化编程一直只是使用比程序员当前可用语言更高级的语言进行编程的一种委婉说法。
他认为,从本质上讲,在大多数情况下,需要给出规范的实际上是解决方案方法,而非问题本身。
当然也存在例外。构建生成器的技术非常强大,且在排序程序中经常被巧妙利用。一些微分方程积分系统也允许直接对问题进行规范。系统会评估参数,从解决方案方法库中选择合适的方法,并生成程序。
这些应用具有非常理想的特性:
- 问题很容易通过相对较少的参数来描述。
- 存在许多已知的解决方案方法,可提供丰富的替代方案库。
- 经过广泛分析,已形成了根据问题参数选择解决方案技术的明确规则。
很难想象这些技术如何能推广到普通软件系统的更广泛领域,因为在这些领域中,具有如此简洁特性的案例只是例外。甚至难以想象这种推广性的突破究竟如何才能实现。
图形化编程。图形化编程(或称可视化编程)是软件工程领域博士论文的热门主题之一,即将计算机图形学应用于软件设计。有时,人们基于超大规模集成电路(VLSI)芯片设计中计算机图形学发挥的巨大作用,来推断图形化编程方法的前景。有时,人们则将流程图视为理想的程序设计媒介,并为其构建提供强大的工具支持,以此证明该方法的合理性。
然而,这些努力至今尚未产生任何令人信服,更不用说令人兴奋的成果了。我确信,未来也不会。
首先,正如我在其他地方所论述的,流程图是对软件结构的一种非常糟糕的抽象。实际上,最好将其视为伯克斯(Burks)、冯·诺依曼(von Neumann)和戈德斯廷(Goldstine)为其提出的计算机设计一种迫切需要的高级控制语言所做的尝试。流程图如今已发展成那种可怜兮兮的、多页连接框的形式,事实证明,它作为设计工具几乎毫无用处——程序员是在编写完程序后,才绘制描述这些程序的流程图,而非之前。
其次,当今的屏幕在像素方面太小,无法同时展示任何严肃细致的软件图表的广度和分辨率。如今工作站所谓的“桌面隐喻”,实则是一种“飞机座位隐喻”。任何在两个体态丰腴的乘客之间坐着整理满膝文件的人,都会明白其中的差别——一次只能看到很少的东西。真正的桌面能提供对二十多页内容的概览和随机访问。此外,当创作灵感如泉涌时,不止一位程序员或作家会放弃桌面,转而使用更宽敞的地板。在硬件技术取得相当大的进步之前,我们显示器的显示范围还不足以胜任软件设计任务。
更根本的是,正如我在上文所论述的,软件非常难以可视化。无论我们绘制控制流图、变量作用域嵌套图、变量交叉引用图、数据流图、层次化数据结构图,还是其他任何图表,我们都只能感受到软件这只错综复杂的大象的一个维度。如果我们将多种相关视图生成的所有图表叠加在一起,也很难提取出任何全局概览。VLSI的类比从根本上说是误导性的——芯片设计是一个分层的二维对象,其几何形状反映了其本质。而软件系统则不是。
程序验证。现代编程中的大量工作都投入到测试和修复错误中。那么,是否有可能在系统设计阶段从源头上消除错误,从而找到灵丹妙药呢?在投入大量精力进行实现和测试之前,通过采用截然不同的策略来证明设计的正确性,能否同时大幅提高生产率和产品可靠性呢?
我不认为我们能在这里找到魔法。程序验证是一个非常强大的概念,对于安全操作系统内核等应用来说将非常重要。然而,这项技术并不能保证节省人力。验证工作如此繁重,以至于只有少数几个实质性程序得到过验证。
程序验证并不意味着程序无错误。这里也没有魔法。数学证明也可能存在错误。因此,尽管程序验证可能会减轻程序测试的负担,但并不能消除这种负担。
更严重的是,即使完美的程序验证也只能证明程序符合其规范。软件任务中最困难的部分是制定出完整且一致的规范,而构建程序的大部分实质内容实际上是对规范的调试。
环境与工具。从对更好的编程环境的爆炸式研究中,还能期望获得多少收益呢?人们的本能反应是,那些能带来巨大回报的问题是最先受到攻击的,并且已经得到了解决:层次化文件系统、统一文件格式以便拥有统一的程序接口,以及通用工具。特定语言的智能编辑器是尚未在实践中广泛应用的开发成果,但它们最多只能保证免受语法错误和简单语义错误的困扰。
也许编程环境中尚未实现的最大收益,是使用集成数据库系统来跟踪单个程序员必须准确回忆,以及在单个系统上的一组合作者必须保持最新的无数细节。
这项工作无疑是有价值的,并且无疑会在生产率和可靠性方面取得一些成果。但就其本质而言,从现在开始的回报必然是微乎其微的。
工作站。随着单个工作站性能和内存容量的确定且迅速提升,软件艺术能从中获得哪些收益呢?那么,一个人能有效地利用多少MIPS(每秒百万条指令)呢?如今的速度已完全支持程序和文档的编写与编辑。编译速度还有待提升,但机器速度提升10倍,无疑会让思考时间成为程序员一天中的主导活动。实际上,现在似乎就是如此。
我们当然欢迎更强大的工作站。但我们不能期望它们带来神奇的增强效果。
针对软件概念本质的有前景的攻克方向
尽管没有技术突破能带来我们在硬件领域早已习以为常的那种神奇效果,但目前仍有许多卓有成效的工作正在进行,且有望实现稳定(尽管并不引人注目)的进展。
所有针对软件过程中偶发问题的技术攻克手段,从根本上都受到生产率公式的限制:

若如我所认为的那样,当前任务中概念性构成部分占据了大部分时间,那么仅仅针对作为这些概念表现形式的任务组成部分开展再多活动,也无法大幅提高生产率。
因此,我们必须考虑那些直击软件问题本质(即构建这些复杂概念结构)的攻克方向。幸运的是,其中一些方向极具前景。
购买与自主开发。构建软件最彻底的解决方案,便是根本不进行构建。
随着越来越多的供应商为种类繁多到令人眼花缭乱的应用提供更多、更好的软件产品,每天这样做都变得越来越容易。在我们软件工程师埋头于生产方法研究之际,个人计算机革命不仅创造了一个,而是多个软件大众市场。每个报摊都售卖按月发行的杂志,这些杂志按机型分类,刊登着数十种产品的广告和评测,价格从几美元到几百美元不等。更专业的渠道则为工作站和其他Unix市场提供功能非常强大的产品。甚至软件工具和环境也能买到现成的。我曾在别处提出过建立一个个人模块交易市场的构想。
购买任何此类产品,都比重新自主开发要便宜。即便一款软件售价高达10万美元,其成本也仅约相当于一名程序员一年的工作成本。而且交付是即时的!至少对于真正存在的产品而言是即时的,这些产品的开发者可以将潜在客户推荐给满意的用户。此外,与自主开发的软件相比,此类产品往往有更完善的文档,维护也稍好一些。
我认为,在大众市场的发展是软件工程领域最深远的长期趋势。软件的成本一直是开发成本,而非复制成本。即便只在少数用户间分摊这一成本,也能大幅降低每位用户的成本。换个角度看,使用n份软件系统副本,实际上相当于将其开发者的生产率提升了n倍。这是对学科乃至国家生产率的提升。
当然,关键问题在于适用性。我能用现成的软件包来完成我的任务吗?这里发生了一件令人惊讶的事情。在20世纪50年代和60年代,一项又一项的研究表明,用户不会使用现成的软件包来处理工资单、库存控制、应收账款等事务。因为需求过于专业化,不同情况间的差异太大。而在20世纪80年代,我们发现这类软件包需求旺盛且应用广泛。是什么发生了变化?
其实并非软件包本身。它们可能比以前更通用一些、更可定制一些,但变化不大。应用领域其实也并非如此。如果说有什么变化的话,那就是如今商业和科学领域的需求比20年前更加多样、更加复杂。
最大的变化在于硬件与软件的成本比例。1960年,购买一台价值200万美元机器的买家会觉得,他有能力再花25万美元购买一个定制的工资单程序,这个程序能轻松且无干扰地融入对计算机持排斥态度的社会环境中。而如今购买价值5万美元办公设备的买家,根本不可能负担得起定制的工资单程序;因此,他们会调整自己的工资单处理流程,以适应现有的软件包。计算机如今已如此普及,即便还未达到备受喜爱的程度,人们也已理所当然地接受了这些调整。
我提出的软件包通用性多年来变化不大的观点,存在一些显著例外:电子表格和简单数据库系统。这些强大的工具,事后看来显而易见,却姗姗来迟,它们适用于众多用途,其中一些相当非传统。如今,关于如何使用电子表格处理意外任务的文章甚至书籍层出不穷。大量原本需要用Cobol或报表程序生成器编写定制程序的应用,如今都常规性地使用这些工具来完成。
如今,许多用户每天都使用计算机处理各种应用,却从未编写过程序。实际上,这些用户中有许多人无法为他们的机器编写新程序,但他们仍然擅长用计算机解决新问题。
我认为,如今对许多组织而言,提高软件生产率的最有力策略,就是为一线对计算机一窍不通的知识工作者配备个人计算机和优秀的通用写作、绘图、文件和电子表格程序,然后放手让他们去用。对于数百名实验室科学家而言,采用具备简单编程功能的同样策略也将行之有效。
需求细化与快速原型开发。构建软件系统过程中,最艰难的单一环节莫过于精准确定要构建的内容。在概念工作中,没有其他部分比确立详细的技术需求更为困难,这其中包括与人、机器以及其他软件系统的所有接口。若此部分工作出现差错,对最终系统的损害也最为严重。同样,没有其他部分在后期更难修正。
因此,软件开发者为客户所做的最重要工作,就是通过迭代方式提取并细化产品需求。事实是,客户往往不清楚自己究竟想要什么。他们通常不知道必须回答哪些问题,也几乎从未从必须明确指出的细节角度思考过问题。即便是看似简单的回答——“让新软件系统像我们旧的手工信息处理系统那样运行”——实际上也过于简单。客户从来不会完全想要这样。更何况,复杂的软件系统是具有行动、运转和工作能力的实体。这种行动的动态特性很难想象。因此,在规划任何软件活动时,都有必要将客户与设计者之间广泛的迭代纳入系统定义的一部分。
我想更进一步指出,即便是与软件工程师合作的客户,在实际构建并试用他们所指定的产品版本之前,也几乎不可能完全、精准且正确地指明现代软件产品的确切需求。
因此,当前技术努力中最具前景的方向之一,也是针对软件问题本质而非偶然性的攻克方向之一,便是开发用于系统快速原型开发的方法和工具,以此作为需求迭代指定的一部分。
原型软件系统能够模拟目标系统的重要接口并执行其主要功能,同时不必受限于相同的硬件速度、规模或成本约束。原型通常能执行应用程序的主要任务,但不会尝试处理异常情况、对无效输入做出正确响应、干净地中止等。原型的目的是将所指定的概念结构变为现实,以便客户能够测试其一致性和可用性。
当前许多软件采购流程都基于这样一个假设:即人们可以预先指定一个令人满意的系统,然后招标进行构建,并最终安装使用。我认为这一假设从根本上就是错误的,许多软件采购问题都源于这一谬误。因此,若不进行根本性修订,包括为原型和产品的迭代开发与指定提供条件,这些问题就无法得到解决。
增量开发——让软件“生长”,而非“构建”。我仍记得1958年,当我第一次听到一位朋友谈论“构建”一个程序,而非“编写”时,内心所受到的震撼。那一刻,我对软件过程的整体认知豁然开朗。这种隐喻的转换既有力又准确。如今,我们已然明白软件构建与其他构建过程何其相似,也乐于运用这一隐喻的其他元素,如规格说明、组件装配以及脚手架搭建等。
然而,“构建”这一隐喻如今已失去了其原有的效用,是时候再次做出改变了。倘若如我所认为的那样,我们如今所构建的概念结构过于复杂,难以事先精准指定,且过于繁复,无法毫无瑕疵地构建出来,那么我们就必须采取一种截然不同的方法。
让我们转向自然,研究生物体中的复杂性,而非仅仅关注人类那些死气沉沉的作品。在这里,我们会发现一些构造,其复杂性让我们心生敬畏。仅就大脑而言,其错综复杂之处便难以绘制,其强大之处难以模仿,其多样性丰富无比,且具备自我保护和自我更新的能力。其奥秘在于,大脑是“生长”出来的,而非“构建”出来的。
我们的软件系统也应如此。数年前,Harlan Mills便提出,任何软件系统都应通过增量开发的方式“生长”出来。也就是说,系统应首先能够运行,即便它除了调用一组恰当的虚拟子程序外,并无其他实际用途。随后,再逐步完善,将子程序进一步开发为具体的操作,或调用下一层级的空存根程序。
自从我在软件工程实验室课程中向项目开发者们推荐这一技术以来,便见证了最为显著的效果。过去十年间,没有什么能如此彻底地改变我自身的实践方式,或提升其实践效果。这一方法要求采用自上而下的设计,因为它是软件自上而下的“生长”过程。它便于回溯修改,适合早期原型开发。每一个新增的功能,以及针对更复杂数据或情境的新规定,都是从已有内容中有机地“生长”出来的。
这种方法对士气的影响令人惊叹。当系统能够运行起来,哪怕只是一个简单的系统,人们的热情也会瞬间高涨。当新的图形软件系统首次在屏幕上呈现出画面,哪怕只是一个矩形,人们的努力也会加倍。在开发过程的每一个阶段,我们始终拥有一个能够运行的系统。我发现,团队在四个月内能够“生长”出比“构建”出更为复杂的实体。
无论是大型项目还是我的小项目,都能实现同样的益处。
卓越的设计师。如何提升软件艺术水准这一核心问题,始终围绕着“人”这一要素展开。
我们可以通过遵循优良实践而非拙劣实践来获得优秀的设计。优秀的设计实践是可以传授的。程序员是人群中极为聪慧的一部分,因此他们能够习得这些优良实践。因此,美国的一大主要推动力便是推广现代优良实践。新的课程体系、新的文献资料,以及诸如软件工程研究所之类的新组织应运而生,旨在将我们的实践水平从拙劣提升至良好。这完全合乎情理。
然而,我并不认为我们能够以同样的方式再迈上一个新台阶。拙劣的概念设计与优秀设计之间的差异,或许在于设计方法的合理性;但优秀设计与卓越设计之间的差异,则绝非仅此而已。卓越的设计源自卓越的设计师。软件构建是一个创造性的过程。合理的方法论能够激发并释放创造性思维;但它无法点燃热情,也无法激励那些埋头苦干的人。
这种差异绝非微不足道,它更像是萨列里(Salieri)与莫扎特(Mozart)之间的差距。一项又一项的研究表明,最顶尖的设计师所设计出的结构,速度更快、体积更小、结构更简单、代码更清晰,且所需投入的努力也更少。卓越设计与普通设计之间的差距,几乎达到了数量级的差异。
回顾过往不难发现,尽管许多优秀且实用的软件系统是由委员会设计、由多方合作项目构建而成,但那些能够激发狂热粉丝热情的软件系统,却无一不是出自一位或几位杰出设计师之手。以Unix、APL、Pascal、Modula、Smalltalk界面,甚至Fortran为例;再与Cobol、PL/I、Algol、MVS/370以及MS-DOS进行对比(下图)。

因此,尽管我大力支持当前正在进行的技术转让与课程体系建设工作,但我认为,我们所能开展的最重要的单项工作,是探索培养卓越设计师的途径。
任何软件组织都无法回避这一挑战。优秀的管理者固然难得,但优秀的设计师也并不比他们更多见。卓越的设计师与卓越的管理者均属凤毛麟角。大多数组织在寻找和培养管理人才方面投入了大量精力;然而,据我所知,没有哪家组织在寻找和培养卓越设计师方面投入同等的精力,而产品的技术卓越性最终正取决于这些设计师。
我的首要建议是,每个软件组织都必须明确并宣告,卓越设计师对其成功的重要性不亚于卓越管理者,并且应当期望他们得到同样的培养与回报。不仅薪资要相当,而且在认可方面的特殊待遇——办公室面积、办公设施、个人技术设备、差旅经费、员工支持等——也必须完全对等。
那么,如何培养卓越设计师呢?篇幅所限,无法展开详细讨论,但有些步骤显而易见:
- 尽早系统地识别顶尖设计师。最优秀的人才往往并非经验最丰富者。
- 为潜力人才指定一位职业导师,负责其职业发展,并妥善保管其职业档案。
- 为每位潜力人才制定并维护一份职业发展规划,包括精心安排其跟随顶尖设计师实习、参加高级正规教育课程和短期培训,所有这些活动均穿插进行个人设计和技术领导任务。
- 为成长中的设计师提供相互交流与激发灵感的契机。