代码改变世界

OO第三单元作业总结

2019-05-22 18:03  木杉月  阅读(194)  评论(0编辑  收藏  举报

1.梳理JML语言的理论基础、应用工具链情况

这一单元我们学习了JML语言,我们通过老师和助教所给的JML规格,然后再完成相应的具体实现代码。JML是一种行为接口规格语言。有两种用途。

1)开展规格化设计。这种就如同我们这几次作业一样,老师和助教写出来JML规格,我们通过JML规格来实现代码。

2)针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。而这个我们在课上上机的时候也做了相应的练习。

JML语言中首先分为两种,一种是方法的正常功能public normal_behavior,另一种是方法的异常功能public exceptional_behavior。而在方法的正常功能中又有requires代表方法的前提条件,副作用范围限定assignable列出这个方法能够修改的类成员属性和ensures子句定义了后置条件。

在方法的异常功能中,我们也有前置条件、后置条件和副作用声明。不同的是,异常功能规格中,后置条件常常表示为抛出异常,使用signals子句来表示。

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

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

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

\not_modified(x,y,...)表达式:与上面的\not_assigned表达式类似,该表达式限制括号中的变量在方法执行期间的取 值未发生变化。

\nonnullelements( container )表达式:表示 container 对象中存储的对象不会有 null 。

操作符:

子类型关系操作符: E1<:E2 ,如果类型E1是类型E2的子类型(sub type),则该表达式的结果为真。

等价关系操作符:b_expr1<==>b_expr2 或者 b_expr1<=!=>b_expr2 ,其中b_expr1和b_expr2都是布尔表达式。

推理操作符:b_expr1==>b_expr2 或者 b_expr2<==b_expr1 。对于表达式 b_expr1==>b_expr2 而言,当 b_expr1==false ,或者 b_expr1==true 且 b_expr2==true 时,整个表达式的值为 true 。

变量引用操作符:除了可以直接引用Java代码或者JML规格中定义的变量外,JML还提供了几个概括性的关键词来 引用相关的变量。\nothing指示一个空集;\everything指示一个全集,即包括当前作用域下能够访问到的所有变 量。

 

而JML的应用工具链有OpenJML,JUnit,JUnitNG等等。

OpenJML用来对JML的语法进行检查,而JUnit,JUnitNG则是通过生成一些样例对程序进行检查。

2.部署SMT Solver,至少选择3个主要方法来尝试进行验证,报告结果

3.部署JMLUnitNG/JMLUnit,针对Graph接口的实现自动生成测试用例

首先,我先对自己写的一个样例来测试。

再在cmd中输入

最后得到结果

 

在最开始我并没有用openjml来对测试文件进行编译,会发现第一个racEnalbed()不能通过。后面看到讨论区,才发现这个是检测主文件是否带有JML的运行检查,若没有则跳过所以测试。如果不用openJML来编译,自然也就没有JML的运行检查。所以不能通过。

4.按照作业梳理自己的架构设计,并特别分析迭代中对架构的重构

1)第一次作业

第一次作业中我们完成了Path类和PathContainer类,在这次作业实现的时候,最开始由于自己没有考虑时间复制度的问题,用的是Arraylist容器,在后面听到群里有人说Arraylist容器在remove和查询时时间复制度会爆,所以我就采取了Hashmap容器。

所以在这次PathContainer类中我用了两个Hashmap容器,一个用来存不同结点出现的次数,一个用来存id所对应的Path。本来打算还用一个容器来存Path所对应id,后来发现对于两个变量应该相等的Path,Hashmap会判断其不同,因为HashmaP对于对象判断,不是判断其内容,而是调用其.toString方法。所以在这次作业中我的remove和通过id查Path是O(1),但是通过path查id却要遍历。通过上网查询发现这种情况是可以改变的,实现方法就是通过重写Hashmap的equit和hashcode方法。而对于这两个容器的修改我放在add,和remove中。

2)第二次作业

第二次作业中我们完成了Path类和Graph类。由于Path类并没有进行改动,所以我直接就是用的上次的Path类。在这次的Graph类中我们增加了containsNode,containsEdge,isConnected,getShortestPathLength四个方法,而其他的并没有变换。

相对于上次的架构变换,在这次作业中我发现上次的所写的容器和方法,都是可以完美用于这次作业的。所以这次作业我直接继承了上次的类,再添加了一个hashMap容器node2math用来将结点id转化为下标,再用三个静态数组math2node(用来存下标转id),edgeMap(用来存两个结点之间是否相连),endMap(两个结点的最短距离的情况)。

我在这次作业中采取的是Flyod方法,通过在每次add和remove中对edgeMap的修改得知所有Path的结点相连情况,再当需要得知两个结点的最短距离时,使用Fylod方法以edgeMap得到endMap。这样这次的作业就完成了。

3)第三次作业

第三次作业中我们完成了Path类和RailwaySystem类,这一次相较于上一次多了getLeastTicketPrice,getLeastTransferCount,getUnpleasantValue,getLeastUnpleasantValue,getConnectedBlockCount5个类。这次最大的变换就是加了换乘这个概念。其实前四个类都可以用同一种实现方法实现。只是图的权重变换了而已。

所以这次相对于上次的架构变换在于我加了,边的出现次数。并在每次add的时候,算出了Path的连通图,再根据Path的连通图算出整个地图的联通图。而其他的并没有太大变换。主要的重点就在于小图建大图。

5.按照作业分析代码实现的bug和修复情况

在这次作业中除了第二次外其他两次在强测中没有出现问题,而第二次出现问题的情况是因为直接在判断是否包含该边时没有判断是否包含两个结点,导致会出现异常。第三次中强测虽然没有出现问题,但是互测中却被别人刀了一次,仔细分析代码发现是因为自己的加法溢出而导致的。而自己将代码中的最大值从int的最大值改为10000,就成功修复了bug。

6.阐述对规格撰写和理解上的心得体会

我们曾在两次上机时有过对规格撰写的体会,在这两次实验中,表面上很简单,不一会就通过代码写出来JML,但是后面仔细思考,发现了很多漏洞,对每个方面并没有完全的考虑,有遗漏的地方。但是当把规格写出来后,发现对代码有了更深的体会。而不是像以前那样只是扫一眼代码,体会到了一段代码各个方面。而且规格是不会出现以前那样,可以把具体的功能要求传达,特别是这三次作业中,我们通过规格写代码,可以说没有二义性,也更容易理解。

但是严谨,可以说是JML的优点,也可以是缺点。正因为这个,JML语言在某些方面就比较繁杂,而且撰写规格时容易出错或遗漏,在三次作业中,老师和助教也曾出现过对规格撰写的错误。但总的来说,这个不失为一个好的工程方法。