OO第三单元总结

时光毁灭记忆、已成空白 提交于 2020-07-28 10:28:26

一、理论基础

1.什么是JML

JML,即Java Modeling Language,是用于对Java程序进行规格化设计的一种表示语言。JML是一种行为接口 、规格语言(Behavior Interface Specification Language,BISL),基于Larch方法构建。BISL提供了对方法和类型的规格定义手段。所谓接口即一个方法或类型外部可见的内容。通过JML及其支持工具,不仅可以基于规格自动构造测试用例,并整合了SMT Solver等工具以静态方式来检查代码实现对规格的满足情况。

2.注释结构

JML的注释主要有行注释和块注释两种,用于不同情景

行注释的表示方式为: //@annotation

块注释的方式为: /*@annotation@*/

3.常用表达式

\result表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。

\old( expr )表达式:用来表示一个表达式 expr 在相应方法执行前的取值。

\not_assigned(x,y,...)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。

\forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。

\exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。

\sum表达式:返回给定范围内的表达式的和。

\max表达式:返回给定范围内的表达式的最大值。 

\min表达式:返回给定范围内的表达式的最小值。 

\num_of表达式:返回指定变量中满足相应条件的取值个数。

4.方法规格

方法规格的核心内容包括三个方面,前置条件、后置条件和副作用约定。

前置条件是对方法输入参数的限制,如果不满足前置条件,方法执行结果不可预测,或者说不保证方法执行结果的正确性。前置条件通过requires子句来表示: requires P;。

后置条件是对方法执行结果的限制,如果执行结果满足后置条件,则表示方法执行正确,否则执行错误。后置条件通过ensures子句来表示: ensures P;。

副作用指方法在执行过程中对输入对象或this对象进行了修改(对其成员变量进行了赋值,或者调用其修改方法)。JML提供了副作用约束子句,使用关键词 assignable 或者 modifiable 。

5.数据规格

JML针对类型规格定义了多种限制规则,目前主要涉及两类,不变式限制(invariant)和 约束限制(constraints)。无论哪一种,类型规格都是针对类型中定义的数据成员所定义的限制规则,一旦违反限制规则,就称相应的状态有错。

不变式(invariant)是要求在所有可见状态下都必须满足的特性,语法上定义 invariant P ,其中 invariant 为关键词, P 为谓词。

对象的状态在变化时往往也许满足一些约束,这种约束本质上也是一种不变式。JML为了简化使用规 则,规定invariant只针对可见状态(即当下可见状态)的取值进行约束,而是用constraint来对前序可见 状态和当前可见状态的关系进行约束。

二、SMT solver与JMLUnit测试

可能是由于版本的原因,这个我一直没能成功,命令行调用openJML时会得到如下报错:

