OO第三次博客作业

OO第三次博客作业

一、JML的理论基础和相关工具

简介

  The Java Modeling Language (JML) is a specification language for Java programs, using Hoare style pre- and postconditions and invariants, that follows the design by contract paradigm. Specifications are written as Java annotation comments to the source files, which hence can be compiled with any Java compiler.

  Various verification tools, such as a runtime assertion checker and the Extended Static Checker (ESC/Java) aid development.

  Java建模语言(JML)是Java程序的规范语言,使用Hoare风格的前置条件,后置条件和不变式,它遵循契约范式进行设计。 规范以Java注释注释的形式写到源文件,因此可以使用任何Java编译器进行编译。

  各种验证工具,例如运行时断言检查器和扩展静态检查器(ESC / Java),有助于开发。

注释结构

行注释://@annotation

块注释:/*@ annotation @*/

  JML以javadoc注释的方式来表示规格,每行都以@起头。有两种注释方式,行注释和块注释。其中行注释的表示方式为//@annotation,块注释的方式为/* @ annotation @*/。按照Javadoc习惯,JML注释一般放在被注释成分的紧邻上部。

常见表达式

原子表达式

  \result表达式:表示一个非void 类型的方法执行所获得的结果,即方法执行后的返回值。\result表达 式的类型就是方法声明中定义的返回值类型。

  \old( expr )表达式:用来表示一个表达式expr 在相应方法执行前的取值。该表达式涉及到评估expr中的对象是否发生变化,遵从Java的引用规则,即针对一个对象引用而言,只能判断引用本身是否发生变化,而不能判断引用所指向的对象实体内容是否发生变化。

  \not_assigned(x,y,...)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。如果没有被赋值,返回为true ,否则返回false 。

  \not_modified(x,y,...)表达式:与上面的\not_assigned表达式类似,该表达式限制括号中的变量在方法执行期间的取值未发生变化。

  \nonnullelements( container )表达式:表示container 对象中存储的对象不会有null ,等价于下面的断言,其中\forall是JML的关键词,表示针对所有i 。

  \type(type)表达式:返回类型type对应的类型(Class),如type( boolean )为Boolean.TYPE。TYPE是JML采用的缩略表示,等同于Java中的java.lang.Class 。

  \typeof(expr)表达式:该表达式返回expr对应的准确类型。如\typeof( false )为Boolean.TYPE。

量化表达式

  \forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。

  \exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。

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

  \product表达式:返回给定范围内的表达式的连乘结果。

  \max表达式:返回给定范围内的表达式的最大值。

  \min表达式:返回给定范围内的表达式的最小值。

  \num_of表达式:返回指定变量中满足相应条件的取值个数。

集合表达式

  集合构造表达式:可以在JML规格中构造一个局部的集合(容器),明确集合中可以包含的元素。

操作符

  JML表达式中可以正常使用Java语言所定义的操作符,包括算术操作符、逻辑预算操作符等。此外,JML 专门又定义了如下四类操作符。

  (1) 子类型关系操作符: E1<:E2 ,如果类型E1是类型E2的子类型(sub type),则该表达式的结果为真, 否则为假。如果E1和E2是相同的类型,该表达式的结果也为真。

  (2) 等价关系操作符: b_expr1<==>b_expr2或者b_expr1<=!=>b_expr2 ,其中b_expr1b_expr2 是布尔表达式,这两个表达式的意思是b_expr1==b_expr2 或者b_expr1!=b_expr2。可以看出,这两 个操作符和Java中的==!=具有相同的效果,按照JML语言定义,<==>== 的优先级要低,同样 <=!=>!=的优先级低。

  (3) 推理操作符:b_expr1==>b_expr2 或者b_expr2<==b_expr1 。对于表达式b_expr1==>b_expr2 而言,当b_expr1==false,或者b_expr1==trueb_expr2==true时,整个表达式的值为true

  (4) 变量引用操作符:除了可以直接引用Java代码或者JML规格中定义的变量外,JML还提供了几个概括 性的关键词来引用相关的变量。\nothing指示一个空集;\everything指示一个全集,即包括当前作用域 下能够访问到的所有变量。变量引用操作符经常在assignable句子中使用,如assignable \nothing 表示当前作用域下每个变量都不可以在方法执行过程中被赋值。

