一、JML语言的理论基础
语言基础
Java建模语言(Java Modeling Language,JML)是一种进行详细设计的符号语言,他鼓励我们用一种全新的方式来看待Java的类和方法。
通过在Java代码中增加一些符号,这些符号用来标识一个方法是干什么的,却并不关心它的实现。通过使用JML,我们能够描述一个方法的预期的功能而不管他如何实现。通过这种方式,JML把过程性的思考延迟到方法设计中,从而扩展了面向对象设计的这个原则。
JML引入了大量用于描述行为的结构,比如有模型域、量词、断言可视范围、预处理、后处理、条件继承以及正常行为(与异常行为相对)规范等等。这些结构使得JML非常强大。
工具链
JML的测试可以使用开源的JML编译器来编译含有JML标记的代码,所生成的类文件会在运行时自动检查JML规格,若程序未实现规格中的要求,JML运行期断言检查编译器会抛出一个uncheckedException来说明程序违背了哪一条规格。JMLdoc工具与Javadoc工具类似,可在生成的HTML格式文档中包含JML规范。还有工具JMLUnitNG可以根据规格的实现自动生成TestNG测试样例。SMT在形式化方法、程序语言、软件工程、以及计算机安全、计算机系统等领域得到了广泛应用。SMT Solver工具可以以静态方式来检查代码实现对规格的满足情况。Openjml使用SMT Solver来对检查程序实现是否满足所设计的规格(specification)。目前openjml封装了四个主流的solver。
使用OpenJML对实现的代码进行JML语法静态检查:
- 给出JML语言上的语法错误,并不关心代码;
- 程序代码静态检查:给出程序中可能出现的潜在问题,并不关心JML语言;
- 运行时检查:生成一个新的.class文件,其中包含了运行时检查的assertion,在运行和单元测试的时候将发挥作用。
使用JMLUnitNG根据JML语言自动生成TestNG测试:
- 基于JML生成测试文件;
- 利用OpenJML的rac,生成含有运行时检查的特殊.class文件并替换原文件;
- 运行TestNG测试。
二、基于度量来分析自己的程序结构
三次规格作业都是基于规格化的程序设计,第一次是实现简单的路径增删改查功能,第二次是在此基础上以所有路径建图,并实现最短路径和图联通算法,第三次是将图看做一个地铁系统,实现最短
票价算法,最小不满意度算法,最少换乘算法和图的联通块算法。层层递进,但是设计理念不变,
依旧是基于给定的规格进行分析,每个规格对应一个函数,保证函数满足规格的所有边界条件,在此前提下实现自己的内部算法。
基于此我用度量来分析自己的程序结构度量类的属性个数、方法个数、每个方法规模、每个方法的控制分支数目、类总代码规模计算经典的OO度量画出自己作业的类图,具体如下
Complexity Metrics(复杂度分析)
这部分我们需要使用的主要是方法和类的复杂度分析。
方法的复杂度分析主要基于循环复杂度的计算。循环复杂度是一种表示程序复杂度的软件度量,由程序流程图中的“基础路径”数量得来。
- ev(G):即Essentail Complexity,用来表示一个方法的结构化程度,范围在[1,v(G)]之间,值越大则程序的结构越“病态”,其计算过程和图的“缩点”有关。
- iv(G):即Design Complexity,用来表示一个方法和他所调用的其他方法的紧密程度,范围也在[1,v(G)]之间,值越大联系越紧密。
- v(G):即循环复杂度,可以理解为穷尽程序流程每一条路径所需要的试验次数。
对于类,有OCavg和WMC两个项目。
- OCavg:类的方法的平均循环复杂度。
- WNC:类的方法的总循环复杂度。
Dependency Metrics(依赖度分析)
- Cyclic:指和类直接或间接相互依赖的类的数量。这样的相互依赖可能导致代码难以理解和测试。
- Dcy和Dcy:计算了该类直接依赖的类的数量,带表示包括了间接依赖的类。
- Dpt和Dpt:计算了直接依赖该类的类的数量,带表示包括了间接依赖的类。
第一次作业
第一次作业很简单,仅需实现Path与PathContainer接口的增删改查操作,Path的列表用Arraylist即可实现。类图如下所示
度量表如下所示
第二次作业
此次作业要求建图,用HashMap实现了对图结构的存储。为了保证效率,仅在增加一条路径时对图进行增加边的连接的操作,仅在删除一条路径时重构整张图,因为不知道删除的路径中的边是否要被删除。之后的静态图可以轻易实现BFS算法来判断连通性,也可以用迪杰斯特拉算法来求最短路径,类图如下所示。
度量表如下所示
第三次作业
这次作业依旧可以延续上次的框架,对于地铁系统只需要准确把握住什么时候进行了换乘并且计算换乘的代价,核心算法还是迪杰斯特拉算法,具体见如下类图
度量表如下所示
三、Bug分析
这一单元的主要内容在于基于规格的程序设计,而由于规格的表示是由JML语言来描述的,众多的bug都会集中对于JML语言的理解不够透彻上。
强测出现bug主要是因为设计不合理漏掉边界条件,或者是运行超时导致。排查后发现问题在于对JML规格的边界条件考虑不够清楚周到。
加上边界条件就可以符合规格要求,解决了bug。针对超时的问题,是因为没有考虑到规格之外,程序复杂度的限制,说明对规格设计与理解的考虑仍然不够周到。然而失败是最好的老师,在这一次作业完成后,
认识到构思程序时,应更加谨慎、全面地考虑基于规格的程序设计问题。
四、心得体会
通过这一单元的实验,我认识到了在面向对象程序设计中基于规格的程序设计的重要性。对于一个优秀的程序员来说,应该做到“程序未动,架构先行”,
任何时候都应该在动手之前先想明白所有细节,只要心里有了明确的架构,那么编程就是一件很简单的事。通过这一单元,我明白了JML的规格化设计,也通过JML
明白了规格的神奇之处,这一单元的实验对我的编程习惯和程序设计思维有了良好的锻炼,我将会一直保持下去。