OO第三单元作业总结
关于JML语言的梳理
1. JML 简介
Java Modell Language, 一种用于规范Java程序行为的行为接口规范语言。
本质是一种契约式设计,为了更好的工程化实现。其优势在于通过已知条件中的不变式,可以直接编写测试验证程序的正确性。
2. JML 语法
方法规格
-
前置条件
requires P;
-
副作用范围限定
assignable v
ormodifiable v
列出能够修改的类成员属性。若无,则为
\nothing
。 -
后置条件
ensures
类型规格
-
不变式
invariant P
要求在所有可见状态下都必须满足的特性。
-
状态变化约束
constraint
对前序可见状态和当前可见状态的关系进行约束。
原子表达式
\result
表示一个非void类型的方法执行所获得的结果,即方法执行后的返回值。\old
用来表示一个表达式expr在相应方法执行前的取值。\not_assigned
用来表示括号中的变量是否在方法执行过程中被赋值。如果没有被赋值,返回为true
,否则返回false
。
量化表达式
\forall
全称量词修饰的表达式\exists
存在量词修饰的表达式\sum
返回给定范围内的表达式的和- ...
3. JML 工具链
-
OpenJML:可以检查JML文档规格语法,其中SMT Solver可以验证代码是否符合规格
-
JMLUnit / JMLUnitNG:用于进行单元测试,可编写和可重复运行的自动化测试
部署JUnit和JMLUnitNG
JUnit
JUnit的使用比较基础。使用起来较为方便,相较于JMLUnitNG和OpenJML而言,配置也很简单,直接在IDEA里下载即可。是三次作业中验证程序正确性的有力工具。
对MyGroup类编写的的基本测试如下:
测试结果如下:
从Coverage可以看出,我的测试对于MyGroup
的代码覆盖度分别达到了81%,已经可以几乎覆盖到所有执行分支。
JMLUnitNG
在cmd中输入以下命令可以得出构造的数据
java -jar jmlunitng-1_4.jar test\MyGroup.java
将其修改成二元运算即可在相应的目录下得到一些生成数据的java文件:
java -cp jmlunitng-1_4.jar test.MyGroup_JML_Test
架构设计分析
第九次作业
主要难点在MyNetwork
中的isCircle
函数,实现连通性查询。在最开始,没有仔细思考实现的情况下,采用递归dfs。算法效率很差。后改用非递归bfs。
容器的选择在本次作业中也有很重要的地位。在查找效率上,HashMap优于ArrayList。故,为了快速查询,将people存为HashMap
第十次作业
添加了MyGroup
类,其中几个查询方法在算法上的优化给我制造了一定障碍。
为了避免o(n^2)复杂度,主要采取预先存储查询方法中涉及的属性(直接在addPerson/addRelation时更新属性),而后在方法中直接调用。
自行添加/维护的属性有:
-
ageSum(组中年龄总和)& ageSqrSum(组中年龄平方和):
代入公式,用于计算平均年龄和年龄方差。
-
relationSum & valueSum:
需要在addPerson和addRelation时,遍历检查,判断是否增加这两个属性。查询时直接返回。
-
conflictSum:
每次addPerson取异或,查询时直接返回。
第十一次作业
UML类图如下:
本次作业对于我的难点在仍然在算法的选择和优化,其难度集中在MyNetwork中的
-
queryBlockSum() 求连通分量个数
此处采用了并查集。需要单写一个并查集类,并在addRelation时进行并查集的维护。直接返回连通个数即可。
-
queryMinPath() 寻找最短路径
-
关于边集的存储:
新建Edge类,存放
。而后,以HashMap 存储边集(其中,键为人的id,值的ArrayList存放邻接边)。 -
采用Dijkstra算法,并使用优先队列PriorityQueue。
-
-
queryStrongLinked() 判断是否强连通(任意两顶点间存在两条不同的路径)
在这块我尝试了多种算法,包括:
- 直接一次DFS找环(有bug,效率低)
- Tarjan(算法本身理解有误导致bug)
- 两次BFS(有bug,因为将第一次BFS的路径删除时会影响第二次寻路)
- 暴力枚举,即遍历people中的人,如果与两个端点都连通,就将该人去掉再此搜索是否联通
- 要解决两人邻接成环且不与第三人连通的情况:增加queryLongPath方法
- 排除某人判断联通:复写isCircle。
Bug分析
第九次作业由于JML理解正确性问题导致强测0分。主要问题出在本次作业时没有进行方法的覆盖性测试,对于JUnit使用还完全不熟悉。
第十次作业对于MyGroup中几个算法复杂度把控不到位,开始没有想到自行添加和维护属性;同时容器选择上效率较低,两问题综合导致大片CTLE。
心得体会
通过这三次作业,我对JML这种规格化语言有了基本的了解。其中,感触最深的还是利用其的测试环节以及工程化实现的能力培养。
本单元第一次作业感到和上一单元比难度降低,于是没有进行充分测试就提交,导致代码正确性上出现问题,实在很不应该。绝对不能惰于测试!!!
后面关于算法的实现,虽然给我造成很多障碍(也充分复习了一波数据结构),但我目前的理解是并不一定去追逐多么高级、所谓高效的算法。比如第十一次中并不一定要写Tarjan,反而直接暴力枚举就可以解决。正确的做法应该是多从代码本身的架构上进行分析,通过自身代码结构上的优化和调整,更加轻松的解决问题。