饶建农
摘要:对于具有关键和非关键功能的“混合临界”系统,对可靠性的最大影响可能是在设计级别。通过设计使每个关键需求都有一个小的可信基,可以大大降低可靠性案例所需的分析成本。该种方法的一个含义是,传统的面向对象设计可能是一种责任,因为它会导致“纠缠”,而基于分离服务的方法可能更可取。
关键词:关注点分离;可信基;解耦;纠缠;混合临界系统
中图分类号:TP311 文献标识码:A
文章编号:1009-3044(2021)04-0240-02
1 背景
“关注点分离(separation of concerns)”是软件工程中的一个基本原则。“关注点分离”原则要求软件设计人员能够以模块化的方式实现各关注点[1]。 “关注点分离”是对只与“特定概念、目标”(关注点)相关联的软件组成部分进行“标识、封装和操纵”的能力,即标识、封装和操纵关注点的能力。是处理复杂性的一个原则[2]。由于关注点混杂在一起会导致复杂性大大增加,所以能够把不同的关注点分离开来,分别处理就是处理复杂性的一个原则,一种方法。
“关注点分离”是面向对象的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用(将针对特定领域问题代码抽象化成较少的程式码,例如将代码封装成function或是class),业务逻辑同特定领域问题的关系通过侧面来封装、维护,这样原本分散在整个应用程序中的变动就可以很好的管理起来。
2 过程和测试
当今的软件开发人员依靠两种技术来使他们的软件更加可靠:过程和测试。
在任何大型软件开发中,定义软件过程都是必不可少的,没有它,标准就很难执行,问题就会陷入无解,组织就无法从过去的错误中吸取教训。通常,一个过程可以包括版本控制、错误跟踪和回归测试的程序;还包括文件的标准结构和会议指南(用于需求收集、设计和代码审查);最重要的是必须包括详细的统计数据和相应调整过程的明确机制。通过将问题领域和实现领域的关注点分离,以及各类软件开发技术和技能的分离,实现了软件开发工作责任的分离和人员的分工,为大规模软件开发提供了合作基础[3]。
测试用于两个非常不同的目的。一方面,它被用来寻找BUG。结构测试利用软件结构的知识来识别已知类别中的错误。例如,突变测试可能侧重于选择错误运算符进行表达式的可能性;回归测试旨在检测特定缺陷的再发生。另一方面,测试可以用来提供可靠性的证据。在这种情况下,侧重于特定已知BUG类别的测试不太有用(因为故障可能来自尚未识别的类别)。 相反,测试是随机地从“操作配置文件”的基础上进行的抽样测试。对于这种测试,一个成功的测试是一个成功的测试用例,因为一个失败的测试用例可能不仅需要一个错误修复(它将测试工作设置为平方,因为测试的目标现在是一个新的程序,旧的结果不再获得),而且提供了低质量的直接证据,从而改变了测试者的假设,提高了显示可靠性的标准。
对于适度的可靠性,过程和测试被认为是有效的,并且被广泛认为是任何发展的必要组成部分。关于过程和测试应该采取什么形式的争论仍然存在:例如,过程应该遵循增量方法还是更传统的瀑布方法,或者单元测试或子系统测试应该占主导地位。但很少有人会认为过程和测试是不好的想法。然而,对于关键应用程序所需要的高度可靠性,过程和测试,虽然是必要的,但似乎又是不够的。尽管多年来在开发程序和进行广泛测试的组织中积累了经验,但几乎没有令人信服的证据表明这些努力确保了所需的可靠性水平。尽管采用严格的程序似乎会间接影响可靠性,但又缺乏直接联系的证据。
统计测试的有效性是问题的核心,因为一个过程建立可靠性的最可能的方法是通过测试,就像工业制造中从装配线上通过测量样品来实现质量一样。不幸的是,即使可以适当地对业务概况进行抽样,并且所有其他统计假设都成立,但建立信心所需的测试数量很少是可行的。要检测R执行中的失败率或要求99%的可信度需要执行大约5R次测试[4];同样,R小时内的一个失败率需要测试5R小时。
3 正式验证方法
近几年来,借鉴系统安全领域的经验,提出了一种不同的方法。这个想法是,开发人员不依赖于来自过程或测试的可靠性的间接证据,而是通过提出一个将软件与可靠性索赔联系起来的论点来提供直接证据。这一论点被称为可靠性案例,它以软件系统的代码和与其交互的领域(如硬件外围设备、物理环境和程序员)的假设为前提,并从这些前提中或多或少地建立一些特定的关键属性。
可靠案例的可信度,就像任何论点的可信度一样,将取决于各种社会和技术因素。有时,它可能包括统计参数,尽管逻辑参数可能发挥更大的作用。为了论证一个组件的故障率低于1/109 ,需要对组件进行详尽的测试,但这种为获得足够的统计信心而进行的测试可能过于昂贵,因此正式验证将是一个更可行的选择。正式验证方法在可靠情况下可能发挥关键作用,因为没有其他方法可以以更合理的成本提供可比性。但是,严格的过程并不能保证可靠性,使用正式验证方法也不能完全保证可靠性。正式验证只有在它在可靠性參数中提供了关键链接的情况下才有用。坚持使用特定的方式,或消除特定的异常现象,不可能比强加任何其他过程实践更具成本效益(如DO178B对MCDC的要求测试)。虽然知道程序不会出现算术溢出或超过数组的界限是很有用的,但这些知识并不能提供任何直接证据,证明软件不会导致灾难性的故障。事实上,构建一个可依赖系统软件[5]表明,运行时错误的风险不是链中最薄弱的环节。
4 构建可信基
统计测试的一大吸引力在于它的成本不取决于被检查的软件的大小。通过对有可能被用作可靠性案例的元素分析,由于其依赖于检查软件的文本,测试成本至少随着文本大小而线性增加。这突出了简单设计的重要性。代码越小,越简单,为其正确性构建案例的成本就越低。但现实的目标不是缩小整个程序的大小,而是必须考虑子程序的大小,以证明一个关键属性是成立的。 该子程序是关键属性的可信基,它不仅必须包括直接实现相关功能的代码,而且还必须包括可能破坏该属性的任何其他代码。
假設可靠性必须建立系统的k属性,并且每个属性都有一个大小为B的可信基。分析成本很可能是超线性的;一个合理的猜测是,它将是二次方的,以解释组件之间的相互作用。在这种情况下,总成本为kB2。现在,如果受信任的基覆盖整个程序(但不重叠),则总码基的大小为kB。对于相同的代码库,不考虑可信基,分析的成本将是k(kB)2。即使分析成本仅在可信基的大小上是线性的,但在大小为kB的码基上分析k属性的成本将是k2与分析k属性的成本kB相比,每个属性都超过了大小为B的可信基。这种分析没有考虑到分析不同属性所涉及的工作重叠的可能性。例如,如果多个属性需要通信信道的可靠性,则可能只确定一次该信道是可靠的,以便对其代码进行一次分析。在经典的验证方法(尚未在大规模系统上完全实现)中,每个组件都根据其规范进行验证;然后,高级别属性只需要使用最大组件的规范在顶层进行分析。
对大多数系统来说,不同的属性在不同程度上至关重要,因此需要不同程度的信心和不同程度的投资。此外,在可靠性的前提下,应用程序的许多功能根本不需要考虑。例如,对于在线书店,对于处理搜索、广告和评论的功能,常规测试可能足以建立应用程序可开发的信心。而与此相反,信用卡号码不被泄露,或者客户按所显示的金额计费的属性可能更值得构建一个可靠的案例。
对于混合临界系统,临界属性对于整体功能来说是尤其重要的部分,以至于很少有机会进行共享子程序分析;验证规范中分析多个属性的组件的成本通常是不合理的。此外,临界属性的高方差使系统被分割成多个可信基是值得的,理想情况下,构建与更关键的属性相关联的较小的可信基也是值得的。
5 脱钩机制
由于两个不同的原因,关键属性的可信基可能大于可取的可信基。一种是,实现该属性的代码不像想象的那样本地化。另一种是相关的代码被适当地本地化,但需要对其他模块进行分析,以确定它们的代码不相关。大多数人可能认为可信基的概念可以在执行中而不是在分析术语中定义。如果模块的失败可能会破坏关键属性,则可以将其包含在可信基中。因此,可信的基本思想提供了一个分阶段分析的思路。在第一阶段,稳健但廉价的解耦分析(最好在设计级别进行),确定哪些模块处于可信基中,因此是相关的;在第二阶段,对这些模块进行分析,以确保属性保持不变。
在最简单的情况下,解耦分析可以依赖于物理隔离,但更常见的是需要对虚拟隔离的吸引力,这是通过解耦机制实现的。各级都有这种机制。计算机及其操作系统可以提供地址空间分离,支持不同进程独立运行的推断;中间件平台可以提供通信通道,隐式保证不可能有其他交互;编程语言可以提供名称空间访问控制和强类型,从而可以封装数据。在所有这些情况下,主张脱钩,从而缩小可信基仍然需要履行一些假设的状况。例如,在具有强类型的语言中,建立的两个模块之间缺乏交互将需要一个简单的参数,即它们的名称空间是不相交的(如果它们的类型重叠,也可能需要别名/转义分析)。这种分析是有意义的,通常比随后的分析成本要低得多,也就是分析相关模块强制执行所需的属性,从而也证明了相分离和可信基的概念是合理的。相反,如果缺乏对解耦的支持,就不会有这种成本差异,而对较小的可信基的索赔将不会那么有用。例如,在用不安全语言(如C)编写的程序中,任何一个模块中的代码原则上可以修改另一个模块访问的数据(例如,通过超出数组的界限和修改任意内存),无论它们的命名空间是否有重叠。
6 面向对象的纠缠
一个好的软件设计(借助可用的解耦机制)为关键属性提供了小的可信基。找到一个好的设计显然需要洞察力、经验和领域专长。然而,值得思考的是,传统上使用的基本设计策略是一种帮助还是一种阻碍。
从可靠性和可信基的角度来看,面向对象实际上可能是一种责任。标准规范对共同属性进行分组,将域实体转换为单个程序对象不仅不能帮助解耦,而且可能通过创建不必要的特征纠缠而使情况变得更糟。例如,网上书店的设计。标准域模型可能包括诸如Customer、Book、Shopping Cart、Order、Credit Card等实体,当这些实体作为代码中的类实现时,它们之间的关联很可能被表示为字段(通常由关系数据库的表支持)。更糟糕的是,这些关联在语义上是双向的,在代码中通常会通过双向引用来支持,以便于导航。获取客户购物车的字段将由购物车返回到客户的字段匹配。因此,不能轻易地将与关键属性无关的代码考虑在内。如果财产是信用卡号码,不是无意中显示出来的,那么整个Customer类将是相关的,因为它的一个字段包含对客户卡的引用;购物车也是相关的,因为它的背面引用了客户。
与其解决面向对象的问题,不如从一开始就将系统构建为独立服务的集合,然后以传统的风格实现,并连接在狭窄的接口中。
在书店的例子中,搜索书籍、评论、广告和计费都可以作为一种不同的服务来实现。要向客户的信用卡收费,协调组件可以使用由服务内部映射到信用卡记录的客户标识符对账单服务进行调用。在面向对象的整体实现中,此标识符将是客户对象的地址,并且保持它允许客户端访问客户的所有特性。与此相反,用于计费目的标识符的使用由计费服务的API控制。
在网络书店等混合临界系统中,与临界属性(如保护信用卡)相关的代码比例可能很低,可能在5%或更低。因此,为一个小的可信基进行设计可能会使构建可依赖案例的成本降低,这可能会影响将来在语言设计、静态分析或验证方面取得的进展。
7 结束语
作为一个研究领域,软件工程可以更少地强调语言和分析,更多地强调设计和方法。现在很多研究都集中在事后分析和遗留代码问题上。应该要改变这种研究方法,重新关注设计的根本问题。分析可以作为辅助,但可靠性不能作为事故出现。只有通过设计才能确定软件的可靠性。
参考文献:
[1] 梅宏,曹东刚.ABC-S2C:一种面向贯穿特性的构件化软件关注点分离技术[J].计算机学报,2005,28(12):2036-2044.
[2] Ober I,Ober I.On patterns of multi-domain interaction for scientific software development focused on separation of concerns[J].Procedia Computer Science,2017(108):2298-2302.
[3] 何明昕.关注点分离在计算思维和软件工程中的方法论意义[J].计算机科学,2009,36(4):60-63.
[4] Littlewood B,Wright D.Some conservative stopping rules for the operational testing of safety-critical software[J].IEEE Transactions on Software Engineering,1997,23(11):673-683.
[5] JACKSON D,THOMAS MILLETT L.Software For Dependable Systems:Sufficient Evidence[M].Washington DC:The National Acade mies Press,2007.
【通联编辑:谢媛媛】