OO第三单元总结

一、JML概述

1.1 JML理论基础

JML简介

  JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。JML是一种行为接口规格语言(Behavior Interface Specifification Language,BISL),基于Larch方法构建。   一般而言,JML有两种主要的用法:   (1)开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语言描述,而是逻辑严格的规格。   (2)针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。

注释结构

  JML以javadoc注释的方式来表示规格,每行都以@起头。有两种注释方式,行注释和块注释。其中行注释的表示方式为 //@annotation ,块注释的方式为 /* @ annotation @*/ 。按照Javadoc习惯,JML注释一般放在被注释成分的紧邻上部。    纯粹查询方法(/*@ pure @ */),即方法的执行不会有任何副作用。   requires子句定义该方法的前置条件(precondition)。   副作用范围限定,assignable列出这个方法能够修改的类成员属性。   ensures子句定义了后置条件。   如果是在Interface中声明规格变量,则要求明确变量的类别。

JML表达式

原子表达式
  • \result表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。\result表达式的类型就是方法声明中定义的返回值类型。
  • \old(expr)表达式:用来表示一个表达式 expr 在相应方法执行前的取值。该表达式涉及到评估 expr中的对象是否发生变化,遵从Java的引用规则,即针对一个对象引用而言,只能判断引用本身是否发生变化,而不能判断引用所指向的对象实体内容是否发生变化。
  • \not_assigned(x,y,...)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。如果没有被赋值,返回为 true ,否则返回 false 。实际上,该表达式主要用于后置条件的约束表示上,即限制一个方法的实现不能对列表中的变量进行赋值。
  • \not_modifified(x,y,...)表达式:与上面的\not_assigned表达式类似,该表达式限制括号中的变量在方法执行期间的取值未发生变化。
  • \nonnullelements(container)表达式:表示 container 对象中存储的对象不会有null。
  • \type(type)表达式:返回类型type对应的类型(Class),如type( boolean )为Boolean.TYPE。
  • \typeof(expr)表达式:该表达式返回expr对应的准确类型。如\typeof( false )为Boolean.TYPE。
量化表达式
  • \forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。
  • \exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。
  • \sum表达式:返回给定范围内的表达式的和。
  • \product表达式:返回给定范围内的表达式的连乘结果。
  • \max表达式:返回给定范围内的表达式的最大值。
  • \min表达式:返回给定范围内的表达式的最小值。
  • \num_of表达式:返回指定变量中满足相应条件的取值个数。
集合表达式
集合构造表达式:可以在JML规格中构造一个局部的集合(容器),明确集合中可以包含的元素。集合构造表达式的一般形式为:new ST {T x|R(x)&&P(x)},其中的R(x)对应集合中x的范围,通常是来自于某个既有集合中的元素,如s.has(x),P(x)对应x取值的约束。
操作符
  • 子类型关系操作符: E1<:E2 ,如果类型E1是类型E2的子类型(sub type),则该表达式的结果为真,否则为假。如果E1和E2是相同的类型,该表达式的结果也为真。
  • 等价关系操作符: b_expr1<==>b_expr2 或者 b_expr1<=!=>b_expr2 ,其中b_expr1和b_expr2都是布尔表达式,这两个表达式的意思是 b_expr1==b_expr2 或b_expr1!=b_expr2 。
  • 推理操作符: b_expr1==>b_expr2 或者 b_expr2<==b_expr1 。对于表达式 b_expr1==>b_expr2而言,当 b_expr1==false ,或者 b_expr1==true 且 b_expr2==true 时,整个表达式的值为 true 。
  • 变量引用操作符:除了可以直接引用Java代码或者JML规格中定义的变量外,JML还提供了几个概括性的关键词来引用相关的变量。\nothing指示一个空集;\everything指示一个全集,即包括当前作用域下能够访问到的所有变量。

方法规格

