OO第三单元总结

OO第三单元总结


一、JML理论基础

JML是设计者提供给开发者的说明书,除了消除自然语言的歧义和可供自动化测试,在大工程中规格注释可能是开发人员间效率最高的交流方式。

JML设定规格,同时有比较高的自由度,如类型规格不限制层次继承结构的设计(满足规格继承前提),抽象数据类型不限制实际使用的数据结构。

JML的注释结构和javadoc相同,行注释表示方法为 //@annotation,块注释为 /* @annotation @*/。

@ public normal_behavior / @ public exceptional_behavior (表示正常行为或异常行为)

@ requires people.length > 0; (前置条件)

@ assignable \nothing; (副作用)

@ ensures \result == BigInteger.ZERO; (后置条件)

注意方法规格中的后置条件,其为了描述所采取的逻辑并不是对实现细节的规定。

@ signals (Exception e) para1 == para2; (触犯异常)

JML表达式中定义了一系列操作符和原子表达式,用于断言(assertion)语句和相关注释体。如\result, \old, \not_assigned,和\forall, \exists等谓词量化表达式。

二、应用JMLUnitNG进行测试

按照jyc大佬博客中的方法建文件树配置jdk8魔改person和group后进行测试,依然遇到报错。

JMLUnitNG.processAllCompilationUnits中报错,未能加载文件。

三、架构设计

三次作业严格按照规格中的层次,都没有自主设计扩展接口,造成MyNetwork类中的方法数越来越多,难以管理。可以将Network中的方法分类,针对涉及Person,Group,Money的分别抽象为PersonOperation,GroupOperation,MoneyOperation接口。

第一次作业是读懂简单JML规格,不能想当然。因为isLinked的id相等判断强测0分。

第二、三次作业难在算法复杂度的估计和实现。第三次作业求强连通使用了tarjan算法,简单地将无向图看作有向图的拓展来求点双,在解决了直接邻接情况和{(1,2),(2,3),(3,4),(2,4)}单次回溯的情况后,在强测中被8字图的情况hack,发现远不是简单增加特判就能完全解决的。于是改为O(N*N)复杂度的暴力枚举删点方法。

getBlocksum和isCircle使用并查集,但因为没按find+join的标准写法,导致关系判断出现bug。

四、bug分析

在用Hashset存储可变元素时,对元素的更改可能导致,foreach无法真正地遍历Hashset中的元素。

在试图使用HashSet<HashSet<MyPerson>>实现并查集时遇到如下问题

1    HashSet<HashSet<MyPerson>> blocks = new HashSet<>();
2    HashSet<MyPerson> subSet = new HashSet<>();
3    subSet.add(new MyPerson(1,"1",BigInteger.valueOf(1),1));
4    blocks.add(subSet);
5    for (HashSet s :
6            blocks) {
7        System.out.println(blocks.contains(s));
8   }

>>> true

此时结果正常,为true。

1    HashSet<HashSet<MyPerson>> blocks = new HashSet<>();
2    HashSet<MyPerson> subSet = new HashSet<>();
3    blocks.add(subSet);
4    subSet.add(new MyPerson(1,"1",BigInteger.valueOf(1),1));
5    for (HashSet s :
6            blocks) {
7        System.out.println(blocks.contains(s));
8   }

>>> false

但调换3,4两行,结果为false,也就是先add添加元素后改变元素,竟导致foreach无法实现正确的语义!

能力有限,目前未能找到原因。

另一种情形,

1   HashSet<MyNetwork> mySet = new HashSet<>();
2   mySet.add(new MyNetwork());
3   mySet.add(new MyNetwork());
4   System.out.println(mySet.size());
       
>>> 2

此时程序正常,但将MyNetwork换成Arraylist(或其他java容器类)就会出现问题

1   HashSet<ArrayList<Integer>> mySet = new HashSet<>();
2   mySet.add(new ArrayList<>());
3   mySet.add(new ArrayList<>());
5   System.out.println(mySet.size());
5 System.out.println(mySet.add(new ArrayList<>()));
       
>>> 1
>>> false

此时add返回值为false,说明集合中已存在此对象。验证如下

1   ArrayList<String> arrayList1 = new ArrayList<>();
2   ArrayList<String> arrayList2 = new ArrayList<>();
3   System.out.println(arrayList1.hashCode());
4   System.out.println(arrayList2.hashCode());
5 System.out.println(arrayList1.equals(arrayList2));

>>> 1
>>> 1
>>> true

五、心得体会

通过三次作业认识到,使用规格和一些抽象数据可以表达很复杂的逻辑,但在真正实现的时候不需要考虑规格描述的逻辑,只要在理解需求的基础上,可以自由地选择最优的算法。但也要充分测试不能想当然。

一般来说,性能的提升不是替换数据容器类型这么简单的事情。除了要熟练掌握算法能灵活应用,还要了解一些底层实现机制,看似O(1)的操作实际上是O(n)的情况还是很常见的。

posted @ 2020-05-23 17:53  Gawson  阅读(147)  评论(0编辑  收藏  举报