面向对象第三单元总结
面向对象第三单元总结
一、JML相关总结
1.1理论基础
JML是Java Modeling Language的缩写,是一种行为接口规范的语言,用于指定Java模块的行为。用于指定一些必要的行为或者结果,但却并不指定具体如何实现它。
JML是写一段特殊规格的注释来指定一个模块的行为,一段JML一般有异常处理部分和正常行为部分,当然,如果没有规定异常的处理,前一部分便可以省略。
正常行为部分一般有三个内容: · requires :规定了这个规格的前置条件,即需要满足什么条件才能保证进入这个方法的数据,最终run出来的结果是正确符合预期的。
· assignable : 这一部分指明了规格的副作用范围,也就是在这个方法执行前后,有哪些变量会被改变。
· ensures : 指明了方法的后置条件,我的理解就是指明了方法运行结束后的结果应该满足的条件。
异常处理的部分大致类似,也有requires和assignable部分,但是最后一个部分可以是ensures也可以是:
· signal_only :抛出一个异常。
JML通过其它相应的语法,完成对上面各个部分的填充,就完成了对Java一个模块的规范化描述,使得懂得JML的人可以根据这段规格来实现这个模块。
1.2工具链
在JML的疑似官网上面我看到了和JML相关的若干工具: · OpenJML : 是Java程序的程序验证工具,允许检查满足JML语言规范的JML注释的程序规范。
· AspectJML : 能够指定并做运行时断言检查Java和AspectJ程序。
· Sireum / Kiasan for Java :这是基于JML规范的Java程序单元自动验证和测试用例生成工具集。
· JMLUnitNG : 用于JML注释的Java代码的自动化单元测试生成工具。
· JMLOk : 这是一种用随机测试来检查JML规范的Java代码的工具,并给出它所发现的不一致性问题的可能原因。
· AutoJML :是一种从各种格式(包括UML图)和安全协议规范的状态图生成JML规范的工具。
二、部署SMT Solver
使用IDEA扩展工具中的OpenJML来进行检查。
对第一次规格作业中的PathContainer进行检测,得到的结果如下:
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:18: 警告: Method size overrides parent class methods and so its specification should begin with 'also'
//@ ensures \result == pList.length;
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:23: 警告: Method containsPath overrides parent class methods and so its specification should begin with 'also'
/*@ requires path != null;
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:36: 警告: Method containsPathId overrides parent class methods and so its specification should begin with 'also'
/*@ ensures \result == (\exists int i; 0 <= i && i < pidList.length;
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:47: 警告: Method getPathById overrides parent class methods and so its specification should begin with 'also'
/*@ public normal_behavior
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:66: 警告: A non-pure method is being called where it is not permitted: com.oocourse.specs1.models.Path.isValid()
@ requires path != null && path.isValid() && containsPath(path);
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:72: 警告: A non-pure method is being called where it is not permitted: com.oocourse.specs1.models.Path.isValid()
@ signals (PathNotFoundException e) !path.isValid();
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:65: 警告: Method getPathId overrides parent class methods and so its specification should begin with 'also'
/*@ public normal_behavior
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:84: 警告: A non-pure method is being called where it is not permitted: com.oocourse.specs1.models.Path.isValid()
@ requires path != null && path.isValid();
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:96: 警告: A non-pure method is being called where it is not permitted: com.oocourse.specs1.models.Path.isValid()
@ requires path == null || path.isValid() == false;
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:83: 警告: Method addPath overrides parent class methods and so its specification should begin with 'also'
/*@ normal_behavior
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:114: 警告: A non-pure method is being called where it is not permitted: com.oocourse.specs1.models.Path.isValid()
@ requires path != null && path.isValid() && \old(containsPath(path));
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:114: 错误: A \old token with no label may not be present in a requires clause
@ requires path != null && path.isValid() && \old(containsPath(path));
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:124: 警告: A non-pure method is being called where it is not permitted: com.oocourse.specs1.models.Path.isValid()
@ signals (PathNotFoundException e) path.isValid()==false;
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:113: 警告: Method removePath overrides parent class methods and so its specification should begin with 'also'
/*@ public normal_behavior
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:139: 错误: A \old token with no label may not be present in a requires clause
@ requires \old(containsPathId(pathId));
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:138: 警告: Method removePathById overrides parent class methods and so its specification should begin with 'also'
/*@ public normal_behavior
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:160: 警告: A non-pure method is being called where it is not permitted: com.oocourse.specs1.models.Path.containsNode(int)
@ (\forall int i; 0 <= i && i < arr.length; (\exists Path p; this.containsPath(p); p.containsNode(arr[i])))
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:161: 警告: A non-pure method is being called where it is not permitted: com.oocourse.specs1.models.Path.containsNode(int)
@ &&(\forall Path p; this.containsPath(p); (\forall int node; p.containsNode(node); (\exists int i; 0 <= i && i < arr.length; node == arr[i])))
^
F:\6\oo\homework 9\first - right\src\MyPathContainer.java:159: 警告: Method getDistinctNodeCount overrides parent class methods and so its specification should begin with 'also'
/*@ ensures (\exists int[] arr; (\forall int i, j; 0 <= i && i < j && j < arr.length; arr[i] != arr[j]);
^
2 个错误
17 个警告
给出了两个错误和十七个警告。两个错误说的似乎都是\old,翻译过来似乎是old不能够出现在requires语句当中。其余的警告主要是两类,一类是重写父类的方法,规格应该以also开头,另外一类是调用了非纯的并且未经允许的方法。
三、部署JMLUnitGN/JMLUnit
针对简单的比较函数进行检测:
/*@ public normal_behaviour
@ ensures \result == lhs - rhs;
*/
public static int compare(int lhs, int rhs) {
return lhs - rhs;
}
用相关指令序列进行JMLUnitGN的使用:
F:\6\oo\blog\third>java -jar F:\6\oo\share\jmlunitng.jar -d test -sp src src\Demo.java
F:\6\oo\blog\third>javac -cp "F:\6\oo\share\jmlunitng.jar" -sourcepath src -d testout test\*.java
F:\6\oo\blog\third>java -jar F:\6\oo\share\openjml\openjml.jar -rac -specspath src src\Demo.java
F:\6\oo\blog\third>java -cp "F:\6\oo\share\jmlunitng.jar;./" testout\Demo_JML_Test
错误: 找不到或无法加载主类 testout\Demo_JML_Test
F:\6\oo\blog\third>cd testout
F:\6\oo\blog\third\testout>java -cp "F:\6\oo\share\jmlunitng.jar;./" Demo_JML_Test
运行的结果是:
F:\6\oo\blog\third\testout>java -cp "F:\6\oo\share\jmlunitng.jar;./" Demo_JML_Test
[TestNG] Running:
Command line suite
Failed: racEnabled()
Passed: constructor Demo()
Passed: static compare(-2147483648, -2147483648)
Passed: static compare(0, -2147483648)
Passed: static compare(2147483647, -2147483648)
Passed: static compare(-2147483648, 0)
Passed: static compare(0, 0)
Passed: static compare(2147483647, 0)
Passed: static compare(-2147483648, 2147483647)
Passed: static compare(0, 2147483647)
Passed: static compare(2147483647, 2147483647)
Passed: static main(null)
Passed: static main({})
===============================================
Command line suite
Total tests run: 13, Failures: 1, Skips: 0
===============================================
只有一个racEnabled()Failed了,其余的从最后的结果看起来,似乎实在进行一些边界情况的检测。
Demo_JML_Test中racEnabled()是这样的:
似乎是通过isRACCompiled这个方法来判断到底是不是RACCompiled的,然后其余的test前面有一句:
@Test(dependsOnMethods = { "test_racEnabled" },
应该是test_racEnabled()这个是false的时候,其余的test就不进行检测。那大概意思就是racEnabled不成立的时候,会跳过后面的所有test,而这个本身是运行时检测,具体为什么failed我好像没有找到原因。
四、架构设计
4.1第九次作业
第一次作业比较简单,在Path中使用一个Arraylist来存储结点。在PathContainer中使用了三个hashmap来存储信息,一个存path和id的对应关系,一个存id和path的对应关系,还有一个存节点和节点统计的hashmap。其余没有什么特别的。其中用两个hashmap来存储id和path的对应关系只是为了让查找的时候时间更短而已。
4.2第十次作业
第二次作业单独开了一个叫做AdjMat类,用了两个hashmap嵌套来存储相关内容,一个存储邻接矩阵,一个存储点到点的距离。
private HashMap<Integer, HashMap<Integer, Integer>> adjMat =
new HashMap<>();
private HashMap<Integer, HashMap<Integer, Integer>> length =
new HashMap<>();
然后通过bfs来计算最短的距离,然后存进length当中。
4.3第十一次作业
第三次作业其实就是处理带权图的问题,不满意度,价钱,换乘三个问题都是算带权图最短路径的问题,只是带权的方式不同而已。对于这三个问题,使用了一个叫做WeightedGraph的类,完善了带权图的相关内容,主要是维护带权图和进行迪杰斯特拉算法存储距离信息。然后另外两个类作为子类继承,重写了赋权的部分,这样就节省了大量的重复代码。至于NodeTuple是一个二元组,是存了节点和节点属于的path。
第三次作业计算block的问题,采用了并查集的相关算法,所以单独造了一个叫做block的类。
五、bug分析和修复
bug1:block当中用于存储属于哪一个block的数据没有清洗。在计算block的时候,采用的是并查集的方法, 并用一个hashmap结构来存储,key为node值,value为node所属的block。但是每次进行path_add或者remove之后,需要清空hashmap然后重新计算。不清空的时候,会导致使用脏数据,而产生错误。在每次重新进行并查集的计算时候,加入clear操作,便可以了。
修复情况:
修复前,输出三条路径,以及计算结果为错误的1.
修复后,输出的三条路径,以及正确结果2.
bug2:
修复后的equals,途中第六行的path.equals原来为==:
修复效果:
六、心得体会
这部分其实是分两步的,一方面是写规格,一方面是根据规格写代码。
写规格的时候需要考虑完善,需要仔细思考慢慢琢磨。
而根据规格写代码似乎工作量要比前者大很多,不仅要考虑如何实现,还得考虑实现的要符合规格。实现的部分需要很仔细的保证没有问题。但同时规格规定的并不一定是实现的,比如这次作业中规定path中应该用数组存,但实际上用Arrayllist存储会更加方便。规格简化了很多东西,这给实现带来了很大的发挥空间。