一、JML概述
1.1 JML理论基础
JML简介
注释结构
JML表达式
原子表达式
- \result表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。\result表达式的类型就是方法声明中定义的返回值类型。
- \old(expr)表达式:用来表示一个表达式 expr 在相应方法执行前的取值。该表达式涉及到评估 expr中的对象是否发生变化,遵从Java的引用规则,即针对一个对象引用而言,只能判断引用本身是否发生变化,而不能判断引用所指向的对象实体内容是否发生变化。
- \not_assigned(x,y,...)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。如果没有被赋值,返回为 true ,否则返回 false 。实际上,该表达式主要用于后置条件的约束表示上,即限制一个方法的实现不能对列表中的变量进行赋值。
- \not_modifified(x,y,...)表达式:与上面的\not_assigned表达式类似,该表达式限制括号中的变量在方法执行期间的取值未发生变化。
- \nonnullelements(container)表达式:表示 container 对象中存储的对象不会有null。
- \type(type)表达式:返回类型type对应的类型(Class),如type( boolean )为Boolean.TYPE。
- \typeof(expr)表达式:该表达式返回expr对应的准确类型。如\typeof( false )为Boolean.TYPE。
量化表达式
- \forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。
- \exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。
- \sum表达式:返回给定范围内的表达式的和。
- \product表达式:返回给定范围内的表达式的连乘结果。
- \max表达式:返回给定范围内的表达式的最大值。
- \min表达式:返回给定范围内的表达式的最小值。
- \num_of表达式:返回指定变量中满足相应条件的取值个数。
集合表达式
操作符
- 子类型关系操作符: 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 。
- 推理操作符: b_expr1==>b_expr2 或者 b_expr2<==b_expr1 。对于表达式 b_expr1==>b_expr2而言,当 b_expr1==false ,或者 b_expr1==true 且 b_expr2==true 时,整个表达式的值为 true 。
- 变量引用操作符:除了可以直接引用Java代码或者JML规格中定义的变量外,JML还提供了几个概括性的关键词来引用相关的变量。\nothing指示一个空集;\everything指示一个全集,即包括当前作用域下能够访问到的所有变量。
方法规格
-
前置条件(pre-condition)前置条件通过requires子句来表示: requires P; 。其中requires是JML关键词,表达的意思是“要求调用者确保P为真”。
-
后置条件(post-condition)后置条件通过ensures子句来表示: ensures P; 。其中ensures是JML关键词,表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”。
-
副作用范围限定(side-effffects)副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。从方法规格的角度,必须要明确给出副作用范围。JML提供了副作用约束子句,使用关键词assignable 或者 modifiable 。
-
signals子句signals子句的结构为 signals (***Exception e) b_expr; ,意思是当 b_expr 为 true 时,方法会抛出括号中给出的相应异常 e 。还有一个简化的signals子句,即signals_only子句,后面跟着一个异常类型。signals子句强调在对象状态满足某个条件时会抛出符合相应类型的异常;而signals_only则不强调对象状态条件,强调满足前置条件时抛出相应的异常。
类型规格
-
不变式invariant不变式(invariant)是要求在所有可见状态下都必须满足的特性,语法上定义 invariant P ,其中invariant 为关键词, P 为谓词。对于类型规格而言,可见状态(visible state)是一个特别重要的概念。下面所述的几种时刻下对象o的状态都是可见状态:对象的有状态构造方法(用来初始化对象成员变量初值)的执行结束时刻在调用一个对象回收方法(fifinalize方法)来释放相关资源开始的时刻在调用对象o的非静态、有状态方法(non-helper)的开始和结束时刻在调用对象o对应的类或父类的静态、有状态方法的开始和结束时刻在未处于对象o的构造方法、回收方法、非静态方法被调用过程中的任意时刻在未处于对象o对应类或者父类的静态方法被调用过程中的任意时刻
-
状态变化约束constraint对象的状态在变化时往往也许满足一些约束,这种约束本质上也是一种不变式。JML为了简化使用规则,规定invariant只针对可见状态(即当下可见状态)的取值进行约束,而是用constraint来对前序可见状态和当前可见状态的关系进行约束。
1.2 JML应用工具链
- OpenJML : 主要是对JML规格进行语法检查,可以进行动态和静态语法检查。
- SMT Solver :用于检查程序和规格是否等价。
- JMLUnitNG : 自动化测试工具
二、部署工具链
- JMLUnitNG
下载地址 :http://insttech.secretninjaformalmethods.org/software/jmlunitng/
去除了Person类和Group类的接口,改成了新的Person类和Group类。
需要补全代码中的HashMap<>括号里的内容。
修改重名的内容
命令行输入以下几条指令:
java -jar jmlunitng.jar test/Group.java javac -cp jmlunitng.jar test/*.java java -jar openjml.jar -rac test/Group.java test/Person.java java -cp jmlunitng.jar test.Group_JML_Test
结果如下:
[TestNG] Running: Command line suite Passed: racEnabled() Passed: constructor Group(-2147483648) Passed: constructor Group(0) Passed: constructor Group(2147483647) Failed: <>.addPerson(null) Failed: < >.addPerson(null) Failed: < >.addPerson(null) Failed: < >.delPerson(null) Failed: < >.delPerson(null) Failed: < >.delPerson(null) Passed: < >.equals(null) Passed: < >.equals(null) Passed: < >.equals(null) Passed: < >.equals(java.lang.Object@60215eee) Passed: < >.equals(java.lang.Object@65e579dc) Passed: < >.equals(java.lang.Object@b065c63) Passed: < >.getAgeMean() Passed: < >.getAgeMean() Passed: < >.getAgeMean() Passed: < >.getAgeVar() Passed: < >.getAgeVar() Passed: < >.getAgeVar() Passed: < >.getConflictSum() Passed: < >.getConflictSum() Passed: < >.getConflictSum() Passed: < >.getId() Passed: < >.getId() Passed: < >.getId() Passed: < >.getRelationSum() Passed: < >.getRelationSum() Passed: < >.getRelationSum() Passed: < >.getValueSum() Passed: < >.getValueSum() Passed: < >.getValueSum() Passed: < >.groupSize() Passed: < >.groupSize() Passed: < >.groupSize() Failed: < >.hasPerson(null) Failed: < >.hasPerson(null) Failed: < >.hasPerson(null) Passed: < >.updateRelation(-2147483648, -2147483648, -2147483648) Passed: < >.updateRelation(-2147483648, -2147483648, -2147483648) Passed: < >.updateRelation(-2147483648, -2147483648, -2147483648) Passed: < >.updateRelation(0, -2147483648, -2147483648) Passed: < >.updateRelation(0, -2147483648, -2147483648) Passed: < >.updateRelation(0, -2147483648, -2147483648) Passed: < >.updateRelation(2147483647, -2147483648, -2147483648) Passed: < >.updateRelation(2147483647, -2147483648, -2147483648) Passed: < >.updateRelation(2147483647, -2147483648, -2147483648) Passed: < >.updateRelation(-2147483648, 0, -2147483648) Passed: < >.updateRelation(-2147483648, 0, -2147483648) Passed: < >.updateRelation(-2147483648, 0, -2147483648) Passed: < >.updateRelation(0, 0, -2147483648) Passed: < >.updateRelation(0, 0, -2147483648) Passed: < >.updateRelation(0, 0, -2147483648) Passed: < >.updateRelation(2147483647, 0, -2147483648) Passed: < >.updateRelation(2147483647, 0, -2147483648) Passed: < >.updateRelation(2147483647, 0, -2147483648) Passed: < >.updateRelation(-2147483648, 2147483647, -2147483648) Passed: < >.updateRelation(-2147483648, 2147483647, -2147483648) Passed: < >.updateRelation(-2147483648, 2147483647, -2147483648) Passed: < >.updateRelation(0, 2147483647, -2147483648) Passed: < >.updateRelation(0, 2147483647, -2147483648) Passed: < >.updateRelation(0, 2147483647, -2147483648) Passed: < >.updateRelation(2147483647, 2147483647, -2147483648) Passed: < >.updateRelation(2147483647, 2147483647, -2147483648) Passed: < >.updateRelation(2147483647, 2147483647, -2147483648) Passed: < >.updateRelation(-2147483648, -2147483648, 0) Passed: < >.updateRelation(-2147483648, -2147483648, 0) Passed: < >.updateRelation(-2147483648, -2147483648, 0) Passed: < >.updateRelation(0, -2147483648, 0) Passed: < >.updateRelation(0, -2147483648, 0) Passed: < >.updateRelation(0, -2147483648, 0) Passed: < >.updateRelation(2147483647, -2147483648, 0) Passed: < >.updateRelation(2147483647, -2147483648, 0) Passed: < >.updateRelation(2147483647, -2147483648, 0) Passed: < >.updateRelation(-2147483648, 0, 0) Passed: < >.updateRelation(-2147483648, 0, 0) Passed: < >.updateRelation(-2147483648, 0, 0) Passed: < >.updateRelation(0, 0, 0) Passed: < >.updateRelation(0, 0, 0) Passed: < >.updateRelation(0, 0, 0) Passed: < >.updateRelation(2147483647, 0, 0) Passed: < >.updateRelation(2147483647, 0, 0) Passed: < >.updateRelation(2147483647, 0, 0) Passed: < >.updateRelation(-2147483648, 2147483647, 0) Passed: < >.updateRelation(-2147483648, 2147483647, 0) Passed: < >.updateRelation(-2147483648, 2147483647, 0) Passed: < >.updateRelation(0, 2147483647, 0) Passed: < >.updateRelation(0, 2147483647, 0) Passed: < >.updateRelation(0, 2147483647, 0) Passed: < >.updateRelation(2147483647, 2147483647, 0) Passed: < >.updateRelation(2147483647, 2147483647, 0) Passed: < >.updateRelation(2147483647, 2147483647, 0) Passed: < >.updateRelation(-2147483648, -2147483648, 2147483647) Passed: < >.updateRelation(-2147483648, -2147483648, 2147483647) Passed: < >.updateRelation(-2147483648, -2147483648, 2147483647) Passed: < >.updateRelation(0, -2147483648, 2147483647) Passed: < >.updateRelation(0, -2147483648, 2147483647) Passed: < >.updateRelation(0, -2147483648, 2147483647) Passed: < >.updateRelation(2147483647, -2147483648, 2147483647) Passed: < >.updateRelation(2147483647, -2147483648, 2147483647) Passed: < >.updateRelation(2147483647, -2147483648, 2147483647) Passed: < >.updateRelation(-2147483648, 0, 2147483647) Passed: < >.updateRelation(-2147483648, 0, 2147483647) Passed: < >.updateRelation(-2147483648, 0, 2147483647) Passed: < >.updateRelation(0, 0, 2147483647) Passed: < >.updateRelation(0, 0, 2147483647) Passed: < >.updateRelation(0, 0, 2147483647) Passed: < >.updateRelation(2147483647, 0, 2147483647) Passed: < >.updateRelation(2147483647, 0, 2147483647) Passed: < >.updateRelation(2147483647, 0, 2147483647) Passed: < >.updateRelation(-2147483648, 2147483647, 2147483647) Passed: < >.updateRelation(-2147483648, 2147483647, 2147483647) Passed: < >.updateRelation(-2147483648, 2147483647, 2147483647) Passed: < >.updateRelation(0, 2147483647, 2147483647) Passed: < >.updateRelation(0, 2147483647, 2147483647) Passed: < >.updateRelation(0, 2147483647, 2147483647) Passed: < >.updateRelation(2147483647, 2147483647, 2147483647) Passed: < >.updateRelation(2147483647, 2147483647, 2147483647) Passed: < >.updateRelation(2147483647, 2147483647, 2147483647)
从测试数据可以看出,JMLUnitNG基本都是在测边界数据,如构造Group时用的是0、最大int、最小int,测试传入person的时候用null。
Fail的一些方法主要是因为调用的时候保证了不会传入NULL。
JMLUnitNG只注重对边界情况的测试,对一般情况则几乎没有涉及,没有随机性,测试意义不是很大。
三、作业架构设计
作业中需要注意存储方式,可能会给时间复杂度带来很大影响。最好多使用Hashmap,也可以与Arraylist结合使用,如Person类我用了一个动态数组存acquaintance,一个HashMap存acquaintance的id和value的对应。以及避免使用Arraylist.contains等可以避免的O(n)方法.
认真阅读JML规格避免看错条件。
3.1 第一次作业
第一次作业主要是熟悉JML,因此主要问题在认真阅读JML规格。
难点是isCircle方法的实现,我采用了bfs的方法实现。
用并查集会更好,不用每次都查找一遍。
UML类图
3.2 第二次作业
相比第一次作业加入了Group类和很多查询方法,如果每次都遍历查找时间复杂度势必会很高,而且测试指令条数很多,要注意缓存。
在加关系的时候也要注意更新缓存。
UML类图
3.3 第三次作业
第三次作业主要难点在三个函数,涉及了数据结构的图相关的知识。
-
queryMinPath 函数
求权值最小路径,采用堆优化迪杰斯特拉算法,用Java的优先队列存储节点。
-
queryStrongLinked 函数
判断两点是否点双联通,就是求是否存在两条通过两点且除这两点外没有相同点的路径,并且只有这两点直接连接的路径只能算一条。
采用暴力遍历与这两点联通的所有点,每次去掉一个点bfs看两点是否还联通,如果存在使两点不联通的点则不是点双联通。
注意两点直接连接的情况要单独考虑,去边后再bfs一次。
-
queryBlockSum 函数
采用并查集实现,求联通块的个数。
同时把isCircle也改成了并查集实现。
UML类图
四、bug相关
三次作业均未测出bug。
互测中第二次作业有同学在加关系时没有更新缓存,有同学没有进行缓存导致遍历复杂度过高。
互测中第三次作业有同学方法实现上复杂度过高。
五、心得体会
规格撰写需要尽可能周到地考虑各种情况,注意JML格式。
阅读JML规格的时候要注意括号位置和符号的含义,看清楚数字,实现每一种情况。
这个单元接触了规格化设计,体会到了性能优化的重要性。但JML相关工具链还不太完善,有些表述也比较繁琐,使用还是需要进一步优化。