OO随笔之追求完美的第三单元——初试JML
前言
这一章的JML比较简单,那么大家的关注点自然地移到了性能优化上。于是大家一股脑地去利用各种数据结构去做时间上的优化(当然很多人最后还是倒在了正确性上),故称追求完美的一单元。当然这也是得益于JML的,有了它的指导,每个方法的职能就非常清楚了,类之间的耦合自然也小了,同学们就可以针对一个方法精打细磨。
JML语言简介
JML(Java Modeling Language)是一种对于Java语言进行规格化设计的一种表示语言,它是一种行为接口规格语言(Behavior Interface Specification Language, BISL)。
一般而言,JML有两种主要用法:
- 开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语言描述,而是逻辑严格的规格。
- 针对已有代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。
方法规格的核心内容包括三个方面:前置条件、后置条件和副作用约定。
前置条件(pre-condition)
前置条件通过requires子句来表示:
requires P;
其中requires是JML关键词,表达的意思是“要求调用者确保P为真”。
后置条件(post-condition)
后置条件通过ensures子句来表示:
ensures P;
其中ensures是JML关键词,表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”。
副作用范围限定(side-effects)
副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。从方法规格的角度,必须要明确给出副作用范围。JML提供了副作用约束子句,使用关键词assignable或者modifiable。从语法上来看,副作用约束子句共有两种形态,一种不指明具体的变量,而是用JML关键词来概括;另一种则是指明具体的变量列表。
signals子句
signals子句的结构为signals (***Exception e) b_expr,意思是当b_expr为true时,方法会抛出括号中给出的相应异常e。
JML工具链主要有OpenJML和JMLUnit,但是他们对于一般的学习者和开发者来说并不太友好,首先它对于代码的数据结构的要求比较高(Hash类好像就不支持),而且对于复杂场景复杂方法的处理存在问题。在实际的开发中,可能程序员们更加偏爱Javadoc和UML的方式。
部署JMLUnitNG/JMLUnit
本地只部署了OpenJML进行测试,也利用Junit手造数据对每个方法进行了测试。
实例代码如下:
package path;
public class path {
static private /*@ spec_public @*/ int[] nodes;
public static void main(String[] args) {
nodes = new int[5];
nodes[0] = 0;
nodes[1] = 1;
nodes[2] = 2;
nodes[3] = 3;
nodes[4] = 4;
System.out.println(size() + "" + getNode(2) + isValid());
}
//@ ensures \result == nodes.length;
public static /*@ pure @*/int size() {
return nodes.length;
}
/*@ requires index >= 0 && index < size();
@ assignable \nothing;
@ ensures \result == nodes[index];
@*/
public static /*@ pure @*/ int getNode(int index) {
return nodes[index];
}
//@ ensures \result == (nodes.length >= 2);
public static /*@ pure @*/ boolean isValid() {
return size() >= 2;
}
}
通过OpenJML的测试,他给了我如下反馈

根据结果发现,三个方法对于JML的实现在静态检查中均没有问题;但是在运行中,它发现我的nodes数组有为null值的可能,也算是个可能的bug吧。
关于JMLUnitNG的问题,我尝试了利用jmlunitng.jar包搭建自动生成数据的平台,但是似乎他自动生成的测试代码有bug,调了很久的源码后还是发现有这样一个问题:非子类访问了一个protected的变量,所以无法运行。想来想去应该还是自己可能有一些地方细节没有调好(甚至可能是JDK版本问题或者是shell的问题)。
所以就只能进行手动测试了:实际上对于每个类和方法,都做了基于Junit(或testNG)的测试,手动编写测试数据,利用断言判断代码逻辑的问题。
架构设计分析
第一次作业PathContainer
第一次作业比较简单,保证正确性不难,难点在于在“少量增删,大量查询”这样一个前提下的效率提高。在Path类和PathContainer类中都利用了数据冗余来提高查找效率:Path中采用了ArrayList来记录节点顺序和HashSet来记录节点种类;在PathContainer中用了两个HashMap来分别记录path及其id和所有出现过的节点及其次数。这使得增删时多了一倍的工作量,但是查询时的复杂度大大降低。
第二次作业Graph
Graph类直接继承了PathContainer类,只是新增了一个距离矩阵(利用映射来给节点和矩阵中元素建立一一对应),并且采用了floyd算法来计算两点间的最短路径:在“少量增删,大量查询”这样一个前提下,只每次图的增删时,重构距离矩阵,重新计算距离。
第三次作业RaiwaySystem
第三次作业RaiwaySystem类直接继承了Graph类,同时将floyd算法单独抽象出一个类,同时修改了祖传的Path类。
Path类中,对于每一个Path都有自己独立的“三矩阵”(换乘矩阵,价格矩阵和不满意度矩阵),当在RaiwaySystem中新增path时就初始化“三矩阵”(而不是新建path时,主要是考虑到程序中会隐含地出现“新建了但不加入RaiwaySystem”的临时path,这样做可以提高效率)。
RaiwaySystem类同样维护了“三矩阵”,在“少量增删,大量查询”这样一个前提下,每次地铁系统的增删时,重构距离矩阵,重新计算距离。
下面用数据分析下性能:

