OO第三单元总结



JML

JML(Java Modeling Language)是用于对Java程序规格化设计的一种表示语言,可以使得在编写程序之前,就预先规定每个函数的功能。一些基本用法如下:

  • 注释:每一行都以 @ 开头;
  • 纯查询方法:以 /*@ pure @ */ 注释,该方法执行不带有任何副作用;
  • 条件和限定:requires规定前置条件,assignable规定副作用范围,ensures规定后置条件,signals规定异常功能规格中的后置条件;
  • 原子/量化表达式:\result定义非void方法的返回值,\old定义某个表达式在方法执行前的取值,\forall为全称量词,\exists为存在量词,\sum为求和,\max和\min分表表示最大值和最小值;
  • 操作符:<==>为等价,==>为推理,\nothing指定一个空集,\everything指定一个全集;

以上总结的用法为本次实验的常用语句,还有一些其他的语句也是属于JML level0的,比如对于数据类型定义的类型规格 invariantconstraints,分别规定可见状态下要满足的以及状态变化时的约束。

工具链的话主要就是openJML吧。


自动化测试

Junit我也在自己的环境中部署了,不过没有针对Junit编写自动化生成测试用例的代码,只是列举了几个自己觉得容易错的数据,用Junit进行测试。我的自动化测试是用Python写的数据生成器,然后和同学抱团取暖,通过对拍的方式来查找bug。

因为我觉得,Junit虽然是一种方便于测试单个函数的工具,但是在生成测试用例之后,我们还是要预先规定正确结果,这样才能进行测试,而对拍的话就不用在意具体是怎么输出的,而且也可以达到定位出错语句的效果。


架构设计

第一次作业

第一次作业规定了Person和Network两个类,要模拟一个社交网络,每个人有自己的id、名字、年龄、value和矛盾值,社交网络中有若干个人,还有若干条人与人之间的关系。因为社交网络的模型和图模型非常相似,所以我从第一次作业开始就抽象地实现了一个图类,这个图可以加结点、加边以及实现一些和图有关的算法,这样我就可以把和图相关的算法实现与Network分离,这样不仅有利于代码编写,也使得结构更加清晰,耦合程度更低。

本次作业中指令条数不超过1000,所以query_circle我就直接用BFS实现(并查集会更好),其它的主要就是翻译JML,总体来说没什么思考难度。

第二次作业

第二次作业在第一次作业的基础上,新增加了Group类,这个类可以看成是Network中的一个子集,形象的可以理解为社交网络中的一些群组(学校中的班级等等)。Network在原来的基础上还要维护所有Group的相关信息,而在Group类中需要维护这个组中所有人的人数、关系和、value和、矛盾值异或和、平均年龄和年龄方差。

本次作业对性能要求比较高,因为指令条数达到了100000条之多。如果group类中需要维护的那些数据每次都遍历求解的话,那肯定会超时,所以得想办法在每次增加人和增加关系的时候都动态更新这些值,然后查询的时候就可以在 O(1) 的时间内返回结果,这个经过思考应该可以很快知道怎么做。另外由于某些指令的数目有限制,所以这次作业和上次作业的差别主要就是Group类需要维护的那些信息,其它的倒没有多大变化。

第三次作业

第三次作业在第二次作业的基础上,类型没有变化,主要变化的是Network类中增加了许多图算法的操作,因为我是独立实现Graph类的,所以其实主要的代码增添在Graph类中完成就行了。新增添的几个操作分别是:求value最短路、判断是否强联通(至少存在两条不相交路径,也就是点双连通)。

最短路算法我用的是带堆优化的Dijkstra算法(实际上我是直接翻译的C++代码),我的一个同学用的SPFA,也就是队列优化的Bellman-Ford算法,SPFA的复杂度没有Dijkstra好,极端情况下可以被卡(但是这次好像数据量还卡不了),而且SPFA可以处理负边权的情况,甚至可以判断是否存在负环。但是本次作业的value都是非负数,所以Dijkstra处理就非常完美了。

而强联通的判断,我用的是Tarjan算法。

我还是没有用并查集维护两个结点是否isCircle,至始至终都是用的BFS,也就是O(n) 的算法,因为我觉得三次作业的指令限制都没办法卡这一点,所以就没写并查集了。

第三次作业的类结构关系如下:

其中 Package structure 里面包括了 Graph类。


Bug

第一次作业因为isCircle忘记判断相等情况了,所以wa了几个点;第二次作业和第三次作业都是满分。其实我个人觉得我的自动化测试覆盖强度还是挺高的,而且我也自己根据容易错的点构造了特殊数据,比如卡最短路的数据、卡强联通的数据、卡求Group年龄方差的数据等等,所以都能及时发现存在的bug。


心得体会

我觉得JML是一种神奇的东西:一方面它严格地规定了方法的满足条件,使得人们可以在编写代码之前就确定每个函数的工作,方便团队合作;另一方面有时候JML的编写又显得有点儿“冗杂”,因为有些方法本来实现就只有几行,但是JML为了严谨,可能会写出多于代码几倍长度的JML语句出来,而且JML本身也不太容易读懂,而且容易出错。所以JML的撰写还是得特别小心,理解也应该仔细推敲。

posted @ 2020-05-20 21:00  dragonylee  阅读(120)  评论(0编辑  收藏  举报