目录
一、理解两个概念:耦合和内聚
二、避错设计
三、查错设计
四、容错设计
讲软件可靠性设计之前,我们先理解两个概念,一个叫耦合,一个叫内聚,在文章中会用到。
耦合是对一个软件结构内不同模块之间互连程度的度量。耦合强弱取决于模块间接口复杂程度,进入或访问一个模块的点,以及通过接口的数据。
低耦合:
如果两个模块中的每一个都能独立地工作而不需要另一个模块的存在,那么它们彼此完全独立,这意味着模块间无任何连接,耦合程度最低。但是,在一个软件系统中不可能所有模块之间都没有任何连接。
如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据,那么这种耦合称为数据耦合。
中耦合:
如果传递的信息中有控制信息(尽管有时这种控制信息以数据形式出现),则这种耦合称为控制耦合。
高耦合:
一个模块访问另一个模块的内部数据;
一个模块不通过正常入口而转到另一个模块的内部;
两个模块有一部分程序代码重叠;
一个模块有多个入口(这意味着一个模块有几种功能)。
两个模块都既往公共环境送数据又从里面取数据,叫公用耦合,这种耦合比较紧密,介于数据耦合和控制耦合之间。
一个模块与另一个模块的内部属性有关,不经调用直接使用另一个模块的程序代码或内部数据,那么这两个模块之间就存在内容耦合。
内容耦合是最高程度的耦合,应该避免使用。
内聚标志着一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概念的自然拓展。简单地说,理想内聚的模块只做一件事情。内聚按模块内各个元素彼此结合的紧密程度通常分为高内聚、中内聚和低内聚3类。设计时应该力求做到高内聚。
高内聚:
如果一个模块内的处理元素和同一个功能密切相关,而且这些处理必须顺序执行(通常一个处理元素的输出数据作为下一个处理元素的输入数据),则成为顺序内聚。
如果模块内所有处理元素属于一个整体,完成一个单一的功能,则称为功能内聚。功能内聚是最高程度的内聚。
中内聚:
如果一个模块内的处理元素是相关的,而且必须以特定次序执行,则称为过程内聚。
如果模块中所有元素都使用同一个输入或产生同一个输出数据,则称为通信内聚。
低内聚:
如果一个模块完成一组任务,这些任务彼此间即使有关系,关系也是很松散的,就叫做偶然内聚。
如果一个模块完成的任务在逻辑上属于相同或相似的一类(例如,一个模块产生各种类型的全部输出),称为逻辑内聚。
如果一个模块包含的任务必须在同一段时间内执行(例如,模块完成各种初始化工作),就叫时间内聚。
软件可靠性设计方法很多。《可靠性工程师手册》一书中主要介绍了三种方法:避错、查错、容错设计。
预防为主,是软件可靠性设计的首要方法。
我们在讲可靠性设计时提到,可靠性定量设计需要大量的基础数据,这在实际中非常困难的。定性方法是在产品设计和开发中制定和实施产品可靠性设计准则,这是提高设计开发产品可靠性最有效的方法。
软件可靠性也一样,减少出错,就要遵守一些准则。《可靠性工程师手册》一书中,避错设计一共提了三大块,分别是考虑可靠性的设计准则,启发规则,以及正确的编程风格。
(1)考虑可靠性的软件设计准则
1.模块化,指的是我们可以将复杂问题分解为若干易于处理的子问题;过程、函数、子程序和宏,都可作为模块。
2.模块独立,则是要求每一个模块完成一个相对独立的特定子功能。
3.信息隐蔽,是说一个模块内包含的信息(过程和数据)对于不需要这些信息的模块来说,是不能访问的。
4.局部化,使得一些关系密切的软件元素物理上彼此靠近,例如在一些模块中使用局部数据元素。
(2)启发规则
软件开发的实践积累了很多经验,总结这些经验可以得出许多启发规则,帮助软件工程师改进软件的设计,提高软件的可靠性。常用的启发规则:
1.提高模块独立性,力求实现高内聚低耦合。
2.控制模块规模,模块的实现语句不宜过多,通常不超过60行。
3.控制软件的深度、宽度、扇入、扇出。这里我解释一下这些概念:
4.模块的作用域应在控制域之内。模块的作用域是指受该模块内一个判定条件影响的所有模块范围。模块的控制域是指该模块本身以及所有该模块的下属模块(包括该模块可以直接调用的下级模块和可以间接调用的更下层的模块)。
我以一个具体例子解释下:
如图所示,模块C的控制域为模块C、E和F;若在模块C中存在一个对模块D、E和F均有影响的判定条件,即模块C的作用域为模块C、D、E和F(图中带阴影的模块),则显然模块C的作用域超出了其控制域。
由于模块D在模块C的作用域中,因此模块C对模块D的控制信息必然要通过上级模块B进行传递,这样不但会增加模块间的耦合性,而且会给模块的维护和修改带来麻烦(若要修改模块C,可能会对不在它控制域中的模块D造成影响)。
因此,软件设计时应使各个模块的作用域处于其控制域范围之内。
本例中的可改进方法:
①将判定位置上移。如将模块C中的判定条件上移到上级模块B中或将模块C整个合并到模块B中。
②将超出作用域的模块下移。如将模块D移至模块C的下一层上,使模块D处于模块C的控制域中。
5.降低模块接口的复杂度。接口信息传递简单,且和模块功能一致。
6.设计单入口和单出口的模块。不要使模块间出现内容耦合,模块的入口、出口只有一个。
(3)正确的编码风格。
这个很好理解,简单讲就是要代码简明、清晰、易读、易懂;有正确、完整的文档等。
避错设计虽然可以大幅度降低引入的错误或缺陷,但不太可能完全避免缺陷的发生,因此,查错设计就非常重要。查错设计分为主动式查错和被动式查错两种。
主动式查错,顾名思义,是主动进行对程序状态的检查。
举例如下:
●例1:提供服务器定期监控功能,查看定位CPU高耗线程,提示系统管理员关注。
●例2:提供服务端软件心跳监测功能,实时监控服务器的运行状态。
被动式查错,在程序不同位置设置监测点等待错误征兆的出现,从而查出缺陷。
举例如下:
●例1:在各单元/模块处理业务逻辑前,应首先判断所有输入参数、条件的合法性。
1.进行条件判断。
2.使用断言。断言是一个在假设不正确时会预警的函数或宏指令,可使用断言监错在开发阶段,断言可以提示相互矛盾的假设、传入程序的不良数值等等。在维护阶段,断言可以表明改动是否影响到了程序其它部分。
3.对异常情况进行处理。
●例2:对于等待信号的程序,应设置等待次数或时间的上限,避免潜在的死循环。
●例3:对于冗余的输入数据应执行一致性验证。
容错的含义:在发生故障的情况下,系统不失效,仍然能够正常工作的特性。软件的容错设计与硬件的冗余设计极为相似。
容错软件:
在一定程度上,对自身故障具有屏蔽能力
在一定程度上,能从错误状态自动恢复到正常状态
因缺陷而发生故障时,仍然能够在一定程度上完成预期的功能
软件设计为什么要容错?原因如下:
◆没有保证软件无缺陷的方法,开发极低缺陷率软件的成本通常会非常高,有时接受带有缺陷的软件可能更为经济。
◆即使系统看起来很可靠,也需要容错
例如规格说明可能存在缺陷,验证和确认活动可能不正确。
◆在关键情形下,软件系统必须容错
有高可用需求,系统失效代价很高时,必须要容错。
接下来介绍《可靠性工程师手册》一书中提到的两种基本的容错设计方法,N-版本程序设计方法和恢复块法。
N-版本程序设计方法
对于一个给定的功能,由N个(N≥2)个不同的设计组独立编制出N个不同的程序,然后在N台机器上运行并比较结果。如果N个版本运行的结果一致,则认为结果正确。若不一致,则按照表决原则,判定结果的正确性。
书本上也是很枯燥的文字,大家可能不好理解,下面这个示意图则很直观的把这个方法表示出来:
◆不同的小组用许多变体(不同的设计方法,不同的算法,不同的程序设计语言,不同的编译程序,不同的实现技术,不同的设计师及程序员)实现相同的规格说明,所有变体同时进行计算,利用表决系统选择多数作为输出。
◆假定不同的小组犯相同错误的概率极低。
◆是最常用方法,例如:在许多型号空中客车商用飞机中,用这种方法去保证。
N-版本程序设计方法,类似于硬件设计里的硬件冗余,即并联模型。
恢复块法
在每次模块处理结束时都要检验运行结果,一旦发现异常后,通过替代模块再次运行。
用示意图表达如下:
◆相同的规格说明被实现成若干个明确不同的版本,顺序执行
◆利用验收检测程序选择接受的输出
◆强制每个版本使用不同的算法,以降低相同错误的概率
◆验收检测程序的设计困难,必须独立于所使用的计算
◆由于冗余版本是依次顺序执行的,用于实时系统时应注意
恢复块法,类似于具有转换开关的硬件冗余,即旁联模型。
本文为《软件可靠性简介》培训课程中摘录的公开内容,关注微信公众号“永恒之地”,后台回复“软件可靠性”,下载培训课件。