方法规格的核心内容包括三个方面,前置条件、后置条件和副作用约定。其中前置条件是对方法输入参数的限制,如果不满足前置条件,方法执行结果不可预测,或者说不保证方法执行结果的正确性;后置条件是对方法执行结果的限制,如果执行结果满足后置条件,则表示方法执行正确,否则执行错误。副作用指方法在执行过程中对输入对象或 this 对象进行了修改(对其成员变量进行了赋值,或者调用其修改方法)。
  • 前置条件(pre-condition) 前置条件通过requires子句来表示: requires P; 。其中requires是JML关键词,表达的意思是“要求调用者确保P为真”。
  • 后置条件(post-condition) 后置条件通过ensures子句来表示: ensures P; 。其中ensures是JML关键词,表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”。
  • 副作用范围限定(side-effffects) 副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。从方法规格的角度,必须要明确给出副作用范围。JML提供了副作用约束子句,使用关键词assignable 或者 modifiable 。
  • signals子句 signals子句的结构为 signals (***Exception e) b_expr; ,意思是当 b_expr 为 true 时,方法会抛出括号中给出的相应异常 e 。还有一个简化的signals子句,即signals_only子句,后面跟着一个异常类型。signals子句强调在对象状态满足某个条件时会抛出符合相应类型的异常;而signals_only则不强调对象状态条件,强调满足前置条件时抛出相应的异常。

类型规格

类型规格指针对Java程序中定义的数据类型所设计的限制规则,一般而言,就是指针对类或接口所设计的约束规则。从面向对象角度来看,类或接口包含数据成员和方法成员的声明及或实现。不失一般性,一个类型的成员要么是静态成员(static member),要么是实例成员(instance member)。一个类的静态方法不可以访问这个类的非静态成员变量(即实例变量)。静态成员可以直接通过类型来引用,而实例成员只能通过类型的实例化对象来引用。
  • 不变式invariant 不变式(invariant)是要求在所有可见状态下都必须满足的特性,语法上定义 invariant P ,其中invariant 为关键词, P 为谓词。对于类型规格而言,可见状态(visible state)是一个特别重要的概念。下面所述的几种时刻下对象o的状态都是可见状态: 对象的有状态构造方法(用来初始化对象成员变量初值)的执行结束时刻 在调用一个对象回收方法(fifinalize方法)来释放相关资源开始的时刻 在调用对象o的非静态、有状态方法(non-helper)的开始和结束时刻 在调用对象o对应的类或父类的静态、有状态方法的开始和结束时刻 在未处于对象o的构造方法、回收方法、非静态方法被调用过程中的任意时刻 在未处于对象o对应类或者父类的静态方法被调用过程中的任意时刻
  • 状态变化约束constraint 对象的状态在变化时往往也许满足一些约束,这种约束本质上也是一种不变式。JML为了简化使用规则,规定invariant只针对可见状态(即当下可见状态)的取值进行约束,而是用constraint来对前序可见状态和当前可见状态的关系进行约束。

1.2 JML应用工具链

  • OpenJML : 主要是对JML规格进行语法检查,可以进行动态和静态语法检查。
  • SMT Solver :用于检查程序和规格是否等价。
  • JMLUnitNG : 自动化测试工具

二、部署工具链

  • JMLUnitNG

  下载地址 :http://insttech.secretninjaformalmethods.org/software/jmlunitng/

  去除了Person类和Group类的接口,改成了新的Person类和Group类。

  需要补全代码中的HashMap<>括号里的内容。

  修改重名的内容

  命令行输入以下几条指令:

java -jar jmlunitng.jar test/Group.java

