一、概述
任务要求:根据jml给定的规格完成代码设计。主要难点在于jml语法理解、算法优化、和JML工具链(完全不会)。
该单元坑点很多,然而每一个坑点我都中了,(因为我在摸鱼),在bugs部分详述。
二、JML基础
简介
JML(Java Modeling Language)是用于对Java程序进行规格化设计、行为接口规格语言(Behavior Interface Specification Language,BISL)。其用处在于开展规格化设计,使得交给实现人员的不是较为模糊的自认语言,而是逻辑严格的规格。
类型规格
主要分为不变式限制(invariant)和状态变化约束(constraint)。不变式(invariant)是要求在所有可见状态下都必须满足的特性。状态变化约束(constraint)用来来对前序可见状态和当前可见状态的关系进行约束。
1 例如: 2 /*@ invariant a != null; 3 @ constraint count == \old(counter) + 1; 4 @*/
方法规格
方法规格的核心内容包括三个方面:前置条件、后置条件和副作用约定。
- 前置条件 : 对方法输入参数的限制,不满足则不能保证正确性。(requires)
- 后置条件 : 对方法执行结果的限制,执行后如果满足则执行正确。(ensures)
- 副作用约定 : 指方法在执行过程中对输入对象或 this 对象进行了修改(对其成员变量进行了赋值,或者调用其修改方法)。(assignable)
此外还有正常行为规格(normal_behavior)和异常行为规格(expcetional_behavior)。分别用关键字normal_behavier和exception_behavior。
1 例如: 2 /*@ normal_behavior 3 @ requires z >=0 && z <= 100; 4 @ assignable \nothing; 5 @ ensures \result == z + 1; 6 @ also 7 @ exceptional_behavior 8 @ requires z < 0; 9 @ assignable \nothing; 10 @ signals_only IllegalArgumentException; 11 @*/
工具链
常用的JML工具主要有OpenJML(对JML进行语法检查、配合Solver进行简单的静态验证、以及运行时验证)、JMLUnitNG(自动化单元测试生成工具)和JMLUnit(对JML进行静态检查)等。
二、JMLUnitNG测试
这部分暂时搞不出来,环境配不出来运行不了。回头能改出来再补吧。
(据说这个自动生成数据只会测试边界数据)。
三、作业架构
我是属于对着jml直接写的,没有考虑类继承关系以及类之间的架构,只用了MyPerson,MyGroup, MyNetwork几个类。主要属于自己设计的就是数据的实现和每个方法的算法设计。
对于数据结构设计的基本原则是查找用hashmap,遍历用arraylist。
第一次作业
没太多可说的,由于方法没有性能要求,容器全都选用arraylist存放。在Network中查询两人是否连通选择使用bfs。
第二次作业
这次作业主要增加了Group类,因为测试数据量会比较大,并且加入了查询Group中Person年龄平均、方差、关系和等方法,于是我再用缓存中间变量的方式,在每一次向Goup添加人的时候修改相应中间变量(和acquintance有关的量在addRelation方法中更新)。这样减少了每次查询年龄方差、关系和等量时的重复遍历。
并且在此次作业中,考虑到容器的应用可大体抽象为遍历和查找两种,于是为每一个数据规格实现了两个容器,一个是arraylist用于成员遍历,一个是hashmap用于成员查找。
第三次作业
对于第二次作业而言,主要是两个方面的迭代,一个是可以从组中删人,network中一些Person关系图的查询。
支持从组中删人,对于大部分求Group内变量的方法没有影响,比如求年龄平均,只需减去这个人的年龄即可。但是仍有方法受到影响,比如求年龄方差,很难从现有的方差和要删去的Person的年龄求得结果方差。而基于xor(异或)的求conflict的方法,更是不可能回退,于是不使用缓存中间变量,而是每次都进行遍历。
Person关系图的新方法主要有3:求最短路,求是否强连通,求连通块数。求最短路直接使用的迪杰特斯拉;连通块数只在addPerson方法调用的时候进行更新,调用getBlockSum方法时直接返还缓存变量,更新使用并查集;强连通算法为:找到一条路径,每次删去其中一个节点,再找路径,若每次都能找到,则强连通,记录路径使用dfs。
四、BUGS
第一次作业
对JML规格理解有误导致的bug:isLinked方法中自己和自己返回的是true,理解不全面。
第二次作业
没有好好看数据的限制,最开始全都使用arraylist进行遍历和查找,使得查找的效率很慢。并且对于Group里的getAgeVar等一系列方法,都是每次调用都重新求一次,导致强测不少点CTLE。
第三次作业
本以为自己测试充分,但还是大意失荆州:第二次作业几乎没有用到需要遍历某个Person对象的acquintance的情况,但第三次作业新加的算法很多都要遍历acquintance(这相当于是图的邻接关系的表示)。但acquintance只用了hashmap进行存储,每次遍历hashmap.getKey()非常的慢。后专门加入了arraylist存acquintance,CTLE修复。
四、心得体会
JML的重点在于契约性,是一种需求的交流,从它的语法来看,非常符合契约设计的理念:requires 描述先验条件,由调用者进行保证,即调用者的义务;ensures 描述后验条件,由开发者进行保证,这也是调用者享受的权利。这种契约性使得设计和实现可以很好的分离,同时,形式化的规格也便于进行严格的形式化验证。
在完成作业的过程中,我意识到,对于JML不能简单的将其理解为从JML翻译代码,而是应该理解其需求后进行自己设计。在写程序前需要完全的理解规格编写者的意图,而不是机械地进行实现。
JML的编写过程十分复杂,很容易出错,是一种为了描述需求需要花费很多精力、甚至大于开发精力的工作。虽然说我了解到了契约式编程和规划化设计的好处,但是单就JML这门语言来说,我今后是不愿意使用的。