2022-OO-Unit3
2022-OO-Unit3
mashiroly
1. 概述
又到輕鬆愉快的部落格周了(赫赫,hw13似乎不是很容易呢),回顧一下過去一個月做了什麼吧。
本單元的目標是學習基於規格的層次化設計,需求是在JML規格的指導下,實現一個模擬社交關係系統。作業重點是閱讀和理解JML規格,掌握異常處理和測試方法,體會設計和實現的分離。
2. 架構設計
2.1 圖結構
引入圖的劃分Partition
,可將社交網路如下圖建模,基本囊括了所有關聯關係。
具體而言,自上而下的構建可分為以下幾步:
-
Network
中維護三個集合。為方便查詢,使用HashMap<id, Class>
。 -
引入新類
Relation
Edge
類),維護關聯的Person
和value
。在
Network
中維護id(自增),同樣將HashMap<id,Edge> EdgeSet
作為屬性。 -
引入新類
Partition
,表示以Person
通過Relation
關聯的連通子圖(劃分)。其儲存劃分內部的所有Vectex(Person)
和Edge(Relation)
,方便查詢。在
Network
中維護id(並查集更新根結點id),同樣將HashMap<id,Partition>PartitionSet
作為屬性。 -
根據JML規格撰寫三個類,對於集合能用
HashMap<id, Class>
盡用。注意Person
MessageSet
需要維護佇列。
設計與實現分離初步體現於此,我們不需要如JML描述逐個遍歷,只要保證符合規格,可以選擇合適的資料結構。
2.2 維護策略
不涉及Partition
的結構維護:
- 基本策略:由於大量使用HashMap,幾乎只需要使用
put()
進行新增操作 。有遍歷時刪除操作的Map可以使用執行緒安全的ConcurrentHashMap。Arraylist的刪除使用removeIf()
。 - 不降複雜度的維護:部分指令複雜度無法優化到O(1),且隨圖結構更新改變,如
qrm
,sim
。維護一個有效位。
三次作業經歷的最大“重構”應該是:為提高並查集+kruskal的效率,從hw9到hw10引入Partition
- 並查集:路徑壓縮。
ar
指令執行時合併劃分,更新劃分屬性,將劃分內所有Person
新定義的ParentId
屬性設為劃分內的某個id,這個id更新為Network
中該劃分在Map中的key。 - 平均年齡、方差等
Group
屬性:Group
自身維護ageSum
,ageSquareSum
,在atg
,dfg
時更新,時間複雜度能做到O(1)。
公式$$D(X)=E(X2)-E^2(X)$$計算方差,與JML描述精度不同。
參考討論區帖子,有以下公式不丟失精度:$$D(X) = \frac{\Sigma X^2 -2\overline{X}}{n}+(\overline{X})^2$$
3. 效能問題和bug修復
3.1 hw9(悲
週六下午,才測出未堆優化的Prim被鏈式資料+qci打穿了,臨時改寫不帶並查集的kruskal,結果完全寫錯。強測也寄的很慘。
此次作業未引入並查集和劃分,qbs在ar時更新,複雜度O(1)。
3.2 hw10
強測未發現bug。
引入了並查集+kruskal,將qci複雜度優化到O(nlogE) 。
但是qgav複雜度O(n^2),被hack TLE了。之後將qgvs和qgav均優化到O(1)
3.3 hw11
強測未發現bug。
sim使用堆優化的dijkstra,複雜度為O(nlogE)。
互測也沒有被卡效能,但是dce規格理解有誤,以為是刪去所有EmojiMessage,被hack了。感謝僅10條指令讓我一眼看出bug的好哥哥!
4. 測試設計
1. Junit實踐
使用Junit編寫測試方法,個人的操作是通過完全遵守JML規格重新撰寫方法,用assertEquals()
判斷方法實現是否符合不變式。龐大的測試類也需要維護一系列屬性(全域性變數),也需要面向物件,也需要封裝好各種修改圖結構的方法。
但實際編寫中,不可能完全照搬JML,例如sim
,qlc
等方法,規格上就有“尋找一個集合(子圖,鏈)”。隨著方法更加複雜,測試方法寫得越來越像實現的方法。hw10和hw11中,也因此被勸退,沒有繼續進行Junit編寫。
2. 隨機資料生成與對拍
最後還是採用python寫隨機資料生成器,並進行覆蓋性測試。為保證資料質量,生成器也要維護好Person、Group、Message集合(使用id),並從集合中取出id生成指令。為保障異常覆蓋率,我人為地設定隨機數按一定比例生成錯誤指令。同時手動構造邊界資料,如鏈式圖、完全圖、1111等。
這是10w條+5k人的資料,可以看到覆蓋率還是有保證的。未覆蓋的方法幾乎都是規格有定義、但實現上沒有使用的方法。
正確結果唯一就是好啊,不用寫正確性判斷,和小夥伴對拍就好了。(
5. Network拓展
假設出現了幾種不同的Person
- Advertiser:持續向外傳送產品廣告
- Producer:產品生產商,通過Advertiser來銷售產品
- Customer:消費者,會關注廣告並選擇和自己偏好匹配的產品來購買 -- 所謂購買,就是直接通過Advertiser給相應Producer發一個購買訊息
- Person:吃瓜群眾,不發廣告,不買東西,不賣東西
如此Network可以支援市場營銷,並能查詢某種商品的銷售額和銷售路徑等 請討論如何對Network擴充套件,給出相關介面方法,並選擇3個核心業務功能的介面方法撰寫JML規格(借鑑所總結的JML規格模式)
- 新增
Advertiser
,Producer
,Customer
類繼承Person
類; - 新增
Product
類; - 新增
PurchaseMeassage
,Advertisement
類繼承Message
類;
Network
中應該新增方法:新增產品,傳送廣告,購買商品,查詢商品銷售額,查詢商品銷售路徑等。
public interface Network {
/*@ public instance model non_null Produce[] produces;
@ ...
@*/
}
- 新增產品
addProducer
/*@ public normal_behavior
@ requires (\exists int i;0 <= i && i < people.length;people[i].getId() == id && people[i] instance of Procuder);
@ requires !(\exists int i;0 <= i && i < produces.length; produces[i].equals(produce));
@ assignable people;
@ ensures produce.length == \old(produce.length) + 1;
@ ensures (\forall int i; 0 <= i && i < \old(produces.length);
@ (\exists int j; 0 <= j && j < produces.length; produces[j] == (\old(produces[i]))));
@ ensures (\exists int i; 0 <= i && i < people.length; people[i] == person);
@ also
@ public exceptional_behavior
@ signals (PersonIdNotFoundException e) (\forall int i; 0 <= i && i < people.length);
@ people[i].getId() != id
@ || (people[i].getId() == id && !(people[i] instance of Producer));
@ public exceptional_behavior
@ signals (EqualProduceIdException e) (\exists int i; 0 <= i
@ && i < produces.length;produces[i].equals(person));
@*/
public void addProduce(/*@ non_null @*/int id, Produce produce)
throws PersonIdNotFoundException, EqualProduceIdException;
- 傳送廣告
sendAdvertisement
/*@ public normal_behavior
@ requires getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()) &&
getMessage(id).getPerson1() != getMessage(id).getPerson2();
@ requires containsMessage(id) && getMessage(id) instanceof Advertisement getMessage(id).getType() == 0 &&;
@ assignable messages;
@ assignalbe getMessage(id).getPerson2().messages;
@ 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]))));
@ 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)));
@ also
@ public exceptional_behavior
@ requires containsMessage(id) && getMessage(id).getType() == 1 && getMessage(id) instanceof Advertisement
@ requires getMessage(id).getGroup().hasPerson(getMessage(id).getPerson1());
@ assignable people[*].messages
@ 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]))));
@ ensures (\forall Person p; \old(getMessage(id)).getGroup().hasPerson(p);(\forall int i; 0 <= i && i < \old(p.getMessages().size());\old(p.getMessages().get(i+1) == \old(p.getMessages().get(i))));
@ signals (MessageIdNotFoundException e) !containsMessage(id);
@ signals (MessageIdNotFoundException e) containsMessage(id) && !(getMessage(id) instanceof Advertisement);
@ signals (RelationNotFoundException e) containsMessage(id) && getMessage(id) instanceof Advertisement && getMessage(id).getType() == 0 &&!(getMessage(id).getPerson1().isLinked(getMessage(id).getPerson2()));
@ signals (PersonIdNotFoundException e) containsMessage(id) && getMessage(id) instanceof Advertisement && getMessage(id).getType() == 1 && !(getMessage(id).getGroup().hasPerson(getMessage(id).getPerson1()));
@ */
public void sendAdvertisement(int id) throws MessageIdNotFoundException,PersonIdNotFoundException,RelationNotFoundException;
- 查詢產品銷售額
queryPrice
/*@ public normal_behavior
@ requires (\exists int i; 0 <= i && i < produces.length);produces[i].getId() == id);
@ assignalbe none;
@ ensures (\exists int i; 0 <= i && i < produces.length);produces[i].getId() == id && \result == produces[i].getPrice());
@ public exceptional_behavior
@ signals (ProduceIdNotFoundException e) !(\exists int i; 0 <= i && i < produces.length);produces[i].getId() == id);
@ */
public void queryPrice(/*@ non_null @*/int id) throws ProduceIdNotFoundException