面向对象编程第三次博客作业——JML与架构总结

面向对象编程第三次博客作业——JML与架构总结

一、JML理论知识梳理

描述方法预期:将注释添加到java代码中,确定方法所执行的内容,不必说明所执行的过程

JML注释始终位于comment内部,不会对正常编译的代码产生影响

开放源码 JML 编译器:捕捉代码中的错误,保持JML注释与代码保持同步

JML的两种主要的用法:

(1)开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语言描述,而是逻辑严格的规格。 (2)针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。

两种规格变量:静态,实例

在Interface中声明规格变量,必须明确变量的类别

举个栗子:

静态:

//@public static model non_null int []elements

 

实例:

//@public instance model non_null int []elements

 

1.注释结构

两种注释方式:

1.行注释

//@annotation

2.块注释

/* @ annotation @ */

JML注释语句放在被注释成份的邻近上部

2.JML表达式

对于Java表达式的扩展

2.1原子表达式

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

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

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

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

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

\type(type) 表达式:返回类型type对应的类型(Class)

\typeof(expr) 表达式:该表达式返回表达式expr对应的准确类型。

2.2 量化表达式

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

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

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

\product表达式:返回给定范围内的表达式的连乘结果

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

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

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

2.3 集合表达式

集合构造表达式:可以在JML规格中构造一个局部的集合(容器),明确集合中可以包含的元素。

集合构造表达式的一般形式为:

new ST {T x|R(x)&&P(x)}
​
/*R(x)对应集合中x的范围,通常是来自于某个既有集合中的元素,如s.has(x),P(x)对应x取值的约束。*/

2.4 操作符

JML表达式中可以正常使用Java语言所定义的操作符,包括算术操作符、逻辑预算操作符等。

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

(2) 等价关系操作符

 b_expr1<==>b_expr2 // b_expr1 == b_expr2 
 b_expr1<=!=>b_expr2 // b_expr1 != b_expr2

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

b_expr1==>b_expr2 
b_expr2<==b_expr1

(4) 变量引用操作符:变量引用操作符经常在assignable句子中使用

\nothing指示一个空集

\everything指示一个全集,即包括当前作用域下能够访问到的所有变量。

3、方法规格

(1)前置条件 reqquires

(2)后置条件 ensures

(3)副作用范围限定 assignable modifiable

(4)signals子句

4、类规格

(1)状态变化约束 constraint

(2)不变式 invariant

5、应用工具链

OpenJml工具

OpenJML最基本的功能就是对JML注释的完整性进行检查。检查包括经典的类型检查、变量可见性与可写性等。通过命令行使用OpenJML时,可以通过-check参数(缺省)指定类型检查。

以一个非常简单的代码举例:

public class main {
    /*@
      @ require args != null
      @*/
    public static void main(String[] args) {
        System.out.print(args);
    }
}

运行报错信息:

C:\>openjml main.java
main.java:17: 错误: 需要';'
          @ require args != null
                        ^
main.java:17: 错误: 找不到符号
          @ require args != null
            ^
  符号:   类 require
  位置: 类 main
2 个错误

(我仅仅根据教程进行了简单的尝试,具体使用还需要更多的理解和研究)

 

二、jmlUnitNg

根据讨论区大佬的方法,进行了尝试,结果如下:

 

 

三、架构设计梳理

第一次作业

主要使用的数据结构:Hashmap

这次作业主要考察的点在于如何根据规格写出正确的代码,需要优化的部分主要集中于查找上。为了提高查找的便捷性和速度,采取了Hashmap来进行数据的储存。将复杂度集中于对于Path List的更新部分,这样在对路径进行查找操作时就十分快捷。

第二次作业

这次作业在第一次作业的基础上构建了图,需要查找的部分也比之前复杂了不少。

关于最短路径的查询,我采用了弗洛伊德算法,在每次对图进行更新的时候同时更新弗洛伊德矩阵,相比而言这是一个简单有效的算法。

主要使用的数据结构还是Hashmap

第三次作业

这次作业在前两次作业的基础上要实现一个地铁系统,需要增加的有最短路径、最低票价、最少换乘、最低不满意度这四种查询,这四种其实都可以使用弗洛伊德算法实现,相对于迪杰斯特拉算法虽然失去了一定的时间效率,但是简单容易完成。

在这次作业前,我首先对第二次作业进行了优化和修改,将佛洛依德算法进行了优化和改变,不再直接对Hashmap进行迭代遍历,因为这样的速度非常慢。采用了首先对节点进行线性映射,然后构造二维数组,这样提高了在遍历时的效率,毕竟弗洛伊德算法的时间复杂度高达O(n^3)

四、bug及修复

第一次作业:

这次的bug非常的弱智,在对路径进行偏序比较的时候少写了两个break,这种错误在学习C语言的时候就不应该再出现了,没想到现在还会有orz

第二次作业:

Hashmap直接进行三重的循环遍历,没有考虑这个数据结构在迭代时对于时间的高额花费,从而导致了强测的CPU时间崩盘

第三次作业:

对于三种查询算法理解不够,导致正确性很低。

 

五、对规格撰写的理解和体会

规格化设计的思想可以避免很多bug的出现,这种设计模式在最开始就设计好了每个方法、每个对象的执行逻辑,理论上来说,只要规格设计没有问题,严格按照规格来编写的代码就是正确的;同时,规格化设计对于代码的全面测试等都是非常方便的。

这个单元的作业,从最基础的两个类开始,最初只有基本的方法,一层层迭代封装,继承原有的代码构建新的功能模块,体会到了真正的工程是如何增量设计的。

posted on 2019-05-21 23:47  Ftee  阅读(180)  评论(0编辑  收藏  举报

导航