11.4 与错误打交道

尽管我很看好网络经济,但是仍然有许多令人担忧的地方,这些问题也同样存在于其他的大型、去中心化的自为[1]系统中。

它们很难被理解

广告:个人专属 VPN,独立 IP,流量大,速度快,连接稳定,多机房切换,每月最低仅 5 美元

它们不太容易受控制

它们并非最优化的

当各种公司取消实体进入某种巴洛式的赛博空间之后,它们就具有了某种类似于软件的特点。无污染、无重量、快速、有用、可移动而且有趣。但同时也可能变得非常复杂,充满了没人能查明的烦人的小毛病。

如果未来的公司和产品就跟现在的软件一样,那意味着什么?会破碎的电视机?突然熄火的汽车?会爆炸的烤面包机?

大型软件程序可能是人类现在所能制造的最复杂的东西了。微软的新操作系统有4百万条代码。当然,在7万个Beta版本的测试点进行测试之后,比尔盖茨肯定会说,现在这个软件没有漏洞了。

那么,我们是否可能制造出那种超级复杂而又没有任何缺陷(或者,只有很少几个缺陷)的东西来呢?网络式经济到底是能帮助我们创造出一种没有缺陷的复杂系统,还是只能为我们建立一个有漏洞的复杂系统?

不管各种公司自己会不会变得更像软件,至少,它们所生产的越来越多的产品肯定会依赖于愈加复杂的软件,所以说,创造没有缺陷的复杂系统是绝对必要的。

在仿真领域,验证一个仿真的真伪,与测试一个大型复杂软件是否有缺陷是同一类问题。

加拿大计算机学家戴维·帕那斯[2]曾经对里根的星球大战计划提出了8条批评意见。他的观点基于超级复杂软件内在的不稳定性,而星球大战计划恰恰就是这么一种超级复杂的软件。戴维·帕那斯的观点中,最有趣的一个是指出存在两种类型的复杂系统:连续的和非连续的。

通用汽车公司在测试新车应对急弯的性能时,会让这辆车在不同的时速下进行测试,譬如50、60、70英里。显然,性能随时速的变化是连续的。如果一辆汽车能够在时速50、60、70英里的时候通过测试,无需测试我们就会知道,在各种中间速度——比如每小时55或者67英里——的时候,它也肯定能通过测试。

他们不用担心这辆车以每小时55英里的速度行驶时会突然长出翅膀来或者翻个底朝天。它在这个速度上的性能,基本上就是它在50英里和60英里时性能的某种插值。一辆汽车就是一个连续的系统。

计算机软件、分布式网络以及绝大多数的活系统都是非连续的系统。在复杂的适应性系统中,你根本不可能依赖插值函数来判断系统的行为。你的软件可能已经平稳运行了好几年,然后突然在某些特定的值点(比如,每小时63.25英里),轰隆一声系统爆炸,或者,突变为某种全新的东西。

断点始终都存在着,而你已经测试到了所有的邻近取值,却没有测试到这特别的一组环境值。事情发生后,你会一目了然为什么这个故障会导致系统崩溃,甚至能明白地指出为什么人们本该找出这个隐患。不过,这都是事后诸葛亮。在一个拥有海量可能性的系统中,根本不可能对所有的可能性进行测试。更糟糕的是,你还不能依靠抽样的方式来对系统进行测试,因为它是非连续的系统。

对于一个超级复杂的系统来说,测试者没有任何把握说那些没测试到的值就一定会和抽样到的数据之间呈现一种连续关系。不过,尽管如此,现在还是出现了一个旨在达到「零缺陷」软件设计的运动。不用多想,这个运动肯定又是发生在日本。

对于小程序来说,这个「零缺陷」的零就是0.000。但是对于那种超大型的程序来说,这个「零」指的就是小于等于0.001。这是指每千行代码允许的错误值,而这只是产品质量的一个大概标准。这些旨在编写零缺陷软件的方法,大量借鉴了日本工程师新乡重夫的零缺陷生产的开创性工作。当然,计算机科学家们声称,「软件不一样」。软件可以被完美复制,因此只需要保证最开始的那一份是「零缺陷」就好了。

