1.3 软件可靠性工程

随着计算机及软件技术的发展,20世纪70年代军事武器、航空航天和工业设备中软硬件耦合度越来越高,软件规模越来越大,软件复杂性也越来越高,软件在整个产品中的功能比重越来越大。软件可靠性与安全性问题日益突出。同时,软件可靠性比硬件可靠性更难保证,即使是美国宇航局的软件系统,其软件可靠性仍比硬件低一个数量级。本节介绍软件可靠性工程的产生和发展。

1.3.1 软件可靠性工程的概念

软件可靠性(Software Reliability)是指在规定的条件下和规定的时间内软件不引起系统失效的概率。业界有一个更传统的定义:软件可靠性是软件产品在规定的条件下和规定的时间内实现规定功能的能力。规定的条件是指直接与软件运行相关的、使用该软件的计算机系统的状态和软件的输入条件,或统称为软件运行时的外部条件;规定的时间是指软件实际运行的时间;规定功能是指为提供给定的服务,软件产品所必须具备的功能。

举例来说,规定的条件如一个软件部署在宇宙飞船上与部署在本地数据中心是不一样的,网络环境、运行环境、主机环境就是这里规定的条件之一,对软件的可靠性影响很大。规定的时间是用户期望完成任务的时间和提供服务的时间。如希望用户在300ms内完成端到端的语音连麦通信。规定时间也包括持续时间,如在高峰期不能中断服务,在平常保证7×24小时持续提供服务。规定的功能是指完成业务期望的任务结果。

1988年,AT&T贝尔实验室将其内部软件可靠性培训教材命名为“软件可靠性工程”教材,并对软件可靠性工程的工作范围做了规范。在1991年的第十三届国际软件工程大会上,贝尔实验室的J.D.Musa正式提出软件可靠性工程(Software Reliability Engineering,SRE)的概念,自此以后软件可靠性工程的概念被业界广泛接受。在这之前有软件可靠性的理论研究,但它不是软件工程的一部分。

Musa最初提出的定义是:“预计、测量、管理以软件为基础的产品可靠性,软件可靠性工程以最大限度减少软件系统在运行中不满足用户要求的可能性为目标。”Musa也是可靠性模型执行时间模型(1975年)的提出者。广义的软件可靠性工程是指以保证、改进、提高软件的可靠性为目标的工程活动和研究活动的总称。

1.3.2 软件可靠性工程发展的两个阶段

软件可靠性面临的挑战引起了业界和科学家对软件可靠性的重视,于是软件可靠性的理论研究和工程研究从传统的物理设备可靠性工程中独立出来,形成了独立的学科。按年代可以将软件可靠性的发展分为两个阶段,以数学模型研究和模型建立为主的1.0阶段和以工程方法为主的2.0阶段。接下来简述这两个阶段的工作。

1.软件可靠性1.0阶段:以模型研究和模型建立为主

20世纪60年代末软件危机爆发,其影响体现在软件可靠性没有保障、软件维护费用不断上升、进度无法预测、编程人员无限度增加等各个方面,最终导致软件开发和软件质量失控的局面。美国银行信托软件系统、IBM操作系统、360操作系统就是典型的失败案例。软件危机是由软件复杂度和规模大幅增加,缺少工程方法和理论指导导致的。于是人们投入了很多资源对软件工程进行研究,软件可靠性研究是其中的一个重要方向。原来研究物理可靠性的一批科学家转向研究软件,著名的如Musa等人。当时研究的主要对象是硬件为主、软件为辅的产品中的软件部分,如武器装备中的嵌入式软件就是参考了物理设备可靠性的理论和方法。

从20世纪70年代到90年代,业界主要参考物理设备可靠性的研究方法,主张使用统计学和概率方法建立数学模型来分析、度量、评估和预测软件的可靠性。这时属于第一个阶段,本书暂称为软件可靠性1.0阶段,也叫模型论阶段。这段时间产生了大量的软件可靠性模型,据统计有上百个。

(1)主要研究方法和常见的概率模型

刚开始研究软件可靠性时主要参照硬件可靠性的方法,研究软件特征与软件失效风险之间的数学关系,希望通过模型评估可靠性,预测出软件在未来发生故障的概率。主要研究特征有软件复杂度、测试工期、运行时长、Bug数量等。模型论认为软件可靠性是这些软件特征因素的函数。

举例来说,研究代码行数与可靠性的关系,通过统计大量现有软件的代码行数与故障次数之间的关系得出一个数学模型,下次评估另外一个软件时,用代码行数去拟合模型,评估出这个新软件可能的故障次数和分布概率,评估可靠性现状和预测未来的软件可靠性。常见的概率模型可以分为以下几大类。

