BUAA 2022 OO 第三单元总结报告
BUAA 2022 OO 第三单元总结报告
一、JML规格和测试数据的准备。
本单元的任务主要是根据给出的JML规格实现相应的代码。在进行测试时一方面是对每一个方法调用时各个选择分支的进入测试,另一方面是由于人际关系网使用到了图论的相关知识和算法,针对算法的复杂度和运行效率需要进行大数据规模下的测试。
1.对于不同的选择分支的测试,使用Junit自动生成测试框架,之后对于各种抛出异常的情况和不同的分支情况编写测试样例,尽量做到每一个方法的各种分支都进行了测试。
2.对于一些需要用到图论的知识和算法的method,需要生成一定规模的图对其进行测试,这里我使用python编写自动生成指定规模的图,并将其转化为相应的ap、ar指令,之后和隔壁的小伙伴跑同一条数据对拍来测试方法的正确性。
二、架构设计
unit3后面的两次作业需要的代码量不断增加,因此导致我的MyNetwork类超出了500行的限制,因此把方法内的具体实现服务放在了工具类里。基本框架和Jml要求一致,具体给出第三次作业的框架如下:
图模型的构建:
在我们实现的MyNetwork中,每一个person是一个节点,而person之间的relation则是边。在Myperson中的acquaintance属性则表示了一个节点可以到达的其他节点,相当于是一个邻接表。但是由于一些算法,比如求最小生成树时的Prim算法,需要直接获取边的信息,所以我新建了一个Edge类来储存一下边的信息,就不需要多次访问Person获取acquaintance或者访问Person的queryValue方法了。
维护策略:
对于数据的维护,因为我第一次作业使用的Arraylist后面造成了一些查询时的性能问题,所以在后期将一些频繁查询的数据换成了使用HashMap储存。其实每一个对象都有唯一一个id也提示了我们使用HashMap储存会更加的方便,否则也不会在后期测试由于性能问题出现cup超时的暴雷了。
三、代码实现出现的性能问题和修复情况
代码性能问题主要集中在几个和图有关的操作上:第一次作业的isCircle、queryBlockSum中查询连通性、第二次作业的queryLeastConnection中求最小生成树、第三次作业的sendIndirectMessage求单源最短路径。
1.第一次作业:查询连通性
最初出现的性能问题是由于查询连通性我在一开始使用了深度优先遍历,但是在强测以及hack中惨遭毒手,遂在bug修复环节改成了并查集。
并查集的简单优化:每一次搜索的时候,将被搜索的节点以及沿途经过的节点均连接到根节点上。这样再次访问沿途节点的时候,用时会减少,优化了时间复杂度。
2.第二次作业:最小生成树、qgvs
对于最小生成树我采用的是Prim算法,并没有出现性能的问题。但是qgvs指令出现了性能的问题,原因是在Mygroup计算value sum过程中使用了很多次iscircle的判断,而iscircle虽然使用了并查集,但是如果数据量很大,依然比较慢,造成了cpu超时
所以在bug修复阶段,将qgvs的实现由qgvs时实现,改成了在addPerson和delPerson的时候动态更新,降低了查询时的时间开销。
3.第三次作业:单源最短路径
对于单元最短路径我直接实现了Dijkstra算法。但是在强测和互测中依然被卡了cpu。经过一天艰难的debug过程,发现bug原因不在于Dijkstra的算法,而在于前面我使用iscircle找出所有与person相关的人的时候,iscircle算法的速度太慢,归根到最后是containPerson中使用的是遍历整个Arraylist<Person>的方法,造成了这一部分的时间复杂度几乎上升到了O(n^3),遂直接改为用HashMap储存。第一次作业遗留的一个大锅直接影响到了第三次作业。
Hack阶段第一次作业hack到了几个同学的qbs,第二次作业也是hack到了3个同学的qlc,我提交的数据没有卡cpu的运行时间,所以可能是其算法实现上有问题。
四、扩展Network
我认为完成这个模型的扩展需要新增如下几个类:
1.Product:
功能:表示这是一个产品 属性:
/*@ public instance model int id; // 每个产品的id唯一 @ public instance model int price; // 价格 @ public instance model int producerId; // 生产商的id @ public instance model int capability; // 表示产品的性能 @ public instance model int sales; // 表示产品的销量 @*/
2.Advertiser:继承自原Person类
功能:持续向外发送产品广告 属性:需要新增一个Product[] products,表示可以提供购买服务的产品 方法:需要新增一个haveProducts(int id)方法,返回值为boolean查询是否有id对应的商品
3.Producer:继承自Person类
功能:产品生产商,通过Advertiser来销售产品 属性:需要新增一个Product[] products,表示可以生产的产品
4.Customer:继承自Person类
功能:消费者,会关注广告并选择和自己偏好匹配的产品购买 属性:需要新增一个偏好值向量int[] prefers,长度一定为3,分别对应价格、性能、销量的偏好权重。 还需要新增一个Product[] products,表示接收到广告的产品 方法:需要有一个createPurchaseMessage方法,返回值是一个PurchaseMessage,没有则返回null。
5.Advertisement:继承自Message
属性:新增一个Product product,表示该广告信息宣传的产品
6.PurchaseMessage:继承自Message
属性:新增一个Product product,表示该订单订购的产品
这种情况下,我们的Network需要新增以下方法:生产商制造新的产品的方法addProduct
,增加新的广告的方法addAdvertisement
,需要修改一下sendMessage和sendIndirectMessage方法来支持发送Advertisement,以及顾客购买产品新建订单的addPurchaseMessage
方法,并添加发送订单的sendPurchaseMessage
方法。
下面选择几个核心的业务方法对齐进行实现:
推出新产品:addProduct()
1 /*@ public normal_behavior 2 @ requires (\exists int i; 0 <= i && i < people.length; people[i].id == product.getProducerId() && 3 @ people[i] instanceof Producer); 4 @ requires !(\exists int i; 0 <= i && i < products.length; products[i].equals(product)); 5 @ assignable products; 6 @ assignable getPerson(product.getProducerId()); 7 @ ensures products.length == \old(products.length) + 1; 8 @ ensures (\forall int i; 0 <= i && i < \old(product.length); 9 @ (\exists int j; 0 <= j && j < product.length; product[j] == 10 @ (\old(product[i])))); 11 @ ensures (\exists int i; 0 <= i && i < product.length; product[i] == 12 @ product); 13 @ ensures getProducer(product.getProducerId()).products.length == 14 @ \old(getProducer(product.getProducerId()).products.length) + 1; 15 @ ensures (\forall int i; 0 <= i && i < \old(getProducer(product.getProducerId()).products.length); 16 @ (\exists int j; 0 <= j && j <getProducer(product.getProducerId()).products.length; 17 @ getProducer(product.getProducerId()).products[j] == (\old(getProducer(product.getProducerId()).products[i]))); 18 @ ensures (\exists int i; 0 <= i && i < getProducer(product.getProducerId()).products.length; 19 @ getProducer(product.getProducerId()).products[i] == product); 20 @ also 21 @ public exceptional_behavior 22 @ signals (ProducerNotFoundException e) 23 @ !(\exists int i; 0 <= i && i < people.length; people[i].id == product.getProducerId() && 24 @ people[i] instanceof Producer); 25 @ signals (EqualProductIdException e) 26 @ (\exists int i; 0 <= i && i < products.length; products[i].equals(product)); 27 @*/ 28 public void addProduct(Product product) throws ProducerNotFoundException, EqualProductIdException;
添加产品的广告信息:addAdvertisement()
1 /*@ public normal_behavior 2 @ requires !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)); 3 @ requires (\exists int i; 0 <= i && i < products.length; products[i].equals(message.product)); 4 @ requires (\exists int i; 0 <= i && i < people.length; people[i].equals(message.getPerson1()) && 5 @ people[i] instanceof Advertiser); 6 @ assignable messages; 7 @ assignable message.getPerson1().products; 8 @ ensures messages.length == \old(messages.length) + 1; 9 @ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)); 10 @ ensures (\forall int i; 0 <= i && i < \old(messages.length); 11 @ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i])))); 12 @ ensures message.getPerson1().products.length == \old(message.getPerson1().products.length) + 1; 13 @ ensures (\exists int i; 0 <= i && i < message.getPerson1().products.length; 14 @ message.getPerson1().products[i].equals(message.getProduct())); 15 @ ensures (\forall int i; 0 <= i && i < \old(message.getPerson1().products.length); 16 @ (\exists int j; 0 <= j && j < message.getPerson1().products.length; 17 @ message.getPerson1().products[j].equals(\old(message.getPerson1().products[i])))); 18 @ also 19 @ public exceptional_behavior 20 @ signals (EqualMessageIdException e) 21 @ (\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)); 22 @ signals (ProductNotFoundException e) 23 @ !(\exists int i; 0 <= i && i < products.length; products[i].equals(message.product)); 24 @ signals (AdvertiserNotFoundException e) 25 @ !(\exists int i; 0 <= i && i < messages.length; messages[i].equals(message)) 26 @ && !(messages[i].getPerson1() instanceof Advertiser); 27 @*/ 28 public void addAdvertisement(Message message) throws EqualMessageIdException, ProductNotFoundException, AdvertiserNotFoundException;
顾客新建订单:addPurchaseMessage()
1 /*@ public normal_behavior 2 @ requires contain(id) && getPerson(id) instanceof Customer; 3 @ requires getPerson(id).createPurchaseMessage() != null; 4 @ assignable messages; 5 @ assignable getPerson(id).products; 6 @ ensures messages.length == \old(messages.length) + 1; 7 @ ensures (\exists int i; 0 <= i && i < messages.length; messages[i].equals(getPerson(id).createPurchaseMessage())); 8 @ ensures (\forall int i; 0 <= i && i < \old(messages.length); 9 @ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i])))); 10 @ ensures getPerson(id).products.length == \old(getPerson(id).products.length) - 1; 11 @ ensures !(\exists int i; 0 <= i && i < getPerson(id).products.length; 12 @ getPerson(id).products[i].equals(getPerson(id).createPurchaseMessage().product)); 13 @ ensures (\forall int i; 0 <= i && i < \old(message.getPerson1().products.length) && 14 @ !getPerson(id).products[i].equals(getPerson(id).createPurchaseMessage().product); 15 @ (\exists int j; 0 <= j && j < message.getPerson1().products.length; 16 @ message.getPerson1().products[j].equals(\old(message.getPerson1().products[i])))); 17 @ also 18 @ public exceptional_behavior 19 @ signals (PersonIdNotFoundException e) 20 @ !contain(id); 21 @ signals (TypeErrorException e) 22 @ contain(id) && !(getPerson(id) instanceof Customer); 23 @ signals (NotProductToPurchaseException e) 24 @ contain(id) && getPerson(id) instanceof Customer && getPerson(id).createPurchaseMessage() == null; 25 @*/ 26 public void addPurchaseMessage(int id) throws PersonIdNotFoundException, TypeErrorException, NotProductToPurchaseException;
五、学习体会
jml语言是一个非常严谨的语言,确保了设计过程中每一个方法的实际作用效果需要和jml语言给出前置、后置条件的约定一致,很有效地避免了在软件开发时对设计实现过程中的二义性。但是,在涉及到求图论相关的量的时候,jml语言会写的非常复杂,让人看起来眼花缭乱,需要耐心去读才能明白,这也时jml语言的一个弊端吧,对于一个复杂的任务jml既不好写,也不好读。总之,在某些要求严格的情境下jml起到了很好的规范化的作用,而在另一些情境下,可能jml反而会导致合作开发的效率降低,是一种牺牲效率保证规范化的Java建模语言。