OO_JAVA_JML_单元总结

时间:2020-05-21 22:30:08   收藏:0   阅读:61

1.JML语言介绍

1.1 JML概念与理论基础

The Java Modeling Language (JML) is a specification language for Java programs, using
 Hoare style pre- and postconditions and invariants, that follows the design by contract
 paradigm. Specifications are written as Java annotation comments to the source files,
 which hence can be compiled with any Java compiler.
                                                                              --WiKi

也就是说,JML是一种以Java注释的形式,进行契约式设计的规格语言。具体就是通过在JML中写明前置条件,后置条件,不变式等等,使得代码实现者在实现代码时只需要满足JML规格;代码调用者只需理解JMl而不需要看代码的具体实现就能知道类和方法的功能和使用条件。

1.2 JML好处

1.3 JML语法

这里仅介绍这一单元常用的语法。

JML结构 功能
requires 定义方法的前置条件
assignable 副作用范围限定,列出这个方法能修改的成员变量
ensures 定义方法的后置条件
public normal_behavoir 表示对方法的正常功能给出规格
public exceptional_behavior 表示对方法的异常功能给出规格
also 表示(1)子类重写父类方法时,补充新的规格 (2)一个方法规格中设计多个方法规格描述
signals (Exception e) expr 表示expr为true时,抛出相应异常 Exceptin e
signals_only Exception 只要满足前置条件就抛出异常
invariant 要求在所有可见状态下都要满足的条件
constraint 对象的状态在发生变化时需要满足的约束
JML原子表达式 功能
\result 表示方法执行后的返回值
\old(exp) 表示一个表达式exp在方法执行前的取值
JML量化表达式 功能
\forall 表示对于给定范围的元素全都满足约束条件
\exists 表示对于给定范围存,在元素满足约束条件
\sum 返回给定范围内的所有表达式的和
\min 返回给定范围内的表达式的最小值
\max 返回给定范围内的表达式的最大值

1.4 JML工具链

JML工具链完整清单
其实在使用过程中,个人感觉JML工具链还远远不够完善,而且近些年好像发展停滞了

2. 使用OpenJML和JMLUnitNG

2.1 使用OpenJML

OpenJML官网下载OpenJML。
环境: Windows10,z3-4.7.1.exe,jdk 1.8
对第三次作业所有源文件进行分析
使用命令

java -jar  openjml.jar -exec Solvers-windows\z3-4.7.1.exe -esc -dir src

结果如下(部分截图)

    src\com\oocourse\spec3\main\Network.java:58: 注: Not implemented for static checking: ensures clause containing \not_assigned
      @     \old(people[i].getId()) != id2; \not_assigned(people[i]));
                                                         ^
src\com\oocourse\spec3\main\Network.java:183: 注: Not implemented for static checking: ensures clause containing \not_assigned
      @ ensures (\forall int i; 0 <= i < groups.length; \not_assigned(groups[i]));
                                                                     ^
src\com\oocourse\spec3\main\Network.java:275: 注: Not implemented for static checking: ensures clause containing \not_assigned
      @ ensures (\forall int i; 0 <= i < groups.length; \not_assigned(groups[i]));


src\hwthree\MyNetwork.java:66: 警告: NOT IMPLEMENTED: Not yet supported feature in converting BasicPrograms to SMTLIB: JML Quantified expression using \sum
     * @param id2 id2
         ^
错误: An error while executing a proof script for queryGroupAgeMean: (error "Error writing to Z3 solver: java.io.IOException: 管道正在被关闭。")
src\hwthree\MyNetwork.java:74: 警告: NOT IMPLEMENTED: Not yet supported feature in converting BasicPrograms to SMTLIB: JML Quantified expression using \sum
        if (id1 == id2 && contains(id1)) { return; }

2.2 使用JMLUnitNG

step 0 修改文件树
jmlUnitNG运行起来容易报错,为了不报错,对文件树进行修改。

技术分享图片

step 1 生成Group的测试文件

java -jar jmlunitng.jar test/MyGroup.java

技术分享图片

step 2 编译测试文件