1)统计缺陷数量与可靠性之间的数学关系的模型。此方法研究软件缺陷与可靠性的关系,统计在设计、代码检查、功能测试、系统测试等过程中发现的缺陷数量,然后分析形成数学模型,以此来度量、评估、预测软件的可靠性。我们以威布尔(Weibull)时间分布模型(下文简称为威布尔模型)为例来看一下基于数学关系的模型,该模型是在物理设备可靠性建模中应用最广的分布模型之一,早期也被套用在软件可靠性上。

威布尔模型假设如下:在软件测试初期有确定数量的错误数,将导致错误a发生的事件记作Ta,然后每隔一段时间记录一次本时间段内的错误数,即将[(0, t1),(t1, t2),(t2, t3),…,(tn-1), tn)]时间间隔内的错误数分别记录为f1, f2, f3, …, fn。威布尔模型将这些错误数画为一个分布曲线,假设软件运行方式不变,每个错误出现的机会相等且严重程度相同时,则该模型可以用来度量软件当前的可靠性,预测未来的可靠性。

2)研究测试工期或运行时长与可靠性的关系的模型。此研究方法认为可靠性与测试的时间长短存在强相关性。在相同测试团队、类似软件的情况下,测试时间越长,故障越少,即测试时长与可靠性存在相关性。在需求、设计和实现阶段进行技术回顾和测试,记录测试时长与发现的错误数量,根据数据分析二者的相关性并建立数学模型,以此来预测软件运行期的可靠性。

执行时间(basic execution time)模型研究CPU执行时间与失效次数的关系。该模型认为执行时间越长,故障越容易发生。如物理设备时间越长,磨损就越严重,最终会导致故障。软件在内存泄露累计、磁盘损坏等场景可能有类似特点。

3)基于复杂度的模型。通过研究软件复杂度与可靠性的关系来估计软件可能的故障。此方法考虑了代码行数、代码变量数、模块数、调用链路长度、代码循环数、函数/模块调用的扇入扇出数等多个维度,通过研究软件错误/故障与这些维度的数学关系来构建一个数学模型,后续在碰到类似的软件或软件改版时,可通过选择其中某个模型来评估或预测软件可靠性。如经典的千行代码缺陷数模型,它由每千行源代码中的缺陷数构成,可以预测、评估软件规模扩大后的可靠性。

(2)1.0阶段研究的效果与问题

1.0阶段研究的效果主要体现在以下3个方面。

1)把软件可靠性从物理设备可靠性研究中分离出来独立研究。软件可靠性工程的产生和发展使军用软件、硬-软结合的装备的可靠性获得大幅提升。软件可靠性工程系统地研究了软件可靠性中的很多基本特点,包括软件为什么失效。软件失效的原因与物理设备的磨损、老化等原因不一样,主要着重于软件错误(Bug)、软件失效的复杂性和软件错误传播等特性,因此要把软件可靠性作为独立的研究领域和方向。

2)对软件可靠性相关因素进行了深入研究。在研究模型的过程中,对可能影响软件可靠性的特征做了多方面的研究,也形成了软件操作概图→可靠性要求→测试→收集数据→选择模型→选择工具→度量和预估→做出决定(验收/改进/上市/得出预测数据)等步骤组成的完整的可靠性评估过程。对影响软件可靠性相关的特征因素进行了深入系统的研究,如可靠性测试、代码规模、复杂度等。

3)开发和积累了多种软件可靠性预测、评估模型及建模工具。可以选择合适的模型来预测未来软件的可靠性,预测未来某个时间点、时间段的软件失效概率和失效数目。

选择合适的模型对评估和预测软件的可靠性有帮助,但也存在一些问题。

1)收集数据困难。建立模型依赖大量的类似软件的可靠性失效数据,需要收集正在开发或待评估的软件的失效数据、软件特征数据,还要做大量的前提假设,而这些假设在软件的高度复杂性和随机性的前提下,大部分是不现实的,所以数据本身并不具有很高的准确性。软件交付给客户或部署到了设备上之后,收集数据就更难了。

2)软件复杂度随机性太高,导致模型有效性很难验证。要选择合适的模型,这本身也是个困难的工作,如怎样选择模型以及如何判断模型是否最优。各种模型公式太复杂,都只考虑了部分软件特征。很难说一个模型比另一个模型好或更适用,直到现在也还没有很系统地研究软件特征与可靠性直接因果关系的度量。模型百花齐放,其解决实际工程问题的效果难以评估。

