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<==>expr2
或expr1<=!=>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 个警告
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 个警告
然后使用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 ===============================================
经过一番魔改终于成功运行。然而观察可以发现其只是将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的使用还不是十分熟练,感觉在形式化验证与单元测试这一方面需要学习的东西还有很多。