文章目录
一、梳理JML的理论基础、应用工具链情况。
JML简介
我们首先先来看一下JML的官方定义。
The Java Modeling Language (JML) is a behavioral interface specification language that can be used to specify the behavior of Java modules.
JML(Java Modeling Language)是一种用于规范Java程序行为的行为接口规范语言。JML为方法和类型的规格进行定义,为程序的形式化验证提供了基础,通过工具链可以实现静态检查和自动测试数据生成。
一般而言,JML有两种主要的用法:
- 开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语言描述,而是逻辑严格的规格。
- 针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。
就个人理解而言,JML是一种只关注方法和类型的规格定义的手段,也就是只关注一个方法或类型外部可见而忽略内部具体实现过程的内容。
简单来说,编写一个程序可以分为三个部分。
第一步,根据需求进行程序任务的划分。就像荣文戈老师在研讨课上说的那样,你要假装有好几个程序员在同时帮你编写代码,而你现在要做的是,完成需求的划分。就像程序在执行过程中的多线程一样,在编写程序之前,可以通过对任务需求的合理划分形成多个模块化设计,且相互之间没有太多的牵绊因素,这个模块可以是一个类或者是一个方法。在这一阶段我们可以忽略内部具体实现方法,而关注模块的行为接口,也就是模块的外部可见的部分的正确性。我们在以往的程序设计中多半是通过自然语言或者在一些小型程序上直接跳过这一步骤,但是随着今后实际开发中的代码量不断提高,有一种规范化的设计语言,来实现设计和代码编写的分离,可以让程序开发人员和程序测试人员进一步分离,进一步提高效率。
第二步,针对第一步中划分的模块,进行具体的代码编写,在这一阶段不需要考虑各个模块之间的相互调用,而只要根据相应的模块接口的定义,在保证实现需求的正确性的基础上提高代码的运行效率。这一阶段主要考虑的是实现行为接口的数据结构和算法。
第三步,在实现前两步之后,需要对已经编写的代码进行检验和改进。在这个时候,JML不仅可以用来形式化检验程序的正确性以及代码的实现情况,还可以进一步提高程序的可维护性,就像是一本程序的使用说明书。
在程序开发的过程中,我们可以发现JML贯穿了始终,从最早的需求开始到最后的测试都离不开JML的帮助,所以不难看出JML在实际开发中的巨大作用。
JML语法
注释结构
我们首先来看一下JML的注释结构。JML以javadoc注释的方式来表示规格,每行都以@起头。有两种注释方式,行注释和块注释。
- 行注释:
//@annotation
- 块注释:
/*@ annotation @*/
- 按照Javadoc习惯,JML注释一般放在被注释成分的紧邻上部。
表达式
我们接下来看一下JML中使用较多的表达式。
- 原子表达式
\result
:表示一个非void 类型的方法执行所获得的结果,即方法执行后的返回值\old( expr )
:用来表示一个表达式expr
在相应方法执行前的取值。\not_assigned(x,y,...)
表达式:用来表示括号中的变量是否在方法执行过程中被赋值。
- 量化表达式
\forall
表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。\exists
表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。\sum
表达式:返回给定范围内的表达式的和。\max
表达式:返回给定范围内的表达式的最大值。\min
表达式:返回给定范围内的表达式的最小值。
- 集合表达式
- 可以在JML规格中构造一个局部的集合(容器),明确集合中可以包含的元素。
- 操作符
- 子类型关系操作符:
E1<:E2
,如果类型E1是类型E2的子类型(sub type),则该表达式的结果为真,否则为假。如果E1和E2是相同的类型,该表达式的结果也为真。 - 等价关系操作符:
b_expr1<==>b_expr2
或者b_expr1<=!=>b_expr2
,其中b_expr1
和b_expr2
都是布尔表达式,这两个表达式的意思是b_expr1==b_expr2
或者b_expr1!=b_expr2
。可以看出,这两个操作符和Java中的==和!=具有相同的效果。 - 推理操作符:
b_expr1== >b_expr2
或者b_expr2< ==b_expr1
,对于表达式b_expr1==>b_expr2
而言,当b_expr1==false
,或者b_expr1==true
且b_expr2==true
时,整个表达式的值为true 。 - 变量引用操作符
\nothing
指示一个空集\everything
指示一个全集
- 子类型关系操作符:
方法规格
方法规格的核心内容包括三个方面,前置条件、后置条件和副作用约定。
-
前置条件(pre-condition):前置条件是对方法输入参数的限制,如果不满足前置条件,方法执行结果不可预测,或者说不保证方法执行结果的正确性。
具体的实现形式为通过requires子句来表示:
requires P;
-
后置条件(post-condition):后置条件是对方法执行结果的限制,如果执行结果满足后置条件,则表示方法执行正确,否则执行错误。
具体的实现形式为通过ensures子句来表示:
ensures P;
-
副作用范围限定(side-effects):副作用指方法在执行过程中对输入对象或this对象进行了修改(对其成员变量进行了赋值&