BUAA_OO第三单元总结(社交网络)

概述

本单元主要学习了JML规格的书写以及理解,通过提供的JML规格实现社交网络的一系列指令。其实就是相当于,把PDF的要求文档,改编成了更加形式化的,与代码结合的JML规格。

实现规格所采取的设计策略

相比之前以PDF文档的形式提供作业要求,JML给人最大的感受就是,它将要求融合在了代码之中,其实隐去了类与类之间,函数与函数之间的关系。而不像自然语言的文档,作者为了介绍清楚,往往需要叙述上述提及的关系。
 
这样看来,JML相比自然语言,提供了更多的细节,但是隐去了更加高层次的关系(继承关系,调用关系...)
 
经过这几次作业的书写经验,为了能够理清代码结构,最重要的就是首先通读一遍JML规格,同时在实现的时候,采用自底向上的策略,也就是从最原子的类开始,逐渐向上构造,能够帮助选择更好的数据结构,更高效地维护数据
 
例如首先构造Person类,可以在实现这一类地过程中了解到,该类型的属性,以及可以通过哪些方法来取得该类型元素,所以,自然而然地,在NetWork类中,你知道Person有通过id取得对象的方法,所以可以建立Hashmap建立id到Person对象的映射。
 
在最初构造的过程中,误以为必须严格遵循JML所提供的数组,如下:

public instance model non_null Person[] people;

再后来才了解到,JML的这种书写方式只是说,这是一个容器,而不代表具体的实现。

基于JML规格来设计测试的方法和策略

  • 最重要的一点,还是仔细阅读JML规格,避免理解偏差导致的低级错误
     
  • 其次,对于复杂函数,可以通过JUnit来进行测试,JUnit便于定位错误的位置,能够很快地确保函数地初步正确
     
    此处,我们以queryBlockSum()函数为例,进行Junit测试:
@Test
void queryBlockSum() {
    System.out.println("=========================");
    System.out.println("queryBlockSum Test Begin");
    System.out.println("=========================");
    Network network = new MyNetwork();
    Person a = new MyPerson(1,"a",1);
    Assert.assertEquals(network.queryBlockSum(), 1);
    Person b = new MyPerson(2,"b",1);
    Assert.assertEquals(network.queryBlockSum(), 2);
    Person c = new MyPerson(3,"c",1);
    Assert.assertEquals(network.queryBlockSum(), 3);
    Person d = new MyPerson(4,"d",1);
    Assert.assertEquals(network.queryBlockSum(), 4);
    Person e = new MyPerson(5,"e",1);
    Assert.assertEquals(network.queryBlockSum(), 5);
    network.addRelation(1,2,1);
    Assert.assertEquals(network.queryBlockSum(), 4);
    network.addRelation(3,4,1);
    Assert.assertEquals(network.queryBlockSum(), 3);
    network.addRelation(1,3,1);
    Assert.assertEquals(network.queryBlockSum(), 2);
}
  • 利用测试程序,生成随机数据进行对拍
    本单元,主要利用了python的networkx库中的函数,并利用python生成随机数据,调用java子进程进行了测试,可以很好地测试程序的整体功能,但是对于CPU_TLE的错误测试,有所欠缺
     
  • 构造极端数据进行测试
    第一次作业中,被通过加入一面包车人(addPerson),然后调用queryBlickSum()导致的CPU_TLE而hack
    在以后的测试中,可以通过对于复杂度较高的函数,构造大量的调用来测试是否会出现CPU超时的问题

容器选择和使用的经验

在本单元的作业中,JML规格仅仅给出了容器需要达成的功能,而没有指定具体需要使用的容器,单纯地跟随规格,傻傻地使用静态数组,显然是会爆炸(各种意义上),为了达成良好的性能和系统的可维护性,需要选择合适的容器存储数据。
 

  • 对于关联度高的数据(例如person.idperson)和需要进行同步化处理的数据(例如emojiidemojiheat)非常建议使用HashMap
    • HashMap可以达到更高的O(1)的查找效率,对于指令所需的大量查找操作,显然是非常好的
    • HashMap便于数据的维护,对于同步化处理的数据,可以通过一次更改,同时改变两个数据的值,因此,更容易维护,避免了手残
       
  • 临时的需要保持对应的数据,可以考虑Pair来保证数据对应
     
  • 对于需要维护FIFO的数据(Person.messages),考虑使用LinkedList,提供更好地插入和删除效率

性能问题

总体上,采用了HashMap,加速了查找效率
另外,对于以下函数,进行了特殊处理

queryGroupValueSum(), queryGroupAgeMean(), queryGroupAgeVar()

对于这三个函数,他们的共同特点是,计算的时候,需要遍历group,queryGroupValueSum()甚至需要双循环,如果每次调用都要重新计算的话,复杂度过高,因此调整为静态查询
 
例如,对于函数queryGroupAgeMean(), 在group对象的内部可以维护一个ageSum的整数,存储该group中总的年龄和,该变量,每次只需要在addToGroup()和delFromGroup()函数中进行更新,相比遍历group的动态查询,效率更高,优化后的queryAgeMean()函数如下

    @Override
    public int getAgeMean() {
        int sz = people.size();
        if (sz == 0) {
            return 0;
        }
        return (int) (ageSum / sz);
    }

对于queryGroupAgeVar(),更改方差的计算方式如下
设年龄的平方和为squreSum,年龄的平均值为mean,group中的总人数为sz,group年龄和为ageSum,得到方差计算公式如下:

(squreSum - 2 * ageSum * mean + sz * mean * mean) / sz

queryBlockSum()

该函数最初通过BFS实现的,即计算遍历图所需的BFS次数,该值即为连通块的数目,但是出现了CPU的超时问题。随后改为了通过查找并查集的组数,计算连通块的数目

作业架构设计

异常类

  • 首先,为每个异常类,设置了HashMap<Integer,Integer> cntManager类静态属性,维护每个id的异常出现次数并更新
  • 设置了int cnt类静态变量,维护该异常出现的总次数
    MessageIdNotFoundException为例
public class MyMessageIdNotFoundExc extends MessageIdNotFoundException {
    private int id;

    private static HashMap<Integer, Integer> cntManager = new HashMap<>();
    private static int cnt = 1;

    public MyMessageIdNotFoundExc(int id) {
        this.id = id;
    }

    @Override
    public void print() {
        if (!cntManager.containsKey(id)) {
            System.out.printf("minf-%d, %d-%d\n", cnt, id, 1);
            cntManager.put(id, 2);
        } else {
            int cnt1 = cntManager.get(id);
            System.out.printf("minf-%d, %d-%d\n", cnt, id, cnt1);
            cntManager.put(id, cnt1 + 1);
        }
        cnt++;
    }
}

UML类图

数据维护

主要在MyNetwork中维护一个并查集,以及通过HashMap维护各种数据

  • Person
    • private HashMap<Integer, Integer> acq维护联通的Person结点
  • Network
    • private HashMap<Integer, Person> people维护id和Person的对应关系
    • private HashMap<Integer, Group> groups; // <id, group>维护id和group的对应关系
      以上仅举了几个例子。

一点想法

在本单元作业中,JML规格经历了非常频繁的修改,这是在以前的作业中没有出现过的,个人感觉,这也能够反映一点,JML当前的可用性依然不是很好,自己在阅读的过程中相比一般的文档,比较有困难,并没有感受到JML形式化所带来的益处。

posted @ 2021-05-29 17:04  HyperCarrot  阅读(93)  评论(1编辑  收藏  举报