OO博客作業2:第5-7周作業總結
(1)從多線程的協同和同步控制方面,分析和總結自己三次作業來的設計策略及其變化。
第5次作業:多線程電梯
基本照搬了課件上“生產者-消費者”模型的設計策略,將InputHandler設計為生產者線程,將Scheduler設計為消費者線程,將RequestQueue設計為托盤。生產者與消費者的工作並發,提高效率。同時,每部電梯設計為一個線程,因為每部電梯的運行彼此不幹擾。InputHandler, Scheduler由主線程創建,三部電梯由Scheduler負責創建,這樣使得調度器可以獲取電梯的狀態。但這種策略並不是最優的。
第6次作業:文件監控
這大概是寫得最失敗的一次作業。
按課件方法構建了線程安全的SafeFile類。實現了同步化的訪問文件狀態、寫文件方法。然而對於每個record型請求單獨建一個文件寫入,違背了訓練初衷。
每個請求設計一個“監控器”線程,直接查看對應文件的狀態,與指導書的設計思路差異較大。這使得程序難以應對path-changed等大規模變化。
總的來說,這次多線程之間沒什麽協同可言。同步控制也僅限於對文件狀態的訪問。
第7次作業:出租車調度
這次作業的設計策略基本照搬第5次作業。輸入處理線程為生產者,調度器線程為消費者,請求隊列為托盤。每輛出租車為一個線程,依然由調度器創建。
寫了線程安全的SafeFile類用於輸出文本文件。乘客請求、參與搶單的出租車信息由調度器負責寫入,出租車運行情況由出租車線程負責寫入,實現了線程協同。
(2)基於度量來分析自己的程序結構
1)OO程序代碼度量
第5次作業:
類名 | 屬性個數 | 方法個數 | 代碼規模 | main / run方法規模 | 點評 |
Elevate_5 | 0 | 1 | 27 | 17 | |
Elevator | 14 | 7 | 176 | 40 | 電梯控制邏輯復雜。 |
Floors | 2 | 4 | 24 | ||
InputHandler | 10 | 6 | 158 | 15 | 寫得太隨意,很多屬性更適合作為局部變量。 |
Request | 4 | 59 | |||
-CarryRequest | 1 | 3 | |||
-FloorRequest | 1 | 3 | |||
RequestQueue | 6 | 4 | 56 | ||
Scheduler | 7 | 8 | 209 | 40 | 保留了上次的調度器代碼。 |
Tray | 3 | 4 | 76 |
第6次作業:
類名 | 屬性個數 | 方法個數 | 代碼規模 | main / run方法規模 | 點評 |
Detail | 4 | 3 | 64 | ||
FileState | 4 | 3 | 26 | ||
InputHandler | 9 | 6 | 107 | 相似的問題 | |
Monitor | 8 | 2 | 60 | 30 | |
SafeFile | 2 | 15 | 110 | 為測試者寫了較多的方法 | |
Summary | 4 | 3 | 64 | ||
TestDrive | 0 | 1 | 15 | ||
TestThread | |||||
Trigger | 3 | 8 | 94 | 設計有問題 |
第7次作業:
類名 | 屬性個數 | 方法個數 | 代碼規模 | main / run方法規模 | 點評 |
_Point | 2 | 7 | 40 | ||
CHandler | 9 | 4 | 76 | 32 | |
CityMap | 4 | 4 | 114 | 算法代碼比較長 | |
FHandler | 2 | 4 | 55 | ||
Queue | 3 | 3 | 49 | ||
Request | 3 | 3 | 24 | ||
SafeFile | 2 | 5 | 57 | ||
Schedule | 6 | 6 | 146 | 44 | 調度方法夾雜輸出代碼 |
Taxi | 16 | 9 | 204 | 40 | 運行方法夾雜大量輸出代碼 |
TestDrive | 0 | 1 | 18 | 12 |
3)各次作業的類圖
第5次作業:由於指導書給出了上次電梯的參考類圖,自己的設計基本遵循了指導書。但是Tray這個類沒能發揮出應有的作用,只是單純地緩存請求。更好的做法是同時緩存電梯狀態,電梯運行時更新,調度器需要時讀取。
第6次作業:寫得很糟糕,有“面條代碼”的嫌疑。對Monitor和Trigger兩個類的職責沒有明確,而且Summary和Detail的代碼有大量重用。
第7次作業:很多地方參考借鑒了第5次作業(多線程電梯)。各個類的職責相對清晰。
4)UML的協作圖(從上到下依次是第5,6,7次作業的圖)
5)設計原則檢查(僅針對第7次作業)
DIP (Dependency Inversion Principle):高層次不依賴低層次。
程序有兩個渠道獲取輸入,一是通過文本文件獲取城市地圖,二是通過控制臺獲取叫車請求。我的程序沒有將這兩種途徑進行抽象和歸納,導致程序對輸入方式變化的適應性不好。
命名:
在為類、實例變量命名時我盡可能遵循“顧名思義”,但是走向了另一個極端。我將一個記錄點信息的類命名為“Point”,結果後來發現有java.awt.Point這個類,使用GUI時很不方便。無奈之下我將自己寫的類改名為“_Point”,花了一部分時間改名字。在顧名思義的同時,也盡量不要取過於簡單、大眾化的名字,否則容易與JAVA類庫重名,造成麻煩。
另外,eclipse建議包名首字母小寫,類名首字母大寫,作為初學者還是遵守得好。
(3)分析自己程序的bug
第5次作業:
所有的“正確請求測試多線程功能”樣例均有錯(公測未發現或是被發現bug)。
問題所在的類是Scheduler。
被發現了一個CRASH是由於想向文件寫入結果,但文件已經關閉導致的。這暴露出我對於多線程同步控制尚未掌握,且自己構造的測試樣例太寬松,很容易露出破綻。
從設計結構角度分析,我把所有的電梯調度方法全寫入一個調度器類。Scheduler類的代碼規模更是破了200行,遠遠超過其它類。
在總結課上,我才了解到有更加均衡的實現方式。總調度器只負責將請求分配給不同的電梯,而每部電梯對應一個專門的調度器負責排請求的執行先後順序。這種方式顯然更合適,既是遵循均衡原則,又符合實際的應用場景。
第6次作業:
對方沒有進行公測,且沒有發現bug,故不作分析。
第7次作業:
被發現了一個bug,為新增節點“等待狀態下出租車的運行不具備隨機性”。
問題所在的類為Taxi,方法為run_edge
這個bug被發現在情理之中,因為我曾經嘗試過實現出租車的隨機運行,但是失敗了(出租車發生了瞬移)。最終改成了一版確定性運行的方法。
(4)分析自己發現別人程序bug所采用的策略
很多學生(包括我)在編寫多線程程序時遇到的一個技術難點就是如何讓程序正常結束(在eclipse下是在控制臺上方看到<terminated>)。盡管這不是指導書的硬性要求,但是能夠正常結束的程序勢必帶來更好的用戶體驗和更強的魯棒性。如果被測者沒有在readme中對程序的結束進行說明,或是說明能結束但實際上沒實現的,則可以報告bug。
邊界條件是在編程過程中需要著重考慮的。一些在實際情況中完全有可能出現,但編程者很難考慮到的測試點上往往容易發現bug。第7次作業我通過構造“乘客請求起始坐標正好是出租車所在位置”的樣例,成功發現了別人的bug。
後期的作業不但註重程序正確性,而且對程序設計原則也提出了要求。測試者可以閱讀被測代碼,發現其中不符合設計要求的部分,予以扣分。
(5)心得體會
1)線程安全
在線程安全方面,重點是把握課上重點講過的“生產者-消費者”模型,學會將共享對象的存取方法設置為同步化的,以及方法內部具體的寫法。拿到指導書之後,在分析程序需要哪些對象、對象之間關系時,必須額外考慮哪些對象需要被共享的問題。無論是自己寫被共享類的代碼,還是包裝為線程安全類,都要想清楚哪些方法需要保證原子性,並使用synchronized關鍵字。
2)設計原則
從第7講開始,課後作業將程序設計原則也納入了作業要求和互評範圍。設計原則與程序的正確性沒有直接的關系,而是為了讓程序具有較好的可讀性、可擴展性。在軟件開發行業,用戶需求發生變更是經常遇到的事件。遵循課上講過的一些設計原則可以增強程序的可擴展性。在需求變更時,只需要對原有程序進行有限的修改,而不是推倒重來。自己在3次電梯作業中,後面的作業往往不能有效利用前面作業的代碼,原因在於自己沒有在一開始構造一個良好的設計,沒有遵循設計原則。另一些原則是為了程序可讀性,因為將來我們開發的程序只是一個大項目的一部分。不但要保證別人放心調用,而且最好在代碼中清楚寫出自己的邏輯。可能經過後面幾次作業的訓練,我能夠更深入地理解設計原則。
OO博客作業2:第5-7周作業總結