面向对象2022-第三单元作业总结
-
分析自测过程中如何利用JML规格来准备测试数据
根据JML代码,可以枚举出所有的分支,从异常情况再到普通操作,构造具有针对性的测试数据,再检验输出结果是否与预期结果相同。下方的queryValue
是一个相对简单的例子。
/*@ public normal_behavior
@ requires contains(id1) && contains(id2) && getPerson(id1).isLinked(getPerson(id2));
@ ensures \result == getPerson(id1).queryValue(getPerson(id2));
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !contains(id1);
@ signals (PersonIdNotFoundException e) contains(id1) && !contains(id2);
@ signals (RelationNotFoundException e) contains(id1) && contains(id2) &&
@ !getPerson(id1).isLinked(getPerson(id2));
@*/
public /*@ pure @*/ int queryValue(int id1, int id2) throws
PersonIdNotFoundException, RelationNotFoundException;
这个函数存在4条分支,首先是不包含id1,其次是不包含id2,然后是id1和id2两者并不相连,最后才是输出正确结果。针对这四种可能,可以构造一个包含3个人的例子,1和2相互连接,1和3不连接。前两种情况就可以对应queryValue(0, 1)
以及queryValue(1, 0)
,后两种情况是queryValue(1, 3)
以及queryValue(1, 2)
。如此一来,这个函数中所有的情况都被列出,通过测试后则可以保证本函数自身功能无误。当然,也要保证函数不能有多余的副作用。
再复杂一些的函数,同样也是根据JML来遍历可能性构造测试数据,只是有时读起来要费神一些。
-
梳理本单元的架构设计,分析自己的图模型构建和维护策略
得益于社交网络在现实生活中的形象性,本单元的架构也简单易懂。在代码运行后会根据MyNetWork
生成一个社交网络图,第一个单元只在这个图中添加了MyPerson
和MyGroup
,你可以想象MyGroup
就是网络中的俱乐部,MyPerson
则是里面的成员。当然,MyPerson
可以不是任意一个俱乐部的成员,也可以在多所俱乐部中做客。二三单元则是让这个社交网络更加热闹起来,彼此之间可以发送邮件,而邮件的形式也变得多样。
总体的架构已经很清楚了,不过针对要求指令,代码中还实现了信息维护。如query_value
,要求返回一个俱乐部中所有成员的相互价值(若两个成员是熟人,则存在一定价值)之和。JML代码中对此给出了一个n2复杂度的代码,实现功能完全没有问题。但是,由于query_value
指令没有被限制使用次数,多次调用该指令将使得评测超时。其余的一些量,比如俱乐部成员的平均年龄,由一个人展开生成的最小生成树,也需要进行维护。
不过维护并没有听起来那么简单,一是需要考虑维护成本和实用性,二是维护的正确性。维护的成本是指每次条件修改时都需要修改有关变量,而这种修改其实是不属于JML说明的副作用,使得单条指令的性能下降了。维护的实用性则是这样的维护是否有价值,比如由英国科学家林柯卡特提出的LCT算法,可以实现对最小生成树的维护。但如果评测指令中请求输出最小生成树的次数很小,比如10000条指令中最多只有100次输出,那么这种维护可能并不值得。
还有一点就是维护的正确性,要求考虑到条件改变的所有情况。比如你作为club的管理员,那么就不仅要在成员入会的时候修改信息,在成员离开的时候也要进行修改。
-
按照作业分析代码实现出现的性能问题和修复情况
多亏了和舍友的讨论以及往年学长的博客,对于可能出现的性能问题都有效规避了。此处谈一下看到的别人中弹的情况吧。第一次代码中有些同学只看到了较为复杂的isCircle
函数,忽略了不限次数的看似简单无害的query_value
,没有在有限时间内完成。不过在经过这一次的经验学习后,也转换为使用维护策略,后期通过了测试。
-
Network进行扩展,并给出相应的JML规格
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
“天下熙熙,皆为利来;天下攘攘,皆为利往。“ ——司马迁 《货殖列传》
此处我们假设4种Person
均是通过原有的addPerson
加入到网络中,在解析时进行分类到不同的容器中。此处或许还要额外设计商品Product
类,并且也要存在NetWork
中,类似于表情包的存在。Advertiser
,Producer
,Customer
也会有涉及到Product
的属性。不过也有可能被简化,只是作为记录。
核心业务功能1号:int querySales(int id)
,实现销售额查询
/*@ public instance model non_null int[] productIdList;
@ public instance model non_null int[] productSalesList;
@*/
/*@ 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 == productSalesList[i]);
@ also
@ public exceptional_behavior
@ requires !(\exists int i; 0 <= i && i < productIdList.length; productIdList[i] == @id);
@ signals_only ProductIdNotFoundException;
@*/
public /*@ pure @*/ int querySales(int id) throws ProductIdNotFoundException;
核心业务功能2号:void buyProduct(int customerId, int advertiserId, int ProducerId, int productId)
,购买产品,客户向广告商发送请求购买产品,然后广告商从生产者处转移商品。其实这个函数有点依赖于架构,因为如果只是各方的金钱数目变化,则本质上和产品没有关系。而且是否需要考虑货存的数目,生产者可以生产什么种类的产品也需要考虑。鉴于此,我们简化的认为生产者一定可以给出相应产品,并不把产品这个概念真正的纳入代码中,而仅是从逻辑变化上来理解这一过程。(甚至不考虑消费者钱够不够)
/*@ public normal_behavior
@ requires customerContains(customerId) && advertiserContains(advertiserId) &&
@ ProducerContains(ProducerId) && ProductContains(productId);
@ ensures getCustomer(customerId).getMoney() ==
@ \old(getCustomer(customerId).getMoney()) - getProduct(ProducerId).getPrice();
@ ensures getAdvertiser(advertiserId).getMoney() ==
@ \old(getAdvertiser(advertiserId).getMoney()) + 0.3*getProduct(ProducerId).getPrice();
@ ensures getProducer(advertiserId).getMoney() ==
@ \old(getProducer(advertiserId).getMoney()) + 0.7 * getProduct(ProducerId).getPrice();
@ ensures (\exists int i; 0 <= i && i < productIdList.length && productIdList[i] ==
@ productId; productSalesList[i] == \old(productSalesList[i]) + 1);
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !customerContains(customerId);
@ signals (PersonIdNotFoundException e) customerContains(customerId) &&
@ !advertiserContains(advertiserId);
@ signals (PersonIdNotFoundException e) customerContains(customerId) &&
@ advertiserContains(advertiserId) && !ProducerContains(ProducerId);
@ signals (ProductIdNotFoundException e) customerContains(customerId) &&
@ advertiserContains(advertiserId) && ProducerContains(ProducerId) &&
@ !ProductContains(productId);
@*/
public void buyProduct(int personId, int advertiserId, int ProducerId, int productId)
throws PersonIdNotFoundException, ProductIdNotFoundException;
核心业务功能3号:int storeProduct(int id)
,上市新产品。
/*@ public normal_behavior
@ requires !(\exists int i; 0 <= i && i < productIdList.length; productIdList[i] ==
@ id);
@ assignable productIdList, productSalesList;
@ ensures (\exists int i; 0 <= i && i < productIdList.length; productIdList[i] == id &&
@ productSalesList[i] == 0);
@ ensures productIdList.length == \old(productIdList.length) + 1 &&
@ productSalesList.length == \old(productSalesList.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(productIdList.length);
@ (\exists int j; 0 <= j && j < productIdList.length; productIdList[j] ==
@ \old(productIdList[i]) && productSalesList[j] == \old(productSalesList[i])));
@ also
@ public exceptional_behavior
@ signals (EqualProductIdException e)(\exists int i;0 <= i && i < productIdList.length;
@ productIdList[i] == id);
@*/
public void storeEmojiId(int id) throws EqualproductIdException;
-
本单元学习体会
本单元是最容易对拍的一个单元,和小伙伴们一起对拍,最棒!
JML的确很好,将规则描述的很清楚。不过读起来并不是特别容易,为了实现准确性而失去了一些简洁性,或许还可以进行改进。但是借助于JML方法进行单元化测试时,的确可以很好地覆盖所有可能性。