前言

这一章的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的更多相关文章

  1. OO第三单元——基于JML的社交网络总结

    OO第三单元--基于JML的社交网络总结 一.JML知识梳理 1)JML的语言基础以及基本语法 JML是用于java程序进行规格化设计的一种表示语言,是一种行为接口规格语言.其为严格的程序设计提供了一 ...

  2. OO第三单元作业(JML)总结

    OO第三单元作业(JML)总结 目录 OO第三单元作业(JML)总结 JML语言知识梳理 使用jml的目的 jml注释结构 jml表达式 方法规格 类型规格 SMT Solver 部署JMLUnitN ...

  3. OO第三单元总结——JML

    目录 写在前面 JML理论基础 JML工具链 JMLUnitNG的使用 架构设计 Bug分析 心得体会 写在前面 OO的第三单元学习结束了,本单元我们学习了如何使用JML语言来对我们的程序进行规格化设 ...

  4. 2020 OO 第三单元总结 JML语言

    title: 2020 OO 第三单元总结 date: 2020-05-21 10:10:06 tags: OO categories: 学习 第三单元终于结束了,这是我目前为止最惨的一单元,第十次作 ...

  5. 第三单元总结——JML契约式编程

    OO第三单元博客作业--JML与契约式编程 OO第三单元的三次作业都是在课程组的JML规格下完成.完成作业的过程是契约式编程的过程:设计者完成规格设计,实现者按照规格具体实现.作业正确性的检查同样围绕 ...

  6. 2019年北航OO第三单元(JML规格任务)总结

    一.JML简介 1.1 JML与契约式设计 说起JML,就不得不提到契约式设计(Design by Contract).这种设计模式的始祖是1986年的Eiffel语言.它是一种限定了软件中每个元素所 ...

  7. OO第三单元总结——JML规格设计

    • 1.JML语言的理论基础.应用工具链情况 JML(Java Modeling Language)—— java建模语言,是一种行为接口规范语言( behavioral interface spec ...

  8. OO第三单元总结——JML规格

    一.JML简介 1.JML语言的理论基础 JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML是一种行为接口规格语言 (Behavior In ...

  9. 【面向对象】第三单元总结——JML

    梳理JML语言的理论基础.应用工具链情况 JML语言理论基础 JML(Java Modeling Language)是一种行为规范接口语言,通过使用不会被编译的注释形式,和固定关键字的语法,指定Jav ...

随机推荐

  1. ApiTesting全链路接口自动化测试框架 - 实战应用

    场景一.添加公共配置 我们在做自动化开始的时候,一般有很多公共的环境配置,比如host.token.user等等,如果这些放在用例中,一旦修改,将非常的不便.麻烦(尤其切换环境). 所以这里我们提供了 ...

  2. 力扣 - 208. 实现Trie(前缀树)

    目录 题目 思路 代码 复杂度分析 题目 208. 实现 Trie (前缀树) 思路 在我们生活中很多地方都用到了前缀树:自动补全,模糊匹配,九宫格打字预测等等... 虽然说用哈希表也可以实现:是否出 ...

  3. Http请求状态码302,已得到html页面但未跳转?HttpServletRequest转发/HttpServletResponse重定向后,前端页面未跳转?Ajax怎么处理页面跳转?

    论断 出现此类错误,服务器端出现问题的可能性不大,大概率是前端问题. 问题概述 事情是这样的,我在用Java开发后端.前端页面使用jQuery库的 $.getJSON() 方法发送了一个Ajax请求. ...

  4. Java学习笔记--文件IO

    简介 对于任何程序设计语言,输入和输出(Input\Output)都是系统非常核心的功能,程序运行需要数据,而数据的获取往往需要跟外部系统进行通信,外部系统可能是文件.数据库.其他程序.网络.IO设备 ...

  5. [矩阵乘法]裴波拉契数列III

    [ 矩 阵 乘 法 ] 裴 波 拉 契 数 列 I I I [矩阵乘法]裴波拉契数列III [矩阵乘法]裴波拉契数列III Description 求数列f[n]=f[n-1]+f[n-2]+1的第N ...

  6. JVM(三)类加载与字节码技术

    1.类文件结构 首先获得.class字节码文件 方法: 在文本文档里写入java代码(文件名与类名一致),将文件类型改为.java 在文件对应目录下运行cmd,执行javac XXX.java 以下是 ...

  7. 05_pytorch的Tensor操作

    05_pytorch的Tensor操作 目录 一.引言 二.tensor的基础操作 2.1 创建tensor 2.2 常用tensor操作 2.2.1 调整tensor的形状 2.2.2 添加或压缩t ...

  8. Tony老师带你来看Java设计模式:代理模式

    目录 定义 作用 意图 主要解决问题 优缺点 与装饰者模式的区别 结构 从Tony老师来看实现方式 静态代理 动态代理 JDK动态代理的实现 cglib动态代理的实现 定义 为其他对象提供一种代理来控 ...

  9. 008-Java中方法的使用(进阶篇)

    目录 一.方法的重载(overload) 一.什么是方法的重载 二.方法执行时的内存变化 一.JVM主要三块内存空间 二.关于栈的数据结构(如图) 三.方法执行过程内存变化(用以下代码演示) 三.方法 ...

  10. Azure CDN 为静态网站创建内容分发网络

    一,引言 最近刚刚接触 Edi.Wang 的 Moonglade 博客系统,正好这套系统中有使用到 Azure CND (内容分发网络),那就学习学习.那么今天就尝试利用 Azure CDN 来发布静 ...