OO第三单元作业总结
本单元的三次作业根据所给的Jml规格完成代码,依次实现Path路径类,PathContainer路径容器类;第二次作业将PathContainer类扩展为Graph类,整合图数据结构功能,解决部分图的问题;第三次作业将Graph类扩展为RailwaySystem类,完成更复杂的图功能。
JML语言理论基础
-
JML(Java Modeling Language)是一种行为接口的规范语言,通过规格所规定的
-
前置条件
-
副作用范围限定
-
后置条件
来对设计的行为进行约束,准确表达方法的功能需求。同样,通过JML在x形式规范的基础上,可以利用第三方工具来进行高效的单元测试,即基于正确规格的程序就可以被认为是正确的程序 .
-
-
原子表达式
-
\result表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。
-
\old( expr )表达式:用来表示一个表达式 expr 在相应方法执行前的取值。
-
-
量化表达式
-
\forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束
-
\exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。
-
-
操作符
-
子类型关系操作符: E1<:E2 ,如果类型E1是类型E2的子类型(sub type),则该表达式的结果为真,否则为假。
-
等价关系操作符: 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 。
-
-
类型规格
-
不变式(invariant)是要求在所有可见状态下都必须满足的特性,语法上定义 invariant P ,其中 invariant 为关键词, P 为谓词。对于类型规格而言,可见状态(visible state)是一个特别重要的概念。
-
状态变化约束(constraint)对象的状态在变化时往往也许满足一些约束,这种约束本质上也是一种不变式
-
openJML使用-check选项可以检查JML语法的正确性,同时也可以不依赖JML,静态验证方法可能存在的问题,或是生成运行时检查的class文件。JMLUnitNG根据-rac运行时检查选项,可以生成一个Java类文件测试的框架,实现对代码的自动化测试。这种自动化测试对于方法的边界测试比较支持。
部署SMT Solver
在讨论区大佬的帮助下,对OpenJML进行了简单的下载安装以及使用,接着对几个方法进行尝试验证。
package demo;
import java.util.ArrayList;
public class Path {
/* @ requires nodes != null;
@ ensures \result == (\num_of int i, j; 0 <= i && i < j && j < nodes.length;nodes[i] != nodes[j]);
@*/
public /*pure*/ static int getDistinctNodeCount(int[] nodes) {
int len = nodes.length;
for (int i = 0;i < nodes.length;i++) {
for (int j = i + 1;j < nodes.length;j++) {
if (nodes[i] == nodes[j]) {
len--;
break;
}
}
}
return len;
}
/* @ ensures \result == (a + b);
@ */
public static int addNode (int a,int b) {
return a + b;
}
public static void main(String[] args) {}
}
使用./openjml.sh -check demo/Path.java对JML语法进行检查,没有发现错误。
使用 ./openjml.sh -exec Java/openjml/win/Solvers-windows/z3-4.7.1.exe -esc demo/Pat h.java对方法静态检查
$ ./openjml.sh -exec Java/openjml/win/Solvers-windows/z3-4.7.1.exe -esc demo/Path.java
demo\Path.java:25: 警告: The prover cannot establish an assertion (ArithmeticOperationRange) in method addNode: underflow in int sum
return a + b;
^
demo\Path.java:25: 警告: The prover cannot establish an assertion (ArithmeticOperationRange) in method addNode: overflow in int sum
return a + b;
^
demo\Path.java:13: 警告: The prover cannot establish an assertion (PossiblyNegativeIndex) in method getDistinctNodeCount
if (nodes[i] == nodes[j]) {
^
demo\Path.java:13: 警告: The prover cannot establish an assertion (PossiblyNegativeIndex) in method getDistinctNodeCount
if (nodes[i] == nodes[j]) {
^
demo\Path.java:14: 警告: The prover cannot establish an assertion (ArithmeticOperationRange) in method getDistinctNodeCount: underflow in int difference
len--;
^
5 个警告
发现getDistinctNode方法中可能存在的错误下标以及len可能溢出的问题,其实我不是很懂为什么会有这两个问题以及add方法可能存在的溢出问题。将方法进行修改后,使用JMLUnitNG自动化生成测试样例
john@DESKTOP-TI0P349 MINGW64 /d
$ java -jar jmlunitng.jar "$@" demo/Path.java
$ ./openjml.sh -rac demo/Path.java
$ javac -cp jmlunitng.jar demo/*.java
$ java -cp jmlunitng.jar demo.Path_JML_Test
[TestNG] Running:
Command line suite
Failed: racEnabled()
Passed: constructor Path()
Passed: static addNode(-2147483648, -2147483648)
Passed: static addNode(0, -2147483648)
Passed: static addNode(2147483647, -2147483648)
Passed: static addNode(-2147483648, 0)
Passed: static addNode(0, 0)
Passed: static addNode(2147483647, 0)
Passed: static addNode(-2147483648, 2147483647)
Passed: static addNode(0, 2147483647)
Passed: static addNode(2147483647, 2147483647)
Failed: static getDistinctNodeCount(null)
Passed: static getDistinctNodeCount({})
Passed: static main(null)
===============================================
Command line suite
Total tests run: 14, Failures: 2, Skips: 0
===============================================
由于不支持exist和forall,因此所能测试的方法极其有限。除此之外,我测试了多个简单的方法,JMLUnitNG自动化生成的样例好像比较难以描述,只会生成一些最大最小空以及Null的测试样例,不知道是不是我的打开方式不对生成的测试代码过于单一,没有太大的价值。
架构分析

三次作业我只截了第三次的类图,虽然这三次作业层层递进,但我每次只是简单的将需要实现的类进行实现,因此毫无架构可言,简直就是堆叠的一座垃圾山。从第一次实现的MyPathContainer到MyGraph再到MyRailwaySystem,当有新的需求产生时我只是简单的将功能叠加在新增加的类中,因此可以看到新增的类十分臃肿,图结构以及对于需求的计算都堆叠在这个类中,如果有新的需求需要增加,那么可能很多类都无法幸免需要修改。
直到我看到了例程,我才意识到架构的美妙。但是自己实现过程中总是忽略架构,想不到架构,只是将任务完成将需求满足例程中将核心图类进行单独封装,实现图的基本功能,增删路径等,然后引出有向图和无向图;缓存计算层,基于图类实现图的l两个核心计算功能。最后图建模层使用了组合模式+工厂模式,将多个问题归为一种问题,需要求解某个问题时直接调用内部的缓存计算模块,进行计算。
在实现了架构以后,如果有新的功能需要实现,便无需或很少的改动已经实现的代码,相比于我的一座垃圾山,真是赞叹不已获益匪浅。
BUG分析
第一次作业在强测中遭遇到TLE和WA双重打击。首先第一次作业中没有意识到复杂度的重要性,每次在进行不同结点个数的计算时,保留上次计算结果,如果之前改变过就重新计算,但还是炸掉了。通过使用HashMap将不同节点进行储存,查询时直接输出size()解决。
其次WA是因为在判断结点不同时,错误的将两个Integer使用==进行了比较,简直愚蠢至极。由于测试不充分,只是进行了较小结点的测试(小于127),因此没有发现这个bug,导致大量WA。
第二次第三次作业中没有出现bug。
心得体会
对于JML规格,其目的在于对一个方法或是类进行描述,来保证其严格性和准确性。在体会到JML的目的后,如果JML规格已经写好,那JML的使用理解大概就是一个参考书,先看一遍对方法的要求有一个大致的印象,然后再去实现方法。如果有哪里模糊则再去查询。
浙公网安备 33010602011771号