OO第三单元总结

一、JML语言的理论基础

JML概述

JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。

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

JML为设计提供了一种思路,但其只是明确出给定条件下的无二义性的结果,在实际的架构设计和代码实现方面还要根据性能、安全、扩展性等方面综合考虑。

JML常用语法

(1)原子表达式

  • \result表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。
  • \old( expr )表达式:用来表示一个表达式 expr 在相应方法执行前的取值。
  • \not_assigned(x,y,...)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。如果没有被赋值,返回为 true ,否则返回 false 。
  • \not_modified(x,y,...)表达式:与\not_assigned表达式类似。
  • \nonnullelements( container )表达式:表示 container 对象中存储的对象不会有 null。

(2)量化表达式

  • \forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。 (\forall int i,j; 0 <= i && i < j && j < 10; a[i] < a[j]) ,意思是针对任意 0<=i<j<10, a[i]<a[j] 。
  • \exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。 (\exists int i; 0 <= i && i < 10; a[i] < 0) ,表示针对0<=i<10,至少存在一个a[i]<0。
  • \sum表达式:返回给定范围内的表达式的和。 (\sum int i; 0 <= i && i < 5; i) ,这个表达式的意思计算[0,5)范围内的整数i的和,即0+1+2+3+4==10。
  • \product表达式:返回给定范围内的表达式的连乘结果。 (\product int i; 0 < i && i < 5; i) ,这个表达式的意思是针对(0,5)范围的整数的连乘结果,即1* 2* 3 * 4==24。
  • \max表达式:返回给定范围内的表达式的最大值。 (\max int i; 0 <= i && i < 5; i) ,这个表达式返回[0,5)中的最大的整数,即4。
  • \min表达式:返回给定范围内的表达式的最小值。 (\min int i; 0 <= i && i < 5; i) ,这个表达式返回[0,5)中的最小的整数,即0。 

(3)操作符

  • 等价关系操作符: 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 。

(4)方法规格

  • 前置条件:通过requires子句来表示: requires P。其中requires是JML关键词,表达的意思是“要求调用者确保P为真”。方法规格中可以有多个requires子句,是并列关系,即调用者必须同时满足所有有的并列子句要求。
  • 后置条件:通过ensures子句来表示: ensures P。其中ensures是JML关键词,表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”。方法规格中可以有多个ensures 子句,是并列关系,即方法实现者必须同时满足有所并列ensures子句的要求。
  • 副作用范围限定:指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。从方法规格的角度,必须要明确给出副作用范围。JML提供了副作用约束子句,使用关键词 assignable 或者 modifiable 。
  • 异常抛出:通过signals子句表示: signals (Exception e) b_expr,意思是当 b_expr 为 true 时,方法会抛出括号中给出的相应异常 e 。

(5)类型规格

  • 不变式:是要求在所有可见状态下都必须满足的特性,语法上定义 invariant P ,其中 invariant 为关键词, P 为谓词。
  • 状态变化约束:JML为了简化使用规则,规定invariant只针对可见状态(即当下可见状态)的取值进行约束,而用constraint来对前序可见状态和当前可见状态的关系进行约束。语法上定义 constraint P ,其中 constraint 为关键词, P 为谓词。

二、JML语言的应用工具链

openJML

openJML的作用是对JML注释进行完整性检查,如类型检查、语法检查等等,主要为静态检查。

JMLUnitNG

JMLUnitNG可以对类通过生成测试数据进行单元测试,主要为动态检查。

尝试配置

配置时出现了一些错误,从错误信息来看应该不是JML和代码的问题,可能是导入包的问题。但我无论是改变路径、删除相关语句都没法成功配置。

从同学的使用情况看,使用openJML进行静态检查时重点检查JML的语法、引用、类型的正确性,而JMLUnitNG动态测试所用的测试数据主要为边界数据,如0、null、2147483648等,覆盖面较小。要对代码进行全面的正确性测试最好还是自己生成测试用例后与小伙伴对拍。

三、架构分析

第三次作业是在前两次作业的基础上迭代而成,迭代的过程中有对原有的方法进行一些优化,这里只介绍优化后的架构分析。

  MyPerson、MyGroup、MyNetwork三个类中要用到容器存储的属性都采用HashMap实现,这样能极大的减少插入、删除和查找的时间。

  MyGroup中有两个要留意的方法,即getRelationSum和getValueSum,因为其复杂度为O(n2),这种复杂度可能会在强测中超时。一种解决方法是通过动态维护relationSum和valueSum两个属性,具体的维护时间为对Group进行添加Person、删除Person和添加Relation操作之时,这样在调用getRelationSum和getValueSum方法时只需要返回相应的属性即可。

  MyNetwork中也有若干难点。isCircle方法采用Bfs算法判断两点间是否存在路径;queryMinPath方法采用堆优化Dijkstra算法求两点间的最短路径;queryStrongLinked方法采用Tarjan算法求无向图的两点是否为强连通;queryBlockSum方法通过维护一个并查集求连通分支数。

四、Bug分析

  第一次作业的强测炸裂,因为没有及时更新Network接口的JML,而更新前的addRelation的规格的四种行为有交集,自己又没有意识到,于是在不该抛出异常的地方抛出了异常。

  第二次作业未出现bug。

  第三次作业的强测出现了超时的情况,主要原因是HashMap的初始容量设置太大,而遍历时是通过调用values()方法获得相应容器进行遍历,这样遍历就会耗费大量时间,导致超时情况。

五、心得体会

  本单元作业相对于之前两个单元难度要小很多,因为有了JML的规范,在代码实现时不用考虑整体的架构设计,只需要考虑方法的性能和正确性。此外,测试方面也容易许多,构造测试用例时可以针对方法进行单元测试,保证每个方法的正确性就可以保证程序的正确性。

  感叹作业难度减小之余,我也发现了JML对代码实现的巨大帮助。JML不仅给代码带来无二义性的指导,还使得代码的维护、测试都更加方便。在之前的作业中,我一直都非常清楚在敲代码前,先对程序架构有一个整体的思考与把控十分重要。而现在看来,这个代码前的思考过程与为各个类、各个方法构建规格没有太大的差异。从这一方面来讲,我对JML规格也有了更深的理解。

  当然教训还是有的,“细节决定成败”,这句话无论放在哪里都很合适。第一次作业因为一个小小的失误导致强测的裂开,这让我恨不得抽自己一巴掌,这就算用分数换个教训吧。在接下来的学习中,每当写完一小单元的代码都要思考有没有什么疏漏,这样的实现是否会对之前或之后的工作有影响,等等,保证历史不再重演。

 

posted @ 2020-05-22 22:49  xxlscxx  阅读(152)  评论(0编辑  收藏  举报