2020北航OO第三单元总结

本单元要求是根据JML规格完善代码,初看是一个简单的代码照搬实现的东西,但最后才发现由于CPU时间的限制,还考察了大量优化策略及数据结构中关于图的知识,是一次非常注重细节构思的一单元,我借此机会学习并巩固了好几个图的算法,并了解了java各类容器的查插删改的效率。

一、JML理论基础

JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言,它相当于一种接口,正确的JML可以给实现人员清楚的代码要求,并且针对已有代码书写JML也可以提高代码的可维护性、可读性。经我使用看来,它有如下几种关键要素组成:

  1. 位于类一开始的//@annotation的属性定义,它规定了类必含的属性(当然也可以由其它形式所展现),在之后的方法规格中会用到这些定义

  2. 变式invariant,要求在所有可见状态(凡是会修改成员变量(包括静态成员变量和非静态成员变量)的方法执行期间,对象的状态都不是可见状态)下都必须满足的特性

  3. 状态变化约束constraint,对前序可见状态和当前可见状态的关系进行约束

  4. pure关键词,表示某些纯粹访问性的方法,即不会对对象的状态进行任何改变,也不需要提供输入参数,这样的方法无需描述前置条件,也不会有任何副作用,且执行一定会正常结束

  5. 前置条件(pre-condition),用requires关键词表示,定义调用者调用方法前必须满足的语句

  6. 后置条件(post-condition),用ensures关键词来表示,定义调用者调用方法后必须满足的语句

  7. 副作用约束子句,使用关键词assignable或者modifiable,表示调用方法后可以改变的变量

  8. 异常处理行为exceptional_behavior,用signals (Exception e) expr表示满足expr条件即抛出Exception异常,用signals only Exception表示满足前置条件即抛出Exception异常

其中,在规格表示时,大多数语句含义与代码类似,只需注意一下几个关键词的意含义:

  1. \nothing,指空集;everything,指全集,通常用在assinable语句

  2. \result,表方法示返回结果

  3. \old(expr)表达式,表示一个表达式expr在相应方法执行前的取值

  4. 推理操作符:expr1==>expr2,即离散中的→

  5. \forall表达式:全称量词修饰的表达式

  6. \exists表达式:存在量词修饰的表达式

  7. \sum表达式:返回给定范围内的表达式的和

二、JML应用工具链

  1. OpenJML:类似于对代码的检查,可以对规格进行语法的检查

  2. JMLUnitNG/JMLUnit: 针对类自动生成测试样例并进行测试

  3. JMLdoc : 类似 Javadoc,可以快速生成 JML 文档的相关文件

三、SMT Solver

下载了openjml,想要运行结果出现了一堆警告和error,类似于下面这种问题,\result都会被报错,但自己写的Demo里只有很简单的一个add方法,完全摸不着头脑,而且openjml也不能检测forall、exsit等语句,只能放弃

public class Demo {
   //@ ensures \result = a + b
   int add(int a, int b) {
       return a + b;
  }
}

四、JMLUnitNG

经过一些奇奇怪怪的操作,我终于配置成功了jmluintng

我先对Person.java进行了测试,主要是先补齐new一个新对象时<>里的变量类型,并加上包名,其它内容并没有改动,可以看出它产生的测试用例都是比较边缘的情况,如0、null、MAX_VALUE等,最后只贴出来出现fail的部分,集中在isLinked函数,原因是输入为null的情况,这应该是不会在使用中出现的一种情况

之后我又进一步对Group.java进行了测试,结果如下

最后失败的仍是null的情况。。。

我感觉到,这其实是一种很好地检验方法正确性工具,尤其体现在它对边缘情况进行测试的方面,但可能不是很适合比较复杂的情况(如我们的图算法)

五、架构设计

先放UML图

看起来有点点复杂,但我在原有架构的基础上,其实只增加了两个类EdgeBcc,一个代表图中的边,一个代表一个双连通块,通过增加这两个类,可以进行更好的双连通分量与最短路径的查询。至于优化的问题,由于都是在TLE之后才意识到需要进行优化,所以放在下部分讲

六、bug及其修复

这次作业的bug主要出现在算法的问题上,由于一开始都是机械地按照规格重现代码,所以导致很多点TLE了,最后bug修复完的算法如下:

  1. 采用hashmap进行存储查找,arraylist太慢,并对hashmap进行适当的初始化容量操作(最后的TLE就死在这上面)

  2. getValueSumgetRelationSum使用缓存机制,提前设置变量,在addRelation和addToGroup时进行即使修改

  3. blockSum进行缓存,并对每个person进行block的标号,在addPerson时使blockSum++,在addRelation时合并block,并改变blockSum(这里在优化过程中出现了一个bug,是因为没有区分好block的标号及blockSum,使得后加进来的人也会被打上之前block的标号)

  4. 通过每个id的block号,可直接判断isCircle

  5. queryMinPath方法使用堆优化的Dijikstra,对于堆优化,采用了java有序队列PriorityQueue来存储,使Edge实现了Comparable的接口,并且每次添加的时候不用进行是否已经访问过的查询操作,因为他永远都是poll出最小值。这里也出过一个小bug,是由于我将每个id初始的路径值设为了1001(value[0,1000]),但忽视了value是一个累加的过程。。最后改为Integer.MAX_VALUE,也是由于自己测试不充分的缘故,这其实只需要将每条边的value值设大一点就可以了

  6. 由于会有大量连续测最短路径的操作,我每查询一次就将查询结果以<id, values>的形式储存起来,如果有addRelation的改动就将相关节点删去,这样也可以减少大量重复的dijisktra查询操作(个人以为qmp是这次的优化重点)

  7. queryStrongLinkedTarjan算法,这个就不多说了,因为大家应该都用了