方法规格

前置条件(pre-condition)

  前置条件是对调用者的限制,即要求调用者确保条件为真。

后置条件(post-condition)

  后置条件是对方法实现者的要求,即方法实现者确保方法执行返回结果一定满足谓词的要求,即确保后置条件为真。

副作用范围限定(side-effects)

  assignble 表示可赋值,而modifiable 则表示可修改

signals子句

  signals子句的结构为signals (***Exception e) b_expr;,意思是当b_exprtrue 时,方法会抛出括号中给出的相应异常e 。

类型规格

不变式invariant

  不变式(invariant)是要求在所有可见状态下都必须满足的特性,语法上定义invariant P ,其中 invariant 为关键词, P 为谓词。

状态变化约束constraint

  对象的状态在变化时往往也许满足一些约束,这种约束本质上也是一种不变式。JML为了简化使用规 则,规定invariant只针对可见状态(即当下可见状态)的取值进行约束,而是用constraint来对前序可见 状态和当前可见状态的关系进行约束。

JML工具

OPENJML

  可以根据JML对实现进行静态的检查,其中用于进行JML分析的部分在solvers中,包括常见的如cvc4以及z3

JMLUnitNG

  可以根据JML自动生成对应的测试样例,用于进行单元化测试。

二、部署JMLUnitNG/JMLUnit

  通过艰难的部署JMLUnitNG以及OPENJML工具并对Group类进行简单的验证,结论是并不能完成充分的覆盖性测试,JMLUnitNG的测试主要考虑了一些边界性的数据,如INT_MAX,INT_MIN,0,然而事实上部分数据并不会在应用场景中出现,对与特殊数据的构造不如手动构造来的便捷和快速,并且工具的部署比较繁琐,在测试需求比较高的情况下,这种测试方法并不能够满足要求,对方法的测试还是得依靠同学之间的对拍或者构建评测机来实现。

 [TestNG] Running:
  Command line suite
 
 Failed: racEnabled()
 Passed: constructor MyGroup(0)
 Passed: constructor MyGroup(-2147483648)
 Passed: constructor MyGroup(2147483647)
 Passed: <>.addPerson(null)
 Passed: <>.addPerson(null)
 Passed: <>.addPerson(null)
 Passed: <>.addPerson(java.lang.Object@3db361f2)
 Passed: <>.addPerson(java.lang.Object@7a52b631)
 Passed: <>.addPerson(java.lang.Object@81a3b443)
 Passed: <>.addrelation()
 Passed: <>.addrelation()
 Passed: <>.addrelation()
 
 ===============================================
 Command line suite
 Total tests run: 12, Failures: 0, Skips: 0
 ===============================================

 

三、代码分析

第9次作业

  作为JML第一次作业,本次作业的代码量较小,实现也比较简单。

架构设计

  按照jml规格进行设计,使用ArrayList来存储信息,对isCircle()函数,使用dfs进行搜索,在第一次数据要求下可以满足要求。

类图

OO第三次博客作业_第1张图片

Metrics分析

