OO第三单元作业(JML)总结
OO第三单元作业(JML)总结
JML语言知识梳理
使用jml的目的
- 开展规格化设计
- 针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性
jml注释结构
在我目前看来,jml其实就是一门被规定好格式要求的注释语言,它的作用也是给方法、类等一个相比自然语言来说更加严谨完善的介绍。它主要有三种注释方法:
//@ public model non_null int [] elements;
/*@ blablabla
@ blablabla
@*/
public abstract/*@ blabla @*/intlargest();
jml表达式
jml表达式的语法还是比较贴近自然语言的,用起来感觉挺容易上手也挺有趣的,整理一下比较眼熟常用的表达式
原子表达式 (约定好的代表一个值的表达式)
\result :表示非void方法返回值
\old(expr) : 表示expr在执行方法前的样子
\not_assigned(x,y,...) : 方法是否对x,y等变量赋值,boolean型表达式
\not_modified(x,y,...) :方法是否改变x,y等变量的值,boolean型表达式
\nonnullelements(container) : 容器中不会有null
量化表达式 (类似离散数学中的符号)
\forall表达式:全称量词修饰,用法如
(\forall int i,j; 0 <= i && i < j && j < 10; a[i] < a[j])
\exists : 存在量词修饰,用法类似 forall
\sum : 求和,用法如
(\sum int i; 0 <= i && i < 5; i*i) 解释:对于int i;i满足的条件;求和对象
\max : 求最大,用法类似sum
\min : 求最小,用法类似sum
操作符 (类似离散)
等价关系操作符:(没太理解<==>与==之间的异同)
b_expr1<==>b_expr2 or b_expr1<=!=>b_expr2
推理操作符:
b_expr1==>b_expr2 or b_expr2<==b_expr1
变量引用操作符:
\nothing:空集,\everything:全集
方法规格
正常行为规格与异常行为规格
/*@ normal_behavior
@ bla bla bla bla bla
@ also
@ exceptional_behavior
@ bla bla bla
@*/
前置条件
/*@ ??_behavior
@ requires P; //表示需要表达式P为真
@ bla bla bla
@*/
后置条件
/*@ bla bla bla
@ ensures P; //表示保证表达式P为真
@ bla bla bla
@*/
副作用限制
assignable
modifiable
signals子句 (用来指示抛出异常)
// @ signals (***Exception e) b_expr //当 b_expr 为真时,抛出指定异常
类型规格
不变式invariant
要求在所有可见状态下都必须满足的特性,可根据invariant约定写出repOk函数,配合assert使用尤佳(荣老师的强烈推荐),可以直接有效的定位在何处导致代码出现违背不变式的bug
状态变化约束constraint
对象的状态在变化时必须满足的一些约束
SMT Solver
暂时先不研究
部署JMLUnitNG与使用
首先十分感谢伦佬在讨论区的精彩分享,教会我如何配置jmlunit进行测试
我测试的代码如下:
public class Testjml {
/*@ public normal_behaviour
@ ensures \result <==> (i1 == i2);
*/
public static boolean intEquals(int i1, int i2) {
return i2 == i1;
}
/*@ public normal_behaviour
@ requires s1 != null && s2 != null;
@ ensures \result <==> (s1 == s2);
*/
public static boolean stringEquals(String s1, String s2) {
return s1.equals(s2);
}
}
按照伦佬的教程,将openjml和jmlunitng都设置成命令

之后,在源文件所在文件夹下依次执行以下命令:
jmlunitng Testjml.java
javac -cp jmlunitng.jar *.java
openjml -rac Testjml.java

