BUAA_OO_Unit3_Summary
目录
-
测试方法
-
数据生成
-
正确性检验
-
-
架构分析
-
核心类图
-
图论算法
-
-
性能分析
-
算法选择
-
动态维护
-
-
拓展任务
-
心得体会
一、测试方法
本次作业中测试方面主要通过随机生成数据的方式,对程序进行了测试。
在最初,只是无规律的随机将作业中需要的二十余类数据进行生成并进行测试,但是很显然这样的方式太过盲目,由于很多关键性指令都需要一定限制条件才能执行代码中核心处理部分,例如sim指令需要在两个人存在并且具有直接或间接关系才能执行核心函数而不是直接在最初就抛出异常,而完全随机生成的数据让大多数指令的结果都是抛出某种异常,这样就很大程度丧失了测试的覆盖范围。
在意识到该问题之后,就对数据生成进行了一定修改。例如在最初添加一定数量的Person和Group,再增加一定关系,这样就可以一定程度保证后续的测试指令可以按照我们预期检测函数功能。同时设计控制指令间的比例,让我们希望测试的指令生成的多一些,简单的指令少一些等等。
在确认了各类异常并无问题之后,就可以只测试函数的非异常抛出部分,更加充分的测试函数功能。例如将已生成的Person加入集合,指令需要Person时直接在集合中调用,这样就保证了不会出现异常抛出。
在随机生成数据之外,还辅以手动构造特殊数据和针对性构造单一指令等方法进行了测试。
正确性检验
本次作业中正确性检验主要采用对拍的方式。
在构造数据之初,就思考了如何对程序的结果进行检验的问题。而本单元也和前两单元不同,第一单元可以通过Sympy库结果比对,第二单元可以通过检查电梯是否有非法情况。而本单元似乎并没有一个对程序结构进行验证的方式,因此还是选择了和同学进行对拍方式验证程序输出结果的正确性。
二、架构分析
核心类图
忽略了部分类,属性和方法,主要注重于三次作业中图模型相关内容。
图论算法
在本单元的作业中用到了一些图论相关的算法,包括判断是否联通,最小生成树,最短路径等。在设计中添加了部分类入Heap,Node,Edge,MinTree来设计相关算法。其余部分根据JML正常编写即可。
三、性能分析
算法选择
-
qci:并查集。本单元第一次作业中最困难的一个函数,在设计时就应当考虑算法时间复杂度问题,显然用最简单朴素的dfs算法很可能超时,所以直接使用并查集+路径压缩。
-
qlc:kruskal + 并查集。在设计中采用kruskal而非prim的原因,一是可以利用本单元第一次作业中实现的并查集算法优化,二是个人对kruskal算法相对更熟悉。
-
sim:dijkstra + 堆优化。在sim指令设计时并未考虑优化导致了强测互测中的超时,在bug修复中予以改正。
动态维护
在部分方法中,如果完全按照JML规格进行设计很可能导致时间复杂度过大,例如出现O(n^2)的二重循环就可能超时,因此最好在相关类中通过增加属性方法进行动态维护,每次添加删除相关元素就一同修改,方法调用时直接返回该属性即可,从而避免时间复杂度过大的情况。
四、拓展任务
新增Person子类
-
Advertiser
-
ArrayList<AdvertisementMessage> AdvertisementMessages
-
ArrayList<PurchaseMessage> purchaseMessages
-
-
Producer
-
int preference
-
int countSales
-
-
Customer
-
int preference
-
新增Message子类
-
AdvertisementMessage
-
Product product
-
-
PurchaseMessage
-
Product product
-
int fromId
-
新增商品类
-
Product
-
int preference
-
三个核心业务接口方法的JML规格
-
countSales
//@ ensures \result == countSales;
public /*@ pure @*/ int countSales();
-
sendAdvertisement
/*
@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Advertiser && people[i].containsAdvertisementMessage(advertisementId));
@ assignable people[*].messages
@ ensures (\forall int i; 0 <= i && i < people.length; (getPerson(id).isLinked(people[i]) && (people[i] instanceof Customer)) ==> (people[i].messages.length == \old(people[i].messages.length) + 1 && people[i].messages[0] == \old(getPerson(id).getAdvertisement(advertisementId)) && (\forall int j; 1 <= j && j < people[i].messages.length; people[i].messages[j] == \old(people[i].messages[j - 1]))));
@ ensures (\forall int i; 0 <= i && i < people.length; (!(getPerson(id).isLinked(people[i])) || !(people[i] instanceof Customer)) ==> (people[i].messages.length == \old(people[i].messages.length && (\forall int j; 0 <= j && j < people[i].messages.length; people[i].messages[j] == \old(people[i].messages[j]))));
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) (\forall int i; 0 <= i && i < people.length; people[i].getId() != id || (people[i].getId() == id && !people[i] instanceof Advertiser));
@ signals (AdvertisementIdNotFoundException e) (\exists int i; 0 <= i && i < people.length && people[i].getId() == id && people[i] instanceof Advertiser; !people[i].containsAdvertisementMessage(advertisementId));
*/
public void sendAdvertisement(int id, int advertisementId) throws PersonIdNotFoundException, AdvertisementIdNotFoundException;
-
sendOrder
/*
@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == id1 && people[i] instanceof Advertiser && people[i].containsPurchaseMessage(purchaseId));
@ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == id2 && people[i] instanceof Producer);
@ assignable people[*].messages
@ ensures (getPerson(id1).isLinked(getPerson(id2)) && (getPerson(id1) instanceof Advertiser) && (getPerson(id2) instanceof Customer));
@ ensures (getPerson(id2).messages.lenth == \pld(getPerson(id2).messages.lenth) + 1) && (getPerson(id2).messages[0] == \old(getPerson(id1).getPurchase(purchaseId)) && (\forall int i; 1 <= i && i < getPerson(id2).messages.length; getPerson(id2).messages[i] == \old(getPerson(id2).messages[i - 1]));
@ ensures (\forall int i; 0 <= i && i < people.length; (people[i].getId != id2) ==> (people[i].messages.length == \old(people[i].messages.length && (\forall int j; 0 <= j && j < people[i].messages.length; people[i].messages[j] == \old(people[i].messages[j]))));
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) (\forall int i; 0 <= i && i < people.length; people[i].getId() != id1 || (people[i].getId() == id1 && !people[i] instanceof Advertiser));
@ signals (PersonIdNotFoundException e) (\forall int i; 0 <= i && i < people.length; people[i].getId() != id2 || (people[i].getId() == id2 && !people[i] instanceof Customer));
@ signals (PurchaseIdNotFoundException e) (\exists int i; 0 <= i && i < people.length && people[i].getId() == id1 && people[i] instanceof Advertiser; !people[i].containsPurchaseMessage(purchaseId));
*/
public void sendOrder(int id1, int id2, int purchaseId) throws PersonIdNotFoundException, PurchaseIdNotFoundException;
五、心得体会
在本单元的学习任务中,我初步了解了JML相关知识,契约式编程思想,同时回顾了数据结构相关知识。
说实话,本单元的作业难度相较于前两单元并不算高,JML大部分时间只需要对着写即可完成功能。本单元作业中的难点似乎都放在了少数几个函数的数据结构算法上,这也是可以理解,毕竟如果所有函数都是简单的照着写就没有了意义。但是我们对于JML的理解,学习到的能力更多的都在于读,当自己动手写的时候就难免出现考虑不周,编写缓慢的情况,或许应当在这方面相对增加一定练习。