在网络式经济中,研发新产品的费用主要源自生产流程的设计,而非产品设计。日本人擅长生产流程的设计和改进,而美国人擅长的是产品的设计和改进。日本人把软件看作一个生产流程而不是产品。在渐露端倪的网络文化中,我们所生产的越来越多的东西——当然也是我们越来越多的财富——都与符号处理流程密切相关,这些流程所装配的是代码而非实物。

软件可靠性大师C.K.曹曾经告诫业界人士,不要把软件看成产品,要把它看成便携式工厂。你卖的,或者说,你给予客户的是一个工厂(程序代码),可以在客户需要的时候为他制造出一个答案。你的难题是要制造一个能生产零缺陷答案的工厂。建造能够生产出完美可靠器件的工厂的方法,也可以轻易地应用到创建能给出完美可靠答案的工厂上。

通常,软件的编制遵循三个中心化的关键步骤。首先设计一个全景图,然后用代码实现细节,最后,在接近项目尾声时,将其作为交互的整体来进行测试。而在零缺陷质量的设计流程中,整个软件编制过程不再是几个大的关键步骤,而是被分散成上千个小步骤。软件的设计、编写和测试工作每天都在成百个小工作间里进行着,每个小工作间里都有一个人在忙碌着。

这些零缺陷的传道者有一个概括网络式经济的口号:「公司里的每个人都有一个客户」。通常而言,这个所谓的客户,就是你的工作伙伴,你要将工作依次转交给他。而你必须首先把你的那个小循环(设计-编写-测试)做好,才能把它交付给你的工作伙伴——就好像你在销售商品一样。

当你把你的工作成果交付给你的客户/工作伙伴的时候,他/她就会立刻对它进行检测,并把其中的错误反馈给你,让你进行修改,让你知道你的这份工作完成的到底怎么样。从某种意义上来看,软件的这种自下向上的发展过程与罗德尼·布鲁克斯的那种包容结构本质上并无不同。每个小步骤都是一个小的代码模块,能确保自身的正常运行,在此基础上,人们叠加和测试更复杂的层级。

单靠这些小步骤并不能得到零缺陷的软件。「零缺陷」的目标隐含着一个关键的概念区分。所谓缺陷,是指被交付出去的错误;而在交付之前被修正的错误,不能算是缺陷。按新乡重夫的说法,「我们绝对不可能避免错误,但是我们可以避免错误成为缺陷」。因此,零缺陷设计的任务就是尽早发现错误,尽早改正错误。

不过,这是显而易见的事情。真正的改进在于尽早发现产生错误的原因,并尽早清除产生错误的原因。如果一个工人总是插错螺栓,那就设置一个防止插错螺栓的系统。犯错的是人,处理错误的则是系统。

日本人在防错领域的经典发明是一种称为Poka-Yoke的防错系统[3]——它可以使事情对人们所犯的错误具有「免疫力」。在装配线上设置一些巧妙而简单的装置就可以防止错误的发生。比如,在放螺栓的托盘上为每一个螺栓设定一个特别的孔位,这样,如果托盘上有螺栓剩下,操作人员就知道自己漏装了一个。在软件生产中,有一种防错设计是「拼写错误检查器」,它不允许程序员输入任何拼写错误的命令,甚至不允许他/她输入任何非法(非逻辑)命令。软件研发人员们有越来越多可供选择的非常精巧的「自动纠错程序」软件,用来检查正在编写中的程序,以防止典型错误的出现。

还有那些顶尖级的研发工具可以对程序的逻辑进行分析和评价——它会说,「嘿!这一步根本没意义!」,从而在逻辑错误一出现的时候就将其清除。有一本软件业的交易杂志最近列出了近百种检错和改错工具,沽价待售。其中最精致的一种还可以像那些优质的拼写检查软件一样,为程序员提供合乎逻辑的改错选择。

