BUAA_OO_2020_Unit3_Overview

JML理论梳理与工具链分析

JML作为一种行为接口规格语言,可以较为准确地对Java程序的行为进行描述。然而在本人使用过程中,由于其工具链的功能的极不完善,大多数的代码编写及测试还是依靠人力完成的,虽然它具有较高的严谨性,但使用体验并不是很好。

JML的注释结构

JML以javadoc注释的方式表示规格,每行以@起头。行注释为//@annotation,块注释为/* @ annotation @ */

规格变量的声明分为静态与实例两种,声明方式分别为//@public static model non_null int[] elements//@public instance model non_null int[] elements

方法规格的声明主要有三个部分:

  • \requires子句:定义方法的前置条件,是调用者承诺满足的条件。
  • assignable子句:限定方法的副作用范围。assignable \nothing的方法不修改规格变量,因此为/*@pure@*/方法。
  • ensures子句:定义方法的后置条件,是被调用者承诺满足的条件。

JML原子表达式

  • \result表达式:表示一个非void方法执行的返回值。
  • \old(expr)表达式:表示表达式expr在执行方法前的取值。
  • \not_assigned(x, y, ...)表达式:表示括号内的变量在执行中未被赋值。若被赋值则该表达式为false。
  • \not_modified(x, y, ...)表达式:与\not_assigned类似,表示取值未发生变化。
  • \nonnullelements(container)表达式:表示container中不含有null对象。等价于断言: container != null && (\foall int i; 0 <= i && i < container.length; container[i] != null) 
  • \type(expr)表达式:等同于java.lang.class
  • \typeof(expr)表达式:返回准确类型。

JML量化表达式

  • \forall表达式:全称量词修饰的表达式。例如 (\forall int i,j; 0 <= i && i < j && j < 10; a[i] < a[j]) 
  • \exists表达式:存在量词修饰的表达式。例如 (\exists int i; 0 <= i && i < 10; a[i] < 0) 
  • \sum表达式:指定范围内求和表达式。例如 (\sum int i; 0 <= i && i < 5; i) ,这个表达式的值为10。
  • \product表达式:指定范围内连乘表达式。例如  (\product int i; 0 < i && i < 5; i)  ,这个表达式的值为24。
  • \max表达式:最大值表达式。例如 (\max int i; 0 <= i && i < 5; i)  这个表达式的值为4。
  • \min表达式:最小值表达式。例如  (\min int i; 0 <= i && i < 5; i)  这个表达式的值0。
  • \num_of表达式:指定范围内满足条件元素个数表达式。 (\num_of T x; R(x);P(x))  表示R(x)范围内满足P(x)条件的x元素的个数。

JML集合表达式

集合表达式的形式为 new ST {T x|R(x)&&P(x)} ,其中ST是构造的容器,T是数据类型,R是范围约束,P是取值约束。

操作符

  • 子类型关系操作符:E1<:E2,若E1是E2的子类或E1与E2同类则为真,否则为假。
  • 等价关系操作符:expr1<==>expr2expr1<=!=>expr2,两端为布尔表达式,表示两者等价或不等价。
  • 推理操作符:expr1==>expr2,其取值等价于expr1→expr2
  • 变量引用操作符:\nothing表示空集,\everything表示全集

方法规格

除了已经提到的三个方法规格外,还有signals与signals_only,用于exceptional behavior的处理。

  • signals子句:signals (***Exception e) b_expr,意为当b_expr为true时方法抛出异常e。
  • signals_only子句:后接异常类型,表示满足条件时直接抛出异常。

工具链使用情况

openjml、SMT solver、JMLUnit等,使用时总体的感受是网络上关于JML工具链的资料实在不够翔实,并且这些工具本身在功能上也不够完善。

应用JMLUnit自动化生成测试用例

首先用openjml检验了一下各次作业的官方包中的jml,发现多多少少都会有报错,经过修改最终也没有跑通。输出效果如下:

src\com\oocourse\spec1\main\Network.java:107: 错误: Invalid expression or missing semicolon here
      @ signals (PersonIdNotFoundException e) !contains(id));
                                                           ^