Method ev(G) iv(G) v(G)
com.homework9.Main.main(String[]) 1 1 1
com.homework9.MyNetwork.MyNetwork() 1 1 1
com.homework9.MyNetwork.addPerson(Person) 3 2 3
com.homework9.MyNetwork.addRelation(int,int,int) 4 4 5
com.homework9.MyNetwork.compareAge(int,int) 2 2 3
com.homework9.MyNetwork.compareName(int,int) 2 2 3
com.homework9.MyNetwork.contains(int) 3 2 3
com.homework9.MyNetwork.dfs(int,int,boolean[]) 2 3 4
com.homework9.MyNetwork.getPerson(int) 4 3 4
com.homework9.MyNetwork.isCircle(int,int) 2 2 3
com.homework9.MyNetwork.queryAcquaintanceSum(int) 2 1 2
com.homework9.MyNetwork.queryConflict(int,int) 2 2 3
com.homework9.MyNetwork.queryNameRank(int) 2 2 4
com.homework9.MyNetwork.queryPeopleSum() 1 1 1
com.homework9.MyNetwork.queryValue(int,int) 3 3 4
com.homework9.MyPerson.MyPerson(int,String,BigInteger,int) 1 1 1
com.homework9.MyPerson.addRelation(MyPerson,int) 1 1 1
com.homework9.MyPerson.compareTo(Person) 1 1 1
com.homework9.MyPerson.equals(Object) 2 1 3
com.homework9.MyPerson.getAcSet() 1 1 1
com.homework9.MyPerson.getAcquaintanceSum() 1 1 1
com.homework9.MyPerson.getAge() 1 1 1
com.homework9.MyPerson.getCharacter() 1 1 1
com.homework9.MyPerson.getId() 1 1 1
com.homework9.MyPerson.getName() 1 1 1
com.homework9.MyPerson.isLinked(Person) 4 2 4
com.homework9.MyPerson.queryValue(Person) 3 3 3
com.homework9.MyPerson.toString() 1 1 1
       
Class OCavg WMC  
com.homework9.Main 1 1  
com.homework9.MyNetwork 2.64 37  
com.homework9.MyPerson 1.46 19  
       
Package v(G)avg v(G)tot  
com.homework9 2.29 64  
       
Module v(G)avg v(G)tot  
OOtask3 2.29 64  
       
Project v(G)avg v(G)tot  
project 2.29 64  

第10次作业

  增加Group类,进行一组数据的处理。

架构设计

  对于数据的存储,对于查询次数较多的数据,尽量避免使用ArrayList来存储,通过HashMap来存储,提高查询的速度。

  修改isCircle()函数,使用bfs进行查询,防止dfs爆栈。对Group内的数据处理,采用缓存机制,尽量避免重复查询时不必要的时间浪费。

类图

OO第三次博客作业_第2张图片

Metrics分析

