OO第三单元——基于JML的社交网络总结
OO第三单元——基于JML的社交网络总结
一、JML知识梳理
1)JML的语言基础以及基本语法
JML是用于java程序进行规格化设计的一种表示语言,是一种行为接口规格语言。其为严格的程序设计提供了一套行之有效的方法。通过JML以及其支持工具,不仅可以基于规格自动构造测试用例,并整合了SMT Solver等工具以静态方式来检查代码实现对规格满足情况。一般来说,其运用于以下两种情况:①开展规格化设计②针对已有的代码,书写其对应的规格,从而提高代码的可维护性。
基本语法:
关键词 |
含义 |
pure |
纯粹的查询方法 |
non_null |
非null修饰 |
normal_behavior |
正常行为界定 |
exceptional_behavior |
异常行为界定 |
requires |
前置条件,满足才可进入 |
ensures |
结束行为的后置条件,保证要求达到 |
assignable |
副作用,可赋值 |
modifiable |
副作用,可修改 |
signals(e) |
异常行为时,抛出异常e |
signal_only |
满足前置条件抛出异常 |
invariant |
不变式,在所有可见状态下,都要满足的性质 |
constraint |
状态变化的约束 |
\result |
方法执行后的返回值 |
\old(x) |
x在方法执行前的值 |
\not_assigned(x, y, ...) |
括号内的元素全程不对其赋值 |
\not_modified(x, y, ...) |
括号内元素在函数执行时不该改变值 |
(\forall T x; R(x); P(x)) |
全程量词,满足R(x)的都可以满足P(x) |
(\exists T x; R(x); P(x)) |
存在量词,存在一个满足R(x)同时也满足P(x) |
(\sum T x; R(x); |
对满足R(x)的x,其对应的表达式求和 |
(\max T x; R(x); |
对满足R(x)的x,其对应表达式的极大值 |
(\min T x; R(x); |
类上,求极小值 |
&& 、||、==、==>、<==> |
与、或、相等、蕴含、互蕴含 |
应用工具链:
JML可以使用的工具链如下:
·(普普通通)Junit:简单的进行构造数据,对其进行规格化检验。
junit.jar https://repo1.maven.org/maven2/junit/junit/4.13/
hamcrest-core.jar https://repo1.maven.org/maven2/org/hamcrest/hamcrest-core/1.3/
两个下载地址,可以下载jar包,然后再IDEA进行添加包就可以测试了。
·OpenJML:可以实现JML语法错误检查。
可以在官网进行下载。http://www.openjml.org/downloads/ ,解压到本地然后利用cmd,即可
- java -jar .\openjml.jar -exec 解释器绝对路径 -esc 被检测的JAVA文件的绝对路径(如果为文件夹或包,记得前面加 -dir)
·SMTSolver: 在逻辑层面、对代码实现形式化验证。
·JMLUnitNG: 根据JML规格生成对应的测试样例来测试程序。
可以在http://insttech.secretninjaformalmethods.org/software/jmlunitng/下载jar包,然后利用cmd进行使用,具体操作见下一章。
二、openJML以及SMT Solver部署
本地使用的MT Solver为z3版。
1)测试已有的作业代码
首先本地运行openJML时发现其不支持三目运算符,所以将存在的
- \result == (people.length == 0? 0 : ...
修改为:
- /*@ public normal_behavior
- @ requires people.length == 0;
- @ ensures \result == 0;
- @ also
- @ public normal_behavior
- @ requires people.length > 0;
- @ ensures \result == ((\sum int i; 0 <= i && i < people.length; people[i].getAge()) / people.length);
- @*/
(另一块类似操作)
之后运行后本地结果为:
其中大部分警告是The prover cannot establish an assertion (ArithmeticOperationRange),这是因为我们给出的规格上并没有明确的JML规格要求,所以这里无法给出断言。
两个独特的是:Precondition conjunct is false,是原因规格并没有对person进行非null的限定(虽然运用equal是可以的,但是规格中使用的是==,所以严格来讲要对输入的person进行非null限定)。
Associated declaration:是因为自己在实现Mygoup的时候使用了这个函数(至于为什么造成这个警告我也很迷)。
2)测试一个简单的代码程序
- public class Testclass {
- public static void main(String[] args) {
- Testclass testclass = new Testclass();
- System.out.println(testclass.mul(520, 521));
- System.out.println(testclass.div(10, 5));
- System.out.println(testclass.mod(10, 3));
- }
- //@ensures \result == a * b;
- public int mul(int a, int b) {
- return a * b;
- }
- //@ensures \result == a / b;
- public int div(int a, int b) {
- return a / b;
- }
- //@ensures \result == a % b;
- public int mod(int a, int b) {
- return a % b;
- }
- }
(这是一个很多人都使用的代码,其中div和mod有明显的错误(不能对0进行操作))
测试结果:
我们注意到2、3条警告信息,PossiblyDivideByZero明确的指出了,div和mod操作不能对0进行运算。而另外两个是出于算数溢出的考虑(ArithmeticOperationRange)。
三、JMLunitng测试
下载JMLunit的jar包:http://insttech.secretninjaformalmethods.org/software/jmlunitng/
点击1.4即可下载。
1、然后,将所下载的jar包添加到我们的project中(具体内容可以参考junit实验时候的添加jar包操作)
2、在相应的路径下(本人选择src目录下)用git bash(cmd也可),生成自动测试用例指令如下:
- java -jar openjml/jmlunitng.jar(jmlnnit.jar的路径) com/oocourse/spec3/main/MyGroup.java(Mygroup路径) com/oocourse/spec3/*(其余文件路径)
(以上都是在src下的相对路径)
3、运行时要修改一些代码(自己的问题和官方代码书写的不规范):
出现52、51的警告信息可以忽略(这是版本不匹配)。然后对于所存在的new Arraylist<>()等等(Hashmap/Hashset)中<>的内容都要补全,官方代码出现的0<=i<xxx.length不符合要求需要改成0<=i&&i<xxx.length。
4、成功生成代码之后,我们只需要运行MyGroup_JML_Test.java这一个已经生成的测试文件
本人结果如下:
- [TestNG] Running:
- Command line suite
- Failed: racEnabled()
- Passed: constructor MyGroup(-2147483648)
- Passed: constructor MyGroup(0)
- Passed: constructor MyGroup(2147483647)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.addPerson(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.addPerson(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.addPerson(null)
- Failed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.delPerson(null)
- Failed: <<com.oocourse.spec3.main.MyGroup@1f>>.delPerson(null)
- Failed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.delPerson(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.equals(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.equals(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.equals(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.equals(java.lang.Object@5479e3f)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.equals(java.lang.Object@27082746)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.equals(java.lang.Object@66133adc)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getAgeMean()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getAgeMean()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getAgeMean()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getAgeVar()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getAgeVar()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getAgeVar()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getConflictSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getConflictSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getConflictSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getId()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getId()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getId()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getPeople()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getPeople()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getPeople()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getRelationS()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getRelationS()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getRelationS()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getRelationSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getRelationSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getRelationSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getValueS()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getValueS()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getValueS()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.getValueSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.getValueSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.getValueSum()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.hasPerson(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.hasPerson(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.hasPerson(null)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.hashCode()
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.hashCode()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.hashCode()
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.setRelationS(-2147483648)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.setRelationS(-2147483648)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.setRelationS(-2147483648)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.setRelationS(0)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.setRelationS(0)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.setRelationS(0)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.setRelationS(2147483647)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.setRelationS(2147483647)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.setRelationS(2147483647)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.setValueS(-2147483648)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.setValueS(-2147483648)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.setValueS(-2147483648)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.setValueS(0)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.setValueS(0)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.setValueS(0)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001f>>.setValueS(2147483647)
- Passed: <<com.oocourse.spec3.main.MyGroup@1f>>.setValueS(2147483647)
- Passed: <<com.oocourse.spec3.main.MyGroup@8000001e>>.setValueS(2147483647)
- ===============================================
- Command line suite
- Total tests run: 67, Failures: 4, Skips: 0
- ===============================================
- Process finished with exit code 0
通过运行结果发现,jmlunitng生成的测试用例主要是对边界情况的测试用例。我们可以看到许多的如2147483647和-2147483648和0等测试用例,对于引用类的测试都是用null来测试的。这样子的测试数据比较特殊,缺少随机性,所以对于正确性检验,强度不是很大。(比如本次出现的3个没有通过的点,都是对对象没有null的判断,但是在本次作业中,我们的输出是不会出现null的,所以正确性还是有保证的,但是这么多pass我就很安全了吗?)
四、设计架构
1)总论
三次作业的设计架构都是基于JML规格进行代码实现的,整体上架构思路还是比较清晰的,最后完成的是一个社交网络Network。其中,社交网络包括三个类——Person,Group,Network。Person类作为基本元素,拥有如ID、姓名、性格、认识的人群的基本信息,Group表示在整个网络中存在的小的组,Network是整个社交网络,内含有多种操作。三次作业完成的架构图如下:
(基于JML所以架构还是非常清晰的)
三次作业不难看出,算法上的考察点基本上是图论的问题,所以数据结构学习很重要(悔数据结构摸鱼)
2)分论
第一次作业
第一次作业基本上按着JML的意思,就可以知道大概的意思,所以第一次作业没有很大的难点,主要的难点就是iscircle函数(函数名太迷惑了,一开始jml没认真看,就以为是寻找环,差点就要提前学习Tarjan了)。
iscircle的函数主要使用了bfs的思想,从id1开始广度搜索,如果无法连接到id2,则返回false。
第二次作业
第二次作业,总体上难度上升比较平和,主要是加强了数据量,在第二次作业更多的要考虑实现的复杂度。第二次作业架构上加入了Group的类,主要实现一个群里的相关数据的总和,这个时候就需要遍历每一个成员,这个时候就要谨慎复杂度,如valuesum若用双循环来写的话,每一次查询都很麻烦。所以在Group类中valuesum,relationsum等成员变量优化了算法,只需在Group添加成员的时候,和添加关系的时候维护一下变量即可。
另一方面,第二次作业相对于第一次,把第一次数组结构进行了优化,(Arraylist转为Hashmap,由id直接得到Person变量,一定程度上优化了)
第三次作业
(个人感觉,突然就不温水煮青蛙了,直接打算卸磨杀驴了)第三次作业,难度上升较大。虽然增加新的方法不多(增加的借钱系统,个人感觉只是为了丰富这个社交网络的实现,难度实现并不大),本次作业个人觉得难度大的函数有二——qmp寻找最小路径(Dijstra算法)、qsl寻找点联通分量(Tarjan算法)。两个函数难度体现在算法上,具体算法可以参考CSDN上多篇博客,算法优化放在后面的bug分析上。
然后在实现querryBlockSum,一下子傻眼了了,如果按照前面两次作业的思维,根据jml写代码,那么直接用jml所说的双循环,再判断是否iscircle,那这样的复杂度绝对很大。最后在大佬的帮助下,渐渐意识到,这个函数的jml的可以理解成有多少连通图,即一旦两个节点连通,那么这两个点可以合成一个图,再来一个点和二者之中一个连通,又成了一个连通图(开始意识到并查集的使用),那么一旦两个不连通,就分别存在两个图中。
所以第三次作业实现上,大改前面的风格,使用了并查集的方法。这一方法使用,大规模降低了iscircle的复杂程度,不过对于querryBlockSum的函数实现,还是使用了维护的方法,加入一个人,blocksum+1,添加关系时,先判断是否iscircle(此时的复制度就o(1)),如果不是则blocksum-1。其他方面,由于并查集优化了iscircle函数,所以在上述最小路径和寻找连通分量的算法,可以先运用iscircle进行快速判断,排除错误情况(我觉得第三次作业还是老老实实用我之前的做法来写的话,十之八九会当场死于cputle)
3)再次总结
架构上,还是没有做到迭代开发,第三次相对于前两次而言改动太大了。同时,也是印证了助教们的提示,不要照着jml写代码!!!看懂jml在说什么不难,但是讲说的内容和题目背景,以及图论一些架构合在一起理解就需要自己的思考,比如qsl,将存在两条不重合的路径理解成存在点连通分量并想到使用Tarjan算法来实现就很有难度(如果按jml意思暴力求解,可行性极低,并将tle)。所以架构上的思考还是需要有的。
五、BUG分析和修改策略
第一次作业
强测互测安然无事,主要还是第一次作业难度不大,主要卡的点也是iscircle函数,在与大佬多次对拍之后,亦可完善,并且,第一次作业对复杂度要求不高,所以iscircle实现起来也不用考虑其复杂度。
第二次作业
强测被炸四个点,而且这个bug出现的也是独特(在众多more than之中就我一个less than),主要原因在于判断的顺序,在addToGroup函数中中jml规定了group中人数不超过1111,大部分人选择不判,而我选择了先判定,这样就造成了,如果一个group人数超过1111,并且加入的这个人是非法的,不会抛出异常,而是会直接先返回,所以输出少了,所以给出的建议就是,先判异常再判断其他。
互测被某狠心玩家测爆了iscircle的复杂度。理论上第二次即使数据再大,也不会测爆一般规格的bfs的算法。但是在构建队列的时候,标记人员是否使用过的时候,使用了indexof函数(事后才知道这是一个o(n)复杂度的函数),所以使得复杂度被强硬的提升了一级。修改上,就是利用其他更高效的容器,hashset存储标记过的人员,使用contais函数进行判断是否标记,优化了函数。
第三次作业
强测依旧死于tle。主要是最短路径的求法上出了问题,不加以优化的Dijistra算法复杂度较高,所以修改时,学习了优先队列的相关知识,使用优先队列,省去了每次寻找最小值的复杂。大大降低了复杂度。
互测自己并未发现bug,但是依靠自己对代码的理解,在互测中hack了7刀。首先,是对qsl的jml代码理解,虽然我们可以理解成双连通分量,但是对于有且只有两个互相关联,它们是双连分量(用Tarjan算法求得),但是在本题的jml规格中并不是,所以可以hack一把。
其次是针对qsl和最小路径复杂度攻击,qsl实现如果用双bfs会存在重大bug。其次对于以下图:
暴力求解会存在效率问题。所以自己和小伙伴一起捏造了该数据进行hack(我的屋中,这一刀hack了4个人)
六、总结
相比与前面两个单元,jml单元对测试的能力要求更高,无论是基于Juint的基础测试,还是自己构造的数据测试,都要求自己能看懂JML规格,根据所理解的规格构造对应的针对数据,对边缘条件进行深度测试。另外一方面,JML这一种规格描述语言,给我最大的体会就是,面向对象的重点不单单是在实现,也在于整体架构,JML就是强调了架构,弱化了具体过程的实现,换句话说,剥离了二者过程,架构师架构了思维,想让程序员实现方法,并不是通过神奇的”先这样再那样“的量子对话,而是用JML实现了二者交流,完成了一个具体项目的实现。
再回到本单元作业实现的本身上,在代码实现上的体会在上文也说到了。但是再具体到其中某些算法的实现,不难看出,本单元更多的与图进行对接,而自己在代码实现中,对图论的知识了解甚少,尤其是到了最后一次,出于无奈之下,才被迫研究起了并查集。而且对于已经学过的迪杰斯特拉算法和未学过的Tarjan算法,在理解上都很难做到吃透吃全。所以下一单元大概率还是基于图来学习新的知识,自己在图论方面的能力也有待提高。
最后,希望自己能够再接再厉,最后在征战第四单元能有更多收获。
OO第三单元——基于JML的社交网络总结
OO第三单元——基于JML的社交网络总结的更多相关文章
- OO第三单元作业(JML)总结
OO第三单元作业(JML)总结 目录 OO第三单元作业(JML)总结 JML语言知识梳理 使用jml的目的 jml注释结构 jml表达式 方法规格 类型规格 SMT Solver 部署JMLUnitN ...
- 2020 OO 第三单元总结 JML语言
title: 2020 OO 第三单元总结 date: 2020-05-21 10:10:06 tags: OO categories: 学习 第三单元终于结束了,这是我目前为止最惨的一单元,第十次作 ...
- OO第三单元总结——JML
目录 写在前面 JML理论基础 JML工具链 JMLUnitNG的使用 架构设计 Bug分析 心得体会 写在前面 OO的第三单元学习结束了,本单元我们学习了如何使用JML语言来对我们的程序进行规格化设 ...
- 2019年北航OO第三单元(JML规格任务)总结
一.JML简介 1.1 JML与契约式设计 说起JML,就不得不提到契约式设计(Design by Contract).这种设计模式的始祖是1986年的Eiffel语言.它是一种限定了软件中每个元素所 ...
- OO第三单元总结——JML规格设计
• 1.JML语言的理论基础.应用工具链情况 JML(Java Modeling Language)—— java建模语言,是一种行为接口规范语言( behavioral interface spec ...
- OO第三单元总结——JML规格
一.JML简介 1.JML语言的理论基础 JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML是一种行为接口规格语言 (Behavior In ...
- OO第三单元(地铁,JML)单元总结
OO第三单元(地铁,JML)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉并了解JML来是我们具有规格化编程架构的思想.这个单元的主题一开始并不明了,从第一次作业的路径到第二次 ...
- OO第三单元——JML规格化设计
OO第三单元--JML规格化设计 JML语言的理论基础以及应用工具链情况 理论基础 JML是对JAVA程序进行规格化设计的一种表示语言,是一种行为接口规格语言.JML整合了Java和JAVAdoc,并 ...
- OO第四单元——基于UML的UML解析器总结&OO课程总结
OO第四单元--基于UML的UML解析器总结&OO课程总结 前言:一学期愉快(痛苦)的OO课程学习结束了,OO几个单元作业都各有特色,实验也各有特色,仔细回味起来,不再是单纯的敲代码(但自己还 ...
随机推荐
- sketch 导出 svg
sketch 导出 svg refs xgqfrms 2012-2020 www.cnblogs.com 发布文章使用:只允许注册用户才可以访问!
- Flutter: MediaQuery
Flutter Widget of the Week 使用MediaQuery根据不同的屏幕大小调整应用程序的UI布局. 您还可以使用它根据用户布局首选项进行UI调整. class _MyHomeSt ...
- Renice INC:解密干型葡萄酒
市场上,干型葡萄酒往往对比甜型葡萄酒(如甜红.甜白)受到更多葡萄酒爱好者的青睐.在葡萄酒界,大部分的红葡萄酒和白葡萄酒也都是干型的,而且它们的口感往往各有特色,并非千篇一律.今天,就跟随Renice ...
- 08.手写KNN算法测试
导入库 import numpy as np from sklearn import datasets import matplotlib.pyplot as plt 导入数据 iris = data ...
- vue-eahars生产编译报错
{ test: /\.js$/, loader: 'babel-loader', include: [resolve('src'), resolve('test'), resolve('node_mo ...
- Anno&Viper -分布式锁服务端怎么实现
1.Anno简介 Anno是一个微服务框架引擎.入门简单.安全.稳定.高可用.全平台可监控.依赖第三方框架少.底层通讯RPC(Remote Procedure Call)采用稳定可靠经过无数成功项目验 ...
- es命令测试
1.新建索引并赋值 :put/索引名/文档名/id //文档名后面会逐渐取消 相当表 PUT /test1/type1/1{ "nmae":"hb", &quo ...
- 水墨屏开发设备,旧 Kindle 改造而成
原文地址:Turning an old Amazon Kindle into a eink development platform 原文作者:adq 译者 & 校正:HelloGitHub- ...
- javascript中的模块系统
目录 简介 CommonJS和Nodejs AMD异步模块加载 CMD ES modules和现代浏览器 在HTML中使用module和要注意的问题 简介 在很久以前,js只是简单的作为浏览器的交互操 ...
- es6 快速入门 系列 —— 变量声明:let和const
其他章节请看: es6 快速入门 系列 变量声明:let和const 试图解决的问题 经典的 var 声明让人迷惑 function demo1(v){ if(v){ var color='red' ...