OO第三单元总结
一、 测试方法
本单元我采用的方法主要为根据规格的边界情况构造数据进行分别测试的方法,因为第三单元随机生成的数据很多情况下并不是那么的有效,可能出现看上去通过了几千条数据测试实际上还是存在很多bug的情况,因此通过对规格的分析自己构造数据更为可行。
针对JML规格进行测试的要点在于测试数据要包含规格中所有的前置条件,判断所有后置条件情况是否正确。并且需要构造出能够使该方法的不同异常都能被抛出的情况。要想满足这几点要求,不仅需要正确理解规格描述的含义,还应该对该方法或该类与其他方法之间的相互作用关系,明确地清楚该方法的所有可能情况,才能构造出合理又相对较强的测试数据。
这里以addToGroup为例进行分析
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < groups.length; groups[i].getId() == id2) &&
@ (\exists int i; 0 <= i && i < people.length; people[i].getId() == id1) &&
@ getGroup(id2).hasPerson(getPerson(id1)) == false &&
@ getGroup(id2).people.length < 1111;
@ assignable getGroup(id2).people;
@ ensures (\forall Person i; \old(getGroup(id2).hasPerson(i));
@ getGroup(id2).hasPerson(i));
@ ensures \old(getGroup(id2).people.length) == getGroup(id2).people.length - 1;
@ ensures getGroup(id2).hasPerson(getPerson(id1));
@ also
@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < groups.length; groups[i].getId() == id2) &&
@ (\exists int i; 0 <= i && i < people.length; people[i].getId() == id1) &&
@ getGroup(id2).hasPerson(getPerson(id1)) == false &&
@ getGroup(id2).people.length >= 1111;
@ assignable \nothing;
@ also
@ public exceptional_behavior
@ signals (GroupIdNotFoundException e) !(\exists int i; 0 <= i && i < groups.length;
@ groups[i].getId() == id2);
@ signals (PersonIdNotFoundException e) (\exists int i; 0 <= i && i < groups.length;
@ groups[i].getId() == id2) && !(\exists int i; 0 <= i && i < people.length;
@ people[i].getId() == id1);
@ signals (EqualPersonIdException e) (\exists int i; 0 <= i && i < groups.length;
@ groups[i].getId() == id2) && (\exists int i; 0 <= i && i < people.length;
@ people[i].getId() == id1) && getGroup(id2).hasPerson(getPerson(id1));
@*/
public void addToGroup(int id1, int id2) throws GroupIdNotFoundException,
PersonIdNotFoundException, EqualPersonIdException;
这里需要满足的前置条件有以下几个
Groups中存在id为id2的group,peoples中存在id为id1的people
原来的group中找不到该people
Group中people的数量不大于1111
构造数据时就可以采用设置未知id、连续两次添加同一people以及添加超过1111个people的方法来测试代码
接着以第三次作业中更为复杂的addMessege为例
/*@ public normal_behavior
@ requires !(\exists int i; 0
<= i && i < messages.length; messages[i].equals(message))
&&
@
(message instanceof EmojiMessage) ==> containsEmojiId(((EmojiMessage)
message).getEmojiId()) &&
@
(message.getType() == 0) ==>
(message.getPerson1() != message.getPerson2());
@ assignable messages;
@ ensures messages.length == \old(messages.length)
+ 1;
@ ensures (\forall int i; 0 <=
i && i < \old(messages.length);
@ (\exists int j; 0 <= j && j <
messages.length; messages[j].equals(\old(messages[i]))));
@ ensures (\exists int i; 0 <=
i && i < messages.length; messages[i].equals(message));
@ also
@ public exceptional_behavior
@ signals (EqualMessageIdException
e) (\exists int i; 0 <= i && i < messages.length;
@
messages[i].equals(message));
@ signals (EmojiIdNotFoundException
e) !(\exists int i; 0 <= i && i < messages.length;
@
messages[i].equals(message)) &&
@
(message instanceof EmojiMessage) &&
@
!containsEmojiId(((EmojiMessage)
message).getEmojiId());
@ signals (EqualPersonIdException
e) !(\exists int i; 0 <= i && i < messages.length;
@
messages[i].equals(message)) &&
@
((message instanceof EmojiMessage) ==>
@
containsEmojiId(((EmojiMessage) message).getEmojiId()))
&&
@
message.getType() == 0 && message.getPerson1() ==
message.getPerson2();
@*/
public void addMessage(Message message) throws
EqualMessageIdException,
EmojiIdNotFoundException, EqualPersonIdException;
这里可能出现的情况为:发送者与接收者的id相同、消息id重复、消息是emoji类型但是emojiMessege中没有找到。
我们可以分别构造数据:
am 1 1 0 1 1
am 1 1 0 1 2
am 1 1 1 1 2
aem 1 1 0 1 2
二、 架构设计
第三单元的主要任务是完成MyNetwork基本上所有的算法都是在MyNetwork里实现,通过维护自己新建的一些数据结构来完成
在添加关系时用HashMap来记录成员之间的父子关系,最后得到的就是一张关系图,保存在relations中
第九次作业:
利用并查集对图的查找进行优化,用时将路径进行压缩,保证同一颗树上的元素getFather后均相同。
private int getFather(int id1) {
int father = id1;
while (father != relations.get(father)) {
father = relations.get(father);
}
relations.put(id1, father);
return father;
}
public int queryBlockSum() {
int sum = 0;
ArrayList<Integer> nowFather = new ArrayList<>();
for (Person person : people) {
if (!nowFather.contains(getFather(person.getId()))) {
nowFather.add(getFather(person.getId()));
sum++;
}
}
return sum;
}
第十次作业:
最短路径问题,重新建立了一张图,用来保存每个点到顶点的最短距离(有了HashMap就觉得java比C语言好写多了)
private int getResult(ArrayList<Integer> allId, int id) {
int result = 0;
int min = 1001;
int minId = id;
HashMap<Integer, Integer> map = new HashMap<>();
map.put(allId.get(0), -1);
for (int i = 0; i < ((MyPerson)getPerson(id)).getAc().size(); i++) {
changeMap(map, ((MyPerson) getPerson(id)).getAc().get(i).getId(),
((MyPerson) getPerson(id)).queryValue(((MyPerson)
getPerson(id)).getAc().get(i)));
}
while (true) {
int flag = 0;
for (Integer key : map.keySet()) {
if (map.get(key) != -1) {
flag = 1;
break;
}
}
if (flag == 0) {
break;
}
for (Integer key : map.keySet()) {
if (map.get(key) != -1 && map.get(key) < min) {
min = map.get(key);
minId = key;
}
}
result += min;
map.put(minId, -1);
for (int i = 0; i < ((MyPerson)getPerson(minId)).getAc().size(); i++) {
changeMap(map, ((MyPerson) getPerson(minId)).getAc().get(i).getId(),
((MyPerson) getPerson(minId)).queryValue(((MyPerson)
getPerson(minId)).getAc().get(i)));
}
min = 1001;
}
return result;
}
第十一次作业:
没什么好说的,dijstra算法,建立了两个HashMap分别用来记录该点是否完成以及该点的距离,注意并不需要将所有的点全部处理完,在处理完需要的点后就可以退出,进一步节省时间。
三、 性能问题和修复情况
如果严格按照JML规则进行代码的编写的话,程序的性能将会特别低,需要自己进行算法的优化,因此类中要新建很多自己的属性和方法用来实现这些算法。
使用HashMap能在单次查找时到O(1)的效率,极大地节省了数组遍历的时间。
四、 扩展作业
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买
-- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息 - Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等
请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
Producer收集Customer的信息,处理信息并且向Advertiser发送信息。
Advertiser接受Producer的信息制作广告后向Customer发送
产品类 Product
广告类Advertise继承自Messege
Advertiser类、Producer类、Customer类继承Person类
发消息
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == advertiserId && people[i] instanceof Advertiser)
@ assignable messages;
@ ensures !containsMessage(adMsgId) && messages.length == \old(messages.length) - 1 &&
@ (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != adMsgId;
@ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i]))));
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length;
@ people[i].getId() == id && people[i] instanceof Advertiser);
@*/
public void advertise(int advertiserId,int adMsgId) throw PersonIdNotFoundException;
产品价值
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < products.length; products[i].getId() == id);
@ ensures (\exists int i; 0 <= i && i < products.length; products[i].getId() == id &&
@ \result == products[i].getValue());
@ also
@ public exceptional_behavior
@ signals (ProductIdNotFoundException e) (\forall int i; 0 <= i && i < products.length;
products[i].getId() != id);
@*/
public /*pure*/ int getProductValue(int id) throws ProductIdNotFoundException;
设置偏好
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == id1);
@ requires (\exists int i; 0 <= i && i < products.length; products[i].getId() == id2);
@ ensures getPerson(personId).isFavorable(productId) == true;
@*/
public /*@ pure @*/void setFavorite(int id1, int id2);
五、 学习心得
本单元的三次作业较为轻松,只需要理解好JML的含义,仔细分析好代码实现和程序运行中的各种情况,并且自己根据JML合理地构造数据,引入一些算法进行代码的优化,就能钩在强测中得到不错的成绩。
JML能够很好地帮助我们理解一个程序或者一段代码的目的和作用,并且清楚地了解一些边界条件和异常处理,是一种很好的代码编写和阅读的辅助工具。