软件工程的史前时代 -- Therac-25 事件
“软件工程”诞生至今的50年间发生了很多小故事。有些有趣,有的沉重。今天开始我来整理一些故事,选几个有代表意义讲出来,让对软件工程感兴趣朋友们可以更多的了解我们的“软件工程”。
软件工程是一门现代化的学科,它的诞生源自上个世纪的“软件危机”。说起那场让软件开发从“蛮荒”进化到“现代”的危机,其中有一个故事不得不提,那就是“Therac-25 事件”。
上面就是Therac-25,它是一台于1985年制造的放疗设备,一台用来给癌症病人做放射性治疗的设备。由加拿大原子能公司(AECL)制造。它核心的软件程序是用来控制放疗的辐射量。
这台机器的投入使用的短短18个月内,因为软件的重大bug,造成了4人死亡,2人重伤,震惊了软件界。迫使软件工程进行巨大的改进,制定了软件测试标准。
case 1
1985年6月3日,美国乔治亚州的Kennestone肿瘤中心里,一名61岁的老妇人因为身患乳腺癌,她正在接受放射性治疗。她安静的躺在床上,机器照射她的左侧锁骨,刚通上电,她就感受到了肩膀的灼热和疼痛,她实在忍受不了,大喊大叫。旁边控制室的医生Tim Still见状赶紧关闭了机器,可他无法解释原因,只是怀疑是辐射过量。他只好打电话询问制造商AECL,询问是不是因为辐射过量?可惜被AECL否认。
AECL的否认暴露了系统工程对危机反应的忽略,AECL如果当时可以对Tim Still医生反应的问题进行及时的跟踪和记录,有助于减少后面发生问题的风险。
以软件工程的角度看,软件产品在发布后并不代表软件开发生命周期的结束,软件在发布后用户反应的问题更应该重视,将用户反应的问题及时记录到issue 并转QA,使得反应的问题成为质量控制过程的一部分。
后来的事故调查表明,她所受到的辐射量是治疗方案中的100倍,虽然她还活着,但因为辐射的关系,她永远的失去左侧乳房和左臂。
case 2
一个月后,远在加拿大安大略的患宫颈癌患者,40岁,女性,在接受治疗过程中机器突然停机,并出现了“HTILT”错误。同时控制台显示“No dose”(无剂量)和治疗暂停,操作人员见状,按键盘让机器继续运行,可是同样的操作继续发生,在发生了5次之后,机器开始自动重启。这名女士当时就感受到了强烈的烧灼感和电击麻刺感。5个月后病人死亡。以后的调查报告表明,病人在治疗过程中收到的辐射剂量是150Gy,而对人体而言辐射剂量达到10Gy就已经是致命的了。
因为开发人员的极度自信,过分的相信自己写的程序。这两起事故并没有引起工程师的足够重视,开始的事故调查中没有非常明确的证据条件下,就确定事故已经查明原因,认为加拿大的这次事故主要原因是机器开关问题。这份调查有两个主要问题,
忽略了一个系统对其它可能性相关因素的分析;
错误的认为改正了一个错误就会解决此类问题的再次发生。我们现在的工程师其实都很清楚,这种“头痛医头,脚痛医脚”的做法只能让bug一个接一个地不断暴露。
从软件工程的角度看,这次的案例中的调研人员忽略了软件的重要性,安全分析没有考虑到软件的,分析者将注意力完全放在了硬件上面,然而,实际情况软件已经应用在各个关键逻辑环节,没有软件硬件不在能够独立的承担系统智能。
从质量管理看,后续的设备改进也缺少科学性,因为没有合理的质量标准,改进后的质量无法得到验证。仅仅改进的微开关后,AECL就对外宣称已经将安全性提高了5个数量级。可安全性数量级的定义无法验证。
case 3
1986年3月,男性,背部肿瘤,在美国东德克萨斯州ETCC泰勒医院接受Therac-25到治疗,操作人员熟练的操作着这台设备,启动机器后,机器很快停机,并且显示“Malfunction 54”。简陋的操作界面如下。
这个代码在操作说明书里面的解释是“能量已经发射,可能过高或过低”。这段含义不清的描述,没有引起操作员的注意,操作人员只是熟练的对机器进行了恢复和重启。可这时治理仓内的病人已经被机器灼烧的疼痛难忍,跳下了病床,并敲打房门。治疗被迫终止,5个月后病人死亡。后面的调查发现该病人的辐射照射量是致死量的25倍。
这件事故和后续的处理,从软件工程的角度看暴露了当时软件开发和系统工程领域的如下问题,
软件设计缺少失效性的保护。系统的安全性必须有独立的模块保证,不管软件出错与否系统级别的安全性必须得到保证。
同样的时间,芝加哥大学联合辐射中心的医生在使用Therac-20进行实验时,因为经常出现保险丝烧毁或继电器短路的事件,实质与Therac-25的故障完全相同,但由于Therac-20有硬件电路的保护没有发生过灾难性的影响。
2. 软件研发中缺少关键性的测试用例,软件测试被忽视。因为复杂的软件系统无论如何设计都会存在错误或bug。而软件测试的目的就是为了证明程序有错,现代软件过程中测试的比重也越来越重,往往测试覆盖到的时间是开发时间时间的1/3左右。这里的测试时间不仅仅表示的是测试人员独立软件测试的时间,目前的敏捷的软件工程,测试的工作很多会拆分成更小的工作量,比如单元测试、模块测试等,这些工作伴随着开发人员边做边测。
因为缺少关键性的软件测试用例,AECL的工程师并不能复现错误。还好当时操作员准确的记着发生事故操作过程,经过一段时间努力,终于重现错误信息“Malfuncation 54”。事故的原因是操作员的操作速度过快达到了临界编辑速度,这时就会导致系统异常辐射量达到饱和量。
当时无论是系统还有另一个核心的保护模块还是系统的测试可以更加完善,其实都可以避免这次悲剧。
3. 软件的界面不够友好。简陋的软件界面将错误代码直接反馈给操作员,并且软件文档的内容也有缺失。简陋错误反馈导致系统严重错误的时候,操作员没有合理判断信息,重启的机器。直到患者大喊大叫时才意识到了问题。
对比看来如今的软件操作流程设计的还比较好,交互式的操作,引导用户使用软件。防御性的操作流程,让用户在绝大部分时间都不会操作出错。
case 4
1986年4月11日,美国东部德克萨斯州。一位患有面部皮肤癌的男性患者正在使用Therac-25 做放射性治疗,因为依然是同一个操作员操作,操作的过程几乎完全相同。刚刚开始治疗,一团巨大的光亮就出现在他的眼前,耳边响起了煎鸡蛋的声音,其实这是机器灼烧了他的大脑和脑干的右侧额叶。可怜的病人感觉自己的脸上仿佛着火了一下,他剧痛的大声喊叫。因为严重的辐射,三周后这名病人就死亡了。
因为之前的案例没有得到AECL和医院的重视,悲剧一再地发生。类似悲剧一共发生了6起,直到1987年雅基马谷医院的最后一次事故,整个悲剧事件才结束。
后期的调查表明,全部的医疗事故都是因为「软件包含严重的bug」,放射性治疗的机器在正常情况下只会发射低能量的电子束,旧型号的机器为了保证不出意外,使用硬件互锁的机制确保能量不会升高。而新的型号Therac-25为了降低成本,改用了软件锁机制。可该程序本身包含的一个一字节的计数器常常会溢出,如果操作员恰好在溢出位输入命令,软件锁的机制就会失效。导致了悲剧,患者受受到100倍的辐射剂量,痛苦的死亡。
这份严重错误的代码「因为工程师的过度自信」和「错误的编码习惯」,没有通过充分的测试就被安装到设备中,因为缺少标准的质量体系,代码的质量无法保证。本应该有个“熔断机制”保证无论如何不能发生辐射量过大的情况,这也在开发中没有实现。
教训
只从软件工程的角度看,Therac-25 事件违反了软件工程的以下要求: 缺少信息准确和完善的软件文档和用户使用手册。 软件质量保证体系没有标准,且不完善。(用用户的喊叫声判断执行的准确,这是血的教训)
❝系统设计复杂。 缺少模块测试。 缺少安全性的分析和程序设计,缺少防御性编程。 ❞
后来人们为了避免发生类似Therac-25悲剧,推出了IEC 62304 软件测试标准。
我们软件工程师应当从Therac-25事件中吸取教训,我们写的代码并不是说仅仅可以运行就没问题了,边界条件的注意,防御性编程的思考,软件文档的完善与准确,和对测试工作的重视,都是对我们自己的要求。
可是如今,每次有人提到这个问题,总有不同的反对声音,其中一类就是,“现在我们使用了很多先进的语言和IDE,以上的问题都会避免”。真的吗?请大家想想2019年的「波音730MAX」吧,这场事故和Therac-25事件有多少相似的地方?
版权声明: 本文为 InfoQ 作者【王泰】的原创文章。
原文链接:【http://xie.infoq.cn/article/db61ee074eff2a24518dc0c85】。文章转载请联系作者。
评论