前言


 

  关于规格设计这一单元的作业结束了,纵观这一单元的作业,和之前的作业感觉明显不同,一方面是这是一次有参考的编码经历,不需要自己去理解指导书、或者设计计算流程,只需要严格按照规格实现每一个方法即可享受到程序跑起来的快感(在这里吹一波官方提供的接口,真好用)。当然也有一些遗憾,就是相对来说最容易全部拿到满分的一单元作业,竟然卡出了OO课程中第一个爆掉的强测点,实在是有些不应该。还是有些大意和过于高估自己对于规格的实现能力,所以下面就对这一单元出现的一些问题做一次总结。


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


 JML语言的理论基础


  根据JML官网的说法:

  Java建模语言(JML)是一种行为接口规范语言,可用于指定Java模块的行为 它结合了Eiffel的契约方法设计 Larch 系列接口规范语言的基于模型的规范方法 ,以及 细化演算的一些元素

  结合这三周的编程经验,我对JML的理解是:

  通过规定:

  前置条件:描述输入参数必须满足的限制,如果不满足,将导致异常行为。

  后置条件:描述输入参数满足前置条件的前提下,方法执行得到的“正确”结果。

  副作用范围限定:描述在方法执行过程中可以被修改的域(实例域和静态域)。

  不变式:表示变量在可见状态下的取值空间。

  状态变化约束:表示变量在方法执行前后所能够变化的程度。

  使得对方法的描述完全避免了自然语言的二义性,从而使得对方法的实现和验证都能更加高效的进行。


jml应用工具链


 

  OpenJML :可以进行规格的静态检查

  JMLUnit   :自动生成测试样例测试规格的正确性,用于进行单元化测试。 


部署JMLUnitNG


   按照系里大佬的教程,我也部署了一个JMLUnitNG文件进行测试,以下是测试的方法和结果:

  测试的方法如下:

 


   测试结果如下:

  

  可以看到检查除了除0的错误。


架构设计梳理


 第一次作业


 设计思路


  这一次的作业主要的任务是设计一个path类和pathcontainer类。

  由于path类需要体现的性质有一条path的节点顺序与所含的不重复节点数,所以在path类中:

    private ArrayList<Integer> nodes = new ArrayList<Integer>();
    private HashMap<Integer,Integer> hashnodes = new HashMap<Integer,Integer>();

  这样做到了查询结点、节点数目的复杂度都为O(1)。

  在pathcontianer中,主要的行为是对path进行查询,对path进行删减,考虑到对复杂度的要求,我设计的成员变量有:

    private HashMap<Integer,Path> hashpList = new HashMap<Integer,Path>();
    private HashMap<Path,Integer> hashpidList = new HashMap<Path, Integer>();
    private HashMap<Integer,Integer> hashnodes = new HashMap<Integer,Integer>();

  这样做到了从id查path,从path查id,查询总结点数等方法的复杂度都为O(1)。

  并且把对上述变量的更新都放到了add、与remove方法中,将计算分散到了数目较少的线性指令中,用空间换取了时间。


 基于度量来分析自己的程序结构


程序结构


  程序结构没有太多值得说的地方,就是按照规格要求完成了所有方法。


 度量分析


  可以看出这个规格设计的是很好的,各项复杂度的数据都非常好看。equals和compareTo的基本复杂度稍微高一些,主要是由于这两个方法内需要调用其他方法访问path的各种变量。


 第二次作业


 设计思路


   这次作业增加的需求是,能够做到对边的查询、查询两点间最短距离。针对这两个要求,我采取的方法是,完全继承上次的代码,增加两个成员变量:

    //EDGES     start_node  neighbour_node  frequency
    private HashMap<Integer,HashMap<Integer,Integer>> hashedges = new HashMap<Integer,HashMap<Integer,Integer>>();
    //NODE TO INDEX
    private HashMap<Integer,Integer> nodetoindex = new HashMap<Integer,Integer>();
    //SHORTEST DISTANCE MATRIX
    private int[][] distancematrix = new int [260][260];、  

  这样的话就能对edge也做到O(1)的查询,同时将整个图的点投射到一个距离矩阵中,通过Floyd算法,计算两点间最短距离。同样,计算更新过程都在add、remove中完成。其余所有方法都为O(1)。


基于度量来分析自己的程序结构


程序结构


 

  这次的程序结构和设计思路一样,也没有太多可说的。


 度量分析


  这一次可以看出floyd方法的基本复杂度和模块复杂度都比较高,这主要是由于没有把floyd方法中的矩阵初始化、读边等过程独立封装成方法,这一点在第三次作业中做了改善。


 第三次作业


 设计思路


  这一次作业的增加的需求是计算不满意度、最少换乘次数、最少票价,这三者其实也是一种“最短距离”,只是需要在同一条path中建立完全图,并对各边赋予合适的权值,就能将“换乘”体现出来。

  所以在代码方面,也是完全继承了上一次的代码,只是增加了三个新的“距离”矩阵,并把floyd方法的各个部分做了一个封装。


 基于度量来分析自己的程序结构


 程序结构


 

  类的设计如设计思想所说。


 度量分析


  可以看出在把folyd做了一个拆分之后,各方法的复杂度都比较好看。


 分析bug和修复情况


   三次作业中除了第一次作业外都存在一些bug:

  第二次作业的bug主要是没有考虑到floyd中读取边是自环对距离矩阵对角线的错误覆盖,这属于逻辑错误,但改起来十分简单,增加一条判断新加入的边的关系是否低于原有边关系的判断即可解决问题。

  第三次作业的bug主要是没有考虑去remove边时,自环的情况,从而出现的空指针异常,改起来也十分简单,直接添加一条判断是由还具有这个key的条件即可。

  综上两次bug都栽在了自环上,由此可看见对图的掌握理解还是差一些火候。


对规格撰写和理解上的心得体会


   在这一单元结束后,我对JML最大的体会是:

  那这JML写代码是一件很方便的事情,但是如果是自己针对一个问题,设计相应的JML却是一件很困难的事情。可能还是因为能力不够,没有办法在拿到一个问题后快速对其进行拆分,在设计方法的时候也很难综合考虑所有边界情况。或许在第四个单元的学习之后能够有一定好转。