第三单元我们对JML进行了学习,并加深了对形式化设计的理解。本单元通过给定的JML来实现了一个人际关系网,最后实现了一个人际关系管理网络,表示了人与人之间的关系,人所在群组,人与人之间的借贷关系,以及离散数学中的连通,强连通,最短通路等关系。
一、JML理论基础
JML是对java程序进行规格化设计的一种规范语言。通过JML以及其支持工具(例如Junit,Openjml等),还可以基于规格自动生成边界测试用例,还可以对JML语法进行检查。一般而言,JML用途主要是规范java程序的实现,提高代码的可读性以及可维护性。
JML通过注释的方式来表示规格,主要定义了方法规格、类型规格以及表达式。
1.方法规格:
前置条件requires表示该方法在进入这个分支之前所需要满足的前置条件。在进入不同的分支后,一般而言会出现normal_behavior和exceptional_behavior,分别表示正常行为和异常行为。对于需要抛出异常的情况,使用signals (Exception e)expr进行表示。同时当一个方法中涉及了多个条件的规格描述时,可用also来进行分割。
2.类型规格
在原本的方法规格的基础上,类规格需要对该类中所有的数据成员进行定义和声明。
3.表达式
为表达执行结果要满足的要求,使用原子表达式\result还有\old等;为表示数组特性,使用量化表达式\forall、\sum、\exist等等。通过这些基本的表达式组合起来便可以实现多种复杂的效果。
二、JML工具链
openjml:底层使用了SML Solver,对jml规格进行语法检查,通过形式化方法来验证jml程序的正确性等等。
SMT Solver:检查jml与代码实现的一致性。
JMLUnit:根据规格自动生成测试样例并且进行测试。
三、JML工具链的使用
3.1 OpenJML
OpenJML用以检查JML代码的语法正确与否。在此附上下载链接:
https://www.openjml.org/downloads/
用到的指令:java -jar openjml.jar -rac test/MyGroup.java test/MyPerson.java test/GroupNature.java(将自己的文件路径进行对应的替换)
使用如上指令对JML进行检查,输出结果如下:
输出了不少警告,但是这些警告基本可以忽略。
3.2 JMLUnitNG
JMLUnit可以根据规格自动生成测试样例并且执行,最后输出通过与否的结果。不过在执行过程中本人发现JMLUnitNG生成的数据点均为极端数据点以及Null,不是很具有说服力以及覆盖性显然不够完善。下为代码运行流程:
将待测试的Group以及相关类(如Person还有自己在Group内声明的类),在将所有java文件的Arraylist以及hashmap中的<>填满后,文件树如下:
第一步:输入java -jar jmlunitng.jar test/MyGroup.java,生成测试文件。
输入后测试文件夹文件如下:
第二步:编译全部java文件:输入javac -cp jmlunitng.jar test/*.java
第三步:执行MyGroup_JML_Test对Group接口进行测试:java -cp jmlunitng.jar test.MyGroup_JML_Test,最终结果如下:
观察上图可以看出,测试数据基本都是边缘数据以及NULL,而failed的数据点由于jml没有规定null的对应处理,因而failed的测试点并没有什么参考价值。如果想要彻底地测试一遍自己的代码,对拍也许是一个不错的选择。
四、作业分析
4.1 架构及算法
本单元作业感觉实现起来难度相对于前几次作业均要简单,这也让我陷入了实现成功便能满分的误区,然而实现算法之后的优化才是重中之重(微笑脸)。
由于本单元的模式是给定规格实现方法,因而本人并未太多考虑架构问题,照着JML写就完了,奥利给!
第三次作业UML类图如下:
第一次作业算法选取:主要考虑了iscircle的算法实现,三次作业本人均采用的非递归dfs。
第二次作业算法选取:主要采用了每次改动数据时进行保存,在方法需要使用时直接调用属性返回对应的值即可。还有选择了合适的容器,例如hashmap来降低时间复杂度。
第三次作业算法选取:对于BlockSum,采取了每加一个person便将块数加一,而在添加关系时判断添加关系的两人之前是否相连来判断块数是否需要减一的算法。而对于queryStrongLink,本人采用的暴力枚举的方法,即遍历图中所有点并将其删去,而后判断该两点是否依然连通的方法。
4.2 出现的bug以及互测
4.2.1 出现的bug以及分析
本单元主要易错点在于对算法的优化方面,JML实现起来较为简单,但是由于中测约等于不测的事实,如果我们在平时写作业缺乏了对算法的优化,那么在强测中极有可能直接爆炸(本人无误了)。
本人在本单元中采用的测试方法主要是与同学对拍,然而由于脚本对拍无法对时间进行限制(当然也很有可能是本人能力不足所造成的),因而在对拍时没有问题,但在强侧中却屡次出现tle的问题,导致了本人对本单元成绩实在是不忍直视。本单元所有bug均为tle,在完善修改的过程中,我发现自己的问题主要集中在容器选择有误,数据初始化重复进而浪费时间,未完全采用数据更新的方法进行代码编写。
4.2.2 互测策略
本单元的互测我主要采用了对拍的方式,但是由于大家的正确性基本都得到了保证,因而对拍在此单元显得没有那么地有优势。到头来还是针对tle盲狙好用。
五、心得体会
本单元主要内容为JML规格的实现,同时涉及到图的基础算法实现。单就JML而言,实现规格无疑是简单的,但是对基础算法的优化还是需要我们细细打磨,而不是实现正确性之后匆匆提交,然后强测爆炸。同时这单元也暴露了本人对于基础算法掌握不牢的缺点。今后我也应该多多重视基础算法的理解、实现以及优化,争取实现一个相对较优的结果。