• OO第三单元总结


     一、JML相关知识

      JML是用于对Java程序进行规格化设计的一种表示语言。通过JML及其支持工具,不仅可以基于规格自动测试用例,还整合了SMT Solver等工具以静态方式来检查代码实现对规格的满足情况。

    原子表达式

    表达式 含义
    esult 非void型方法执行结果
    old(expr) 表示表达式expr在相应方法执行前的取值
    ot_assigned(x,y,…) 用来表示括号中的变量是否在方法执行过程中被赋值
    ot_modifed(x,y,…)  表达式限制括号中的变量在方法执行期间的取值未发生变化
    onnullelements(container)  表示 container 对象中存储的对象不会有 null
    ype(type) 返回类型type对应的类型(Class)
    ypeof(expr)  该表达式返回expr对应的准确类型

    量化表达式

    表达式 含义 举例
    forall

    全称量词修饰的表达式,表示对于给定范围内的元素,

    每个元素都满足相应的约束。

    (forall inti,j; 0 <= i && i < j && j < 10; a[i] < a[j])
    exists 存在量词修饰的表达式 (existsint i; 0 <= i && i < 10; a[i] < 0)
    sum 返回给定范围内的表达式的和 (sum int i; 0 <= i && i < 5; i)
    product 返回给定范围内的表达式的连乘结果 (product int i; 0 < i && i < 5; i)
    max 返回给定范围内的表达式的最大值 (max int i; 0 <= i && i < 5; i)
    min 返回给定范围内的表达式的最小值 (min int i; 0 <= i && i < 5; i)
    um_of 返回指定变量中满足相应条件的取值个数 ( um_of int x; 0<x && x<=20;x%2==0)

    操作符

    符号 含义 举例
    <: 子类型关系操作符

    E1<:E2

    // 如果 E1是 E2的子类型或者与 E2类型相同,则返回真

    <==>和<=!=> 等价关系操作符 b_expr1<==>b_expr2
    ==>和<== 推力操作符 b_expr1==>b_expr2
    othing和everything 变量引用操作符

    assignable othing

    // 当前作用域下每个变量都不可以在方法执行过程中被赋值

    方法规格

      前置条件 : 对方法输入参数的限制,不满足则不能保证正确性。

      后置条件 : 对方法执行结果的限制,执行后如果满足则执行正确。

      副作用约定 : 指方法在执行过程中对输入对象或 this 对象进行了修改。

    类型规格

      不变式限制:不变式是要求在所有可见状态下都必须满足的特性。

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

    二、架构设计

    第一次作业

    类图:

     

    第一次作业功能较简单卡的也不严,我大部分功能是完全按照jml的描述写的,没有什么优化。其中isCircle方法,怕使用dfs会报栈,所以我使用的bfs。

    第二次作业

    类图:

     

      第二次作业加入了Group接口,而且数据量非常大,许多功能如果单纯按照jml写,会产生许多o(n^2),o(n^3)的时间复杂度。这次需要好好优化了。

           首先MyPerson类中,使用HashMap替换掉原来的ArrayList来存储认识的人。

      MyGroup类中,有两个属性sum和xor,分别代表年龄之和、性格的异或,每次组里添加一个人,就更新这两个属性,这样在getAgeMean和getConflictSum两个方法中就可以直接返回结果,不需要进行循环了。

      MyGroup类的getValueSum和getRelationSum两个方法,不可避免地要用双循环,可以优化的就是,因为这里的关系是对称的,同一个关系只算一次然后数值上乘2,相当于砍掉原来循环次数的一半。

      在MyNetwork类中,也是用HashMap替换掉了原来的ArrayList来存储Person对象,提高查找效率。

    第三次作业

    类图:

     

           第三次作业加入了一些新的方法,其中queryMinPath,queryBlockSum和queryStrongLinked较为复杂。

           queryBlockSum使用并查集来进行查询,并在每次添加Person的时候更新并查集。同时之前的isCircle方法也可以使用同一个并查集,而不用再bfs了。

           queryMinPath求最短路径,我使用了堆优化的dijkistra算法,并且自己写了一个MyHeap类来实现堆。

           queryStrongLinked本质是判断两个点是否为点双连通。好多同学用的Tarjan算法,我Tarjan算法没看懂,最后是根据https://oi-wiki.org/graph/bcc/里面描述的判断点双连通的方法来做的,使用了一个dfs和一个并查集,倒是也通过了测试。后来研讨课上听同学讲Tarjan算法,总算听懂了,实在太巧妙了~

    三、强测/互测中出现的bug及修复

      第一次作业没有测出bug。

      第二次作业在getValueSum出现了超时的问题,原因是我用的双循环来计算,而且没有记忆功能。

      修复bug时,我加了一个变量用来标记组内是否有新关系的加入,如果没有新关系的加入,getValueSum就直接返回上一次计算的值,有新关系加入才重新计算。

      第三次作业新添加了delPerson方法,但我没有在方法里更新上面说的那个标记,导致一个bug。以及在queryMinPath中,dijkistra算法在松弛时,遍历了全部节点,导致超时。

      因为这次输入量很少,所以解决第一个bug的办法就是去掉了getValueSum的记忆的功能。第二个bug,改为遍历与这一轮最小堆弹出的节点相连的节点。其中涉及到直接修改最小堆里节点的值的操作,会破坏最小堆的性质。由于我是手写的最小堆,所以每修改一个节点的值之后,进行上移或者下移来保持最小堆性质。

    四、JMLUnitNG的使用

      通过阅读别人的博客学习了如何配置和使用JMLUnitNG,其中需要改一些原本的JML,不然会报错。

    我的工作目录:

     

      针对MyGroup类进行测试。cd到工作目录下,依次输入以下四条指令(以及根据途中的报错修改JML):

      java -jar jmlunitng.jar test/MyGroup.java

      javac -cp jmlunitng.jar test/*.java

      java -jar openjml.jar -rac test/MyPerson.java test/MyGroup.java

      java -cp jmlunitng.jar test.MyGroup_JML_Test

      最终运行结果:

     

      可以发现JMLUnitNG会使用一些极端数据来测试我们的程序,但是没有构造更普通和更完整的数据。其更高级的功能有待我们挖掘。

    五、体会

           按照jml写代码,感觉效率能得到很大提升。写一个方法的时候,只需要考虑这个方法自己,以及和它关联的数据结构,而不需要同时考虑其他方法甚至整个类。只需要专心满足jml要求的内容。而且用jml来描述规格,非常严谨,不会像自然语言那样模糊,不会有二义性,干净利落脆,感觉很舒服。之后我写其他代码也打算先自己写JML,然后再具体实现代码部分。

           我们现在写的代码都比较简单,如果在一个特别复杂的工程里,一串JML超级长,想要读懂它估计会有难度吧,所以我感觉要是在JML或其他形式化规格的基础上能再配合上自然语言的“注释”来方便理解,应该会更好。

  • 相关阅读:
    无线路由器的工作模式
    php 利用root 权限执行shell脚本
    shell 终端常用插件
    linux space/mark设置
    推送唯一标识符
    微信支付跨平台软件架构
    celery 动态配置定时任务
    两个报文是如何进行 TCP 分组传输
    接口 Interfaces
    How does Circus stack compare to a classical stack?
  • 原文地址:https://www.cnblogs.com/wangyiou/p/12943302.html
Copyright © 2020-2023  润新知