OO2022第三单元作业总结
OO2022第三单元作业总结
自测策略
本单元自测采取的策略主要还是随机测试,一开始尝试了一下单元测试,发现跟我之前预想的不太一样,并不能根据JML
直接自动测试,还需要自己构造测试数据,较为麻烦,因此还是采用了跟前两个单元类似的随机测试方法,在对指令进行构造时,根据JML
的前置条件的不同情况来构造数据,尽量保证覆盖所有前置条件,例如在测试可能出现PersonIdNotFound
异常的指令时,设定较小的概率生成不存在的id
,较大概率为不发生异常的情况。每次作业主要测试本次作业新增的指令,构造特定的指令顺序来测试,例如第二次作业边加关系边测qgvs
,边删关系边测qgvs
等。最后再完全随机所有指令进行回归测试。另外对一些可能较慢的指令如qlc
,构造完全图等情形对性能进行测试。
架构设计及维护策略
本单元作业的架构基本与官方包给的架构一致,另外对部分指令的实现做了优化防止超时。除了个别限定数量的指令外,保证单条指令时间复杂度为O(n)
就较难超时。
第一次作业中较易超时的指令是qbs
以及qci
指令,主要采用并查集对人之间的关系进行维护,采用路径压缩和启发式合并,时间复杂度可以认为是O(1)
,并且顺便在并查集中维护一下块的个数从而使qbs
时间复杂度也为O(1)
。
第二次作业中较易超时的指令是qgvs
,qgav
以及qlc
指令。对于qgvs
指令,主要做法是在group
内维护valueSum
变量,每次向组里加人时,遍历组内其他人,如果和要加的人之间有关系就增加valueSum
。添加人之间关系后时,对所有组遍历,如果同时含有两个人就增加valueSum
。在删去组内人时,遍历组内其他人,如果二人有关系就减少valueSum
。这样atg
,dfg
,ar
时间复杂度为O(n)
,qgvs
时间复杂度O(1)
。对于qgav
指令,维护组内所有人年龄之和以及所有人年龄平方之和,每次对组增删人时进行维护,然后利用数学关系算出方差即可,时间复杂度O(1)
。对于qlc
指令,我将所有关系都抽象成边,在NetWork
中维护边的序列,每次增加关系时就加入一条边,最后该指令其实要求的是最小生成树,采用Kruskal
算法,并利用并查集优化。时间复杂度为O(mlogm)
,m
最大为10000
,并且限制最多100
条指令,基本不会超时。
第三次作业中较易超时的指令是sim
指令,还是将关系抽象为图的边,该指令即求最短路,并且边权都是正的,主要采用dijkstra
算法,并用堆优化,时间复杂度O(mlogm)
,m
最大为10000
,限定指令数1000
,基本不会超时。
程序出现的问题
本单元作业测试中没有出现bug
。在互测中,第一次作业发现了两个同学的bug
,主要问题是在qbs
指令采用暴力的做法。第二次作业发现了一个同学的bug
,主要是qrm
指令对JML
理解有误。第三次作业没有发现bug
。本次作业中较难通过黑盒测试发现性能方面的问题,主要还是用来测试指令的正确性,对于性能方面的问题,还是需要通过阅读代码,寻找时间复杂度较高的方法,比较花费时间。
扩展功能
首先可以设计四种人作为类继承原先的Person
类,并在Person
内增加一个存储广告的容器,以及记录商品和其销量的容器。并增添广告类以及产品类。
增加addAd
方法,生产商要求广告商为其推销广告。
/*@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < Ads.length; Ads[i].equals(ad))
@ assignable Ads
@ ensures Ads.length == \old(Ads.length) + 1
@ ensures (\forall int i; 0 <= i && i < \old(Ads.length);
@ (\exists int j; 0 <= j && j < Ads.length; Ads[j].equals(\old(Ads[i]))))
@ ensures (\exists int i; 0 <= i && i < Ads.length; Ads[i].equals(ad))
@
@ also
@ public exceptional_behavior
@ signals (EqualAdIdException e) (\exists int i; 0 <= i && i < Ads.length; Ads[i].equals(ad))
@
@*/
public void addAd(Ad ad) throws EqualAdIdException;
增加sendAd
方法,广告商向其所在群组内的所有人发送广告。
/*@ public normal_behavior
@ requires hasAd(id) && getAd(id).getGroup().hasPerson(getAd(id).getPerson())
@ assignable people[*].Ads
@ ensures (\forall Person p; getAd(id).getGroup().hasPerson(p) && p != getAd(id).getPerson();
@ (\exists int i; 0 <= i && i < p.getAds().size; p.getAds().get(i) == getAd(id)) &&
@ (\forall int j; 0 <= j && j < \old(p.getAds().size);
@ (\exists int k; 0 <= k && k < p.getAds().size;
@ p.getAds().get(k) == \old(p.getAds().get(j)))))
@
@ ensures (\forall Person p; getAd(id).getGroup().hasPerson(p) && p != getAd(id).getPerson();
@ p.getAds().size == \old(p.getAds().size()) + 1)
@
@ also
@ public exceptional_behavior
@ signals (AdIdNotFoundException e) !hasAd(id)
@ signals (PersonIdNotFoundException e) hasAd(id) &&
@ !getAd(id).getGroup().hasPerson(getAd(id).getPerson())
@
@*/
public void sendAd(int id) throws AdIdNotFoundException, PersonIdNotFoundException
查询商品销量的方法querySale
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < ProductIdList.length; ProductIdList[i] == id)
@ ensures (\exists int i; 0 <= i && i < ProductIdList.length; ProductIdList[i] == id && \result == ProductSaleList[i]);
@
@ also
@ public exception_behavior
@ signals(ProductIdNotFoundException e) !(\exists int i; 0 <= i && i < ProductIdList.length; ProductIdList[i] == id)
@
@
@*/
public int querySale(int id) throws ProductIdNotFoundException;
体会与感受
本单元作业主要学习了JML
,我认为其确实是一种非常严谨的表述方法,为每个函数的条件、功能等都做出了规范化的表达,优点就是非常准确、清晰。但是同时正是由于其要求严谨,因此表述上往往非常繁琐,也许本身一两句话就可以叙述出的功能在JML
中需要写上非常多,只能说各有利弊,看具体情况来使用。