另外一种非常重要的防错方法是对复杂软件进行模块化。1982年发表在IEEE的《软件工程交易》上的一个研究显示,在其它条件完全相同的状况下,代码总行数相同的程序拆分为子程序之后,错误数量是如何减少的。一个1万行的程序,如果是一整块,它有317个错误,如果把它拆分为三个子程序,那么总数还是1万行的程序,错误数则略有减少,为265个。每拆分一次所减少的错误量,大致符合一个线性方程,所以模块化虽然不能完全解决问题,但它却是一种有效的手段。

进一步来说,当程序小到某个阈限以下之后,就可以达到完全没有错误的状态。IBM为它们的IMS系列所写的代码,就是以模块化的方式编制的,其中有四分之三的模块达到了完全没有缺陷的状态。具体来说,就是在425个模块中,有300个是完全没有错误的。而在剩下的125个有错误的模块中,有超过一半的错误集中发生在仅仅31个模块上。从这个意义上说,程序编制的模块化,就是程序的「可靠化」。

在软件设计领域,现在最热的前沿就是所谓「面向对象」的软件。一个面向对象的程序(OOP)实际上就是一个相对去中心化的、模块式的程序。对于一个OOP来说,它的一个「碎片」,就是一个独立成立、保持自身完整性的单元;它可以和其它的OOP「碎片」整合在一起形成一个可分解的指令结构。「对象」限制了程序漏洞所能造成的损害。和那种传统程序不同,OOP有效地对功能实行了隔离,把每一个功能都限制在一个可掌控的单元内,这样一来,即使一个对象崩溃了,程序的其它部分也能够继续运转,而对于传统程序来说,一个地方出了问题,整个程序就会崩溃。程序员可以把这个坏掉的单元换掉,就好象我们可以给一个汽车换刹车片一样。软件的销售商可以购买或者销售各种事先编制好的「对象」库给其它的软件研发人员,后者则可以基于这些库里的对象快速地组装起大型软件,而不用再像以前那样重新一行一行地编写新的代码。而到了要为这种大型软件升级的时候,你所要做的就是升级旧的对象或者加入新的对象。

OOP中的「对象」,其实就像乐高(Lego)积木玩具中的那些小块,但这些小块可能还带着非常微小的智能。一个对象可以类似于苹果电脑显示器上的一个文件夹图标,只不过这个图标知道自己是一个文件夹,而且可以对某个程序要求所有文件夹列出内容清单的请求作出响应。一个OOP也可以是一张税表,或者某个雇员在公司的数据库,或者某个电子邮件信息。对象知道自己能干什么不能干什么,同时也在和其它的对象横向交流。

面向对象的程序使软件具备了中等程度的分布式智能。它和其它分布式的存在一样,有一定的抗错性,能够(通过删除对象)快速修复,并且通过有效单元的组装来实现扩展。

前面提到,在IBM的代码中有31处错误。而包含这些错误的模块充分说明了软件的一个特性——错误总是扎堆出现的。我们可以利用这个特性来达到质量管理上的希格玛精度。零缺陷运动的圣经《零缺陷软件》写道,「你发现的下一个错误,极有可能出现在你已经找出了11个错误的模块里,而那些从未出过错误的模块,则可能会一直保持不败金身。」错误扎堆现象在软件中是如此普遍,以至于被当作一条「魔鬼定律」:当你发现一个错误的时候,也就意味着还有另外一堆你没看见的错误在什么地方等着你。

《零经》中提到的补救方法是这样的,「不要把钱花在错误百出的代码上,抛弃它!重写一段代码的代价和修补一个错误百出的模块的代价相差无多。如果软件的某个单元的错误率超过了一定的阈限,就把它扔掉,另找一个开发人员来重写代码。如果你手上正在编写的代码显示出某种容易出错的倾向,就放弃它,因为在前期出现错误的话,也就意味着后面还将不断地出错。」

