OO第三次博客作业

OO第三次博客作业

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

1、JML语言的理论基础

JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。

1、注释结构

JML以javadoc注释的方式来表示规格,每行都以@起头,一般放在被注释成分的近邻上部。有行注释和块注释两种注释方式。

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

块注释的表示方式为/*annotation*/。

2、JML表达式(常用)

(1)原子表达式

\result

\old(expr)

(2)量化表达式

\forall

\exists

\sum

\max

\min

(3)操作符

等价关系:<==>等价  <=!=>不等价

推理操作:==>

变量引用:\noting 空集  \everything 全集

3、方法规格

前置条件:requires

后置条件:ensures

副作用范围限定:assignale

signals子句:signals (***Exception e) b_expr

2、应用工具链

1、OpenJML

OpenJML是对JML进行JML语法检查的工具,OpenJML可以通过命令行暴力使用,也可以通过插件与IDEA/Eclipse配套使用。以下分别为使用git bash直接运行OpenJML和使用IDEA插件对同一个Demo.java中的JML语法进行检查的截图。

其中Demo1.java源码如下:

package test;
// Demo1.java
public class Demo1 {
    /*@
      @ public normal_behaviour
      @ requires lhs<0 && (\result=lhs-rhs;
    */
    public static int compare(int lhs, int rhs) {
        return lhs - rhs;
    }
}

运行git bash结果如下:

在IDEA中使用插件结果如下:

2、JMLUnitNG

 JMLUnitNG是一个用于JML注释的Java代码的自动化单元测试生成工具,具体的使用见下面的自动生成测试样例部分。

 二、部署JMLUnitNG实现自动生成测试用例

 此部分我使用自己编写的一个简单的测试测试样例来体验一下JMLUnitNG的使用。测试使用的代码如下:

package arraytest;

import java.util.ArrayList;

public class Arraytest {
    public ArrayList<Integer> list = new ArrayList<Integer>();

    // @public normal_behavior
    // @ensures \result==list.size();
    public /*@pure@*/ int size() {
        return list.size();
    }

    /*@ requires index >= 0 && index < size();
      @ assignable \nothing;
      @ ensures \result == list.get(index);
      @*/
    public /*@pure@*/ int getnum(int index) {
        return list.get(index);
    }

    //@ ensures \result == (\exists int i; 0 <= i && i < size(); list.get(i) == num);
    public /*@pure@*/ boolean containsnum(int num) {
        return list.contains(num);
    }
}

首先使用如下命令生成测试使用文件:

./jmlunitng arraytest/Arraytest.java

然后在该文件所在的包内就会出现很多原本不存在的代码:

接下来是编译部分,依次按照以下的顺序将文件编译完成:

