OO_unit3总结

OO第三单元总结

本单元作业的重点在于阅读和理解JML规格,并依据规格接口的要求设计满足条件的具体实现细节。一开始笔者没有特别深入的理解JML的内涵,所以在设计具体实现时会不自觉的跟着规格走,但是很快发现这种方式会带来性能的损失。本单元的核心也就在于此,即在合乎规格限定的范围内尽可能发挥主观能动性

测试数据

本单元的测试环节和其他单元不同,类及相应的属性和方法很繁杂,手搓数据难以全面覆盖。另一方面,考虑到规格的限制,我们还需要依据规格的不同边界条件特定的构造相应数据以验证正确性。
笔者在测试时主要采取以下方法:

  • 初步覆盖测试——正确性的初步检验(可以借助Junit工具完成,不过对于更加复杂的测试,Junit则显得有些力不从心)
  • 随机测试——大量数据
    • 随机测试是比较常见的测试方法,通过进行大量的无差别攻击来发现bug。
    • 随机数据生成的思路:随机生成指令,设置一定的概率获取已有的id等,减少异常的比例(简单但大部分时候有效的思路,如果需要更精准的debug还是需要手搓数据)(如下为部分随机指令生成代码)
import random
import os
instructions=[]
people=[]
groups=[]
messages=[]
#随机生成数据
def toString(*params):
    str0 = params[0]
    for i in range(1,len(params)):
        str0 += " "+str(params[i])
    str0+="\n"
    return str0
def get_id(is_rand,existing):
    if is_rand :
        #随机生成一个数
        return random.randint(1,1000)
    if  len(existing)==0:
        return random.randint(1,1000)
    else:
        index= random.randint(0,len(existing)-1)
        return existing[index]
def contain(list,id):
    for x in list:
        if x.id==id:
            return True
    return False
def is_random(random_ratio):
    judge = random.uniform(0,1)
    return judge>random_ratio
def get_instructions(): 
    file_in = open('stdin.txt', 'a')
    for i in range(1000):
        type = random.randint(0,8) 
        ...
        if type==1:
            id1=get_id(False,people)
            id2=get_id(False,people)
            value=get_value()
            file_in.write(toString("ar",id1,id2,value))
        if type==2:
            id1 = get_id(False, people)
            id2 = get_id(False, people)
            file_in.write(toString("qv",id1,id2))
        if type==3:
            file_in.write(toString("qps"))
        ...
    file_in.close()
  • 压力测试
    • 为满足性能要求,本单元另一个比较重要的测试环节是进行压力测试,在指令数量限额下投放尽可能多时间复杂度大的指令(如qlc);生成尽可能复杂的图
    • 查看cpu时间可以通过wsl(windows下的linux子系统)的time指令
    • time指令可以用cat stdin.txt|time -v java -jar hw.jar

架构设计

数据结构的选择

  • 大量使用了HashMap存储数据,如MyPersonacquaintancevalueMyNetworkpeoplegroupsmessagesemojilist
  • 自定义了一些规格以外的属性,用来简化特定的指令计算过程,如在MyNetwork中设立HashMap<Integer,Hashset<Interger>>blockPoints(key表示一个节点,对应的value表示和它同block的节点们),用于维护block,在添加relation时动态维护blockPoints,这样在访问blocksum时可以直接返回blockPoints的size

最小生成树

对于最小生成树问题,采用了并查集优化的kruskal算法,用并查集优化了是否成环的判定

最短路径

使用了Dijkstra算法,运用优先队列PriorityQueue维护最短路径

代码实现出现的性能问题和修复情况

本单元作业可能造成超时的指令如下:

  • isCircle: 使用并查集优化,只需判断两个节点是否拥有共同的根节点即可(tips:最好使用非递归的方法寻找根节点,否则当指令数膨胀时会导致爆栈)
  • queryLeastConnection: 最小生成树,通过堆优化的kruskal算法解决
  • sendIndirectMessage:最短路径,使用优先队列和Dijkstra算法
  • queryGroupValueSum:在Group内部新增valueSum属性,动态维护,直接返回
  • queryBlockSum: 动态维护blockPoints,直接返回其size
    笔者在第十次和第十一次作业分别出现了bug:
  • hw10:未使用并查集优化kruskal算法导致CTLE
  • hw11:混用emojiId与messageId,导致出现NullPointer;在deleteColdEmoji中未将emoji从message列表中删去(但是这个bug在我和同学对拍上百组数据后依然没有发现,数据构造能力仍需加强)

请针对以下内容对NetWork进行扩展,并给出相应的JML规格

Person

  • Advertiser:持续向外发送产品广告
  • Producer:产品生产商,通过Advertiser销售产品
  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
  • Person:吃瓜群众,不发广告,不买东西,不卖东西

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

  • 新增三个Person的子类:

    • Advertiser
    • Producer
    • Customer
  • 新增消息类的子类产品类Product,记录所有出现的产品(属性包括产品id、价格price、生产商id)

  • 三个核心业务功能:

    • Producer产生新产品
    • Producer向Advertiser发布新产品
    • Advertiser向外推销产品

