OO第三单元总结

OO第三单元总结

1. JML基础梳理及工具链

1.1 注释结构

  • JML以javadoc注释的方式来表示规格,每行都以@起头
  • 行注释的表示方式为//@annotation块注释的表示方式为/* @ annotation @*/

1.2 JML表达式

​ JML相对于Java新增的表达式成分仅用于JML中的断言(assertion)语句和其他相关的注释体。

​ 在JML断言中,不可以使用带有赋值语义的操作符,如++,--,+=等操作符,因为这样的操作符会对被限制的相关变量的状态进行修改,产生副作用。

1.2.1 原子表达式

  • \result表达式

    ​ 表示一个非void类型的方法执行所获得的结果,即方法执行后的返回值。\result表达式的类型就是方法声明中定义的返回值类型。

  • \old(expr)表达式

    ​ 表示一个表达式expr相应方法执行前的取值。针对一个对象引用而言,只能判断引用本身是否发生变化,而不能判断引用所指向的对象实体内容是否发生变化。

  • \not_assigned(x,y,...)表达式

    ​ 表示括号中的变量是否在方法执行过程中被赋值。如果没有被赋值,返回为true,否则返回false。该表达式主要用于后置条件的约束表示上,即限制一个方法的实现不能对列表中的变量进行赋值

  • \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。

1.2.2 量化表达式

  • \forall表达式

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

  • \exists表达式

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

  • \sum表达式

    ​ 返回给定范围内的表达式的

  • \product表达式

    ​ 返回给定范围内的表达式的连乘结果

  • \max表达式

    ​ 返回给定范围内的表达式的最大值

  • \min表达式

    ​ 返回给定范围内的表达式的最小值

  • \num_of表达式

    ​ 返回指定变量中满足相应条件的取值个数

1.2.3 集合表达式

  • 集合构造表达式

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

    ​ 集合构造表达式的一般形式为:new ST {T x|R(x)&&P(x)},其中的R(x)对应集合中x的范围,通常是来自于某个既有集合中的元素,如s.has(x),P(x)对应x取值的约束。

1.2.4 操作符

  • 子类型关系操作符E1<:E2

    ​ 如果类型E1是类型E2的子类型或是相同类型,则该表达式的结果为真,否则为假。

  • 等价关系操作符b_expr1<==>b_expr2b_expr1<=!=>b_expr2

    ​ 这两个意思是b_expr1==b_expr2b_expr1!=b_expr2,其中b_expr1和b_expr2都是布尔表达式。这两个操作符和Java中的==!=具有相同的效果,但<==>==的优先级要低,<=!=>!=的优先级低。

  • 推理操作符b_expr1==>b_expr2b_expr2<==b_expr1

    ​ 对于表达式b_expr1==>b_expr2而言,当b_expr1==falseb_expr1==trueb_expr2==true时,整个表达式的值为true

  • 变量引用操作符

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

1.3 方法规格

1.3.1 前置条件

​ 前置条件是对方法输入参数的限制,如果不满足前置条件,方法执行结果不可预测,或者说不保证方法执行结果的正确性。

​ 前置条件通过requires子句来表示:**requires P;**。其中requires是JML关键词,表达的意思是“要求调用者确保P为真”。若方法规格中有多个requires子句,则调用者必须同时满足所有的并列子句要求。

1.3.2 后置条件

​ 后置条件是对方法执行结果的限制,如果执行结果满足后置条件,则表示方法执行正确,否则执行错误。

​ 后置条件通过ensures子句来表示:ensures P;。其中ensures是JML关键词,表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”。若方法规格中有多个ensures子句,则方法实现者必须同时满足有所并列ensures子句的要求。

1.3.3 副作用范围限定

​ 副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。

​ JML提供了副作用约束子句,使用关键词assignable或者modifiable

​ 用/*@ pure @ */表示的方法都是纯粹查询方法,即方法的执行不会有任何副作用

1.3.4 signals子句

​ signals子句的结构为signals (***Exception e) b_expr;,意思是当b_exprtrue时,方法会抛出括号中给出的相应异常e。如果一个方法在运行时会抛出异常,一定要在方法声明中明确指出(使用Java的throws表达式),且必须确保signals子句中给出的异常类型一定等同于方法声明中给出的异常类型,或者是后者的子类型。

signals_only子句不强调对象状态条件,强调满足前置条件时抛出相应的异常。

​ 为了更加明确的区分异常,会针对输入参数的取值范围抛出不同的异常,从而提醒调用者进行不同的处理。可以使用多个exceptional_behavior

1.4 类型规格

1.4.1 不变式invariant

​ 不变式是要求在所有可见状态下都必须满足的特性,语法上定义invariant P,其中invariant为关键词,P为谓词。

下面所述的几种时刻下对象o的状态都是可见状态

  • 对象的有状态构造方法(用来初始化对象成员变量初值)的执行结束时刻
  • 在调用一个对象回收方法(finalize方法)来释放相关资源开始的时刻
  • 在调用对象o的非静态、有状态方法(non-helper)的开始和结束时刻
  • 在调用对象o对应的类或父类的静态、有状态方法的开始和结束时刻
  • 在未处于对象o的构造方法、回收方法、非静态方法被调用过程中的任意时刻
  • 在未处于对象o对应类或者父类的静态方法被调用过程中的任意时刻

