一、JML理论基础及应用工具链
JML存在的意义
1. 开展规格化设计,能够实现设计与实现分离,实现进行提前测试。
2. 方便程序代码的阅读和维护。
JML方法规格
requires —— pre-condition
assignable —— side-effects,列出这个方法能够修改的类成员属性
ensures —— post-condition
区分正常功能行为和异常行为:
1 /*@ public normal_behavior
2 @
3 @ also
4 @ public exceptional_behavior
5 @ signals (Exception e) expr;
6 @*/
JML类型规格
针对Java重定义的数据类型所设计的限制规则。
invariant —— 不变式
constraint —— 状态变化约束
JML表达式
esult
old(expr) —— 这个使用起来需要注意
forall
exist
othing
==>
<==>
等等,此处仅列举一些常用的
JML应用工具链
OpenJML —— 检测JML注释正确性
JMLunitNG —— 进行测试
等等
二、部署JMLUnitNG/JMLUnit
这个部分让AND gate同学彻底疯了,虽然自知时间有限+能力有限我怕是搞不了Graph,但是抱着尝试的态度,我还是自己写了一个简单的测试样例去做......
但是,体验极差,虽然最后跑了一个什么东西出来,但是我甚至不知道是怎么跑出来的......
接下来,我来记录一下我糟糕的体验过程...顺便希望得到一些指教。
先放一下我自己的测试样例:
1 package test;
2
3 public class Test extends Exception{
4 public static void main(String[] args) {
5 try {
6 divide(100, 20);
7 } catch (Exception e) {}
8 }
9
10 /*@ public normal_behavior
11 @ requires dividend != 0;
12 @ ensures
esult == divisor / dividend;
13 @ also
14 @ public exceptional_behavior
15 @ requires dividend == 0;
16 @ signals (Exception e);
17 @*/
18 public static int divide(int divisor, int dividend) throws Exception {
19 if (divisor == 0) {
20 throw new Exception("hehe");
21 } else {
22 return divisor / dividend;
23 }
24 }
25 }
首先,我按照讨论区的方法开始使用——很好,前几步都很顺利,接下来就是噩梦的开始。
我用javac编译文件,有一个.java文件无法编译,报错信息是一堆乱码:),然后我用idea打开看,idea的报错是,我的Test是个抽象类,无法被实例化......然后出现了Test.divide的地方也全都报错了。
然后,我仔细地再看了一遍伦佬的教程,思考我和他的区别,我肤浅地觉得是我没有写package,然后我就写上了,然后就是彻底的噩梦的开始。
本来只是一个文件没法用命令行编译,这回好了,我没有能编译的文件了???然后报错依然是一堆混杂着乱码的东西
再后来,我琢磨着是不是包名的问题呢?但是我完全模仿伦佬,从文件名到包名到.java的名字和内容都一样,但是还是失败了——难道我是瞎哪边看错了写错了大小写???
(由于急着交作业,我没有再从头尝试,之后我会再次尝试,看看能不能复现坑爹的结果)
然后,我决定放弃用命令行来做这件事,我选择用ide。
打开ide以后,我就慌了,因为在那个Test文件里,我出现了几处红色,但是我不会解决它,我抱着尝试的心态,点了run,然后居然——
当然没有运行成功,但不是因为红色区域,而是,它提醒我jdk版本有问题?我???
我又抱着尝试的心态,把jdk8换成了jdk11......居然,运行成功——在多处红色警告下,它运行出来了......
我实在太震惊了,导致无话可说,我不知道我到底是怎么完成了这个任务的。我觉得这次尝试,除了震撼我妈以外,我没有什么收获。还请有人能赐教。
三、架构设计梳理
1/ 第一次作业
这一次作业比较简单,基本按照所给规格写就完事了。
唯一注意了一下的是当时怕时间复杂度太高,用hashmap做了一个双向的操作。即我用了两个hashmap,一个key值为id,value为path,另一个key值为path,value为id。
这样在删除和通过path找id时都会更快一些。
2/ 第二次作业
单独开了一个floyd类来处理我的图,但其实并没有什么优越性......
3/ 第三次作业
我所有的图全都用二位数组实现了(没错是写死的),所有的边和节点全都用hashmap存储,后来上课知道这样设计非常垃圾......因为不能拓展,也很难修改。
说起重构,其实也还是没有很好的想法,可能会建一个工厂来建不同的图吧......这样图就不会是写死的了......
的确,这部分当时更关心算法和时间复杂度的问题,并没有去考虑架构的问题(当然其实也是有点不知道对于这次作业什么架构算是比较好的?)
算法的话最后用的就是把换乘也算作权值,然后floyd到底。
由于我觉得这个部分重在分析架构,但是我现在没什么特别好的想法,感觉算法没必要谈,大家心里都有数...所以就暂时简短地结束吧。
以及我比较关心的一个问题是我知道可以用继承,让system继承graph,但是在graph的父类里我存了很多数据,我想在system里快速使用的话就想用protected关键字,但是这和checkstyle的检测冲突,这样我就要写一些新的方法,去完成一些事。第一,我不知道是不是我对继承理解有问题,父类的数据就是不能随便被子类使用的;第二,之前上C++课程的时候,老师却说尽量使用protected...是因为JAVA和C++特性不同吗?为什么此处我们不鼓励(起码checkstyle不鼓励)我们使用protected。
四、BUG及修复情况
很幸运......这单元三次作业强测和互测都没有出现什么问题,个人仅从想当然的角度思考自己的程序的话,先不论垃圾的架构,我更担心的是运行时间和内存,我甚至也不太担心运行时间,因为我觉得我的算法不会tle,但是的确会比较关心内存的使用,静态二维数组开的太多,我的每一条path都有自己的四个二维数组存一些数据,完整的图又有四个较大的二维数组......其实我没有很好地除了二维数组的建图方向,我觉得用邻接表...(特别是我知道有些同学用hashmap来模拟)会非常非常的慢。
五、阐述对规格撰写和理解上的心得体会
我觉得其实这次的实验训练,在规格撰写和阅读上并没有做的很多,更多的着眼点还是跟以前的作业给我的感觉差不多。
其实JML的阅读体验还可以,除了有时候一堆(/forall,,)里嵌套(/forall,,)甚至还不止一层嵌套......但是这种情况不会太多,如果真的层数深了也说明这个方法设计并不是非常合理。
第一次去实验的时候,写起规格还是挺懵逼的,懵逼的原因1、根本没有办法想清楚这个东西的定义,所以不会写;2、我自认为我脑内有了一个定义,但是我无法用JML的语言做出表达。但后来看了几次作业给我们的规格之后,写起来就比刚开始顺滑一些了。也能从JML的角度而不是实现的角度去看待一个方法了。