OO第三次博客作业
OO Unit3 总结
第三单元的作业是以规格为基础的编程,通过这一单元的作业,笔者基本掌握了规格化设计和实现的思想方法。
JML梳理
-
理论基础
JML是一种形式化的、面向JAVA的行为接口规格语言,主要用于进行契约式编程,可以基于工具形成对代码的自动验证和测试。
-
注释结构:JML以javadoc注释的方式来表示规格,每行都以@起头。有两种注释方式,行注释和块注释。其中行注释的表示方式
为 //@annotation ,块注释的方式为 /* @ annotation @*/ 。 -
JML表达式:
-
原子表达式:\result、\old(expr)、\not_assigned()、\not_modified()、\type()等。
-
量化表达式:\forall、\exists、\sum、\product、\max、\min等。
-
集合表达式:可以在JML规格中构造一个局部的集合(容器),明确集合中可以包含的元素 。
-
-
方法规格:
- 前置条件:前置条件通过requires子句来表示 。
- 后置条件:后置条件通过ensures子句来表示 。
- 副作用范围限定:副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,使用关键词 assignable 或者modifiable 。
-
类型规格:
- 不变式:要求在所有可见状态下都必须满足的特性。
- 状态变化约束:用constraint来对前序可见状态和当前可见状态的关系进行约束。
-
-
应用工具链
- openJML:用于对JML规格进行检查和对实现代码进行验证。
- JMLUnitNG:基于JML规格自动生成测试。
部署SMT Solver对代码进行验证
下面是利用SMT Solver对第二次作业的MyGraph中所实现的方法的验证结果。
-
public boolean containsNode(int nodeId)
E:\program\java\blog1\src\second\homework\MyGraph.java:21: 警告: The prover cannot establish an assertion (Constraint: E:\program\java\blog1\src\com\oocourse\specs2\models\PathContainer.java:7: 注: ) in method containsNode return graph.containsNode(nodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:21: 警告: The prover cannot establish an assertion (Constraint: E:\program\java\blog1\src\com\oocourse\specs2\models\PathContainer.java:7: 注: ) in method containsNode return graph.containsNode(nodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:21: 警告: The prover cannot establish an assertion (Postcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:7: 注: ) in method containsNode return graph.containsNode(nodeId); ^
-
public boolean containsEdge(int fromNodeId, int toNodeId)
E:\program\java\blog1\src\second\homework\MyGraph.java:25: 警告: The prover cannot establish an assertion (Constraint: E:\program\java\blog1\src\com\oocourse\specs2\models\PathContainer.java:7: 注: ) in method containsEdge return graph.containsEdge(fromNodeId, toNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:25: 警告: The prover cannot establish an assertion (Constraint: E:\program\java\blog1\src\com\oocourse\specs2\models\PathContainer.java:7: 注: ) in method containsEdge return graph.containsEdge(fromNodeId, toNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:25: 警告: The prover cannot establish an assertion (Postcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:10: 注: ) in method containsEdge return graph.containsEdge(fromNodeId, toNodeId); ^
-
public boolean isConnected(int fromNodeId, int toNodeId)
![homework1](E:\学习\面向对象设计\homework\12\pic\homework1.png)![homework1](E:\学习\面向对象设计\homework\12\pic\homework1.png)E:\program\java\blog1\src\second\homework\MyGraph.java:34: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:16: 注: ) in method isConnected throw new NodeIdNotFoundException(toNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:34: 警告: The prover cannot establish an assertion (Constraint: E:\program\java\blog1\src\com\oocourse\specs2\models\PathContainer.java:7: 注: ) in method isConnected throw new NodeIdNotFoundException(toNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:37: 警告: The prover cannot establish an assertion (Postcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:20: 注: ) in method isConnected return (length >= 0); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:37: 警告: The prover cannot establish an assertion (Postcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:22: 注: ) in method isConnected return (length >= 0); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:37: 警告: The prover cannot establish an assertion (Constraint: E:\program\java\blog1\src\com\oocourse\specs2\models\PathContainer.java:7: 注: ) in method isConnected return (length >= 0); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:37: 警告: The prover cannot establish an assertion (Postcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:24: 注: ) in method isConnected return (length >= 0); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:34: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:25: 注: ) in method isConnected throw new NodeIdNotFoundException(toNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:34: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:26: 注: ) in method isConnected throw new NodeIdNotFoundException(toNodeId);
-
public int getShortestPathLength(int fromNodeId, int toNodeId)
E:\program\java\blog1\src\second\homework\MyGraph.java:43: 警告: The prover cannot establish an assertion (Constraint: E:\program\java\blog1\src\com\oocourse\specs2\models\PathContainer.java:7: 注: ) in method getShortestPathLength throw new NodeIdNotFoundException(fromNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:48: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:30: 注: ) in method getShortestPathLength int length = graph.shortestPathLength(fromNodeId, toNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:43: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:42: 注: ) in method getShortestPathLength throw new NodeIdNotFoundException(fromNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:43: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:43: 注: ) in method getShortestPathLength throw new NodeIdNotFoundException(fromNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:50: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:44: 注: ) in method getShortestPathLength throw new NodeNotConnectedException(fromNodeId, toNodeId); ^ E:\program\java\blog1\src\second\homework\MyGraph.java:52: 警告: The prover cannot establish an assertion (Postcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:41: 注: ) in method getShortestPathLength return length; ^ E:\program\java\blog1\src\second\homework\MyGraph.java:52: 警告: The prover cannot establish an assertion (Postcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:34: 注: ) in method getShortestPathLength return length; ^ E:\program\java\blog1\src\second\homework\MyGraph.java:52: 警告: The prover cannot establish an assertion (Postcondition: E:\program\java\blog1\src\com\oocourse\specs2\models\Graph.java:39: 注: ) in method getShortestPathLength return length; ^ E:\program\java\blog1\src\second\homework\MyGraph.java:52: 警告: The prover cannot establish an assertion (Constraint: E:\program\java\blog1\src\com\oocourse\specs2\models\PathContainer.java:7: 注: ) in method getShortestPathLength return length;
部署JMLUnitNG,对简单方法进行测试
-
方法代码:
public class Demo { //@ ensure x < y ==> \result < 0; //@ ensure x > y ==> \result > 0; //@ ensure x == y ==> \result = 0; public static int compare(int x, int y) { return (x - y); } }
-
测试结果:
[TestNG] Running: Command line suite Passed: racEnabled() Passed: static compare(-2147483648, -2147483648) Passed: static compare(0, -2147483648) Passed: static compare(-2147483648, 0) Passed: static compare(2147483647, -2147483648) Passed: static compare(0, 0) Passed: static compare(-2147483647, 2147483648) Passed: static compare(2147483648, 0) Passed: static compare(0, 2147483648) Passed: static compare(2147483648, 2147483648) Passed: constructor Demo() =============================================== Command line suite Total tests run: 11, Failures: 0, Skips: 0 ===============================================
-
结果分析:
从自动生成的测试来看,jmlunit主要是基于规格对边界数据进行测试。
各次作业架构设计
-
第九次作业
-
task:
实现一个路径管理系统,由Path和PathContainer组成。
-
类图
-
架构设计
第一次作业直接基于所给出的规格实现了相应的功能就结束了,利用hashMap和hashSet数据结构在时间复杂度方面进行了优化,没有扩展。
-
-
第十次作业
-
task:
在上一次作业的基础上,增加图结构,对所有节点和边组成的路径图进行管理,新增了Graph类。
-
类图
-
架构设计
第二次作业在第一次作业的基础上进行扩展,创建了GraphCalculate类来单独管理图结构计算,利用MyGraph类继承MyPathContainer类的功能,在保持原有功能不变的基础上,建立图结构与GraphCalculate类交互,实现最短路等功能。
在迭代方面,这一次作业完全没有改动上一次作业,所有的扩展内容都在MyGraph中实现,对方法的重写也在一开始调用了父类方法,然后加入新实现的功能。
-
-
第十一次作业
-
task:
在上一次作业的基础上实现RailwaySystem,新增了不满意度查询、换乘查询等三种功能。
-
类图
-
架构设计
第三次作业改动较大,第一个体现在对GraphCalculate类新增了许多方法,然后对于MyGraph类的一些方法进行了修改,使得其能够适应新需求,这一点不是很符合开闭原则。
新增的MyRailwaySystem类继承了MyGraph类的图结构,但是额外增加了其自身的图结构,用于实现新功能,而所有的旧功能调用MyGraph的方法即可完成,与MyRailwaySystem中新增的方法无关,也就是说新增加的功能与旧功能之间没有耦合,完全独立实现。这一点是我在迭代设计中所注意的,但是这样做的缺陷在于有大量重复的数据,浪费了时间空间。
-
BUG分析
第一次、第二次作业公测均未出现错误。
第三次作业公测中发现最短路径算法出现bug,经过分析发现,出现原因是在第二次作业基础上进行扩展时,有一处地方修改错误。
这让我明白了在修改已经实现了的部分的代码时,一定要格外小心,尽量减少修改。
规格撰写心得
体会
-
契约式编程的确是一种非常好的编程范式,在保证对外功能不变的情况下,内部实现可以由实现者本身自行决定,将设计和实现完美的分离,极大地提高了设计和实现的效率。并且有规格作为正确性的保证,对代码的验证、测试都可以基于规格来展开,极大提高了测试的方便性。
-
架构仍然是很重要的。规格从本质上看与实现代码无异,都可以表达对功能的描述,因此规格的设计与架构也是密切相关的,规格本身就含有设计者一定的架构规范,但是更多的架构上的空白需要实现者来填补,基于规格实现代码时,要以架构为导向为前提来实现,实现者自身扩展的内容要与原有规格相容,二者相辅相成才能较好地达到目的。