本文是其中的一部分嵌入式软件系列:使用ADA合同强制编码
使用您在日常工作中使用的任何界面。它可能是来自标准组件的应用程序编程接口(API),某些内部库在您的公司内共享,或者是下一个小隔间中的一些人的代码正在为您开发。你怎么知道该怎么称呼它?
现代语言通过键入,函数声明或参数规范提供各种方式来识别服务以及各种手段,以引用这些服务。某些语言可能会给你一点。Java指定可能抛出的异常,ADA使您可以指定类型范围,C允许......嗯,从不介意。
然而,这种级别的规范略微涉及应用程序的行为甚少,因此需要任何其他工具的其他内容,非正式的,而不是任何工具:臭名昭着的文档,几乎完全以评论的形式。“现在为什么这是一个问题?”你可能会说。“这已经很难让人发表评论!”
在许多情况下,文档是一项成就。但当可靠性受到威胁时,它只是一个石器时代的规范。如何验证规格书是正确的?如何验证调用者以他们应该的方式进行调用以及他们依赖于他们应该依赖的属性?如何确保在项目的整个生命周期中,在各种修复、重构和重写之后,规范保持一致?
一些替代的技术超越了简单的注释来指定行为,并使用工具检查的形式主义。这里,我们将特别考虑三种语言:C、Java和Ada。我们还将展示,一旦编写了规范,它不仅可以用于文档,还可以直接用于测试和静态分析工具。
目录
前提条件,后期性和键入不变
让我们首先介绍合同的概念。在编程语言中,合同通常是在程序中某些点验证的布尔表达式。最有趣的合同可能是后期性。它定义了函数调用后必须为真的的属性。通常,它可以是指定该函数的行为或要求的手段。这是对实施者(必须履行实施后的后期后的限制)和给予呼叫者的保证(谁可以依赖呼叫后的后期)。
为了满足后期性,我们通常需要两件事:正确实现函数和需要在呼叫之前需要满足的属性。这些属性称为前提条件。前提是给予呼叫者的约束(谁必须在呼吁前履行它,以确保适当的行为)以及给予实施者的保证(谁可以依赖其实施中的前提)。位置是给予呼叫者的保证(呼叫之后的环境状态)以及对实施者的约束(函数必须执行的)。
类型不变的是对于给定类型的每个对象始终必须为真的属性。它可以描述值范围,字段之间的关系和类似属性。
在这里,我们将考虑一个简单的系统,其中物理对象在二维环境中移动。每个对象与x和y坐标,大小和标识符相关联。每个循环调用函数“迭代”,并负责通过步骤更新对象的位置。对象将存储在列表上。
除了结构规范外,我们还会增加一些行为要求:
- x和y必须在间隔内[-200.0,200]。
- 尺寸必须在间隔内[0 .. 100]。
- 除了设置为特殊的非初始化值时,所有对象ID必须是唯一的。
- 必须仅在初始化的对象上调用迭代。
- 迭代无法更改对象的大小或ID。
- 通过迭代制造的每个运动必须小于物体尺寸的十分之一。
这些要求中的一些可以清楚地映射到类型不变,即1,2和3,这是在整个程序的整个寿命中必须是真实的类型和数据。其他可以映射到预处理,特别是4.最后,要求5和6描述函数的行为,因此映射到后期条件。现在,可以使用基于Java,C和ADA的三种不同语言来实现上述规范。
Java和JML
Java建模语言(Java Modeling Language, JML)是Java的扩展,使用常规Java编译器,Java代码应该是可兼容的。因此,所有JML注释都是用特殊的java注释编写的,以//@或/*@开头。让我们先翻译并注释下面的代码。
public class Object {public static String NO_INIT = 0;public int id = NO_INIT;Public float x, y, size = 0;// @公共不变式x >= -200 && x <= 200;// @公共不变式>= -220 && y <= 200;// @公共不变式sie >= 0 && size <= 100;// @需要id /= NO_INIT;// @可分配的x // @可分配的y // @确保数学。√数学。pw (\old (x) - x, 2) // @ + Math。\旧的(y) - y, 2)) <= size / 10; public void iterate () ( { … } ) public static Object [] list = new Object [100]; // @ public invariant // @ (\forall int j; j >= 0 && j < list.size; // @ list [j].id.equals (NO_INIT) || // @ (\forall int k; k >= j + 1 && k < list.size; // @ !list [j].id.equals (list [k].id)));
ACSL和JML示例之间的第一个显着差异是,虽然JML允许引入可以参考代码中实际实体的可执行合同,但ACSL目前纯粹是正式的。因此,ACSL不能调用“真实”的功能,而是只能正式的函数,并且只能在相应的正式属性上推出。这是数学公理的特殊意图,引入POW和SQRT正式功能。
Range_Object公理是定义对象的x,y和大小字段的值范围。然后在类型不变中使用该公理以验证对象类型的所有实例尊重该公理。请注意\有效(v)构造,指定指针必须有效(可以解释)。
除了这两个差异之外,这非常接近JML示例。有关ACSL的更多信息可以找到http:///frama-c.com/acsl.html.。
ADA和ADA 2012
ADA 2012语言的主要新功能之一是其能够以语言定义中的重大合同信息,无需外部工具以及相应的动态语义。下面的代码显示了先前示例中的相同规范如何在ADA 2012中编写。
package对象是no_init:常量:= 0;类型对象是记录ID:Integer:= no_init;X,Y:浮子范围-200.0 .. 200.0:= 0.0;尺寸:浮子范围0.0 .. 100.0:= 0.0;结束记录;procedure Iterate (V : in out Object) with Pre => V.Id /= No_Init, Post => V.Size = V.Size'Old and then V.Id = V.Id'Old and then Sqrt (( V.X = V.X'Old ) ** 2 + (V.Y -V.Y'Old) ** ) <= V.Size / 10.0; type Arr is array (Integer range <>) of Object with Dynamic_Predicate => (for all J in Arr'Range => Arr (J).Id = No_Init or else (for all K in J + 1 .. Arr'Last => Arr (J).Id /= Arr (K.Id)); List : Arr (1 .. 100); end Object ;
这只是一个例子,说明了在试图引入强大的形式证明技术时所产生的各种约束。这类问题可以通过使用比低级指针(比如SPARK中的引用)约束更大的结构来检测。这里还有更多要说的,但它可能是即将发表的一篇论文的主题。
结论
合同编程的工业化年龄正在开设一个新的软件开发时代。正如开发技术从汇编到结构化语言以及从结构化语言到对象方向,就基于合同的编程为软件设计提供了一个更多的抽象。
长期以来,这些技术仅适用于有一些扩展数学知识的开发人员。由于新的形式主义如ADA 2012,他们现在基于任何软件开发商可以理解的语义,使他们的使用情况。这是尝试他们的好时机!
从中阅读更多嵌入式软件系列:使用ADA合同强制编码