北航oo第三单元总结

JML理论基础,应用工具链情况

理论基础

JML是用于对Java程序进行规格化设计的一种表示语言。从理论角度,JML要求方法和数据类型在特定的时刻满足特定的谓词逻辑,因此JML语言没有二义性。JML主要由方法规格和类型规格组成。

方法规格

  • 前置条件

    • 调用者保证满足前置条件

  • 后置条件

    • 方法实现者保证后置条件满足

  • 副作用范围限定

    • 副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来 影响。

通过上述方法规格的描述可以形成实现者和调用者的一种契约。

除此之外,使用normal_behavior和exceptional_behavior对正常功能行为和异常行为进行区分,这样既能使方法的适应条件更广,鲁棒性增强。也规定了异常行为发生时要抛出何种异常,便于规格层面的设计。

类型规格

方法规格更倾向于规定一个方法如何实现,类型规格则倾向于规定一个类所管理的数据类型是否变化,做何种变化,这是由这个类管理的数据本身的特点决定的。两者是相互补充的关系。类型规格主要分为不变式和状态变化约束。

不变式

不变式(invariant)是要求在所有可见状态下都必须满足的特性。JML level0手册中对可见状态进行了严谨的定义。通俗理解就是方法执行前后不变式需要得到满足,不操作这个对象时,不变式应该保持满足的状态。

状态变化约束

对象的状态在变化时往往也许满足一些约束,这种约束本质上也是一种不变式。通俗理解,状态变化约束常常规定了数据在变化前后满足什么关系,即数据如何变化。

类型规格与方法规格是从不同的角度进行规格化描述。由于方法规格需要遵从类型规格,有的时候一个类的实现满足所有方法规格即可满足所有功能,类型规格显得不必要。但类型规格对数据类型做一个总体的贯穿整个类的规定,有助于理解和实现。

JML的语法、关键字本文不再赘述。

JML的应用工具链

  • OpenJML:OpenJML可以对含有JML的代码进行编译 ,并提供不同类型的选项进行检查。-esc可以静态检查可能出现的隐藏bug,-rac是运行时检查,-check则可以检查代码是否符合JML规格要求。

  • JMLUnitNG:可以根据代码中所写JML规范自动生成测试框架进行自动化测试

JMLUnitNG

在折腾完一堆报错之后,终于能够使用JMLUnitNG了。(助教大大们其实可以出个教程)

  1. jmlunitng会对极端数据进行测试,如null,int的最大最小值等

  2. 每个方法是单独测试的,因此一些上下文不能很好的自动生成,导致测试failed(可能需要增加更多的不变式?但这些不变式就与我的实现紧密相关了...)

  3. age的范围理应在Person的构造函数上加以限制,但没学过构造函数的规格(不会讲过吧???),所以age的测范围就是int的范围,failed。

  4. 真的不好用,我往下写了。

架构设计

由于三次作业都是根据JML来补充完善代码,因此三次作业架构设计主要体现在数据结构的选取、未定义的方法的设计、算法设计、查询内容的显式存储。最后介绍模型构建策略。

数据结构的选取

  1. 关联数据使用HashMap

    • 如Person中acquaintance和value存在对应关系,且使用时需要根据acquaintance查询value,比较自然的想到用Hashmap

  2. isCircle的实现使用并查集。为此,我新建了一个类,包装了一个Hashmap,留下了find,put,Union,isLinked接口(对其中的find进行了优化,在查询时能缩短与根的距离)。并且在第三次作业中加入了block元素,用来统计联通分量的个数。北航oo第三单元总结_第1张图片

  3. 为了在加入Relation时能够更新Group的relationSum和valueSum,在Person类中增加储存所属Group的ArrayList,以便更新Person所属Group的relationSum和valueSum。

未定义方法的设计

  1. Person中包括link,用于与其他的Person对象连接;addedToGroup,用于更新当前Person所属的Group信息;delFromGroup,用于移除当前Person对象所属的某一个Group。getAcquaintance,用于Network中的某些算法。(出于效率,直接将acquaintance对象返回,由调用者保证不对acquaintance进行修改)

    北航oo第三单元总结_第2张图片

  2. Group中包括getPeopleSum,用于控制Group内人数小于1111;updateRelation,用于addRelation时更新 relationSum和valueSum

    北航oo第三单元总结_第3张图片

  3. Network中为实现算法而设置的private方法,如Dijkstra算法等

查询内容的显式存储

  1. Network中只要求存people,但由于有getPerson方法。故将id与Person构成Hashmap存储

  2. Group中加入数据元素conflictSum ageSum relationSum valueSum等元素,对查询内容显式存储,提高性能。由于异或操作,加法都可逆,所以在Group中删除Person也没有问题。其中conflictSum 和ageSum仅涉及在Group中加入Person时更新,而relationSum和valueSum在加入新Person和加入Relation时都要更新。

  3. isCircle的实现采用并查集的数据结构(上文提到的UnionFind),其思路也是对查询内容进行同步更新。

算法设计

  1. 连通性采用并查集实现

  2. 最短路径采用Dijkstra实现

  3. 点双联通的实现采用了dfs寻找环路的方法。由于此种方法需要遍历所有环路,故造成了超时。修改思路见bug修复。

模型构建策略

  • 首先浏览整个类,确定数据类型,然后逐一实现每个方法

  • 按照Person->Group->Network的顺序逐层实现

  • 实现过程中可能涉及新方法的设计与添加

  • 对于复杂的方法进行拆分,合理增加类和方法

  • 除简单方法外,每实现一个算法进行覆盖性的验证

  • 完成所有方法后对顶层模块进行测试和验证

bug和修复情况

三次作业唯一的问题是最后一次作业出现超时的情况,涉及queryStrongLinked方法。在原先设计中,错误地认为dfs遍历圈的复杂度即是通常dfs的复杂度,造成复杂度的误判,在测试时由于时间紧迫也没有设计可能超时的样例。bug修复采用第一次判断连通后,枚举去掉所有点再次bfs判断是否仍然联通的方法。

心得体会

  1. 我理解的规格以谓词逻辑为依托,无二义性,对程序运行的特定时间点数据的状态,调用的前提条件,返回值,抛出异常等都进行了规定。

  2. 显然我们只学习了level0的基础规格。如规格如何规定对异常的处理等内容还未涉及。尽管如此,还是能感受到JML语言具有很强的表达能力和严谨性。

  3. 从代码抽象出规格比较难写,比如课上实验有阅读代码的障碍。从规格到实现相对而言好写,本单元作业中较困难的是对算法复杂度的控制。

  4. 有时规格提供了实现的框架,参照规格可直接实现,这种情况出错的概率较小,测试程序的思路与实现的思路几乎无异。有时规格与实现相差较大,需要自行考虑实现的算法细节,这时,规格提供了一种很好的验证思路。这种规格与实现差别较大的方法的实现更容易出错,需要重点测试。如isCircle和queryStrongLinked,在规格中都有array[],但实际上并不需要实现这个array[]。我将其理解为JML为了严谨表达无二义性而带来的理解困难的负面影响。

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