OO Unit3 SNS(Social Network Service)

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;

心得体会

  • 不要轻敌喔

  • 前置条件检查好,为了程序Robustness;后置条件约束好结果、side effect,为了保证不被甲方橄榄(

  • JUnit要是能通过JML生成一定的数据就好了

posted @ 2022-05-31 21:57  Lumyn  阅读(168)  评论(0编辑  收藏  举报