BUAA_2022_OO_第三单元总结

利用JML规格构造自测数据

在第三单元的学习之后,我认为JML语言就是一种十分精密的语言,它可以避免使用自然语言描述代码功能时产生的模糊性、二义性,因此可以成为需求方(甲方爸爸)与实现方(码农)之间一种可靠的交流语言。
此外,JML语言还有描述具体、逻辑性强的特点。对于每一个方法,JML语言明确定义了前置条件和后置条件,由此严格规定了方法的各个输入域及其对应的操作正确性判定,通过确定副作用范围严格规定了方法的影响范围。正是这样的“协议”使得在实现代码时只需要关注某个方法本身,实现了底层实现与外部使用的隔离。

根据以上关于JML语言的分析,我给出构造自测数据的思路如下:

  1. 首先根据方法的前置条件划分的问题域,将一个大方法拆解成多个“小方法”,分别进行测试。
  2. 根据前置条件对问题域的描述与限制,保证构造出的数据对于问题域的典型数据、边缘数据实现全覆盖。
  3. 根据不同问题域的后置条件判断方法的正确性在。
  4. 注意方法实现过程中是否超出副作用范围或破坏不变式。

架构设计与图模型构建和维护策略

在架构设计与迭代方面,三次作业都以接口要求为基础:将MyPerson抽象为点集,根据其中acquaintance和value属性抽象出边集,在此基础上管理整个图。之后的每次迭代都只是增加一些交互与查询方法。

除官方接口的要求外,我还以分支为单元实现了对图模型的构建与管理,建立MyLinkedMap类,存储一个分支(最大强连通子图),它的主要属性如下:

private HashMap<Integer, Person> people;
private ArrayList<MyRelation> relations;

维护方法:

  1. MyNetwork类中新增person时,新建一个MyLinkedMap对象,其中只有一个人,没有关系。
  2. 新增relation时,如果relation连接的两个person属于不同的分支,则将两个分支合并:people、relation绝对无重复,直接合并即可,在新分支中加入新增的relation。

性能问题与优化

容器选择

  • 在前两次作业中,MyNetwork、MyGroup中对类的管理都使用Arraylist容器,导致查询时总要遍历,复杂度O(n),在最后一次作业中察觉到ID的唯一性使用Hashmap管理person,降低复杂度。
  • 使用PriorityQueue容器可以以O(1)复杂度取出按照自定义方式排序后的最小元素。

算法选择

  • 基本原则:选择的算法复杂度尽量低[最好低于O(n²)]
  • qci 使用并查集取代广度遍历

动态维护

  • 当改变结构时维护数据的复杂度低于每次查询计算时
  • qbs 维护最大强连通子图
  • qgvs 增加减少group人数、增加关系时维护

数据缓存

  • 当没有复杂度较低的算法且维护成本较高时,使用一boolean值标志是否需要重新计算
  • qlc 当最大强连通子图改变时,采用kruskal算法更新其最小生成树边权之和

扩展Network

  • 新增 Producer、Advertiser、Customer 接口并继承 Person
  • 新增 ItemMessage接口并继承 Message,用于存储商品相关信息和表示购买行为

三个核心方法如下:

/*@ public normal_behaviour  
  @ requires !producer.hasItem(item), item.producer == null;  
  @ assignable producer.items, item.producer;  
  @ ensures producer.items.length = \old(producer.items.length) + 1;  
  @ ensures producer.hasItem(item);  
  @ ensures (\forall int i; 0 <= i && i < \old(producer.items.length);  
  @ (\exists int j; 0 <= j && j < producer.items.length; producer.items[j] == (\old(producer.items[i]))));  
  @ ensures item.producer == producer;
  @ also   
  @ public exceptional_behaviour  
  @ signals (DuplicatedItemException e) producer.hasItem(item);  
  @ also   
  @ public exceptional_behaviour  
  @ signals (ProducedItemException e) !producer.hasItem(item) && item.producer != null;  
  @*/  
