一. JML语言的理解
JML语言是为了明确、清晰描述行为而诞生的,语言习惯类似离散数学里面的语句。本质上,JML语言是在规范的、明确的指出操作的行为,这样的方式不仅可以在逻辑层面分析bug、也有利于对单元进行测试、设计。这一点和设计本身是完全不一样的,实现的具体方式应基于数据的模式来选择。
pure (assignable \nothing)+ \\ensure
\old(exp) 如果exp是引用的话,注意变化的内容
\not_assigned(x,y) 变量不赋值(后置条件约束)
\not_modified() 同上
\nonnullelements(container); container不是null 且 保存的内容没有null
\type(type) \typeof(expr) 返回class Boolean.TYPE
\forall \exist (;;判断对象)
\sum (;;求和对象)求和
\product 连乘
\max \min
\num_of (;;条件)返回满足条件的指定变量个数
子类型 E1<:E2 E1是E2的子类型或本身类型
布尔等价 <==> 优先级比==低
布尔推理 ==> 类似→ 10为假
\nothing 空集 \everything当前作用域全部变量
requires P;Q; 要求P真且Q真
requires P||Q; 要求P真或Q真
ensures P; 确保结果满足P;
assignable ,分割
also 子类补充规格前 ; 分割多个功能描述
signals (Excption e ) exp 当exp为true时抛出异常
Exceptional behavior下也可以使用ensures
(instance\static) invariant P; 可见状态下必须满足P
修改 成员变量 的 方法 执行 , 不是可见状态。
constraint P; \old前序状态 和 此时状态 满足条件
二.基于JML的测试和测试工具链
有JML规格的设计重心在于理解单元的规格描述、并在满足时间要求上正确实现,因此JMLUNIT单元测试无异于一种最点对点的模式。缺点是设计效率偏低。
可以使用的JML工具链包括
OpenJML:可以实现JML语法错误检查。
SMTSolver: 在逻辑层面、对代码实现形式化验证。
JMLUnitNG: 根据JML规格生成对应的测试样例来测试程序。
由于网络原因,一开始下载OpenJML就不成功,因此本次作业并没有通过检查和编译JML语言来debug。
三.作业设计
(1)第一次作业是实现基本的无向图的结构,比如Person接口定义点、NetWork接口定义网络,通过Person内的Acquaintance确定了邻接点和边。
这次作业对速度没有要求,唯一设计算法的是isCircle、用于判断两点是否连通的方法,dfs与bfs都可以达到效果。
(2)第二次作业增加了Group类,即点集合。并且增加了Group相关的增加点、求集合内点联系和、联系权重合、求年龄平均值、方差等方法。
本次作业的很重要的一点是提高了对时间的要求。这也让JML语言本质有所体现。在第一次作业中,即便按照JML语言表述的方式、比如存在循环来得到对应的要查找的值也是可以获得正确结果的。而这一次就明显给出了,如果按照JML规格描述的逻辑去完成函数,必然会超时,因此JML规格本身只是传递实现的目标和要求本身。
在这一次作业中为了提高查找类方法的速度,在三个类中增加了几个版本的容器,如
在Person类中增加了Acquaintance的id的Arraylist集合,目的是再执行isCircle的时候遍历时有更快的速度;
在Group中,为了提高getRelationSum、getValueSum等设计两重循环,复杂度在O(n^2)下的方法,提供了对Group中每个点对应的组内关系、权值的Hashmap:people_relation和people_value,同时在Group内增加人物、人物之间增加关系的时候更新这两个缓存。
在Network中,利用Hashmap的哈希存储方式,将常用的get类方法和contain类方法用Hashmap实现,增加了两个Hashmap:people_buff和group_buff;
除了增加缓存之外,为了保证isCircle的速度,还修改了原来的dfs采用bfs。
(3)第三次作业的重点则转为对相关复杂度的估计,以及图的最短路径算法、并查集、两点双连通的查找。这部分是最难处理的。
这次作业就在于给定的复杂度下如何使用恰当、正确的算法。qmp使用了堆优化的Dij算法、qsl使用了dfs+bfs,qbs即查找连通分支个数,选择了并查集机制,其余的没有修改。
四.问题总结
这次的作业出现的问题要不然是过于无厘头,让人觉得可笑又笑不出来;要不然就是在细节、实现上有所要求,让人压力山大。
前者主要集中于JML规格本身,第一次作业的规格有过第二次修改,修改后没有认真再看一遍,在完成相关内容的时候一直致力于理解规格本身,好多东西没有过脑子,结果不同behavior之间竟然被我写出了交集;第二次作业大部分的错也是因为作业的规格不知道什么时候增加了一段,但我本身因为第一次的原因,特意晚了一点写作业,而且没有关注论坛本身,所以也没什么好说。
这次作业和之前差距太大,当然还有很大一部分原因也是因为测试工具的原因,由于这次题目提供方式特殊,基本围绕方法来提供,因此完成一个黑盒测试很难精确解决问题。但基于规格的测试工具,比如OpenJMLz在下载的时候花费了我好多时间还是没有成功,只能最后选择放弃,第二次作业对新增加的内容使用JUNIT测试,检查出一些问题,但由于我手上的规格没有更新,就算符合我手上的规格也犯了很致命的大分错误。
比较难以解决的问题集中在算法实现、选择上,尤其是第三次作业,可类比瞎猫找老鼠,我选择实现的方式之来自第一学年的学习成果和百度,更优选择连听都不了解。但好在别的细节没有再犯相同的错误了。
JML规格这一单元本身任务并不繁重,但带来的感觉是前所未有的,可能实现、正确本身并不是问题、而对不同环境下的不同要求,才是应当首先关注的标准。