可以得到文件树:
.
├── jmlunitng.jar
├── PackageStrategy_int.class
├── PackageStrategy_int.java
├── PackageStrategy_java_lang_String.class
├── PackageStrategy_java_lang_String.java
├── Testjml.class
├── Testjml_ClassStrategy_int.class
├── Testjml_ClassStrategy_int.java
├── Testjml_ClassStrategy_java_lang_String.class
├── Testjml_ClassStrategy_java_lang_String.java
├── Testjml_InstanceStrategy.class
├── Testjml_InstanceStrategy.java
├── Testjml_intEquals__int_i1__int_i2__0__i1.class
├── Testjml_intEquals__int_i1__int_i2__0__i1.java
├── Testjml_intEquals__int_i1__int_i2__0__i2.class
├── Testjml_intEquals__int_i1__int_i2__0__i2.java
├── Testjml.java
├── Testjml_JML_Test.class
├── Testjml_JML_Test.java
├── Testjml_stringEquals__String_s1__String_s2__20__s1.class
├── Testjml_stringEquals__String_s1__String_s2__20__s1.java
├── Testjml_stringEquals__String_s1__String_s2__20__s2.class
└── Testjml_stringEquals__String_s1__String_s2__20__s2.java
0 directories, 23 files
执行测试:
java -cp jmlunitng.jar Testjml_JML_Test
可以得到测试结果:

发现程序运行出错,测试结果很明确的让我门发现了没有处理传入参数为null的情况,及时改正代码(瞎改改,主要为了测试jml)增加处理null的情况如下:
public class Testjml {
/*@ public normal_behaviour
@ ensures \result <==> (i1 == i2);
*/
public static boolean intEquals(int i1, int i2) {
return i2 == i1;
}
/*@ public normal_behaviour
@ requires s1 != null && s2 != null;
@ ensures \result <==> (s1 == s2);
*/
public static boolean stringEquals(String s1, String s2) {
if (s1 == null || s2 == null) {
return true;
} else {
return s1.equals(s2);
}
}
}
再次按相同步骤运行可以得到:

这次就全部通过啦!
再次感谢各位大佬在讨论区精彩分享的帮助!
三次作业架构设计梳理
第一次作业
第一次作业要求我们实现两个类MyPath以及MyPathContainer来完成以下几个功能:
添加路径
删除路径
根据路径id删除路径
查询路径id
根据路径id获得路径
获得容器内总路径数
根据路径id获得其大小
根据路径id获取其不同的结点个数
接结点序列判断容器是否包含路径
根据路径id判断容器是否包含路径
容器内不同结点个数
根据字典序比较两个路径的大小关系
路径中是否包含某个结点
其中线性指令(需要构建path的指令)较少,而查询指令较多,且题目明确说明需要我们考虑时间复杂度以防超时,这就要求我们在设计之初便考虑好使用何种容器来保存相应的数据。
对于Path类,由于每条path的长度只有1000,而且构造path不可避免的需要遍历一遍输入的节点序列,因此我选择Arraylist作为存path的容器,又因为需要统计path不同节点数,为了简便,我使用java自带的treeset容器在构造path时就将节点扔进去,这样无论是增长路径或者截断路径,统计不同节点数方面都很好拓展。
对于PathContainer类,根据需求描述,最花时间的操作即确定路径id与路径间的对应关系,而我们在path中实现了compareto接口,因此我选择treemap来保存路径与路径id间的映射关系,且路径id是不会重复并且小于300的,那么可以直接使用数组来存储id与路径的映射关系,因为要确定不同节点的数量,我使用了treemap来维护节点的出现情况。


第二次作业
第二次作业新拓展的需求为
容器中是否存在某一条边
两个结点是否连通
两个结点之间的最短路径
这次作业中之前各类指令数量的比例不变,在考虑过后,认为可以直接继承上一次的代码,只需拓展构图即可。
构图方面,我也比较简单粗暴,直接在graph类里进行,为了防止重复连边,使用treeset保存一个点与哪些点之间直接相连,因为构图指令较少,每次图结构发生变化时,我都重新进行构图,并且使用n遍bfs求出每个点到其他点的最短距离保存下来。
然而由于个人太懒,而且没有仔细考虑代码的维护和拓展性,没有想到需要构造出多种不同的图,也就没有听从指导书的建议,将算法和建图封装成单独的类,这就导致整个类显得很冗杂。


