OO第三单元作业总结

第三次博客作业


本单元的三次作业根据所给的Jml规格完成代码,依次实现Path路径类,PathContainer路径容器类;第二次作业将PathContainer类扩展为Graph类,整合图数据结构功能,解决部分图的问题;第三次作业将Graph类扩展为RailwaySystem类,完成更复杂的图功能。

 
JML语言与工具链

JML语言理论基础
  • JML(Java Modeling Language)是一种行为接口的规范语言,通过规格所规定的

    • 前置条件

    • 副作用范围限定

    • 后置条件

    来对设计的行为进行约束,准确表达方法的功能需求。同样,通过JML在x形式规范的基础上,可以利用第三方工具来进行高效的单元测试,即基于正确规格的程序就可以被认为是正确的程序 .

  • 原子表达式

    • \result表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。

    • \old( expr )表达式:用来表示一个表达式 expr 在相应方法执行前的取值。

  • 量化表达式

    • \forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束

    • \exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。

  • 操作符

    • 子类型关系操作符: E1<:E2 ,如果类型E1是类型E2的子类型(sub type),则该表达式的结果为真,否则为假。

    • 等价关系操作符: b_expr1<==>b_expr2 或者 b_expr1<=!=>b_expr2 ,其中b_expr1和b_expr2都是布尔表达式,这两个表达式的意思是 b_expr1==b_expr2 或者 b_expr1!=b_expr2 。

    • 推理操作符: b_expr1==>b_expr2 或者 b_expr2<==b_expr1 。

  • 类型规格

    • 不变式(invariant)是要求在所有可见状态下都必须满足的特性,语法上定义 invariant P ,其中 invariant 为关键词, P 为谓词。对于类型规格而言,可见状态(visible state)是一个特别重要的概念。

    • 状态变化约束(constraint)对象的状态在变化时往往也许满足一些约束,这种约束本质上也是一种不变式

 应用工具链
  • openJML使用-check选项可以检查JML语法的正确性,同时也可以不依赖JML,静态验证方法可能存在的问题,或是生成运行时检查的class文件。
  • JMLUnitNG根据-rac运行时检查选项,可以生成一个Java类文件测试的框架,实现对代码的自动化测试。这种自动化测试对于方法的边界测试比较支持。

 

部署SMT Solver


在讨论区大佬的帮助下,对OpenJML进行了简单的下载安装以及使用,接着对几个方法进行尝试验证。

 package demo;
 ​
 import java.util.ArrayList;
 ​
 public class Path {
     /* @ requires nodes != null;
        @ ensures \result == (\num_of int i, j; 0 <= i && i < j && j < nodes.length;nodes[i] != nodes[j]);
     @*/
     public /*pure*/ static int getDistinctNodeCount(int[] nodes) {
         int len = nodes.length;
         for (int i = 0;i < nodes.length;i++) {
             for (int j = i + 1;j < nodes.length;j++) {
                 if (nodes[i] == nodes[j]) {
                     len--;
                     break;
                 }
             }
         }
         return len;
     }
     
     /* @ ensures \result == (a + b);
        @ */
     public static int  addNode (int a,int b) {
         return a + b;
     }
     
     public static void main(String[] args) {}    
 }
 

  

使用./openjml.sh -check demo/Path.java对JML语法进行检查,没有发现错误。

