OO第三单元表达式求导作业总结
一、JML理论基础
JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。在我的理解中,规格的目的是为了摒弃自然语言描述的诸多弊端,试图通过形式化建模语言来描述需求并未Junit等测试提供依据。其能在团队开发中起到重要作用。在并非合作非常默契,团队成员编程水平都很高的情况下,把结构性需求设计好,把框架搭出来,JML写出来。需求就会变得异常清晰可以交给码农们来完成把架构落地的工作,同时测试组也主要参考JML即可写出相对有效的回归测评数据。因此其好处是不言而喻的,能让纷繁的工作变得清晰的方法,能让容易出现混乱、不匹配的合作变得有序有据的工具,其初衷一定是非常棒的。下面介绍一些JML的常用理论,部分来源于维基百科和课程组下发手册。
- requires 定义该方法的前置条件
- ensures 在随后的方法上定义一个后置条件。
- signals 定义一个后置条件,用于随后的方法引发触发了给定的Exception时。
- assignable 定义方法修改的字段
- pure 声明一种无副作用的方法
- invariant 定义类的不变属性。
原子表达式
- \result 随后方法的返回值的标识符。\result表达式的类型就是方法声明中定义的返回值类型。可以表示equals执行结果。
- \old( expr )表达式:用来表示一个表达式 expr 在相应方法执行前的取值。
量化表达式
- (\forall ; ; ) 全称量词,表示所有都要满足。
- (\exists ; ; ) 存在量词,存在某个满足即可。
- \sum表达式:返回给定范围内的表达式的和。
二、JMLUnitNG
关于OpenJML提一句,有一个jar包,一个solvers(-windows)就能用,但是具体没有过多探索,只是验证了一个Person的规格,发现似乎我的openJML对于三目运算表达式并不支持。
执行语句java -jar .\openjml.jar -exec E:\OO\JML\jmlunitNG\Solvers\z3-4.7.1.exe -esc E:\OO\JML\jmlunitNG\test*.java
后面加-verboseness=3可以输出具体参数。
参考了大佬们的配置的博客,我配置了自己的JMLUnitNG.
以下几张图片是我的目录树。
为了跑通,我对Group进行了魔改,截取部分代码
package test;
import java.util.ArrayList;
import java.util.HashSet;
public class Group {
private int id;
private ArrayList peopleList = new ArrayList();
private HashSet peopleSet = new HashSet();
首先是祛除了所有的@Override标识符,把包名改成了test。
其次我在ArrayList和HashMap的<>里指定了相对应的类,否则报错,
奇怪的是,我的使用BigInteger的方法也报错,于是我干脆直接删了那个方法。
下面给出我的命令行依次执行就可得到jmlunitng产生的自动测试,测试数据非常极端,其实基本没有什么价值。
\E:\OO\JML\jmlunitNG>java -jar jmlunitng.jar test/Person.java test/Group.java
E:\OO\JML\jmlunitNG>javac -cp jmlunitng.jar test/*.java
E:\OO\JML\jmlunitNG>java -jar openjml.jar -rac test/Group.java test/Person.java
E:\OO\JML\jmlunitNG>java -cp jmlunitng.jar test.Group_JML_Test
[TestNG] Running:
Command line suite
Passed: racEnabled()
Passed: constructor Group(-2147483648)
Passed: constructor Group(0)
Passed: constructor Group(2147483647)
Failed: <>.addPerson(null)
Failed: <>.addPerson(null)
Failed: <>.addPerson(null)
Skipped: <>.afterAddRelation(null, null)
Skipped: <>.afterAddRelation(null, null)
Skipped: <>.afterAddRelation(null, null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(null)
Passed: <>.equals(java.lang.Object@4ca8195f)
Passed: <>.equals(java.lang.Object@61baa894)
Passed: <>.equals(java.lang.Object@768debd)
Passed: <>.getAgeMean()
Passed: <>.getAgeMean()
Passed: <>.getAgeMean()
Passed: <>.getAgeVar()
Passed: <>.getAgeVar()
Passed: <>.getAgeVar()
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: <>.peopleSum()
Passed: <>.peopleSum()
Passed: <>.peopleSum()
===============================================
Command line suite
Total tests run: 37, Failures: 3, Skips: 3
===============================================
三、基于度量的程序结构分析
首先解释若干参数:
ev(G)基本复杂度,是用来衡量程序非结构化程度的,非结构成分降低了程序的质量,增加了代码的维护难度,使程序难于理解。因此,基本复杂度高意味着非结构化程度高,难以模块化和维护。实际上,消除了一个错误有时会引起其他的错误。
Iv(G)模块设计复杂度,是用来衡量模块判定结构,即模块和其他模块的调用关系。软件模块设计复杂度高意味模块耦合度高,这将导致模块难于隔离、维护和复用。模块设计复杂度是从模块流程图中移去那些不包含调用子模块的判定和循环结构后得出的圈复杂度,因此模块设计复杂度不能大于圈复杂度,通常是远小于圈复杂度。
v(G)圈复杂度,是用来衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数,即合理的预防错误所需测试的最少路径条数,圈复杂度大说明程序代码可能质量低且难于测试和维护,经验表明,程序的可能错误和高的圈复杂度有着很大关系。
下面先给出三次作业的类图和和相关代码的度量统计,然后一并分析。
第一次作业
UML类图:
代码度量分析
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
com.oocourse.spec1.exceptions.EqualPersonIdException.EqualPersonIdException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.exceptions.EqualPersonIdException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.exceptions.EqualRelationException.EqualRelationException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.exceptions.EqualRelationException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.exceptions.PersonIdNotFoundException.PersonIdNotFoundException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.exceptions.PersonIdNotFoundException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.exceptions.RelationNotFoundException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.exceptions.RelationNotFoundException.RelationNotFoundException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.main.Runner.addPerson() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec1.main.Runner.addRelation() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec1.main.Runner.compareAge() | 1.0 | 2.0 | 4.0 |
com.oocourse.spec1.main.Runner.compareName() | 1.0 | 2.0 | 4.0 |
com.oocourse.spec1.main.Runner.queryAcquaintanceSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec1.main.Runner.queryCircle() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec1.main.Runner.queryConflict() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec1.main.Runner.queryNameRank() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec1.main.Runner.queryPeopleSum() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec1.main.Runner.queryValue() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec1.main.Runner.run() | 1.0 | 12.0 | 12.0 |
com.oocourse.spec1.main.Runner.Runner(Class extends Person>,Class extends Network>) | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 3.0 | 3.0 |
MyNetwork.addPerson(Person) | 2.0 | 1.0 | 2.0 |
MyNetwork.addRelation(int,int,int) | 4.0 | 5.0 | 8.0 |
MyNetwork.compareAge(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.compareName(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.contains(int) | 1.0 | 1.0 | 1.0 |
MyNetwork.getPerson(int) | 2.0 | 2.0 | 2.0 |
MyNetwork.isCircle(int,int) | 7.0 | 5.0 | 9.0 |
MyNetwork.MyNetwork() | 1.0 | 1.0 | 1.0 |
MyNetwork.queryAcquaintanceSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryConflict(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.queryNameRank(int) | 2.0 | 2.0 | 4.0 |
MyNetwork.queryPeopleSum() | 1.0 | 1.0 | 1.0 |
MyNetwork.queryValue(int,int) | 3.0 | 4.0 | 6.0 |
MyPerson.addAcquaintance(Person,int) | 1.0 | 1.0 | 1.0 |
MyPerson.compareTo(Person) | 1.0 | 1.0 | 1.0 |
MyPerson.equals(Object) | 3.0 | 1.0 | 3.0 |
MyPerson.getAcquaintance() | 1.0 | 1.0 | 1.0 |
MyPerson.getAcquaintanceSum() | 1.0 | 1.0 | 1.0 |
MyPerson.getAge() | 1.0 | 1.0 | 1.0 |
MyPerson.getCharacter() | 1.0 | 1.0 | 1.0 |
MyPerson.getId() | 1.0 | 1.0 | 1.0 |
MyPerson.getName() | 1.0 | 1.0 | 1.0 |
MyPerson.isLinked(Person) | 2.0 | 1.0 | 2.0 |
MyPerson.MyPerson(int,String,BigInteger,int) | 1.0 | 1.0 | 1.0 |
MyPerson.queryValue(Person) | 2.0 | 2.0 | 2.0 |
Total | 68.0 | 88.0 | 111.0 |
Average | 1.4782608695652173 | 1.9130434782608696 | 2.4130434782608696 |
在第一次作业,我已经确立了基本的架构,但是也造成了隐患。整体的实现与JML的形式没有特别大的区别,除了我将JML中的每一个静态数组都换成了一个HashMap,一个ArrayList,有的甚至还有HashSet,大量冗余,这种毫不吝惜空间的做法为第二次作业带来了麻烦。isCircle方法是稍微需要设计一点的方法,我采用了bfs(后面作业联通相关除了并查集我基本都用的是bfs)。其余的都是简单的照着JML实现。
第二次作业
UML类图:
代码度量分析
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
com.oocourse.spec2.exceptions.EqualGroupIdException.EqualGroupIdException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.EqualGroupIdException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.EqualPersonIdException.EqualPersonIdException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.EqualPersonIdException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.EqualRelationException.EqualRelationException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.EqualRelationException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.GroupIdNotFoundException.GroupIdNotFoundException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.GroupIdNotFoundException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.PersonIdNotFoundException.PersonIdNotFoundException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.PersonIdNotFoundException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.RelationNotFoundException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.exceptions.RelationNotFoundException.RelationNotFoundException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.main.Runner.addGroup() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.addPerson() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.addRelation() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec2.main.Runner.addtoGroup() | 1.0 | 4.0 | 4.0 |
com.oocourse.spec2.main.Runner.compareAge() | 1.0 | 2.0 | 4.0 |
com.oocourse.spec2.main.Runner.compareName() | 1.0 | 2.0 | 4.0 |
com.oocourse.spec2.main.Runner.queryAcquaintanceSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryCircle() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec2.main.Runner.queryConflict() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryGroupAgeMean() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryGroupAgeVar() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryGroupConflictSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryGroupPeopleSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryGroupRelationSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryGroupSum() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.main.Runner.queryGroupValueSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryNameRank() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec2.main.Runner.queryPeopleSum() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec2.main.Runner.queryValue() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec2.main.Runner.run() | 1.0 | 21.0 | 21.0 |
com.oocourse.spec2.main.Runner.Runner(Class extends Person>,Class extends Network>,Class extends Group>) | 1.0 | 1.0 | 1.0 |
Main.main(String[]) | 1.0 | 1.0 | 1.0 |
MyGroup.addPerson(Person) | 1.0 | 3.0 | 3.0 |
MyGroup.afterAddRelation(Person,Person) | 1.0 | 3.0 | 3.0 |
MyGroup.equals(Object) | 2.0 | 1.0 | 2.0 |
MyGroup.getAgeMean() | 2.0 | 1.0 | 2.0 |
MyGroup.getAgeVar() | 2.0 | 1.0 | 2.0 |
MyGroup.getConflictSum() | 2.0 | 2.0 | 3.0 |
MyGroup.getId() | 1.0 | 1.0 | 1.0 |
MyGroup.getRelationSum() | 1.0 | 1.0 | 1.0 |
MyGroup.getValueSum() | 1.0 | 1.0 | 1.0 |
MyGroup.hashCode() | 1.0 | 1.0 | 1.0 |
MyGroup.hasPerson(Person) | 1.0 | 1.0 | 1.0 |
MyGroup.MyGroup(int) | 1.0 | 1.0 | 1.0 |
MyGroup.peopleSum() | 1.0 | 1.0 | 1.0 |
MyNetwork.addGroup(Group) | 3.0 | 2.0 | 3.0 |
MyNetwork.addPerson(Person) | 2.0 | 1.0 | 2.0 |
MyNetwork.addRelation(int,int,int) | 4.0 | 6.0 | 9.0 |
MyNetwork.addtoGroup(int,int) | 5.0 | 1.0 | 5.0 |
MyNetwork.compareAge(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.compareName(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.contains(int) | 1.0 | 1.0 | 1.0 |
MyNetwork.getGroup(int) | 1.0 | 1.0 | 1.0 |
MyNetwork.getPerson(int) | 2.0 | 2.0 | 2.0 |
MyNetwork.isCircle(int,int) | 7.0 | 5.0 | 9.0 |
MyNetwork.MyNetwork() | 1.0 | 1.0 | 1.0 |
MyNetwork.queryAcquaintanceSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryConflict(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.queryGroupAgeMean(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupAgeVar(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupConflictSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupPeopleSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupRelationSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupSum() | 1.0 | 1.0 | 1.0 |
MyNetwork.queryGroupValueSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryNameRank(int) | 2.0 | 2.0 | 4.0 |
MyNetwork.queryPeopleSum() | 1.0 | 1.0 | 1.0 |
MyNetwork.queryValue(int,int) | 3.0 | 4.0 | 6.0 |
MyPerson.addAcquaintance(Person,int) | 1.0 | 1.0 | 1.0 |
MyPerson.compareTo(Person) | 1.0 | 1.0 | 1.0 |
MyPerson.equals(Object) | 3.0 | 1.0 | 3.0 |
MyPerson.getAcqSum() | 1.0 | 1.0 | 1.0 |
MyPerson.getAcquaintance() | 1.0 | 1.0 | 1.0 |
MyPerson.getAcquaintanceSum() | 1.0 | 1.0 | 1.0 |
MyPerson.getAge() | 1.0 | 1.0 | 1.0 |
MyPerson.getCharacter() | 1.0 | 1.0 | 1.0 |
MyPerson.getId() | 1.0 | 1.0 | 1.0 |
MyPerson.getName() | 1.0 | 1.0 | 1.0 |
MyPerson.hashCode() | 1.0 | 1.0 | 1.0 |
MyPerson.isLinked(Person) | 2.0 | 1.0 | 2.0 |
MyPerson.MyPerson(int,String,BigInteger,int) | 1.0 | 1.0 | 1.0 |
MyPerson.queryValue(Person) | 2.0 | 2.0 | 2.0 |
Total | 122.0 | 150.0 | 188.0 |
Average | 1.4523809523809523 | 1.7857142857142858 | 2.238095238095238 |
第二次作业看到讨论区大佬的分享,我迅速改成了类似缓存机制,每加一个关系就将和Age总和,value总和等相关的更新一遍,避免被TLE。总的架构就是基于JML进行方法的扩充,没有很大的改动,但是其实信息的冗余存储是没有必要的,有HashMap就完全没有必要再存一个ArrayList等。
第三次作业
UML类图:
代码度量分析
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
com.oocourse.spec3.exceptions.EqualGroupIdException.EqualGroupIdException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.EqualGroupIdException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.EqualPersonIdException.EqualPersonIdException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.EqualPersonIdException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.EqualRelationException.EqualRelationException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.EqualRelationException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.GroupIdNotFoundException.GroupIdNotFoundException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.GroupIdNotFoundException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.PersonIdNotFoundException.PersonIdNotFoundException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.PersonIdNotFoundException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.RelationNotFoundException.print() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.exceptions.RelationNotFoundException.RelationNotFoundException() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.main.Runner.addGroup() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.addPerson() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.addRelation() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec3.main.Runner.addtoGroup() | 1.0 | 4.0 | 4.0 |
com.oocourse.spec3.main.Runner.borrowFrom() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec3.main.Runner.compareAge() | 1.0 | 2.0 | 4.0 |
com.oocourse.spec3.main.Runner.compareName() | 1.0 | 2.0 | 4.0 |
com.oocourse.spec3.main.Runner.delFromGroup() | 1.0 | 4.0 | 4.0 |
com.oocourse.spec3.main.Runner.query_money() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryAcquaintanceSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryAgeSum() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.main.Runner.queryBlockSum() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.main.Runner.queryCircle() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec3.main.Runner.queryConflict() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryGroupAgeMean() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryGroupAgeVar() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryGroupConflictSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryGroupPeopleSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryGroupRelationSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryGroupSum() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.main.Runner.queryGroupValueSum() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryMinPath() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryNameRank() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryPeopleSum() | 1.0 | 1.0 | 1.0 |
com.oocourse.spec3.main.Runner.queryStrongLinked() | 1.0 | 2.0 | 2.0 |
com.oocourse.spec3.main.Runner.queryValue() | 1.0 | 3.0 | 3.0 |
com.oocourse.spec3.main.Runner.run() | 1.0 | 28.0 | 28.0 |
com.oocourse.spec3.main.Runner.Runner(Class extends Person>,Class extends Network>,Class extends Group>) | 1.0 | 1.0 | 1.0 |
Connection.checkLink(int,int) | 9.0 | 7.0 | 13.0 |
Connection.Connection(HashMap |
1.0 | 1.0 | 1.0 |
Connection.init(int,int) | 4.0 | 4.0 | 6.0 |
Connection.whenLinkedJudge(int,int) | 7.0 | 4.0 | 7.0 |
Main.main(String[]) | 1.0 | 1.0 | 1.0 |
MyGroup.addPerson(Person) | 1.0 | 3.0 | 3.0 |
MyGroup.afterAddRelation(Person,Person) | 1.0 | 3.0 | 3.0 |
MyGroup.delPerson(Person) | 1.0 | 4.0 | 4.0 |
MyGroup.equals(Object) | 2.0 | 1.0 | 2.0 |
MyGroup.getAgeMean() | 2.0 | 1.0 | 2.0 |
MyGroup.getAgeVar() | 2.0 | 1.0 | 2.0 |
MyGroup.getConflictSum() | 2.0 | 2.0 | 3.0 |
MyGroup.getId() | 1.0 | 1.0 | 1.0 |
MyGroup.getRelationSum() | 1.0 | 1.0 | 1.0 |
MyGroup.getValueSum() | 1.0 | 1.0 | 1.0 |
MyGroup.hashCode() | 1.0 | 1.0 | 1.0 |
MyGroup.hasPerson(Person) | 1.0 | 1.0 | 1.0 |
MyGroup.MyGroup(int) | 1.0 | 1.0 | 1.0 |
MyGroup.peopleSum() | 1.0 | 1.0 | 1.0 |
MyNetwork.addGroup(Group) | 3.0 | 2.0 | 3.0 |
MyNetwork.addPerson(Person) | 2.0 | 1.0 | 2.0 |
MyNetwork.addRelation(int,int,int) | 4.0 | 6.0 | 9.0 |
MyNetwork.addtoGroup(int,int) | 5.0 | 1.0 | 5.0 |
MyNetwork.borrowFrom(int,int,int) | 3.0 | 3.0 | 5.0 |
MyNetwork.compareAge(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.compareName(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.contains(int) | 1.0 | 1.0 | 1.0 |
MyNetwork.delFromGroup(int,int) | 4.0 | 1.0 | 4.0 |
MyNetwork.findFather(int,HashMap |
1.0 | 3.0 | 3.0 |
MyNetwork.getGroup(int) | 1.0 | 1.0 | 1.0 |
MyNetwork.getPerson(int) | 2.0 | 2.0 | 2.0 |
MyNetwork.isCircle(int,int) | 9.0 | 5.0 | 11.0 |
MyNetwork.MyNetwork() | 1.0 | 1.0 | 1.0 |
MyNetwork.queryAcquaintanceSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryAgeSum(int,int) | 1.0 | 2.0 | 4.0 |
MyNetwork.queryBlockSum() | 1.0 | 7.0 | 7.0 |
MyNetwork.queryConflict(int,int) | 2.0 | 2.0 | 3.0 |
MyNetwork.queryGroupAgeMean(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupAgeVar(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupConflictSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupPeopleSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupRelationSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryGroupSum() | 1.0 | 1.0 | 1.0 |
MyNetwork.queryGroupValueSum(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryMinPath(int,int) | 4.0 | 11.0 | 14.0 |
MyNetwork.queryMoney(int) | 2.0 | 1.0 | 2.0 |
MyNetwork.queryNameRank(int) | 2.0 | 2.0 | 4.0 |
MyNetwork.queryPeopleSum() | 1.0 | 1.0 | 1.0 |
MyNetwork.queryStrongLinked(int,int) | 5.0 | 3.0 | 6.0 |
MyNetwork.queryValue(int,int) | 3.0 | 4.0 | 6.0 |
MyPerson.addAcquaintance(Person,int) | 1.0 | 1.0 | 1.0 |
MyPerson.compareTo(Person) | 1.0 | 1.0 | 1.0 |
MyPerson.equals(Object) | 3.0 | 1.0 | 3.0 |
MyPerson.getAcqList() | 1.0 | 1.0 | 1.0 |
MyPerson.getAcqMap() | 1.0 | 1.0 | 1.0 |
MyPerson.getAcqSum() | 1.0 | 1.0 | 1.0 |
MyPerson.getAcquaintanceSum() | 1.0 | 1.0 | 1.0 |
MyPerson.getAge() | 1.0 | 1.0 | 1.0 |
MyPerson.getCharacter() | 1.0 | 1.0 | 1.0 |
MyPerson.getId() | 1.0 | 1.0 | 1.0 |
MyPerson.getName() | 1.0 | 1.0 | 1.0 |
MyPerson.hashCode() | 1.0 | 1.0 | 1.0 |
MyPerson.isLinked(Person) | 2.0 | 1.0 | 2.0 |
MyPerson.MyPerson(int,String,BigInteger,int) | 1.0 | 1.0 | 1.0 |
MyPerson.queryValue(Person) | 2.0 | 2.0 | 2.0 |
Total | 175.0 | 224.0 | 289.0 |
Average | 1.6666666666666667 | 2.1333333333333333 | 2.7523809523809524 |
第三次作业在第二次作业基础上主要就是多加了qmp和qsl为首的依赖图的相关算法的方法。因此我增加了一个专门为qsl准备的类,connection,在这里专门进行相对麻烦的暴力bfs处理,包括检查id1和id2之间是否连通并预处理的bfs。还有qbs的并查集实现我也专门去查了资料,我没有单开一个类,但是在MyNetwork里加入了一个方法findFather来辅助实现并查集的优化。
四、关于bug的分析
这个单元的三次作业,我的情况可以说是惨不忍睹,为此我反思很久,三次作业的低分暴露出的是不同的问题,下面进行分析。
- 第一次代码我只花了一两个小时仔细阅读过JML后就完成了,由于觉得很简单,几乎没有做什么测试。而且公测一遍就过了,我便没有过多理会。果不其然,收到了我OO第一个0分。存在的bug主要可以概括为两点,算法实现问题,没有仔细满足规格的问题,没有仔细满足规格的问题相对较好解决,queryNameRank的初始值设置错误,这几乎完全是JML阅读不仔细的问题;isCircle则是出现了两个实现的细节上的问题,对于稍微大一些的数据就完全不对。这些bug都是直接肉眼就可以发现的。这一次0分是一个极其惨痛的教训。我存在着,需求解读与实现不准确,没有测试,代码基础不扎实等等问题。实在是一个强提醒。但是后面两次作业仍然让我几乎心灰意冷。
- 第二次在互测中没有被hack,反而hack到了别人。但当我知道我强测只有80分的时候我是很蒙的,毕竟我已经使用了缓存策略,没有也不应该被卡TLE。然后我发现我四个错误的数据点报的都是一样的错误:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:704)
at java.util.HashMap.putVal(HashMap.java:629)
at java.util.HashMap.put(HashMap.java:612)
at MyPerson.addAcquaintance(MyPerson.java:94)
at MyNetwork.addRelation(MyNetwork.java:65)
at com.oocourse.spec2.main.Runner.addRelation(Runner.java:322)
at com.oocourse.spec2.main.Runner.run(Runner.java:48)
at Main.main(Main.java:6)
非常醒目的Exception,于是我开始查看原因,全都是在addRelation的时候出问题,全都是一样的错误,甚至我原封不动交上去原版的时候还多了几个这样的错误,经过请教同学,上网查资料,发现我这并不是简单的爆内存,而是堆溢出,并不是全部可用内存全部耗尽,而是能分配给HashMap的那篇空间被用尽,经过提醒,推测是我的MyPerson等类里存了大量的冗余信息,而且我的HashMap全部设置了初始大小为6000,这不止拖慢了运行速度,更是会撑爆堆。我把所有初始大小全部去掉了,就过了......上网查询相关资料发现,这个堆的大小是JVM的一个参数可以设置的,助教说课程组采用的是默认值,而我本地IDEA跑出来的是不会抛出异常的,可见我本地IDEA的JVM参数和评测机的不一样,助教建议是以后用jar包评测。于是我第三次乖乖用jar包,去掉了所有初始大小的设置,最终因为“程序写的不好”迎来了除0分以外的最低分,60分。
- 第三次强测我没有被hack,一开始我以为我这个单元的春天要来了,直到我交了两组我的评测机生成的数据,每一个都是全部爆破的时候(即 hack6/6),我就知道似乎是出了问题。一看,60分......我CTLE了8个点,这8个点是对qmp(最短路径)和qsl(双联通问题),经过和助教确认,CTLE意味着就是算法实现有问题。而助教头子说标程的代码就是一样的暴力删点再用bfs判断,有人说是HashMap遍历的速度等问题,而助教头子又说标称也是HashMap,然而我把一部分对Map的遍历改成ArrayList还是只多过了三个点,变成了和一些人一样的5个CTLE果真是细节决定成败,我的程序细节里果然全都是效率低下的问题。对于最短路径,没有堆优化的原因,应该是十分明显的CTLE的原因,我当时为了保稳,就没用不熟悉的堆优化,没想到我竟然码出来的代码已经差到放如此宽的TLE线还是诸多超时,这个单元的bug给我感触颇深。
(助教头子说:“标程就是暴力算法”“标程就是HashMap,所以不是很懂”(指不是很懂我们说的HashMap遍历慢),我想不可能由强的人理解弱者,不可能由专业的助教理解我们的菜,那也只有我们不断改进自己的程序,助教看来很简单自然不用动脑子的优化我们不应视为超越舒适区的任务,而是也应该把那些让程序更高效的技巧变得自然起来,强者对弱者大概只有同情,无法理解,那就只有弱者去变强了)对于最短路径,没有堆优化的原因,应该是十分明显。
五、感想
虽然没有协作码代码这一过程,但我仍然清晰地体会到JML使架构、需求与具体实现分离的感受,但我体会到了其相对于自然语言无二义性的优势,虽然其工具链和本身的可读性可能还有待改进,但我看到了一个趋势,一个形式化描述进入编程工程的极具潜力的趋势。对于这几次作业,我对我曾经在数据结构课已经码的非常熟练的迪杰算法的实现能力产生了怀疑,一方面是码代码的时候磕磕绊绊,bug频出,另一方面性能并不过关。加上这个单元三次作业,三次惨痛的教训,三个不同方向的问题,给我留下了极为深刻的印象,虽然我的分数已经不可逆地收到了较大影响,但是我认为其对我今后编程道路提了重要的醒,编程最终的效果优秀永远离不开设计、实现的功底、细节处理、对于环境和工具的熟练使用等等等等。也许惨痛的教训给我换来的是今后更大损失的与避免,祸兮福之所倚。总之,这个单元,我印象颇深。