第三次作业
第三次作业新拓展的需求为:
整个图中的连通块数量
两个结点之间的最低票价
两个结点之间的最少换乘次数
两个结点之间的最少不满意度
这次作业要新拓展的功能很多,不过基本上也都是对建图和最短路算法的改变,如果还像之前那样直接全塞在MyRailwaySystem中,那么这个类将变得“又臭又长”(可能我自己写得丑),因此,我选择将构图和求最短路封装起来,每次要构图时,将已有的path信息交给图类,令其构造出符合条件的图,此外为了构图还新封装了edge类表示图中的边。
除了新加的需求外,之前的需求可以直接复用之前的代码,不再多说。
联通块的数量,我选择在第二次作业bfs时加入染色机制,求连通块,这也是这次作业最简单的需求。
其它三个需求其实本质上都可以使用同一个图连接结构,配以不同的权值,使用最短路算法求得,而这个构图方式需要一定技巧。即将不同路径的点都视作不同的节点,节点标号若相同,则都连在同一源点(类比地铁换乘站)上,并且给这条边给予合理的权值,使得两点间最低票价(不满意度)就是该图上两节点相应源点间最短路。


三次作业中代码的bug及修复
此次三次作业主要是按照提出的需求和给好的架构写代码,因此bug产生的几率也少了很多,三次强测中我都安全度过,happy
然而,如助教所说,度过强测并不意味着代码没有bug,第一次作业和第一次互测中都没被发现bug的我,在第二次互测中被人找到了如下bug

