OO第三单元总结
OO第三单元总结
JML语言的理论基础、应用工具链情况
JML基础语法
- requires : requires a ,即为前置条件语句,在方法满足条件的情况下实现之后的功能。
- ensure:ensure a ,即为后置条件语句,在满足ensure对应require前置条件方法执行后对各个容器变量改变的要求。
- assign:即标明方法执行后的副作用,指明对无关容器变量的影响。
- public normal_behavior:正常处理部分。
- public exceptional_behavior:抛出异常部分。
- \forall:全称量词,具体实现采用遍历容器的for代码实现,对于单层遍历,使用Arraylist遍历较遍历Hashmap复杂度更低。
- \exists:存在量词,具体为实现选取容器中的特定元素,使用key与value一一映射的Hashmap即可。
- \old:old a ,表示方法使用改变a之前的值,常见于表示增加删除容器元素后容器的容量变化。
工具链应用
- Junit:单元检测
- OpenJml:JML语法检测
- JMLUnitNG:生成测试样例,主要为爆破边界。
部署SMT Solver
折腾了半天jdk,魔改demo,还是会有各种奇怪的报错,遂quit
。根据部署成功的同学的介绍,由于SMT在遇到\forall, \exists等复杂逻辑语句时会报错,对我们大学的OO单元作业用处并不太大。
部署JMLUnitNG/JMLUnit
JMLUnitNG
总算配成功了orz
根据J哥的博客,部署好文件树后,输入一下代码来生成对MyGroup的测试,
java -jar jmlunitng.jar jmlunit/MyGroup.java
javac -cp jmlunitng.jar jmlunit/*.java
java -jar openjml.jar -rac jmlunit/MyGroup.java
java -cp jmlunitng.jar jmlunit.MyGroup_JML_Test
最后得到如下反馈
[TestNG] Running:
Command line suite
Passed: racEnabled()
Passed: constructor MyGroup(-2147483648)
Passed: constructor MyGroup(0)
Passed: constructor MyGroup(2147483647)
Failed: <<MyGroup@4ebba42a>>.addPerson(null)
Failed: <<MyGroup@467eca23>>.addPerson(null)
Failed: <<MyGroup@66133adc>>.delPerson(null)
Failed: <<MyGroup@287534f5>>.delPerson(null)
Failed: <<MyGroup@ba4d4a7e>>.delPerson(null)
Passed:<<MyGroup@5c0e39b3>>.equals(null)
Passed:<<MyGroup@d793cb21>>.equals(null)
Passed:<<MyGroup@508a4bba>>.equals(null)
Passed: <<MyGroup@a2b1398c>>.equals(java.lang.Object@574caa3f)
Passed: <<MyGroup@63bcaa71>>.equals(java.lang.Object@64cee07)
Passed: <<MyGroup@2d7f89e3>>.equals(java.lang.Object@1761e840)
Passed: <<MyGroup@23fea612>>.getAgeMean()
Passed: <<MyGroup@b45e2fa7>>.getAgeMean()
Passed: <<MyGroup@7a645219>>.getAgeMean()
Passed: <<MyGroup@685b3ad2>>.getAgeVar()
Passed: <<MyGroup@3536c874>>.getAgeVar()
Passed: <<MyGroup@c2f309a6>>.getAgeVar()
Passed: <<MyGroup@ab27c89c>>.getConflictSum()
Passed: <<MyGroup@1ef7015a>>.getConflictSum()
Passed: <<MyGroup@74b8b58f>>.getConflictSum()
Passed: <<MyGroup@1c56bc89>>.getId()
Passed: <<MyGroup@445a8ca1>>.getId()
Passed: <<MyGroup@38b6c32a>>.getId()
Passed: <<MyGroup@3ae8b12a>>.getRelationSum()
Passed: <<MyGroup@3e2851ac>>.getRelationSum()
Passed: <<MyGroup@a93c83c7>>.getValueSum()
Passed: <<MyGroup@721f0239>>.getValueSum()
Passed: <<MyGroup@6ab01c56>>.getValueSum()
Failed: <<MyGroup@1f6ff78c>>.hasPerson(null)
Failed: <<MyGroup@c81f7a90>>.hasPerson(null)
Failed: <<MyGroup@48e75ac0>>.hasPerson(null)
Passed: <<MyGroup@5e876f2a>>.updatePerson(null)
Passed: <<MyGroup@6f91e371>>.updatePerson(null)
Passed: <<MyGroup@b274b4d7>>.updatePerson(null)
Passed: <<MyGroup@13fa6a7b>>.downloadPerson(null)
Passed: <<MyGroup@49a3b226>>.downloadPerson(null)
Passed: <<MyGroup@6821a38b>>.downloadPerson(null)
Failed: <<MyGroup@21ab83e5>>.updateLink(null, null)
Failed: <<MyGroup@48e2ff12>>.updateLink(null, null)
Failed: <<MyGroup@67a8815e>>.updateLink(null, null)
===============================================
Command line suite
Total tests run: 44, Failures: 11, Skips: 0
===============================================
可以发现JUMLUnitNG生成的测试样例大部分为null,INT_MAX,0,等等极其边界条件,针对本单元作业没有太大作用。
架构设计与模型构建策略
第九次作业
由于是新单元的第一次作业,整体结构直接根据官方的接口,建立MyPerson,Mynetwork三个大类即可,接口的实现绝大部分可以照搬JML翻译,其中需要考量的有两点:iscircle的算法选择,与不同属性的容器选择。
- isclrcle:采用BFS:由于DFS存在爆栈的可能,担心被某些
饥渴的狼人🔪中,遂放弃。 - 容器选择:用于Hashmap有红黑树等的隐形优化,除去要求且仅要求每次遍历的数据使用Arraylist外,其余的使用Hashmap存储。
第十次作业
第二次作业相较于第一次作业主要新加Group大类,并根据Goup的成员实现group中所有成员年龄平均值,方差等方法,值得思考的同第九次作业有两点:Group容器的选择;qgrs,qgvs如何优化二重遍历,避免O(n^2)。
- Group容器的构造:由于Group中的方法对遍历group全体成员与选取单个成员都有较多的要求,因此我选择分别建立
private HashMap<Integer, Person> people1 = new HashMap<>(8192)
与private ArrayList<Person> people2 = new ArrayList<>(8192)
两个容器来存取group中的person信息,在选取判断特定成员时,使用Hashamap,遍历全体时使用Arraylist,能有效缩短由于Hashmap预留的空间而导致遍历全部的key-value时间开销过大。并根据Group存储person的最大数量限制,为两个容器开设8192的初始容量,避免后续添加person时单独开设空间的时间浪费。 - 二重遍历的优化:采用及时建立缓冲的策略,在addRelation与addtoGroup的过程中及时更新Group中的relationSum与valueSum。
第十一次作业
第三次作业的难点集中在三个方法的实现qmp, qsl, qbs,主要的设计与时间投入也集中在这块。
- qmp:采用Dijsktra + Priorityqueue优化的算法。
- qsl:根据JML的ensure分析,先通过DFS遍历得到所有的路径,if:不存在节点个数大于等于3的路径,return false;else:选取该路径,遍历该路径上除去首尾的所有点,遮掩该点后再次DFS,判断有无第二条路径。
- qbs:采用并查集 + 路径压缩的算法,主要代码如下,分别进行查与并的功能。
public Person findPa(Person son) {
if (!pre.get(son).equals(son)) {
pre.replace(son, findPa(pre.get(son)));
}
return pre.get(son);
}
void merge(Person son1,Person son2) {
Person papa1 = findPa(son1);
Person papa2 = findPa(son2);
if (!papa1.equals(papa2)) {
pre.replace(papa2, papa1);
}
}
bug和修复情况
BUG分析
- 第一次由于首次接触JML,书写时照搬JML语句
people.get(i) == person
,而没使用重写的equal方法。 - 第三次中用手搓DIJ + PriorityQueue
荣幸获得边界tle,与同学代码比对后发现具体实现没有太大差异,最后发现是Person.acq使用hashmap时初始化大小过大,导致qmp遍历时耗时过多,导致tle。emmm,出这种错蛮无语的,当然也说明测试时极端数据没有构造的太好。
HACK策略
- 第一次和第二次都没🔪中。
- 第三次主要针对qmp构造极限数目的点连乘一条直线,针对qsl的多重深搜构造多环图,Hack刀了3个小伙伴。
阐述对规格撰写和理解上的心得体会
第三单元感想体会
自我感觉第三单元的整体难度较第二单元有所下降,主要重点集中在对JML的理解,即该方法实现的目的是什么来思考,而不是机械的翻译。第三次作业由于涉及性能要求,考察了一定算法知识,思考算法层面的时间占据了绝大部分。另外一点便是由于JML已经给出了大部分的代码框架,且群佬除我,因此有一点微小的错误都会造成强测直接被专业团队愉快送走,对我们自动化测试与极端测试数据的构造都有了极大要求,all in all ,对拍真香。