BUAA_OO_2022 第三单元总结

BUAA_OO_2022 第三单元总结

(1)    分析在本单元自测过程中如何利用JML规格来准备测试数据

  第三单元的一个特征就是指令的条数非常多,并且指令之间的联系与最后显示结果有很密切的关系,为了支持较多的指令,类的种类和属性以及具有的方法也达到了一个新的高度,因此相比于以往一二单元的手动构造数据就可以较好地评测,第三单元需要使用新的测试方法,又因为有JML的约束,必须要严格实现,否则会出现不等价的情况,并且凭主观臆想很难找到问题的所在,因此我一般采取以下步骤进行测试:

  单元测试,利用Junit工具对方法单独编写程序进行测试,在测试中其实关键在于构造具有强度的数据,举一个很典型的例子,比如sendMessage方法对于红包的发送中就需要严格实现红包金额的正确分配,群发红包100,1人发红包到三人群,按照JML必须是先100/3后取整33,分配给收红包每个人33,最后余下的34,给发红包的人,反之如果实现的过程中认为就是平均分配,一个人发出来三人抢,自己收回1/3就会出现错误,没有严格按照JML实现代码,这一点其实是可以通过单元测试来检查的,还有一些比较低级的错误也是可以通过单元测试发现的,比如deleteColdEmoji方法最后需要更改表情列表,可能会出现忘记刷新的情况,这时单元测试就很容易测出问题。同时也必须认识到,单元测试比较适合作为功能明确且较为单一的方法测试,如果需要更复杂的,需要多方法来测试的,就会显得不再简洁,不再单元化,因此还需要其他的测试。

  压力测试,对于较为复杂的方法,往往是实现的核心和难点,在基础的方法通过单元测试进行了检验的情况下,对于复杂的方法可以进行压力测试,本次实验中复杂度较高的方法主要为对于连通集确认,求最小生成树、单源最短路径方法,因此对于这样的方法我们需要助python等其它工具进行数据的投喂以及编写同样能实现该功能的代码,最后进行输出对拍。

当然最后要注意复杂度的优化,否则很容易超时

(2) 梳理本单元的架构设计,分析自己的图模型构建和维护策略

  本单元三次作业迭代开发,并且已有接口已经较为充分,因此新增加的类较少,分别是异常计数器类,Edge类,Path类。

  对于连通集合的维护主要使用HashMap容器,将父节点作为Value,本身作为Key,可以建立起并查集,降低复杂度。初始时addPerson时加入map,父节点为自身,在addRelation时进行节点的合并,保持一个连通分量只有一个父节点,在查询两者是否在一个连通分量时,查询他们的父节点是否相同即可。

  对于最小生成树问题,可以采用并查集优化的克鲁斯卡尔算法,建立并查集的方法类似连通集的建立,主要有优化了查找是否在形成环的步骤,同时在这个过程中,要使用edge类。

  对于最短路径的问题,使用了队列优化的迪杰斯特拉算法,使用到了path类存储出发点到当前点的距离,因此在迭代更新最短距离时可以方便操作。

(3) 按照作业分析代码实现出现的性能问题和修复情况

  第一次作业出现了对于计数器异常次数统计上的理解错误,导致强测很不理想,修改后强测全部通过,但是遇到了互测中对于连通集的压力测试,当时使用的是BFS算法,导致测试的结果不理想,加入并查集优化以后降低了复杂度,完成了修复。

       第二次作业中主要出现了对于qlc指令的rtle和对于求平均指令的JML实现不严格造成了错误,严格实现后再对于qlc指令采取了并查集优化,至此完成了修复。

       第三次作业同样出现了实现不严格的问题(发红包问题),并且由于没有做充分的压力测试导致了sim指令出现了RE,这是个很严重的bug,针对这个指令重新进行了代码的编写采取队列优化进行了修复。

(4) 请针对下列内容对Network进行扩展,并给出相应的JML规格

假设出现了几种不同的Person

Advertiser:持续向外发送产品广告

Producer:产品生产商,通过Advertiser来销售产品

Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息

Person:吃瓜群众,不发广告,不买东西,不卖东西

如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等。请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)

  就像查询社交值一样,新增的商品也要新建类,属性至少要包括价格销售额等信息

需要添加几种新的消息类型,用于表示购买或推销某种产品,其中包含推销消息和购买消息等。这些消息可以继承Message有关的方法。

Producer向Advertiser发送推销message。可以考虑是向推销群的Advertiser 发送消息,然后会加入到推销者的messages中,之后再由推销者发送到他们的推销群或者熟人中。

  在这里我们考虑书写的方法为:

 

查询某个商品的销售总量(金额)方法queryProductSales(int id),返回产品的销售额。

在这里可以考虑沿用Person的JML规格

/*@ public normal_behavior

      @ requires containsProduct(id);

      @ ensures \result == getProduct(id).getSales();

      @ also

      @ public exceptional_behavior

      @ signals (ProductIdNotFoundException e) !containsProduct(id);

      @*/