​ 凡是会修改成员变量(包括静态成员变量和非静态成员变量)的方法执行期间,对象的状态都不是可见状态。在方法执行期间,对象的不变式有可能不满足

1.4.2 状态约束变化constraint

对象的状态在变化时往往也许满足一些约束

1.5 应用工具链情况

  • JML高亮插件:可以帮助更好地阅读JML规格
  • OpenJML:可以根据JML对实现进行静态的检查
  • JMLUnitNG:可以根据JML生成自动的测试样例,用于进行单元测试

2. 部署JMLUnitNG

由于在部署JMLUnitNG遇到了很多错误,所以将整个过程记录了下来,实测可行

  • 首先需要下载并安装jdk8,具体操作可参考https://blog.csdn.net/weixin_42109012/article/details/94388518,在所有操作完成后,需要达到的效果是打开cmd,并且执行java -versionjavac -version指令,得到的是自己安装的版本。
  • 之后的具体操作可以参考https://www.cnblogs.com/nbao01/p/12935615.html,需要注意的是其中的第一步操作,除了package需要调整外,import的地方也需要进行相应的调整
  • 本人的最后执行结果如下所示


​ 在测试中一共运行了58个测试用例,其中失败了10个。它生成了三个MyGroup对象,其id分别为-2147483648、0和2147483647。JMLUnitNG在测试时,如果参数是int类型,则会传入-2147483648、0和2147483647进行测试;如果参数是Object子类,则会传入null。可见,JMLUnitNG的测试注重于边界情况,但是addPersondelPersonhasPerson方法传入的对象不能是null,所以才会失败。整体尝试发现,JMLUnitNG不怎么好用,还是自己写测试样例比较保险。

3. 架构设计

3.1 第一次作业

​ 此次作业的架构比较简单,首先依据Person接口和JML规格来实现MyPersonMyPerson类生成的每个对象都会有id(编号)、name(姓名)、character(性格)、age(年龄)、acquaintAndVaule(认识的Person和两人关系的权重)和circleFlag(所处的连通块编号)等属性,其中,因为id是独一无二,可以作为equals的判断依据;acquaintAndValueHashMap容器实现,Person作为key,两人关系的权重作为value。

​ 然后依据Network接口和JML规格来实现MyNetwork,其中people属性用来表示社交网络中的Person,用HashMap容器实现,id作为key,Person作为value。在判断两人是否间接认识即isCircle时,采用的是并查集算法。每个人初始的circleFlag是其id,每次addRelation时,都将其中一人的circleFlag变成另一人的circleFlag,基于QuickUnion实现并查集,并通过高度低的树向高度高的数合并和路径压缩,就可以高效查询网络中两个节点的连接状态。

3.2 第二次作业

​ 这次作业的架构则是在上次作业的架构上作了扩展,首先依据Person接口和JML规格来实现MyPerson,和上次作业大致相同,其中MyPerson类中的acquaintAndValue拆分成了acquaintancevalue,都用HashMap容器实现,用id作为key,用Personvalue分别作为value。

​ 然后依据Group接口和JML规格来实现MyGroupMyGroup类生成的每个对象都会有id(群组编号)、people(群组中的人)、peopleSize(群组中的人员数)、ageSum(群组内所有人的年龄之和)、conflictSum(群组内所有人的异或和)、relationSum(群组内关系的数量之和)和valueSum(群组内关系的权重之和)等属性。其中,peopleHashMap容器实现,id作为key,Person作为value。其中peoplepeopleSizeageSumconflictSum都是在addPerson时动态维护的,relationSumvalueSum都是在addPersonaddRelation时动态维护的。

​ 最后依据Network接口和JML规格来实现MyNetwork,在上次作业的基础上增加了groups属性,用来记录Network中所有的群组,用HashMap来实现,群组编号id作为key,群组Group作为value。

3.3 第三次作业

​ 这次作业则是在上次作业的基础上做了进一步的扩展。其中,MyGroup类中增加delPerson方法,在执行该方法时,要动态维护peoplepeopleSizeageSumconflictSum等属性。

MyNetwork相较于上次作业增加了很多新方法,为了更好地实现,增加了Relation类,记录一个Relation中的两个人的编号和关系权重。其中,queryMinPath要求两人间最短社交路径,本次作业使用了Dijkstra算法来实现;queryStrongLinked要求两人间是否有两条不相交的社交路径,本次作业使用了tarjan算法来寻找点双连通分量;queryBlockSum要求连通块数目,由于使用了并查集,所以不同的circleFlag数量就是连通块数目。

4. Bug修复情况

​ 本单元只在第三次作业中出现3个点CTLE的情况,这三个点的特征是大量的queryMinPath的命令。在求最短路径时,本人采用的是Dijkstra算法,所以重新提交后,bug修复。

5. 心得体会

​ 第三单元的作业是JML规格下进行代码的实现,在具体实现时,数据的实现方式、容器的选择、方法的实现算法都是值得仔细考察的内容。JML规格像是签订了一份“契约”,在未来的工程实践中也会十分有用。经过整个单元的学习,我对JML规格有了更深的认识,但是在工具链的使用和单元测试等方面还需要进一步的了解和学习。

posted @ 2020-05-23 15:17  韩程凯  阅读(177)  评论(0编辑  收藏  举报