void produce(Producer producer, ItemMessage item);  

/*@ public normal_behaviour
  @ requires !advertiser.hasItem(item) && !(\exists int i; 0 <= i && i < ADV.length; ADV[i] == item) && item.advertiser == null;
  @ assignable advertiser.items, item.advertiser, ADV;
  @ ensures advertiser.items.length = \old(advertiser.items.length) + 1;
  @ ensures advertiser.hasItem(item);
  @ ensures (\forall int i; 0 <= i && i < \old(advertiser.items.length);
  @ (\exists int j; 0 <= j && j < advertiser.items.length; advertiser.items[j] == (\old(advertiser.items[i]))));
  @ ensures ADV.length = \old(ADV.length) + 1;
  @ ensures (\exists int i; 0 <= i && i < ADV.length; ADV[i] == item);
  @ ensures (\forall int i; 0 <= i && i < \old(ADV.length);
  @ (\exists int j; 0 <= j && j < ADV.length; ADV[j] == (\old(ADV[i]))));
  @ ensures item.advertiser == advertiser;
  @ also 
  @ public exceptional_behaviour
  @ signals (DuplicatedItemException e) advertiser.hasItem(item) || (\exists int i; 0 <= i && i < ADV.length; ADV[i] == item);
  @ also   
  @ public exceptional_behaviour  
  @ signals (AdvertisedItemException e) !advertiser.hasItem(item) && !(\exists int i; 0 <= i && i < ADV.length; ADV[i] == item) 
  @ && item.advertiser != null;  
  @*/
void advertise(Advertiser advertiser, ItemMessage item);

/*@ public normal_behaviour
  @ requires (\exists int i; 0 <= i && i < ADV.length; ADV[i] == item) && !customer.hasItem(item);
  @ assignable customer.items, ADV;
  @ assignable customer.money, item.getProducer().money;
  @ ensures customer.items.length = \old(customer.items.length) + 1;
  @ ensures customer.hasItem(item);
  @ ensures (\forall int i; 0 <= i && i < \old(customer.items.length);
  @ (\exists int j; 0 <= j && j < customer.items.length; customer.items[j] == (\old(customer.items[i]))));
  @ ensures ADV.length = \old(advertiser.items.length) - 1;
  @ ensures !(\exists int i; 0 <= i && i < ADV.length; ADV[i] == item);
  @ ensures (\forall int i; 0 <= i && i < ADV.length;
  @ (\exists int j; 0 <= j && j < \old(ADV.length); ADV[i] == (\old(ADV[j]))));
  @ ensures customer.money = customer.money - item.getPrice();
  @ ensures item.getProducer().money = item.getProducer().money + item.getPrice();
  @ also 
  @ public exceptional_behaviour
  @ signals (ItemNotFoundException e) !(\exists int i; 0 <= i && i < ADV.length; ADV[i] == item)
  @ also 
  @ public exceptional_behaviour
  @ signals (DuplicatedItemException e) (\exists int i; 0 <= i && i < ADV.length; ADV[i] == item) && customer.hasItem(item);
  @*/
void purchase(Customer customer, ItemMessage item);

学习体会

本单元主要学习了JML规格,了解了契约化编程的基本思想。在工程中使用JML规格取代自然语言有着相当明显的优势:

  1. JML的定义更加严格准确,解决了自然语言模糊性的问题。
  2. JML将底层实现与外部调用分隔开来,使得代码目标更加清晰,专注于某个特定的性能。

此外,复习了图的相关知识,但在做作业的过程中,我的更多精力放在了优化算法而非理解JML规格,感觉JML规格或许不需要四周一单元的时间来学习。

最后,学会了如何用 JUnit 进行单元测试,测试的目标更加明确,可以针对复杂的算法方便地构造测试数据。

posted @ 2022-06-04 23:37  现充宅  阅读(14)  评论(0编辑  收藏  举报