OO第三单元总结
面向对象JML系列------第三单元
JML
注释格式
单行注释://@annotation
多行注释:/*@ annotation @*/
表达式格式
JML表达式与Java表达式十分相似,就是多了一些操作符、原子表达式
原子表达式
\result
一个方法(返回值不是void)的返回值
\old(expr)
expr在执行该方法前的取值
对象引用:只判断本身是否变化,不判断其指向对象实体内容是否变化
\not_assigned(x,y,...)
表示括号中的变量是否在执行过程中被赋值
被赋值了返回true,反之返回false
\not_modified(x,y,...)
限制括号中变量在方法执行的时候取值不能变化
\nonnullelements(container)
表示该container不能含有null的对象
\type(type)
返回type对应的类型
\typeof(expr)
返回expr对应的准确类型
量化表达式
\forall
表示每个给定范围中的元素都满足对应约束
\exists
表示存在至少一个给定范围中的元素满足对应约束
\sum
返回给定范围中的表达式的和
\product
返回给定范围中的表达式的积
\max
返回给定范围中的表达式的最大值
\min
返回给定范围中的表达式的最小值
\num_of
返回满足相应条件的变量的取值个数
集合表达式
在JML规格中构建一个明确其可以包含什么元素的集合
操作符
E1<:E2
若类型E1为类型E2的子类型(或同类型),返回true,反之返回false
b_expr1<===>b_expr2
b_expr1<=!=>b_expr2
b_expr1与b_expr2均为布尔表达式
表示b_expr1 == b_expr2
或者b_expr1 != b_expr2
但他们的优先级低于==以及!=
b_expr1==>b_expr2
当且仅当b_expr1为true且b_expr2为false时,返回false
\nothing
空集
\everything
全集
方法规格
requires
前置条件,限制方法输入的参数
ensures
后置条件,限制方法产生的结果
side-effects
assignable 可赋值
modifiable 可修改
signals
signals(Exception e) b_expr: b_expr为true,抛出异常e
signals_only:只要满足前置条件就抛出对应的异常
类型规格
invariant
invariatn P:在所有可见状态下都必须满足特性P
constraint:对前序可见状态、当前可见状态关系进行的约束
有用的工具
VScode:高亮显示JML注释(好看)
JUnit:单元测试
利用JML规格准备测试数据
本次测试主要采用黑盒测试,在数据生成器中通过控制不同的指令的数量,尽量覆盖了规格提及的情况,并且通过多次重复测试某一个指令,保证了程序不会超时。
评测机的一些代码:
from random import choice, randint, random
instrs = ['ap', 'ap', 'ar', 'ar', 'qv', 'qci',
'qbs', 'qps', 'ag', 'atg', 'dfg', 'qgps', 'qgvs', 'qgav',
'am', 'sm', 'qsv', 'qrm', 'qlc', 'qlc', 'qlc', 'qlc', 'qlc',
'arem', 'anm', 'cn', 'aem', 'sei', 'qp', 'dce', 'qm', 'sim']
...
instrlist += "ap {} {} {} {}\n".format(i, choice(adj) + "_" + choice(name), randint(1000000, 10000000),randint(1, 80))
relations = randint(people_num, people_num * (people_num) / 2)
relations = min(LENGTH - 2 * people_num, relations)
...
架构
主要的框架其实都已经被规定了 在这里简单说一下图模型的构建以及维护的策略
主要在Network里面动态维护了一些量:
用HashMap把Personid,MessageId,GroupId,EmojiId都做了一一对应,方便后续操作;
qci和qbs都是用的并查集,比起dfs时间复杂度降低了;采用路径压缩使得深度尽量小;
(不得不说hashmap写还是很舒服的)
public static int find(int id, HashMap<Integer, Integer> father) {
int dad = father.get(id);
if (dad == id) {
return id;
} else {
dad = find(dad, father);
father.put(id, dad);
return dad;
}
}
public static void union(int i1, int i2, HashMap<Integer, Integer> father,
HashMap<Integer, Integer> rank, HashMap<Integer, Boolean> dirty) {
int id1;
int id2;
id1 = find(i1, father);
id2 = find(i2, father);
int rank1;
int rank2;
rank1 = rank.get(id1);
rank2 = rank.get(id2);
if (id1 != id2) {
if (rank1 < rank2) {
father.put(id1, id2);
rank.put(id2, rank1 + rank2);
} else {
father.put(id2, id1);
rank.put(id1, rank1 + rank2);
}
} else {
father.put(id2, id1);
rank.put(id1, rank1 + rank2);
}
dirty.put(find(id1, father), false);
}
}
qlc用的堆优化的dijkstra算法,使用Priorityqueue进行操作
PriorityQueue<int[]> queue = new PriorityQueue<>(priComparator);
//[distance,id]
queue.add(new int[]{0, id1});
while (!queue.isEmpty()) {
int[] e = queue.poll();
if (visited.contains(e[1])) {
continue;
}
visited.add(e[1]);
distance.put(e[1], e[0]);
if (e[1] == id2) {
return e[0];
}
((MyPerson) getPerson(e[1])).getValue().forEach((p, v) -> {
if ((!distance.containsKey(p.getId())) || distance.get(p.getId()) > e[0] + v) {
distance.put(p.getId(), e[0] + v);
queue.add(new int[]{e[0] + v, p.getId()});
}
});
}
这里为了动态维护,设置了一个dirty的hashmap,如果有addperson这种改变的操作,就把对应的personid的value设置为true,下一次查询的时候要重置
Network拓展
假设出现了几种不同的Person
Advertiser:持续向外发送产品广告
Producer:产品生产商,通过Advertiser来销售产品
Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
Person:吃瓜群众,不发广告,不买东西,不卖东西
如此Network可以支持市场营销,并能查询某种商品的销售额和销售路径等 请讨论如何对Network扩展,给出相关接口方法,并选择3个核心业务功能的接口方法撰写JML规格(借鉴所总结的JML规格模式)
- Advertiser Producer Customer 都继承了类Person
- 增加类Product,属性包括id,price
- 假设advertiser有一定智商,只对customer打广告,并发送给所有的customer
- advertiser有一定数量的products
选择了购买商品,打广告,设置用户偏好撰写了JML规格;
customer通过advertiser购买商品
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == CustomerId && people[i] instanceof Customer) &&
(\exists int i; 0 <= i && i<= people.length; people[i].getId() == AdvertiserId && people[i] instanceof Advertiser) &&
(\exists int i; 0 <= i && i<= products.length;products[i].getId() == ProductId);
@ ensures getPerson(CustomerId).money = \old(getPerson(customerId).mondy) - getProduct(ProductId).getPrice();
@ ensures getPerson(AdvertiserId).getRemaining(ProductId) = \old(\getPerson(AdvertiserId).getRemaining(ProductId)) -1 ;
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length;
@ people[i].getId() == id && people[i] instanceof CustomerId);
@ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length;
@ people[i].getId() == id && people[i] instanceof AdvertiserId);
@ signals (ProductIdNotFoundException e) !(\exists int i; 0 <= i && i < products.length;
@ products[i].getId() == ProductId);
@*/
public void purchase(int AdvertiserId,int CustomerId,int ProductId) throw PersonIdNotFoundException,ProductIdNotFoundException;
advertiser投放广告
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < people.length; people[i].getId() == advertiserId && people[i] instanceof Advertiser) &&
(\exists int i; 0 <= i && i<= messages.length;products[i].getId() == adMsgId && products[i] instanceof adMessage);
@ 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]))));
@ ensures (\forall int i; 0<= i && i < people.length; people[i] instanceof Customer ;
(\forall int j; 0 <= j && j < \old(getPerson(i).getMessages().size());
@ getPerson(i).getMessages().get(j+1) == \old(getPerson(i).getMessages().get(i)) &&
@ getPerson(i).getMessages().get(0) == \old(getMessage(adMsgId)) &&
@ \old(getPerson(i).getMessages().size() == \old(getPerson(i).getMessages().size()) + 1));
@ 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 < people.length; people[i].getId() == Personid && people[i] instanceof Customer) &&
(\exists int i; 0 <= i && i<= products.length;products[i].getId() == ProductId);
@ assignable getPerson(personId).preferences;
@ ensures (\forall Product i;\old(getPerson(PersonId).prefer(i));getPerson(PersonId).prefer(i));
@ ensures getPerson(personId).prefer(ProductId);
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length;
@ people[i].getId() == id && people[i] instanceof Customer);
@ signals (ProductIdNotFoundException e) !(\exists int i; 0 <= i && i < products.length;
@ products[i].getId() == ProductId);
@*/
public void setPreference(int PersonId, int ProductId) throw PersonIdNotFoundException,ProductIdNotFoundException;
心得体会
第三单元相对来说是比较友好的单元,但是对JML的运用还是掌握的不太好,虽然能勉强读懂JML规格要我做什么,也能通过实例等对其有所了解,但是如果让我自己写一个JML规格代码还是一件很痛苦的事情。而且两次实验课上都是压着时间线交的答案,很多时候并不太理解,要频繁查手册。
考虑到时间复杂度,这个单元主要把力气都花在了优化上面,非常担心自己会T,也构造了大量的数据去测试某几个容易卡的指令。但不太清楚这一部分内容跟我们这单元所学有什么关系。