概述
本单元的主要内容是JML,JML是一种对Java程序进行规格化设计的表示语言。本单元的任务是按照JML语言所描述的规格编写类和方法,虽然编写的代码量并不大,但是我们需要考虑自己实现的架构和性能,否则在强测中很容易超时。
一、JML语言的理论基础及应用工具链
(1)JML语言理论基础
注释结构
- 行注释的表示方式 为 //@annotation ,
- 块注释的表示方式为 /* @ annotation @*/ 。
方法规格
- 前置条件(pre-condition) (使用requires子句实现)
- 后置条件(post-condition) (使用ensures子句实现)
- 副作用范围限定(side-effects) (使用assignable/modifiable子句实现)
类型规格
- 不变式invariant (可见状态下都必须满足的特性)
- 状态变化约束constraint (在变化时满足的一些约束)
原子表达式
- esult表达式:表示一个非 void 类型的方法执行所获得的结果,即方法执行后的返回值。
- old(expr)表达式:用来表示一个表达式 expr 在相应方法执行前的取值。
- ot_assigned(x,y,...)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。
- ot_modified(x,y,...)表达式:与上面的 ot_assigned表达式类似,该表达式限制括号中的变量在方法执行期间的取值未发生变化。
- onnullelements(container)表达式:表示 container 对象中存储的对象不会有 null 。
- ype(type)表达式:返回类型type对应的类型(Class)。
- ypeof(expr)表达式:该表达式返回expr对应的准确类型。
量化表达式
- forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。
- exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。
- sum表达式:返回给定范围内的表达式的和。
- product表达式:返回给定范围内的表达式的连乘结果。
- max表达式:返回给定范围内的表达式的最大值。
- min表达式:返回给定范围内的表达式的最小值。
- um_of表达式:返回指定变量中满足相应条件的取值个数。
(2)应用工具链
OpenJML
检查JML规格的正确性,主要调用SMT Solver进行检查。
JMLUuitNG
可根据规格自动化生成测试样例,进行单元测试。
二、JMLUnitNG使用实例
使用讨论区大佬的方法简单测试了一下第二次作业的MyGraph类,可以看到自动生成的测试样例基本是根据输入数据类型的边界进行暴力测试,个人感觉对于证明本单元需求的正确性没有帮助,所以就没有深入测试。可以看到自动生成样例方法的局限性。
三、架构设计分析
(1)第一次作业
第一次作业实现思路和架构很简单,需要注意的是MyPath类中使用一个Hashmap计算不同点个数,需要查询时直接返回Hashmap大小即可,MyPathContainer类中同理,这样查询时间是常数级别。
MyPathContainer类中维护了两个Hashmap,即Path到Id和Id到Path的两个映射,这样保证了查询的效率,因此也需要为MyPath类重写一个Hashcode()方法,需要注意add()和remove()相关指令中对类内成员变量的修改。
(2)第二次作业
第二次作业MyPath类和第一次作业实现相同,MyGraph类在第一次作业的MyPathContainer类的基础上实现了图结构。我的具体实现是使用两个Hashmap存储边和最短路,这样查询的时候只需要查询Hashmap中是否存在相应的Key即可,最短路采用Floyd算法,在add()和remove()后计算出所有点之间的最短距离。但是由于对Hashmap操作时间常数较大,导致强测超时。在修改后我使用了静态数组存储边和最短路,同时维护一个Node到数组下标的映射。
(3)第三次作业
第三次作业增加了最少换乘、最少票价、最少不满意度、联通块数量四个需求。我参照了讨论区大佬的算法,在MyPath类的构造函数中使用Floyd算法计算了这条路径中任意两点的距离和不满意度,并存储在数组中,为MyRailwaySystem类建图提供数据。在MyRailwaySystem类中,整体架构仍然延续上一次作业,即add()和remove()方法之后进行建图和Floyd计算。区别是使用四个不同的静态二维数组分别存储不同的需求值,为确保正确性在初始化建图时根据MyPath类中的对应数组元素采用不同的策略。求联通块数量则使用并查集在初始化建图时一并计算。
四、BUG分析
第一次作业和第三次作业均未发现BUG。
在第二次作业中,由于我采用的Floyd+Hashmap存储图的的算法和数据结构,在Floyd方法中有大量对Hashmap的操作导致强测CPU超时。修复方法就是使用静态数组存储图,为此维护了一个Node到数组下表的Hashmap,修改后运算速度有很大提升。
五、心得体会
个人认为,JML语言是用一种规范的语言描写程序设计中对于不同模块需求。相对于自然语言,它的信息传递效率可能没有那么高,但是却确保了准确性,在大型工程项目或多人协作的场景中能够发挥更大作用,避免不必要的信息传达错误,保证正确性。
在本单元的实际操作中,我也发现了JML语言的缺点,尤其是在第三次作业中尤甚。在第三次作业中,随着需求的愈加复杂,为了能正确描述每个方法的规格,动辄就是五六行冗长的并列量化表达式,看的让人十分头疼(于是我就基本只看了指导书);不仅如此,有时为了描述一个方法,还需要定义一些新的函数辅助JML语言的描述,但是对我们的实现并没有任何帮助。
另外,本单元的作业也告诉我不要过于相信Java库的运行速度。即使是面向对象编程,一味不考虑算法复杂度而滥用系统库的话,最终的结果就是白给。