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);exp)

对满足R(x)的x,其对应的表达式求和

(\max T x; R(x);exp)

对满足R(x)的x,其对应表达式的极大值

(\min T x; R(x);exp)

类上,求极小值

&& 、||、==、==>、<==>

与、或、相等、蕴含、互蕴含

应用工具链:

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的社交网络总结的更多相关文章

  1. OO第三单元作业(JML)总结

    OO第三单元作业(JML)总结 目录 OO第三单元作业(JML)总结 JML语言知识梳理 使用jml的目的 jml注释结构 jml表达式 方法规格 类型规格 SMT Solver 部署JMLUnitN ...

  2. 2020 OO 第三单元总结 JML语言

    title: 2020 OO 第三单元总结 date: 2020-05-21 10:10:06 tags: OO categories: 学习 第三单元终于结束了,这是我目前为止最惨的一单元,第十次作 ...

  3. OO第三单元总结——JML

    目录 写在前面 JML理论基础 JML工具链 JMLUnitNG的使用 架构设计 Bug分析 心得体会 写在前面 OO的第三单元学习结束了,本单元我们学习了如何使用JML语言来对我们的程序进行规格化设 ...

  4. 2019年北航OO第三单元(JML规格任务)总结

    一.JML简介 1.1 JML与契约式设计 说起JML,就不得不提到契约式设计(Design by Contract).这种设计模式的始祖是1986年的Eiffel语言.它是一种限定了软件中每个元素所 ...

  5. OO第三单元总结——JML规格设计

    • 1.JML语言的理论基础.应用工具链情况 JML(Java Modeling Language)—— java建模语言,是一种行为接口规范语言( behavioral interface spec ...

  6. OO第三单元总结——JML规格

    一.JML简介 1.JML语言的理论基础 JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML是一种行为接口规格语言 (Behavior In ...

  7. OO第三单元(地铁,JML)单元总结

    OO第三单元(地铁,JML)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉并了解JML来是我们具有规格化编程架构的思想.这个单元的主题一开始并不明了,从第一次作业的路径到第二次 ...

  8. OO第三单元——JML规格化设计

    OO第三单元--JML规格化设计 JML语言的理论基础以及应用工具链情况 理论基础 JML是对JAVA程序进行规格化设计的一种表示语言,是一种行为接口规格语言.JML整合了Java和JAVAdoc,并 ...

  9. OO第四单元——基于UML的UML解析器总结&OO课程总结

    OO第四单元--基于UML的UML解析器总结&OO课程总结 前言:一学期愉快(痛苦)的OO课程学习结束了,OO几个单元作业都各有特色,实验也各有特色,仔细回味起来,不再是单纯的敲代码(但自己还 ...

随机推荐

  1. H5 直播 & App 直播

    H5 直播 & App 直播 polyv 直播 https://github.com/polyv 宝利威 直播 https://www.polyv.net/live/ SDK https:// ...

  2. Flutter NotificationListener 监听列表的滚动

    import 'package:flutter/material.dart'; import 'package:flutter_imagenetwork/flutter_imagenetwork.da ...

  3. scala:分别使用懒汉式和饿汉式实现单例模式

    在java中,单例模式需要满足以下要求: 构造方法私有化,使得本类之外的地方不能使用构造方法new出对象 提供私有静态属性,接收单例对象 公共的.静态的getInstance方法,便于外界拿到单例对象 ...

  4. sql if else 用法

    语法: case when 条件1 then 结果1 when 条件2 then 结果2 else 结果N end 可以有任意多个条件,如果没有默认的结果,最后的else也可以不写, select c ...

  5. JS输出为[object object]

    问题描述:在控制台打印时应输出对象,但是却输出[object object] 解决办法:先将数据转换为json格式,然后再转换为json对象 JSON.parse(JSON.stringify(use ...

  6. Java编程开发之数据图表分析模型

    数据统计分析 多曲线图表分析实现 基本需求分析 假设在怪兽出没的年岁,加上年关在即,需要统计分析各个道路卡口车流量出入统计,主要从车流量和车牌地角度出发.如图所示的业务需求: 道路卡口-车流量分析: ...

  7. vue3中的通过proxy实现双向数据绑定的原理

    1.什么是Proxy?它的作用是? 据阮一峰文章介绍:Proxy可以理解成,在目标对象之前架设一层 "拦截",当外界对该对象访问的时候,都必须经过这层拦截,而Proxy就充当了这种 ...

  8. AJAX基本操作

    XMLHttpRequest对象: XMLHttpRequest 是 AJAX 的基础.所有现代浏览器均支持 XMLHttpRequest 对象(IE5 和 IE6 使用 ActiveXObject) ...

  9. Azure Front Door(一)为基于.net core 开发的Azure App Service 提供流量转发

    一,引言 之前我们讲解到使用 Azure Traffic Manager.Azure LoadBalancer.Azure Application Gateway,作为项目的负载均衡器来分发流量,转发 ...

  10. 如何在 Istio 中支持 Dubbo、Thrift、Redis 以及任何七层协议?

    赵化冰,腾讯云高级工程师,Istio Member,ServiceMesher管理委员,Istio 项目贡献者, Aerika 项目创建者 ,热衷于开源.网络和云计算.目前主要从事服务网格的开源和研发 ...