OO第三单元总结
OO第三单元总结
一、本单元架构设计
本单元由于给出了相关的JML规格,因此在架构上没有什么能够特别设计的,一般是为了性能问题而对方法实现做一定的优化。
我觉得主要难点在于需要理解课程组所给出的JML规格是怎么设计这个社交网络的,每一部分的作用是什么以及每一个方法的具体含义(比如根据课程组的JML,其实queryLeastConnection
因为规格中条件的嵌套,就比较难懂),必要时写一些方法类来简化实现。
本单元总体设计如下
1、异常部分
这应该是我第一次接触异常具体的实现机制,本次作业中,三次作业异常类是不断增加的,但其实所有异常类的思想都是一样的,就是计数+输出异常信息,因此我写了一个用来计数的类,并在每个异常类中初始化一个该类的静态变量用来对这一类异常进行计数:
public class Count {
private int allCount;
private HashMap<Integer, Integer> counts;
public Count() {
this.allCount = 0;
this.counts = new HashMap<>();
}
public void addAll() {
allCount += 1;
}
public void addOne(int id) {
if (counts.containsKey(id)) {
int x = counts.get(id);
counts.replace(id, x + 1);
} else {
counts.put(id, 1);
}
}
public int getAllCount() {
return allCount;
}
public int getOneCount(int id) {
return counts.get(id);
}
}
之后所有的异常类都利用这个原理来进行就可以了。
2、具体网络实现部分
本次主要是根据JML规格实现了我们自己的Myperson
、MyGroup
、MyMessage
、MyNetwork
四个类以及后续加入的几个Message
类。
在容器的选择上,基本都选择使用了HashMap
来储存,使用id
作为key
,相应的内容作为value
。
大部分方法都比较简单,就只在这里说一下比较复杂的几个方法:
isCircle
和queryBlockSum
这两个方法我使用到了并查集的思想,在Myperson
类中维护一个father
属性,初始时设定为自己,在添加关系时修改。最终相同father
的就是在同一个相连的树中,同时,储存目前总共拥有的father
的个数,进而可以实现上述两个方法。
queryLeastConnection
这个方法本质上是计算带权的最小生成树的问题,只需要采用最小生成树的算法即可。
sendIndirectMessage
这个方法需要计算最短路径。在这里我采用了迪杰斯特拉算法来计算最短路径。
二、测试相关
本单元的测试我采用了随机生成数据测试加多人对拍的方式来进行。
首先,根据每一次指令的个数,利用随机数来生成指令并输出到输入文件。为了保证测试更加有效(防止全是异常触发),对于随机的id采用了两种方式,一种是采用直接计数的方式:
int count = 1;
int id(){
return count++;
}
另一种是采用了在已有的id中随机选择:
int id1(){
return (rand()%count);
}
每次都会在两种中随机选择一种,第二种的存在保证了数据的有效概率。
生成数据后直接利用shell文件将数据输入到已经打包好的代码jar包中,并且将数据输出到结果文件中。对拍的对比则采用已有的数据对比网站来进行:
a | java -jar code1.jar > stdout1.txt //a为自动生成的指令的文件
pause
本次测试采用的数据数量大概在每次10000条左右,测出了不少代码中的bug,当发现输出结果不同时,我们也会采取将其他指令注释掉,只测试出问题的指令(还有必要的基础指令)来更好的定位问题所在。
同时,在对拍过程中,发现结果不同也可以通过直接与其他人交流思路来判断自己对于JML规格的理解是否有误,然后再更具体的判定自己是否是在写的过程中出了问题。
三、性能问题以及bug修复
本单元的三次作业,我都在互测中被hack了,并且基本都是性能问题,只有一个是正确性问题。
第一次作业中,因为没有采用并查集,导致queryBlockSum
直接被卡ctle;第二次作业中,由于我对于getAgeVar()
方法中的一些处理有问题,忘记将getAgeMean()
提前计算出值并且储存,导致二重循环加计算getAgeMean()
直接在互测中ctle;第三次作业则是因为有个条件判断错误而导致了正确性问题。
后来在采用并查集等优化性能后,解决了上述问题。
在互测中,由于意识到了自己tle的性能问题,我也用类似的思路去hack同屋的同学,发现确实有很多人和我一样也没有优化性能,采用单纯的二重循环等看似从JML中直接得到的方法去处理一些指令。
四、Network的扩展以及相应的JML规格
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
/*@ public normal_behavior
@ requires advertisers.contains(advertiser);
@ assignable advertiser.getAdvert();
@ ensures (\forall int i; 0 <= i && i < \old(advertiser.getAdvert().size());
@ (\exists int j; 0 <= j && j < advertiser.getAdvert().size();
@ advertiser.getAdvert().get(j) == \old(advertiser.getAdvert()).get(i)));
@ ensures \old(advertiser.getAdvert().size()) == advertiser.getAdvert().size() - 1;
@ ensures (\exists int i; 0 <= i && i < advertiser.getAdvert().size();
@ advertiser.getAdvert().get(i) == advert)
@ also
@ public exceptional_behavior
@ signals (AdvertiserNotFoundException e) !advertisers.contains(advertiser);
*/
public void sendAdvert(Advertiser advertiser,Advert advert) throws AdvertiserNotFoundException;
//用于producers将advert传给advertisers让其投放广告
/*@ public normal_behavior
@ requires contains(id) && (getPerson(id) instanceof Producer);
@ ensures \result == getPerson(id).getAllSales();
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !contains(id);
@ signals (PersonTypeErrorException e) !(getPerson(id) instanceof Producer);
*/
public int querySales(int id) throws PersonIdNotFoundException,PersonTypeErrorException;
//查询某个人的销售总额
/*@ public normal_behavior
@ requires advertisers.contains(advertiser);
@ assignable advertiser.getOffer();
@ ensures (\forall int i; 0 <= i && i < \old(advertiser.getOffer().size());
@ (\exists int j; 0 <= j && j < advertiser.getOffer().size();
@ advertiser.getOffer().get(j) == \old(advertiser.getOffer()).get(i)));
@ ensures \old(advertiser.getOffer().size()) == advertiser.getOffer().size() - 1;
@ ensures (\exists int i; 0 <= i && i < advertiser.getOffer().size();
@ advertiser.getOffer().get(i) == offer)
@ also
@ public exceptional_behavior
@ signals (AdvertiserNotFoundException e) !advertisers.contains(advertiser);
@*/
public void sendOffer(Advertiser advertiser, Offer offer) throws AdvertiserNotFoundException;
//由Customer调用,发送给Advertiser购买消息.
总结
本单元还是很感慨,读官方法JML规格时有时很难理解,写代码时感觉按照规格写很简单,却忽略了很多在性能上不合理的地方导致bug频发。
虽然不用自己来搭建整体的架构,还算比较简单的一个单元,但是在这次作业中终于在几次作业后初步尝试了一些测试方法,进行了大量的对拍测试,也体会到了测试的好处。
我觉得很多时候JML规格的描述会很抽象(比如目的是计算最小生成树的那个方法),不太能想象以后可能会有一些其他的复杂或者更复杂的方法,这种规格读起来困难是怎么解决的