title: 2020 OO 第三单元总结
date: 2020-05-21 10:10:06
tags: OO
categories: 学习

第三单元终于结束了,这是我目前为止最惨的一单元,第十次作业强测20分,互测杀成狗……虽然都知道只关注分数没有什么意义,我更应该去体会的是通过JML学习对于程序设计理念的认知,但是……心真的很痛。下面就进入单元总结:

1. JML语言理论基础与工具链

1.1 JML语言是什么

参考课程组下发的《JML(Level 0)使用手册》,JML语言的定义如下:

JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。JML是一种行为接口规格语言(Behavior Interface Specification Language,BISL),基于Larch方法构建。

也就是说,JML是一种对代码语言的抽象——不同的语言有着不同的语法,而JML通过自己的规则对代码进行了形式化的表述,一旦规格确定,除非涉及复杂的算法要求,实现代码就变成了一个相对简单的事情。

1.2 JML语言基本语法

JML的核心在于用表达式对规格进行描述,首先从常见的表达式说起,给人的感觉类似离散数学:

表达式 含义
\result 方法的返回值(非void类型)
\old(expr) 表达式expr在方法执行前的取值
\not_assigned(x, y, ...) 括号中的变量是否在方法执行过程中未被赋值
\not_modified(x, y, ...) 限制括号中的变量在方法执行期间的取值未发生变化
\forall 全称量词
\exists 存在量词
\sum 返回给定范围内的表达式的和
\product 返回给定范围内的表达式的连乘结果
\max 返回给定范围内的表达式的最大值
\min 返回给定范围内的表达式的最小值
\num_of 返回指定变量中满足相应条件的取值个数
<: 子类型关系操作符
<==> 等价关系操作符
==> 推理操作符
\everything 全集
\nothing 空集

表达式中容易错的地方在于\old(expr)表达式,作为一般规则,任何情况下,都应该使用\old把关心的表达式取值整体括起来。

方法规格 含义
requires 表达的意思是“要求调用者确保P为真”
ensures 表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”
assignable/modifiable 副作用范围限定,副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。assignble表示可赋值,而modifiable则表示可修改
pure 纯粹访问性的方法,不会对对象的状态进行任何改变,也不需要提供输入参数
public normal_behavior 正常功能,一般指输入或方法关联this对象的状态在正常范围内时所指向的功能。
public exceptional_behavior 与正常功能相对
signals 结构为signals (***Exception e) b_expr;,意思是当b_exprtrue时,方法会抛出括号中给出的相应异常e

最后是类型规格,指针对类或接口所设计的约束规则。

类型规格 含义
invariant 不变式,只针对可见状态(即当下可见状态)的取值进行约束
constraint 状态变化约束,对前序可见状态和当前可见状态的关系进行约束

1.3 应用工具链情况

在网络上查找JML工具链,搜索到的大多是OO博客的内容。工具主要包括OpenJML、JMLUnitNG等。

在编写三次作业时,我用的java版本是JDK13,无法使用相关工具链,于是也在作业中并没有使用;撰写本次博客时重新安装了JRE1.8,具体使用在下面的部分描述。

2. SMT Solver验证

