OO第三次博客作业
一、JML简介及工具链
JML是一种进行详细设计的符号语言,他鼓励你用一种全新的方式来看待Java的类和方法。
JML综合了Eiffel,Larch两者的优点,为Java提供了一个专门设计行为的接口语言。JML用来描述方法模块的动作行为,基于数学模型,其比自然语言更加精确。
JML编译器jmlc可以检查JML形式规范是否正确;JMLdoc与Javadoc工具相似,不同的是它在生成的HTML格式文档中包含JML规范;JMLunit可以成生一个Java类文件测试的框架,它可以让你很方便地使用JUnit工具测试含有JML标记的Java代码。
二、JMLUnitNG
采用了比较暴力的方法,将jmlunitng的包和课程组的包直接添加进了CLASSPATH里。
对第三次作业中的MyPath类进行测试。
import com.oocourse.specs3.models.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; public class MyPath implements Path { private ArrayList<Integer> nodes; private HashMap<Integer, Integer> map = new HashMap(); private int distinctCnt; private int hashCode; // Iterable<Integer>和Comparable<Path>接口的规格请参阅JDK public Iterator<Integer> iterator() { return nodes.iterator(); } public int hashCode() { return hashCode; } public int compareTo(Path o) { MyPath compareThis = (MyPath) o; int len = nodes.size(); if (compareThis.size() < len) { len = compareThis.size(); } for (int i = 0; i < len; i++) { int t = nodes.get(i).compareTo(compareThis.getArrayList().get(i)); if (t < 0) { return -1; } if (t > 0) { return 1; } } if (nodes.size() > len) { return 1; } if (compareThis.size() > len) { return -1; } return 0; } public MyPath(int[] nodeList) { map.clear(); distinctCnt = 0; hashCode = 0; nodes = new ArrayList<Integer>(); for (int x : nodeList) { nodes.add(x); if (!map.containsKey(x)) { map.put(x, 1); distinctCnt++; } hashCode = hashCode() * 233 + x; } } public /*@pure@*/int size() { return nodes.size(); } public /*@pure@*/ int getNode(int index) { return nodes.get(index); } public /*@pure@*/ boolean containsNode(int node) { return map.containsKey(node); } public /*pure*/ int getDistinctNodeCount() { return distinctCnt; } public boolean equals(Object obj) { if (obj == null || !(obj instanceof Path)) { return false; } return nodes.equals(((MyPath) obj).getArrayList()); } public /*@pure@*/ boolean isValid() { return nodes.size() >= 2; } ArrayList<Integer> getArrayList() { return nodes; } private static int[] pow4 = {1, 4, 16, 64, 256}; public /*@pure@*/ int getUnpleasantValue(int nodeId) { if (!containsNode(nodeId)) { return 0; } return pow4[(nodeId % 5 + 5) % 5]; } public boolean containsEdge(int fromNodeId, int toNodeId) { for (int i = 0; i < nodes.size() - 1; i++) { if ((nodes.get(i) == fromNodeId && nodes.get(i + 1) == toNodeId) || (nodes.get(i) == toNodeId && nodes.get(i + 1) == fromNodeId) ) { return true; } } return false; } }
生成测试文件
jmlunitng MyPath.java
编译测试文件
javac MyPath_JML_Test.java
编译MyPath.java
javac MyPath.java
运行测试
java MyPath_JML_Test
三、架构设计
1、第一次作业
第一次的构架为三个类,即主类,继承Path的MyPath类和继承PathContainer的MyPathContainer类,在类内部使用map来完成相应工程
2、第二次作业
第二次作业代码构架仍然为三个类,主类,继承Path类的MyPath类,继承Graph类的MyGraphy类,这一次的作业采用了Floyd算法,做的很不好的一点是没有将图的模块独立出MyGraphy类,而是在MyGraphy类里直接开设了相应的数组运行算法
3、第三次作业
第三次作业除了继承了前两次作业的写法之外,新增了一个ShortestPath类,由于第三次作业中的三个询问不同点在于建边的边权不一,所以只要规定ShortestPath类内部的TYPE,就能用以边权的区分。
四、BUG情况
在第二次作业中,由于采用了Floyd算法,所以每次只要有图结构变化相关指令,最短路就需要重新运算。由于Floyd使用邻接矩阵,所以对于点编号需要map离散化之后加以使用,在这里出现了一个bug:在addPath和removePath两个不同的图结构修改指令中,对离散化map的操作顺序不正确,addPath之前需要先修改map维护编号,removePath则在之后,当时编写这一部分代码时出现了问题
五、心得体会
我觉得JAVA里的规格让工程代码有了灵魂。
之前在学习c语言的时候,对于每一个函数方法,内心也会想着这个函数有什么作用,对于传入的参数应该有什么操作,返回结果是多少,不过这样都是感性思考。规格真正量化了这个思想。就像是在写递归一样,我只要知道这个函数的行为,那我就可以直接调用,而不用深层次进一步地思考这个函数内部执行,这对于编写代码时的流畅性非常有帮助。
JML的写法也不复杂,除了几个域之外,剩下的就是JAVA语法和简单的符号\forall,\exists等等。
JML还能使测试更加简单,模块化测试更加容易。