BUAA OO 第三单元总结
BUAA OO 第三单元总结
1 规格实现设计策略
本次作业中的规格实现我采取了宏观到具体、增量修改 (改不了就重构) 的策略,大致进行以下几个步骤。
1.首先浏览整体某个类中所存储的数据和需要实现的方法,梳理方法的大致含义以及方法与数据存储之间的关系。
3.若复杂度过高,则尝试优化,可通过增加存储数据或优化算法的方法进行优化,若需要增加存储数据,则在第2步进行完成后重复第1步。
4.对于非新增的方法,利用文本比对工具筛选出不同的地方,对整体实现的关系进行修正。
5.具体实现,先实现抛出异常的部分,再实现正常处理的部分,先易后难。
2 测试方法
2.1 阅读代码
本单元的作业通过JML给出规格,而不需要自己设计架构,因此代码不会出现千奇百怪的现象。
首先是分析JML给出的规格有没有数据类型导致的问题,比如 queryGroupAgeVar() 方法中使用的mean是一个int类型,这里int型与实际的平均值有一定的差距,因此最终结果可能会出现差距。
其次分析复杂度,对于可能出现 复杂度的JML规格,查看代码,如果同样出现 的实现方式,就会出现问题。
2.2 黑箱测试
本单元的方式主要是通过这种方式与其他同学对拍进行测试。
通过python或C语言随机生成数据,由于考虑到id带来的一些影响 (随机生成的id重复率实在感人) ,因此将查询指令分为正常数据和异常数据,正常数据需要对已经生成的id进行处理,异常数据采用随机生成的方式,实际上是伪异常数据(因为可能随机生成的id恰好与已生成的id相同)。
2.3 Junit单元测试
按照从底层到顶层,对简单和已经确定没有问题的方法不需要进行测试的思路进行单元测试的编写和测试,大大减少了测试所用的时间,降低了测试的复杂性。
3 容器选择
这一个单元我使用的容器比较多,包括ArrayList、LinkedList、HashMap、HashSet,使用策略如下。
1.需要按次序进行储存,经常进行按索引查询,但是不需要经常进行添加和删除的使用ArrayList。
2.需要按次序进行储存,经常进行增删,且查询主要存在于头部的使用LinkedList。
3.与关键词紧密联系,经常按照关键词进行查询的使用HashMap。
4.不需要按次序进行储存,主要查询存在性的使用HashSet。
4 性能问题
本单元的主要问题出现在性能方面,而性能问题可能出现的地方主要在各种数据的统计方面 (尤其是按照JML给出的方法直接书写得到复杂度O(n^2)的地方) 。
这个方法实际上是计算某一个图的所有路径的权重之和的2倍,在这里同样采用了修改过程中维护的方法进行降低复杂度,涉及到以下方法。
4.2.1 addToGroup() / delFromGroup()
在这里可以认为是需要增删某个点,在这里需要对这一个点的邻接路径进行遍历,进行增删。
4.2.2 addRelation()
在这个方法可以认为是增加某一条路径,仅需要增加两倍的权重即可。
在这个方法中没有涉及到类似于 delRelation() 的方法,但是应类似于 addRelation() 方法的处理即可。
相比较于便历,同样采用修改图时进行相关数据的修正的方法。
这一个方法实际上是查询两个点之间的最短加权路径,主要问题是算法问题。
在本单元的作业中使用了堆优化Dijkstra算法进行优化。
5 架构设计
本次作业由JML给出了规格,因此基本上不需要自己实现架构,因此本部分主要对容器选择和方法实现方式进行分析。
5.1 第一次作业
5.1.1 容器选择
选择HashMap作为容器,在MyPeople类中以id为关键词设计联系acquaintance和value,同样在MyNetwork类中以id为关键词存储所有的person对象。
5.2.2 并查集
本次作业中对图的操作主要可以采用并查集的方法进行降低复杂度。
由于增加图中某个点或增加点之间的路径必然会以某种次序进行,因此可以采用类似于findRoot的方法进行记录。
当增加关系的时候,查询两个点的root结点,若结点不相同,则使其中一个结点的root定位为另一个root结点即可。
5.2 第二次作业
在第一次作业的基础上新增Group和Message接口,容易出错的地方就在于valueSum、ageMean、ageVar这三个变量,对其进行分别的预处理,详细操作可详见4.2、4.3。
5.3 第三次作业
第三次作业的难点在于加权最短路径的计算,在本次作业使用了堆优化Dijkstra算法(非堆优化Dijkstra算法仍有O(n^2)的复杂度)。
private int dijkstra(int personId1, int personId2) {
Map<Integer, Integer> disMap = new HashMap<>();
Set<Integer> hasCheck = new HashSet<>();
Queue<Pair> pairQueue = new PriorityQueue<>();
disMap.put(personId1, 0);
pairQueue.offer(new Pair(personId1, 0));
while (!pairQueue.isEmpty()) {
Pair pair = pairQueue.poll();
int personId = pair.getId();
if (hasCheck.contains(personId)) {
continue;
}
hasCheck.add(personId);
Person person = getPerson(personId);
Map<Integer, Person> acquaintance = ((MyPerson) person).getAcquaintance();
for (Integer itemId : acquaintance.keySet()) {
int value = disMap.get(personId) + person.queryValue(getPerson(itemId));
if (!hasCheck.contains(itemId) &&
(!disMap.containsKey(itemId) || value < disMap.get(itemId))) {
disMap.put(itemId, value);
pairQueue.offer(new Pair(itemId, value));
}
}
}
return disMap.get(personId2);
}
新创建的Pair类是为了优先队列进行排序时按照我们所需要的规则进行相应的排序。
6 个人感想
6.1 不要盲目直接根据JML写代码
在第一次作业中,我根据JML规格直接完成了 queryBlockSum() 方法,直接导致CTLE……
所以JML规格只是规定了这一个方法在什么条件下需要干什么,不考虑其性能代价,所以需要在这个基础上进行自己的优化。
(另外,想说一句JML在容器里删除或增加某个数据,写起来真麻烦呃……)
6.2 不要在容器遍历过程中增删
第三次作业在自己本地写的时候掉进了这个坑……
如果按照C语言理解的话,增删过程中会出现类似于指针丢失的情况,因此会出现类似于 NullPointException 的异常情况。
6.3 JML规格基础的整体认知
课程组给出的指导书还是相对来说比较全面的(虽然增增删删好多次),但是无法要求所有的JML规格总是如此全面(架构师也可能想不了这么全面),所以我们需要在JML规格的基础上进行整体认知,至少知道这个类,这个方法需要我们做什么,不然很有可能出现奇奇怪怪的bug,以及自己编写过程中一些难以考虑到的地方。

浙公网安备 33010602011771号