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来是我们具有规格化编程架构的思想.这个单元的主题一开始并不明了,从第一次作业的路径到第二次 ...
随机推荐
- mybatis3中几个@Provider的使用方式
Mybatis的原身是ibatis,现在已经脱离了apache基金会,新官网是http://www.mybatis.org/.Mybatis3中增加了使用注解来配置Mapper的新特性,这里主要介绍@ ...
- CF864A Fair Game
CF864A Fair Game 题意翻译 CF864A Fair Game 题意: Petya和Vasya决定玩一个游戏,他们有偶数张卡片,每张卡片上一个数字.每人选择一个数字(两个人选择的数字不能 ...
- POJ 1948
这道题我记得是携程比赛上的一道. 开始时想直接设面积,但发现不可以,改设能否构成三角形.设dp[i][j][k]为前i根木棍构成边长为j和k的三角形,那么转移可以为dp[i][j][k]=dp[i-1 ...
- Android适屏
总结一下自己的适屏经验,仅仅希望自己不断进步,不断完好,假设有热心肠的"前辈"指导一下,不胜感激! Android5.0已经出来了,说是这个版本号对Android屏幕适配做了非常多 ...
- mysql改动用户password
登录root用户 用root用户登录控制台. use mysql use mysql,mysql是mysql数据库自己主动创建的一个数据库. 改动user表的数据 update user set pa ...
- C# DispatcherTimer Start之后立即执行
如果DispatherTimer 的Interval 不是在实例化时赋值,那么Start之后,Tick方法会立即执行一次. DispatcherTimer timer = new Dispatcher ...
- DNS查询报文实例
2.2 DNS查询报文实例 以www.baidu.com为例,用Wireshark俘获分组,结合2.1的理论内容,很容易看明白的,DNS请求报文如下: 图7 DNS请求报文示例 2.3 DNS回答报文 ...
- python类的继承和多态,获取对象信息
继承 类的继承机制使得子类可以继承父类中定义的方法,拥有父类的财产,比如有一个Animal的类作为父类,它有一个eat方法: class Animal(object): def __init__(se ...
- ROS-OPENCV
前言:opencv是一个开源的跨平台计算机视觉库. 前提:1.已下载并编译了相关功能包集,如还未下载,可通过git下载:https://github.com/huchunxu/ros_explorin ...
- java8 Lambad表达式自己的例子
service层方法 public <E> E outer(Function<Session, E> function) { return dao.outer(function ...