BUAA_OO_2020_Unit3_Overview
Posted littlenyima
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BUAA_OO_2020_Unit3_Overview相关的知识,希望对你有一定的参考价值。
JML理论梳理与工具链分析
JML作为一种行为接口规格语言,可以较为准确地对Java程序的行为进行描述。然而在本人使用过程中,由于其工具链的功能的极不完善,大多数的代码编写及测试还是依靠人力完成的,虽然它具有较高的严谨性,但使用体验并不是很好。
JML的注释结构
JML以javadoc注释的方式表示规格,每行以@起头。行注释为//@annotation
,块注释为/* @ annotation @ */
。
规格变量的声明分为静态与实例两种,声明方式分别为//@public static model non_null int[] elements
与//@public instance model non_null int[] elements
。
方法规格的声明主要有三个部分:
equires
子句:定义方法的前置条件,是调用者承诺满足的条件。assignable
子句:限定方法的副作用范围。assignable othing
的方法不修改规格变量,因此为/*@pure@*/
方法。ensures
子句:定义方法的后置条件,是被调用者承诺满足的条件。
JML原子表达式
esult
表达式:表示一个非void方法执行的返回值。old(expr)
表达式:表示表达式expr在执行方法前的取值。ot_assigned(x, y, ...)
表达式:表示括号内的变量在执行中未被赋值。若被赋值则该表达式为false。ot_modified(x, y, ...)
表达式:与ot_assigned
类似,表示取值未发生变化。onnullelements(container)
表达式:表示container中不含有null
对象。等价于断言: container != null && (foall int i; 0 <= i && i < container.length; container[i] != null)ype(expr)
表达式:等同于java.lang.class
ypeof(expr)
表达式:返回准确类型。
JML量化表达式
forall
表达式:全称量词修饰的表达式。例如 (forall int i,j; 0 <= i && i < j && j < 10; a[i] < a[j])exists
表达式:存在量词修饰的表达式。例如 (exists int i; 0 <= i && i < 10; a[i] < 0)sum
表达式:指定范围内求和表达式。例如 (sum int i; 0 <= i && i < 5; i) ,这个表达式的值为10。product
表达式:指定范围内连乘表达式。例如 (product int i; 0 < i && i < 5; i) ,这个表达式的值为24。max
表达式:最大值表达式。例如 (max int i; 0 <= i && i < 5; i) 这个表达式的值为4。min
表达式:最小值表达式。例如 (min int i; 0 <= i && i < 5; i) 这个表达式的值0。um_of
表达式:指定范围内满足条件元素个数表达式。 ( um_of T x; R(x);P(x)) 表示R(x)范围内满足P(x)条件的x元素的个数。
JML集合表达式
集合表达式的形式为 new ST {T x|R(x)&&P(x)} ,其中ST是构造的容器,T是数据类型,R是范围约束,P是取值约束。
操作符
- 子类型关系操作符:
E1<:E2
,若E1是E2的子类或E1与E2同类则为真,否则为假。 - 等价关系操作符:
expr1<==>expr2
或expr1<=!=>expr2
,两端为布尔表达式,表示两者等价或不等价。 - 推理操作符:
expr1==>expr2
,其取值等价于expr1→expr2
。 - 变量引用操作符:
othing
表示空集,everything
表示全集
方法规格
除了已经提到的三个方法规格外,还有signals与signals_only,用于exceptional behavior的处理。
signals
子句:signals (***Exception e) b_expr
,意为当b_expr为true时方法抛出异常e。signals_only
子句:后接异常类型,表示满足条件时直接抛出异常。
工具链使用情况
openjml、SMT solver、JMLUnit等,使用时总体的感受是网络上关于JML工具链的资料实在不够翔实,并且这些工具本身在功能上也不够完善。
基于SMT Solver的程序验证
>>>TODO>>>
应用JMLUnit自动化生成测试用例
>>>TODO>>>
作业架构分析
由于官方包中接口的限定,本次作业在架构设计上比较单一,只要设计相应类并实现相应接口即可。主要的创新空间在于具体方法的实现上。由于本单元作业为增量开发,此处仅给出最后一次作业的UML图。
除了规定中要实现的各个类外,我还编写了MyRelation类用来管理关系、MyBalance类用来管理存款、UnionFindSet并查集类用来优化查找速度。
在实现细节方面,在第一次作业中我使用了ArrayList来存储各个量,但到第二次作业时迫于10w指令的大计算量,我改用了以id为key的hashmap作为查找容器、ArrayList作为枚举容器的混合方法。
第一次作业几乎没有难点,主要就是一些细节方面的内容,只要仔细阅读、正确理解JML即不会出现问题。第一次作业最复杂的方法是query_circle(),我采用了广度优先搜索的方法。
第二次作业主要是各种query_sum方法比较麻烦,并且考虑到10w指令的影响,需要将各个方法时间复杂度控制在O(N)范围内。我采取的策略时person与relation更新时同时更新各个sum,并将其取值缓存。用这种方式,ap与ar时间复杂度为O(N),各个查询方法时间复杂度为O(1),在性能上达到了要求。
第三次作业的qsl和qmp是最困难的两个方法。我的qsl采用的是枚举删点法,也就是如果某个人与两个端点均连通,则将这个人删去,再求两个端点的连通性。如此遍历关系网中的各个人,如果所有的结果均为连通,则两个端点为强连通。根据估计,计算量最高约为10^8数量级,勉强符合要求,也通过了强测。qmp我采用的是堆优化的dijkstra算法,虽然理论复杂度只有O(nlogn),但由于实现时不够注意细节,在找到人时没有提前break,也没有重写person类的hashcode()方法,导致强测中一个点CTLE。修改后快了0.8s有余,这使我反思,不能仅依靠理论复杂度,更应该注意细节方面的问题。
Bug与修复情况
前两次作业在强测与互测中均没有被发现bug。第三次作业强测CTLE一个点(qmp方法),互测没有被发现bug。修复时重写了person的hashcode,简化了访问标记的初始化过程(主要是去除了一个O(N)遍历),并加入了找到时的提前break。成功修复了CTLE的点。
心得体会
首先的感受是来到了JML这一单元后OO作业的负担减轻了许多,从需要自己设计到只需要根据规格来实现方法,的确简单了许多。但仅仅根据规格又是不够的,还需要我们考虑实现上的细节与性能方面的优化。除此之外,我对“契约式”的理解更加深入了,只有接口的提供者与实现者遵守一套共同的规范,才能保证程序的正确性。
这个单元虽然学习了JML,但因为没有真正地动手写过JML代码(只有实验课做了几个JML的填空题),至今只能读JML来实现方法,而无法写出自己的JML。而且对JUnit的使用还不是十分熟练,感觉在形式化验证与单元测试这一方面需要学习的东西还有很多。
以上是关于BUAA_OO_2020_Unit3_Overview的主要内容,如果未能解决你的问题,请参考以下文章