javac -cp jmlunitng.jar test/*.java

java -jar openjml.jar -rac test/Group.java test/Person.java

java -cp jmlunitng.jar test.Group_JML_Test
结果如下:

[TestNG] Running: Command line suite Passed: racEnabled() Passed: constructor Group(-2147483648) Passed: constructor Group(0) Passed: constructor Group(2147483647) Failed: <<test.Group@64cee07>>.addPerson(null) Failed: <<test.Group@1761e840>>.addPerson(null) Failed: <<test.Group@5ecddf8f>>.addPerson(null) Failed: <<test.Group@3f102e87>>.delPerson(null) Failed: <<test.Group@27abe2cd>>.delPerson(null) Failed: <<test.Group@5f5a92bb>>.delPerson(null) Passed: <<test.Group@6fdb1f78>>.equals(null) Passed: <<test.Group@51016012>>.equals(null) Passed: <<test.Group@29444d75>>.equals(null) Passed: <<test.Group@44e81672>>.equals(java.lang.Object@60215eee) Passed: <<test.Group@4ca8195f>>.equals(java.lang.Object@65e579dc) Passed: <<test.Group@61baa894>>.equals(java.lang.Object@b065c63) Passed: <<test.Group@768debd>>.getAgeMean() Passed: <<test.Group@490d6c15>>.getAgeMean() Passed: <<test.Group@449b2d27>>.getAgeMean() Passed: <<test.Group@5479e3f>>.getAgeVar() Passed: <<test.Group@27082746>>.getAgeVar() Passed: <<test.Group@66133adc>>.getAgeVar() Passed: <<test.Group@7bfcd12c>>.getConflictSum() Passed: <<test.Group@42f30e0a>>.getConflictSum() Passed: <<test.Group@24273305>>.getConflictSum() Passed: <<test.Group@5b1d2887>>.getId() Passed: <<test.Group@46f5f779>>.getId() Passed: <<test.Group@1c2c22f3>>.getId() Passed: <<test.Group@33e5ccce>>.getRelationSum() Passed: <<test.Group@5a42bbf4>>.getRelationSum() Passed: <<test.Group@270421f5>>.getRelationSum() Passed: <<test.Group@52d455b8>>.getValueSum() Passed: <<test.Group@4f4a7090>>.getValueSum() Passed: <<test.Group@18ef96>>.getValueSum() Passed: <<test.Group@6956de9>>.groupSize() Passed: <<test.Group@769c9116>>.groupSize() Passed: <<test.Group@6aceb1a5>>.groupSize() Failed: <<test.Group@2d6d8735>>.hasPerson(null) Failed: <<test.Group@ba4d54>>.hasPerson(null) Failed: <<test.Group@12bc6874>>.hasPerson(null) Passed: <<test.Group@de0a01f>>.updateRelation(-2147483648, -2147483648, -2147483648) Passed: <<test.Group@4c75cab9>>.updateRelation(-2147483648, -2147483648, -2147483648) Passed: <<test.Group@1ef7fe8e>>.updateRelation(-2147483648, -2147483648, -2147483648) Passed: <<test.Group@6f79caec>>.updateRelation(0, -2147483648, -2147483648) Passed: <<test.Group@67117f44>>.updateRelation(0, -2147483648, -2147483648) Passed: <<test.Group@5d3411d>>.updateRelation(0, -2147483648, -2147483648) Passed: <<test.Group@2471cca7>>.updateRelation(2147483647, -2147483648, -2147483648) Passed: <<test.Group@5fe5c6f>>.updateRelation(2147483647, -2147483648, -2147483648) Passed: <<test.Group@6979e8cb>>.updateRelation(2147483647, -2147483648, -2147483648) Passed: <<test.Group@763d9750>>.updateRelation(-2147483648, 0, -2147483648) Passed: <<test.Group@5c0369c4>>.updateRelation(-2147483648, 0, -2147483648) Passed: <<test.Group@2be94b0f>>.updateRelation(-2147483648, 0, -2147483648) Passed: <<test.Group@d70c109>>.updateRelation(0, 0, -2147483648) Passed: <<test.Group@17ed40e0>>.updateRelation(0, 0, -2147483648) Passed: <<test.Group@50675690>>.updateRelation(0, 0, -2147483648) Passed: <<test.Group@3ac42916>>.updateRelation(2147483647, 0, -2147483648) Passed: <<test.Group@47d384ee>>.updateRelation(2147483647, 0, -2147483648) Passed: <<test.Group@2d6a9952>>.updateRelation(2147483647, 0, -2147483648) Passed: <<test.Group@22a71081>>.updateRelation(-2147483648, 2147483647, -2147483648) Passed: <<test.Group@3930015a>>.updateRelation(-2147483648, 2147483647, -2147483648) Passed: <<test.Group@629f0666>>.updateRelation(-2147483648, 2147483647, -2147483648) Passed: <<test.Group@1bc6a36e>>.updateRelation(0, 2147483647, -2147483648) Passed: <<test.Group@1ff8b8f>>.updateRelation(0, 2147483647, -2147483648) Passed: <<test.Group@387c703b>>.updateRelation(0, 2147483647, -2147483648) Passed: <<test.Group@224aed64>>.updateRelation(2147483647, 2147483647, -2147483648) Passed: <<test.Group@c39f790>>.updateRelation(2147483647, 2147483647, -2147483648) Passed: <<test.Group@71e7a66b>>.updateRelation(2147483647, 2147483647, -2147483648) Passed: <<test.Group@2ac1fdc4>>.updateRelation(-2147483648, -2147483648, 0) Passed: <<test.Group@5f150435>>.updateRelation(-2147483648, -2147483648, 0) Passed: <<test.Group@1c53fd30>>.updateRelation(-2147483648, -2147483648, 0) Passed: <<test.Group@50cbc42f>>.updateRelation(0, -2147483648, 0) Passed: <<test.Group@75412c2f>>.updateRelation(0, -2147483648, 0) Passed: <<test.Group@282ba1e>>.updateRelation(0, -2147483648, 0) Passed: <<test.Group@13b6d03>>.updateRelation(2147483647, -2147483648, 0) Passed: <<test.Group@f5f2bb7>>.updateRelation(2147483647, -2147483648, 0) Passed: <<test.Group@73035e27>>.updateRelation(2147483647, -2147483648, 0) Passed: <<test.Group@64c64813>>.updateRelation(-2147483648, 0, 0) Passed: <<test.Group@3ecf72fd>>.updateRelation(-2147483648, 0, 0) Passed: <<test.Group@483bf400>>.updateRelation(-2147483648, 0, 0) Passed: <<test.Group@21a06946>>.updateRelation(0, 0, 0) Passed: <<test.Group@77f03bb1>>.updateRelation(0, 0, 0) Passed: <<test.Group@326de728>>.updateRelation(0, 0, 0) Passed: <<test.Group@25618e91>>.updateRelation(2147483647, 0, 0) Passed: <<test.Group@7a92922>>.updateRelation(2147483647, 0, 0) Passed: <<test.Group@71f2a7d5>>.updateRelation(2147483647, 0, 0) Passed: <<test.Group@2cfb4a64>>.updateRelation(-2147483648, 2147483647, 0) Passed: <<test.Group@5474c6c>>.updateRelation(-2147483648, 2147483647, 0) Passed: <<test.Group@4b6995df>>.updateRelation(-2147483648, 2147483647, 0) Passed: <<test.Group@2fc14f68>>.updateRelation(0, 2147483647, 0) Passed: <<test.Group@591f989e>>.updateRelation(0, 2147483647, 0) Passed: <<test.Group@66048bfd>>.updateRelation(0, 2147483647, 0) Passed: <<test.Group@61443d8f>>.updateRelation(2147483647, 2147483647, 0) Passed: <<test.Group@445b84c0>>.updateRelation(2147483647, 2147483647, 0) Passed: <<test.Group@61a52fbd>>.updateRelation(2147483647, 2147483647, 0) Passed: <<test.Group@233c0b17>>.updateRelation(-2147483648, -2147483648, 2147483647) Passed: <<test.Group@63d4e2ba>>.updateRelation(-2147483648, -2147483648, 2147483647) Passed: <<test.Group@7bb11784>>.updateRelation(-2147483648, -2147483648, 2147483647) Passed: <<test.Group@33a10788>>.updateRelation(0, -2147483648, 2147483647) Passed: <<test.Group@7006c658>>.updateRelation(0, -2147483648, 2147483647) Passed: <<test.Group@34033bd0>>.updateRelation(0, -2147483648, 2147483647) Passed: <<test.Group@47fd17e3>>.updateRelation(2147483647, -2147483648, 2147483647) Passed: <<test.Group@7cdbc5d3>>.updateRelation(2147483647, -2147483648, 2147483647) Passed: <<test.Group@3aa9e816>>.updateRelation(2147483647, -2147483648, 2147483647) Passed: <<test.Group@17d99928>>.updateRelation(-2147483648, 0, 2147483647) Passed: <<test.Group@3834d63f>>.updateRelation(-2147483648, 0, 2147483647) Passed: <<test.Group@1ae369b7>>.updateRelation(-2147483648, 0, 2147483647) Passed: <<test.Group@6fffcba5>>.updateRelation(0, 0, 2147483647) Passed: <<test.Group@34340fab>>.updateRelation(0, 0, 2147483647) Passed: <<test.Group@2aafb23c>>.updateRelation(0, 0, 2147483647) Passed: <<test.Group@2b80d80f>>.updateRelation(2147483647, 0, 2147483647) Passed: <<test.Group@3ab39c39>>.updateRelation(2147483647, 0, 2147483647) Passed: <<test.Group@2eee9593>>.updateRelation(2147483647, 0, 2147483647) Passed: <<test.Group@7907ec20>>.updateRelation(-2147483648, 2147483647, 2147483647) Passed: <<test.Group@546a03af>>.updateRelation(-2147483648, 2147483647, 2147483647) Passed: <<test.Group@721e0f4f>>.updateRelation(-2147483648, 2147483647, 2147483647) Passed: <<test.Group@28864e92>>.updateRelation(0, 2147483647, 2147483647) Passed: <<test.Group@6ea6d14e>>.updateRelation(0, 2147483647, 2147483647) Passed: <<test.Group@6ad5c04e>>.updateRelation(0, 2147483647, 2147483647) Passed: <<test.Group@6833ce2c>>.updateRelation(2147483647, 2147483647, 2147483647) Passed: <<test.Group@725bef66>>.updateRelation(2147483647, 2147483647, 2147483647) Passed: <<test.Group@2aaf7cc2>>.updateRelation(2147483647, 2147483647, 2147483647)

从测试数据可以看出,JMLUnitNG基本都是在测边界数据,如构造Group时用的是0、最大int、最小int,测试传入person的时候用null。

Fail的一些方法主要是因为调用的时候保证了不会传入NULL。

JMLUnitNG只注重对边界情况的测试,对一般情况则几乎没有涉及,没有随机性,测试意义不是很大。

三、作业架构设计

作业中需要注意存储方式,可能会给时间复杂度带来很大影响。最好多使用Hashmap,也可以与Arraylist结合使用,如Person类我用了一个动态数组存acquaintance,一个HashMap存acquaintance的id和value的对应。以及避免使用Arraylist.contains等可以避免的O(n)方法.

认真阅读JML规格避免看错条件。

3.1 第一次作业

第一次作业主要是熟悉JML,因此主要问题在认真阅读JML规格。

难点是isCircle方法的实现,我采用了bfs的方法实现。

用并查集会更好,不用每次都查找一遍。

UML类图

OO第三单元总结

3.2 第二次作业

相比第一次作业加入了Group类和很多查询方法,如果每次都遍历查找时间复杂度势必会很高,而且测试指令条数很多,要注意缓存。

在加关系的时候也要注意更新缓存。

UML类图

OO第三单元总结

3.3 第三次作业

第三次作业主要难点在三个函数,涉及了数据结构的图相关的知识。

  • queryMinPath 函数

    求权值最小路径,采用堆优化迪杰斯特拉算法,用Java的优先队列存储节点。

  • queryStrongLinked 函数

    判断两点是否点双联通,就是求是否存在两条通过两点且除这两点外没有相同点的路径,并且只有这两点直接连接的路径只能算一条。

    采用暴力遍历与这两点联通的所有点,每次去掉一个点bfs看两点是否还联通,如果存在使两点不联通的点则不是点双联通。

    注意两点直接连接的情况要单独考虑,去边后再bfs一次。

  • queryBlockSum 函数

    采用并查集实现,求联通块的个数。

    同时把isCircle也改成了并查集实现。

UML类图

OO第三单元总结

四、bug相关

三次作业均未测出bug。

互测中第二次作业有同学在加关系时没有更新缓存,有同学没有进行缓存导致遍历复杂度过高。

互测中第三次作业有同学方法实现上复杂度过高。

五、心得体会

规格撰写需要尽可能周到地考虑各种情况,注意JML格式。

阅读JML规格的时候要注意括号位置和符号的含义,看清楚数字,实现每一种情况。

这个单元接触了规格化设计,体会到了性能优化的重要性。但JML相关工具链还不太完善,有些表述也比较繁琐,使用还是需要进一步优化。

上一篇:牛客 自然数拆分Lunatic版


下一篇:从一个失败的强化学习训练说起:OpenAI 探讨应该如何设计奖励函数?