OO第三单元作业总结

梳理JML语言的理论基础、应用工具链情况

 JML语言是一种用于对Java程序进行规格化设计的表示语言,JML主要用于开展规格化设计、提高代码的可维护性。

  • 在开展规格化设计方面:给出某类、某函数的规格,就相当于在使用者和程序员中签订了一个协议,使用者知道某函数的功能、需要满足的条件,程序员在满足调用条件下实现相应功能,并且JML将模糊的自然语言化为严谨的逻辑语言,保证在理解的正确性方面没有二义性,下面从常用表达式、操作符、方法规格、类型规格这几部分对JML语言进行梳理。
    • 表达式:
      • 原子表达式:
        • \result,表示方法执行的返回值
        • \old(exp)表示exp在方法执行前的值
        • \not_assigned(a,b,c...)表示括号中的变量若在执行过程中被赋值则返回false,反之返回true
        • \not_modified(a,b,c...)限制括号内的变量在方法执行期间的取值未发生变化
        • \notnullelements(container)表示container中不会存储null
        • \type(type)返回type对应的类型
        • \type(expr)返回expr对应的准确类型
      • 量化表达式:
        • \forall表达式,全程量词,给定范围内每个都满足约束
        • \exists表达式,存在量词,给定范围内存在元素满足约束
        • \product,返回给定范围内的表达式的连乘
        • \sum,返回给定范围内表达式的和
        • \max返回给定范围内表达式的最大值
        • \min返回给定范围内表达式的最小值
        • \num_of返回指定变量中满足相应条件的取值个数
      • 集合表达式:集合构造表达式,在JML规格中构造局部的集合,明确其中可以包含的元素。
    • 操作符:
      • 子类型关系操作符E1<:E2
      • 等价关系操作符b_expr1<==>b_expr2
      • 推理操作符b_expr1==>b_expr2
      • 变量引用操作符,其中\nothing指示一个空集;\everything指示一个全集。
    • 方法规格:十分重要,决定了函数的方方面面。pure修饰的方法表示不会对对象的状态进行改变、不需要提供参数、不需要描述前置条件、不会有任何副作用,且执行一定会正常结束,且在其他方法规格中可以直接引用。
      • 前置条件:requires P,调用者确保P条件为真。
      • 后置条件:ensures P,实现者必须满足P。
      • 副作用范围限定:assignable表示可赋值,modifiable表示可修改,后面加上可以被修改的范围。
      • 异常处理:signal子句抛出异常。
    • 类型规格: 
      • 不变式约束:对象在任何可见状态下都必须满足。
      • 状态变化约束:对象状态变化时必须满足。
  • 在提高代码的可维护性方面:针对已经存在的代码,书写对应的规格,有利于代码的维护。

在JML应用工具链方面,openjml可以根据jml对代码实现进行语法检查、静态检查和运行时检查;JMLUnitNG可以根据jml自动生成对应的测试样例对代码进行测试。

JMLUnitNG/JMLUnit

  • 针对Group接口的实现自动生成测试用例OO第三单元作业总结_第1张图片

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

    • 如上图所示,针对group生成了相应的测试用例并进行测试。   
  • 结合规格对生成的测试用例和数据进行简要分析:从图中可以看成,生成的数据主要是特别大的数据、0、null这几种,failed的测试点也说明了问题:null和大数相关的数据某些方法会出现错误fail掉。

