OO第三单元总结
梳理JML语言的理论基础、应用工具链情况
理论基础
JML是用来对JAVA程序进行规格化描述的表示语言。
-
规格变量声明:如
//@public static model non_null int []elements
。可区分静态或实例,non_null表明对象引用不能为null -
前置条件:由requires子句定义。
-
副作用:assignable限定能够修改的类成员属性。
/*@ pure @ */
表示纯粹的查询方法。 -
后置条件:由ensures子句定义。
-
不变式:可以由invariant子句定义,是可见状态下都需要满足的特性。constraint用来定义状态变化约束,本质上也是不变式,特别地,是对前序可见状态和当前可见状态的约束。
-
原子表达式若干:\result关键字表示返回结果;\old(expr)表示方法执行前expr的取值等
-
量化表达式若干:\forall、\exists、\sum、\max等
应用工具链
-
OpenJML: 基本功能是对JML注释进行完整性检查,
-check
参数可以指定类型检查。而要对内容进行检查则要使用-esc
参数,使用-rac
进行运行时检查。 -
JMLUnitNG: 自动生成测试样例进行测试。
部署JMLUnitNG/JMLUnit,针对Group接口的实现自动生成测试用例,并结合规格对生成的测试用例和数据进行简要分析
JMLUnit
使用java -jar openjml\openjml.jar -exec openjml\Solvers-windows\z3-4.7.1.exe -esc -dir src
后听说这个可能是三目运算符的原因,改为
/*@ public normal_behavior
@ requires people.length == 0;
@ assignable \nothing;
@ ensures \result == 0;
@ also
@ public normal_behavior
@ requires people.length != 0;
@ assignable \nothing;
@ ensures \result == ((\sum int i; 0 <= i && i < people.length; people[i].getAge()) / people.length);
@*/
该错误消失。但是出现了大量如NOT IMPLEMENTED: Not yet supported feature in converting BasicPrograms to SMTLIB: JML Quantified expression using \sum
的警告。似乎是因为SMT尚不支持\sum等关键字。
JMLUnitNG
这个过程是比较痛苦的很容易出现各种问题,看了一些其他同学/学长学姐的博客才达到目前这个踉踉跄跄的结果。
下载JMLUnitNG后,将其放置于之前的openjml文件夹。
JML放在接口处似乎是不能被测试的,所以把规格粘贴到MyGroup里,然后为了一会能编译把所有代码提出来放入一个package(我这里是Src),并在所有代码中添加package Src;
在import com.
前加上Src.
。
生成测试文件java -jar jmlunitng.jar Src
改正为0 <= i && i < groups.length
消失,但出现了一些新问题。
警告: C:\Program Files\Java\jre1.8.0_241\lib\rt.jar(java/io/FilterOutputStream.class): 主版本 52 比 51 新, 此编译器支持 最新的主版本。
建议升级此编译器。
C:\Users\Zars19\Downloads\openjml\src\MyGroup.java:24: 错误: 类型变量数目错误; 需要2
peopleMap = new HashMap<>();
C:\Users\Zars19\Downloads\openjml\src\MyGroup.java:89: 错误: 需要数组, 但找到ArrayList<Person>
@ people[i].isLinked(people[j]); people[i].queryValue(people[j])));
^
C:\Users\Zars19\Downloads\openjml\src\MyGroup.java:89: 错误: An identifier with private visibility may not be used in a ensures clause with public visibility
@ people[i].isLinked(people[j]); people[i].queryValue(people[j])));
可以通过1. 把容器类型写全 2. 把和JML规格变量同名的变量改名
去除。
编译javac -cp jmlunitng.jar Src/*.java
java -jar openjml.jar -rac Src/MyGroup.java
测试java -cp jmlunitng.jar Src.MyGroup_JML_Test
C:\Users\Zars19\Downloads\openjml>java -cp jmlunitng.jar Src.MyGroup_JML_Test
[TestNG] Running:
Command line suite
Failed: racEnabled()
Passed: constructor MyGroup(-2147483648)
Passed: constructor MyGroup(0)
Passed: constructor MyGroup(2147483647)
Failed: <<Src.MyGroup@64cee07>>.addPerson(null)
Failed: <<Src.MyGroup@1761e840>>.(null)
Failed: <<Src.MyGroup@5ecddf8f>>.addPerson(null)
Failed: <<Src.MyGroup@3f102e87>>.delPerson(null)
Failed: <<Src.MyGroup@27abe2cd>>.delPerson(null)
Failed: <<Src.MyGroup@5f5a92bb>>.delPerson(null)
Passed: <<Src.MyGroup@6fdb1f78>>.equals(null)
Passed: <<Src.MyGroup@51016012>>.equals(null)
Passed: <<Src.MyGroup@29444d75>>.equals(null)
Passed: <<Src.MyGroup@2280cdac>>.equals(java.lang.Object@1517365b)
Passed: <<Src.MyGroup@4fccd51b>>.equals(java.lang.Object@44e81672)
Passed: <<Src.MyGroup@60215eee>>.equals(java.lang.Object@4ca8195f)
Passed: <<Src.MyGroup@65e579dc>>.getAgeMean()
Passed: <<Src.MyGroup@61baa894>>.getAgeMean()
Passed: <<Src.MyGroup@768debd>>.getAgeMean()
Passed: <<Src.MyGroup@490d6c15>>.getAgeVar()
Passed: <<Src.MyGroup@7d4793a8>>.getAgeVar()
Passed: <<Src.MyGroup@449b2d27>>.getAgeVar()
Passed: <<Src.MyGroup@5479e3f>>.getConflictSum()
Passed: <<Src.MyGroup@27082746>>.getConflictSum()
Passed: <<Src.MyGroup@66133adc>>.getConflictSum()
Passed: <<Src.MyGroup@7bfcd12c>>.getId()
Passed: <<Src.MyGroup@42f30e0a>>.getId()
Passed: <<Src.MyGroup@24273305>>.getId()
Passed: <<Src.MyGroup@46f5f779>>.getPeopleSum()
Passed: <<Src.MyGroup@1c2c22f3>>.getPeopleSum()
Passed: <<Src.MyGroup@18e8568>>.getPeopleSum()
Passed: <<Src.MyGroup@33e5ccce>>.getRelationSum()
Passed: <<Src.MyGroup@5a42bbf4>>.getRelationSum()
Passed: <<Src.MyGroup@270421f5>>.getRelationSum()
Passed: <<Src.MyGroup@52d455b8>>.getValueSum()
Passed: <<Src.MyGroup@4f4a7090>>.getValueSum()
Passed: <<Src.MyGroup@18ef96>>.getValueSum()
Failed: <<Src.MyGroup@6956de9>>.hasPerson(null)
Failed: <<Src.MyGroup@769c9116>>.hasPerson(null)
Failed: <<Src.MyGroup@6aceb1a5>>.hasPerson(null)
Passed: <<Src.MyGroup@2d6d8735>>.setRelationSum(-2147483648)
Passed: <<Src.MyGroup@ba4d54>>.setRelationSum(-2147483648)
Passed: <<Src.MyGroup@12bc6874>>.setRelationSum(-2147483648)
Passed: <<Src.MyGroup@de0a01f>>.setRelationSum(0)
Passed: <<Src.MyGroup@4c75cab9>>.setRelationSum(0)
Passed: <<Src.MyGroup@1ef7fe8e>>.setRelationSum(0)
Passed: <<Src.MyGroup@6f79caec>>.setRelationSum(2147483647)
Passed: <<Src.MyGroup@67117f44>>.setRelationSum(2147483647)
Passed: <<Src.MyGroup@5d3411d>>.setRelationSum(2147483647)
Passed: <<Src.MyGroup@2471cca7>>.setValueSum(-2147483648)
Passed: <<Src.MyGroup@5fe5c6f>>.setValueSum(-2147483648)
Passed: <<Src.MyGroup@6979e8cb>>.setValueSum(-2147483648)
Passed: <<Src.MyGroup@763d9750>>.setValueSum(0)
Passed: <<Src.MyGroup@5c0369c4>>.setValueSum(0)
Passed: <<Src.MyGroup@2be94b0f>>.setValueSum(0)
Passed: <<Src.MyGroup@d70c109>>.setValueSum(2147483647)
Passed: <<Src.MyGroup@17ed40e0>>.setValueSum(2147483647)
Passed: <<Src.MyGroup@50675690>>.setValueSum(2147483647)
===============================================
Command line suite
Total tests run: 58, Failures: 10, Skips: 0
===============================================
可以看到JMLUnitNG会着重测试一些边界情况(包括null),这里的Failures是规格中没有做出限制的addPerson和delPerson的null情况(但null应该其实不会影响实际测试),其他全部通过。
按照作业梳理自己的架构设计,特别分析自己的模型构建策略
架构设计大体上就是接口中给定的样子,贴UML可能大家都一样。
第九次作业
好像没什么可说的基本是在照JML模拟,连数组都僵硬到用了静态。isCircle的时候是用并查集。
第十次作业
这次欢快地将ArrayList和HashMap配合使用,判断contains或者查找会比较快,add也更方便。
Group里的一些查询是在addToGroup的时候处理的,比如异或和什么的,这样比较快。
第十一次作业
蛮狠的,这次出现了判断是否点双联通和求最短路的需求。
点双联通判断使用tarjan,然后特殊处理两点之间只有距离为1的一条路径的情况(是点双但这里是false因为规格中点不重复的两条路径一条至少点数为3)。
最短路用Dijkstra+堆优化(但是好像仍然可能看脸TLE)。
Group这次可以删除,不过还是可以向上次添加一样在删除时更新那些之后可能查询的值。还有一些小技巧什么的大家应该都知道了比如\(A\oplus B \oplus B=A\),并查集的时候更新联通块数云云。
按照作业分析代码实现的bug和修复情况
我错了,本单元作业在摸鱼。三次作业代码都写得很快,然后有bug。
既第九次作业强测没出问题也没有被hack之后,后两次作业每次出一个可在两行之内一次性修复的bug,全是读规格的问题。第十次我认为输入是必会满足其中一种requires后的条件的,没有管1111什么的(虽然迄今我也隐约觉得不满足JMLrequires的输入应该不保证运行结果)。第十一次作业addRelation直接复制signals后面的条件忽视了requires后面的id1 != id2
在添加自环的时候输出了er
@ requires !contains(id1) || !contains(id2) ||
@ (getPerson(id1).isLinked(getPerson(id2)) && id1 != id2);
@ signals (PersonIdNotFoundException e) !contains(id1) || !contains(id2);
@ signals (EqualRelationException e) contains(id1) && contains(id2) &&
@ getPerson(id1).isLinked(getPerson(id2));
@*/
虽然不知道是不是特意的但感谢OO课程组把这一档设在60分。
第十次作业在度五一假期没有hack。
第十一次作业随便生成了一点qsl和qmp的数据,hack成功6次,截至目前可以得分5.25(怀疑是不是其实有人不修bug)。其中有qsl和qmp的WrongAnswer bug,还有TLE的房友。(感觉房友们好像是算法上的问题多一点)
阐述对规格撰写和理解上的心得体会
是的写JML的感觉有点离散,填JML实验的那次对JML的语法其实还不太熟悉,感觉有一些比较白痴的地方有写错(还把空看少了)。不过写过几次作业后这个感觉好了很多(只不过还是会读错条件)。JML让人体会到一种新的思想,离程序设计艺术更进一步。