3)实施工作周期长,难以大范围推广。需要投入人员完成收集数据、选择模型、选择评估工具等评估准备工作,需要大量的、专门的可靠性专业人力和时间投入,但可靠性评估的效率仍不能适应大部分软件的实际需求。非常重要且开发周期为几年以上的项目会引入专门的软件可靠性团队,但也只能起到辅助作用,如虽然在航空航天、精密软件等重大项目中应用较多,却没有在一般性软件项目中得到广泛应用。

20世纪90年代,业界逐渐认识到了模型论的局限,开始跳出纯数学模型,更多转向工程方法研究。

2.软件可靠性2.0阶段:以工程方法为主

90年代以后软件形态及软件可靠性的方法发生了很大变化。以AT&T为代表的软件开发商开始围绕软件生命周期的各个阶段开展可靠性工作,更加重视工程过程对可靠性的影响。如在软件设计、开发、测试、交付等阶段都进行可靠性分析、测量、管理等方面的工作,同时结合软件工程,在这些环节中加入适当的工程活动来保证或提升软件的可靠性。这个时期属于工程实践为主的阶段,暂且叫作软件可靠性2.0阶段,也可以称为工程论阶段。

(1)软件可靠性工程大纲

AT&T旗下的贝尔实验室较早开展了可靠性工程工作,认为重要软件项目的开发应该制定软件可靠性工程大纲,规定在软件生命周期各个阶段要开展的工程工作。大纲一般应该包括如下4个方面的内容。

可行性与需求阶段:

□确定功能剖面

□失效定义和分类

□识别并获取软件可靠性需求

□进行综合权衡分析

设计与实现阶段:

□可靠性分配

□确定并采取适应可靠性目标的工程措施

□基于功能剖面的资源配置

□对故障引入和传播的管理

测试与验证阶段:

□确定运行剖面

□进行可靠性增长测试

□测试进展跟踪

□附件测试,如强度测试、回归测试等

□可靠性目标估计、分析和验证

交付后活动:

□建立售后服务机构和管理体系

□监控软件可靠性是否能达到其可靠性目标

□跟踪用户对可靠性的满意程度

□确定维护方案

□确定软件及其开发过程改进指南

可以看出,上述大纲是围绕软件生命周期的各个阶段开展工作的,AT&T公司内部也是用上述大纲对内部工程师进行可靠性方面的培训。软件可靠性工程概念和大纲的提出标志着对软件可靠性的研究重点从纯数学模型转向了工程方向。

(2)研究方法和工程方法

我们以贝尔实验室软件可靠性大纲为例,简单介绍各个阶段的工作。软件可靠性工程2.0阶段主要研究可靠性定量评估评测,包括:跟随软件工程软件生命周期,把可靠性工作纳入软件的各个阶段,在每个阶段开展不同的可靠性工作,且软件可靠性工程与软件工程同步进行。软件可靠性工作主要包括可靠性分析、设计、测评、管理,下面分别简述。

可行性与需求阶段

可靠性工程强调在需求阶段,分析软件的功能模块,分析用户使用剖面,对可靠性失效故障进行定义与分类,了解故障发生的后果和严重程度,提出可靠性需求形成可靠性规格说明。对可靠性需求进行分析,也与软件工程的其他部分综合考虑,如成本、人力、开发和测试时间等,再建立可靠性目标,形成一些新的设计和功能需求。

设计与实现阶段

设计与实现阶段的工作包括指标分解分配、开发过程分析等。在设计过程进行避错、容错、查错、纠错设计。对设计方案进行可靠性分析来发现设计中的缺陷,按目标要求指导改进和测试。比较有名的有失效模式和影响分析、故障树分析等技术。分析后进行故障恢复设计、可靠性增长分析等。指标分配到对应服务/模块/团队;在开发过程度量预计软件交付后的可靠性。

□避错设计。避错设计也叫防错、预防错误设计,是指在软件设计中规避可能的错误,如限制用户输入范围、隐藏易错信息、减少故障传播、增加冗余等。

□容错设计。容错设计是指在软件中设计特殊功能,使系统在已经触发错误的情况下仍能运行,如多副本冗余重试、异常捕获等。

□查错设计。查错设计也叫检错、检测设计,是指在设计中开发某些特殊功能,方便发现和定位、诊断错误。

□纠错设计。纠错设计也叫改错、恢复、修复技术,是指在程序中自我改正错误以减少错误或降低危害程度的设计方法,如故障隔离方法。

可靠性测试阶段

在软件测试阶段加入可靠性相关的测试。通过测试来发现程序中影响可靠性的缺陷,控制和改进软件开发过程,督促软件改进从而保证开发的软件达到可靠性要求,在验收阶段进行可靠性测试来验证软件达到了业务的可靠性要求。通过统计分析发现的问题来量化评估,预计、预测软件的可靠性。

