OO第三单元总结——人际关系网
JML语言
1.JML语言理论基础
JML是用于对Java程序进行规格化设计的一种表示语言。有两种主要的用法:
在编写代码之前根据需要进行规格化设计。
针对已有的代码实现,书写其对应的规格,从而提高代码的可维护性。
(1)JML表达式
原子表达式 | |
---|---|
\result | 方法执行后的返回值。 |
\old(expr) | 表示一个表达式expr 在相应方法执行前的取值。 |
\not_assigned(x,y,...) | 用来表示括号中的变量是否在方法执行过程中被赋值。如果没有被赋值,返回为true ,否则返回 false 。 |
\not_modified(x,y,...) | 该表达式限制括号中的变量在方法执行期间的取值未发生变化 |
\nonnullelements(container) | 表示container对象中存储的对象不会有null。 |
\type(type) | 返回类型type对应的类型(Class),如type(boolean)为Boolean.TYPE。TYPE是JML采用的缩略表示,等同于Java中的 java.lang.Class。 |
\typeof(expr) | 该表达式返回expr对应的准确类型。如\typeof(false)为Boolean.TYPE。 |
量化表达式 | |
---|---|
\forall | 全称量词修饰的表达式,表示对于给定范围内的元素,每个元素都满足相应的约束。 |
\exists | 存在量词修饰的表达式,表示对于给定范围内的元素,存在某个元素满足相应的约束。 |
\sum | 返回给定范围内的表达式的和。 |
\product | 返回给定范围内的表达式的连乘结果。 |
\max | 返回给定范围内的表达式的最大值。 |
\min | 返回给定范围内的表达式的最大值。 |
\num_of | 返回指定变量中满足相应条件的取值个数。 |
(2)操作符
E1<:E2 | 判断类型E1是不是类型E2的子类型 |
---|---|
<==> | 等价关系操作符 |
==> | 推理操作符 |
\nothing | 空集 |
\everything | 全集 |
(3)方法规格
前置条件 require | 对方法输入参数的限制,如果不满足前置条件,方法执行结果不可预测,或者说不保证方法执行结果的正确性。 |
---|---|
后置条件ensure | 对方法执行结果的限制,如果执行结果满足后置条件,则表示方法执行正确,否则执行错误。 |
副作用assignable (表示可赋值)、modifiable (可修改)、pure(纯粹访问)signals (抛出异常) |
副作用指方法在执行过程中会修改对象的属性数据或者类的静态成员数据,从而给后续方法的执行带来影响。 |
(4)类型规格
invariant (不变式) | 在所有可见状态下都必须满足的特性(在方法执行期间,对象的不变式有可能不满足。) |
---|---|
状态变化约束constraint | 对前序可见状态和当前可见状态的关系进行约束。 |
(4)应用工具链
openjml | 可以编译含有JML的代码,同时内置SMT Solver,可以实现静态检查 |
---|---|
JMLUnitNG/JMLUnit | 可以根据JML自动生成测试文件 |
2.JMLUnitNG/JMLUnit
为了成功测试,改写了测试用例。
我的应用来说,只能进行简单代码的一些测试。太过于鸡肋。生成的测试样例只对一些极端情况进行测试。使用价值不大。
Failed: racEnabled()
Passed: constructor MyGroup(-2147483648)
Passed: constructor MyGroup(0)
Passed: constructor MyGroup(2147483647)
Passed: <>.addPerson(null)
Passed: <>.addPerson(null)
Passed: <>.addPerson(null)
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()
作业架构分析
第一次作业:
基本上是照着规格写代码,没什么架构可言。唯一需要说一下的是,isCircle方法写了图,用bfs来实现的。并查集或dfs也可。容器采用的是hashmap,方便查询。
难点主要是对于JML语言的理解,能否正确理解,并且正确实现。
第二次作业:
第二次作业增加了Group这个类,并进行了一系列查询工作。
对于容器的选择,选择了hashmap,arrarlist两个,一个方便查询,一个方便遍历。并且hashmap进行了初始化容量,关于初始化到底是变慢了,还是变快了,还是要因地制宜,不能一味的大容量初始化。
新增类group,实际上是network的子图,所以也采用了图来储存。
相应的ConflictSum、getAgeMean、getAgeVar采用了缓存法来解决,在增加人的时候进行更新。通过缓存agesum、age*agesum完成年龄平均数、方差的计算。这里的公式采用同学在讨论区的分享,避免了由于int除法带来的计算误差。
对于RelationSum、ValueSum采用了图来缓存边的个数,以及边的权值和来进行。需要注意的是在group新增一个人时,需要判断是否有新边产生。以及network增加关系时,group的边是否更新。
第三次作业
第三次作业,在上次基础上增加了删除group元素,只需要在group图中删除顶点,以及相关的边,并且更新相关的缓存。
增加了最短路径的查询,采用优先队列实现堆优化的迪杰特斯拉算法。
连通块数的查询采用了并查集算法,判断两点是否双连通,采用了bfs暴力枚举路径上的点来实现。如果两点直接相连,需要删除两点间的边,或者标记,然后在bfs能否连通。
大体架构没有改变,network、group各管理一张自己的图,相应的算法通过给图上顶点增加属性,来一一实现。
本次代码分析没有画类图,没什么主要的继承关系,就是单纯的一个指导书明确需要写的类,以及图的三个类:点、边、图。
代码复杂度分析:
method | ev(G) | iv(G) | v(G) |
---|---|---|---|
myclass.MyPerson.varMoney(int,int) | 1.0 | 1.0 | 2.0 |
myclass.MyPerson.queryValue(Person) | 2.0 | 2.0 | 2.0 |
myclass.MyPerson.MyPerson(int,String,BigInteger,int) | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.isLinked(Person) | 1.0 | 2.0 | 3.0 |
myclass.MyPerson.getName() | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.getMoney() | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.getId() | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.getCharacter() | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.getAge() | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.getAcquaintanceSum() | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.getAcquaintance() | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.equals(Object) | 2.0 | 2.0 | 2.0 |
myclass.MyPerson.compareTo(Person) | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.addValue(int) | 1.0 | 1.0 | 1.0 |
myclass.MyPerson.addAcquaintance(Person) | 1.0 | 1.0 | 1.0 |
myclass.MyNetwork.queryValue(int,int) | 3.0 | 5.0 | 6.0 |
myclass.MyNetwork.queryStrongLinked(int,int) | 3.0 | 4.0 | 4.0 |
myclass.MyNetwork.queryPeopleSum() | 1.0 | 1.0 | 1.0 |
myclass.MyNetwork.queryNameRank(int) | 2.0 | 3.0 | 4.0 |
myclass.MyNetwork.queryMoney(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.queryMinPath(int,int) | 4.0 | 5.0 | 5.0 |
myclass.MyNetwork.queryGroupValueSum(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.queryGroupSum() | 1.0 | 1.0 | 1.0 |
myclass.MyNetwork.queryGroupRelationSum(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.queryGroupPeopleSum(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.queryGroupConflictSum(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.queryGroupAgeVar(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.queryGroupAgeMean(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.queryConflict(int,int) | 2.0 | 3.0 | 3.0 |
myclass.MyNetwork.queryBlockSum() | 1.0 | 1.0 | 1.0 |
myclass.MyNetwork.queryAgeSum(int,int) | 1.0 | 2.0 | 4.0 |
myclass.MyNetwork.queryAcquaintanceSum(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.MyNetwork() | 1.0 | 1.0 | 1.0 |
myclass.MyNetwork.isCircle(int,int) | 3.0 | 4.0 | 4.0 |
myclass.MyNetwork.getPerson(int) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.getGroup(int) | 1.0 | 1.0 | 1.0 |
myclass.MyNetwork.delFromGroup(int,int) | 5.0 | 8.0 | 9.0 |
myclass.MyNetwork.contains(int) | 1.0 | 1.0 | 1.0 |
myclass.MyNetwork.compareName(int,int) | 2.0 | 3.0 | 3.0 |
myclass.MyNetwork.compareAge(int,int) | 2.0 | 3.0 | 3.0 |
myclass.MyNetwork.borrowFrom(int,int,int) | 3.0 | 5.0 | 6.0 |
myclass.MyNetwork.addtoGroup(int,int) | 5.0 | 9.0 | 10.0 |
myclass.MyNetwork.addRelation(int,int,int) | 4.0 | 8.0 | 9.0 |
myclass.MyNetwork.addPerson(Person) | 2.0 | 2.0 | 2.0 |
myclass.MyNetwork.addGroup(Group) | 2.0 | 2.0 | 2.0 |
myclass.MyGroup.MyGroup(int) | 1.0 | 1.0 | 1.0 |
myclass.MyGroup.hasPerson(Person) | 1.0 | 1.0 | 1.0 |
myclass.MyGroup.getValueSum() | 1.0 | 1.0 | 1.0 |
myclass.MyGroup.getRelationSum() | 1.0 | 1.0 | 1.0 |
myclass.MyGroup.getPerson(int) | 1.0 | 1.0 | 1.0 |
myclass.MyGroup.getPeoplenum() | 1.0 | 1.0 | 1.0 |
myclass.MyGroup.getId() | 1.0 | 1.0 | 1.0 |
myclass.MyGroup.getConflictSum() | 1.0 | 1.0 | 1.0 |
myclass.MyGroup.getAgeVar() | 2.0 | 2.0 | 2.0 |
myclass.MyGroup.getAgeMean() | 2.0 | 1.0 | 2.0 |
myclass.MyGroup.equals(Object) | 2.0 | 2.0 | 2.0 |
myclass.MyGroup.delPerson(Person) | 1.0 | 2.0 | 2.0 |
myclass.MyGroup.addrelation(int,int,int) | 1.0 | 3.0 | 3.0 |
myclass.MyGroup.addPerson(Person) | 1.0 | 4.0 | 4.0 |
myclass.Main.main(String[]) | 1.0 | 1.0 | 1.0 |
graph.Vertex.Vertex(int) | 1.0 | 1.0 | 1.0 |
graph.Vertex.unVisited() | 1.0 | 1.0 | 1.0 |
graph.Vertex.setVisited() | 1.0 | 1.0 | 1.0 |
graph.Vertex.setPreNode(Vertex) | 1.0 | 1.0 | 1.0 |
graph.Vertex.setParent(int) | 1.0 | 1.0 | 1.0 |
graph.Vertex.setDist(int) | 1.0 | 1.0 | 1.0 |
graph.Vertex.NextIterator.NextIterator() | 1.0 | 1.0 | 1.0 |
graph.Vertex.NextIterator.next() | 2.0 | 2.0 | 2.0 |
graph.Vertex.NextIterator.hasNext() | 1.0 | 1.0 | 1.0 |
graph.Vertex.isVisited() | 1.0 | 1.0 | 1.0 |
graph.Vertex.getWeight() | 1.0 | 1.0 | 1.0 |
graph.Vertex.getPreNode() | 1.0 | 1.0 | 1.0 |
graph.Vertex.getParent() | 1.0 | 1.0 | 1.0 |
graph.Vertex.getnextInterator() | 1.0 | 1.0 | 1.0 |
graph.Vertex.getId() | 1.0 | 1.0 | 1.0 |
graph.Vertex.getEdgelist() | 1.0 | 1.0 | 1.0 |
graph.Vertex.getDist() | 1.0 | 1.0 | 1.0 |
graph.Vertex.equals(Object) | 2.0 | 2.0 | 2.0 |
graph.Vertex.delnect(int) | 3.0 | 3.0 | 3.0 |
graph.Vertex.connect(Vertex,int) | 4.0 | 3.0 | 4.0 |
graph.Vertex.addWeight(int) | 1.0 | 1.0 | 1.0 |
graph.Graph.unionElements(int,int) | 2.0 | 3.0 | 3.0 |
graph.Graph.isPath(int,int) | 4.0 | 5.0 | 5.0 |
graph.Graph.islinked(int,int) | 1.0 | 1.0 | 1.0 |
graph.Graph.isDoupath(int,int) | 5.0 | 7.0 | 8.0 |
graph.Graph.Graph() | 1.0 | 1.0 | 1.0 |
graph.Graph.getSumvalue() | 1.0 | 1.0 | 1.0 |
graph.Graph.getSize() | 1.0 | 1.0 | 1.0 |
graph.Graph.getEdgenum() | 1.0 | 1.0 | 1.0 |
graph.Graph.find(int) | 1.0 | 2.0 | 2.0 |
graph.Graph.dijkstra(int,int) | 1.0 | 6.0 | 6.0 |
graph.Graph.delver(int,Person,MyGroup) | 1.0 | 3.0 | 3.0 |
graph.Graph.allunVisit() | 1.0 | 2.0 | 2.0 |
graph.Graph.addVertex(int) | 1.0 | 1.0 | 1.0 |
graph.Graph.addEdge(int,int,int) | 1.0 | 2.0 | 4.0 |
graph.Edge.setValue() | 1.0 | 1.0 | 1.0 |
graph.Edge.getValue() | 1.0 | 1.0 | 1.0 |
graph.Edge.getEnd() | 1.0 | 1.0 | 1.0 |
graph.Edge.Edge(Vertex,int) | 1.0 | 1.0 | 1.0 |
graph.Edge.Edge() | 1.0 | 1.0 | 1.0 |
Total | 197.0 | 296.0 | 315.0 |
Average | 1.407 | 2.114 | 2.25 |
class | OCavg | WMC |
---|---|---|
graph.Edge | 1.0 | 5.0 |
graph.Graph | 2.6 | 39.0 |
graph.Vertex | 1.33 | 24.0 |
graph.Vertex.NextIterator | 1.33 | 4.0 |
myclass.Main | 1.0 | 1.0 |
myclass.MyGroup | 1.57 | 22.0 |
myclass.MyNetwork | 2.4 | 72.0 |
myclass.MyPerson | 1.26 | 19.0 |
Total | 258.0 | |
Average | 1.82 | 17.2 |
代码复杂度都不高,还算可以。
三次作业其实不需要自己思考太多架构的问题,都是一些图的算法的堆叠。但是在运行时间有所限制,需要考虑一下自己的性能问题。之前同学们提到的hashmap初始化容量到底应该适用于什么时候,以及容器的选取,需要仔细考虑一下。还有图的相关算法的选取,以及针对一些缓存容易实现的查询变量来实现缓存,以降低遍历的时间。
本次优化主要将类似于遍历的大块时间,采用缓存的思想,均摊进其他更节省时间的操作中。
bug分析
第三单元的课程公布区偶然成为了“第三单无”,这也预示了我这单元的悲惨结局。
第一次作业,强测只过了一个点,由于我平时太过相信弱测,自己没有进行测试,导致程序竟然抛出无法捕获的异常。唉,这次作业人没了。导致抛出异常的原因主要是,第一次使用迭代器进行遍历,没有正确的理解,好在修改起来也很容易。
第二次作业,自我进行了一些测试,但也都是很弱的测试。忽略了在人数>1111的条件下,抛出了异常,wa了四个点。虽然JML中的require定义中提到如果不满足前置条件,方法执行结果不可预测,或者说不保证方法执行结果的正确性。但是自己用头随便想一想也不应该在人数过多的时候抛出并不符合此行为的异常。再次劝告大家在写代码时候,一定要看清楚满足什么条件需要抛出什么异常,不要想当然的一个else就完事了。修复当然也非常简单,只要在else中加个条件限定就OK了。
第三次作业,wa了一半,bug原因是在并查集中的寻找根节点的方法,手残+脑残在while中的条件打错了一下,要命的是这个方法还在多个查询中使用到了。自己手测竟然没测出来,唉太过于悲痛,这个单元,我真的人没了。
不过,反思一下自己,失败并非偶然,究其背后必有原因,主要是我懒于自我测试,在如何自动化测试上毫无了解,也导致了我失败的必然。其次,在面对代码量多一些的任务,有时偶尔会头脑不清晰,这也是需要提高的一点。
互测中主要bug是源于超时,没有考虑程序的运行时间,简单的根据JML进行多次遍历,是不可取的。
心得体会
对于JML来说,他的目的是实现代码的形式化描述,那么这样,不同的编程人员,可以有统一的标准去编写代码,可以实现代码的形式化验证,以求真正的消除bug。目前来说,对于我的代码基于完成作业的目的来看,没什么优势。但是,如果今后代码需要应用在更加重要的场合时,代码的形式化验证或许要成为一种必然。感谢这单元让我了解了JML,感受了或许有一天我的程序可以做到“无bug”,也就是指导书中说的那句话,严格满足JML,就能保证正确性。不过目前只有在实验中写了一点点JML,对于如何撰写JML,只能说了解尚浅。
虽然,现在JML的形式化验证的一系列工具现在还没有发挥到想象中的效果,但是能学一些新的东西还是有所收获的。
最后,反思一下自我,长篇幅代码的总体把握程度还需要加强,以及对于自己的测试能力能要有所提高。
OO课程至今,收获也是很多,越发的明白,代码能够实现功能仅仅是代码的一个方面,对于代码时间的优化、设计架构的简洁、扩展性、美观等等也是要追求的方面。OO除了让我学会了如何实现面向对象,也更考验了我的多种能力,也让我有了多种思考,绝不仅仅是一门教你如何写代码的课。
最后,希望最后两次作业能对我好一点。