单元总述
这个单元分为三次作业,主要是给出了JML描述的规格,然后需要我们根据JML描述的规格来写出相应的程序模块。
JML语言的理论基础、应用工具链
语言基础
(部分内容参考于官网)
JML全称Java Modeling Language,是一种行为接口规范语言,可用于指定Java模块的行为 。它结合了Eiffel的契约方法设计 和Larch系列接口规范语言的基于模型的规范方法,以及细化演算的一些元素 。
JML是一种行为界面规范语言(BISL),它建立在落叶松方法上 ,在APP [Rosenblum95]和Eiffel中找到。在这种规范风格中,可以称为面向模型,指定方法或抽象数据类型的接口及其行为。特别是JML建立在Leavens和其他人在Larch / C ++中完成的工作之上 。
方法或类型 的接口是从程序的其他部分使用它所需的信息。对于JML,这是调用方法或使用字段或类型所需的Java语法和类型信息。对于方法,接口包括诸如方法的名称,其修饰符(包括其可见性以及是否为最终),其参数数量,返回类型,可能抛出的异常等等。对于字段,接口包括其名称和类型及其修饰符。对于类型,接口包括其名称,修饰符,包,它是类还是接口,它的超类型,以及它声明和继承的字段和方法的接口。JML使用Java的语法指定所有这些接口信息。
常见的JML工具
以下是Common JML发行版中每个工具的unix介绍页。每个手册页都有一些关于如何使用该工具的信息,并指定它们的选项和参数。
- jml-launcher(图形用户界面工具的启动器)。
- jml和jml-gui(检查器)。
- jmlc和jmlc-gui(编译用于运行时断言检查)。
- jmldoc和jmldoc-gui (包含JML规范信息的javadoc版本)。
- jmle (编译用于执行或原型规范)。
- jmlrac(java的一个版本,VM,包含CLASSPATH中的bin / jmlruntime.jar文件,用于运行使用jmlc编译的文件)。
- jmlre(java的一个版本,VM,包括执行规范所需的运行时支持,用于运行使用jmle编译的文件 )。
- jmlspec和jmlspec-gui (从Java源文件生成框架规范文件)。
- jmlunit和jmlunit-gui (生成用于JUnit的单元测试代码存根 )。
- jtest (结合了jmlc和 jmlunit的工作)
- jml-junit(JUnit的swing用户界面的一个版本,包括CLASSPATH中的bin / jmlruntime.jar和bin / jmljunitruntime.jar文件,用于对使用jmlc编译的文件和jmlunit生成的测试用例运行基于JML和JUnit的测试)。
部署SMT Solver
在辗转IJ和Eclipse反复安装OpenJML工具并对自己的代码进行分析后,我仍然是无法得到一份比较完整的测试结果。很可惜网上关于OpenJML的资料实在是有些少得可怜,而OpenJML的一些报错也实在是令人摸不着头脑。
就目前已经探索出来的一些结果而言,在windows10环境下,IntelliJIdea 2019.3版本下我们可以选取OpenJML中0.8.42版本中z3-4.7.1的Solver进行分析,其中z3-4.3.2不适用于win10系统,但是z3-4.7.1会报一些无法生成断言(检测变量)的错误,至今仍没有解决问题的头绪。
这里贴出对第一次作业中MyPath的检测结果
public /*@pure@*/ boolean containsNode(int node)
C:UsersRyanwIdeaProjectshomework9srcTest.java:28: 警告: The prover cannot establish an assertion (PossiblyNegativeIndex) in method containsNode if (this.node[i] == node) { ^ C:UsersRyanwIdeaProjectshomework9srcTest.java:32: 警告: The prover cannot establish an assertion (Postcondition: C:UsersRyanwIdeaProjectshomework9srcTest.java:24: 注: ) in method containsNode return false; ^ C:UsersRyanwIdeaProjectshomework9srcTest.java:24: 警告: Associated declaration: C:UsersRyanwIdeaProjectshomework9srcTest.java:32: 注: //@ ensures esult == (exists int i; 0 <= i && i < nodes.length; nodes[i] == node);
public boolean equals(Object obj)
C:UsersRyanwIdeaProjectshomework9srcTest.java:78: 警告: The prover cannot establish an assertion (PossiblyNegativeIndex) in method equals if (node[i] != ((Test) obj).getNode(i)) { ^ C:UsersRyanwIdeaProjectshomework9srcTest.java:82: 警告: The prover cannot establish an assertion (Postcondition: C:UsersRyanwIdeaProjectshomework9srcTest.java:57: 注: ) in method equals return true; ^ C:UsersRyanwIdeaProjectshomework9srcTest.java:57: 警告: Associated declaration: C:UsersRyanwIdeaProjectshomework9srcTest.java:82: 注: @ ensures esult == (forall int i; 0 <= i && i < nodes.length; ^ C:UsersRyanwIdeaProjectshomework9srcTest.java:74: 警告: The prover cannot establish an assertion (ExceptionalPostcondition: C:APPOpenJMLopenjml-0.8.42-20190401openjml.jar(specs/java/lang/Object.jml):76: 注: ) in method equals if (node.length != ((Test) obj).size()) { ^
public /*@pure@*/ int getNode(int index)
C:UsersRyanwIdeaProjectshomework9srcTest.java:21: 警告: The prover cannot establish an assertion (PossiblyTooLargeIndex) in method getNode return node[index]; ^ C:UsersRyanwIdeaProjectshomework9srcTest.java:21: 警告: The prover cannot establish an assertion (Postcondition: C:UsersRyanwIdeaProjectshomework9srcTest.java:15: 注: ) in method getNode return node[index];
部署JMLUnitNG/JMLUnit
我们选择了比较简单的Path类来进行测试:
生成相应的文件树:
生成相应的测试结果:
[TestNG] Running: Command line suite Passed: racEnabled() Failed: constructor Path(null) Passed: constructor Path({}) Failed: <<homework9.Path@1>>.compareTo(null) Passed: <<homework9.Path@1>>.containsNode(-2147483648) Passed: <<homework9.Path@1>>.containsNode(0) Passed: <<homework9.Path@1>>.containsNode(2147483647) Passed: <<homework9.Path@1>>.equals(null) Passed: <<homework9.Path@1>>.equals(java.lang.Object@1c2c22f3) Passed: <<homework9.Path@1>>.getDistinctNodeCount() Failed: <<homework9.Path@1>>.getNode(-2147483648) Failed: <<homework9.Path@1>>.getNode(0) Failed: <<homework9.Path@1>>.getNode(2147483647) Passed: <<homework9.Path@1>>.isValid() Passed: <<homework9.Path@1>>.iterator() Passed: <<homework9.Path@1>>.size() =============================================== Command line suite # Total tests run: 16, Failures: 5, Skips: 0 ===============================================
架构设计
这三次作业是在整个作业过程中做得最为艰辛的三次作业。因为每次都因为各种各样的原因进行重构。首先在第一次作业中采用了数组的格式进行对数据的存储,第二次作业由于第一次作业的TLE进行重构改成了图的结构进行存储。在第二次作业中采用图的格式来进行对图结构的存储,并完成最短路径的计算,结果是使用十分不方便,并且由于数据结构的过于复杂,没有一个比较好的封装,导致了在设计算法的时候出现了问题(问题应该还是太过自信写了个自己眼中的并不正确的Dij)。第三次作业中整体重构了整个的数据结构。采用在Path阶段采用hashMap进行存储,而在生成图的时候生成一张由数组存储的邻接表矩阵,由此来避免算法出现的问题。
bug和修复情况
这次作业中主要的bug都出现在数据结构与针对数据结构的算法中。在第二次作业刚刚开始的时候使用到了Dijsktra算法来实现两点之间最短距离的计算,但是遗憾的是在写这个算法的时候出现了一些问题,导致后两次作业体验极差,几乎在de算法的bug。好在在最后一次作业中对中测第二组数据的测试过程中发现了这个bug并进行了改正。
对规格的心得体会
其实三次作业加两次实验做下来只是对JML有了一个基本的认识。运用了JML之后可能确实对每一种方法的描述更为精确了,毕竟个人感受JML几乎是一种数学语言的表达方式。但是这种表达方式既不接近我们平时的语言表达习惯,又不接近我们写程序的时候的思维习惯。几乎规格是独立于这两者之外的另外一种表达模式。甚至在我们进行第三次作业的时候出现了原来的JML规格不满足需求描述的情况。因此是否有必要为了程序的精确性,在需求与coding中间,引入另一个更可能出现问题的规格,在这里我持怀疑态度。不知道更好的解决方法是不是将需求用更规范的语言表达出来。但是JML的优点可能是JML提供了一套完整的从规格到代码的验证方法。总之,我们必须承认JML是一种避免程序出现问题的必要手段,但是希望有更好的从需求到规格的描述方法。