相应的JML规格如下

  • Producer产生新产品
  /*@ public normal_behavior
     @ requires !(\exists int i; 0 <= i && i < getPerson(personId).products.length; getPerson(personId).products[i].getId() == id);
     @ assignable getPerson(personId).products ;
     @ ensures (\forall int i; 0 <= i && i < \old(getPerson(personId).products.length) ;
     @          getPerson(personId).products[i].getId() == id );
     @ ensures getPerson(personId).products.length == \old(getPerson(personId).products.length) + 1 ;
     @ ensures getPerson(personId).hasProduct(id);
     @ also
     @ public exceptional_behavior
     @ signals (EqualAdvertiseIdException e) (\exists int i; 0 <= i && i < getPerson(personId).products.length;
     @                                     getPerson(personId).products[i].getId() == id) ;
     @*/

    public void addProduct(String personId, Advertisement id);
  • Producer点对点的发布新产品(不可群发,否则触发AdvertisementException)
//Producer向Advertiser发布新产品
//发布新产品实际就是由生产商向推销员发布信息
/* @public normal_behavior
      @ requires containsMessage(id) && getMessage(id).getType() == 0 &&
      @          getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
      @          getMessage(id).getPerson1() != getMessage(id).getPerson2() &&
      @          getMessage(id).getPerson1().hasProduct(id) ;
      @ assignable messages, adList;
      @ assignable getMessage(id).getPerson1().socialValue;
      @ assignable getMessage(id).getPerson2().messages getMessage(id).getPerson2().getAdvertisements();
      @ 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 \old(getMessage(id)).getPerson1().getSocialValue() ==
      @         \old(getMessage(id).getPerson1().getSocialValue()) + \old(getMessage(id)).getSocialValue() &&
      @         \old(getMessage(id)).getPerson2().getSocialValue() ==
      @         \old(getMessage(id).getPerson2().getSocialValue()) + \old(getMessage(id)).getSocialValue();
      @ ensures (\old(getMessage(id)) instanceof advertisement ) ==>
      @         (\old(getMessage(id)).getPerson2().containsAdvertisement(id)) &&
      @         (\old(getMessage(id)).getPerson2().getAdvertisementSize() = \old(getMessage(id).getPerson2().getAdvertisementSize()) + 1 ) &&
      @         (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().getAdvertisementSize());
      @                 \old(getMessage(id)).getPerson2().containsAdvertisement(\old(getMessage(id)).getPerson2().advertisements[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 (AdvertisementException e) containsMessage(id) && getMessage(id).getType() == 1 ;
      @ signals (ProductNotFoundException e) containsMessage(id) && getMessage(id).getType() == 0 && !getMessage(id).getPerson1().hasProduct(id) ;
      @ signals (PersonIdNotFoundException e) containsMessage(id) && getMessage(id).getType() == 0 && getMessage(id).getPerson1().hasProduct(id) && (!contains(getMessage(id).getPerson1().getId() || !contains(getMessage(id).getPerson1().getId()));
      @ signals (RelationNotFoundException e) containsMessage(id) && getMessage(id).getType() == 0 &&
      @          !(getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()));
      @*/
    public void sendAdvertiseMessage(int id) throws
            MessageIdNotFoundException, PersonIdNotFoundException,AdvertiseException;
  • Advertiser向外推销产品
/*@ public normal_behavior
      @ requires containsMessage(id) && getMessage(id).getType() == 3 &&
      @           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() == 3 &&
      @          !(getMessage(id).getGroup().hasPerson(getMessage(id).getAdvertiser()));
      @*/
    public void sendAdvertiseMent(int id) throws
            MessageIdNotFoundException, PersonIdNotFoundException;

心得体会

首先是JML的阅读和理解,JML是契约式编程的一种约束,当然官方是这么说的,“JML语言是Java程序的一种规格语言,其主要对接口行为、功能和规格作出描述和规定,能够有效确保设计者的本意被清晰表达”,JML的意义在于用统一的语言要求沟通规格提出者与设计者,JML本身并不具有任何的设计细节层面的意义。在本单元的debug中,有时候多看两遍规格比盲构数据来的重要。
另一个印象比较深刻的点是三次作业中的算法,分别应用了并查集、kruskal和Dijistra算法。算法一直算是我比较头疼的部分(我头疼的部分可能有点多),大概率是因为很多算法我只停留在理论层面,自己动手写的时间比较少,所以真正要用的时候会出大问题:(。数据结构留的坑等过完烤漆一定补上(这次一定)

posted @ 2022-06-06 11:11  _反派甲  阅读(18)  评论(0编辑  收藏  举报