复杂度(仅列出v(G)较高的方法):

我们看下floyd()这个大头,作为一个多源最短路算法,他采用了for-for-for嵌套的方式计算最短路。思路很简单,但是表现在算法上的复杂度就比较复杂了。作为一个固定的算法,并没有很大的优化空间。
下一个是addPath(),它主要体现在逻辑复杂上。由于在Graph中维护了两个HashMap,分别记录出现过的所有节点及其次数、出现过的所有边及其次数,那么在新增一个path时,就必定要将新path中的所有点和边加入到这个hashmap中:对于已经存在的节点,我们可以简单地将它的出现次数加一;但对于图中不存在的节点,就必须要在我们的“三矩阵”中给他分配一个位置,便于我们计算这个点到其他点的距离。这个方法其实有一点点的优化空间,但是为了尽量满足单一责任原则(OCP)原则,想了想,算了,不改了。
下一个是path中的initMatrix(),这玩意干的事情就是对于每个路径path,初始化他的“三矩阵”,然后用floyd去给他算一遍。逻辑和代码复杂度都还行,但是为什么最后体现的v(G)足足有8点。
下一个是path中的祖传equals()方法,它首先利用hash判断两个path是否相同,如是,再遍历判断。本来想法是挺好的,但是在看了hash的源码之后,发现事情没有那么简单:既然ArrayList的hash是遍历还要做乘法加法,为什么不直接遍历逐项比较呢,对吧。所以讲道理这个可以有一定的优化。
其他复杂方法暂不分析。
bug情况
一共出现了两个bug:第一个是在第一次作业因为重写equals()方法时的大意,盲目相信了前任造的轮子,忽略了Hash值相同的情况;第二次是在第三次作业在最后优化时,在修改Path中某个新成员变量时误改动了旧的变量,导致代码逻辑出现了漏洞。
本来写了一个自动数据生成器,但是因为生成器的不够完善,生成的数据不够极端,导致上述第二个bug没有被检测出。
但是其实更大的原因应该是自己在修改时,擅自动了旧的代码,甚至轻微改变了旧的代码逻辑,这其实有违单一责任原则(OCP)原则。
心得体会
这一次的作业难度其实并不是很大,除了学会了JML这一种代码描述语言的应用,还在搭建本地测评机时提高自己利用命令行来操作java程序的能力。但是同时也发现一个大问题:自己经常在一个大的项目中,犯下一些细节的错误,比如将一个数字写错,导致空指针;比如轻微改变了一下几行代码的顺序,却导致严重问题……自己在写大量的代码的时候,思路依然不够清楚;或者说是自己在设计的时候,往往只是设计了一个大的框架之后,就开始动笔,而没有将每一个细节都想好——当遇到一个自己从来没注意到的细节的地方的时候,可能会因为临时的仓促而忽略了代码的其他部分,而在逻辑上产生隐含的问题。所以下一单元,不论难度怎么样,我都应该更加严格地遵循SOLID原则,经过更加缜密的思考之后,再下笔。
OO随笔之追求完美的第三单元——初试JML的更多相关文章
- OO第三单元——基于JML的社交网络总结
OO第三单元--基于JML的社交网络总结 一.JML知识梳理 1)JML的语言基础以及基本语法 JML是用于java程序进行规格化设计的一种表示语言,是一种行为接口规格语言.其为严格的程序设计提供了一 ...
- OO第三单元作业(JML)总结
OO第三单元作业(JML)总结 目录 OO第三单元作业(JML)总结 JML语言知识梳理 使用jml的目的 jml注释结构 jml表达式 方法规格 类型规格 SMT Solver 部署JMLUnitN ...
- OO第三单元总结——JML
目录 写在前面 JML理论基础 JML工具链 JMLUnitNG的使用 架构设计 Bug分析 心得体会 写在前面 OO的第三单元学习结束了,本单元我们学习了如何使用JML语言来对我们的程序进行规格化设 ...
- 2020 OO 第三单元总结 JML语言
title: 2020 OO 第三单元总结 date: 2020-05-21 10:10:06 tags: OO categories: 学习 第三单元终于结束了,这是我目前为止最惨的一单元,第十次作 ...
- 第三单元总结——JML契约式编程
OO第三单元博客作业--JML与契约式编程 OO第三单元的三次作业都是在课程组的JML规格下完成.完成作业的过程是契约式编程的过程:设计者完成规格设计,实现者按照规格具体实现.作业正确性的检查同样围绕 ...
- 2019年北航OO第三单元(JML规格任务)总结
一.JML简介 1.1 JML与契约式设计 说起JML,就不得不提到契约式设计(Design by Contract).这种设计模式的始祖是1986年的Eiffel语言.它是一种限定了软件中每个元素所 ...
- OO第三单元总结——JML规格设计
• 1.JML语言的理论基础.应用工具链情况 JML(Java Modeling Language)—— java建模语言,是一种行为接口规范语言( behavioral interface spec ...
- OO第三单元总结——JML规格
一.JML简介 1.JML语言的理论基础 JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML是一种行为接口规格语言 (Behavior In ...
- 【面向对象】第三单元总结——JML
梳理JML语言的理论基础.应用工具链情况 JML语言理论基础 JML(Java Modeling Language)是一种行为规范接口语言,通过使用不会被编译的注释形式,和固定关键字的语法,指定Jav ...
随机推荐
- codefoces B - Phoenix and Beauty
原题链接:https://codeforc.es/problemset/problem/1348/B 题意:告诉我们一个数组及其长度和k,判断是否可以构造一个新数组使得每K段长度和都相等. 思路:首先 ...
- vps-java环境配置
昨天准备用vps打一台反序列化的机子要java环境,java环境安装了但是javac一直用不了,鼓捣了一段时间把配置文件给搞没了,只好重装vps,找了很多帖子才搞定,为了防止下次继续出现这种情况又要重 ...
- CPython-对象/类型系统
Python中一切皆对象,包括实例对象和类型对象,如整数.浮点数.字符串是实例对象,整数类型.浮点数类型.字符串类型是类型对象. # [Python]>>> n=10 >> ...
- Mybatis日志源码探究
一.项目搭建 1.pom.xml <dependencies> <dependency> <groupId>log4j</groupId> <ar ...
- 有关指针和C语言中的常量
常量类型(五种): 字面常量(2,3,6....) ; enum 定义的枚举常量; 字符常量('a','b'....) ; ...
- Windows Service 2016 Datacenter\Stand\Embedded激活方法
安装好系统后连入互联网之后使用管理员身份打开命令行 输入命令 slmgr /skms kms.03k.org 弹出窗口提示模式修改成功后再输入命令:slmgr /ato 以下为各个版本的key 版本: ...
- kali 2019-4中文乱码解决方法
1.更换阿里源 编辑源,apt-get update && apt-get upgrade && apt-get clean ,更新好源和更新软件 #阿里云deb ht ...
- Linux pgrep命令
1 pgrep pgrep是一个根据名称查找进程ID的命令,返回的是进程ID,若存在当个进程,则分为不同的行返回ID(默认实现). 2 示例 查找java进程: pgrep java 上图还显示了ps ...
- linux 更新python3.8
1 下载源码 地址 选版本下载即可,目前最新为3.8.2版本. 2 解压 tar -zxvf Python-3.8.2.tgz cd Python-3.8.2 3 新建安装目录 安装目录在/usr/l ...
- 实现Web请求后端Api的Demo,实现是通过JQuery的AJAX实现后端请求,以及对请求到的数据的解析处理,实现登录功能
本篇实现Web请求后端Api的Demo,实现是通过JQuery的AJAX实现后端请求,以及对请求到的数据的解析处理,实现登录功能需求描述:1. 请求后端Api接口地址2. 根据返回信息进行判断处理前端 ...