Method ev(G) iv(G) v(G)
com.homework10.Main.main(String[]) 1 1 1
com.homework10.MyGroup.MyGroup(int) 1 1 1
com.homework10.MyGroup.addPerson(Person) 1 3 3
com.homework10.MyGroup.equals(Object) 2 1 3
com.homework10.MyGroup.getAgeMean() 1 1 1
com.homework10.MyGroup.getAgeVar() 1 1 1
com.homework10.MyGroup.getConflictSum() 1 1 1
com.homework10.MyGroup.getId() 1 1 1
com.homework10.MyGroup.getRelationSum() 1 4 5
com.homework10.MyGroup.getSize() 1 1 1
com.homework10.MyGroup.getValueSum() 1 5 5
com.homework10.MyGroup.hasPerson(Person) 1 1 1
com.homework10.MyGroup.update() 1 1 1
com.homework10.MyNetwork.MyNetwork() 1 1 1
com.homework10.MyNetwork.addGroup(Group) 2 1 2
com.homework10.MyNetwork.addPerson(Person) 4 2 4
com.homework10.MyNetwork.addRelation(int,int,int) 4 5 6
com.homework10.MyNetwork.addtoGroup(int,int) 5 1 5
com.homework10.MyNetwork.bfs(int,int) 3 4 5
com.homework10.MyNetwork.compareAge(int,int) 2 2 3
com.homework10.MyNetwork.compareName(int,int) 2 2 3
com.homework10.MyNetwork.contains(int) 1 1 1
com.homework10.MyNetwork.getGroup(int) 1 1 1
com.homework10.MyNetwork.getPerson(int) 1 1 1
com.homework10.MyNetwork.isCircle(int,int) 2 2 3
com.homework10.MyNetwork.queryAcquaintanceSum(int) 2 1 2
com.homework10.MyNetwork.queryConflict(int,int) 2 2 3
com.homework10.MyNetwork.queryGroupAgeMean(int) 2 1 2
com.homework10.MyNetwork.queryGroupAgeVar(int) 2 1 2
com.homework10.MyNetwork.queryGroupConflictSum(int) 2 1 2
com.homework10.MyNetwork.queryGroupPeopleSum(int) 2 1 2
com.homework10.MyNetwork.queryGroupRelationSum(int) 2 1 2
com.homework10.MyNetwork.queryGroupSum() 1 1 1
com.homework10.MyNetwork.queryGroupValueSum(int) 2 1 2
com.homework10.MyNetwork.queryNameRank(int) 2 2 4
com.homework10.MyNetwork.queryPeopleSum() 1 1 1
com.homework10.MyNetwork.queryValue(int,int) 3 3 4
com.homework10.MyPerson.MyPerson(int,String,BigInteger,int) 1 1 1
com.homework10.MyPerson.addRelation(MyPerson,int) 1 1 1
com.homework10.MyPerson.compareTo(Person) 1 1 1
com.homework10.MyPerson.equals(Object) 2 1 3
com.homework10.MyPerson.getAcSet() 1 1 1
com.homework10.MyPerson.getAcquaintanceSum() 1 1 1
com.homework10.MyPerson.getAge() 1 1 1
com.homework10.MyPerson.getCharacter() 1 1 1
com.homework10.MyPerson.getId() 1 1 1
com.homework10.MyPerson.getName() 1 1 1
com.homework10.MyPerson.hashCode() 1 1 1
com.homework10.MyPerson.isLinked(Person) 2 1 2
com.homework10.MyPerson.queryValue(Person) 3 3 3
com.homework10.MyPerson.toString() 1 1 1
       
Class OCavg WMC  
com.homework10.Main 1 1  
com.homework10.MyGroup 1.92 23  
com.homework10.MyNetwork 2.33 56  
com.homework10.MyPerson 1.29 18  
       
Package v(G)avg v(G)tot  
com.homework10 2.08 106  
       
Module v(G)avg v(G)tot  
OOtask3 2.08 106  
       
Project v(G)avg v(G)tot  
project 2.08 106  

第11次作业

  收官作业,没有增加新的类,但基于Network类增加了一些查询方法,对算法的要求较高。

架构设计

  沿用上次作业的存储方式,使用HashMap来存储访问频率高的数据。

  这次作业有三大难点,一是求单源最短路径,二是求两点是否在一个点双连通分量中,三是求图中联通块数。

  对于单源最短路径,由于没有负权边存在,使用dijkstra算法进行求解,而普通的dijkstra算法由于的复杂度会被卡,所以采用堆优化来降低时间复杂度。

  对于点双连通分量的求解,通过tarjan算法来进行求解。

  对于连通块数的求解,使用并查集来求解,最大限度地降低时间复杂度。

类图

OO第三次博客作业_第3张图片

Metrics分析

