OO第三单元总结

OO第三单元作业总结

关于JML语言的梳理

1. JML 简介

Java Modell Language, 一种用于规范Java程序行为的行为接口规范语言

本质是一种契约式设计,为了更好的工程化实现。其优势在于通过已知条件中的不变式,可以直接编写测试验证程序的正确性。

2. JML 语法

OO第三单元总结_第1张图片

方法规格

  • 前置条件requires P;

  • 副作用范围限定assignable v or modifiable v

    列出能够修改的类成员属性。若无,则为\nothing

  • 后置条件ensures

类型规格

  • 不变式invariant P

    要求在所有可见状态下都必须满足的特性。

  • 状态变化约束constraint

    对前序可见状态和当前可见状态的关系进行约束。

原子表达式

  • \result表示一个非void类型的方法执行所获得的结果,即方法执行后的返回值
  • \old用来表示一个表达式expr在相应方法执行前的取值。
  • \not_assigned用来表示括号中的变量是否在方法执行过程中被赋值。如果没有被赋值,返回为true,否则返回false

量化表达式

  • \forall 全称量词修饰的表达式
  • \exists 存在量词修饰的表达式
  • \sum 返回给定范围内的表达式的和
  • ...

3. JML 工具链

  1. OpenJML:可以检查JML文档规格语法,其中SMT Solver可以验证代码是否符合规格

  2. JMLUnit / JMLUnitNG:用于进行单元测试,可编写和可重复运行的自动化测试

部署JUnit和JMLUnitNG

JUnit

JUnit的使用比较基础。使用起来较为方便,相较于JMLUnitNG和OpenJML而言,配置也很简单,直接在IDEA里下载即可。是三次作业中验证程序正确性的有力工具。

对MyGroup类编写的的基本测试如下:

OO第三单元总结_第2张图片

测试结果如下:

OO第三单元总结_第3张图片

OO第三单元总结_第4张图片

从Coverage可以看出,我的测试对于MyGroup的代码覆盖度分别达到了81%,已经可以几乎覆盖到所有执行分支。

JMLUnitNG

在cmd中输入以下命令可以得出构造的数据

java -jar jmlunitng-1_4.jar test\MyGroup.java

将其修改成二元运算即可在相应的目录下得到一些生成数据的java文件:

OO第三单元总结_第5张图片

java -cp jmlunitng-1_4.jar test.MyGroup_JML_Test

image-20200523140425585

OO第三单元总结_第6张图片

架构设计分析

第九次作业

主要难点在MyNetwork中的isCircle 函数,实现连通性查询。在最开始,没有仔细思考实现的情况下,采用递归dfs。算法效率很差。后改用非递归bfs。

容器的选择在本次作业中也有很重要的地位。在查找效率上,HashMap优于ArrayList。故,为了快速查询,将people存为HashMap(其中键为人的id);熟人acquaintance存为HashMap(其中,键为熟人,值为熟人间的value)。

第十次作业

添加了MyGroup类,其中几个查询方法在算法上的优化给我制造了一定障碍。

为了避免o(n^2)复杂度,主要采取预先存储查询方法中涉及的属性(直接在addPerson/addRelation时更新属性),而后在方法中直接调用。

自行添加/维护的属性有:

  • ageSum(组中年龄总和)& ageSqrSum(组中年龄平方和):

    代入公式,用于计算平均年龄和年龄方差。

  • relationSum & valueSum:

    需要在addPerson和addRelation时,遍历检查,判断是否增加这两个属性。查询时直接返回。

  • conflictSum:

    每次addPerson取异或,查询时直接返回。

第十一次作业

UML类图如下:

OO第三单元总结_第7张图片

本次作业对于我的难点在仍然在算法的选择和优化,其难度集中在MyNetwork中的

  • queryBlockSum() 求连通分量个数

    此处采用了并查集。需要单写一个并查集类,并在addRelation时进行并查集的维护。直接返回连通个数即可。

  • queryMinPath() 寻找最短路径

    • 关于边集的存储:

      新建Edge类,存放。而后,以HashMap存储边集(其中,键为人的id,值的ArrayList存放邻接边)。

    • 采用Dijkstra算法,并使用优先队列PriorityQueue。

  • queryStrongLinked() 判断是否强连通(任意两顶点间存在两条不同的路径)

    在这块我尝试了多种算法,包括:

    1. 直接一次DFS找环(有bug,效率低)
    2. Tarjan(算法本身理解有误导致bug)
    3. 两次BFS(有bug,因为将第一次BFS的路径删除时会影响第二次寻路)
    4. 暴力枚举,即遍历people中的人,如果与两个端点都连通,就将该人去掉再此搜索是否联通
      • 要解决两人邻接成环且不与第三人连通的情况:增加queryLongPath方法
      • 排除某人判断联通:复写isCircle。

Bug分析

第九次作业由于JML理解正确性问题导致强测0分。主要问题出在本次作业时没有进行方法的覆盖性测试,对于JUnit使用还完全不熟悉。

第十次作业对于MyGroup中几个算法复杂度把控不到位,开始没有想到自行添加和维护属性;同时容器选择上效率较低,两问题综合导致大片CTLE。

心得体会

通过这三次作业,我对JML这种规格化语言有了基本的了解。其中,感触最深的还是利用其的测试环节以及工程化实现的能力培养。

本单元第一次作业感到和上一单元比难度降低,于是没有进行充分测试就提交,导致代码正确性上出现问题,实在很不应该。绝对不能惰于测试!!!

后面关于算法的实现,虽然给我造成很多障碍(也充分复习了一波数据结构),但我目前的理解是并不一定去追逐多么高级、所谓高效的算法。比如第十一次中并不一定要写Tarjan,反而直接暴力枚举就可以解决。正确的做法应该是多从代码本身的架构上进行分析,通过自身代码结构上的优化和调整,更加轻松的解决问题。

你可能感兴趣的:(OO第三单元总结)