这智障的bug居然是经过我和同学一起检查之后都没发现的,这也从侧面反映了我的测试做的并不充分和全面,单元测试也没有做到100%的覆盖便偷懒收手,值得反思。(也从侧面反映了互测中真的很少有人会通过看别人代码来找bug,否则这么明显只要看代码就白给的bug第一次互测应该是能被发现的)
修复起来也很简单,把最后的return -1;改成 return 1;就ok了,ORZ
规格撰写与理解的心得体会
首先,规格撰写方面,经过两次课上实验写规格的经历,感觉确实如水群中助教所说,写出一个优秀的规格对一个架构师的离散数学基础以及逻辑思维能力都有很大考验,因此JML并没有在工业界广泛推广。写规格时需要跳出程序员的思维,这一点在多次看规格之后才慢慢理解,规格里主要需要写出这个方法的功能,这个方法运行之后元素的状态,而不是在规格中写出得到想要状态的具体实现的过程,个人感觉,一个好的规格规定不是一个过程,也不是一个由具体实现过程推导的结果,而是用逻辑语言描述的结果本身。
其次,个人对jnuit单元测试的理解,我觉得jnuit的正确使用方式应该是在写代码前先按照需求写junit测试代码,这样既可以预先考虑这歌方法需要处理什么情况从而写出完善的代码,还可以边写边测试防止代码bug拖到最后才发现。
最后,吐槽一下,在三次课下代码中,我们并没有写规格的过程,而是按照给定的规格写代码,但实际操作时,我们完全可以通过读指导书以及指令操作知道一个方法需要实现什么样的功能,而且并不觉得规格比自然语言易懂清晰,反而出现了同学们通过对自然语言的理解提出了规格存在的bug(这也从侧面反映了优秀规格的难得和重要),那么这单元的作业实际上对JML的练习完全是依靠个人的自觉自愿来进行的,实际上,就算完全忽略规格,不使用junit还是可以很好的完成作业,因此个人感觉这单元并没有很充分的让大多数人练习到 jml 和 junit 的部分。
OO第三单元作业(JML)总结的更多相关文章
- OO第三单元作业总结
OO第三单元作业总结--JML 第三单元的主题是JML规格的学习,其中的三次作业也是围绕JML规格的实现所展开的(虽然感觉作业中最难的还是如何正确适用数据结构以及如何正确地对于时间复杂度进行优化). ...
- 【OO学习】OO第三单元作业总结
[OO学习]OO第三单元作业总结 第三单元,我们学习了JML语言,用来进行形式化设计.本单元包括三次作业,通过给定的JML来实行了一个对路径的管理系统,最后完成了一个地铁系统,来管理不同的线路,求得关 ...
- OO第三单元——基于JML的社交网络总结
OO第三单元--基于JML的社交网络总结 一.JML知识梳理 1)JML的语言基础以及基本语法 JML是用于java程序进行规格化设计的一种表示语言,是一种行为接口规格语言.其为严格的程序设计提供了一 ...
- OO第三单元作业——魔教规格
OO第三单元作业--魔教规格 JML的理论基础和相关工具 JML(Java Modeling Language,Java建模语言),在Java代码种增加了一些符号,这些符号用来标志一个方法是干什么 ...
- 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理论基础及应用工具链情况 理论基础 1.JML表达式 \result:表示方法执行后的返回值. \old(expr):表示一个表达式expr在相应方法执行前的取值. \foall:全称量词修 ...
- OO第三单元总结——JML规格设计
• 1.JML语言的理论基础.应用工具链情况 JML(Java Modeling Language)—— java建模语言,是一种行为接口规范语言( behavioral interface spec ...
- 2019年北航OO第三单元(JML规格任务)总结
一.JML简介 1.1 JML与契约式设计 说起JML,就不得不提到契约式设计(Design by Contract).这种设计模式的始祖是1986年的Eiffel语言.它是一种限定了软件中每个元素所 ...
随机推荐
- P1601_A+B Problem(高精)(JAVA语言)
思路:BigInteger first blood! //四行搞定 题目背景 无 题目描述 高精度加法,x相当于a+b problem,[b][color=red]不用考虑负数[/color][/b] ...
- C语言之文件操作
C语言之文件操作 在本节我们将会讲解有关文件的读写操作: 纲要: 一些需要掌握的知识点 文件名 文件类型 数据流 文件缓冲区 文件指针 与文件操作相关的一些函数 文件的打开及关闭 文件的顺序读写 文件 ...
- [题解] [NOI Online 2021 入门组 T3] 重力球
题目大意 在一个 \(n\times n\) 的矩形中,题目会给出 \(m\) 个障碍物.有两个小球,你可以选定四个方向(上下左右)的其中一个,小球会朝着这四个方向一直滚动,直到遇到障碍物或是矩形的边 ...
- java例题_47 读取 7 个数(1—50)的整数值,每读取一个值,程序打印出该值个数的*
1 /*47 [程序 47 打印星号] 2 题目:读取 7 个数(1-50)的整数值,每读取一个值,程序打印出该值个数的*. 3 */ 4 5 /*分析 6 * 1.多次读取---for循环 7 * ...
- 获取本机外网ip
获取内网ip ifconfig eth0 | grep 'inet'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $2}' 获取公网ip ifc ...
- 3,turicreate入门 - 优化回归模型,使得预测更准确
turicreate入门系列文章目录 1,turicreate入门 - jupyter & turicreate安装 2,turicreate入门 - 一个简单的回归模型 3,turicrea ...
- Spring Security Oauth2 认证(获取token/刷新token)流程(password模式)
https://blog.csdn.net/bluuusea/article/details/80284458
- 安卓安装kali linux之Termux
解决安装kali无模组问题 https://blog.csdn.net/weixin_44690490/article/details/108599693?utm_source=app 步骤 1.获取 ...
- 承接上一篇,whale系统开篇,聊聊用户认证
写在前面 上次老猫和大家说过想要开发一个系统,从简单的权限开始做起,有的网友表示还是挺支持的,但是有的网友嗤之以鼻,认为太简单了,不过也没事,简单归简单,主要的还是个人技术的一个整合和实战. 没错,系 ...
- 1.设置和验证MySQL数据库的隔离级别
登录数据库 mysql -u root - p 新建用户,各启动一个事物,用于同时操作数据库表中数据 create user tom identified by 'tom'; 然后同时再用该用户启动M ...