OO第三单元作业总结

OO第三单元作业总结

一、综述

  第三单元的主要内容以及核心就是根据JML规格来补充相应的功能代码,建立一个包含人,消息,群组的复杂社交网络。而JML的指导作用在这三次作业中体现地淋漓尽致,也让我明白了规格化设计的强大之处。JML规格包括常规行为和异常行为,而在每个行为的背后由前置条件,副作用,以及后置条件组成。前置条件用requires表示,就是函数发挥作用的前提,没有这个前提,函数不会发挥出任何作用,而后置条件,用ensures表示,代表前提条件满足后函数运行的结果。而副作用是一个特殊的JML语句,它的表示是assignable,而它的后面会带着一系列变量,意思就是在运行这个函数之后,这些非运行函数所携带的固有变量会发生改变,而如何改变就要看后置条件的描述了。接下来我将描述如何测试以及三次作业的图模型构建以及维护策略。

二、测试分析

  本单元的测试与前2个单元有所不同。如果说前2个单元是靠题目的种种约束构造测试数据,那么这个单元就非常依赖JML规格来准备测试数据。

  首先每种函数有两个行为,就是常规行为和异常行为。那么我们就可以分类进行数据构造。需要注意的是,异常数据的生成也必然包含着常规数据的产生,因此,异常数据的随机性要更大,而常规行为则蕴含着更大的限制。因此,我们需要使用不同的数据生成器来生成这两类数据。

  在测试时,性能是非常重要的一点需要关注,因此在观察每个函数的JML规格时,如果发现一个函数的复杂度到达了O(n方)就很有可能会发生CPU超时的问题。因此,针对这类函数,在构造数据的时候可以刻意一次构造出上百条相同的指令来运行这个函数并判断是否会超时。当然还有一点基础的问题要注意,那就是我们需要先用ap,ar等基础指令先构建出一个复杂的社交网络,再去针对各个运算指令来进行构造,才能达到最好的测试效果。

  同时,有一些方法相对难以实现,例如说qbs,qlc等等指令,这时就可以使用助教推荐的工具JUNIT了,这还是十分好用的,这样相比于前两个单元的数据测试,这个单元的数据测试就相对来说容易许多。对拍其实相对来说反而重要性要有所下降,因为这个单元有JML指导函数的编写,代码的底层逻辑一般来说不会出现错误,反而是性能超时的问题非常严重,因此一个好用的数据生成器就显得非常重要,可以帮助我们规避掉很多不必要的错误。

三、三次作业的图模型构建以及性能维护分析

1、第一次作业

  第一次作业主要是为了熟悉JML规格的阅读和相关底层函数的编写,因此,我创建出了社交网络相关的一系列类,包括主函数,MyNetwork,MyGroup和Person,以及6个异常类。编写这些类函数基本都很顺利,但第一次作业主要也是为了对JML的规格理解更深,所以难度相对容易。

第一次作业UML图:

 

 

 第一次作业性能维护分析:

  第一次作业的类函数虽然很好写,但有两个指令如果要使用优化算法编写其实还是非常困难,那就是qbs以及qci。qbs的目的是查询连通块的数量,qci则是要查询两个人是否在一个连通块中。其中qbs在JML规格中还使用到了qci,而qci如果没有优化,本身使用DFS或者BFS的复杂度就非常高,容易超时,qbs那就更加复杂,嵌套之下耗时过长,基本必定会超时。因此必须要对这两个函数进行优化。首先要优化qci,这里就使用的是助教提出的方法,也就是并查集,同时还要进行路径压缩来优化并查集算法,同时还要注意的是,如果使用递归实现并查集就很容易爆栈,因此我个人使用了迭代的方法,借助while循环来防止函数的递归调用,这样qci的耗时就会非常之短。要优化qbs,重要的是不能让qbs使用qci,因此我个人就使用了一个新的变量来维护连通块的数量,并且使用了并查集来存储所有人之间的关联。当加入新的人时,连通块数量加一,而当加入新的关系时,使用并查集判断两个人是否位于同一连通块,如果是,那就让连通块数量减一,当使用qbs的时候,就可以直接返回连通块的数量而不需要耗费任何时间。因此第一次作业的重点就是要维护人之间的连通关系以及连通块的数量,并且使用并查集来使得性能达到最优。

 2、第二次作业

  第二次作业添加了Message类,并且添加了很多新的方法在Network中,可以说使得整个社交网络系统变得更加复杂,当然,相对来说还是比较容易。需要注意的一点是,MyMessage类需要包含两个构造函数,这两个函数分别代表构造不同类型的消息,一个是人人之间的关系,一个是人和群组之间的关系。当然最难的还是qlc指令,也涉及到最小生成树的构建。