参考了J哥的教程(https://www.cnblogs.com/pekopekopeko/p/12920417.html#4581709),进行OpenJML的组件SMT Solver验证,文件树如下:

 PS G:\STUDY\BUAA-2020-OO\Week12> tree /F
 卷 办公 的文件夹 PATH 列表
 卷序列号为 AA85-2D30
 G:.
 │ Group.java
 │ Person.java
 │
 └─openjml-0.8.44-20200413
    │ epl-v10.html
    │ jml-reference-manual.pdf
    │ jmlruntime.jar
    │ jmlspecs.jar
    │ LICENSE.rtf
    │ openjml-template.properties
    │ openjml.jar
    │ OpenJMLUserGuide.pdf
    │ VERSION_INFO
    │
    ├─Solvers-linux
    │     cvc4-1.6
    │     z3-4.3.0
    │     z3-4.3.1
    │     z3-4.7.1
    │
    ├─Solvers-macos
    │     cvc4-1.6
    │     z3-4.3.1
    │     z3-4.5.0
    │     z3-4.6.0
    │     z3-4.7.1
    │
    └─Solvers-windows
            cvc4-1.6.exe
            z3-4.3.2.exe
            z3-4.7.1.exe

先运行静态验证指令:java -jar .\openjml.jar -exec .\Solvers-windows\z3-4.7.1.exe -esc ..\Group.java,得到初步结果:

 public class Person {
        ^
 ..\Group.java:34: 警告: A non-pure method is being called where it is not permitted: Person.isLinked(Person)
      @         (\sum int j; 0 <= j && j < people.length && people[i].isLinked(people[j]); 1));
                                                                                ^
 ..\Group.java:40: 警告: A non-pure method is being called where it is not permitted: Person.isLinked(Person)
      @           people[i].isLinked(people[j]); people[i].queryValue(people[j])));
                                    ^
 ..\Group.java:40: 警告: A non-pure method is being called where it is not permitted: Person.queryValue(Person)
 ..\Group.java:47: 警告: A non-pure method is being called where it is not permitted: Person.getCharacter()
      @         temp.length == people.length && temp[0] == people[0].getCharacter();
                                                                                  ^
 ..\Group.java:49: 警告: A non-pure method is being called where it is not permitted: Person.getCharacter()
      @           temp[i] == temp[i-1].xor(people[i].getCharacter())) &&
                                                                  ^
 ..\Group.java:59: 警告: A non-pure method is being called where it is not permitted: Person.getAge()
                                                                            ^
 ..\Group.java:58: 错误: 不可比较的类型: int和INT#1
    /*@ ensures \result == (people.length == 0? 0 :
                        ^
  其中, INT#1是交叉类型:
    INT#1扩展Number,Comparable
      @         (people[i].getAge() - getAgeMean()) * (people[i].getAge() - getAgeMean())) /
                                  ^
 ..\Group.java:64: 警告: A non-pure method is being called where it is not permitted: Person.getAge()
      @         (people[i].getAge() - getAgeMean()) * (people[i].getAge() - getAgeMean())) /
                                                                        ^
 ..\Group.java:63: 错误: 不可比较的类型: int和INT#1
                        ^
  其中, INT#1是交叉类型:
    INT#1扩展Number,Comparable
 3 个错误
 8 个警告

报错和警告主要有两种,在不允许的地方调用非纯方法,以及不可比较的类型(貌似是不能识别三目运算符?),动态验证的信息就更多了。

总之我感觉看着结果云里雾里的,SMT Solver目前好像也无法对exist等进行验证,不过作者一直在更新,相信下一届的同学们可以享受到更加舒适的SMT Solver的!

3. JMLUnitNG/JMLUnit自动生成测试用例

辛辛苦苦以10KB/S的速度下载下来,一运行傻了——

到百度求助,表示版本太低,然而我已经下载了官网最新的1.8版本……另外发现这中间似乎插了句人话:错误: 非法的类型开始 value = new ArrayList<>();,感情必须要写成value = new ArrayList<Integer>();的形式?这也太OUT了吧……

之后又出现了群里提到的神秘的++,触发了JmlInternalError,更加迷惑了。

 org.jmlspecs.openjml.JmlInternalError: The operation symbol ++ for type java.lang.Object could not be resolved

简要分析

最后我折腾了很久还是不行……运行完什么结果也没有……我也很疑惑为什么运行SMT Solver时就相对顺风顺水但JMLUnitNG怎么整都不行(对帮我一起惨兮兮找问题的PB深表歉意)。因为我的电脑不能生成,所以就看了很多同学的博客,这里引用@VOIDMalkuth的测试结果:

从同学们的结果来看,JMLUnitNG自动生成的样例可以检测到一定的错误,对于INT数据测试了极端情况等与实际应用不符合的数据输入,要成为真正有用的工具,还有很长的路要走……

4. 架构设计梳理与模型构建策略分析

4.1 第九次作业

UML类图如下:

本次作业只要对着规格就能完成大部分任务,架构上使用的MyPerson、MyNetwork均继承课程组所给的接口。

需要考虑算法实现的是isCircle,我采用了BFS广度优先搜索,从每个Person内调出邻接表搜索,复杂度为O(N+E)。

4.2 第十次作业

UML类图如下:

第十次作业与第九次作业相比新增了Group,在MyNetwork中调用MyGroup。我直到周四才开始动笔,此时同学们已经把雷区排得差不多了……于是在Group的方法上我采用了缓存法的形式。需要特别注意的是缓存法在addRelation时也要更新缓存,我采用的方式为为Group增加更新的检索方法:

     public void addRelation(int id1, int id2, int value) {
         if (people.containsKey(id1) && people.containsKey(id2)) {
             relationSum += 2;
             valueSum += (2 * value);
        }
    }

而在计算方差时,则出现了过度化简导致误差的问题,同样需要注意。

另外需要注意的是初始化问题,HashMap默认容量为16,当当前的size超过容量×常数时就会进行扩容,而扩容操作需要耗费的时间较多,因此我直接将关系表初始化为8192容量。

4.3 第十一次作业

UML类图如下:

第十一次作业对算法的考察增加了:

  • 在queryBlockSum我采用了并查集,进而isCircle的复杂度降至O(1);

  • queryMinPath则采用了堆优化的Dijkstra,没有手写堆,而是采用自带的PriorityQueue进行维护;

  • queryStrongLink原本在想要不要学tarjan,但发现助教给的标程复杂度很宽容,所以就用了两步BFS,特判第一次BFS找到的为直连路径的情况,我写了一个忽略某点的BFS函数:

 private boolean searchIgnore(int id1, int id2, int ignore) {
     if (id1 == ignore || id2 == ignore || id1 == id2) {
         // err("meet id1 == ignore || id2 == ignore || id1 == id2");
         return false;
    }
     HashSet<Integer> visited = new HashSet<>(1024);
     Queue<Integer> queue = new LinkedList<>();
     queue.add(id1);
     while (!queue.isEmpty()) {
         int currentId = queue.poll();
         if (visited.contains(currentId)) {
             continue;
        }
         ArrayList<Integer> points = ((MyPerson) getPerson(currentId)).getAcquaintance();
         for (Integer i : points) {
             if (i == id2) {
                 // err("Succeed! searchIgnore");
                 return true;
            } else if (!visited.contains(i) && i != ignore) {
                 queue.offer(i);
                 // err("queue update to " + queue);
            }
        }
         visited.add(currentId);
         // err("point " + currentId + " has been searched");
    }
     return false;
 }

如果第一次找到的路径长度为2,那么就遍历起点的邻接表,找到一条不经过起点就能到终点的路径就返回成功;否则依次忽略第一次BFS路径中的点,如果每次都能找到路径就返回成功。最后在研讨课也学了tarjan,在求割点的过程中求出所有双连通分量,进而判断点双。

这样的结构导致MyNetwork非常臃肿,我因此将并查集、堆优化的Dijkstra单独抽象为一个类。

5. 代码bug和修复情况

第九次、第十一次作业均未出现Bug。

第十次作业

第九次作业整体相对简单,也就让我放松了警惕,在第十次作业时,我最后改了一行代码,却忘了测试:

 return (HashSet<Integer>) acquaintance.keySet();

就是这行代码,让我直接RE了大部分测试点……在编写时想当然地以为HashMap的KeySet可以用强制类型转换为HashSet,之后又没有做哪怕一次对isCircle的测试,于是强测直接爆炸……在互测屋里,看到了没有除零直接RE的同学;看到了缓存更新不及时的同学,看到了好多Bug一起犯的同学……是一次难忘的体验。

第十一次作业

在第十一次作业,吸取了第十次作业的惨痛教训,用Junit对新加入的函数进行单元测试,优化算法,没有出现Bug。

对规格的心得体会

JML作为一种统一的规格化语言,在对代码进行了形式化的表述的同时,也给机器理解程序提供了一种可能性。

虽然从目前来看,JML相关的工具或是陈旧不堪,或是Bug满天飞,而撰写规格甚至比写程序还要复杂得多,比如对于一些Contains方法,JML的表述虽然严谨,但为人的阅读与理解提供了更大的负担。

而在未来的工作中,不单要知道如何写好程序,也需要写出好的文档,我想本单元的学习就是一次预习,虽然中间由于我的懈怠造成了大翻车,但这一单元只要认真阅读代码并不难。

2020 OO 第三单元总结 JML语言的更多相关文章

  1. OO第三单元——基于JML的社交网络总结

    OO第三单元--基于JML的社交网络总结 一.JML知识梳理 1)JML的语言基础以及基本语法 JML是用于java程序进行规格化设计的一种表示语言,是一种行为接口规格语言.其为严格的程序设计提供了一 ...

  2. OO第三单元作业(JML)总结

    OO第三单元作业(JML)总结 目录 OO第三单元作业(JML)总结 JML语言知识梳理 使用jml的目的 jml注释结构 jml表达式 方法规格 类型规格 SMT Solver 部署JMLUnitN ...

  3. OO第三单元总结——JML

    目录 写在前面 JML理论基础 JML工具链 JMLUnitNG的使用 架构设计 Bug分析 心得体会 写在前面 OO的第三单元学习结束了,本单元我们学习了如何使用JML语言来对我们的程序进行规格化设 ...

  4. OO第三单元总结——JML规格设计

    • 1.JML语言的理论基础.应用工具链情况 JML(Java Modeling Language)—— java建模语言,是一种行为接口规范语言( behavioral interface spec ...

  5. OO第三单元总结——JML规格

    一.JML简介 1.JML语言的理论基础 JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML是一种行为接口规格语言 (Behavior In ...

  6. 2019年北航OO第三单元(JML规格任务)总结

    一.JML简介 1.1 JML与契约式设计 说起JML,就不得不提到契约式设计(Design by Contract).这种设计模式的始祖是1986年的Eiffel语言.它是一种限定了软件中每个元素所 ...

  7. 2020北航OO第三单元总结

    2020北航OO第三单元总结 本单元要求是根据JML规格完善代码,初看是一个简单的代码照搬实现的东西,但最后才发现由于CPU时间的限制,还考察了大量优化策略及数据结构中关于图的知识,是一次非常注重细节 ...

  8. OO第三单元(地铁,JML)单元总结

    OO第三单元(地铁,JML)单元总结 这是我们OO课程的第二个单元,这个单元的主要目的是让我们熟悉并了解JML来是我们具有规格化编程架构的思想.这个单元的主题一开始并不明了,从第一次作业的路径到第二次 ...

  9. OO第三单元——JML规格化设计

    OO第三单元--JML规格化设计 JML语言的理论基础以及应用工具链情况 理论基础 JML是对JAVA程序进行规格化设计的一种表示语言,是一种行为接口规格语言.JML整合了Java和JAVAdoc,并 ...

随机推荐

  1. HTML Imports & deprecated

    HTML Imports & deprecated https://caniuse.com/#search=html imports https://www.chromestatus.com/ ...

  2. 算法型稳定币USDN有什么魔力引发市场热潮?

    最近比特币重新突破了8万大关,区块链行业又再次火爆起来,吸引了圈内圈外人的火热讨论,而这其中市场投资者讨论最频繁的就要属算法型稳定币USDN了. USDN是基于NGK.IO区块链中的稳定币, 1枚US ...

  3. Mila Fletcher :其实高度自律的人生并没有那么难养成

    在日常生活中,我们经常会发现,不论是学习,考证,工作,都需要坚持付出.但是很多人都没有办法在枯燥的学习过程中持续下去,这通常是因为不够自律导致的.但是尽管大家都知道自律是多么重要,却没有几个人可以真正 ...

  4. scala函数至简原则是什么?

    1.return可以省略,Scala会使用函数体的最后一行代码作为返回值 2.如果函数体只有一行代码,可以省略花括号 3.返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略) 4.如果有 ...

  5. 生产者和消费者问题(synchronized、Lock)

    1.synchronized的生产者和消费者 synchronized是锁住对象 this.wait()释放了锁 并等待 this.notify()随机通知并唤醒同一个对象中的一个线程 this.no ...

  6. JS输出为[object object]

    问题描述:在控制台打印时应输出对象,但是却输出[object object] 解决办法:先将数据转换为json格式,然后再转换为json对象 JSON.parse(JSON.stringify(use ...

  7. @Transaction注解失效的几种场景

    一.@Transactional介绍 1.@Transactional注解可以作用于哪些地方? @Transactional 可以作用在接口.类.类方法上. 作用于类:表示所有该类的public方法都 ...

  8. Sapper:迈向理想的 Web 应用框架

    ​扎稳阵脚,再进一步. 注意:原文发表于2017-12-31,随着框架不断演进,部分内容可能已不适用. 给迫不及待的小伙伴们的快速入门:Sapper 文档 和快速模板 starter template ...

  9. linux调度全景指南

  10. 在16G笔记本上安装GaussDB 200

    云主机太贵(最便宜的每月几千吧),长期如果需要GaussDB200有个功能测试或学习环境,那么性价比最高的方式还是在自己的笔记本电脑上尝试安装一个本地的数据库进行学习和功能验证. 01 安装环境信息 ...