Method ev(G) iv(G) v(G)
com.homework11.Main.main(String[]) 1 1 1
com.homework11.Methods.dfs(int,int,MyNetwork) 6 9 11
com.homework11.Methods.getBlock(int,MyNetwork) 1 4 4
com.homework11.MyGroup.MyGroup(int) 1 1 1
com.homework11.MyGroup.addPerson(Person) 1 1 1
com.homework11.MyGroup.delPerson(Person) 3 3 3
com.homework11.MyGroup.equals(Object) 2 1 3
com.homework11.MyGroup.getAgeMean() 2 4 5
com.homework11.MyGroup.getAgeVar() 2 4 5
com.homework11.MyGroup.getConflictSum() 1 1 1
com.homework11.MyGroup.getId() 1 1 1
com.homework11.MyGroup.getRelationSum() 1 4 5
com.homework11.MyGroup.getSize() 1 1 1
com.homework11.MyGroup.getValueSum() 1 5 5
com.homework11.MyGroup.hasPerson(Person) 1 1 1
com.homework11.MyGroup.update() 1 1 1
com.homework11.MyNetwork.MyNetwork() 1 1 1
com.homework11.MyNetwork.addGroup(Group) 2 1 2
com.homework11.MyNetwork.addPerson(Person) 4 2 4
com.homework11.MyNetwork.addRelation(int,int,int) 4 5 6
com.homework11.MyNetwork.addtoGroup(int,int) 5 1 5
com.homework11.MyNetwork.borrowFrom(int,int,int) 3 2 4
com.homework11.MyNetwork.compareAge(int,int) 2 2 3
com.homework11.MyNetwork.compareName(int,int) 2 2 3
com.homework11.MyNetwork.contains(int) 1 1 1
com.homework11.MyNetwork.delFromGroup(int,int) 4 1 4
com.homework11.MyNetwork.dijkstra1(int,int) 5 7 9
com.homework11.MyNetwork.findRoot(int) 1 3 3
com.homework11.MyNetwork.getGroup(int) 1 1 1
com.homework11.MyNetwork.getPeople() 1 1 1
com.homework11.MyNetwork.getPerson(int) 1 1 1
com.homework11.MyNetwork.ifIsCircle(int,int) 2 1 2
com.homework11.MyNetwork.isCircle(int,int) 2 2 3
com.homework11.MyNetwork.join(int,int) 1 2 2
com.homework11.MyNetwork.queryAcquaintanceSum(int) 2 1 2
com.homework11.MyNetwork.queryAgeSum(int,int) 1 3 4
com.homework11.MyNetwork.queryBlockSum() 1 1 1
com.homework11.MyNetwork.queryConflict(int,int) 2 2 3
com.homework11.MyNetwork.queryGroupAgeMean(int) 2 1 2
com.homework11.MyNetwork.queryGroupAgeVar(int) 2 1 2
com.homework11.MyNetwork.queryGroupConflictSum(int) 2 1 2
com.homework11.MyNetwork.queryGroupPeopleSum(int) 2 1 2
com.homework11.MyNetwork.queryGroupRelationSum(int) 2 1 2
com.homework11.MyNetwork.queryGroupSum() 1 1 1
com.homework11.MyNetwork.queryGroupValueSum(int) 2 1 2
com.homework11.MyNetwork.queryMinPath(int,int) 4 2 5
com.homework11.MyNetwork.queryMoney(int) 2 1 2
com.homework11.MyNetwork.queryNameRank(int) 2 2 4
com.homework11.MyNetwork.queryPeopleSum() 1 1 1
com.homework11.MyNetwork.queryStrongLinked(int,int) 5 5 8
com.homework11.MyNetwork.queryValue(int,int) 3 3 4
com.homework11.MyPerson.MyPerson(int,String,BigInteger,int) 1 1 1
com.homework11.MyPerson.addRelation(MyPerson,int) 1 1 1
com.homework11.MyPerson.compareTo(Person) 1 1 1
com.homework11.MyPerson.equals(Object) 2 1 3
com.homework11.MyPerson.getAcSet() 1 1 1
com.homework11.MyPerson.getAcquaintanceSum() 1 1 1
com.homework11.MyPerson.getAge() 1 1 1
com.homework11.MyPerson.getCharacter() 1 1 1
com.homework11.MyPerson.getId() 1 1 1
com.homework11.MyPerson.getName() 1 1 1
com.homework11.MyPerson.hashCode() 1 1 1
com.homework11.MyPerson.isLinked(Person) 2 1 2
com.homework11.MyPerson.queryValue(Person) 2 1 2
com.homework11.MyPerson.toString() 1 1 1
com.homework11.Point.Point(int,int) 1 1 1
com.homework11.Point.compareTo(Point) 1 1 1
com.homework11.Point.equals(Object) 3 1 6
com.homework11.Point.getCost() 1 1 1
com.homework11.Point.getTo() 1 1 1
com.homework11.Point.hashCode() 1 1 1
com.homework11.Point.setCost(int) 1 1 1
com.homework11.Point.setTo(int) 1 1 1
com.homework11.Point.toString() 1 1 1
com.homework11.PointInfo.PointInfo(int,int,int) 1 1 1
com.homework11.PointInfo.getDfn() 1 1 1
com.homework11.PointInfo.getLow() 1 1 1
com.homework11.PointInfo.getParent() 1 1 1
com.homework11.PointInfo.isCutPoint() 1 1 1
com.homework11.PointInfo.setCutPoint(boolean) 1 1 1
com.homework11.PointInfo.setDfn(int) 1 1 1
com.homework11.PointInfo.setLow(int) 1 1 1
com.homework11.PointInfo.setParent(int) 1 1 1
       
