OO第三次单元作业之探寻神秘规格
一、JML语言梳理
(一)理论基础
JML(即Java Modeling Language),是java建模语言,通过注释的形式对Java程序中数据、方法、类的功能进行描述,着重于代码的功能而非实现形式。是一种行为接口的规范语言,可以用来指定Java模块的行为。
JML最基本的用途是作为Java的合同设计(DBC语言),同时也可以通过其相关支持工具,检查规格是否合乎规范、并基于规格自动构造测试用例,还可以使用SMT Solver等工具以静态方式来检查代码实现对规格的满足情况。
主要的规格有:
JML表达式
- \result表达式:方法执行后的返回值。
- \forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。
- \exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。
- \old( expr )表达式:用来表示一个表达式expr 在相应方法执行前的取值。
- \sum表达式:返回给定范围内的表达式的和。
- \max,\min表达式:返回给定范围内的表达式的最大值、最小值。
- 等价关系操作符: b_expr1<==>b_expr2 或者b_expr1<=!=>b_expr2 分别表示表达式含义相等或不等。
- 推理操作符: b_expr1 = => b_expr2 或者b_expr2 <==b_expr1 。
方法规格
- 前置条件:requires P1||P2;对方法输入参数的限制。
- 后置条件:ensures P; 对方法执行结果的限制。
- 副作用范围限定: assignable 代表可赋值, modifiable 代表可修改
- pure方法:纯粹访问性的方法,即不会对对象的状态进行任何改变,也不需要提供输入参数。
- 方法的异常行为:normal_behavior, also, exceptional_behavior, signals (***Exception e) b_expr, signals_only。
类型规格
- 不变式invariant,要求在所有可见状态下都必须满足的特性。
- 状态变化约束constraint,来对前序可见状态和当前可见状态的关系进行约束。
(二)JML应用工具链情况
可以通过开源的JML编译器,比如OpenJML,编译含有JML标记的代码。通过加入不同的选项可以实现不同的功能。
- -check 选项可以对生成的类文件进行JML规范检查,检查是否含有语法错误;
- -esc 选项可以对程序代码进行静态检查(不依赖JML),检查程序中的潜在问题;
- -rac 选项可以对程序代码进行运行时检查,生成一个Java类文件测试的框架,实现对代码的自动化测试。
JMLdoc,与javadoc工具相似,不同的是它在生成的Html格式文档中包含JML规范;
JMLUnitNG,可以根据JML生成一个Java类文件测试的框架,结合OpenJML的-rac运行时检查选项,实现对代码的自动化测试。
二、部署SML Solver进行验证
鉴于时间关系以及安装使用问题PASS掉OnO
三、部署JMLUnitNG并针对Graph接口的实现进行测试
JMLUnitNG是JMLUnit的一个基于TestNG的继承者,而TestNG(Test Next Gerneration)是一个开源自动化测试框架。
JMLUnitNG的部署方法来自一个简单的测试用例(使用讨论区大佬的测试用例会疯狂failed让我措手不及TAT)
运行结果如下:
[TestNG] Running:
Command line suite
Failed: racEnabled()
Passed: constructor Testoo()
Passed: static subtest(-2147483648, -2147483648)
Failed: static subtest(0, -2147483648)
Failed: static subtest(2147483647, -2147483648)
Passed: static subtest(-2147483648, 0)
Passed: static subtest(0, 0)
Passed: static subtest(2147483647, 0)
Failed: static subtest(-2147483648, 2147483647)
Passed: static subtest(0, 2147483647)
Passed: static subtest(2147483647, 2147483647)
Passed: static main(null)
Passed: static main({})
===============================================
Command line suite
Total tests run: 13, Failures: 1, Skips: 0
===============================================
根据测试结果可知,JMLUnitNG主要是针对临界值(边缘条件)进行测试,例如int型的数据生成+-2147483647和0来测试,对于Object及子类,生成null以及其类型的空集来进行测试。
四、架构设计
(一)第一次作业
各个接口设计就依照指导书给的jml规格来写,没有什么改变。
(二)第二次作业
依旧是按照指导书给的接口来实现,同时规格设计实现最短路径时,添加了一些其他的方法来分工实现结果,Graph类可以直接继承第一次作业的Container类,但是出于懒我就直接ctrl+c、ctrl+v了。
(三)第三次作业
这次作业的重点在于重复性极强,因此可以沿用OO面向对象设计的思想,建造一个总的方法,然后其他的方法继承复用即可。无论是计算transfer还是price还是Unpleasant,总的思想都是拆点法改变权值,在求最短权值和的过程。因此这一次将第二次作业里的Floyd类单独拿出来,方便多次调用,同时给每个Node建造了一个类,进行拆点,具体拆法可见讨论区大佬的分享(大佬真是tql)
这里需要注意的是,每一次addpath以及removepath和removepathid时,都要对使用的总的点的list和存储权值边的list进行更替,否则一不小心就会出错。
五、出现bug以及修复情况
(一)第一次作业
第一次作业只是简单的计算,需要注意的是最好引入hashmap,存储当前某个节点出现了多少次,查询时利用hashmap的函数输出结果。防止遍历时多次调用使评测机的时间过长出现tle的情况,无bug。
(二)第二次作业
这里出现的bug比较智障,在对integer型的数据进行比较时,一定要用equals而不是==(……)这是一个很严肃的问题,一定要认真记下来。
(三)第三次作业
重点问题都出现在remove上面,在每一次remove时,如何处理点集和边权集,需要认真地思考再进行设计,否则就会出现爆C情况。
六、心得与体会
Jml代码规格能够对对代码的功能进行一定的规范,在团队合作中代码规格是必不可少的存在,一方面提前写好规格可以保证其功能的正确性,避免了每个人因为不同的逻辑思路导致理解不一致等问题,同时也优化了代码风格;另一方面逻辑清晰明了的规格增加了代码的易读性,便于出现问题时进行维护。
OO作业一直写到了第三个单元,对于面向对象思想的掌握相比于第一次来说已经提升了很多,掌握了jml规格设计后,在代码里的作用真的非常的大,对每一个函数的功能都有了掌握,在实现方式上不限制,体现了Java语言的灵活性。