第二次作业UML图:

 

 

 

 第二次作业性能维护分析:

  第二次作业首先需要维护的性能就是qlc指令,也就是最小生成树的算法的优化。如果使用普通的Kruskal算法,那么一定会超时。因此我采用的是经过并查集优化的Kruskal算法,也就是说,我可以借用我第一次作业使用的并查集qci,来帮助构造最小生成树。当然,事情没那么简单,我们仍需要维护一个列表,而这个列表就是人与人之间的关联,再添加关联时将信息存入到这个列表中,也就是记录社交网络图中的每一条线,最后还是比较顺利地完成了qlc的编写。同时第二次作业还有一个指令的性能需要关注,那就是qgvs。qgvs的优化十分容易被忽略,但却非常重要。在最底层的Person类中,最好使用HashMap来维护熟人和价值之间的关系,HashMap无疑可以极大地提高查找地性能。同时在计算价值之和时,我们会发现两个不同人之间的价值被算了两遍,所以只需要计算一遍即可,可以减少一半的计算量,对性能优化极为重要。

3、第三次作业

  第三次作业与第二次作业类似,在原来的Message类上又分为了三种具体信息类,例如红包信息等等,这些信息也有自己独特的属性。同时第三次作业最难的指令也就是sim,具体考察的便是最短路径算法,我将在性能分析时详细解读。

第三次作业UML图:

 

 

 第三次作业性能维护分析:

  第三次作业的性能问题主要表现在sim的最短路径算法上。最短路径算法的优化不比最小生成树,非常复杂,不仅要使用堆结构进行优化,还要使用java自带的一个类,中文名叫做优先队列。通过优先队列来维护堆结构非常重要,因为优先队列不需要完全的排序,只需要找到其中最短的距离即可。所以,我又创建了一个类Edge来维护消息的价值,将它当作图中的一条线来看待创建优先队列,进行排序,才能最终实现堆优化的最小路径算法。

四、bug分析

1、第一次作业

  第一次作业就遇上了超时的bug,也就是qbs和qci的优化问题,我使用BFS导致超时,最终在助教的指导下完成了对算法的性能优化。具体性能分析见上。

2、第二次作业

  第二次作业吸取了教训,优化了最小生成树的算法,但是却出现了另外的bug,就是在计算平均年龄和方差年龄时除以了人的总数,但是当总数为0时就产生了问题,导致了RunError,而导致这哥bug的原因就是我没有仔细看JML规格,这也让我意识到了JML规格的指导可以避免很多不必要的bug,即使已经理解的函数的功能也要仔细参考。同时我也发现了其他人qlc的计算漏洞bug。

3、第三次作业

  第三次作业的bug就是最短路径算法的问题,由于没有使用优先队列,即使我尽可能使用堆进行优化,但结果仍然是超时,因为没有优先队列维护排序,就不可能称之为真正堆优化算法。最终我还是使用了优先队列解决这个问题。

五、Network拓展

拓展需要:

假设出现了几种不同的Person

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

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

拓展结果:

  首先由题目可以得出,最重要的一点就是需要创建一个新的类,叫做产品Product,而产品类在Network里就可以继承Message类,而产品的属性很多Message都已经包括,但还是需要新增L两个属性,也就是生产者和广告商的信息,因为三次作业中只有2种基础构造函数信息,一种是2个人之间的信息,一个是人在群组的信息,而这个信息在购买之前只有生产者没有消费者,再卖出去后产生购买信息,产品信息的属性才能补充完整。同时Advertiser,Producer,以及Customer都需要继承Person类,来代表三种特别的人类。

  而需要新增的方法非常多,有Advertiser的收广告,发广告,发购买信息方法,Producer的生产产品方法,Customer的收广告以及发购买信号方法。以下是三个核心方法的JML规格。

Producer的生产产品方法produce:

public void produce(Product product);
/*@ public normal_behavior
  @ requires !(\exists int i; 0 <= i && i < products.length; products[i].equals(product));
@ assignable products;
@ ensures products.length == \old(products.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(products.length);
@ (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i]))));
@ ensures (\exists int i; 0 <= i && i < products.length; products[i] == product);
@ also
@ public exceptional_behavior
@ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < products.length;
@ products[i].equals(product));
@*/

Advertiser的发广告方法sendProduct:

public void sendProduct(Product product,Customer customer);
/*@ public normal_behavior
  @ requires (\exists int i; 0 <= i && i < products.length; products[i].equals(product));
@ assignable products;
@ ensures products.length == \old(products.length) - 1;
@ ensures (\forall int i; 0 <= i && i < \old(products.length && products[i]!=product);
@ (\exists int j; 0 <= j && j < products.length; products[j] == (\old(products[i]))));
@ ensures !(\exists int i; 0 <= i && i < products.length; products[i] == product);
@ also
@ public exceptional_behavior
@ signals (ProductIdNotFoundException e) !(\exists int i; 0 <= i && i < products.length;
@ products[i].equals(product));
@ also
@ public normal_behavior
  @ requires !(\exists int i; 0 <= i && i < customer.products.length; customer.products[i].equals(product));
@ assignable customer.products;
@ ensures customer.products.length == \old(customer.products.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(customer.products.length);
@ (\exists int j; 0 <= j && j < customer.products.length; customer.products[j] == (\old(customer.products[i]))));
@ ensures (\exists int i; 0 <= i && i < customer.products.length; customer.products[i] == product);
@ also
@ public exceptional_behavior
@ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < customer.products.length;
@ customer.products[i].equals(product));
@*/

Customer的购买方法purchase:


public void purchase(Product product);
/*@ public normal_behavior
  @ requires (\exists int i; 0 <= i && i < customer.products.length; customer.products[i].equals(product));
@ assignable customer.products;
@ ensures customer.products.length == \old(customer.products.length) - 1;
@ ensures (\forall int i; 0 <= i && i < \old(customer.products.length && customer.products[i]!=product);
@ (\exists int j; 0 <= j && j < customer.products.length; customer.products[j] == (\old(customer.products[i]))));
@ ensures !(\exists int i; 0 <= i && i < customer.products.length; customer.products[i] == product);
@ also
@ public exceptional_behavior
@ signals (ProductIdNotFoundException e) !(\exists int i; 0 <= i && i < customer.products.length;
@ customer.products[i].equals(product));
@ also
@ public normal_behavior
  @ requires !(\exists int i; 0 <= i && i <product.advertiser.buy.length; product.advertiser.buy[i].equals(product));
  @ assignable product.advertiser.buy;
@ ensures product.advertiser.buy.length == \old(product.advertiser.buy.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(product.advertiser.buy.length);
@ (\exists int j; 0 <= j && j < product.advertiser.buy.length; product.advertiser.buy[j] == (\old(product.advertiser.buy[i]))));
@ ensures (\exists int i; 0 <= i && i < product.advertiser.buy.length; product.advertiser.buy[i] == product);
@ also
@ public exceptional_behavior
@ signals (EqualProductIdException e) (\exists int i; 0 <= i && i < product.advertiser.buy.length;
@ product.advertiser.buy[i].equals(product));
@*/

六、学习体会

  在这个单元,我学会了JML编程规格的阅读和编写。让我感受最深的是,在我前两个单元以及之前编写代码时,总是会在一些莫名奇妙的地方卡住,思路无法动弹。原因就是我的代码思路不清晰,总是想到哪写到哪,没有统筹规格的意识。而在学习了JML规格后,根据助教提供的JML规格,我的代码写起来十分流畅,除了一些复杂的算法之外几乎没有任何障碍,所以,JML规格的重要性不言而喻。这对我以后编写代码也可以起到非常重要的帮助。当然需要注意的是,有时JML规格反而会使得代码更难以理解,因为有些函数的逻辑过于复杂,因此JML规格并不是任何时候都是代码编写的最优解,需要酌情使用。同时这个单元我也锻炼了图里各种算法的编写,对算法的理解可以说是更上一层楼,同时对数据测试也更加游刃有余。希望在接下来的OO学习中可以再接再厉,继续努力。

posted @ 2022-06-06 15:50  谷小来  阅读(33)  评论(0编辑  收藏  举报