Class OCavg WMC  
com.homework11.Main 1 1  
com.homework11.Methods 6 12  
com.homework11.MyGroup 2.46 32  
com.homework11.MyNetwork 2.54 89  
com.homework11.MyPerson 1.21 17  
com.homework11.Point 1.22 11  
com.homework11.PointInfo 1 9  
       
Package v(G)avg v(G)tot  
com.homework11 2.31 192  
       
Module v(G)avg v(G)tot  
OOtask3 2.31 192  
       
Project v(G)avg v(G)tot  
project 2.31 192  

四、bug分析

第一次作业

  强测炸了,原因是在添加关系时,两参数编号相同时会抛出EqualRealtion异常,没进互测,很烦。

第二次作业

  强测和互测都被二重循环卡掉了一个点,修改前每次调用都会遍历一次人员列表来求值,修改后当人员关系发生变化(新增人员、新增关系)会求一次值并保存,降低了时间。

第三次作业

  除了强测被卡了算法时间之外,还出现了Group内无成员求平均数时会出现除零bug,奇怪的是在本地复测却不会出现异常,可能是jdk版本或者ide的问题。

  此外,qmp由于使用了一般的dijkstra算法超时,修复bug时用堆优化加找到最短路径就退出的方式降低了CPU时间。

  qbs由于使用规格中的方法暴力求值导致超时,最后通过在更新并查集的过程中直接算出连通块数目,减少了CPU时间。

五、体会与感想

做完这三次JML作业,我的收获很多:

1)规范化的重要性

  课程组通过JML工具来规定作业需求,并要求我们通过这种规格化的文本来理解作业需求,完成作业,实际上从很大程度上减少了之前使用自然语言描述需求是可能存在的歧义问题。更高效的传达了信息,避开了一些死角。同时,与前两个单元相比,这个单元中对指导书的阅读少了很多,我不用花大量的时间来理解需求,反而是通过简洁的JML语言,快速了解了需求,规划好了完成作业的思路。

2)对JAVA语言的进一步了解

  这一单元的核心是构造一个人际关系网络,实际上是构建一个无向图,并对图中的各种元素进行操作。JML规格中只规定了一个抽象的方法行为,而对于方法的实现,则都有我们自主决定。在实现方法的过程中,容器的选择,算法的选择,都能够影响程序最终的表现。而要准确地选择合适的容器或是算法,就需要理解它们地实现过程,为此,我阅读了一些JAVA容器的源码,学习并实现了一些图论中的经典算法,在这一过程中,我也收获了很多重要的经验。

  总而言之,这一单元应该是我做的比较头痛的一个单元,除了收获,还有很多因为没有仔细阅读规格外加测试不充分而产生的低级错误,最终也导致挂了一次强测,在事后回想这些bug,感觉自己还需要在细节上多花功夫,希望自己在最后一个单元的学习与作业中,能进一步地减少这些低级错误,并有所收获。

 

 

你可能感兴趣的:(OO第三次博客作业)