src\com\oocourse\spec1\main\Network.java:138: 错误: Invalid expression or missing semicolon here
      @ signals (PersonIdNotFoundException e) !contains(id));
                                                           ^
src\com\oocourse\spec1\main\Person.java:19: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ String getName();
^
src\com\oocourse\spec1\main\Person.java:22: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ BigInteger getCharacter();
^
src\com\oocourse\spec1\main\Person.java:25: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ int getAge();
^
src\com\oocourse\spec3\main\Group.java:57: 错误: 不可比较的类型: int和INT#1
    /*@ ensures \result == (people.length == 0? 0 :
                        ^
  其中, INT#1是交叉类型:
    INT#1扩展Number,Comparable
src\com\oocourse\spec3\main\Group.java:62: 错误: 不可比较的类型: int和INT#1
    /*@ ensures \result == (people.length == 0? 0 : ((\sum int i; 0 <= i && i < people.length;
                        ^
  其中, INT#1是交叉类型:
    INT#1扩展Number,Comparable
src\com\oocourse\spec1\main\Network.java:30: 错误: A \old token with no label may not be present in a requires clause
      @ requires !(\exists int i; 0 <= i && i < \old(people.length);
                                                     ^
src\com\oocourse\spec1\main\Network.java:31: 错误: A \old token with no label may not be present in a requires clause
      @              \old(people[i].equals(person)));
                          ^
src\com\oocourse\spec1\main\Network.java:46: 错误: A \old token with no label may not be present in a requires clause
      @             !\old(getPerson(id1).isLinked(getPerson(id2)));
                          ^
src\com\oocourse\spec1\main\Network.java:73: 错误: A \old token with no label may not be present in a requires clause
      @             (\old(getPerson(id1)).isLinked(\old(getPerson(id2))) && id1 != id2);
                                                        ^
src\com\oocourse\spec1\main\Network.java:73: 错误: A \old token with no label may not be present in a requires clause
      @             (\old(getPerson(id1)).isLinked(\old(getPerson(id2))) && id1 != id2);
                          ^
src\com\oocourse\spec1\main\Network.java:104: 错误: 无法取消引用int
      @ ensures \result.equals(getPerson(id).getAcquaintanceSum());
                       ^
src\com\oocourse\spec1\main\Network.java:122: 错误: 找不到符号
      @ ensures \result == getPerson(id1).getName().compareTo(getPerson(id2).getName);
                                                                            ^
  符号:   变量 getName
  位置: 接口 Person
src\com\oocourse\spec1\main\Person.java:27: 警告: Method equals overrides parent class methods and so its specification should begin with 'also'
    /*@ public normal_behavior
               ^
src\com\oocourse\spec1\main\Person.java:63: 错误: 找不到符号
    //@ ensures \result == name.compareTo(p2.getName);
                                            ^
  符号:   变量 getName
  位置: 类型为Person的变量 p2
src\com\oocourse\spec1\main\Person.java:63: 警告: Method compareTo overrides parent class methods and so its specification should begin with 'also'
    //@ ensures \result == name.compareTo(p2.getName);
        ^
15 个错误
2 个警告
openjml运行结果(静态检查)
src\com\oocourse\spec1\main\Network.java:107: 错误: Invalid expression or missing semicolon here
      @ signals (PersonIdNotFoundException e) !contains(id));
                                                           ^
src\com\oocourse\spec1\main\Network.java:138: 错误: Invalid expression or missing semicolon here
      @ signals (PersonIdNotFoundException e) !contains(id));
                                                           ^
src\com\oocourse\spec1\main\Person.java:19: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ String getName();
^
src\com\oocourse\spec1\main\Person.java:22: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ BigInteger getCharacter();
^
src\com\oocourse\spec1\main\Person.java:25: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ int getAge();
^
The operation symbol ++ for type java.lang.Object could not be resolved
org.jmlspecs.openjml.JmlInternalError: The operation symbol ++ for type java.lang.Object could not be resolved
        at org.jmlspecs.openjml.JmlTreeUtils.findOpSymbol(JmlTreeUtils.java:291)
        at org.jmlspecs.openjml.JmlTreeUtils.findOpSymbol(JmlTreeUtils.java:282)
        at org.jmlspecs.openjml.JmlTreeUtils.makeUnary(JmlTreeUtils.java:739)
        at com.sun.tools.javac.comp.JmlAttr.createRacExpr(JmlAttr.java:4465)
        at org.jmlspecs.openjml.ext.QuantifiedExpressions$QuantifiedExpression.typecheck(QuantifiedExpressions.java:214)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlQuantifiedExpr(JmlAttr.java:4070)
        at org.jmlspecs.openjml.JmlTree$JmlQuantifiedExpr.accept(JmlTree.java:2685)
        at com.sun.tools.javac.comp.Attr.attribTree(Attr.java:577)
        at com.sun.tools.javac.comp.Attr.visitParens(Attr.java:2995)
        at com.sun.tools.javac.tree.JCTree$JCParens.accept(JCTree.java:1661)
        at com.sun.tools.javac.comp.Attr.attribTree(Attr.java:577)
        at com.sun.tools.javac.comp.Attr.attribExpr(Attr.java:619)
        at com.sun.tools.javac.comp.JmlAttr.attribExpr(JmlAttr.java:6209)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlMethodClauseExpr(JmlAttr.java:3117)
        at org.jmlspecs.openjml.JmlTree$JmlMethodClauseExpr.accept(JmlTree.java:2332)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlSpecificationCase(JmlAttr.java:3361)
        at org.jmlspecs.openjml.JmlTree$JmlSpecificationCase.accept(JmlTree.java:2837)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlMethodSpecs(JmlAttr.java:3423)
        at org.jmlspecs.openjml.JmlTree$JmlMethodSpecs.accept(JmlTree.java:2539)
        at com.sun.tools.javac.comp.JmlAttr.checkMethodSpecsDirectly(JmlAttr.java:1560)
        at com.sun.tools.javac.comp.JmlAttr.visitMethodDef(JmlAttr.java:1121)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlMethodDecl(JmlAttr.java:6053)
        at org.jmlspecs.openjml.JmlTree$JmlMethodDecl.accept(JmlTree.java:1261)
        at com.sun.tools.javac.comp.Attr.attribTree(Attr.java:577)
        at com.sun.tools.javac.comp.Attr.attribStat(Attr.java:646)
        at com.sun.tools.javac.comp.JmlAttr.attribStat(JmlAttr.java:558)
        at com.sun.tools.javac.comp.Attr.attribClassBody(Attr.java:4378)
        at com.sun.tools.javac.comp.JmlAttr.attribClassBody(JmlAttr.java:536)
        at com.sun.tools.javac.comp.Attr.attribClass(Attr.java:4286)
        at com.sun.tools.javac.comp.JmlAttr.attribClass(JmlAttr.java:414)
        at com.sun.tools.javac.comp.JmlAttr.completeTodo(JmlAttr.java:492)
        at com.sun.tools.javac.comp.JmlAttr.attribClass(JmlAttr.java:458)
        at com.sun.tools.javac.comp.Attr.attribClass(Attr.java:4215)
        at com.sun.tools.javac.comp.Attr.attrib(Attr.java:4190)
        at com.sun.tools.javac.main.JavaCompiler.attribute(JavaCompiler.java:1258)
        at com.sun.tools.javac.main.JmlCompiler.attribute(JmlCompiler.java:479)
        at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:898)
        at com.sun.tools.javac.main.JmlCompiler.compile2(JmlCompiler.java:712)
        at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:867)
        at com.sun.tools.javac.main.Main.compile(Main.java:553)
        at com.sun.tools.javac.main.Main.compile(Main.java:410)
        at org.jmlspecs.openjml.Main.compile(Main.java:581)
        at com.sun.tools.javac.main.Main.compile(Main.java:399)
        at com.sun.tools.javac.main.Main.compile(Main.java:390)
        at org.jmlspecs.openjml.Main.execute(Main.java:417)
        at org.jmlspecs.openjml.Main.execute(Main.java:375)
        at org.jmlspecs.openjml.Main.execute(Main.java:362)
        at org.jmlspecs.openjml.Main.main(Main.java:334)
src\com\oocourse\spec1\main\Network.java:30: 错误: A \old token with no label may not be present in a requires clause
      @ requires !(\exists int i; 0 <= i && i < \old(people.length);
                                                     ^
src\com\oocourse\spec1\main\Network.java:31: 错误: A \old token with no label may not be present in a requires clause
      @              \old(people[i].equals(person)));
                          ^
src\com\oocourse\spec1\main\Network.java:46: 错误: A \old token with no label may not be present in a requires clause
      @             !\old(getPerson(id1).isLinked(getPerson(id2)));
                          ^
src\com\oocourse\spec1\main\Network.java:73: 错误: A \old token with no label may not be present in a requires clause
      @             (\old(getPerson(id1)).isLinked(\old(getPerson(id2))) && id1 != id2);
                                                        ^
src\com\oocourse\spec1\main\Network.java:73: 错误: A \old token with no label may not be present in a requires clause
      @             (\old(getPerson(id1)).isLinked(\old(getPerson(id2))) && id1 != id2);
                          ^
src\com\oocourse\spec1\main\Network.java:104: 错误: 无法取消引用int
      @ ensures \result.equals(getPerson(id).getAcquaintanceSum());
                       ^
src\com\oocourse\spec1\main\Network.java:122: 错误: 找不到符号
      @ ensures \result == getPerson(id1).getName().compareTo(getPerson(id2).getName);
                                                                            ^
  符号:   变量 getName
  位置: 接口 Person
src\com\oocourse\spec1\main\Person.java:27: 警告: Method equals overrides parent class methods and so its specification should begin with 'also'
    /*@ public normal_behavior
               ^
src\com\oocourse\spec1\main\Person.java:63: 错误: 找不到符号
    //@ ensures \result == name.compareTo(p2.getName);
                                            ^
  符号:   变量 getName
  位置: 类型为Person的变量 p2
src\com\oocourse\spec1\main\Person.java:63: 警告: Method compareTo overrides parent class methods and so its specification should begin with 'also'
    //@ ensures \result == name.compareTo(p2.getName);
        ^
13 个错误
2 个警告src\com\oocourse\spec1\main\Network.java:107: 错误: Invalid expression or missing semicolon here
      @ signals (PersonIdNotFoundException e) !contains(id));
                                                           ^
src\com\oocourse\spec1\main\Network.java:138: 错误: Invalid expression or missing semicolon here
      @ signals (PersonIdNotFoundException e) !contains(id));
                                                           ^
src\com\oocourse\spec1\main\Person.java:19: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ String getName();
^
src\com\oocourse\spec1\main\Person.java:22: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ BigInteger getCharacter();
^
src\com\oocourse\spec1\main\Person.java:25: 错误: The expression is invalid or not terminated by a semicolon
    public /*@pure@*/ int getAge();
