• 1、JML语言的理论基础、应用工具链情况

  JML(Java Modeling Language)—— java建模语言,是一种行为接口规范语言( behavioral interface specification language, BISL)。同时,JML也是一种进行详细设计的符号语言,它鼓励你用一种全新的方式来看待Java的类和方法,既规定了方法或抽象数据类型的接口,也规定了它们的行为。

  面向对象的分析和设计(OOAD)的一个重要原则就是过程性的思考应该尽可能地推迟,先以一定的规范建立框架,这类的规范通常被称作面向模型。

注释结构

//@ annotation   行注释

/*@ annotation*/ 块注释

原子表达式

\result             表示返回值

\old(x)            表示x在该方法运行前的取值

\not_assigned(x,y,...)     表示括号内对象不被赋值

\not_modified(x,y,...)    表示括号内对象不被修改

nonnullelements(container)  表示container的存储对象没有null

type(x)             表示x的类型

typeof(x)           表示x的准确类型

量化表达式

\forall 全称量词

\exists 存在量词

\sum    一组对象的和

操作符

expr1<==>expr2   等价关系操作符

expr1==>expr2    推理操作符

\nothing      空操作符

\everything     全部对象操作符

方法规格

requires X   pre-condition

ensures X   post-condition

assignable  赋值

pure       关键字

singals     子句

  下图是JML官网上的应用工具链详情,具体包括JML-launcher、jmlc-gui、jmle、Jtest、JML-Junit等相关工具 。

  一般而言,JML有两种主要的用法:

  1)  开展规格化设计。

  2)  书写已有的代码实现的规格,从而提高代码的可维护性。

• 2、部署SMT Solver

   由于本地安装出现了很多问题(主要集中在多版本JDK共存上),所以我一开始采用openJML官网的线上测试,来体验SMT Solver的功能。

    测试代码:

public class MaybeAdd {
// requires a > 0;
//@ requires b > 0
//@ ensures \result = a+b;
public static int add(int a, int b){
return a-b;
} public static void main(String args[]){
System.ou.println(add(2,3));
}
}

    测试结果:

/tmp/tmp9iwx_W/MaybeAdd.java:3: error: The expression is invalid or not terminated by a semicolon
//@ requires b > 0
^
/tmp/tmp9iwx_W/MaybeAdd.java:4: error: unexpected type
//@ ensures \result = a+b;
^
required: variable
found: value
/tmp/tmp9iwx_W/MaybeAdd.java:4: error: Assignments are not allowed where pure expressions are expected
//@ ensures \result = a+b;
^
/tmp/tmp9iwx_W/MaybeAdd.java:10: error: cannot find symbol
System.ou.println(add(2,3));
^
symbol: variable ou
location: class System
4 errors 

  后来我在同学的电脑(openJML配置成功,部署SMT Solver)上进行了openJML的测试(在这里感谢zjh大佬的帮助),结果如下:

  1) 测试程序:

 public class TestPath{
//@ public instance model non_null int[] nodes;
private int [] nodes; public TestPath(int... nodeList) {
nodes = nodeList;
} //@ ensures \result == nodes.length;
public /*@pure@*/int size() {
return nodes.length;
} /*@ requires index >= 0 && index < size();
@ assignable \nothing;
@ ensures \result == nodes[index];
@*/
public /*@pure@*/int getNode(int index) {
return nodes[index];
} public static void main(String[] args) {
MyPath demo=new MyPath(1,5,10);
System.out.println(demo.size());
System.out.println(demo.getNode(2));
System.out.println(demo.getNode(0));
}
}

    测试结果:

   这是由于规格中要求node为public,而在实现过程中node为private,并且openJML找不到private的对象,所以报错如上图所示。

  2) 测试程序:

 public class TestPath{
//@ public instance model non_null int[] nodes;
public int [] nodes; public TestPath(int... nodeList) {
nodes = nodeList;
} //@ ensures \result == nodes.length;
public /*@pure@*/int size() {
return nodes.length;
} /*@ requires index >= 0 && index < size();
@ assignable \nothing;
@ ensures \result == nodes[index];
@*/
public /*@pure@*/int getNode(int index) {
return nodes[index];
} public static void main(String[] args) {
MyPath demo=new MyPath(1,5,10);
System.out.println(demo.size());
System.out.println(demo.getNode(2));
System.out.println(demo.getNode(0));
}
}

    测试结果:

  这是由于在程序中使用node,没有使用this.node,前者对于IDEA来说可以接受,等价于后者,但是在openJML中,由于严格规格的要求,两者不等价,导致默认前者为一个方法内的局部对象,所以找不到,报错如上图所示。

  3) 测试程序:

 public class TestPath{
//@ public instance model int[] nodes;
public int [] node; public TestPath(int... nodeList) {
this.node = nodeList;
} //@ ensures \result == nodes.length;
public /*@pure@*/int size() {
return this.node.length;
} /*@ requires index >= 0 && index < size();
@ assignable \nothing;
@ ensures \result == nodes[index];
@*/
public /*@pure@*/int getNode(int index) {
return this.node[index];
} public static void main(String[] args) {
TestPath demo = new TestPath(1,5,10);
System.out.println(demo.size());
System.out.println(demo.getNode(2));
System.out.println(demo.getNode(0));
}
}

    测试结果:

    这是由于创建node的时候没有实现 non_null 的要求,并且没有在规格或方法中写明,当node为空时的操作。