随着软件的复杂性迅速增加,在最后关头对其进行详细检测是不可能的。因为它们是非连续的系统,所以总会隐藏着某些诡异的个例或是某种致命的响应——其被激活的几率可能只有百万分之一,无论是系统化的测试还是抽样测试都无法发现它们。另外,尽管统计抽样能够告诉我们是否有出错的可能,却无法确定出错的位置。

新生物学的解决之道是用一个个可以正常工作的单元来搭建程序,并在这个过程中不断地对其进行检测和修正。不过,我们还会面临这样的问题:尽管各个单元是没有漏洞的,但在搭建的过程中,仍然会发生意料之外的「突现行为」(即漏洞)。不过,你现在所要做的就是在更高一级的层面上进行测试(因为底层单元已经被证明是没有问题的),因而是有希望做到「零缺陷」的——这比要同时应付突现问题和深埋问题的情况要好得多了。

泰德靠发明新的软件语言谋生。他是面向对象程序语言的先行者,是SmallTalk和HyperCard的编写者,现在正在为苹果电脑研发一种「直接操作」(directmanipulation)式语言。当我问起苹果的零缺陷软件时,他一语带过:「我认为是有可能在产品化的软件中达到零缺陷的,譬如说你正在写的又一款数据库软件。只要你真正明白自己在干什么,就可以做到没有任何错误。」

泰德永远都不可能跟日本的那种软件作坊合得来。他说,「一个好的程序员可以对任何一个已知的、规律的软件进行重写,巧妙地减少代码。但是,在创造性编程过程中,没有任何已经被完全理解的东西。你不得不去编写自己也并不明白的东西……嗯,是,你是可以写出零缺陷的软件,但它会有好几千行超出所需的代码。」

自然亦是如此:它通过牺牲简洁性来换取可靠性。自然界中存在的神经元回路,其非最优化程度始终令科学家们瞠目结舌。研究小龙虾尾部神经细胞的科学家们揭示了这种回路是多么令人震惊地臃肿和丑陋。只要花点功夫,他们就能设计出一种紧凑得多的结构。不过,尽管小龙虾的尾部回路要比它真正需要的冗余很多,但却是不会出错的。

零缺陷软件的代价就是它的「过度设计」,超量建设,多少有点浮肿——永远不会处在泰德和他的朋友所经常逗留的那种未知的边缘。它是用执行效率来换取生产效率。

我曾经问诺贝尔奖得主赫伯特·西蒙[4]如何让这个零缺陷哲学与他那个「满意化」概念——不求最优,但求够好——相包容。他笑着说,「哦,你可以去生产零缺陷的产品。但问题在于你是否能够以一种有利可图的方式来生产它?如果你关心的是利润,那么你就得对你的零缺陷概念进行满意化处理。」哦,又是那个复杂性的妥协问题。

网络式经济的未来在于设计出可靠的流程,而不是可靠的产品。与此同时,这种经济的本质意味着这种流程是不可能最优化的。在一个分布式的、半活性[5]的世界中,我们的所有目标只能被「满意化」,而且这种满意也只能保持很短的一瞬。也许一天之后整个形势就完全变化了,正所谓「乱哄哄,你方唱罢我登场」。

自在(self-being)与自为(self-making):19世纪德国古典哲学家黑格尔的专门术语,用以表述绝对理念发展的不同阶段。自在反映为客观存在;自为则反映为主观映像和对自在的统一。抛开哲学概念的话,简单地说,自为可以理解为自己做主、自我管理。

戴维·帕那斯(David Parnas):世界著名的软件工程专家,现任加拿大皇家学院院士。爱尔兰利默瑞克大学计算机科学与信息系统系软件质量实验室主任,教授。

Poka-Yoke,日文中意指「失败也安全」或「错不怕」。丰田汽车在其生产系统中首先采用了这种防错体系。

赫伯特·西蒙(Herbert Simon):美国科学家,也是信息处理心理学和人工智能的开创者之一。

半活性(semiliving):原是用来描述介于有活力和无活力之间的客体属性。这里可以理解为KK一贯主张的「人造」和「天生」的混合特性。