OO第三单元总结——JML规格设计
• 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规格设计的更多相关文章
- 2019年北航OO第三单元(JML规格任务)总结
一.JML简介 1.1 JML与契约式设计 说起JML,就不得不提到契约式设计(Design by Contract).这种设计模式的始祖是1986年的Eiffel语言.它是一种限定了软件中每个元素所 ...
- OO第三单元总结——JML规格
一.JML简介 1.JML语言的理论基础 JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML是一种行为接口规格语言 (Behavior In ...
- OO第三单元——基于JML的社交网络总结
OO第三单元--基于JML的社交网络总结 一.JML知识梳理 1)JML的语言基础以及基本语法 JML是用于java程序进行规格化设计的一种表示语言,是一种行为接口规格语言.其为严格的程序设计提供了一 ...
- OO第三单元作业(JML)总结
OO第三单元作业(JML)总结 目录 OO第三单元作业(JML)总结 JML语言知识梳理 使用jml的目的 jml注释结构 jml表达式 方法规格 类型规格 SMT Solver 部署JMLUnitN ...
- 2020 OO 第三单元总结 JML语言
title: 2020 OO 第三单元总结 date: 2020-05-21 10:10:06 tags: OO categories: 学习 第三单元终于结束了,这是我目前为止最惨的一单元,第十次作 ...
- OO第三单元总结——JML
目录 写在前面 JML理论基础 JML工具链 JMLUnitNG的使用 架构设计 Bug分析 心得体会 写在前面 OO的第三单元学习结束了,本单元我们学习了如何使用JML语言来对我们的程序进行规格化设 ...
- OO第三单元——JML规格化设计
OO第三单元--JML规格化设计 JML语言的理论基础以及应用工具链情况 理论基础 JML是对JAVA程序进行规格化设计的一种表示语言,是一种行为接口规格语言.JML整合了Java和JAVAdoc,并 ...
- 规格化设计——OO第三单元总结
规格化设计--OO第三单元总结 一.JML语言理论基础.应用工具链 1.1 JML语言 JML(java modeling language)是一种描述代码行为的语言,包括前置条件.副作用等等.J ...
- OO第三单元(地铁,JML)单元总结
OO第三单元(地铁,JML)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉并了解JML来是我们具有规格化编程架构的思想.这个单元的主题一开始并不明了,从第一次作业的路径到第二次 ...
随机推荐
- Top English interview Q&A
http://www.hjenglish.com/new/p581292/ vocabulary endeavour [ɪn'devər] relevant ['reləvənt] , efficie ...
- jquery日历插件FullCalendar使用技巧
原文链接:http://blog.csdn.net/u013493957/article/details/44920341 FullCalendar是一款基于jquery的日历控件,它有着很强大的 ...
- 在ActivityA中关闭还有一个ActivityB
1.对于简单的两个Activity public class A_activity extends Activity { public static A_activity _instance = nu ...
- XTU1201:模和除
题目描写叙述 两个整数x和y,满足1<=x<=a,1<=y<=b 且x%y等于x/y的x和y的对数有多少? x%y是x除以y的余数, x/y是x除以y的商,即整数除. 输入 不 ...
- Android - 使用JD-GUI反编译Android代码
使用JD-GUI反编译Android代码 本文地址: http://blog.csdn.net/caroline_wendy Android程序出现Bug时,须要依据Bug寻找问题出错的地方; 须要使 ...
- Oracle 中UNDO与REDO的差别具体解释
一 为了更清楚的看出2者差别,请看下表: UNDO ...
- Android图文混排-实现EditText图文混合插入上传
前段时间做了一个Android会议管理系统,项目需求涉及到EditText的图文混排,如图: 在上图的"会议详情"中.须要支持文本和图片的混合插入,下图演示输入的演示样例: 当会议 ...
- oracle 实时查询最耗CPU资源的SQL语句
1. 先通过top命令查看产用资源较多的spid号 2.查询当前耗时的会话ID,用户名,sqlID等: select sid,serial#,machine,username,program,sql_ ...
- ubuntu sublime text 2 破解版
原网地址 : http://blog.csdn.net/icephone/article/details/8217567 关于sublime text 2 的一个使用教程以及前端的不错的学习博客 ht ...
- Redis学习笔记(十二) 高级命令:服务器管理命令
原文链接:http://doc.redisfans.com/server/index.html save 执行一个同步操作,将redis实例的所有数据以rdb的形式保存到硬盘,一般来说,生产环境很少执 ...