oo第三单元作业总结

一、概述

  本单元主要内容为掌握JML规格理解与代码实现,理解JML规格在面向对象设计与构造中的重要意义,并掌握利用JML规格提高代码质量的能力;

为此我们根据官方提供的代码框架和部分函数的JML规格,实现了一个简单的社交关系模拟和查询系统,能模拟社交中的群组和消息功能,并实现其

相关操作。

二、测试数据生成

  采用随机生成数据的策略,利用python代码写了一个随机测试数据生成器,对两个不同的jar包的进行数据对拍测试。

  实际测试时发现随机生成的数据使得前后的随机消息无法体现系统中本该有的内在联系,比如一条消息的发出者和接受者都从不曾存在,导致大部分

测试数据的结果都是触发异常,无法有效且充分地测试到系统的核心功能;故而在后续版本的生成器中记录生成的Person、Group、Message的id,存入

待选池中,当需要生成与之相关的数据时则从池中随机取出。

  之后为提高测试强度以验证方法复杂度,控制了各类数据的生成数量比例,确保如sim、qgvs这样的关键数据集有足够的数量且尽可能覆盖更多的情况;

同时也可比较查询算法在边稀疏和边密集的不同情况下的效率,探究算法的优化。

三、架构设计

  系统主要功能涉及查询点是否联通、最小生成树、最短路径生成问题;

  采用并查集、克鲁斯塔尔算法、迪杰斯特拉算法以完成核心功能设计,自行定义了Side类和SideSet类以记录边信息,便于最短路径生成,以空间换时

间,减小了算法复杂度;主体框架与课程组提供的代码和JML规格一致。

四、性能分析

  对于并查集,采用缩短每个节点与根节点的路径以进行优化;每次进行find查询时,更新其父亲节点为根节点;在merge操作时将较矮的树嫁接到更深的

树上。

  对于克鲁斯塔尔算法,利用已经实现的并查集进行优化;对于迪杰斯特拉算法,自行定义了Side类和SideSet类以记录边信息,便于查询某个点所对应的

连通分支;以及识别是否有新的点或边加入某一联通分支,并对每一个联通分支的整体信息加以维护,使得每次调用迪杰斯特拉算法时不需要遍历整个图以得到

所求的联通分支。经实测,在边稀疏的情况下取得了明显优于未采用该策略的系统的查询速度。

五、bug分析

  第一次作业的bug在于qbs指令超时,由于直接采用了JML规格中每次调用都进行二重循环查询的策略导致超时,后通过为每一个Group维护valueSum和

valueSumIsValid变量大大减少了二重循环查询的调用次数以修复。

  第二次作业的bug在于qgvs指令超时,错因与第一次作业的bug一致,同样通过维护相关数据变量以解决,由此可见我并没有真正吸取前次作业的教训。

  第三次作业的bug依然在于qgvs指令超时,错因为每次维护变量的触发条件被hack,hack者通过每次查询都再次加边的方式使得对相关数据变量的维护

形同虚设;通过每次动态地维护数据变量,而非每次从头开始计数的方式,降低了维护变量的代价,得以解决超时问题。

六、NetWork扩展任务

(1)相关新增接口:

public interface Advertiser extends Person {
private ArrayList<Advertisement> advertisements;
public void sendAdvertisement(Advertisement advertisement, Person person);
public void getRequest(Advertisement advertisement);
public void sendRequest(Advertisement advertisement);
}
public interface Customer extends Person {
private ArrayList<Product> preference;
private ArrayList<Product> products;
private int money;
public void getAdvertisement(Advertisement advertisement);
public void buy(Advertisement advertisement);
}
public interface Producer extends Person {
private ArrayList<Product> products;
private ArrayList<Integer> sales;
private int money;
public void sale(Product product);
}

 

(2)核心业务功能接口方法的JML规格:

/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < products.length;
@ products.get(i).equals(product));
@ ensures money == \old(money) + product.getValue();
@ ensures (\exists int i; 0 <= i && i < products.length;
@ products.get(i).equals(product) && sales.get(i) += 1);
@*/
public /*@ pure @*/ void sale(Product product);
/*@ public normal_behavior
@ requires preference.contains(advertisement.getProduct());
@ ensures money == \old(money) - product.getValue();
@ ensures products.length == \old(products.length) + 1;
@ ensures (\exists int i; 0 <= i && i < products.length;
@ products.get(i).equals(product));
@ ensures advertisement.getAdvertiser().getRequest(advertisement);
@*/
public /*@ pure @*/ void buy(Advertisement advertisement);
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < advertisements.length;
@ advertisements.get(i).equals(advertisement));
@ ensures (\exists int i; 0 <= i && i < products.length;
@ products.get(i).equals(product));
@ ensures person.getAdvertisement(advertisement);
@*/
public /*@ pure @*/ void sendAdvertisement(Advertisement advertisement, Person person);

七、心得体会
  本单元主要内容在于掌握JML规格理解,学会利用JML来表达和辅助代码实现。在实践过程中发现下发了JML规格的代码在实现过程中确实基本不会
出现由歧义而产生的功能差异,但并不能保证方法复杂度符合要求;同时过长的JML规格十分难以阅读,个人觉得助教们要写出这么长的JML也实属辛苦了;
而且在已有相关JML规格后仍需要自行设计很多类和方法来实现JML规格的要求。个人觉得JML作为文字说明的替代品很难及时让阅读者对系统结构建立起一个
比较整体而形象的认知,由于JML的语法而导致其存在过多的括号嵌套也大大加大了阅读成本。总之,我觉得JML让写作者和阅读者都十分为难,但有时
相比于较模糊的自然语言,它的存在也是挺有必要的,建议将JLM和文字注释相结合,以减小阅读成本。

  在互测中,三次bug都为TLEbug,但是都并不是出在自己重点关注的指令上,根据我的了解,许多同学的bug也往往出于自己不曾预料到的代码细节部分;

由此可见本次作业要求实现的算法其实都较为简单,大家或许性能有所差异,但大体都可满足需求。真正的hack点还是在于代码细节,不同信息的发送实现、

以及某些需要维护,而并没有或没有以正确方式得到维护的数据变量;究竟如何才能对一个复杂的系统设计做到面面俱到,考虑充分呢?恐怕连许多充分的测试

也无法解决这个问题吧,oo课程的主体部分即将结束,而这个问题仍然将继续困扰着我。 

posted @ 2022-06-06 03:07  林则海纳  阅读(21)  评论(0编辑  收藏  举报