OO2022第三单元作业总结

OO2022第三单元作业总结

自测策略

本单元自测采取的策略主要还是随机测试,一开始尝试了一下单元测试,发现跟我之前预想的不太一样,并不能根据JML直接自动测试,还需要自己构造测试数据,较为麻烦,因此还是采用了跟前两个单元类似的随机测试方法,在对指令进行构造时,根据JML的前置条件的不同情况来构造数据,尽量保证覆盖所有前置条件,例如在测试可能出现PersonIdNotFound异常的指令时,设定较小的概率生成不存在的id,较大概率为不发生异常的情况。每次作业主要测试本次作业新增的指令,构造特定的指令顺序来测试,例如第二次作业边加关系边测qgvs,边删关系边测qgvs等。最后再完全随机所有指令进行回归测试。另外对一些可能较慢的指令如qlc,构造完全图等情形对性能进行测试。

架构设计及维护策略

本单元作业的架构基本与官方包给的架构一致,另外对部分指令的实现做了优化防止超时。除了个别限定数量的指令外,保证单条指令时间复杂度为O(n)就较难超时。

第一次作业中较易超时的指令是qbs以及qci指令,主要采用并查集对人之间的关系进行维护,采用路径压缩和启发式合并,时间复杂度可以认为是O(1),并且顺便在并查集中维护一下块的个数从而使qbs时间复杂度也为O(1)

第二次作业中较易超时的指令是qgvsqgav以及qlc指令。对于qgvs指令,主要做法是在group内维护valueSum变量,每次向组里加人时,遍历组内其他人,如果和要加的人之间有关系就增加valueSum。添加人之间关系后时,对所有组遍历,如果同时含有两个人就增加valueSum。在删去组内人时,遍历组内其他人,如果二人有关系就减少valueSum。这样atgdfgar时间复杂度为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中需要写上非常多,只能说各有利弊,看具体情况来使用。

posted @ 2022-06-03 23:19  sicongl  阅读(23)  评论(1编辑  收藏  举报