梳理架构设计,分析模型构建策略

  •  第一次作业

    • 在完成第一次作业时没有考虑很多,对着jml写代码,没有考虑模型构建的问题,采用简单的ArrayList存储数据,并且每次都寻找person都要进行查找,只是实现了相应的功能,就认为功能实现了就可以了,没有从整体的角度考虑问题,这是很错误的想法;相对麻烦的函数是iscircle,我采用的是bfs算法,为了方便对其进行测试,将具体实现的方法新增了一个类。
    • 类图如下:OO第三单元作业总结_第3张图片
  • 第二次作业

    • 在完成第二次作业时,根据水群中大佬们的启发,将存储容器全都改成了HashMap,这样在获取相应的person、group时就不用了遍历了,速度更快,并且保留ArrayList版本的person容器以供遍历使用,但是此时我还是没有意识到从整体角度构建图;一开始我的query relation sum方法等全都采用了遍历的方法,但是后来看到大佬们在讨论是否会卡的问题,我又将其修改为缓存觉得比较稳妥一点,在每次添加person或者添加relation时,对相应的relationsum、valuesum、均值等进行更新。
    • 类图如下:OO第三单元作业总结_第4张图片
  • 第三次作业

    • 在完成第三次作业时,非常遗憾的是有一点图的意识了,但是考虑到做的时间比较晚,所以还是没有从宏观角度构建出一个图,本质上还是就着JML写代码,在强测过后意识到应该从全局出发,构造维护一个图,delete person时我维护了相应group的relationsum等数据,query block sum我采用dfs算法、query min path我采用迪杰特斯拉算法优先队列优化的版本、query strong link这个方法我写错了,写着写着就将其理解成为有两条不同的路径就可以了,并且为了方便测试,以上三个方法我都放在了类methods中,也避免了network类的代码长度过长;通过对以上算法的实现,更加意识到整体模型构建的重要性,而自己只是看到什么就去实现什么,没有更好的全局观点,在实现与图有关的算法时也更加麻烦不舒服。
    • 类图如下:OO第三单元作业总结_第5张图片

按照作业分析代码实现的bug和修复情况

  • 第一次作业:强测和互测都没有发现bug。第一次作业比较简单,如果注意到了自己和自己linked的问题,一般不会出现问题。
  • 第二次作业:
    • 强测没有出现问题。
    • 互测中iscircle方法被大佬们用极端数据hack了ctle。iscircle我采用的是bfs算法,并且每次都是重新查找,在极端数据允许范围内人数最多、判定距离最远的、尽可能多的iscircle数据下,会出现ctle的情况。我的修复方法是增加缓存机制,增加一个集合存储之前已经查找过的关系,如果已经查找过,就直接访问集合即可,这样提交确实通过了bug修复,不过我却忘记了重新添加关系时原来的需要作废,这个bug在第三次作业中被发现了。
  • 第三次作业:
    • 强测中:这次的强测就比较惨了,1个点WA,5个点ctle,WA的点就是上文中提到的iscircle问题,由于这次数据量比较小,不采用这种机制就可以了;ctle的点中包含了两个bug,一个bug是查找最短路径的算法超时,应用Dijkstra算法时,我采用了优先队列优化,但是没有设定计算到终点最短路径时即结束,而是直接将所有的路径求出然后在方法执行结束后直接返回终点id对应的路径,应该判定计算到终点时即结束算法,这样就不会超时,目前正在修复中。将其还有就是我对query strong link方法的理解产生错误,误以为只要是两条不同的路径就可以,一开始读规格的时候脑子还是清醒的,但是后面写起来就又忘记了,重写改用查找一次路径后标记、再次查找后避开标记点是否仍然存在的算法,目前正在修复中。
    • 互测中:互测被找出了两个bug,一个bug是上文中提到的query strong link方法的bug,一个是在将某人从某group删除时,忘记更新其所属group集合,这样导致更新relation时会误将其不属于的group也更新,目前已经修复。

阐述对规格撰写和理解上的心得体会

个人感觉规格的撰写比理解规格要更加困难,尽管理解规格也并不简单。在第一次实验课中就有相关补充规格的题目,觉得撰写规格不是易事,要根据函数的实现代码提炼出来函数的功能等并将其用规格准确无误地表达出来,可以从需要什么样的数据、提供什么样的功能、造成了什么样的副作用等方面来考虑;在理解规格方面,对于嵌套多层的括号容易出现理解错误,就比如第二次作业的平均值、方差,如果不仔细看很容易看成是逐个相除相加,我的做法是一层一层将其删除,逐层查看,并且当规格比较长的时候,可能看了前面就忘了后面,或者写着写着就忽视了某些重要的点,就比如我在上文中提到的bug,所以可以在阅读规格的时候就将重要的信息记下来,此外在理解规格时要精细到位,不能忽视不起眼的条件或者细节,就比如第一次很多人炸了的islinked。

 

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