使用 ./openjml.sh -exec Java/openjml/win/Solvers-windows/z3-4.7.1.exe -esc demo/Pat h.java对方法静态检查

 $ ./openjml.sh -exec Java/openjml/win/Solvers-windows/z3-4.7.1.exe -esc demo/Path.java
 ​
 demo\Path.java:25: 警告: The prover cannot establish an assertion (ArithmeticOperationRange) in method addNode:  underflow in int sum
         return a + b;
                  ^
 demo\Path.java:25: 警告: The prover cannot establish an assertion (ArithmeticOperationRange) in method addNode:  overflow in int sum
         return a + b;
                  ^
 demo\Path.java:13: 警告: The prover cannot establish an assertion (PossiblyNegativeIndex) in method getDistinctNodeCount
                 if (nodes[i] == nodes[j]) {
                          ^
 demo\Path.java:13: 警告: The prover cannot establish an assertion (PossiblyNegativeIndex) in method getDistinctNodeCount
                 if (nodes[i] == nodes[j]) {
                                      ^
 demo\Path.java:14: 警告: The prover cannot establish an assertion (ArithmeticOperationRange) in method getDistinctNodeCount:  underflow in int difference
                     len--;
                        ^
 5 个警告
 

发现getDistinctNode方法中可能存在的错误下标以及len可能溢出的问题,其实我不是很懂为什么会有这两个问题以及add方法可能存在的溢出问题。将方法进行修改后,使用JMLUnitNG自动化生成测试样例

 john@DESKTOP-TI0P349 MINGW64 /d
 $ java -jar jmlunitng.jar "$@" demo/Path.java
 $ ./openjml.sh -rac demo/Path.java
 $ javac -cp jmlunitng.jar demo/*.java
 $ java -cp jmlunitng.jar demo.Path_JML_Test
 ​
 [TestNG] Running:
   Command line suite
 ​
 Failed: racEnabled()
 Passed: constructor Path()
 Passed: static addNode(-2147483648, -2147483648)
 Passed: static addNode(0, -2147483648)
 Passed: static addNode(2147483647, -2147483648)
 Passed: static addNode(-2147483648, 0)
 Passed: static addNode(0, 0)
 Passed: static addNode(2147483647, 0)
 Passed: static addNode(-2147483648, 2147483647)
 Passed: static addNode(0, 2147483647)
 Passed: static addNode(2147483647, 2147483647)
 Failed: static getDistinctNodeCount(null)
 Passed: static getDistinctNodeCount({})
 Passed: static main(null)
 ​
 ===============================================
 Command line suite
 Total tests run: 14, Failures: 2, Skips: 0
 ===============================================

由于不支持exist和forall,因此所能测试的方法极其有限。除此之外,我测试了多个简单的方法,JMLUnitNG自动化生成的样例好像比较难以描述,只会生成一些最大最小空以及Null的测试样例,不知道是不是我的打开方式不对生成的测试代码过于单一,没有太大的价值。

 

架构分析


三次作业我只截了第三次的类图,虽然这三次作业层层递进,但我每次只是简单的将需要实现的类进行实现,因此毫无架构可言,简直就是堆叠的一座垃圾山。从第一次实现的MyPathContainerMyGraph再到MyRailwaySystem,当有新的需求产生时我只是简单的将功能叠加在新增加的类中,因此可以看到新增的类十分臃肿,图结构以及对于需求的计算都堆叠在这个类中,如果有新的需求需要增加,那么可能很多类都无法幸免需要修改。

直到我看到了例程,我才意识到架构的美妙。但是自己实现过程中总是忽略架构,想不到架构,只是将任务完成将需求满足例程中将核心图类进行单独封装,实现图的基本功能,增删路径等,然后引出有向图和无向图;缓存计算层,基于图类实现图的l两个核心计算功能。最后图建模层使用了组合模式+工厂模式,将多个问题归为一种问题,需要求解某个问题时直接调用内部的缓存计算模块,进行计算。

在实现了架构以后,如果有新的功能需要实现,便无需或很少的改动已经实现的代码,相比于我的一座垃圾山真是赞叹不已获益匪浅。

 

BUG分析


第一次作业在强测中遭遇到TLE和WA双重打击。首先第一次作业中没有意识到复杂度的重要性,每次在进行不同结点个数的计算时,保留上次计算结果,如果之前改变过就重新计算,但还是炸掉了。通过使用HashMap将不同节点进行储存,查询时直接输出size()解决。

其次WA是因为在判断结点不同时,错误的将两个Integer使用==进行了比较,简直愚蠢至极。由于测试不充分,只是进行了较小结点的测试(小于127),因此没有发现这个bug,导致大量WA。

第二次第三次作业中没有出现bug。

 

心得体会


对于JML规格,其目的在于对一个方法或是类进行描述,来保证其严格性和准确性。在体会到JML的目的后,如果JML规格已经写好,那JML的使用理解大概就是一个参考书,先看一遍对方法的要求有一个大致的印象,然后再去实现方法。如果有哪里模糊则再去查询。

而撰写JML规格的体验不是很好,如果方法比较简单,那么JML规格撰写起来也比较简单。但是当方法比较复杂时,我觉得JML规格的撰写就变得极其困难。因此我对于JML规格的印象有一些鸡肋,觉得简单的方法撰写的必要性不高,太复杂的方法撰写起来又太过于困难,有些许的矛盾。

posted on 2019-05-22 14:48  tsfis  阅读(205)  评论(0编辑  收藏  举报

导航