javac -cp jmlunitng.jar test/*.java

step 3 运行测试文件

java -cp jmlunitng.jar test.MyGroup_JML_Test

运行结果如下:

[TestNG] Running:
  Command line suite

Failed: racEnabled()
Passed: constructor MyGroup(-2147483648)
Passed: constructor MyGroup(0)
Passed: constructor MyGroup(2147483647)
Failed: <<test.MyGroup@6842775d>>.addPerson(null)
Failed: <<test.MyGroup@574caa3f>>.addPerson(null)
Failed: <<test.MyGroup@1761e840>>.addPerson(null)
Failed: <<test.MyGroup@6c629d6e>>.delPerson(null)
Failed: <<test.MyGroup@5ecddf8f>>.delPerson(null)
Failed: <<test.MyGroup@3f102e87>>.delPerson(null)
Passed: <<test.MyGroup@27abe2cd>>.equals(null)
Passed: <<test.MyGroup@5f5a92bb>>.equals(null)
Passed: <<test.MyGroup@6fdb1f78>>.equals(null)
Passed: <<test.MyGroup@51016012>>.equals(java.lang.Object@29444d75)
Passed: <<test.MyGroup@2280cdac>>.equals(java.lang.Object@1517365b)
Passed: <<test.MyGroup@4fccd51b>>.equals(java.lang.Object@44e81672)
Passed: <<test.MyGroup@60215eee>>.getAgeMean()
Passed: <<test.MyGroup@4ca8195f>>.getAgeMean()
Passed: <<test.MyGroup@61baa894>>.getAgeMean()
Passed: <<test.MyGroup@b065c63>>.getAgeVar()
Passed: <<test.MyGroup@768debd>>.getAgeVar()
Passed: <<test.MyGroup@490d6c15>>.getAgeVar()
Passed: <<test.MyGroup@7d4793a8>>.getConflictSum()
Passed: <<test.MyGroup@449b2d27>>.getConflictSum()
Passed: <<test.MyGroup@5479e3f>>.getConflictSum()
Passed: <<test.MyGroup@27082746>>.getId()
Passed: <<test.MyGroup@66133adc>>.getId()
Passed: <<test.MyGroup@7bfcd12c>>.getId()
Passed: <<test.MyGroup@24273305>>.getPeopleSum()
Passed: <<test.MyGroup@5b1d2887>>.getPeopleSum()
Passed: <<test.MyGroup@46f5f779>>.getPeopleSum()
Passed: <<test.MyGroup@1c2c22f3>>.getRelationSum()
Passed: <<test.MyGroup@18e8568>>.getRelationSum()
Passed: <<test.MyGroup@33e5ccce>>.getRelationSum()
Passed: <<test.MyGroup@5a42bbf4>>.getValueSum()
Passed: <<test.MyGroup@270421f5>>.getValueSum()
Passed: <<test.MyGroup@52d455b8>>.getValueSum()
Passed: <<test.MyGroup@4f4a7090>>.hasPerson(null)
Passed: <<test.MyGroup@18ef96>>.hasPerson(null)
Passed: <<test.MyGroup@6956de9>>.hasPerson(null)

===============================================
Command line suite
Total tests run: 40, Failures: 7, Skips: 0
===============================================

2.3 分析自动生成的测试数据

观察测试数据,发现大概可以分为三类

总体来说,自动生成的测试数据的测试强度很弱,仅能检查一下对空指针和INT_MAX,INT_MIN的处理,难以发现代码的逻辑错误。
因此,我们还是需要自己精心构造datamaker和judger来做测试。

3. 部署SMT Solver

// TODO

4. 架构设计分析

第一次作业

本单元第一次难度低,对性能也没有要求,因此直接对JML规格无脑翻译。

唯一稍有难度的方法:isCircle。使用的是BFS。然而由于第一次作业无脑翻译,没有实现类似邻接链表的结构来表示图的连接关系,因此采用数组遍历(邻接矩阵)的方法实现BFS,复杂度为O(n^2)。

第二次作业

第二次作业,有复杂度限制。
采用遍历容易TLE的方法:

采用:hash + 邻接链表 + 保存中间结果 的方法

第三次作业

在第二次作业的基础上,第三次作业比较难的方法有BlockSum, StrongLinked, MinPath.

另外,为了实现架构和算法分离,将BFS,dijkstra等图算法封装到Graphic类。具体UML如下:
技术分享图片

反思

在本单元作业中,并查集显然比BFS效率要高很多。在第二次和第三次作业中,我均没有采用并查集。原因如下:

在第二次作业时,考虑到第三次作业可能会出现删除关系的需求,就没有采用并查集。到了第三次作业,发现实际没有这种需求,但是由于懒得增加新方法,就直接将第二次的BFS重用。

实际上,我的这种思维方式没有遵循模块化(高内聚,低耦合)的设计原则。本次作业中,算法与架构关联不大,是完全可以单独封装的(事实我也是这么做的)。因此,在不改变架构的情况下,多种算法应该是可以灵活替换的。

因此,正确的设计思路应该是:在第二次作业,并查集效率比BFS高,因此采用并查集。但考虑到第三次迭代时可能增加删除关系的需求,因此将算法单独封装一个类。如果第三次作业不能采用并查集,将并查集替换为BFS即可。

5. bug

5.1 bug分布情况

三次作业在中测,强测,互测中均未出现bug。

5.2 测试策略

我觉得我三次作业均无bug(包括前两单元也很少有bug)的原因是我的测试策略。

当然,我的测试策略也有不足之处:没有采用课程组推荐的Junit进行单元测试。

其实使用过,但是感觉Junit构造数据和用assert检查结果过于麻烦,直接实现Python测评机更加方便。而且当数据量很大,Python测评机应该也可以保证逻辑全覆盖。

6.心得体会

第三单元主要是JML规格化设计,让我们提前体验了一把工程开发中的契约式设计,味道极佳。

实现者需要明白,代码实现需要严格满足规格,但是不能对规格照猫画虎。这也正是这一单元的一个难点,需要满足规格的同时保证算法的高效性。

另外,正如在上面4.4提到的,既要保证算法的高效性,又要考虑算法的可扩展性。这就要求将算法尽可能与架构分离,使算法具有可替换性。

再次感谢OO课程组老师和助教大大们,OO课程体验极佳!

原文:https://www.cnblogs.com/yy-blogs/p/12933802.html

评论(0
© 2014 bubuko.com 版权所有 - 联系我们:wmxa8@hotmail.com
打开技术之扣,分享程序人生!