---恢复内容开始---
JML 语言
理论基础
JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。JML是一种行为接口规格语言(Behavior Interface Specification Language,BISL),基于Larch方法构建。它在Java代码中增加了一些符号,这些符号用来标识一个方法是干什么的,却并不关心它的实现。如果使用JML的话,我们就能够描述一个方法的预期的功能而不管他如何实现。通过这种方式,JML把过程性的思考延迟到方法设计中,从而扩展了面向对象设计的这个原则。
那么添加这些标记语言有什么好处呢:
- 能够更为精确地描述这些代码是做什么的
- 能够高效地发现和修正程序中的bug
- 可以在应用程序升级时降低引入bug的机会
- 可以提早发现客户代码对类的错误使用
- 可以提供与应用程序代码完全一致的JML格式的文档
应用工具链
OpenJML:OpenJML is a program verification tool for Java programs that allows you to check the specifications of programs annotated in the Java Modeling Language.
JMLUnit:For each Java(TM) or JML source file, and for the top-level class or interface defined in that source file with the name of the source file, the commmand jmlunit, or the graphical user interface version jmlunit-gui , generates code for an abstract test oracle class, that can be used to test that class or interface with JUnit.
JMLUnitNG/JMLUnit测试用例
我使用了IDEA自带的插件进行简单的测试,简单流程如下
选择Test
一下是初始化和对size()的简单测试
import com.oocourse.specs2.models.Graph; import com.oocourse.specs2.models.Path; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; public class MyGraphTest { @Before public void setUp() throws Exception { int[] p1 = {1, 2, 3}; int[] p2 = {1, 2, 3, 6}; int[] p3 = { 2, 3, 4, 5}; Path path1 = new MyPath(p1); Path path2 = new MyPath(p2); Path path3 = new MyPath(p3); Graph graphA = new MyGraph(); graphA.addPath(path1); graphA.addPath(path2); graphA.addPath(path3); System.out.println("begin"); } @After public void tearDown() throws Exception { System.out.println("end"); } @Test public void size() { Assert.assertEquals(1, graph.addPath(path1), 1); }
最后完成测试
程序架构
第一次作业
第一次作业是让我们完成MyPath和MyPathContainer的构建。对MyPath的架构三次作业没有任何变化,使用普通int数组完成nodelist的存储,同时增加一个Hashmap用于存储不同的node,使得containsNode函数和getDistinctNodeCount函数的复杂度降到O(1)。所有函数实现起来都不难。
对MyPathContainer的实现,使用了三个Hashmap存储所有数据,分别是HashMap<Path, Integer> pmap,HashMap<Integer, Path> pidmap,HashMap<Integer, Integer> nodemap,三个map在add和remove时进行更新。pmap和pidmap使的查询是否有path和获取path的复杂度降低,而nodemap是用于统计所有不同的点的个数而使用。
第二次作业
第二次作业是让我们完成MyPath和MyGraph的构建。对于MyPath没有任何改动。
对MyGraph,由于需要查询边,因此增加edgemap来用于查询。而对于查询两点是否连接的需求,我是用并查集操作,新增connectmap来对构建连接图,只要两个点的value相同就可以判断为连接。而对于getShortestPathLength函数,构建邻接表,使用广度优先遍历得到。以上几个map在每次add和remove的时候都会重新构建,且最短路径对每个点都会求出来,所以每次查询的复杂度都为O(1),但是在add和remove时候复杂度会非常高。
具体实现见下图
第三次作业
第三次作业是让我们完成MyPath和MyRailwaySystem的构建。MyPath多了getUnpleasantValue函数,但并没有大改动。
对MyRailwaySystem的构建,新增的getConnectedBlockCount函数直接在并查集的基础上统计有多少不同的value值就行了。而对于最少换乘,最低票价,最低不满意度,其实就是构建三个带不同权值的无向图,最少换乘图就是同一条Path的各个点都相连并设置权值为1,求出的结果减1。最低票价就是同一条Path各个点间算出需要的最低票价并加2就是权值,求出结果减2。最低不满意度就是同一条Path各个点间算出最低不满意度并加32就是权值,求出结果减32即可。这次我没有使用邻接表,而是构建了邻接矩阵,将各个点的id映射到邻接矩阵中,然后使用迪杰斯特拉算法求出最短路径,然后就得到了各个值。我也是在每次add和remove时就构建并算出全部的点对应的各项数据。
这三次作业一二次是直接叠加,但是第三次由于前两次考虑欠妥,重构过多,使得结构异常臃肿。
具体实现如下图
BUG修复
第一次作业的compareTo函数出现问题,因为我在比较的时候使用了两数相减和0比较,没有考虑溢出的情况,导致有较大的数据时就会产生错误的结果,因此我改成两数直接用if比大小就可以了。
第二次作业由于我将equal直接使用==代替,在比较两个点是否连接时,由于我的map的value为Integer类型,我直接使用==,在数据量大时会判定两个值相等的value为不相等,从而造成isConnected函数出错,间接造成getShortestPathLength出错,这个错误也使得我强测只拿了60分。
第三次作业我没有考虑当两个点相同时,价格和不满意度都返回0,我返回了错误的值导致错误。
心得体会
JML给我们带来的不仅仅只是一种规范化的语言,更是对整个架构的规范和思考,从怎么做到做什么,这是一种更高的层次。同时,利用JML和我们所写代码的结合,我们能进行更加高效和规范化的测试。然而我在这三次作业中做的还是太少,对JML的代码仅仅在于理解层次,用来方便写自己代码,对整个架构的理解还是太过于肤浅,这也导致了代码的复用性差,第三次重构的多,代码比较臃肿冗余,还有许多方面仍有欠缺。
---恢复内容结束---