OO第三单元JML总结

第三单元JML总结

JML语言的理论基础

JML语言是对java程序进行规格化设计的一种表示语言,通过对方法的输入和输出的结果进行规格化的描述以此规定方法的功能和要求;JML语言有一套完整的语法体系,能够比较好的展示方法之间的关系,以及对方法输入和输出的描述,通常JML语言用于定义接口中的方法的实现要求,规定接口的行为。同时JML有一套自己的工具链,用于对JML进行语法检查(openjml),自动生成测试用例(Junit),检查代码和规格之间的满足情况(SMT Solver)等工具。

总的来说JML语言的有两个主要的作用

  1. 在对设计的规格进行准确的传达,保证方法实现的正确性
  2. 对已实现的代码进行梳理,便于之后的维护

应用工具链情况(openjml,jmlunit)

openjml的使用

使用openjml进行静态检查

使用步骤:

  1. 首先将需要检查的规格代码的文件的绝对路径放到一个txt文件中

  2. 使用如下命令进行代码检查:

    java -jar yourPath\openjml.jar -exec yourPath\Solvers-windows\z3-4.7.1.exe -check @yourpath\java.txt -encoding UTF-8

    参考博客:https://www.cnblogs.com/Yzx835/p/10907084.html

    分析使用 -exec制定solver进行静态代码检查

    结果如下,发现两个错误

YOB0Y9.png

使用openjml进行动态代码的检查,但好像由于openjml不支持++的原因报错(没能成功)

OO第三单元JML总结_第1张图片

Junit的使用情况

使用Junit进行自动化的测试,产生测试的用例,运行测试程序即可

具体步骤:

一开始尝试用Junit的时候由于包之间的引用关系的原因导致产生测试的时候找不到引用的类,因此我采用的办法是将包去掉,同时需要将所有涉及到的代码文件中的中文删去补全new Hashmap<....,....>;在完成上面的这些准备工作之后,才可以使用Junit产生测试用例

步骤:

  1. java -jar D:\ideaProjectFiles\jmlunitng.jar D:\ideaProjectFiles\H11JML\src\MyGroup.java

    使用该指令生成Group接口(这里用的是Mygroup,因为自己添加了一些方法)的方法的测试用例

  2. javac -cp D:\ideaProjectFiles\jmlunitng.jar D:\ideaProjectFiles\H11JML\src\*.java

    编译所有需要的java文件

  3. java -cp jmlunitng.jar src.MyGroup_JML_Test

    运行测试用例

部分结果如下:

OO第三单元JML总结_第2张图片

这是对方发进行修改之后的结果,刚开始运行的时候由于传入null,2147483648这种边界数据的时候,并没有考虑这种情况,因为在码代码的时候,只考虑了给定情况下的,对这种未定义的边界情况就没有考虑,所以导致出错;这也是Junit自动化测试的一个问题,他只产生了边界的数据,正常情况下的数据没有,所以这种测试的覆盖面是比较弱的,还是建议自己生成测试用例对程序进行正确性的检测,这个可以帮助检测便捷数据,提高鲁棒性

架构设计(代码质量分析)

第一次作业

UML图如下:

OO第三单元JML总结_第3张图片

分析:实现person和group的接口,总体比较简单

复杂度分析:

OO第三单元JML总结_第4张图片

总体来说还可以addrelation因为判断情况的条件导致复杂度可能会稍微大一点

第二次作业

第二次作业和第一次作业在架构上的差别就是增加了一个network接口的实现

UML图如下

OO第三单元JML总结_第5张图片

分析:架构上没有太大的变化,依旧是按照规格进行实现

复杂度分析

OO第三单元JML总结_第6张图片

分析:可以看到主要的复杂度集中在network中,这是整体架构所决定的,network中处理的关系涉及的类比较的,因为是接口实现,所以无法自己改变架构,不够架构还是比较清晰的。

第二次作业和第一次作业在实现上的不同,主要是将arraylist的存储方式改成hashmap,以此来减少访问所需要的时间;在算法上,由于这次的强测的指令条数有10W条,所以group的一些属性采用的是动态记录的方法,而不是遍历的方法,以防止ctle。