最后MyNetWork的各个方法复杂度分析如下

MyNetwork.addGroup(Group) 2.0 1.0 2.0
MyNetwork.addPerson(Person) 2.0 1.0 2.0
MyNetwork.addRelation(int,int,int) 4.0 6.0 9.0
MyNetwork.addtoGroup(int,int) 4.0 2.0 5.0
MyNetwork.borrowFrom(int,int,int) 3.0 2.0 4.0
MyNetwork.compareAge(int,int) 2.0 2.0 3.0
MyNetwork.compareName(int,int) 2.0 2.0 3.0
MyNetwork.contains(int) 1.0 1.0 1.0
MyNetwork.delFromGroup(int,int) 4.0 1.0 4.0
MyNetwork.getGroup(int) 1.0 1.0 1.0
MyNetwork.getPerson(int) 2.0 1.0 2.0
MyNetwork.isCircle(int,int) 2.0 2.0 3.0
MyNetwork.MyNetwork() 1.0 1.0 1.0
MyNetwork.queryAcquaintanceSum(int) 2.0 1.0 2.0
MyNetwork.queryAgeSum(int,int) 1.0 3.0 4.0
MyNetwork.queryBlockSum() 1.0 1.0 1.0
MyNetwork.queryConflict(int,int) 2.0 2.0 3.0
MyNetwork.queryGroupAgeMean(int) 2.0 1.0 2.0
MyNetwork.queryGroupAgeVar(int) 2.0 1.0 2.0
MyNetwork.queryGroupConflictSum(int) 2.0 1.0 2.0
MyNetwork.queryGroupPeopleSum(int) 2.0 1.0 2.0
MyNetwork.queryGroupRelationSum(int) 2.0 1.0 2.0
MyNetwork.queryGroupSum() 1.0 1.0 1.0
MyNetwork.queryGroupValueSum(int) 2.0 1.0 2.0
MyNetwork.queryMinPath(int,int) 10.0 9.0 14.0
MyNetwork.queryMoney(int) 2.0 1.0 2.0
MyNetwork.queryNameRank(int) 2.0 2.0 4.0
MyNetwork.queryPeopleSum() 1.0 1.0 1.0
MyNetwork.queryStrongLinked(int,int) 5.0 7.0 10.0
MyNetwork.queryValue(int,int) 3.0 2.0 4.0
MyNetwork.renew(int,int) 2.0 7.0 7.0
MyNetwork.tarjan(int,int) 6.0 7.0 8.0

也可以看出来是qmp和qsl方法复杂度比较高,也是只有这两个方法含有图的反复查询操作

七、心得体会

坦白地讲,在做第一次作业时,我用了oo有史以来的最短时间,而且个人感觉没什么需要测试的地方,不由感叹oo作业变简单了,在进行互测时,也是第一次认真看完了所有人的代码,并找出了他们的漏洞,感觉第一次作业还是比较注重规格的。

第二次作业,虽然形式与第一次相同,但突然讲究的多了起来,也开始对时间和算法有要求了,但由于当周的os作业太难搞,我还是放松了对oo的优化及测试,导致我出了一个很大的bug没有进互测!!!真的没有想到中测突然变得如此简单。。我是大大的失策了,再加上双重循环的遍历,挂掉了所有强测。

第三次作业,我吸收了前两次的教训,自己对算法和测试都比较认真地规划了,而且我也能感受到第三次作业十分看重算法,难度甚至大过了数据结构,这次的结果还算比较正常,也由于优化程度不够导致TLE了一些点,而且互测的大佬们也查出了我没注意的很小的点,经过最艰难的一次bug修复,我终于完成了这一单元的代码部分。

真的感触颇多,从这一次作业,我充分认识到了JML规格的实现不是纯”实现”的问题,要进行自己的优化, 使程序效率达到最好,想想这其实是合理的,规格的书写者只负责想好方法的效果,保证规格正确性即可,不可能连我们怎么实现都考虑到了。最后,再赞一句助教这次强测的点简直太妙了,就卡那一点点时间,我是挣扎着才由2.0XX秒到了1.9X秒,真是一次颇为“愉悦”的经历。。

