工业可靠性:换掉分母
我当时在做一台力控工业机器人的操作员界面。客户问了我一个这些年听到的最好的问题:“车间里真正出事的,一定是我们没想到的问题,‘没想到’本来就是这个意思。可每一个可靠性估算,用的分母都是‘我们想到的那些’。那工业可靠性到底怎么保证?”
为什么“再审仔细点”永远收敛不了
审查能找到你想得到的 bug,而伤到你的 bug,按定义正是你想不到的那些。于是再审一遍。第二遍又找出几个,第三遍更少。这个数不会降到零,只会逼近“我能想到的全部”,而这完全是两回事。那个项目我已经审了五轮,有我自己审的,也有请别人用新眼光审的,每一轮都还能找出真问题。这不说明代码特别差,而是说明“再审仔细点”这条路没有尽头。第五轮还能找到 bug,第六轮也一样,你永远没法站在一台运动的机器前说“全找完了”。
数 bug 这件事赢不了,因为分母是无穷大,而且不可知。出路是换一个东西来数。
换掉分母
一句话概括:**不再去保证“我的软件没有 bug”,而是保证“就算我的软件完全错了,后果也有上限”。**前者做不到,后者做得到,因为它把无穷大的分母换成了有限的。bug 你列不全,但 bug 能造成哪几类伤害,列得全。像这样一台机器,所有坏结果都能归进五类:
- 有人受伤。 · 机器或工件被损坏。 · 已存数据丢失。 · 系统停机或界面卡死。 · 活儿做错了。
不管有多少 bug、多离奇,造成的伤害一定落在这五类里,不存在第六种。于是问题从“我想到了多少 bug?”(分母无穷,永远做不完)变成“这五类后果,每一类有没有一个不依赖我代码正确的兜底?”(分母是 5,有限、可检查、能做完)。最后这个条件最关键。和 bug 写在同一套代码里的兜底,等于自己给自己判卷。保证必须落在一个出错的软件写不到、也改不了的层上。
换个问法去审,审出了什么
项目后期,界面要把操作员设定的参数下发到机器人的运动控制器。我自己的代码已经逐行审了五遍。这次我换了个问法,把从操作员到机器动作之间的所有代码都过了一遍,包括不是我写的那部分,比如厂商平台自带的集成代码:假设这条路径是恶意的,它最坏能做什么?靠什么拦住它?
结果查出了严重的安全问题,而且不只这个项目有:同一类缺陷在很多厂商的集成代码里都能看到。简单说,来自系统外部的输入,最后能控制的东西远远超出它该有的范围。具体在哪、怎么触发,我不写,因为类似的代码装在别人的机器上,写出来等于给攻击者递工具。
五轮逐行审查都没发现它,不是因为审得不认真。按“这一行对不对”去看,代码确实没错,其中一部分甚至还没开始运行。可一旦不问“这行对不对”,改问“如果它是恶意的,兜底在哪”,问题马上就清楚了:没有兜底。不可信的输入和一台运动的机器之间,唯一挡着的是我的代码必须完美,而前面五轮审查恰恰说明我做不到这个保证。
兜底具体是什么样
- **把整类问题堵死,而不只是补一个点。**只修发现的那一处,就只修好了那一处。可靠的修法是结构性的,我现在对每一条能到达机器的路径都这样做:来自系统外部的值只能作为数据传递,绝不拼进控制器要执行的任何东西;每个值在进入的地方先按白名单和硬上限检查,控制器动作之前再查一遍;任何从外部导入的东西,完整校验之前一律不可信。输入只能是数据,并且每过一道边界都被检查,这类攻击不管从哪里来,都无从下手。
- **在你不写的那一层,在远端再核一次限值。**我的界面会把力和速度限制在安全范围内,但这个限制和可能发出错误值的代码在同一套软件里。所以控制器在它自己的程序里再独立限一次,那段程序不是我写的。界面就算发 999,机器实际也只用 40。控制器那道限制才算数,界面那道只是方便。就这一行,决定了是“代码对才安全”还是“本来就安全”。
- **一个软件越不过去的物理边界。**多数工业控制器都能设一个几何空间范围,在安全密码后面配置一次,运动系统自己就不会越出去。把它紧贴着工作单元设好,我的软件算出什么坐标都无所谓了,算对还是算得离谱,安全层都会在边界处把机械臂停住。我的应用写不到、改不了,甚至看不到这一层。对算错坐标这类 bug,这是唯一真正的兜底,因为位置没法像数字那样简单地限幅。
- **分级解锁,把“没想到”变成“在安全的情况下看到它发生”。**不会一步就从“软件控制机器”跳到“在真工件上全力加工”。中间要过几道关,每一道都给你还没发现的问题设了后果上限。干跑:机器不动,软件真实运行,把本来要发出的每条指令都记进日志,参数错了,代价只是日志里的一行。*空切:*真实运动,但工具关闭,路径抬到工件上方,路径错了,只是在空中画了个形状。废料:在一块扔了也不心疼的料上施加真实的力。最后才是生产。你没想到的那个 bug 在每一关都还在,它从没被移除,但它会在后果有上限的那一关暴露出来。
这才是工业可靠性真正的意思。不是机器不会出问题,它一定会出。而是问题出现时,是在工件上方的空中、工具关着,没有人受伤。
该说出来却很少有人说的话
我觉得每个做“会动的东西”的供应商,都应该愿意说这样一句话:我们的目标不是“没有 bug”。bug 一定会有,其中有些我们永远找不到。我们做的是:bug 一旦触发,要么被拦住,要么后果有上限,要么在物理上根本不可能造成伤害。这听起来像在示弱,我认为恰恰相反。承诺软件没有 bug 的人,要么是天真,要么是在推销。能给你看分级解锁、两道独立限幅、机械臂出不去的那个空间范围的人,才真正想过出错的那一天,而那一天才是要紧的。“无 bug”是营销说法,“后果有界”是一种设计。
根据一次真实的力控机器人集成项目整理,客户、厂商和产品细节均已删除。方法是通用的:只要软件出错会带来物理上的或不可逆的后果,从运动控制到支付再到部署,都适用。
这套东西的出处
这些都不是我发明的,是安全工程里早有的做法,我只是用大白话讲出来:
- *“测试只能证明 bug 存在,不能证明 bug 不存在。”*Edsger Dijkstra,1969 年。“没预见到的东西没法测”就出自这里。
- Nancy Leveson,《Engineering a Safer World》(MIT Press),讲为什么安全不等于可靠,以及为什么要约束整个系统,而不是指望每个部件都完美。
- IEC 61511 保护层分析(LOPA):独立兜底背后的工业标准(控制系统失效时,安全层也必须继续工作)。