^
The operation symbol ++ for type java.lang.Object could not be resolved
org.jmlspecs.openjml.JmlInternalError: The operation symbol ++ for type java.lang.Object could not be resolved
        at org.jmlspecs.openjml.JmlTreeUtils.findOpSymbol(JmlTreeUtils.java:291)
        at org.jmlspecs.openjml.JmlTreeUtils.findOpSymbol(JmlTreeUtils.java:282)
        at org.jmlspecs.openjml.JmlTreeUtils.makeUnary(JmlTreeUtils.java:739)
        at com.sun.tools.javac.comp.JmlAttr.createRacExpr(JmlAttr.java:4465)
        at org.jmlspecs.openjml.ext.QuantifiedExpressions$QuantifiedExpression.typecheck(QuantifiedExpressions.java:214)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlQuantifiedExpr(JmlAttr.java:4070)
        at org.jmlspecs.openjml.JmlTree$JmlQuantifiedExpr.accept(JmlTree.java:2685)
        at com.sun.tools.javac.comp.Attr.attribTree(Attr.java:577)
        at com.sun.tools.javac.comp.Attr.visitParens(Attr.java:2995)
        at com.sun.tools.javac.tree.JCTree$JCParens.accept(JCTree.java:1661)
        at com.sun.tools.javac.comp.Attr.attribTree(Attr.java:577)
        at com.sun.tools.javac.comp.Attr.attribExpr(Attr.java:619)
        at com.sun.tools.javac.comp.JmlAttr.attribExpr(JmlAttr.java:6209)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlMethodClauseExpr(JmlAttr.java:3117)
        at org.jmlspecs.openjml.JmlTree$JmlMethodClauseExpr.accept(JmlTree.java:2332)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlSpecificationCase(JmlAttr.java:3361)
        at org.jmlspecs.openjml.JmlTree$JmlSpecificationCase.accept(JmlTree.java:2837)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlMethodSpecs(JmlAttr.java:3423)
        at org.jmlspecs.openjml.JmlTree$JmlMethodSpecs.accept(JmlTree.java:2539)
        at com.sun.tools.javac.comp.JmlAttr.checkMethodSpecsDirectly(JmlAttr.java:1560)
        at com.sun.tools.javac.comp.JmlAttr.visitMethodDef(JmlAttr.java:1121)
        at com.sun.tools.javac.comp.JmlAttr.visitJmlMethodDecl(JmlAttr.java:6053)
        at org.jmlspecs.openjml.JmlTree$JmlMethodDecl.accept(JmlTree.java:1261)
        at com.sun.tools.javac.comp.Attr.attribTree(Attr.java:577)
        at com.sun.tools.javac.comp.Attr.attribStat(Attr.java:646)
        at com.sun.tools.javac.comp.JmlAttr.attribStat(JmlAttr.java:558)
        at com.sun.tools.javac.comp.Attr.attribClassBody(Attr.java:4378)
        at com.sun.tools.javac.comp.JmlAttr.attribClassBody(JmlAttr.java:536)
        at com.sun.tools.javac.comp.Attr.attribClass(Attr.java:4286)
        at com.sun.tools.javac.comp.JmlAttr.attribClass(JmlAttr.java:414)
        at com.sun.tools.javac.comp.JmlAttr.completeTodo(JmlAttr.java:492)
        at com.sun.tools.javac.comp.JmlAttr.attribClass(JmlAttr.java:458)
        at com.sun.tools.javac.comp.Attr.attribClass(Attr.java:4215)
        at com.sun.tools.javac.comp.Attr.attrib(Attr.java:4190)
        at com.sun.tools.javac.main.JavaCompiler.attribute(JavaCompiler.java:1258)
        at com.sun.tools.javac.main.JmlCompiler.attribute(JmlCompiler.java:479)
        at com.sun.tools.javac.main.JavaCompiler.compile2(JavaCompiler.java:898)
        at com.sun.tools.javac.main.JmlCompiler.compile2(JmlCompiler.java:712)
        at com.sun.tools.javac.main.JavaCompiler.compile(JavaCompiler.java:867)
        at com.sun.tools.javac.main.Main.compile(Main.java:553)
        at com.sun.tools.javac.main.Main.compile(Main.java:410)
        at org.jmlspecs.openjml.Main.compile(Main.java:581)
        at com.sun.tools.javac.main.Main.compile(Main.java:399)
        at com.sun.tools.javac.main.Main.compile(Main.java:390)
        at org.jmlspecs.openjml.Main.execute(Main.java:417)
        at org.jmlspecs.openjml.Main.execute(Main.java:375)
        at org.jmlspecs.openjml.Main.execute(Main.java:362)
        at org.jmlspecs.openjml.Main.main(Main.java:334)