javac -cp jmlunitng.jar arraytest/**/*.java
javac -cp jmlunitng.jar arraytest/*.java
./openjml -rac arraytest/Arraytest.java

然后在arraytest文件夹内就会对每个.java文件都产生一个.class文件与之对应,然后就是运行这个测试,对应命令如下:

java -cp jmlunitng.jar arraytest/Arraytest_JML_Test

测试结果如下:

由测试样例可以看出,自动测试样例生成都是产生的一些极端情况,比如0,Integer的最大值或者最小值。

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

1、第一次作业架构设计

第一次作业没有什么架构设计,就是按照给定的规格来写,给定的规格中有哪些方法就按照JML注释来写就行了,但要注意的是class中数据结构的使用,如果单纯用ArrayList来做,那么就会超时,因为ArrayList查询是否存在、不同节点等情况是很慢的。以下是我第一次作业的类图:

以下是我第一次作业的代码统计和方法复杂度统计:

2、第二次作业架构设计

第二次作业相比第一次就又多了几个方法,这时候就能看出来一点重构的影子:因为Path类是完全独立封装的,所以如果之前的MyPath类写的没有问题的话,就完全可以复制之前的代码,不用做改动,因为调用接口都已经实现好了,具体实现方法与调用没有太大关系。而到了MyGraph类,由于之前实现了很多基本的方法,而且Graph类就是继承的PathContainer类,所以存储数据使用的数据结构基本不用做很大改变,只要对新加入的几个方法按照JML规格注释来填写就可以了。因为Graph类是继承的之前的PathContainer类,所以其实当初写这个的时候完全可以把MyGraph类继承之前的MyPathContainer类,这样代码就可以复用了,而不是一味的复制粘贴。以下是我第二次作业的类图:

 以下是我第二次作业的代码统计和方法复杂度统计:

3、第三次作业架构设计

第三次作业是在第二次的作业的基础上增加了更加复杂的方法,将图的内容具体化了,将抽象的图具体化为地铁图,然后增加了关于加权最短路径的计算。依然,MyPath类可以完全照搬之前的代码,而RailwaySystem类则是继承之前的Graph类,但是这次不仅有上次的最短路径的计算,还有加权最短路径的计算,所以可以直接继承之前的PathContainer类,而我则是把之前的复制了。对于图的加权,我另开了一个类,专门存储带有不同权值的图结构,但其实图结构都是一样的,只是每条边的权值不同,所以我的设计从一定程度上来说是有点多余的。以下是我第三次作业的类图:

以下是我第三次作业的代码统计和复杂度统计:

 

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

1、第一次作业代码bug及修复

第一次作业的bug有两个,就是这两个导致我没能进入互测。首先是一个非常智障的bug--在addPath方法中我的返回值不是pathId,我不知道为什么嘛返回来在ArrayList中的index,我也不知道当时自己怎么想的。还有一个就是我的程序超时了,这是因为在我自己的电脑上测试的时候我的程序跑得蛮快的,然后我就以为时间上没有什么问题,但是评测机的实际时间跟我电脑上的差距其实还是蛮大的,而且跑强测数据的时候的确用ArrayList很慢,这就是我数据结构选的不好的结果了。

bug修复中,我先把那个智障的bug修复以后,然后再换了一种数据结构--hashmap,它在查询的时候非常的快,然后对我的程序作出相应的修改以后,TLE问题也就解决了。

2、第二次作业代码bug及修复

第二次作业的bug说起来我非常的后悔,我在MyPath类最后的迭代器中使用了hashmap的迭代器,这就让我非常难受。我是知道hashmap是无序的,但是我当时可能是不知道这个迭代器是用来干嘛的,以为只是普通的遍历,然后就把hashmap的迭代器写上去了,然后我也没有细看使用这个迭代器是在什么情况下,然后测试的时候就在测试其他我认为有可能出错的了,关于那个getPathbyId我就没怎么管,结果就翻车了T_T。

bug修复就三行,第一行在MyPath类中加一个ArrayList数据成员,第二行在MyPath类构造方法的时候把数据顺序存放在ArrayList中,第三行就是把迭代器改成ArrayList的迭代器。就因为这简单的三行,我的互测一塌糊涂,惨不忍睹。然后修复的时候,整个程序交上去,很快就运行出结果了,强测一个都没有错,如果我当时多看一眼就不会出现这种智障的错误了,好后悔啊。

3、第三次作业代码bug及修复

第三次作业说实话我当时拿到就非常的懵,我数据结构中图学的不好,然后这次弄了一个这么复杂的图,让我感觉无从下手。然后看了讨论区里同学的帖子,看到葛毅飞同学的想法非常有道理,然后感觉那样的话就只需要多出一个图的类,然后不同的权值对应该类的不同对象,然后在类的内部进行加权的最短路径计算就可以了。然而想法很美好,现实很残酷,我的代码问题出在了图的遍历上,我一开始使用的是迪杰斯特拉算法,但是后来越写越复杂,搞到最后我都写不下去了,然后我就临时又换了弗洛伊德算法,然后就一直在调,一直找不到问题在哪里。到截止时间的时候我还是没有找到问题,我自己测试都过不去,然后我就没能交上去,脑子一团乱。

那个bug我到现在都没修复,我当时debug的时候就发现,这个程序运行的时候就很奇怪,有的时候运行到那个类的时候图是空的,有的时候又不是空的,然后整个运行的时候又报异常。我觉得可能是我的结构不合理,加上方法写的有问题,所以代码整体看起来非常的混乱,我正在看标程,我觉得我在看完标程以后要重新写一次这个作业,虽然可能挽回不了什么,但是我还是希望我能学会一点东西,而不是直接放弃。

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

 对于规格的理解,我觉得首先得知道JML规格中的一些关键词是的含义,比如\max的整个表达式,三部分分别表示的是什么意思,这就跟学C语言一样,for循环的三部分每一部分都是干嘛的要知道,其次就是把这些用到规格的理解上,读了作业中的规格,我觉得这个很类似与当初学习离散数学的时候那种感觉,要将符号语言翻译成自己能理解的自然语言,一点点的梳理出来。为什么规格不用自然语言描述呢?因为自然语言有二义性,而如果用符号语言定义下来就不会出现这个情况,对于代码的编写十分有利。这个规格就是一种需求,按照需求来写代码,也算是提前的一种锻炼吧。

对于规格的撰写,其实我觉得这个就是离散数学的那种思维,将自然语言没有遗漏、无二义性地表达出来,翻译成符号语言,这个有利于加深自己对于符号语言的理解,也有助于锻炼自己的逻辑思维能力吧我觉得,能锻炼自己严谨抽象的思维能力。

posted @ 2019-05-22 16:53  17373183  阅读(143)  评论(0编辑  收藏  举报