BUAA_OO_Unit3 单元总结

BUAA_OO_Unit3 单元总结

测试方法:

在本单元的测试中,我采取的测试方法主要分为黑盒与白盒测试两种类型。黑盒测试的主要模式就是通过自动数据生成器产生随机数据,然后将结果与多个同学对拍以发现代码中的bug。这样的好处是数据遍历性较广,可以找出一些难以想到的bug,但是自动生成的数据一般条数较多,在后续定位复现的过程比较耗时。在此基础上,我结合了白盒测试的思想,针对自动测试中出现问题的指令来仔细阅读JML规格,然后针对规格的各种可能情况构建较为简单的数据,从而更好的找到bug。

架构分析:

整体的架构其实已经由源代码设计好,大部分指令只需要按照JML的规格进行编写就可以。但是针对一些较为复杂的指令,完全按照JML来翻译编写,要么直接实现难度较大,要么是实现的时间代价过高,因此我在三次作业中进行了一些额外建模来优化指令代价。

第一次作业:

针对MyNetWork中的isCircle、queryBlockSum两条指令,我建了一个新的类MyMap来维护并查集。

public class MyMap {
   private final HashMap<Integer, Integer> unions;
   private final HashMap<Integer, Integer> roots;

   public MyMap() {
       unions = new HashMap<>();
       roots = new HashMap<>();
  }

   public void addPoint(int id) {
       unions.put(id, id);
       roots.put(id, 1);
  }

   public void addEdge(int id1, int id2) {
       merge(findRoot(id1), findRoot(id2));
  }

   public int findRoot(int id) {
       int nowId = id;
       int father = unions.get(id);
       while (nowId != father) {
           nowId = father;
           father = unions.get(nowId);
      }
       return nowId;
  }

   public void merge(int root1, int root2) {
       int count1 = roots.get(root1);
       int count2 = roots.get(root2);
       if (root1 != root2) {
           if (count1 < count2) {
               unions.put(root1, root2);//root2 is the father of root1
               roots.put(root2, count1 + count2);
               roots.remove(root1);
          } else {
               unions.put(root2, root1);//root1 is the father of root2
               roots.put(root1, count1 + count2);
               roots.remove(root2);
          }
      }
  }

   public int getRootsNum() {
       return roots.size();
  }
}

其中进行了一个优化为在合并两个并查集时,会将所含结点更多的根节点作为另一个根节点的父节点,以减少整个树的深度。

第二次作业:

本次作业中的query least connection指令需要我们找到最小生成树,因此在之前的MyMap类中进行了边集的维护(以更好的找到最小生成树)

private final HashMap<Integer, ArrayList<MyEdge>> edges;

public int getMinTree(int id) {
       HashMap<Integer, Integer> unions = new HashMap<>();
       int root = findRoot(id, this.unions);
       int value = 0;
       ArrayList<MyEdge> myEdges = edges.get(root);
       for (MyEdge myEdge : myEdges) {
           int id1 = myEdge.getBegin();
           int id2 = myEdge.getEnd();
           if (!unions.containsKey(id1)) {
               unions.put(id1, id1);
          }
           if (!unions.containsKey(id2)) {
               unions.put(id2, id2);
          }
           int root1 = findRoot(id1, unions);
           int root2 = findRoot(id2, unions);
           if (root1 != root2) {
               //set root2 the father of root1
               unions.put(root1, root2);
               value += myEdge.getValue();
          }
      }
       return value;
  }

最小生成树算法采取了并查集的优化,整体最小生成树算法采取的是Kruskal算法。

第三次作业:

本次作业中的send indirect message需要找到一个Group里面的最短路径。整体实现采取了使用了堆优化的Dijkstra算法。

private final HashMap<Integer, Boolean> vis;
private final HashMap<Integer, Integer> dis;
private final HashMap<Integer, ArrayList<MyEdge>> findEdges;

