OO_Unit3_Summary
心得体会
丈育最近没有读书,所以玩不了前两次总结的文摘花活了
-
本单元的难度总体来说还是比较小(shui)的,因此也帮助本人为同期的其他事务挣得了更宽裕的时间,不胜感激。阅读 JML 约束并遵照它进行代码书写的过程给我一种更加工程化的舒适感觉,它让我想起了之前上羽毛球课的时候,搭档同学曾经说过的一句话:“比起羽毛球,还是更喜欢套路武术那种记动作的项目啊……”虽然这个想法本人不敢苟同,但 JML 带给我的感觉就跟“套路武术”很像,它就如一张完备的图纸,只要仔细阅读并遵照它的规则指引,其实就很难犯什么错,或许这就是为什么,本单元的所有问题几乎都可以用“请仔细阅读 JML 表述”来回应。不过正是因为 JML 本身的完备,也让见识浅薄的我产生了一个贯穿全单元的疑问——如果以后的工程生涯中还要与之见面,那么它应该由谁来写?在我看来,将完备的 JML 文档写出,已经与自行完成所有代码工作无疑,如果只是为了制定一份约束 / 形式化思路,那么更自然语言化的文档好像也能起到一样的作用。
-
比起 JML 的阅读理解,本单元的工作量重点似乎还是在性能优化和测试上,反而实验课给人带来的训练感更具有针对性一些。我不太理解这是否应该成为本单元的教学重点(虽然它唤醒了我
尘封已久的图论算法知识,很大程度上亦锻炼了我的测试能力),但我自己也没想到什么很好的 JML 方向的教学任务;同时网上的 JML 相关资料也非常少,让人有点怀疑它在今后工程生涯的应用广度究竟几何(即使我觉得它很具有工程特色)。 -
本单元跟群友的对拍过程很开心。前两个 OO 单元本人单打独斗的成分更多一些,也吃过好几次测试上的亏,而这单元从头到尾都在跟 X, Y 两位群友分享测试数据、对拍、完善性能优化方法,帮群友找bug、de 自己 bug 的体验都着实不错,群友的 bug 还让我学到了一些很厉害的东西——测试在增进群友友谊的方面起到的作用可以说是跟美赛等量齐观(大误)
-
没有性能分的强测页面观感真不错,已经和三位数得分阔别许久了……
不读书真是会变成丈育,感觉连个语句通顺的心得体会都写得十分困难,自省。)
自测历程
分析在本单元自测过程中如何利用JML规格来准备测试数据。
本单元我采用了数据生成器 + 群友对拍的方式进行测试。JML 的规格化特点使得准备自测数据的过程变得非常自然,所有的指令、约束和边界条件都已经给出,我们只需要设计合适的场景,让各个方法与限制条件按照合适的比例出现在数据中(或者说,按照一个个“单元”来逐个测试),就可以构造覆盖率很高的数据,因此写测试数据生成器的效率比前两个单元高了很多,用 cpp 来实现还是比较容易的。得益于覆盖率较好的数据和与群友的充分对拍,本单元平安通过。
至于指导书中推荐的 JUnit,我试水了一阵后感觉书写效率不是太高(每个方法都要设计),同时由于能力问题也没太搞明白它的奥妙,所以没有使用它进行单元测试,究其根源,还是自己太水了。
BTW,OO 互测也随着本单元的结束落下了帷幕。第二单元的摆烂过后,这单元总算又开始 hack 了些人……但总地来说,在互测这个环节我始终是不够积极,需要反省。不过学习别人优秀代码的过程还是很高兴,这也是互测环节带给我的最大收获。
架构设计
梳理本单元的架构设计,分析自己的图模型构建和维护策略。
正如在开头的心得体会里写的那样,本单元所有的问题(包括架构设计)都可以用“请仔细阅读 JML 表述”来解决,因此三次作业的架构都是与之强相关的。唯一需要注意 / 可能引起不同的点应该就是性能优化涉及的那几个图论相关算法了,为了方便使用,我将它们抽象成了几个类。至于具体的性能优化服务算法放到下一节中讨论。
以下的架构图略去所有异常类。为了方便异常类中的计数需求,我设计了一个 Counter 类,借助 static 的特性完成异常出现次数的统计。
第九次作业
本次作业架构图如下:
注:MyNetwork 里面那个 nodes 属性并没有用,后面被抽象到 Dsu 类中实现并查集算法去了,但是我忘了删(悲)
该次作业中,由于需求太简单 + 冯如杯赶时间,我有许多地方直接无脑用 ArrayList 管理了,后面为了便于管理 / 考虑性能都替换成了 HashMap。
第十次作业
本次作业架构图如下:
第十一次作业
本次作业架构图如下:
性能分析
按照作业分析代码实现出现的性能问题和修复情况。
本单元的性能优化问题主要可以分为图论算法考察和方法复杂度降低两部分。前者明白写在 JML 脸上,再不济通过同学的讨论也可以发现,实现的过程比较简单,权当复习数据结构了;后者可能需要稍微想一下(群友和互测房友都在此出过 TLE 的锅),但其实只要发现某方法中存在多重循环 / 频繁的查询,就要好好考虑是不是要改变一下它的实现思路了,一般通过不断更新维护一个属性都可以解决的。
-
图论算法考察
这一部分相关算法都比较基础,如果已经忘了,网上的板子很多,助教(应该)也还是会在讨论区发布暖心帖帮助的,因此就不在此浪费口舌赘述代码细节和理论原理了。
- isCircle()
- 查询两个 Person 之间是否存在联通关系
- 显然朴素的 dfs 会超时,这里使用路径压缩并查集维护连通性,本人抽象出的 Dsu 类就是为此服务的
- queryLeastConnection()
- 在两个 Person 之间寻找权值和最小的一个 Connection
- 实则为最小生成树问题,将 Person 化为点,Person 之间的 Link 化为带权边处理即可,本人抽象出的 Edge 类就是为此服务的;如果第九次作业中已经实现过路径压缩并查集,在此可以很自然地使用 Kruskal 算法
- Java 中自带的小顶堆容器 PriorityQueue 很好用,感谢实验课
- sendIndirectMessage()
- 在两个(不一定)直接相连的 Person 之间传递信息,要求选择最短路
- 使用堆优化Dijkstra
- isCircle()
-
方法复杂度降低
-
queryBlockSum()
- 在 MyNetwork 类中维护一个 blockSum 属性,每次加人和加关系时对其进行修改,避免反复查询
-
getValueSum()
- 在 MyGroup 类中维护一个 valueSum 属性,每次往组里加人、删人、增加关系时对其进行修改,避免循环
-
getAgeMean()
- 在 MyGroup 类中再维护一个 ageMean 属性,每次往组里加人、删人时对其进行修改,避免二重循环
-
getAgeVar()
-
在 MyGroup 类中再维护一个 sqrSum 属性,每次往组里加人、删人时对其进行修改,最后利用方差公式计算,避免二重循环
public int getAgeVar() { if (people.size() == 0) { return 0; } //(squreSum - 2 * ageSum * mean + sz * mean * mean) / sz int sum = (sqrSum - 2 * ageMean * getAgeMean() + getSize() * getAgeMean() * getAgeMean()); sum /= people.size(); return sum; }
-
-
扩展任务
假设出现了几种不同的Person
- Advertiser:持续向外发送产品广告
- Producer:产品生产商,通过Advertiser来销售产品
- Customer:消费者,会关注广告并选择和自己偏好匹配的产品来购买 -- 所谓购买,就是直接通过Advertiser给相应Producer发一个购买消息
- Person:吃瓜群众,不发广告,不买东西,不卖东西
如此 Network 可以支持市场营销,并能查询某种商品的销售额和销售路径等。请讨论如何对 Network 扩展,给出相关接口方法,并选择 3 个核心业务功能的接口方法撰写 JML 规格(借鉴所总结的JML规格模式)
-
我认为“偏好”应当是一种商品属性,不同属性的商品的广告应该由不同的 Advertiser 投入到整个 Network 中一个类似“广告池”的宏观容器里,每个 Customer 类应有用于描述偏好的属性,然后不断从“广告池”中读取符合偏好的广告并执行购买行为,下单后,购买信息会传递给对应属性的 Advertiser,积累了一定数量后由它传递给对应属性的 Producer 生产,生产好后再发送给 Advertiser,由它负责分发。如果“广告池”中没有符合顾客偏好的广告就等待。
-
新增 3 个 Person 的子类
- Advertiser
- 发送产品 p 的广告:sendAd(): AdvertisementMessage
- 给 Producer 发送订购产品 p 的消息:sendOrder(): int
- 管理的偏好类型:int preference
- Producer
- 接收订购产品 p 的信息:receiveOrder(int productSum): void
- 统计产品 p 的销售额:countSales(): int
- 生产的偏好类型:int preference
- Customer
- 关注和自己偏好匹配的产品:getAd(): void
- 发送订购信息:sendOrder(Product p, int customerId): PurchaseMessage
- 偏好类型:int preference
- Advertiser
-
新增 2 个 Message 的子类
- AdvertisementMessage
- 广告主体:Product p
- PurchaseMessage
- 订单主体:Product p
- 订单来源:int CustomerId
- AdvertisementMessage
-
新增商品类
- Product
- 符合的“偏好”属性:int preference
- Product
-
选择 3 个核心业务的接口方法撰写 JML 规格
-
countSales()
/*@ public normal_behavior @ ensures \result == countSales; @*/ public /*@ pure @*/ int countSales();
-
receiveOrder(int productSum)
/*@ public normal_behavior @ requires (productSum >= 1); @ assignable countSales; @ ensures countSales == \old(countSales) + productSum; @ also @ public exceptional_behavior @ signals (ProductSumNotEnoughException e) @ !(productSum >= 1) @*/ public void receiveOrder(int productSum) throws ProductSumNotEnoughException;
-
getAd(): void
/*@ public normal_behavior @ requires (\exists int i; 0 <= i && i <= adPool.size; @ adPool[i].equals(preference)); @ assinable receivedAds; @ ensures receivedAds == \old(receivedAds) + 1; @ also @ public exceptional_behavior @ signals (ProperAdNotFoundException e) @ !(\exists int i; 0 <= i && i <= adPool.size; @ adPool[i].equals(preference)); @*/ public void getAd() throws ProperAdNotFoundException;
-
感觉这个改动还挺有意思的,有没有一种可能是 21 级课程会加入呢(喜)