src\com\oocourse\spec1\main\Network.java:30: 错误: A \old token with no label may not be present in a requires clause
      @ requires !(\exists int i; 0 <= i && i < \old(people.length);
                                                     ^
src\com\oocourse\spec1\main\Network.java:31: 错误: A \old token with no label may not be present in a requires clause
      @              \old(people[i].equals(person)));
                          ^
src\com\oocourse\spec1\main\Network.java:46: 错误: A \old token with no label may not be present in a requires clause
      @             !\old(getPerson(id1).isLinked(getPerson(id2)));
                          ^
src\com\oocourse\spec1\main\Network.java:73: 错误: A \old token with no label may not be present in a requires clause
      @             (\old(getPerson(id1)).isLinked(\old(getPerson(id2))) && id1 != id2);
                                                        ^
src\com\oocourse\spec1\main\Network.java:73: 错误: A \old token with no label may not be present in a requires clause
      @             (\old(getPerson(id1)).isLinked(\old(getPerson(id2))) && id1 != id2);
                          ^
src\com\oocourse\spec1\main\Network.java:104: 错误: 无法取消引用int
      @ ensures \result.equals(getPerson(id).getAcquaintanceSum());
                       ^
src\com\oocourse\spec1\main\Network.java:122: 错误: 找不到符号
      @ ensures \result == getPerson(id1).getName().compareTo(getPerson(id2).getName);
                                                                            ^
  符号:   变量 getName
  位置: 接口 Person
