• 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

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

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

    JML原子表达式

    • esult表达式:表示一个非void方法执行的返回值。
    • old(expr)表达式:表示表达式expr在执行方法前的取值。
    • ot_assigned(x, y, ...)表达式:表示括号内的变量在执行中未被赋值。若被赋值则该表达式为false。
    • ot_modified(x, y, ...)表达式:与 ot_assigned类似,表示取值未发生变化。
    • onnullelements(container)表达式:表示container中不含有null对象。等价于断言: container != null && (foall int i; 0 <= i && i < container.length; container[i] != null) 
    • ype(expr)表达式:等同于java.lang.class
    • ypeof(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。
    • um_of表达式:指定范围内满足条件元素个数表达式。 ( um_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
    • 变量引用操作符: othing表示空集,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,发现多多少少都会有报错,经过修改最终也没有跑通。输出效果如下:

    srccomoocoursespec1mainNetwork.java:107: 错误: Invalid expression or missing semicolon here
          @ signals (PersonIdNotFoundException e) !contains(id));
                                                               ^
    srccomoocoursespec1mainNetwork.java:138: 错误: Invalid expression or missing semicolon here
          @ signals (PersonIdNotFoundException e) !contains(id));
                                                               ^
    srccomoocoursespec1mainPerson.java:19: 错误: The expression is invalid or not terminated by a semicolon
        public /*@pure@*/ String getName();
    ^
    srccomoocoursespec1mainPerson.java:22: 错误: The expression is invalid or not terminated by a semicolon
        public /*@pure@*/ BigInteger getCharacter();
    ^
    srccomoocoursespec1mainPerson.java:25: 错误: The expression is invalid or not terminated by a semicolon
        public /*@pure@*/ int getAge();
    ^
    srccomoocoursespec3mainGroup.java:57: 错误: 不可比较的类型: int和INT#1
        /*@ ensures 
    esult == (people.length == 0? 0 :
                            ^
      其中, INT#1是交叉类型:
        INT#1扩展Number,Comparable
    srccomoocoursespec3mainGroup.java:62: 错误: 不可比较的类型: int和INT#1
        /*@ ensures 
    esult == (people.length == 0? 0 : ((sum int i; 0 <= i && i < people.length;
                            ^
      其中, INT#1是交叉类型:
        INT#1扩展Number,Comparable
    srccomoocoursespec1mainNetwork.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);
                                                         ^
    srccomoocoursespec1mainNetwork.java:31: 错误: A old token with no label may not be present in a requires clause
          @              old(people[i].equals(person)));
                              ^
    srccomoocoursespec1mainNetwork.java:46: 错误: A old token with no label may not be present in a requires clause
          @             !old(getPerson(id1).isLinked(getPerson(id2)));
                              ^
    srccomoocoursespec1mainNetwork.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);
                                                            ^
    srccomoocoursespec1mainNetwork.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);
                              ^
    srccomoocoursespec1mainNetwork.java:104: 错误: 无法取消引用int
          @ ensures 
    esult.equals(getPerson(id).getAcquaintanceSum());
                           ^
    srccomoocoursespec1mainNetwork.java:122: 错误: 找不到符号
          @ ensures 
    esult == getPerson(id1).getName().compareTo(getPerson(id2).getName);
                                                                                ^
      符号:   变量 getName
      位置: 接口 Person
    srccomoocoursespec1mainPerson.java:27: 警告: Method equals overrides parent class methods and so its specification should begin with 'also'
        /*@ public normal_behavior
                   ^
    srccomoocoursespec1mainPerson.java:63: 错误: 找不到符号
        //@ ensures 
    esult == name.compareTo(p2.getName);
                                                ^
      符号:   变量 getName
      位置: 类型为Person的变量 p2
    srccomoocoursespec1mainPerson.java:63: 警告: Method compareTo overrides parent class methods and so its specification should begin with 'also'
        //@ ensures 
    esult == name.compareTo(p2.getName);
            ^
    15 个错误
    2 个警告
    openjml运行结果(静态检查)
    srccomoocoursespec1mainNetwork.java:107: 错误: Invalid expression or missing semicolon here
          @ signals (PersonIdNotFoundException e) !contains(id));
                                                               ^
    srccomoocoursespec1mainNetwork.java:138: 错误: Invalid expression or missing semicolon here
          @ signals (PersonIdNotFoundException e) !contains(id));
                                                               ^
    srccomoocoursespec1mainPerson.java:19: 错误: The expression is invalid or not terminated by a semicolon
        public /*@pure@*/ String getName();
    ^
    srccomoocoursespec1mainPerson.java:22: 错误: The expression is invalid or not terminated by a semicolon
        public /*@pure@*/ BigInteger getCharacter();
    ^
    srccomoocoursespec1mainPerson.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)
    srccomoocoursespec1mainNetwork.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);
                                                         ^
    srccomoocoursespec1mainNetwork.java:31: 错误: A old token with no label may not be present in a requires clause
          @              old(people[i].equals(person)));
                              ^
    srccomoocoursespec1mainNetwork.java:46: 错误: A old token with no label may not be present in a requires clause
          @             !old(getPerson(id1).isLinked(getPerson(id2)));
                              ^
    srccomoocoursespec1mainNetwork.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);
                                                            ^
    srccomoocoursespec1mainNetwork.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);
                              ^
    srccomoocoursespec1mainNetwork.java:104: 错误: 无法取消引用int
          @ ensures 
    esult.equals(getPerson(id).getAcquaintanceSum());
                           ^
    srccomoocoursespec1mainNetwork.java:122: 错误: 找不到符号
          @ ensures 
    esult == getPerson(id1).getName().compareTo(getPerson(id2).getName);
                                                                                ^
      符号:   变量 getName
      位置: 接口 Person
    srccomoocoursespec1mainPerson.java:27: 警告: Method equals overrides parent class methods and so its specification should begin with 'also'
        /*@ public normal_behavior
                   ^
    srccomoocoursespec1mainPerson.java:63: 错误: 找不到符号
        //@ ensures 
    esult == name.compareTo(p2.getName);
                                                ^
      符号:   变量 getName
      位置: 类型为Person的变量 p2
    srccomoocoursespec1mainPerson.java:63: 警告: Method compareTo overrides parent class methods and so its specification should begin with 'also'
        //@ ensures 
    esult == name.compareTo(p2.getName);
            ^
    13 个错误
    2 个警告srccomoocoursespec1mainNetwork.java:107: 错误: Invalid expression or missing semicolon here
          @ signals (PersonIdNotFoundException e) !contains(id));
                                                               ^
    srccomoocoursespec1mainNetwork.java:138: 错误: Invalid expression or missing semicolon here
          @ signals (PersonIdNotFoundException e) !contains(id));
                                                               ^
    srccomoocoursespec1mainPerson.java:19: 错误: The expression is invalid or not terminated by a semicolon
        public /*@pure@*/ String getName();
    ^
    srccomoocoursespec1mainPerson.java:22: 错误: The expression is invalid or not terminated by a semicolon
        public /*@pure@*/ BigInteger getCharacter();
    ^
    srccomoocoursespec1mainPerson.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)
    srccomoocoursespec1mainNetwork.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);
                                                         ^
    srccomoocoursespec1mainNetwork.java:31: 错误: A old token with no label may not be present in a requires clause
          @              old(people[i].equals(person)));
                              ^
    srccomoocoursespec1mainNetwork.java:46: 错误: A old token with no label may not be present in a requires clause
          @             !old(getPerson(id1).isLinked(getPerson(id2)));
                              ^
    srccomoocoursespec1mainNetwork.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);
                                                            ^
    srccomoocoursespec1mainNetwork.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);
                              ^
    srccomoocoursespec1mainNetwork.java:104: 错误: 无法取消引用int
          @ ensures 
    esult.equals(getPerson(id).getAcquaintanceSum());
                           ^
    srccomoocoursespec1mainNetwork.java:122: 错误: 找不到符号
          @ ensures 
    esult == getPerson(id1).getName().compareTo(getPerson(id2).getName);
                                                                                ^
      符号:   变量 getName
      位置: 接口 Person
    srccomoocoursespec1mainPerson.java:27: 警告: Method equals overrides parent class methods and so its specification should begin with 'also'
        /*@ public normal_behavior
                   ^
    srccomoocoursespec1mainPerson.java:63: 错误: 找不到符号
        //@ ensures 
    esult == name.compareTo(p2.getName);
                                                ^
      符号:   变量 getName
      位置: 类型为Person的变量 p2
    srccomoocoursespec1mainPerson.java:63: 警告: Method compareTo overrides parent class methods and so its specification should begin with 'also'
        //@ ensures 
    esult == 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: <<MyGroup@6842775d>>.add2relation()
    Passed: <<MyGroup@574caa3f>>.add2relation()
    Passed: <<MyGroup@1761e840>>.add2relation()
    Passed: <<MyGroup@6c629d6e>>.add2value(-2147483648)
    Passed: <<MyGroup@5ecddf8f>>.add2value(-2147483648)
    Passed: <<MyGroup@3f102e87>>.add2value(-2147483648)
    Passed: <<MyGroup@27abe2cd>>.add2value(0)
    Passed: <<MyGroup@5f5a92bb>>.add2value(0)
    Passed: <<MyGroup@6fdb1f78>>.add2value(0)
    Passed: <<MyGroup@51016012>>.add2value(2147483647)
    Passed: <<MyGroup@29444d75>>.add2value(2147483647)
    Passed: <<MyGroup@2280cdac>>.add2value(2147483647)
    Passed: <<MyGroup@1517365b>>.addPerson(null)
    Passed: <<MyGroup@4fccd51b>>.addPerson(null)
    Passed: <<MyGroup@44e81672>>.addPerson(null)
    Passed: <<MyGroup@60215eee>>.addPerson(java.lang.Object@4ca8195f)
    Passed: <<MyGroup@65e579dc>>.addPerson(java.lang.Object@61baa894)
    Passed: <<MyGroup@768debd>>.addPerson(java.lang.Object@490d6c15)
    Passed: <<MyGroup@7d4793a8>>.delPerson(null)
    Passed: <<MyGroup@449b2d27>>.delPerson(null)
    Passed: <<MyGroup@27082746>>.delPerson(null)
    Passed: <<MyGroup@66133adc>>.delPerson(java.lang.Object@7bfcd12c)
    Passed: <<MyGroup@42f30e0a>>.delPerson(java.lang.Object@24273305)
    Passed: <<MyGroup@5b1d2887>>.delPerson(java.lang.Object@46f5f779)
    Passed: <<MyGroup@1c2c22f3>>.equals(null)
    Passed: <<MyGroup@18e8568>>.equals(null)
    Passed: <<MyGroup@33e5ccce>>.equals(null)
    Passed: <<MyGroup@5a42bbf4>>.equals(java.lang.Object@270421f5)
    Passed: <<MyGroup@52d455b8>>.equals(java.lang.Object@4f4a7090)
    Passed: <<MyGroup@18ef96>>.equals(java.lang.Object@6956de9)
    Passed: <<MyGroup@6aceb1a5>>.getAgeMean()
    Passed: <<MyGroup@2d6d8735>>.getAgeMean()
    Passed: <<MyGroup@ba4d54>>.getAgeMean()
    Passed: <<MyGroup@12bc6874>>.getAgeVar()
    Passed: <<MyGroup@de0a01f>>.getAgeVar()
    Passed: <<MyGroup@4c75cab9>>.getAgeVar()
    Passed: <<MyGroup@1ef7fe8e>>.getConflictSum()
    Passed: <<MyGroup@6f79caec>>.getConflictSum()
    Passed: <<MyGroup@67117f44>>.getConflictSum()
    Passed: <<MyGroup@5d3411d>>.getId()
    Passed: <<MyGroup@2471cca7>>.getId()
    Passed: <<MyGroup@5fe5c6f>>.getId()
    Passed: <<MyGroup@6979e8cb>>.getRelationSum()
    Passed: <<MyGroup@763d9750>>.getRelationSum()
    Passed: <<MyGroup@5c0369c4>>.getRelationSum()
    Passed: <<MyGroup@2be94b0f>>.getValueSum()
    Passed: <<MyGroup@d70c109>>.getValueSum()
    Passed: <<MyGroup@17ed40e0>>.getValueSum()
    Passed: <<MyGroup@50675690>>.hasPerson(null)
    Passed: <<MyGroup@31b7dea0>>.hasPerson(null)
    Passed: <<MyGroup@3ac42916>>.hasPerson(null)
    Passed: <<MyGroup@47d384ee>>.hasPerson(java.lang.Object@2d6a9952)
    Passed: <<MyGroup@22a71081>>.hasPerson(java.lang.Object@3930015a)
    Passed: <<MyGroup@629f0666>>.hasPerson(java.lang.Object@1bc6a36e)
    Passed: <<MyGroup@1ff8b8f>>.size()
    Passed: <<MyGroup@387c703b>>.size()
    Passed: <<MyGroup@224aed64>>.size()
    
    ===============================================
    Command line suite
    Total tests run: 61, Failures: 1, Skips: 0
    ===============================================
    JMLUnit运行效果

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

    作业架构分析

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

    除了规定中要实现的各个类外,我还编写了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的使用还不是十分熟练,感觉在形式化验证与单元测试这一方面需要学习的东西还有很多。

  • 相关阅读:
    ElasticSearch 2 (23)
    ElasticSearch 2 (22)
    微信小程序框架模板部署:mpvue2.x+typescript+webpack3.x
    mpvue添加对scss的支持
    mpvue 封装axios请求方法
    Vue中qs插件的使用
    在微信小程序中使用less/sass
    微信小程序封装request请求
    VSCode --tsc : 无法加载文件
    Vue项目中的RSA加解密
  • 原文地址:https://www.cnblogs.com/littlenyima/p/12917809.html
Copyright © 2020-2023  润新知