一、JML语言
理论基础:
常见注释结构//@...和 /*@...@*/
原子表达式:\result表达式:表示一个非void类型的方法执行所获得的结果,即方法执行后的返回值\old(expr)表达式:表示一个表达式expr在相应方法执行前的取值量化表达式:\forall表达式:全称量词修饰的表达式,表示对于给定范围的元素,每个元素都满足相应的约束\exists表达式:存在量词修饰的表达式,表示对于给定范围的元素,存在元素都满足相应的约束\sum表达式:返回给定范围内的表达式之和\max表达式:返回给定范围内表达式最大值\min表达式:返回给定范围内表达式最小值\num_of表达式:返回指定变量中满足相应条件的取值个数 集合表达式: 可以在JML规格中构造一个局部的集合(容器),明确集合总可以包含的元素 一般形式:new ST{T x|R(x)&&P(x)} 操作符: 等价关系符:b_expr1 <==> b_expr2 (<=!=>) 推理操作符:b_expr1 ==> b_expr2 变量引用操作符:变量引用操作符经常在assignable句子中使用,例如:assignable \nothing 方法规格: 前置条件:requires P; 要求确保P为真 后置条件:ensures P;保证方法执行返回结果一定满足谓词P的要求 /*@spec_public*/注释一个私有成员变量 /*@pure @*/用来注释纯粹访问性方法,不会对对象状态进行改变,也不需要任何参数 public normal_behavior表示对方法正常功能给出规格 public exceptional_behavior表示异常功能规格 signals子句: signals(***Exception e)b_expr当b_expr为true时,抛出括号中的异常e signals_only子句后面跟个异常类型,不强调对象状态条件,强调满足前置条件时抛出相应异常 类型规格: 不变式invariant对所有可见状态下都必须满足的特性 状态变化约束constraint用于对前序可见状态和当前可见状态的关系进行约束
应用工具链:
OpenJML工具可以对规格进行检查,
-check 检查语法正确性-cp 带入jar包的语法检查-esc 静态检查-rac 运行时检查
JMLUnitNG工具可以根据规格自动生成测试样例,测试程序正确性
二、部署SMT Solver(选)
三、利用JMLUnitNG,根据规格实现自动生成测试用例,并对测试用例和数据进行简要分析
使用JMLUnit的命令行
java -jar jmlunitng.jar -cp specs-homework-2-1.2-raw-jar-with-dependencies.jar path/MyGraph.java
生成了一系列测试文件:
运行MyGraph_JML_Test 的结果如下:
[TestNG] Running: Command line suiteFailed: racEnabled()Passed: constructor MyGraph()Passed: <>.addMapNode(-2147483648, -2147483648)Passed: < >.addMapNode(0, -2147483648)Passed: < >.addMapNode(2147483647, -2147483648)Passed: < >.addMapNode(-2147483648, 0)Passed: < >.addMapNode(0, 0)Passed: < >.addMapNode(2147483647, 0)Passed: < >.addMapNode(-2147483648, 2147483647)Passed: < >.addMapNode(0, 2147483647)Passed: < >.addMapNode(2147483647, 2147483647)Passed: < >.addPath(null)Passed: < >.containsEdge(-2147483648, -2147483648)Passed: < >.containsEdge(0, -2147483648)Passed: < >.containsEdge(2147483647, -2147483648)Passed: < >.containsEdge(-2147483648, 0)Passed: < >.containsEdge(0, 0)Passed: < >.containsEdge(2147483647, 0)Passed: < >.containsEdge(-2147483648, 2147483647)Passed: < >.containsEdge(0, 2147483647)Passed: < >.containsEdge(2147483647, 2147483647)Passed: < >.containsNode(-2147483648)Passed: < >.containsNode(0)Passed: < >.containsNode(2147483647)Passed: < >.containsPathId(-2147483648)Passed: < >.containsPathId(0)Passed: < >.containsPathId(2147483647)Passed: < >.containsPath(null)Passed: < >.getDistinctNodeCount()Passed: < >.getMap()Failed: < >.getPathById(-2147483648)Failed: < >.getPathById(0)Failed: < >.getPathById(2147483647)Failed: < >.getPathId(null)Failed: < >.getShortestPathLength(-2147483648, -2147483648)Failed: < >.getShortestPathLength(0, -2147483648)Failed: < >.getShortestPathLength(2147483647, -2147483648)Failed: < >.getShortestPathLength(-2147483648, 0)Failed: < >.getShortestPathLength(0, 0)Failed: < >.getShortestPathLength(2147483647, 0)Failed: < >.getShortestPathLength(-2147483648, 2147483647)Failed: < >.getShortestPathLength(0, 2147483647)Failed: < >.getShortestPathLength(2147483647, 2147483647)Failed: < >.isConnected(-2147483648, -2147483648)Failed: < >.isConnected(0, -2147483648)Failed: < >.isConnected(2147483647, -2147483648)Failed: < >.isConnected(-2147483648, 0)Failed: < >.isConnected(0, 0)Failed: < >.isConnected(2147483647, 0)Failed: < >.isConnected(-2147483648, 2147483647)Failed: < >.isConnected(0, 2147483647)Failed: < >.isConnected(2147483647, 2147483647)Failed: < >.judgeIn(null, -2147483648)Passed: < >.judgeIn([], -2147483648)Failed: < >.judgeIn(null, 0)Passed: < >.judgeIn([], 0)Failed: < >.judgeIn(null, 2147483647)Passed: < >.judgeIn([], 2147483647)Failed: < >.removePathById(-2147483648)Failed: < >.removePathById(0)Failed: < >.removePathById(2147483647)Failed: < >.removePath(null)Passed: < >.size()===============================================Command line suiteTotal tests run: 63, Failures: 30, Skips: 0===============================================Process finished with exit code 0
可以看出自动生成的测试数据都是边界值,failed的数据都是边界值
说明我写程序时没有考虑全面
四、架构设计与重构
第一次作业
这次的作业很简单,就是按照规格的要求完成MyPath和MyPathContainer
private ArrayListnodes;private ArrayList pList;private ArrayList pidList;private static int id;
第二次作业
这次作业在上次的基础上增加判断节点连通性以及两个节点的最短路径长度
其他的代码来自第一次作业,这次作业我设计了一个新类NodesConect用于存父节点以及其子节点,然后用一个动态数组nodesConects来存整个图,设计一个新类SearchNode来实现广度优先算法
第三次作业
这次新建了一个类NodesConect利用矩阵来存储一个带权无向图并实现迪杰斯特拉算法寻找带权无向图的最小路径,SearchFromNet类用于建带权无向图和调用迪杰斯特拉算法,LeastPrice类、LeastTransfer类、LeastUnpleasantValue类、UnpleasantValue类继承SearchFromNet类分别实现最小票价、最少换乘、最小不满意度、以及两点之间的不满意度
对于求解最少连通块,还是利用第二次实现的算法,没有考虑到分模块设计,这设计的确实不好,其实第二三次所有的问题都可以归结为一个问题
五、代码实现和bug修复
第一次作业
按照规格实现代码。出现了两种bug:第一种是因为没有注意使用equals来判断相等,但是中测的时候并没有这种情况,有点玄学
@Override public int getDistinctNodeCount() { // TODO Auto-generated method stub int time = 0; for (int i = 0;i < nodes.size();i++) { int j = 0; for (j = 0;j < i;j++) { //nodes.get(j) == nodes.get(i) if (nodes.get(j).equals(nodes.get(i))) { break; } } if (j == i) { time++; } } return time; }
第二种是因为没有使用HashMap导致出现CPU超时的bug
第二次作业
这次作业我设计了一个新类用于存父节点以及其子节点,然后用一个动态数组来存整个图,利用广度优先搜索算法搜索节点,节点的深度就是最小路径长度(设计一个新类来实现广度优先算法)。
这次没有bug
第三次作业
这次作业添加了四个功能:
1.计算连通块的个数
2.计算最少票价
3.计算最小换乘
4.计算最小不满意度
架构设计参看上节,NodesNet类实际就是一个矩阵对象,包含一个120乘120矩阵,可以改变图矩阵大小,每个矩阵元素的值,获取每个元素的值,还实现了迪杰斯特拉算法。
这次比较重要的是一个特殊的数据结构,我参考了讨论区葛毅飞同学的讨论帖,2、3、4中的问题归结为一个问题,将一个路径中的连通节点之间简化为一条边,边权值是该路径上两节点的最短路径值,这些路径组成一个新图,将这个新图保存在NodesNet对象中(需要对权值进行特殊处理),然后上述问题就转化为求带权无向图的最小路径
而连通块的个数就比较简单,直接利用第二次作业实现的搜索算法
这次的bug都是cpu超时,我还是没有使用HashMap,这样肯定会造成搜索的代价太大
六、规格撰写理解的心得体会
JML规格语言给了一个通用标准的方式来表达对类和方法的详细设计,有助于用户和开发人员的理解,撰写规格需要对类和方法要实现的具体功能非常了解才行。
规格的撰写可以体现思路,而且可以帮助进行测试
感觉规格撰写还是很难的吧,对于比较复杂的功能表达起来还是比较复杂感觉,可能是因为我才开始学这个没多久吧,
但规格真是个好东西,感觉这三次作业写起来都简单很多