public /*@ pure @*/ int queryProductSales(int id) throws ProductIdNotFoundException;

 

推销者向其组员推销产品(发送广告)的方法,沿用sendMessage的方法(考虑其type为2)

/*@ public normal_behavior

@ requires containsMessage(id) && getMessage(id).getType() == 2 &&

@           getMessage(id).getGroup().hasPerson(getMessage(id).getAdvertiser());

@ assignable people[*].products, messages

@ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 &&

@      (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id;

@         (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));

@ ensures (\forall Person p; \old(getMessage(id)).getGroup().hasPerson(p); \old(p.products.length) == p.products.length-1;

@ ensures (\forall Person p; \old(getMessage(id)).getGroup().hasPerson(p);

@      (\exists int i; 0 <= i && i < p.products.length; products[i].equals(\old(((AdvertiseMessage)getMessage(id))).getProduct()));

@ ensures (\forall Person p; \old(getMessage(id)).getGroup().hasPerson(p); (\forall int i; 0 <= i && i < \old(p.products.length);

@      (\exists int j; 0 <= j && j <= p.products.length;\old(p.products[i]).equals(p.products[j])    ));

@ ensures (\forall Person p; !\old(getMessage(id)).getGroup().hasPerson(p); \old(p.products.length) == p.products.length;

@ ensures (\forall Person p; !\old(getMessage(id)).getGroup().hasPerson(p);

@      (\forall int i; 0 <= i && i < \old(p.products.length);(\exists int j; 0 <= j && j <= p.products.length;\old(p.products[i]).equals(p.products[j])    ));

@ also

@ public exceptional_behavior

@ signals (MessageIdNotFoundException e) !containsMessage(id);

@ signals (PersonIdNotFoundException e) containsMessage(id) && getMessage(id).getType() == 2 &&

@          !(getMessage(id).getGroup().hasPerson(getMessage(id).getAdvertiser()));

@*/

public void sendAdvertiseMessage(int id) throws

MessageIdNotFoundException, PersonIdNotFoundException;

 

生产者向其推销员发布任务的方法,沿用sendMessage的方法(考虑其type为3)

/*@ public normal_behavior

@ requires !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)) &&

@           (message instanceof AdvertiseMessage) ==>containsProductId(((AdvertiseMessage)message).getProductId()) &&

@            (message.getType() == 3) ==> (message.getProducer() != message.getAdvertiser());

@ assignable messages;

@ ensures messages.length == \old(messages.length) + 1;

@ ensures (\forall int i; 0 <= i && i < \old(messages.length);

@          (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));

@ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].equals(message));

@ also

@ public exceptional_behavior

@ signals (EqualMessageIdException e) (\exists int i; 0 <= i && i < messages.length;messages[i].equals(message));

@ signals (ProductIdNotFoundException e) !(\exists int i; 0 <= i && i < messages.length;

@                                       messages[i].equals(message)) &&

@                                       (message instanceof AdvertiseMessage) &&

@                                       !containsEmojiId(((AdvertiseMessage) message).getProductId());

@ signals (EqualPersonIdException e) !(\exists int i; 0 <= i && i < messages.length;

@                                     messages[i].equals(message)) &&

@                                     ((message instanceof AdvertiseMessage) ==>

@                                     containsProductId(((AdvertiseMessage) message).getProductId())) &&

@                                     message.getType() == 3 && message.getProducer() == message.getAdvertiser();

@*/

public void addAdvertiseMessage(Message message) throws

EqualMessageIdException, ProductIdNotFoundException, EqualPersonIdException;

(5) 本单元学习体会

  本单元绝大部分实现的要求与内容由JML给出,乍一看很轻松,甚至有的代码可以复制JML中的文字,然而却是我最不顺利的一个单元。怠惰会招致惨剧,一是因为JML的代码并不能表达和java完全相同的意思,有的逻辑判断会有出入,另一个是透彻理解JML有一定难度,稍有出入就会存在难以发现bug,对于前置条件还比较容易书写,但对于后置条件的理解和正确实现就具有难度了,如果尝试将后置条件翻译成中文,虽然让人可以短时间大致理解含义但也只是大致,没有办法确保没有二义性,这就要求阅读JML时要有耐心,仔细思考,保证实现的步骤符合规格,此外本单元对于算法的考察较高,相比于第一单元不考察时间复杂度,第二单元调度策略较为简单不易因为算法而超时,第三单元的优化问题非常需要考量,不论是正确性还是性能需求都提出了较高的要求。

  JML规格是方便沟通和正确实现的工具,因此充分理解和掌握对于学习OO和以后的学习来说非常有意义。

 

 

 

 

 

 

 

 

 

 

 

posted on 2022-06-02 11:08  Lzchhh  阅读(57)  评论(0编辑  收藏  举报

导航