第三次作业

UML图如下

OO第三单元JML总结_第7张图片

分析:相较于第二次作业由于加入了qmp、qbs、qsl这种复杂度比较高的操作,整体对时间上的要求更加的严格,因此在实际实现的时候会用到一些算法和数据结构,我添加了一个类,将其统一封装在alogrithm类中

复杂度分析:

OO第三单元JML总结_第8张图片

OO第三单元JML总结_第9张图片

分析:和实际的实现是一致的,对于qsl,qmp这种需要使用特定的算法的复杂度会搞一些

具体实现上:

qmp:使用堆优化的dijstra算法求最短路径的长度,不要使用未优化的dijistra算法,n^2的复杂度可能会超时,在指令条数比较多的时候一定会超时;堆优化可以使用java自带的PriorityQueue优先队列进行优化

qbs:实测如果是采用的遍历的方法一定会超时,可以采用动态记录的方式进行优化;具体实现:在加入一个人的时候blocksum++,在添加关系的时候,如果两个在加关系之前是!iscircle的blocksum--即可;

qsl:有两种实现的方法均可,一种是采用tarjan算法,比较复杂度要小一下,保险,但是在实现tarjan算法的时候要做好测试,如一个节点在多个连通分量中的情况等等;另一种是标程使用的方法,双重遍历,每次mark掉一个节点,看两个目标节点是否iscircle,如果遍历完之后每次都是连通的,则为true,否则为false;注意:这里说的mark不是实际的删去节点,可以在使用bfs或dfs判断iscircle之前将对应节点的isvisited设置为true即可

剩下的其他方法的实现需要注意的是尽量的减少遍历,采用hashmap进行存储,减少访问时的遍历;尽可能的采用动态记录的方式,提高性能。

bug及修复情况

第一次作业

没有bug:第一次作业比较简单,按照规格进行实现即可

第二次作业

WAbug:这次作业的bug其实是之前我把network的整个过程理解错了,原来我以为person在创建开始的时候可以不被加入到network中而自己进行一些操作,后来开了Runner的代码之后发现并不是这样的,所有的指令都是调用network的方法进行的,也就是说任何的person和group在创建的同时就已经被加入到network中,后来改的时候忘记把属性的static的变量改成正常的变量,导致错误,如果仅仅是这样的话,在课下debug的时候也是可以de出来的,但是无独有偶,我的测试脚本写错了,写成了自己和自己对拍(虽然我也不知道是啥时候把脚本改错了,心里痛)结果以为自己没问题,结果强测爆炸,难受。

第三次作业

CTLE(超时bug):原因是queryblocksum采用的是遍历的写法,导致时间超时,最后改成动态记录的方法即可,这告诉我们当需要考虑性能的时候,不要放过任何一个遍历操作,以为任何一个都有可能被卡T

其他人的bug:

主要是第三次作业发现的别人的bug:主要是tarjan算法没有考虑一个节点在多个连通分量中的情况

心得体会

本以为这张比较的简单,但是被现实打脸,搞过之后才知道助教说的简单是指0,1的简单(过就满,有bug就凉),皮完之后说些正经的,总的来说,这一张的收获还是蛮大的,了解了JML语言在Java中的使用,明白了规格在整个代码中的重要作用,规格的重要性,了解了目前JML检测的一些工具链的使用,虽然一些工具还有很大的提升空间,但是总的来说,JML语言形成了比较完整的规格描述的体系,能够很大程度上提高代码的可维护性,相信对于规格的检查也会越来越完善,推广规格的使用;

另一个收获就是算法方面的学习,学习了dijstra算法,tarjan算法,复习了dfs,bfs等常用的数据结构,一定程度上提高了自己的代码编写能力吧。

这单元虽然不是像多线程和多项式那么难,但是需要仔细细心的阅读规格,做好优化,希望之后的作业中能够少一些粗心,多一些收获!

你可能感兴趣的:(OO第三单元JML总结)