OO Unit3 SNS(Social Network Service)
本篇文章从以下几个部分展开:对契约式编程的认识、性能与测试、SNS扩展JML、心得体会
契约式编程
Dbc(Designed by Contract)要求软件设计者为软件组件(通常是接口约束)添加正式的可验证的接⼝,在传统抽象数据类型基础上增加了先验条件、后验条件、不变式来进行约束,本质上是信任问题:被调⽤者⽆法信任调⽤者传⼊合法参数;调⽤者⽆法信任被调⽤者没进⾏⾮法操作(致命的side effect) 因此需要通过断⾔进⾏传入参数检查(先验条件/不变式)与输出结果检查(后验条件/不变式)来对模块单元⾏为进⾏规约。
Eiffel语⾔(DbC的初号机)中的契约式体现 将require、ensure、old等都设置成关键字,提供对先验条件与后验条件检验的机制。和JML不能说很像,只能说一模一样,区别仅在于JML没有在语言层面提供服务于DbC的机制。
put (x: ELEMENT; key: STRING) is -- Insert x so that it will be retrievable through key. require count <= capacity not key.empty do ... Some insertion algorithm ... ensure has (x) item (key) = x count = old count + 1 end
本单元三次作业相似,都是通过给定JML规格完成相应的社交网络数据统计、参数计算。难点在于JML语义理解、降低时间复杂度两方面。
性能与测试
本部分主要讲述为了提升性能的一些尝试。
如何测试呢?荆爷曰:对拍大法好,我对曰:荆爷带飞好。
hw9
本次作业大头是isCircle 和 qbs,用并查集(没有去边操作)维护连通分量的代表元,这里安利一下lambda,可以让代码变得简洁高效。
@Override public int queryBlockSum() { return (int) people.values().parallelStream().filter(p -> find(p).equals(p)).count(); }
再者就是valueSum查询可以在加人加边时维护该人的value和,从而达到O(n)的求和效率
值得一提的是我在group中求valueSum和AgeMean和AgeVar三个方法中进行了并行优化,reduce操作使用时需要注意第一个参数identity的含义,详见https://stackoverflow.com/questions/32866581/in-stream-reduce-method-must-the-identity-always-be-0-for-sum-and-1-for-multipl?noredirect=1&lq=1
public int getValueSum() { return values.keySet().parallelStream() .reduce(0, (sum, p) -> sum + values.get(p), Integer::sum); } public int getAgeMean() { if (people.size() == 0) { return 0; } return people.parallelStream().reduce(0, (sum, p) -> sum + p.getAge(), Integer::sum) / people.size(); }
本次作业出于新鲜与课程组安利我尝试了JUnit测试,本来期望JUnit可以配合JML直接生成测试代码,毕竟JML已经对规格进行了充分的限制,测试需要做的就是遍历规格的各部分分支生成数据,再测试在给定条件下行为是否满足条件。
结果却发现JUnit能自动生成的部分仅限于测试代码的函数名,主体部分仍需要自己手动写数据,进行断言仍需要自己根据JML逻辑进行演算,实际上貌似不会带来测试工作的简化。
通过JML构造数据需要覆盖各个requires的情况以保证正确性。性能则无关乎JML,而在于具体实现方式。
本次作业强测互测未出问题,互测发现有同学qbs复杂度较高出锅了。
hw10
本次作业新增了message类以及和已有的类交互,阅读JML即可。
主要难点在于qlc,我采用Kruscal算法,由于保证了该操作个数上限,在加边时动态维护最小生成树给频度较高的ar带来额外的时间开销,故选择将建图、求最小生成树的复杂度给到频度较低的qlc中。
其中边升序排序的要求对应于PriorityQueue。
本次作业强测互测未出问题,互测发现有同学qgvs复杂度较高出锅了(第一次作业已完成部分很容易被忽视诶)
hw11
本次作业新增了发红包、统计emoji热度等,但最关键的还是最短路径查询,用堆优化降低复杂度到O(nlogn),具体来说就是通过PriorityQueue确定下一个需要访问的节点来避免遍历所有节点。
此外,为了维护图的信息,我新增了Edge类维护val,为了在emoji热度较低删除时避免遍历,将emojiId对应到一个emojiInfo类,类中存储其对应的emojiMessage和heat
public class EmojiInfo { private final HashSet<Integer> messages; private int heat; } public class MyNetwork implements Network { ... private final HashMap<Integer, EmojiInfo> emojis; }
伪代码如下:
int minDist(MyPerson p1, MyPerson p2) { if (p1.equals(p2)) { return 0; } PriorityQueue<Node> pq = new PriorityQueue<>(); init(); //for dist and visited p1.setDis(0); pq.offer(p1); while (!pq.isEmpty()) { Node curN = pq.poll(); if (curP.equals(p2)) { break; } //achieve if (curP.isVisited()) { continue; } //updated curP.setVisited(true); curP.getAccquaintance().forEach(p->{ updateDist(); }); } return p2.getDis(); }
完成的过程中遇到bug,最终发现是init过程中未设置visited。
本次作业强测互测未出问题,却是OO课程以来最狼狈一次作业。听说第三单元简单我就没有参考往届学长博客,写完搁那儿摆着看番哇酷哇酷地享受生活去了。ddl前一天才听超哥说到堆优化最短路,彼时已是听完杰伦演唱会重制,赶往大运村广场野餐party的路上,箭在弦上不得不发,寻欢作乐好生热闹,直到半夜一点半才被保安terminate。第二天睡眠不足头晕目眩改起来bug连连,好在有超哥、HIO、涛涛、DGO、BGO等一众好友帮助才有惊无险,看来人类最大的敌人不是无知而是傲慢这句话不无道理。
SNS扩展
需求
假设出现了几种不同的Person
-
Advertiser:持续向外发送产品广告
-
Producer:产品生产商,通过Advertiser来销售产品
-
Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
-
Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
实现
广告设置message type = 2,由Advertiser发送给单个Customer,记录advertiser
订单设置message type=3,由Advertiser发送给Customer,同时减少Customer的钱
实现发送广告、发送订单、选择产品三个功能,选择产品通过判断订单的preference(与自己钱包呜呜呜,但是鉴于可以贷款,Customer有了决定自己买什么东西而不看自己有多少钱的权利)决定
public class Advertiser extends Person { } public class Producer extends Person { } public class Customer extends Person { } public class Advertisement extends Message { private int price; private boolean taken; } public class Order extends Message { private int price; } /*@ public normal_behavior @ requires containsMessage(id) && getMessage(id).getType() == 1 && @ getMessage(id) instanceof Advertisement @ getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) && @ getMessage(id).getPerson1() != getMessage(id).getPerson2() && @ getMessage(id).getPerson1() instanceof Advertiser && @ getMessage(id).getPerson2() instanceof Customer @ assignable messages; @ assignable getMessage(id).getPerson2().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 int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size()); @ \old(getMessage(id)).getPerson2().getMessages().get(i+1) == \old(getMessage(id).getPerson2().getMessages().get(i))); @ ensures \old(getMessage(id)).getPerson2().getMessages().get(0).equals(\old(getMessage(id))); @ ensures \old(getMessage(id)).getPerson2().getMessages().size() == \old(getMessage(id).getPerson2().getMessages().size()) + 1; @ also @ public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id); @ signals (InvalidAdvertisementException e) getMessage(id).getType() != 1 || @ !getMessage(id) instanceof Advertisement; @ signals (RelationNotFoundException e) containsMessage(id) && getMessage(id).getType() == 1 && @ !(getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2())); @ signals (InvalidPersonTypeException e) containsMessage(id) && getMessage(id).getType() == 1 && @ (getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2())) && @ (!getMessage(id).getPerson1() instanceof Advertiser || @ !getMessage(id).getPerson2() instanceof Consumer) */ public void advertise(int id) throws MessageIdNotFoundException, RelationNotFoundException, InvalidAdvertisementException, InvalidPersonTypeException; /*@ public normal_behavior @ requires containsMessage(id) && getMessage(id).getType() == 1 && @ getMessage(id) instanceof Order @ getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) && @ getMessage(id).getPerson1() != getMessage(id).getPerson2() && @ getMessage(id).getPerson1() instanceof Advertiser && @ getMessage(id).getPerson2() instanceof Producer && @ getPerson(customerId) instanceof Customer @ assignable messages; @ assignable getMessage(id).getPerson2().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 int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size()); @ \old(getMessage(id)).getPerson2().getMessages().get(i+1) == \old(getMessage(id).getPerson2().getMessages().get(i))); @ ensures \old(getMessage(id)).getPerson2().getMessages().get(0).equals(\old(getMessage(id))); @ ensures \old(getMessage(id)).getPerson2().getMessages().size() == \old(getMessage(id).getPerson2().getMessages().size()) + 1; @ ensures getPerson(customerId).money = \old(getPerson(customerId).money) - getMessage(id).getPrice(); @ also @ public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id); @ signals (InvalidAdvertisementException e) getMessage(id).getType() != 1 || @ !getMessage(id) instanceof Order; @ signals (PersonIdNotFoundException e) !containsPerson(customerId) @ signals (RelationNotFoundException e) containsMessage(id) && getMessage(id).getType() == 1 && @ (!getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) || @ !getPerson(customerId).isLinked(getMessage(id).getPerson1())) @ signals (InvalidPersonTypeException e) containsMessage(id) && getMessage(id).getType() == 1 && @ (getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2())) && @ (!getMessage(id).getPerson1() instanceof Advertiser || @ !getMessage(id).getPerson2() instanceof Producer || @ !getPerson(customerId) instanceof Customer) */ public void placeOrder(int customerId, int id) throws MessageIdNotFoundException, RelationNotFoundException, InvalidOrderException, InvalidPersonTypeException, PersonIdNotFoundException; /*@ public normal_behavior @ requires containsMessage(id) && getPerson(id) instanceof Customer @ assignable getPerson(id).getMessages(); @ ensures (\forall int i; 0 <= i && i < getPerson(id).getMessage().size(); getPerson(id).getMessage().get(i) instanceof Advertisement && getPerson(id).getMessage().get(i).getPrice() <= value ==> getPerson(id).getMessage().get(i).getTaken() == true); @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !containsPerson(id) @ signals (InvalidPersonTypeException e) containsPerson(id) && !getPerson(id) instanceof Customer) */ public void select(int id, int value) throws PersonIdNotFoundException, InvalidPersonTypeException;
心得体会
-
不要轻敌喔
-
-