BUAA_OO_2022 Unit3 总结
BUAA_OO_2022 Unit3 总结
一、JML与测试
JML是对Java程序进行规格化设计的一种表示语言,它可以消除二义性,帮助我们更好的完成代码。在经历了三次作业后,我对其有了更深的了解和认知。首先就是JML语言相比自然语言确实更加严谨,但一个关键的问题在于,写者要考虑全面通过JML限定完全,读者要充分理解约束条件。在本单元里,规格是由课程组提供的,所以写者方面不会有什么问题,但是我却出现了大问题,因为理解不充分的原因甚至出现了被HACK100%的壮举,实在是绝望。
JML是十分严谨的,并且对每个功能都有相应的说明,在测试时只要保证每一块都符合设计就能保证代码的正确性,所以我就采用了选择了对复杂的功能分别进行单独的测试(对正常情况和异常分别设计测试样例),对于简单的功能指令就略过(毕竟近乎照搬JML)。但是经过两次测试,我发现花时间去测试不如多去读读JML保证理解不出错,因为对某块地方设计测试样例的时候,是按照自己的已有认知设计的,但是如果对JML的理解出现问题,那么不管怎么测都测不出来,而且在测试的时候很可能不能覆盖所有情况。
在第一次测试中,我针对几个复杂的指令进行了测试,但是没测出来自己并查集的合并有问题(在一个函数感觉规格需要于是加了合并,但是应该在别的地方),因为是直接构造的,大数据规律性强(为了方便观察),小数据有点弱。第二次测试,对发送消息理解出现偏差,测试了很多但也只是在错误的基础上测试,难逃一死。最后一次索性直接盯JML,反倒是没有bug了,但因为最短路性能被卡了(这可能就是放弃构造数据的代价吧,毕竟构造规律的大数据也是能看出性能问题的)。所以,我认为应当首先多次复查JML确保理解不能出错,一定一定要充分理解整体的架构需求,再然后就是进行适量的针对性测试。当然,考虑到这次的输出固定唯一,所以说我认为对拍才是最好的测试方法。
二、架构设计
整体来看,完全按照规格走,除了为了排序而新增的类外基本没有任何的自我创造的东西,这主要还是因为对JML的不完全理解使得自己在很多地方的处理显得十分复杂,现在复盘来看是可以进行很多的化简的。一个最为基础的化简就是改变使用的数据结构,我使用的基本就是JML规格对数据模型的声明,然后在此基础上实现各种功能,虽然我在多次使用的位置进行了记录,但每次依然会遍历寻找一次,不如直接使用HashMap。除此之外也有很多可以化简的地方但在这里就不赘述了。
接着就是关于图的模型构建和维护。如同我上面所说,我并没有自己创建属性状态, 所以相关信息是按照JML规格对数据模型的描述存储的,当需要的时候,直接通过方法获取所需的列表进行操作。
当然,也不是完全没有进行设计。在第一次作业中,使用并查集降低qbs指令的复杂度。在之后的作业中通过堆减小了Prim和Dijkstra的复杂度(虽然优化的比较晚)。同时,考虑到部分指令本身复杂度较高,同时在某些状态未修改时答案保持不变,所以我记录下了相关答案,并在状态改变影响答案时重新计算结果,以确保不会被大量的重复查询卡掉。
三、性能问题及修复情况
首先就是直接使用ArrayList导致的查询相关人或组的复杂度过高,这个问题可以通过使用HashMap解决。
qbs和qlc查询的复杂度也比较高,如果每次都依靠原始信息查询会寄,用并查集维护分组可以解决。
qgvs同样也是因为每次计算复杂度较高,所以在相关状态不改变时第一次计算就要记录下答案以供相同查询使用。
最短路被大数据爆杀,用堆优化减少每次获取中间点的开销。
四、Network扩展
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买-- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
在关系网中要增加生产者列表,消费者列表以及广告列表。
/*@ public instance model non_null Person[] producers; @ public instance model non_null Person[] advertisers; @ public instance model non_null Person[] customers; @*/
相关接口方法:
1、购买商品,送出购买商品的消息
/*@ public normal_behavior @ requires containsMessage(id) && getMessage(id).getType() == 0 && @ (\exist i; 0 <= i && i < advertisers.length;advertisers[i].isLinked(getMessage(id).getPerson2())) && advertisers[i].isLinked(getMessage(id).getPerson1())); @ assignable getMessage(id).getPerson2().messages, getMessage(id).getPerson2().money; @ ensures getMessage(id).getPerson2().money = \old(getMessage(id).getPerson2().money) + getMessage(id).getsocialValue(); @ ensures (\forall int i; 0 <= i && i < \old(getMessage(id).getPerson2().getMessages().size()); @ \old(getMessage(id)).getPerson2().getMessages().get(i+1) == \old(getMessage(id).getPerson2().getMessages().get(i))); @ ensures \old(getMessage(id)).getPerson2().getMessages().size() == \old(getMessage(id).getPerson2().getMessages().size()) + 1; @ ensures \old(getMessage(id)).getPerson2().getMessages().get(0).equals(\old(getMessage(id))); @ ensures !containsMessage(id) && messages.length == \old(messages.length) - 1 && @ (\forall int i; 0 <= i && i < \old(messages.length) && \old(messages[i].getId()) != id; @ (\exists int j; 0 <= j && j < messages.length; messages[j].equals(\old(messages[i])))); @ also @ public exceptional_behavior @ signals (MessageIdNotFoundException e) !containsMessage(id); @ signals (PersonIdNotFoundException e) !containsCustomer(getMessage(id).getPerson1().getId()) || @ !containsProducer(getMessage(id).getPerson2().getId()) @ signals (RelationNotFoundException e) containsMessage(id) && (getMessage(id).getType() != 0 || @ (\forall i; 0 <= i && i < advertisers.length;!advertisers[i].isLinked(getMessage(id).getPerson2())) || !advertisers[i].isLinked(getMessage(id).getPerson1()))); */ public void buy(int id) throws RelationNotFoundException, MessageIdNotFoundException, PersonIdNotFoundException;
2、查询销售额
/*@ public normal_behavior @ requires containsProducer(id); @ assignable \nothing; @ ensures \result == getProducer(id).money; @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !containsProducer(id); @*/ public /*@ pure @*/ int getMoney(int id) throws PersonIdNotFoundException;
3、设置消费者的购买信息
/*@ public normal_behavior @ requires contains(personId) && containsMessage(messageId); @ assignable getPerson(personId).buylist; @ ensures (\forall int i;0 <= i && i < \old(getPerson(personId).length); getPerson(personId).hasBuyMessage(\old(getPerson(personId).buylist(i)).getId())); @ ensures getPerson(personId).hasBuyMessage(messageId); @ also @ public exceptional_behavior @ signals (PersonIdNotFoundException e) !(\exists int i; 0 <= i && i < people.length; people[i].getId() == id && people[i] instanceof Producer) @ signals (ProductNotFoundException e) !(\exists int i; 0 <= i && i < products.length; products[i].getId() == productId); */ public void setPreference(int personId, int messageId) throws PersonIdNotFoundException, MessageIdNotFoundException;
五、心得体会
经过三次作业,我对JML也有了更深的理解,简单方法的规格是真简单,又少又直接,复杂方法的规格也是真的复杂,尤其是关于算法的地方,因为实现的目标复杂,不论是用JML描述算法,还是从JML中看出来算法需求,都是一件非常困难的事情。
所以说,当去读JML的时候,一定要反复再三的阅读,确保自己的理解没有偏差,不能想当然,按照惯性思维去写从而违反了相应的规格(真的会很惨)。而在充分理解需求后就需要考虑如何使用高效的设计达成目标,毕竟JML描述了目标,但是并没有规定方法,虽然按照描述照搬可以完成目的,但是性能却不一定有保障。
而当去写JML的时候,就需要尽量简化函数的作用,当函数复杂起来之后,JML就会变得又臭又长,而且很容易写错,不过这个其实要在写代码之前就要设计好避免函数过于复杂,也就是说自己写程序前一定要注意架构的设计,否则将会非常的痛苦!