在把jmlunitng.jar复制到src文件夹后,执行如下四条命令

  java -jar jmlunitng.jar test/MyGroup.java

  javac -cp jmlunitng.jar test/*.java

  java -jar openjml.jar -rac test/MyPerson.java test/MyGroup.java

  java -cp jmlunitng.jar test.MyGroup_JML_Test

然而在执行到第二步的时候发生了如下报错:

三、作业架构设计

1.第一次作业

类图

emm由于直接没进互测,找不到当时的代码了,生成不了类图,不过我还是可以简单的复述一下自己的架构的,毕竟第一次作业非常简单(简单到没进互测...)。当时我本来给这次作业预留了和前六次作业差不多的时间完成,没想到一上手,呦,这么简单的吗,这不就照着规格写嘛。于是我沉浸在这意外的喜悦中,也没有认真的思考应该采用什么容器,通通使用了Arraylist,这使得我后一次作业一开始的性能极差,同时,我也没有特别仔细的看官方包的规格,直接导致了我这一次没进互测。

2.第二次作业

类图

第二次作业增加了一个group类,同时还有时间限制,这一次我吸取了上次没进互测的惨痛教训,写每一个方法前都认真的读一遍规格,过了中测之后,我长呼一口气,认为这次应该没有什么问题了。诶,对了,这次好像还有时间限制,我自己生成了一组比较极端的数据,跑了一遍,然后我的IDEA直接卡炸了...必须开任务管理器清那种,于是又开始修复性能...最终,我的Network里的Person用Arraylist和Hashmap两个容器各存一遍,遍历用Arraylist,查找用Hashmap,IsCircle方法也使用了查并集,而Group里的方法我是通过设置有效位来避免重复计算,addperson之后5个有效位都变false,addrelation之后前两个有效位变false,最终终于过了强测,却还是被组里的大佬hack出了tle...这点到后面的bug修复再讨论。

3.第三次作业

类图

第三次的作业增加了组内删人的操作,同时在Network里增加了几个方法,也就是这几个方法成为了第三单元最大的难点(个人认为),关于新增的Money系统,相关的方法不多,除了新写的借钱和查钱,只需要在Network里加一个Hashmap来存储每个人的钱,同时在addperson的时候记得初始化出来就好。至于blocksum,和第二次作业中group里面的五个属性类似,选择动态维护。最关键的qmp和qsl两个函数,qsl我借鉴讨论区的大佬,采用了bsf+分别删去路径上每一个结点检查是否联通的方法,而qmp我自己想了一个方法,那就是每次addrelation的时候就更新每个点到每个点的最短路径,这样一来每次调用这个方法就只用查表就好了,简直完美!但是这个方法在性能上也有大问题,最终导致了我又一次没进互测...这个悲伤的故事也留到bug修复那里再谈。

四、BUG及修复

1.第一次作业

第一次作业的bug是在addrelation方法,我在查看规格的时候漏掉了一行,没有看到id1==id2要当无事发生,我直接当异常了,于是一个悲伤的故事就发生了...

2.第二次作业

第二次作业没有在强测中测出bug,但是同屋的大佬测出了我的性能还是不过关,尤其是group类里的五个统计量,虽然我每次设置了有效位,但是当有效位无效时,我还是要从头开始一个一个算,这就导致了前面的人的属性被重复的计算了,造成了性能上的浪费,最终我再一次改进,改成每次在addtogroup的时候就更新几个统计值,包括年龄和、年龄的平方的和等,再由这些数比较方便的求出想要求出的数值,如方差,性能得到了进一步的提升。

3.第三次作业

第三次作业仍然没有进互测...主要有三个bug,第一个是关于blocksum的,主要原因是把更新是的判断条件islcircle写成了islinked,我也不知道当时为啥要这么写,大概脑子短路了吧...然后就是再qsl中我有一个辅助debug的print没注释掉,似乎中测也没测到这个方法,然后强测就悲剧了;还有就是之前说过的qmp,我选择在addrelation的时候更新每个人到每个人的最短距离,看似qmp的时候很美好,一下就出来了,但是在addrelation的时候太慢了啊,而且addrelation执行得到频率可比qmp要多,以强测中的一条数据为例,3000条指令只有addperson和addrelation,我居然跑了七十多秒。。。最后还是老老实实改了个堆优化的Dijkstra。

五、心得体会

短短三次作业,我两次没进互测,内心是崩溃的......

崩溃完了,还得找找原因,照着JML规格写代码一定要仔细,认真理解其全部含义。规格最基本的一个特点就是准确,无二义,那么在阅读这样精确的语言时,一个疏忽就可能酿成大错,造成严重后果,比如没进互测...另外就是,在写那些考验性能的算法时,如果想采用什么奇技淫巧,一定要先算好复杂度,看看是不是真的很简单,就算采用了一些经典的算法,也可以自己试着写一写复杂度,也可以加深对代码的理解,总之不会有坏处。

这个单元主要学习了JML语言规格,做作业的时候感觉也和前两次不大一样,前两次其实没有怎么考验性能(性能分占比不高),以正确性优先。而这个单元里,尤其时前两次作业,做到正确并不难,但是性能却成为了影响分数的重要因素。这也提醒了我们,规格只是规格,和实现没有关系,在具体实现上不能一点脑子不动,若是看规格给了个数组,那就也声明一个数组,正确性固然是问题不大了,但是和别人真正动了脑子想架构的相比,性能就差了十万八千里了。

马上进入OO最后一个单元了,这个学期也马上进入尾声了,希望自己在接下来的时间要更加努力,也给自己交一个满意的答卷。

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!