• 3、部署JMLUnitNG/JMLUnit

  使用JunitNG可以实现简单的程序测试和验证,下载好对应的jar包后,运行相关指令即可完成测试。

(参考资料:https://course.buaaoo.top/assignment/71/discussion/199 )

  测试函数如下:

 package test;

 public class Test {
/*@ ensures \result == (a + b);
@ */
public static int add(int a, int b) {
return a + b;
} public static void main(String[] args) {
add(1, 999);
}
}

  运行结果如下图所示:

 [TestNG] Running:
Command line suite Failed: racEnabled()
Passed: constructor Test()
Passed: static add(-2147483648, -2147483648)
Passed: static add(0, -2147483648)
Passed: static add(2147483648, -2147483648)
Passed: static add(-2147483648, 0)
Passed: static add(0, 0)
Passed: static add(2147483648, 0)
Passed: static add(-2147483648, 2147483648)
Passed: static add(0, 2147483648)
Passed: static add(2147483648, 2147483648)
Passed: static main(null)
Passed: static main({}) ===============================================
Command line suite
Total tests run: 13, Failures: 1, Skips: 0

  可以发现,自动生成的样例主要是在各种边界情况,比如int的范围(最大值、最小值、正负数、零),传参的类型(int、object、NULL)。对程序的鲁棒性进行了较好的测试。

• 4、梳理架构设计

作业一、MyPathContainer规格设计

1. 统计信息图

2. 复杂度分析

基本复杂度(Essential Complexity (ev(G))、模块设计复杂度(Module Design Complexity (iv(G)))、Cyclomatic Complexity (v(G))圈复杂度

OCavg为平均循环复杂度;WMC为总循环复杂度

3. 结构信息图

可见,由于逻辑简单,本次作业的复杂度和耦合度都不高。

4. 分析架构设计

  为了减少时间复杂度,采用的是三个hashmap的策略(起先我用的是单hashmap,但是由于hashmap的getValue方法的时间复杂度是O(n),所以对于一些极端数据存在超时的风险),效果明显,时间收益很好。也为以后减少时间开销打下了基础。

作业二、MyGraph规格设计

1. 统计信息图

2. 复杂度分析

基本复杂度(Essential Complexity (ev(G))、模块设计复杂度(Module Design Complexity (iv(G)))、Cyclomatic Complexity (v(G))圈复杂度

OCavg为平均循环复杂度;WMC为总循环复杂度

3. 结构信息图

可见,由于逻辑简单,只比上次作业多了迪杰斯特拉算法,所以本次作业的复杂度和耦合度都不高。

4. 分析架构设计

  在上一次作业的基础上,采用使用优先队列的迪杰斯特拉算法实现最小路径,并将中间结果储存,减少重复计算,以最大化减少运行时间。

  架构设计不好,虽然没有重构,但是直接将上一次作业的内容复制粘贴,而没有采用继承等方式实现。层次性不明显,框架比较冗杂。  

  对下一次作业也不友好,没有对迪杰斯特拉算法建模或单独建类,复用起来较为困难。

  完成作业时,还不是特别明白规格设计的意图,和建模建类的重要性。所以对结构方面的考虑较少,导致第三次作业有了比较大的重构,花费了很多时间,还不利于扩展和DEBUG。

作业三、MyRailwaySystem规格设计

1. 统计信息图

2. 复杂度分析

基本复杂度(Essential Complexity (ev(G))、模块设计复杂度(Module Design Complexity (iv(G)))、Cyclomatic Complexity (v(G))圈复杂度

OCavg为平均循环复杂度;WMC为总循环复杂度

3. 结构信息图

  可见,本次作业功能增加,复杂度和耦合度上升,尤其是addPathGraph和removePathGraph两个函数。

  因为图结构发生变化,要对多个不同功能的初始邻接表(initXXX)和一些结果邻接表(connect,edge等)进行计算,所以复杂度和耦合度较高。

4. 分析架构设计

  在上次作业的基础上,进行了一定的重构。

  首先是将迪杰斯特拉和弗洛伊德等最短路径算法分离出去,单独建类,并将对于图的有关操作单独建类。

  其次,模型采用评论区大佬的“完全图方法”,主要算法是弗洛伊德算法,保存所有中间矩阵和初始建图矩阵。

  整体速度还不错,但是极端情况CPU时间会贴近35s的限制。

  

• 5、按照作业分析代码实现的bug和修复情况

 1) 第一次作业中未出现BUG,互测中也未发现BUG。

 2) 第二次作业中未出现BUG,互测中发现了同学的一个BUG,当查询在图中的某一点到这一点的距离时,应该输出:

Ok, length is .

  但是他输出:

Ok, length is .

  这个BUG我在第二次作业没有发现,但在第三次作业自测中发现过,原因是没有进行特判两个点为相同时的距离。因为按照最短路径算法,点A与点A之间没有边的话,那么A-A之间的最短路径为A-B(某一个和A相连的点)-A,距离为2,导致出错。

  犯这个错误的原因之一可能是没有好好看JML规格说明

 3) 第三次作业中出现一个BUG,出错点是判断连通块个数出错。

  我判断连通块是通过遍历paths中所有的path,如含有path_new中的点,则将两者判为连通块。

  出错原因:当pathA和pathB连通时,pathC加入,若pathA中含有pathC中点,pathB中不含有pathC中的点,那么我会判断pathA和pathC连通,但是判断pathB和pathC不连通,与实际不符。

e.g.
pathA : 1-2-3-4-5-6-7
pathB : 1-3-5-7-9-11
pathC : 2-4-6-8-10-12 

  解决措施:在判断时,将所有与path_new连通的path的标志号记下,最后再遍历一遍paths,将所有标志号在其中的path,判断与path_new连通。

    反思: 这个错误的出现更深层次的原因是自己太懒,在写连通块个数的算法时,本应该建树查找,更加稳妥,但是我采用了自己的想出来的“简单”方法,贪图一时休息,却导致了BUG的发生。

  在互测中,我一共发现了同组同学的4类不同BUG:

   1. 对于输入:

PATH_ADD  2
CONTAINS_PATH 2 2

错误输出 NO. ,应该是 YES.

原因:2-2进入图中变为一个点,而没有记下自环边(2-2)

   2. 对于输入:

CONNECTED_BLOCK_COUNT

错误输出

Ok, connected block count is xxx.  (xxx是全部点的个数)

应该输出

Ok, connected block count is 0.

错误原因猜测:建图时,对每个点建立一棵树,但是在查询时没有及时更新数据,导致输出的是“原始”结果。

   3. 对于输入:

LEAST_UNPLEASANT_VALUE 15 143

错误输出

Ok, least unpleasant value is 99968.

应该输出

Ok, least unpleasant value is 1060.

错误原因猜测:计算不满意度方法中存在一个“上限”或者计算方法通过减法等,但这组数据的不满意度超过限制范围,导致计算结果出错。

  4. 对于复杂输入, CPU_TIME_LIMIT_EXCEED ,原因为程序复杂度过高,没有很好的完成时间限制的要求。

• (6)阐述对规格撰写和理解上的心得体会

  对于规格这个东西,第一感觉是“什么?我为什么要写这奇奇怪怪没用的东西?”。但当我真正实践过后,发现我之所以怎么想,是因为有指导书的存在,其实指导书就是类似于一种规格。只是JML规格更加精准,减少语言上的二义性或者模糊性

  在本单元的作业中,我对于规格的理解和灵活运用方面比较欠缺,有时候会被规格束缚了手脚,比如在完成某个方法时,不会跳出规格的思路,采用一些简便的方法实现:

  以下在实现MyPath的equals方法时,直接按照JML规格一步步写完,用原始的flag判断结束等,导致不美观,易写错。

     public boolean equals(Object obj) {
if (obj instanceof Path) {
Path path = (Path) obj;
if (path.size() == size()) {
int flag = 1;
for (int i = 0; i < size(); i++) {
if (path.getNode(i) != getNode(i)) {
flag = 0;
break;
}
}
return flag == 1;
}
}
return false;
}

  在层次化规格上,我做的不好,本单元三次作业其实有着严格的继承关系,而我没有发现,还在简单复制粘贴方法。如果使用继承关系,可以较好的分离出不同功能的对象,较好的解耦。

  对于JML较为复杂的第三次作业中,我没有特别理解课程组设置几个选择性实现的方法的意义,当时为了省时间,直接按照自己的理解写程序,写完之后又发现和要求不匹配,改了挺久的程序。其实应该完成那几个方法,通过完成的过程,就能理解题意,还能较少错误,其实会更省时间。(这就说明了,先想明白架构、逻辑,远比先写代码重要的多)。

  对于建模,我只是初步的将最短路径算法和一些有关图的计算封装成独立的类,但是并没有从点、边一层一层建立起图结构,也没有对四种不同的“最短路径“建立统一接口,用工厂方法实现统一模型下的不同图结构及其计算模型。

  要学的东西还是很多,不过最起码知道要学什么,希望自己以后能深入学习有关规格设计统一建模的资料。共勉~

OO第三单元总结——JML规格设计的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

    OO第三单元--JML规格化设计 JML语言的理论基础以及应用工具链情况 理论基础 JML是对JAVA程序进行规格化设计的一种表示语言,是一种行为接口规格语言.JML整合了Java和JAVAdoc,并 ...

  8. 规格化设计——OO第三单元总结

    规格化设计--OO第三单元总结 一.JML语言理论基础.应用工具链 1.1 JML语言 ​ JML(java modeling language)是一种描述代码行为的语言,包括前置条件.副作用等等.J ...

  9. OO第三单元(地铁,JML)单元总结

    OO第三单元(地铁,JML)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉并了解JML来是我们具有规格化编程架构的思想.这个单元的主题一开始并不明了,从第一次作业的路径到第二次 ...

随机推荐

  1. 2.SSH 两个表全套增删改(运动员住宿管理)

    0.创建如下oracle的命令 create table HOTALINFO ( HOTALID ) not null, HOTALNAME ) not null, HOTALADDRESS ) no ...

  2. MyBatis中动态SQL元素的使用

    掌握MyBatis中动态SQL元素的使用 if choose(when,otherwise) trim where set foreach <SQL>和<include> 在应 ...

  3. [Linux]第三部分-学习Shell和Shell脚本

    vim 高级的 vii o a 进入编辑模式 esc进入一般模式:wq离开alias vi='vim' 使用vim打开viv块选择 y复制反白,d删除反白在vi中打开一个文件后,可以使用 sp fil ...

  4. HDU 1171 Big Event in HDU(多重背包)

    Big Event in HDU Problem Description Nowadays, we all know that Computer College is the biggest depa ...

  5. Oracle学习(11):PLSQL程序设计

    PL/SQL程序结构及组成 什么是PL/SQL? •PL/SQL(Procedure Language/SQL) •PLSQL是Oracle对sql语言的过程化扩展 •指在SQL命令语言中添加了过程处 ...

  6. CDH使用秘籍(一):Cloudera Manager和Managed Service的数据库

    背景 从业务发展需求,大数据平台须要使用spark作为机器学习.数据挖掘.实时计算等工作,所以决定使用Cloudera Manager5.2.0版本号和CDH5. 曾经搭建过Cloudera Mana ...

  7. 2014.08.04,读书,读书笔记-《Matlab概率与数理统计分析》-第1章 MATLAB的数据基础

    第1章 MATLAB数据基础 虽然一直间或使用MATLAB,但从来没有系统的学习过,现在开始也不晚.先对几个重点或者平时忽略的要点做下笔记. %后的所有文字为注释,多条命令可以放在一行,但要用逗号或分 ...

  8. 广东工业大学2016校赛决赛-网络赛 1174 Problem F 我是好人4 容斥

    Problem F: 我是好人4 Description 众所周知,我是好人!所以不会出太难的题,题意很简单 给你n个数,问你1000000000(含1e9)以内有多少个正整数不是这n个数任意一个的倍 ...

  9. jquery 弹窗插件 layer

    jquery 弹窗插件 layer 官网:http://sentsin.com/jquery/layer/ 1 <!DOCTYPE html PUBLIC "-//W3C//DTD H ...

  10. NESTED LOOPS & HASH JOIN & SORT MERGE JOIN

    表连接方式及使用场合 NESTED LOOP 对于被连接的数据子集较小的情况,nested loop连接是个较好的选择.nested loop就是扫描一个表,每读到一条记录,就根据索引去另一个表里面查 ...