public int getMinPath(int begin, int end) {
       int root = findRoot(begin, unions);
       ArrayList<Integer> myPoints = points.get(root);
       PriorityQueue<MyPoint> priQueue = new
               PriorityQueue<>(Comparator.comparingInt(MyPoint::getDis));
       int value = -1;
       for (Integer id : myPoints) {
           if (id == begin) {
               dis.replace(id, 0);
               MyPoint myPoint = new MyPoint(id, 0);
               priQueue.add(myPoint);
          }
           else {
               dis.replace(id, maxValue);
          }
           vis.replace(id, false);
      }
       while (!priQueue.isEmpty()) {
           MyPoint point = priQueue.poll();
           int id = point.getId();
           boolean isVis = vis.get(id);
           if (!isVis) {
               vis.replace(id, true);
               for (MyEdge edge : findEdges.get(id)) {
                   int linkId = edge.getBegin();
                   if (linkId == id) {
                       linkId = edge.getEnd();
                  }
                   if (!vis.get(linkId)) {
                       int newDis = dis.get(id) + edge.getValue();
                       if (dis.get(linkId) > newDis) {
                           dis.replace(linkId, newDis);
                           MyPoint myPoint = new MyPoint(linkId, newDis);
                           priQueue.add(myPoint);
                      }
                  }
              }
               if (id == end) {
                   value = point.getDis();
                   break;
              }
          }
      }
       return value;
  }

本次代码的编写利用到了java中自带的PriorityQueue来实现堆优化,带来了很大的便利性。

性能问题与修复情况:

三次作业中,我出现的bug为在部分指令反复调用时没有进行数据记录,而是反复进行重复计算,导致了超时;另外就是初版的Kruskal算法与Dijkstra算法优化做的不够到位,导致了cpu运行时间超时。

NetWork扩展

要求:

假设出现了几种不同的Person

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

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

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

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

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

实现:

Advertiser、Producer、Customer三个类均继承Person类,Advertiser类中包含有Advertise的实例化对象以及它的id,Producer类可以生产Product对象,Product对象中包含了价值与id两个属性。

发布广告信息:

/*@ public normal_behavior
@ requires containsAdvertiserid(Advertiserid) && containsId(personid) && getperson(Advertiseid).isLinked(personid);
@ assignable getperson(personid).getadvertise;
@ ensures getperson(personid).getadvertise.size() = \old(getperson(personid).getadvertise.size()) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(getperson(personid).getadvertise.size()); getperson(personid).getadvertise[i+1] == \old(getperson(personid).getadvertise[i]));
@ ensures (getperon(personid).getadvertise[0] == newadvertise);
@ also
@ public exception_behavior
@ assignable nothing
@ requires !containsAdvertiseid(Advertiseod) || !containsid(personid) || !getperson(Advertiseid).isLinked(getperson(personid));
@ signals (PersonIdNotFoundException e) !containsAdvertiseid(Advertiseod);
@ signals (PersonIdNotFoundException e) !containsid(personid);
@ signals (RelarionNotFoundException e) !getperson(Advertiseid).isLinked(getperson(personid));
@*/
public void sendadvertise(int Advertiserid, int personid) throws PersonIdNotFoundException, RelarionNotFoundException;

向顾客发送信息:

/*@ public normal_behavior
@ requires containspersonid(personid)
@ assignable getcustomer(personid).itsproducts;
@ ensures (\forall int i; i >= 0 && i <= advertise.size(); getcustomer(personid).itsproducts.add(advertise2produce(advertise[i])));
@ ensures itsproducts.size() == \old(itsproducts.size()) + advertise.size();
@ ensures advertise.size() == 0;
@ public exception_behavior
@ assignable nothing
@ requires !containspersonid(personid)
@ signals (PersonIdNotFoundException e) !containspersonid(personid);
@*/
public void sendmessageToCustomer(int personid) throws PersonIdNotFoundException;

查询商品销售额:

/*@ public normal_behavior
@ requires containsproid(proid);
@ ensures \result == getpro(proid).getvalue;
@ also
@ public exception_behavior
@ requires !containsproid(proid);
@ signals (ProductionidNotFoundException e) !containsproid(proid);
public /*pure*/ int getvalueodproduction(int proid) throws ProductionidNotFoundException;

体会感想:

这个单元我们接触到了JML规格与契约式编程,表面上这个单元的思考难度下降了很多,因为大部分代码的编写只需要照着规格翻译编写即可。这也让我认识到了在多人协作的任务之中,拥有一种标准化的语言规范的重要性,这能够帮我们解决掉很多自然语言的二义性,准确的传答出程序的目的性与限制之处。

但是在本单元的程序编写中,我也难免会出现一些bug,经过观察发现大部分bug主要是代码性能不够好所致。因此我们不难发现JML只是确保了程序的正确性,对于程序的高性能的追求,是我们在规格之下的自由之处,也是我们需要努力学习做到的。

posted @ 2022-06-06 10:38  wodsk  阅读(20)  评论(0编辑  收藏  举报