2020北航OO第三单元总结的更多相关文章

  1. 2020北航OO第四单元总结

    2020北航OO第四单元总结 一.本单元架构设计 本单元作业是实现一个UML图解析器,其中实现接口及主要框架课程组已经提供,只需要我们完成特定功能. 在第一次作业时,感到十分迷茫,不知道如何下手,最后 ...

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

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

  3. 2019北航OO第三单元作业总结

    1.梳理JML语言的理论基础.应用工具链情况 JML基础理论: JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言.JML是一种行为接口规格语言,基 ...

  4. 北航OO第三单元作业总结(3.1~3.3)

    JML简介及相关工具链使用 1.JML规格描述语言介绍 本单元学习的内容是JML规格描述语言.我们知道,面向对象方法是一个抽象过程,需求者仅需关注方法的规格.规格是对一个方法/类/程序的外部可感知行为 ...

  5. 北航OO第三单元总结

    JML基础梳理及工具链 JML的全称是Java Modeling language,即Java建模语言.JML是一种行为接口规格.它为严格的程序设计提供了一套行之有效的方法.通过JML不仅可以基于规格 ...

  6. 2020北航OO第二单元总结

    2020北航OO第二单元总结 前言 本单元考察基于多线程的电梯调度问题,成功让我从一个多线程小白到了基本掌握了使用锁来控制线程安全的能力,收获颇多(充分体验了迷茫地de一个又一个死锁bug的痛苦). ...

  7. 2020 OO 第三单元总结 JML语言

    title: 2020 OO 第三单元总结 date: 2020-05-21 10:10:06 tags: OO categories: 学习 第三单元终于结束了,这是我目前为止最惨的一单元,第十次作 ...

  8. 2019年北航OO第1单元(表达式求导)总结

    2019年北航OO第1单元(表达式求导)总结 1 基于度量的程序结构分析 量化指标及分析 以下是三次作业的量化指标统计: 关于图中指标在这里简要介绍一下: ev(G):基本复杂度,用来衡量程序非结构化 ...

  9. 北航OO第四单元——UML图解析

    北航OO第四单元--UML图解析 作业要求简析 刚接触本次作业可能需要花上一会才能搞清楚到底是要我们写个啥,在这里简单说一下: UML图的保存格式.mdj文件是以json文件的形式存储的,将每一个Um ...

随机推荐

  1. Tomcat 安装Manager

    sudo apt-get install tomcat8-admin tomcat8-docs tomcat8-examplessudo vi /etc/tomcat8/tomcat-users.xm ...

  2. LDAP启动TLS 完整操作流程

    配置LDAP启动TLS 阅读本文之前,建议初学的小伙伴先看一下上一篇:完整的 LDAP + phpLDAPadmin安装部署流程 (ubuntu18.04) 以下正文: 接下来的操作承接上文,还是在同 ...

  3. Hexo一键部署到阿里云OSS并设置浏览器缓存

    自建博客地址:https://bytelife.net,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客 本文作者: Jeffrey 本文链接: https://bytel ...

  4. Java基础自学小项目

    实现一个基于文本界面的<家庭记账软件> 需求:能够记录家庭的收入,支出,并能够收支明细表 主要涉及一下知识点: - 局部变量和基本数据类型 - 循环语句 - 分支语句 - 方法调用和返回值 ...

  5. 【HTB系列】靶机Querier的渗透测试

    出品|MS08067实验室(www.ms08067.com) 本文作者:大方子(Ms08067实验室核心成员) 总结与反思: 1.收集信息要全面 2.用snmp-check检查snmp目标是否开启服务 ...

  6. 转: 微信已支持发送最大 200MB 的视频了,并且不会被压缩 来自腾讯微信团队的视频消息,目前 iOS 版本的微信已支持发送最大 200MB 的视频与图片了,并且不会被压缩.安卓版本未来一段时间会支 ...

  7. Elasticsearch 为了搜索

    前言 Elasticsearch 是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene 基础之上. Lucene 可以说是当下最先进.高性能.全功能的搜索引擎库--无论是开源还是 ...

  8. windows基线检测脚本编写指南-powershell版

    前言:   因为工作的原因,要写windows下的基线检查脚本.之前没接触过,在网上找了半天也没找到现成的,无奈只好自己研究,最后还是成功完成了工作. 在我编写之后发现windows下的基线基本就是检 ...

  9. 《Asp.Net Core3 + Vue3入坑教程》 - 6.异常处理与UserFriendlyException

    简介 <Asp.Net Core3 + Vue3入坑教程> 此教程适合新手入门或者前后端分离尝试者.可以根据图文一步一步进操作编码也可以选择直接查看源码.每一篇文章都有对应的源码 目录 & ...

  10. FreeBSD WIFI 配置

    ee /boot/ loader.conf ee是个编辑器 中写入 rtwn_usb_load="YES" legal.realtek.license_ack=1 在 /etc/ ...