运行维护阶段

传统软件交付给客户后被部署到了客户的系统中,从此进入运行维护阶段,也叫运维阶段。此阶段需要规划发行后的运维需求,监控基础设施、系统、软件运行的状况;在监测到异常时触发告警和通知,维护值守人员进行系统可靠性相关的维护操作。维护操作也包括多种情况下的动作,如改正性维护、适应性维护、预防性维护、完善性维护等。同时跟踪用户满意度,获得可靠性相关反馈及改进。

软件维护包括在线维护和停机维护,传统软件的维护多是计划内离线停机维护,现在越来越需要在线维护,发展出可维护性等定性指标。可维护性也就是软件容易维护的程度,它取决于设计阶段与编码阶段的实现。

(3)软件可靠性工程成果与问题

软件可靠性2.0阶段取得了三点成果。

1)明确研究软件失效因素并提出解决办法。

软件可靠性工程论认为可靠性不足是因开发的各个阶段的可靠性不足造成的,于是深入研究软件开发各个阶段的失效因素。

□需求错误:如业务提出的需求不完整,理解不准确。

□设计错误:如模块结构与算法错误,对特殊情况与错误未处理或处理不当等。

□编码错误:如流程错误、语法错误、变量错误、逻辑错误等。

□测试错误:如测试用例错误、测试遗漏等。

□文档错误:文档描述不准确、遗漏、错误。

还有运维过程错误。

□操作错误:维护人员操作错误、配置错误。

□环境变化:网络环境变化、抖动、中断等,依赖的软件环境、组件软件错误等。

□基础设施故障:风火水电故障,服务器、操作系统等故障。

上述环境和基础设施是属于软件运行的环境(也就是软件可靠性中规定的条件),包括如网络、操作系统、数据库、硬件CPU/Cache/内存/IO等具体技术因素。

2)形成了以软件生命阶段为主线的软件可靠性工程框架。

基于上一点在对开发阶段可靠性做了深入研究后,可靠性工作重点放在了如何加强各个阶段的可靠性,形成软件可靠性的工程框架。即在贝尔实验室软件可靠性大纲的基础上发展出更为完善的软件可靠性工程框架,如图1-2所示。

图1-2 软件可靠性工程框架

软件可靠性被整合到软件工程中,在需求、开发、测试、验收等各阶段加入软件可靠性相关工作,分阶段制定并实施可靠性计划、确认、控制可靠性,形成了软件可靠性的开发方法。结合软件的生命周期阶段,使用先进的程序架构设计、算法、开发技术,在各个阶段提出可靠性保证技术和方法,包括可靠性设计技术、可靠性容错技术、可靠性测试技术等。如软件可靠性设计中加强避错设计、查错设计、纠错设计、容错设计等。

3)对软件可靠性的过程和结果的定性定量度量。

形成了较为全面的可靠性技术和方法,如软件可靠性的分析技术、可靠性数学建模及估计方法等,且都能有定量和定性的评估。常用的软件可靠性度量指标有错误检出率、失效强度、失效率、平均故障前时间(Mean Time To Failure, MTTF)、平均故障间隔时长(Mean Time Between Failure, MTBF)、平均故障修复时长(Mean Time To Repair, MTTR)、故障计数数据(统计次数、分布等)等。

经过多年发展,软件形态和开发方法都发生了重大变化,但相关软件可靠性工程没有跟上软件行业的快速发展的步伐,从而较难用于互联网的软件系统。

1)没有适应敏捷开发模式和微服务的架构模式。按软件开发阶段实施的过程太重。传统软件开发过程是按瀑布模式把每个阶段分得很清楚。最近十几年,软件开发主流使用敏捷开发模式,特别是进入大型分布式+微服务的软件阶段后,已经属于超敏捷模式。微服务开发模式可以看作一种新型的敏捷开发模式,数以千计的微服务独立进行需求、开发、发布迭代。测试工作方式也不太一样,传统软件迭代较慢,测试可以重度参与,而微服务模式更强调在快速迭代中迅速解决问题,而较少时间去强调严格测试。微服务数量规模也导致难以做到每个微服务的每次修改都有测试人员进行完整测试。所以分软件阶段的方法也难以复用到当前互联网的软件中。

2)可靠性工程作为软件过程的一部分,随意裁剪较多。可靠性工程被打散到软件开发工程中,缺乏全过程对可靠性的持续关注。没有把可靠性作为独立的一条线,而是把可靠性工作作为软件工程各个阶段需要关注的一个小点,重要性被放低甚至被完全忽略。