src\com\oocourse\spec1\main\Person.java:27: 警告: Method equals overrides parent class methods and so its specification should begin with 'also'
    /*@ public normal_behavior
               ^
src\com\oocourse\spec1\main\Person.java:63: 错误: 找不到符号
    //@ ensures \result == name.compareTo(p2.getName);
                                            ^
  符号:   变量 getName
  位置: 类型为Person的变量 p2
src\com\oocourse\spec1\main\Person.java:63: 警告: Method compareTo overrides parent class methods and so its specification should begin with 'also'
    //@ ensures \result == name.compareTo(p2.getName);
        ^
13 个错误
2 个警告
openjml运行结果(动态检查)

然后使用JMLUnit对MyGroup生成测试用例:

1 java8 -jar jmlunitng.jar MyGroup.java
2 javac8 -cp jmlunitng.jar *.java
3 java8 -cp jmlunitng.jar MyGroup_JML_Test
[TestNG] Running:
  Command line suite

Failed: racEnabled()
Passed: constructor MyGroup(-2147483648)
Passed: constructor MyGroup(0)
Passed: constructor MyGroup(2147483647)
Passed: <>.add2relation()
Passed: <>.add2relation()
Passed: <>.add2relation()
Passed: <>.add2value(-2147483648)
Passed: <>.add2value(-2147483648)
Passed: <>.add2value(-2147483648)
Passed: <>.add2value(0)
Passed: <>.add2value(0)
Passed: <>.add2value(0)
Passed: <>.add2value(2147483647)
Passed: <>.add2value(2147483647)
Passed: <>.add2value(2147483647)
Passed: <>.addPerson(null)
Passed: <>.addPerson(null)
Passed: <>.addPerson(null)
Passed: <>.addPerson(java.lang.Object@4ca8195f)
Passed: <>.addPerson(java.lang.Object@61baa894)
Passed: <>.addPerson(java.lang.Object@490d6c15)
Passed: <>.delPerson(null)
Passed: <>.delPerson(null)
Passed: <>.delPerson(null)
Passed: <>.delPerson(java.lang.Object@7bfcd12c)
Passed: <>.delPerson(java.lang.Object@24273305)
Passed: <>.delPerson(java.lang.Object@46f5f779)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(java.lang.Object@270421f5)
Passed: <>.equals(java.lang.Object@4f4a7090)
Passed: <>.equals(java.lang.Object@6956de9)
Passed: <>.getAgeMean()
Passed: <>.getAgeMean()
Passed: <>.getAgeMean()
Passed: <>.getAgeVar()
Passed: <>.getAgeVar()
Passed: <>.getAgeVar()
Passed: <>.getConflictSum()
Passed: <>.getConflictSum()
Passed: <>.getConflictSum()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getId()
Passed: <>.getRelationSum()
Passed: <>.getRelationSum()
Passed: <>.getRelationSum()
Passed: <>.getValueSum()
Passed: <>.getValueSum()
Passed: <>.getValueSum()
Passed: <>.hasPerson(null)
Passed: <>.hasPerson(null)
Passed: <>.hasPerson(null)
Passed: <>.hasPerson(java.lang.Object@2d6a9952)
Passed: <>.hasPerson(java.lang.Object@3930015a)
Passed: <>.hasPerson(java.lang.Object@1bc6a36e)
Passed: <>.size()
Passed: <>.size()
Passed: <>.size()

===============================================
Command line suite
Total tests run: 61, Failures: 1, Skips: 0
===============================================
JMLUnit运行效果

经过一番魔改终于成功运行。然而观察可以发现其只是将INT_MAX、INT_MIN、0、null等特殊值代入,基本上没有实际意义。

作业架构分析

由于官方包中接口的限定,本次作业在架构设计上比较单一,只要设计相应类并实现相应接口即可。主要的创新空间在于具体方法的实现上。由于本单元作业为增量开发,此处仅给出最后一次作业的UML图。

BUAA_OO_2020_Unit3_Overview_第1张图片

除了规定中要实现的各个类外,我还编写了MyRelation类用来管理关系、MyBalance类用来管理存款、UnionFindSet并查集类用来优化查找速度。

在实现细节方面,在第一次作业中我使用了ArrayList来存储各个量,但到第二次作业时迫于10w指令的大计算量,我改用了以id为key的hashmap作为查找容器、ArrayList作为枚举容器的混合方法。

第一次作业几乎没有难点,主要就是一些细节方面的内容,只要仔细阅读、正确理解JML即不会出现问题。第一次作业最复杂的方法是query_circle(),我采用了广度优先搜索的方法。

第二次作业主要是各种query_sum方法比较麻烦,并且考虑到10w指令的影响,需要将各个方法时间复杂度控制在O(N)范围内。我采取的策略时person与relation更新时同时更新各个sum,并将其取值缓存。用这种方式,ap与ar时间复杂度为O(N),各个查询方法时间复杂度为O(1),在性能上达到了要求。

第三次作业的qsl和qmp是最困难的两个方法。我的qsl采用的是枚举删点法,也就是如果某个人与两个端点均连通,则将这个人删去,再求两个端点的连通性。如此遍历关系网中的各个人,如果所有的结果均为连通,则两个端点为强连通。根据估计,计算量最高约为10^8数量级,勉强符合要求,也通过了强测。qmp我采用的是堆优化的dijkstra算法,虽然理论复杂度只有O(nlogn),但由于实现时不够注意细节,在找到人时没有提前break,也没有重写person类的hashcode()方法,导致强测中一个点CTLE。修改后快了0.8s有余,这使我反思,不能仅依靠理论复杂度,更应该注意细节方面的问题。

Bug与修复情况

前两次作业在强测与互测中均没有被发现bug。第三次作业强测CTLE一个点(qmp方法),互测没有被发现bug。修复时重写了person的hashcode,简化了访问标记的初始化过程(主要是去除了一个O(N)遍历),并加入了找到时的提前break。成功修复了CTLE的点。

心得体会

首先的感受是来到了JML这一单元后OO作业的负担减轻了许多,从需要自己设计到只需要根据规格来实现方法,的确简单了许多。但仅仅根据规格又是不够的,还需要我们考虑实现上的细节与性能方面的优化。除此之外,我对“契约式”的理解更加深入了,只有接口的提供者与实现者遵守一套共同的规范,才能保证程序的正确性。

这个单元虽然学习了JML,但因为没有真正地动手写过JML代码(只有实验课做了几个JML的填空题),至今只能读JML来实现方法,而无法写出自己的JML。而且对JUnit的使用还不是十分熟练,感觉在形式化验证与单元测试这一方面需要学习的东西还有很多。

你可能感兴趣的:(BUAA_OO_2020_Unit3_Overview)