OO第三单元作业总结——JML下的图查找

 

一、JML语言的理论基础梳理

  JML的语言基础主要包括JML表达式、方法规格以及类规格JML以javadoc注释的方式来表示规格,每行都以@起头。有两种注释方式,行注释和块注释。其中行注释的表示方式为//@annotation ,块注释的方式为/* @ annotation @*/ 。按照Javadoc习惯,JML注释一般放在被注释成分的近邻上部。

1.JML表达式

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

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

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

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

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

\type(type)表达式:返回类型type对应的类型(Class),如type( boolean )为Boolean.TYPE。TYPE是JML采用的缩略表示,等同于Java中的java.lang.Class 。

\typeof(expr)表达式:该表达式返回expr对应的准确类型。如\typeof( false )为Boolean.TYPE。

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

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

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

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

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

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

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

2.操作符

(1) 子类型关系操作符: E1<:E2 ,如果类型E1是类型E2的子类型(sub type),则该表达式的结果为真,否则为假。如果E1和E2是相同的类型,该表达式的结果也为真,如Integer.TYPE<:Integer.TYPE 为真;但Integer.TYPE<:ArrayList.TYPE 为假。需要指出的是,任意一个类X,都必然满X.TYPE<:Object.TYPE。

(2) 等价关系操作符: b_expr1<==>b_expr2 或者b_expr1<=!=>b_expr2 ,其中b_expr1和b_expr2都是布尔表达式,这两个表达式的意思b_expr1==b_expr2 或者b_expr1!=b_expr2 。可以看出,这两个操作符和Java中的==和!= 具有相同的效果,按照JML语言定义, <==> 比== 的优先级要低,同样<=!=> 比!= 的优先级低。

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

(4) 变量引用操作符:除了可以直接引用Java代码或者JML规格中定义的变量外,JML还提供了几个概括性的关键词来引用相关的变量。\nothing指示一个空集;\everything指示一个全集,即包括当前作用域下能够访问到的所有变量。变量引用操作符经常在assignable句子中使用,如assignable \nothing 表示当前作用域下每个变量都不可以在方法执行过程中被赋值。

3.方法规格

(1)前置条件(pre-condition)通过requires子句来表示: requires P; 。其中requires是JML关键词,表达的意思是“要求调用者确保P为真”。注意,方法规格中可以有多个requires子句,是并列关系,即调用者必须同时满足所有的并列子句要求。如果设计者想要表达或的逻辑,则应该使用一个requires子句,在其中的谓词P中使用逻辑或操作符来表示相应的约束场景: requires P1||P2; 。

(2)后置条件(post-condition)通过ensures子句来表示: ensures P; 。其中ensures是JML关键词,表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”。同样,方法规格中可以有多个ensures子句,是并列关系,即方法实现者必须同时满足有所并列ensures子句的要求。如果设计者想要表达或的逻辑,这应该在在一个ensures子句中使用逻辑或( || )操作符来表示相应的约束场景: ensures P1||P2; 。

(3)副作用范围限定(side-effects)指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。从方法规格的角度,必须要明确给出副作用范围。JML提供了副作用约束子句,使用关键词assignable 或者modifiable。

(4)signals子句的结构为signals (***Exception e) b_expr ,意思是当b_expr 为true 时,方法会抛出括号中给出的相应异常e。需要注意的是,所抛出的既可以是Java预先定义的异常类型,也可以是用户自定义的异常类型。此外,还有一个注意事项,如果一个方法在运行时会抛出异常,一定要在方法声明中明确指出(使用Java的throws 表达式),且必须确保signals子句中给出的异常类型一定等同于方法声明中给出的异常类型,或者是后者的子类型。

还有一个简化的signals子句,即signals_only子句,后面跟着一个异常类型。signals子句强调在对象状态满足某个条件时会抛出符合相应类型的异常;而signals_only则不强调对象状态条件,强调满足前置条件时抛出相应的异常。有时候,为了更加明确的区分异常,会针对输入参数的取值范围抛出不同的异常,从而提醒调用者进行不同的处理。这时可以使用多个exceptional_behavior。

二、应用工具链情况

配置环境后输入指令如上图,可见测试通过

三、作业架构设计

各次作业类图

  可见3次作业架构基本相同,通过在类中增加方法来完成设计,但这样的设计在方便的同时存在缺点,采取每次作业继承上一次作业类的架构比较合理。

  第一次作业十分简单,内容也是十分的清晰。构建了两个类一个是Path用来储存节点序列,另一个是一个用来储存Path的容器,对于每一个容器实现了对于Path的增删改查等一系列方法。为了能够降低时间复杂度,采取了Hashmap储存结构,使用hash查找的方式来减少查询时间。

  第二次相比第一次作业本次作业加入了对于图结构的一些判断,除了继承上一次Pathcontainer中的方法,还新增加了判断两个节点之间是否有边是否连接以及最短路径。我采取了BFS的方法来计算俩点间的最短路径,每次查找点A到点B的距离时,先判断点A 是否已经计算过,如果没有则通过BFS计算到各个点的距离,并将其保存,来达到降低时间复杂度的目的。

  第三次作业相比于前俩次作业难度加大了很多,增加了连通块、最小换乘次数、最小票价、最小满意度指令。连通块的计算较为简单,剩下指令在添加路径时建立路径内各个点之间完全图的邻接矩阵,再通过迪杰斯特拉拉算法计算出始末点的距离,并减去相应参数即可得到各个结果。

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

  第一次作业由于功能十分简单,所有没有发现正确性的问题,但最开始我只使用了一个HashMap数据结构进行存储,这就导致了查询的时间复杂度非常高,在进行极限测试的时候会出现CPU时间超时的现象。之后增加了一个Key与Vaulue互换的Hashmap和不同节点个数的Hashmap进行存储使时间复杂度大幅度下降。

  第二次作业没有发现特别需要注意的问题。

  第三次作业特别需要注意算法的实现方式,每一条边都需要通过floyd算法求出各个点之间的距离,并加上权值,后由所有点形成的邻接表通过迪杰斯特拉计算出始末节点之间的距离,减去权值即是所求值。

  总的来说这单元作业考察的较为综合,首先要能够看懂复杂的JML规格说明,此外还要对数据结构的知识有着比较深入的理解才可以实现既可以保证正确性又可以控制时间复杂度。

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

  JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。JML是一种行为接口规格语言(Behavior Interface Specification Language,BISL),基于Larch方法构建。BISL提供了对方法和类型的规格定义手段。所谓接口即一个方法或类型外部可见的内容。JML主要由Leavens教授在Larch上的工作,并融入了BetrandMeyer, John Guttag等人关于Design by Contract的研究成果。近年来,JML持续受到关注,为严格的程序设计提供了一套行之有效的方法。通过JML及其支持工具,不仅可以基于规格自动构造测试用例,并整合了SMT Solver等工具以静态方式来检查代码实现对规格的满足情况。

一般而言,JML有两种主要的用法:

  (1)开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语言描述,而是逻辑严格的规格。

  (2)针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。

posted @ 2019-05-22 20:23  发际线后移是技术提升的标志  阅读(188)  评论(0编辑  收藏  举报