OO第三单元总结

一、测试数据准备

JML规格可以有效避免自然语言的表达不准的问题。

JML规格中的前置条件至关重要,本单元中的前置条件会包括许多异常条件,所以在构造测试数据的时候应该完全覆盖各种条件,但是对于不同的测试点应该略有不同的侧重,有时侧重正确操作的测试,有时侧重异常抛出的测试。在本单元中我和三位同学进行了对拍测试,对拍测试可以有效地找到bug,提高测试点的正确率。

二、架构设计

合适的容器

第九次作业我采用Arraylist容器来进行数据存储,虽然数据查找的时候性能为O(n),但是并没有导致CTLE的情况出现,所以我也就没有修改成hashmap容器,第十次作业我原本采用Arraylist但是发生了CTLE的问题,在bug修复的时候我把数据统一放进了hashmap中,第十一次作业中只出现了未实现堆优化而导致CTLE的情况。

选择合适的容器并按照JML规格进行搭建基本上就不会出现什么bug

三、性能问题

性能优化

并查集

private HashMap<Integer, Integer> fa;
    private void merge(int x, int y, HashMap<Integer, Integer> fa) {
       int fx = find(x, fa);
       int fy = find(y, fa);
       if (fx != fy) {
           fa.put(fx, fy);
      }
  }
    private boolean judge(int x, int y, HashMap<Integer, Integer> fa) {
       return find(x, fa) == find(y, fa);
  }
    private int find(int x, HashMap<Integer, Integer> fa) {
       if (fa.get(x) == x) {
           return x;
      } else {
           return find(fa.get(x), fa);
      }
  }

第一次作业中的qci,若不采用并查集,前者的时间复杂度为O(n),后者更是由于调用前者,时间复杂度可以达到O(n^3);而采用并查集后,则可以将前者降为接近O(1),后者则为O(n),效果显著。

路径压缩,可以有效降低复杂度。

qbs指令我采用的是维护一个blocksum变量,大大缩短时间复杂度。

堆优化

通过PriorityQueue容器实现堆优化,可避免查找最小导致耗费时间复杂度.

Queue<Person> priorityqueue = new PriorityQueue<>();
    @Override
   public int compareTo(Person p) {
       return this.length - ((MyPerson)p).getLength();
  }
        priorityqueue.poll();
       neighbourNodesArray = min.getAcquaintance();
       neighbourLengthArray = min.getValue();
       int temp = min.getLength();
       for (int i = 0; i < neighbourNodesArray.size(); i++) {
           tempPerson = (MyPerson) neighbourNodesArray.get(i);
           newLength = temp + neighbourLengthArray.get(i);
           tempPerson.setLength(newLength);
           priorityqueue.add(tempPerson);
      }
       
       while (!priorityqueue.isEmpty()) {
           min = (MyPerson) priorityqueue.poll();
           if (min.isFind()) {
               continue;
          }
           min.setFind(true);
           neighbourNodesArray = min.getAcquaintance();
           neighbourLengthArray = min.getValue();
           temp = min.getLength();
           for (int i = 0; i < neighbourNodesArray.size(); i++) {
               tempPerson = (MyPerson) neighbourNodesArray.get(i);
               oldLength = tempPerson.getLength();
               newLength = temp + neighbourLengthArray.get(i);
               if (oldLength > newLength) {
                   tempPerson.setLength(newLength);
                   priorityqueue.add(tempPerson);
              }
          }
      }

四、扩展

假设出现了几种不同的Person

  • Advertiser:持续向外发送产品广告

  • Producer:产品生产商,通过Advertiser来销售产品

  • Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息

  • Person:吃瓜群众,不发广告,不买东西,不卖东西

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

/*@ public normal_behavior
     @ requires !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)) && (message.getPerson1() instanceof Advertiser);
     @ assignable messages;
     @ 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));
     @ ensures messages.length == \old(messages.length) + 1;
 @ also
     @ public exceptional_behavior
     @ signals (AdvertiserNotFoundException e)
     @ !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message))
     @ && !(messages[i].getPerson1() instanceof Advertiser);
     @*/
public void addAdvertise(/*@ non_null @*/Message message) throws AdvertiserNotFoundException;
/*  @ public normal_behavior
   @ requires (\exists int i; 0 <= i && i < list.length; list[i] == product)
   @ assignable nothing
   @ ensures \result = (\sum int i; 0 <= i && i < people.length;
@ (\sum int j; 0 <= j && j < people[i].poccess && people[i].poccess == product; 1));
   @ also
   @ public exceptional_behavior
   @ signals (ProductNotFoundException e) !(\exists int i; 0 <= i && i < list.length; list[i] == product)
   @*/
   public int trade(/*@ non_null @*/ Product product) throws ProductNotFoundException
/*@ public normal_behavior
 @ assignable getPerson(personId1).money, getTrader(personId2).getProduct(productId)
 @ requires contains(personId1) && contains(personId2);
 @ requires containsProduct(productId);
 @ requires containsTrader(personId2);
 @ ensures getPerson(personId1).money = \old(getPerson(personId1).money) - getProduct(productId).getValue;
 @ ensures getTrader(personId2).getProduct(productId) = \old(getTrader(personId2).getProduct(productId)) - 1;
 @ also
 @ public exceptional_behavior
 @ signals (PeronIdNotFoundException) !contains(personId1);
 @ also
 @ public exceptional_behavior
 @ signals (PeronIdNotFoundException) !contains(personId2);
 @ also
 @ public exceptional_behavior
 @ signals (TraderIdNotFoundException) !containsTrader(personId2);
 @ also
 @ public exceptional_behavior
 @ signals (ProductIdNotFoundException) !containsProduct(productId);
 @*/
public void purchase(int personId1, int personId2, int productId) throws PeronIdNotFoundException, TraderIdNotFoundException, ProductIdNotFoundException;

五、学习体会

熟练JML规格的语法、语义, 在JML规格的描述下,代码的需求变得十分明晰,不会像自然语言那样有二义性。掌握了并查集、迪杰斯特拉算法、对优化的具体运用, 在具体实现时,数据的实现方式、容器的选择、方法的实现算法都是值得仔细考察的内容, 经过整个单元的学习,我对JML规格有了更深的认识,并且在自动化测试方面有了长足的进步,JML终归是一种语言,是可以被替代的,但是JML所代表的规范格式与简练无二义性的表达方式是学习的核心。