OO第三单元总结——规格不易

目录

  • JML 的理论基础和应用工具链
  • SMT 验证
  • JMLUnitNG 测试
  • 架构设计
  • Bug 分析
  • 心得体会

一、JML 的理论基础和应用工具链:

  • 理论基础:

    • JML 是一种基于 Larch 方法构建的行为接口规格语言。通过类 Java 的语法和近似数学公式的语言来描述类型(Type)(包括类和接口)、方法的不变约束、前置和后置条件。这种形式化的语言不仅没有自然语言产生的二义性,同时也便于使用工具做代码等价性检验。
    • JML 中几个比较重要的概念分别是:
      • 前置条件:描述输入参数必须满足的限制,如果不满足,将导致异常行为。
      • 后置条件:描述输入参数满足前置条件的前提下,方法执行得到的“正确”结果。
      • 副作用范围限定:描述在方法执行过程中可以被修改的域(实例域和静态域)。
      • 不变式:表示变量在可见状态下的取值空间。
      • 状态变化约束:表示变量在方法执行前后所能够变化的程度。
    • 在规格的撰写者和程序的实现者都充分理解了 JML 的前提下,JML 规格可以没有二义性地约束类的状态空间和方法的执行后果,帮助架构师指导程序员实现他所期望的功能。但同其他规格一样,JML 并不关心功能的具体实现,这给实现者留下了一定的灵活度。
  • 应用工具链:

    • 由于 JML 在工业界并没有被广泛接受,JML 的应用工具链不是很完善。
    • 主要有:
      • OpenJML:基于 JML 规范实现的 JML 检查工具,可以进行基本的 JML 语法检查,在搭载了 SMT Solver 后可以对代码进行形式化验证,检查代码的实现是否满足规格要求(支持静态和动态检查)。
      • JMLUnitNG:根据代码中的 JML 规格,自动生成测试样例的工具。

二、SMT 验证:

  • 由于我的实现模型跟规格所要求的并不完全一致(主要是 assignable 部分的问题),因此在 SMT 检查时出现了错误信息:
    OO第三单元总结——规格不易_第1张图片

三、JMLUnitNG 测试:

  • 这三个 Project 的代码由于不知名的原因无法生成测试文件,改用其他代码进行生成。

  • 改用自己写的代码进行测试:

    public class MyTest {
        //@ ensures a > b ==> \result > 0;
        //@ ensures a < b ==> \result < 0;
        //@ ensures a == b ==> \result == 0;
        public int compare(int a, int b) {
            return Integer.compare(a, b);
        }
    }
    
  • 测试结果截图:
    OO第三单元总结——规格不易_第2张图片

四、架构设计:

  • 由于三次作业架构上变化并不是很大,因此我只放出第三次作业的 UML 类图,避免文章过于冗长。

  • 第一次作业:

    • 第一次作业由于需求比较简单,因此直接按照 JML 规格给出的要求实现就基本完成了,并没有什么很特别的架构。
    • 值得注意的是,由于会有很多次查询Path是否存在于PathContainer中,因此直接使用ArrayList之类的查询时间为\(O(n)\)容器是肯定会超时的,在此我使用了TreeMap作为存储Path的容器,避免超时。
    • 同时由于接口中有getDistinctNodeCount()这个查询不同点的方法,如果每次查询都需要遍历一次容器也肯定会超时,在此我使用了缓存的思想:每次添加、删减Path的时候都更新distinctNodeCount,避免每次查询都需要遍历。
  • 第二次作业:

    • 第二次作业中与新增需求无关的部分全部沿用第一次作业的代码。
    • 第二次作业由于增加了最短路计算和边的删减等图论问题,因此我单独构造了一个无权图类UndGraph(非下发的Graph接口),支持:增加、删减边;计算所有点对的最短路径;获取两点间的最短路;检查是否包含边;检查是否包含路径等操作。
    • 这个图类并没有兼容第三次的要求。其实,我已经考虑到了第三次作业可能会加入带权边的需求,但是为了能够在第二次作业中使用 BFS 算法(不支持带权最短路),我就没有加入对带权最短路的支持了。
    • 实现了Model\Graph接口的MyGraph接口通过组合UndGraph对象实现与图论相关的需求。
  • 第三次作业:

    • 第三次作业新增了带权图的需求,这部分需求我通过一个带权图类来实现。
    • 但这个带权图类并非继承自第二次作业的无权图类,而是另起炉灶写了带权图类,同时修改了无权图类,这两个类的耦合度很高:在无权图类组合了 3 个带权图类的对象,把票价、不满意度、换乘等查询交给带权图类对象完成,(请见 UML 类图),这个地方不太OO,也是我自己不太满意的地方。
    • UML 类图:
      OO第三单元总结——规格不易_第3张图片

五、Bug 分析:

  • 本单元的三次作业强测都是 100 分,互测中也没有被发现 Bug 。课下自己测试的时候只有第三次出现了 1 个小 Bug 和 TLE 的问题。
  • 第三次作业的小 Bug :带权图中获取两点间最短路时没有考虑fromNode == toNode的情况,是写代码的时候不够细心导致的。
  • TLE 的问题:带权图类沿用了之前无权图类使用的容器TreeMap来存储数据,导致时间复杂度中的常数过大,自己测试的时候发现超时了。

六、心得体会:

  • 为什么要撰写规格:具体的工程项目实现时是有分工的,并不可能一个人从头到尾全部完成,因此完成不同部分的人需要统一接口,这就需要规格来进行约束。同时,工程项目的顶层设计是由架构师来完成,程序员负责具体实现的,这也需要规格来进行交流。

  • 从撰写规格的原因就可以看出来,撰写的规格必须具备两个特性:通俗易懂和无二义性。通俗易懂能够避免解读规格所需的时间,提高程序员实现的效率。无二义性能够避免规格被错误解读,避免程序的实现与设想不符。

  • 同时,规格只约束了方法的功能、变量的取值空间,却没有限制方法、变量的具体实现,这就给实现者留下了一定的灵活性,能让他们按照自己的习惯实现程序,在效率和可读性、可维护性上进行有考虑地折中。

你可能感兴趣的:(OO第三单元总结——规格不易)