OOUnit3总结分析
- OOUnit3总结分析
- JML
- 理论基础
- 注释结构
- JML表达式
- 方法规格
- 类型规格
- 应用工具链
- OpenJML
- JMLUnitNG
- 理论基础
- JMLUnitNG/JMLUnit部署
- 架构设计
- Task1
- Task2
- Task3
- BUG及修复
- Task1
- Task2
- Task3
- 心得体会
- JML
JML
理论基础
JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。JML是一种行为接口规格语言(Behavior Interface Specification Language,BISL),基于Larch方法构建。近年来,JML持续受到关注,为严格的程序设计提供了一套行之有效的方法。通过JML及其支持工具,不仅可以基于规格自动构造测试用例,并整合了SMT Solver等工具以静态方式来检查代码实现对规格的满足情况。
一般而言,JML有两种主要的用法:
(1)开展规格化设计。这样交给代码实现人员的将不是可能带有内在模糊性的自然语言描述,而是逻辑严格的规格。
(2)针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。这在遗留代码的维护方面具有特别重要的意义。
注释结构
-
JML以javadoc注释的方式来表示规格每行都以@起头。有两种注释方式,行注释和块注释。其中行注释的表示方式为
//@annotation
,块注释的方式为/* @ annotation @*/
。 -
规格中的每个子句都必须以分号结尾,否则会导致JML工具无法解析。
-
按照JML的语法,可以区分两类规格变量,静态或实例。如果是在Interface中声明规格变量,则要求明确变量的类别。针对上面的例子,如果是静态规格变量,则声明为
//@public static model non_null int []elements
;如果是实例规格变量,则可声明为//@public instance model non_null int []elements
。
JML表达式
- \result表达式:表示一个非
void
类型的方法执行所获得的结果,即方法执行后的返回值。 - \old(expr)表达式:用来表示一个表达式
expr
在相应方法执行前的取值。 - \not_assigned(x,y,...)表达式:用来表示括号中的变量是否在方法执行过程中被赋值。
- \not_modified(x,y,...)表达式:与上面的\not_assigned表达式类似,该表达式限制括号中的变量在方法执行期间的取值未发生变化。
- \type(type)表达式:返回类型type对应的类型(Class),如type(
boolean
)为Boolean.TYPE。 - \typeof(expr)表达式:该表达式返回expr对应的准确类型。
- \forall表达式:全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。
- \exists表达式:存在量词修饰的表达式,表示对于给定范围内的元素,存在某~个元素满足相应的约束。
- \sum表达式:返回给定范围内的表达式的和。
- \product表达式:返回给定范围内的表达式的连乘结果。
- \max表达式:返回给定范围内的表达式的最大值。
- \min表达式:返回给定范围内的表达式的最小值。
- \num_of表达式:返回指定变量中满足相应条件的取值个数。
- 子类型关系操作符:
E1<:E2
,如果类型E1是类型E2的子类型(sub type),则该表达式的结果为真,否则为假。 - 等价关系操作符:
b_expr1<==>b_expr2
或者b_expr1<=!=>b_expr2
,其中b_expr1
和b_expr2
都是布尔表达式,这两个表达式的意思是b_expr1==b_expr2
或者b_expr1!=b_expr2
。 - 推理操作符:
b_expr1==>b_expr2
或者b_expr2<==b_expr1
。对于表达式b_expr1==>b_expr2
而言,当b_expr1==false
,或者b_expr1==true
且b_expr2==true
时,整个表达式的值为true
。 - 变量引用操作符: \nothing指示一个空集;\everything指示一个全集,即包括当前作用域下能够访问到的所有变量。
- 等等
方法规格
- 前置条件(pre-condition):前置条件通过requires子句来表示:
requires P;
。其中requires是JML关键词,表达的意思是“要求调用者确保P为真”。 - 后置条件(post-condition):后置条件通过ensures子句来表示:
ensures P;
。其中ensures是JML关键词,表达的意思是“方法实现者确保方法执行返回结果一定满足谓词P的要求,即确保P为真”。 - 副作用范围限定(side-effects):副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。使用关键词
assignable
或者modifiable
。 - signals子句 :signals子句的结构为
signals (***Exception e) b_expr;
,意思是当b_expr
为true
时,方法会抛出括号中给出的相应异常e
。还有一个简化的signals子句,即signals_only子句,后面跟着一个异常类型。signals子句强调在对象状态满足某个条件时会抛出符合相应类型的异常;而signals_only则不强调对象状态条件,强调满足前置条件时抛出相应的异常。
类型规格
- 不变式invariant:不变式(invariant)是要求在所有可见状态下都必须满足的特性,语法上定义
invariant P
,其中invariant
为关键词,P
为谓词。 - 状态变化约束constraint:对象的状态在变化时往往也许满足一些约束,这种约束本质上也是一种不变式。JML为了简化使用规则,规定invariant只针对可见状态(即当下可见状态)的取值进行约束,而是用constraint来对前序可见状态和当前可见状态的关系进行约束。
以上理论基础内容摘自课程组下发的《JML Level 0手册》
应用工具链
OpenJML
OpenJML是Java程序的程序验证工具,它使您可以检查用Java Modeling Language注释的程序的规范。 支持静态的检查,也支持运行时检查。此外它还集成了一些SMT Solvers,便于对程序进行更深层次的验证。
官网:http://www.openjml.org/
JMLUnitNG
JMLUnitNG是用于带有JML注释的Java代码的自动化单元测试生成工具,包括使用Java 1.5+特性(例如泛型,枚举类型和增强的for循环)的代码。 像原始的JMLUnit一样,它使用JML断言作为测试Oracle。 它通过允许对要测试的类的每个方法参数轻松地自定义数据,以及使用Java反射自动生成非原始类型的测试数据,对原始JMLUnit进行了改进。 在FoVeOOS 2010中,JMLUnitNG:NGF的初始版本在论文《 JMLUnit:下一代》中被引入,尽管该文件中的某些信息现在已经过时。
其功能简单描述即可以根据JML自动生成对应的测试样例,进行单元化测试。但由于长时间没有进行更新迭代,最早的一版是在2014年发行,所以各方面使用起来非常不方便。
官网:http://insttech.secretninjaformalmethods.org/software/jmlunitng/
JMLUnitNG/JMLUnit部署
由于JMLUnitNG年代久远,这部分完成起来十分困难,配置过程中出现各种报错情况。笔者在部署JMLUnitNG时,参考了这篇博客(下称博客),在此表达感谢,读者可以进行参考。
按照博客中的方法部署JMLUnitNG后,笔者做了一些顺序的调整和步骤内容的补充,笔者的具体部署步骤如下:
- 下载jdk8,安装,重新配置环境变量(类似于安装java时配置环境变量)
- 整个文件树复制到工作目录下test文件夹,并将所有的java文件开头加上
package test;
或在已有package
前加上test.
(eg.package test.com.oocourse .spec3.main;
),同时修改所有文件import语句(eg.import test.com.oocourse .spec3.main.Group;
) - 将MyGroup.java程序中的变量名修改,使其不与Group.java中JML的变量名重名
- 将Group.java中所有的JML代码复制到MyGroup.java中相对应的地方,将所有的
@Override
删掉 - 在构造方法中,对HashMap的new操作显式指出HashMap的键值对类型。如
this.peopleMap = new HashMap();
,否则会报错
部署后WorkSpace的目录树如下:
C:\Users\17670>cd Desktop
C:\Users\17670\Desktop>cd WorkSpace
C:\Users\17670\Desktop\WorkSpace>tree /f
卷 Windows 的文件夹 PATH 列表
卷序列号为 5A43-6D40
C:.
│ jmlunitng.jar
│ openjml.jar
│
└─test
│ MainClass.java
│ MyEdge.java
│ MyGroup.java
│ MyNetwork.java
│ MyPerson.java
|
└─com
└─oocourse
└─spec3
├─exceptions
│ EqualGroupIdException.java
│ EqualPersonIdException.java
│ EqualRelationException.java
│ GroupIdNotFoundException.java
│ PersonIdNotFoundException.java
│ RelationNotFoundException.java
│
└─main
Group.java
Network.java
Person.java
Runner.java
此时,在cmd中WorkSpace目录下依次执行以下四条指令:
java -jar jmlunitng.jar test/MyGroup.java # 执行该条命令后正常不会出现任何信息
javac -cp jmlunitng.jar test/*.java # 执行该条命令后正常不会出现任何信息
java -jar openjml.jar -rac test/MyGroup.java # 执行该条命令后会出现“错误: 程序包test.com.oocourse.spec3.main不存在”“错误: 找不到符号”,不需理会
java -cp jmlunitng.jar test.MyGroup_JML_Test # 执行该条命令后正常会出现自动测试结果
得到结果:
C:\Users\17670\Desktop\WorkSpace>java -jar jmlunitng.jar test/MyGroup.java
C:\Users\17670\Desktop\WorkSpace>javac -cp jmlunitng.jar test/*.java
C:\Users\17670\Desktop\WorkSpace>java -jar openjml.jar -rac test/MyGroup.java
test\MyGroup.java:3: 错误: 程序包test.com.oocourse.spec3.main不存在
import test.com.oocourse.spec3.main.Group;
^
test\MyGroup.java:4: 错误: 程序包test.com.oocourse.spec3.main不存在
import test.com.oocourse.spec3.main.Person;
^
test\MyGroup.java:10: 错误: 找不到符号
public class MyGroup implements Group {
^
符号: 类 Group
test\MyGroup.java:13: 错误: 找不到符号
@ public instance model non_null Person[] people;
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:17: 错误: 找不到符号
private final LinkedHashMap groupPeople;
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:61: 错误: 找不到符号
public void addPerson(Person person) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:69: 错误: 找不到符号
public /*@pure@*/ boolean hasPerson(Person person) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:129: 错误: 找不到符号
public void delPerson(Person person) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:140: 错误: 找不到符号
public void updateRelation(Person person1, Person person2) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:140: 错误: 找不到符号
public void updateRelation(Person person1, Person person2) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:145: 错误: 找不到符号
private void updateInfo1(Person person) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:174: 错误: 找不到符号
private void updateInfo2(Person person) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:27: 错误: 找不到符号
groupPeople = new LinkedHashMap(1024);
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:43: 错误: 找不到符号
@ requires obj != null && obj instanceof Group;
^
符号: 类 Group
位置: 类 MyGroup
test\MyGroup.java:45: 错误: 找不到符号
@ ensures \result == (((Group) obj).getId() == id);
^
符号: 类 Group
位置: 类 MyGroup
test\MyGroup.java:48: 错误: 找不到符号
@ requires obj == null || !(obj instanceof Group);
^
符号: 类 Group
位置: 类 MyGroup
test\MyGroup.java:53: 错误: 找不到符号
if (obj instanceof Group) {
^
符号: 类 Group
位置: 类 MyGroup
test\MyGroup.java:54: 错误: 找不到符号
return ((Group) obj).getId() == groupId;
^
符号: 类 Group
位置: 类 MyGroup
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.visitBlock(JmlAttr.java:706)
at com.sun.tools.javac.comp.JmlAttr.visitJmlBlock(JmlAttr.java:3804)
at org.jmlspecs.openjml.JmlTree$JmlBlock.accept(JmlTree.java:1333)
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.visitMethodDef(Attr.java:1015)
at com.sun.tools.javac.comp.JmlAttr.visitMethodDef(JmlAttr.java:1112)
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.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)
test\MyGroup.java:153: 错误: 找不到符号
for (Map.Entry entry : groupPeople.entrySet()) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:154: 错误: 找不到符号
Person groupMember = entry.getValue();
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:191: 错误: 找不到符号
for (Map.Entry entry : groupPeople.entrySet()) {
^
符号: 类 Person
位置: 类 MyGroup
test\MyGroup.java:192: 错误: 找不到符号
Person groupMember = entry.getValue();
^
符号: 类 Person
位置: 类 MyGroup
22 个错误
C:\Users\17670\Desktop\WorkSpace>java -cp jmlunitng.jar test.MyGroup_JML_Test
[TestNG] Running:
Command line suite
Failed: racEnabled()
Passed: constructor MyGroup(-2147483648)
Passed: constructor MyGroup(0)
Passed: constructor MyGroup(2147483647)
Failed: <>.addPerson(null)
Failed: <>.addPerson(null)
Failed: <>.addPerson(null)
Failed: <>.delPerson(null)
Failed: <>.delPerson(null)
Failed: <>.delPerson(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(java.lang.Object@2280cdac)
Passed: <>.equals(java.lang.Object@4fccd51b)
Passed: <>.equals(java.lang.Object@60215eee)
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: <>.getPeopleNum()
Passed: <>.getPeopleNum()
Passed: <>.getPeopleNum()
Passed: <>.getRelationSum()
Passed: <>.getRelationSum()
Passed: <>.getRelationSum()
Passed: <>.getValueSum()
Passed: <>.getValueSum()
Passed: <>.getValueSum()
Failed: <>.hasPerson(null)
Failed: <>.hasPerson(null)
Failed: <>.hasPerson(null)
Failed: <>.updateRelation(null, null)
Failed: <>.updateRelation(null, null)
Failed: <>.updateRelation(null, null)
===============================================
Command line suite
Total tests run: 43, Failures: 13, Skips: 0
===============================================
根据上面的输出可以发现:
-
JMLUnitNG共进行测试43次,通过30次,失败13次
-
JMLUnitNG自动创建了3个MyGroup对象,其groupId分别为-2147483648,0,2147483648,为int型数据的边界情况。之后,JMLUnitNG对该类的所有方法进行测试。对有参数的传入的方法,会自动传入边界值与特殊值进行测试(例如-2147483648,0,2147483648,null),对无参数传入的方法,会调用方法对象内部状态做检查。
-
笔者们可以发现JMLUnitNG很注重类和方法在边界情况和特殊情况下的稳定性和正确性,但是测试数据的量和测试的覆盖性远远不够。一些方法在现有规格下不会出现null作为参数传入的情况,故这样的测试没有任何意义。
总而言之,笔者认为虽然JMLUnitNG的测试注重特殊值和边界值,但是其测试没有针对性、数据随机性与覆盖性不强、对类的内部状态的判别能力不强、几乎无法判别各种方法之间的协作,所以JMLUnitNG不能满足笔者的测试需求,更无法满足工业的测试需求。
架构设计
Task1
Task1较为简单,只是实现了Person接口和Network接口。在实现过程中也没有什么难点,除了Network中一个判断两个人是否连通的isCircle()方法外,其他按照JML规格书写即可。
在MyPerson类和MyNetwork类中,笔者采用LinkedHashMap容器来存放Person。其好处有二:一是可以像HashMap那样通过Key来获取Value,时间复杂度为O(1);二是克服了HashMap无序的缺点,LinkedHashMap在遍历时仍然保持着插入时的顺序,不会打乱,这样也满足了规格一些地方的要求。
在实现判断两个人是否连通的isCircle()方法时,笔者采用了较为常见的bfs(宽度优先搜索)算法。该算法比较常见也比较稳定,故笔者选择使用它。
复杂度分析如下:
Task2
Task2较Task1新增加了一个MyGroup类,在此类内部按照规格实现相关内容。由于Task2的测试数据量比较大,为了保证程序运行时间不会太大(尤其在一些$O(n^2)$的算法内部,例如queryGroupAgeMean()
,queryGroupAgeVar()
),所以在MyGroup类内部实现缓存机制,在新增群组成员(MyGroup类内部的行为)和新增成员关系(MyNetwork类内部的行为)时,维护MyGroup内部的各种缓存量。
Task3
Task3较Task2在增加了一些考察图算法的方法,其余的新增方法按照JML规格实现即可。
例如:求两个顶点的最短路径的方法queryMinPath(),笔者使用了堆优化的Dijkstra算法实现该方法;判断两个顶点是否为双连通的方法queryStrongLinked(),如果这两个顶点不相邻,笔者通过删除图中的每一个节点,来判断这两个顶点是否在每种情况下都仍然连通的方法实现判断,如果这两个顶点相邻,笔者采用试图寻找另一条长度大于等于2 的路径来判断;判断图中共有几个连通分量的方法queryBlockSum(),笔者通过bfs方法来实现。
除此之外,代码架构和类的构成没有很大的变化。
UML类图如下:
复杂度分析如下:
BUG及修复
Task1
BUG1:在实现判断两个人是否连通的isCircle()方法时,笔者采用了bfs算法。但在实现过程中,忘记处理到id1==id2
的情况,最终强测只得了60分。
修复1:在contains(id1) && contains(id2) && id1 == id2
为true
时,返回true
,即可修复BUG。
BUG2:在互测阶段,出现了利用超长名字使queryNameRank()方法超时的数据。
修复2:在MyNetwork类中,维护一个桉顺序存储所有Person名字的ArrayList。在加入新人时,更新该ArrayList;在调用queryNameRank()方法时,直接返回ArrayList中第一个与其名字相同的项的下标。通过此方法,即可修复超长名字使queryNameRank()方法超时的BUG,但是该方法的引进时太过匆忙,没有测试充分,结果导致了Task2的新BUG出现。
Task2
BUG:在修复Task1的BUG2时引进了NameRank缓存。原算法实现的是:在新人到来时,如果没有相同的名字,直接在合适的地方插入;如果有相同的名字时,不再插入。读者可能已经发现,这样会导致在相同名字之后的名字的排名发生错误,不符合queryNameRank()方法的JML规格。数据量很大的情况下很容易出现名字相同的情况,所以由于这个BUG,本次强测只得了65分。
/*@ public normal_behavior
@ requires contains(id);
@ ensures \result == (\sum int i; 0 <= i && i < people.length &&
@ compareName(id, people[i].getId()) > 0; 1) + 1;
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !contains(id);
@*/
修复:在新人到来时,如果没有相同的名字,直接在合适的地方插入;如果有相同的名字时,在相同名字前插入新名字。这样在相同名字之后的名字的排名才会满足JML规格,BUG修复成功。
Task3
BUG:由于MyNetwork类太过于庞大,所以本人将queryStrongLinked()使用到的两个函数放在了MyEdge类中,并且将这两个函数设置为静态函数,以便调用。但是,MyEdge类在queryMinPath()方法中大量实例化。这两个巨大的静态方法的加入,使得MyEdge类过于臃肿,直接导致queryMinPath()方法在巨大数据量面前超时。这导致本次作业在强测中只获得90分。
修复:将queryStrongLinked()使用到的这两个函数移动到MainClass类中,或 者移动到新创建的工具类当中,并且设为静态函数。这样就保证MyEdge类的轻便,也使queryMinPath()方法能够在正常时间内处理巨大的数据量。BUG被成功修复。
除此之外,在强测前课下自己测试代码时,还发现zheng其他BUG。例如:MyGroup类中关于conflictSum属性缓存的BUG(对conflictSum属性缓存的理解出错,导致在delPerson()时,对conflictSum属性的更新出错);MyNetwork类中queryStrongLinked()在处理相邻顶点时的BUG(当两个顶点相邻时,只要找到一条定点数大于3的路径即可证明这两个点双连通,所以使用bfs算法寻找另一条路径时,在最开始需要将目标顶点标记为已访问,否则在寻找路径时出现BUG:会以目标顶点为中间点寻找路径,这样即会返回寻找成功,但是并没有,详见代码)。
心得体会
在经过了这单元的三次作业和对JML学习的经历后,我有如下心得体会:
- 学会对自己的程序进行全面的测试,不经过充分测试的代码有很大概率存在BUG
- 阅读JML要逐字逐句仔细理解,不能自以为是地掠过一些部分,很有可能对规格产生误解
- 自动化测试可以极大地提高测试效率和测试能力,是值得一试的好方法
- 作业不能等到最后几天才开始着手,这样在写完程序后很有可能不再想进行测试,导致最后的代码漏洞百出
- 对一些常见算法还是要熟练掌握,良好的数理基础有利于算法能力的提高
- 学会对JML进行抽象化理解,不能总是按照JML生硬地翻译代码,而是进行功能抽象,架构设计,以实现“同样的功能,更好的架构”的目的
- 好的架构才是程序设计的最终目的