OO第三单元总结
JML语言的理论基础
JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。JML以javadoc注释的方式来表示规格,每行都以@起头。
JML的用处
主要有两种用法,一是开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语言描述,而是逻辑严格的规格。二是 针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。
JML level 0基础语言特征
\result表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。
\old( expr )表达式:用来表示一个表达式 expr 在相应方法执行前的取值。
\not_assigned(x,y,...)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。
\forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。
\exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。
\sum表达式:返回给定范围内的表达式的和。
\max表达式:返回给定范围内的表达式的最大值。
\min表达式:返回给定范围内的表达式的最小值。
前置条件通过requires子句来表示,后置条件通过ensures子句来表示,JML提供了副作用约束子句,使用关键词assignable 或者 modifiable 。
对于纯粹访问性的方法,可以使用简单的(轻量级)方式来描述其规格。
signals子句的结构为 signals (***Exception e) b_expr; ,意思是当 b_expr 为 true 时,方法会抛出括号中给出的相应异常 e 。
不变式(invariant)是要求在所有可见状态下都必须满足的特性。同时用 constraint来对前序可见状态和当前可见状态的关系进行约束。
JML应用工具链情况
使用OpenJML直接对官方包的group接口进行测试,可以得到如下结果:
1 /tmp/tmpKOG_Nn/Group.java:9: error: cannot find symbol 2 @ public instance model non_null Person[] people; 3 ^ 4 symbol: class Person 5 location: interface Group 6 /tmp/tmpKOG_Nn/Group.java:28: error: cannot find symbol 7 public void addPerson(Person person); 8 ^ 9 symbol: class Person 10 location: interface Group 11 /tmp/tmpKOG_Nn/Group.java:31: error: cannot find symbol 12 public /*@pure@*/ boolean hasPerson(Person person); 13 ^ 14 symbol: class Person 15 location: interface Group 16 /tmp/tmpKOG_Nn/Group.java:69: error: cannot find symbol 17 public void delPerson(Person person); 18 ^ 19 symbol: class Person 20 location: interface Group 21 4 errors
我认为这是由于接口未实现导致的报错。
似乎OpenJML的功能还不太完善,对实现的代码进行测试还会报错。我又用它对level 0 手册上给出的样例代码进行检测,
public class Student { private /*@ spec_public @*/ String name; //@ public invariant credits >= 0; private /*@ spec_public @*/ int credits; /*@ public invariant credits < 180 ==> !master && @ credits >= 180 ==> master; @*/ private /*@ spec_public @*/ boolean master; /*@ requires sname != null; @ assignable \everything; @ ensures name == sname && credits == 0 && master == false; @*/ public Student (String sname) { name = sname; credits = 0; master = false; } /*@ requires c >= 0; @ ensures credits == \old(credits) + c; @ assignable credits, master; @ ensures (credits > 180) ==> master; @*/ public void addCredits(int c) { updateCredits(c); if (credits >= 180) { changeToMaster(); } } /*@ requires c >= 0; @ ensures credits == \old(credits) + c; @ assignable credits; @*/ private void updateCredits(int c) { credits += c; } /*@ requires credits >= 180; @ ensures master; @ assignable master; @*/ private void changeToMaster() { master = true; } /*@ ensures this.name == name; @ assignable this.name; @*/ public void setName(String name) { this.name = name; } /*@ ensures \result == name; @*/ public /*@ pure @*/ String getName() { return name; } }
这次没有报error。
可能是我的代码完善程度不够,也可能是由于JML工具链的功能限制,导致对作业代码的检查以失败告终。
JMLUnitNG
尝试多种方法使用jnlunitng对Group进行自动生成测试用例,但是无论如何都发生错误,以我的能力无法解决,只能放弃。
作业架构分析
以上为UML图,
由于本次作业给出的JML规格较为完善,我这次代码结构也较为清晰。同时在实现类中多次使用HashMap容器、DFS算法,并且为了queryMinPath的实现额外增加了Edge类。可以说基本上是依据JML建构了模型。
bug和修复情况
由于我对方法的测试不完善,第一次和第二次作业都出现了极大的BUG。
第一次:DFS编写考虑不周导致可能遍历不到某些节点,已成功修复。
第二次:由于jml看花眼导致某个参数计算写反了,已成功修复。
第三次:存在情况使stack不能及时清空,已成功修复。
心得体会
由于中测不能为程序提供有效测试导致我的每次提交都十分提心吊胆。
在本单元作业的实现过程中,基本上每次都符合SRP及OCP原则,编写过程十分明朗。
个人感觉JML存在可读性差的问题,在相关工具链不完善的情况下这个问题比较突出。考虑到JML面世时间也不短,可以说是一个遗憾。