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和以后的学习来说非常有意义。