OO Project3 规格、JML与测试
综述:这单元在我看来,难度相比之前大打折扣,得分也是,究其原因,一方面有自己的大意,也有一方面是对测试的不熟悉。三次作业的要求都是按照给定的JML来完成代码架构,实现一个模拟的网络,实际上就是图论问题。坑点很多,做得很惨。
一、JML回顾
JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。
JML是一种行为接口规格语言(所谓接口即一个方法或类型外部可见的内容),它提供了对方法和类型的规格定义手段,目的是为严格的程序设计提供了一套行之有效的方法。通过JML及其支持工具,不仅可以基于规格自动构造测试用例,而且可以使用一些工具以静态方式来检查代码实现对规格的满足情况。
一般而言,JML有两种主要的用法:
(1)开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语言描述,而是逻辑严格的规格。
(2)针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。
简单核心语句示例:
//@ ensures \result == id; public /*@pure@*/ int getId() { return id; } /*@ public normal_behavior @ requires elements.length >= 1; @ assignable \nothing; @ ensures \result @ == (\max int j; @ 0 <= j && j < elements.length; @ elements[j]); @*/ public abstract /*@ pure @*/ int largest() { //TODO }
(1)requires子句定义前置条件,一般在执行方法时都会先检查前置条件,不满足前置条件会有相应的处理,以便出现问题时的追溯,避免了bug出现在A函数,但表现在B函数,但从B函数追溯问题难以追溯到A的尴尬场面。
(2)assignable子句是副作用限定,规定可以改动的属性,在debug、编写中是十分重要的,另外\nothing表示这个方法不能对类的属性做任何改变,所以是一个/*@ pure @*/方法。
(3)ensures子句定义后置条件,\result表示返回值,生成测试样例时是很重要的参考依据。
下面是一些常见的表达式:
(1)原子表达式:
\result、\old、\not_assigned、\not_modified、\nonnullelements、\type、\typeof等
(2)量化表达式:
\forall、\exists、\sum、\product、\max、\min、\num_of
(3)集合表达式:
new ST {T x|R(x)&&P(x)}
(4)操作符:
<:、<==>、==>、\everything、\nothing
最后是类型规格有关的内容:
不变式:简单理解就是没有正在执行和它有关的类的时候需要满足的式子。复杂点的话...就是在可见状态下需要保持的式子。
可见状态: 对象的有状态构造方法(用来初始化对象成员变量初值)的执行结束时刻 在调用一个对象回收方法(finalize方法)来释放相关资源开始的时刻 在调用对象o的非静态、有状态方法(non-helper)的开始和结束时刻 在调用对象o对应的类或父类的静态、有状态方法的开始和结束时刻 在未处于对象o的构造方法、回收方法、非静态方法被调用过程中的任意时刻 在未处于对象o对应类或者父类的静态方法被调用过程中的任意时刻
实际上也是分静态与实例不变式,不细讲了。
状态变化约束:不变式约束状态,状态变化约束前一状态和现在的状态的变化情况
二、JMLUnit(不好使)
一堆报错搞我心态,不过看着大家都这样,整出来的也只能检查边界我就放心了。
类似这样的
......\main\Person.java:19: 错误: The expression is invalid or not terminated by a semicolon public /*@pure@*/ String getName(); ......\main\Network.java:30: 错误: A \old token with no label may not be present in a requires clause @ requires !(\exists int i; 0 <= i && i < \old(people.length);
还有很多很长的。。。真不会整。
三、梳理架构
课程组JML给写好了,换言之,就像是题目要求本来是中文,现在改成了英文而已,实现也很简单。
第九次作业:JML说啥,写啥就行,不需要思考
第十次作业:查找操作较多,考虑使用Hashmap将查询操作复杂度降为o(1),考虑使用缓存机制以加速对没有改变的Group的询问无需再次计算。
第十一次作业:操作复杂、高级。考虑使用并查集、堆优化Dijkstra、Tarjan算法,仍使用上次的缓存机制。
四、bug与修复
第九次作业:isCircle忘记判断两人是同一人的情况,加上即可。
第十次作业:超时,因为查询操作的遍历,将容器从ArrayLiat换为Hahmap,即可。
第十一次作业:并查集有误,修改并查集即可。
原因吗。。。大意了,测试没少做,bug太高级,测试机测了十万回没测出并查集错,主要是随机生成数据时有人为成分。
很惨,后两次没进屋,第一次60,,自闭了
五、心得体会
这就是面向对象的数据结构课吗?i了i了。
重大教训:多对简单的方法加以测试,基础出错,血本无归。
规格的书写还是很不熟练,但JML已经可以很快的读懂并且实现了,还算有些收获吧。
总之JML这种东西...怎么说,用起来是舒服的,但投入的时间与回报不成正比,再有,测试工具链不好用,在以后如果写Java,肯定是不愿意使用的。不过规格和契约式编程的思想是好的,这种思想可能在我以后的程序中,体现成为一些注释,而不是这种严谨的语言,虽然可以用程序进行测试,但并不完善的测试花费的时间,不如人工构造一些边界数据+随机生成大量数据的测试方法来得实在。