轉眼第三單元的學習就結束了,這學期oo的餘額剩餘也很少了,且行且珍惜java
這個單元的主題是規格,依賴於JML來展開的學習,首先不得不吐槽一下JML是真的商業化產品有點少,不過畢竟JML不是重點,規格纔是重點,因此也就很少說了算法
相比於以前單元,這個單元並無讓你們去全套實現一個系統,而是去根據規格,也就是"契約"去實現一些偏底層的組件,知足相應的要求,同時,做業也是沒有了以前指導書上的長篇文字描述,取而代之的是JML的規格描述。shell
不急着說做業,談談我的理解吧。我的認爲,規格的使用主要有兩大好處,一個是便於測試的展開,不管是javadoc
仍是JML
,它們都比較全面的表述了一個方法應該達到什麼樣的效果,並對於一些特殊狀況或是邊界狀況又該如何處理,能夠說是一次全面的思考,通過這番思考,寫出規格,咱們就能夠根據規格作出相應的測試,特別是單元測試;第二是便於溝通和協調,在一個多人團隊合做開發的項目中,有一個共用的規範是頗有意義的,這樣能夠直接統一你們的思路,每一個人按規格實現方法,規格就是成員之間的承諾,而JML
的準確表達的能力又是讓這個效果進一步放大,雖說不是很好讀。緩存
好,那麼下面說說做業吧。數據結構
首先先說一說JML相關的生態架構
JML語言其實對於複雜一些的邏輯,描述其相應的規格自己就是一件比較難的事情,會把規格寫的不少很複雜,也所以,閱讀難度就更是可想而知了,雖然咱們鼓勵寫簡單函數,寫短函數,可是不少時候,一些複雜的業務邏輯情景必需要不少的內容來支持才能寫出相應的規格,這件事情仍是無法避免的框架
JML的語法這裏就很少說了,指導書寫的很詳細,網上的資料也不少,因此這裏說一說JML,或者說是規格所體現出的思想和用法函數
咱們的規格包括下面幾個部分:工具
經過規定這幾個部分,一個函數的行爲基本就被框住了,也就是,調用者從這個規格能夠知道,這個函數須要什麼樣的數據,而後做用後會產生哪些反作用,最終的結果會是怎麼樣,這就基本徹底歸納了一個函數對於調用者的須要內容單元測試
同時,實現者經過閱讀規格,能夠知道本身的函數要處理什麼樣的情形的數據,對於不符合的數據要怎麼處理,處理過程當中要只能改變哪些變量和屬性的值,最終應該達到什麼樣的效果
經過規格,調用者和實現者的思想就統一了,造成了一種契約,而JML只是以一種無二意性的符號語言的形式將規格的內容展示了出來,JML自己沒什麼神奇的,厲害的是規格和契約的概念
至於JML的工具鏈是真的沒啥,從IDEA這樣的比較知名的IDE上都沒有相關的開發工具這件事上也可見一斑,可是聰明的你們仍是找到了不少相關的工具,基本能夠最大化地發揮JML的做用
這裏將工具鏈一一列出,下面會詳細介紹到相關的內容:
OpenJML
能夠靜態檢查JML語法的正確性,同時也能夠做爲編譯時的依賴包,生成能夠運行時檢查程序是否符合JML規格的class
文件JMLUnitNG
能夠根據JML自動生成TestNG
測試文件的工具(Junit大法好),親測對於邊緣測試等的支持仍是不錯的SMT Solver
這個好像和JML沒啥關係,是用來證實程序邏輯等價的,也就是能夠用來從形式上證實兩個函數的效果是等價的不實名感謝倫佬的博客
首先是先進行相應的編譯和測試文件生成
~/Desktop java -jar openjml-0.8.42-20190401/jmlunitng.jar demo/Demo.java ~/Desktop javac -cp openjml-0.8.42-20190401/jmlunitng.jar demo/**/*.java ~/Desktop ./openjml-0.8.42-20190401/openjml -rac demo/Demo.java
生成和編譯出來的文件中,Demo_JML_Test就是最終的測試類
用jmlunitng
來運行這個主類,能夠獲得以下的結果
~/Desktop java -cp openjml-0.8.42-20190401/jmlunitng.jar: demo.Demo_JML_Test [TestNG] Running: Command line suite Passed: racEnabled() Passed: constructor Demo() Passed: static compare(-2147483648, -2147483648) Failed: static compare(0, -2147483648) Failed: static compare(2147483647, -2147483648) Passed: static compare(-2147483648, 0) Passed: static compare(0, 0) Passed: static compare(2147483647, 0) Failed: static compare(-2147483648, 2147483647) Passed: static compare(0, 2147483647) Passed: static compare(2147483647, 2147483647) Passed: static main(null) Passed: static main({}) =============================================== Command line suite Total tests run: 13, Failures: 3, Skips: 0 ===============================================
能夠看到測試對於邊界條件的支持仍是作得很好的,對於int類型數據的邊界測試作得是比較徹底的
惋惜這個工具的能力還不是很強,不少場景還不能勝任,不得不說是這個工具的一大遺憾
我不打算一次一次的分析了,這麼作可能有點違規,可是我以爲第三次做業就能徹底涵蓋我前兩次做業的設計
那麼先上UML圖
首先誇一波這個單元做業的迭代開發設計,每次的迭代徹底不會影響以前系統的功能,徹底不存在像迭代過程當中,原來對的時候後來又不對了的狀況。
這三次做業我都是用的繼承下來實現的,能夠看UML圖中間的那兩根曲折的藍線,MyRailWaySystem
繼承MyGraph
繼承PathContainer
,這樣的好處是,不用複製粘貼,以前的實現是通過考驗的,咱們直接繼承下來,而後在此基礎上作擴展,就能夠在絲絕不影響原來功能的狀況下,實現新的功能。
而後每次的迭代由於都有新的功能加入,因此能夠每次都設計新的一組專用的數據結構來專門負責實現相應的功能,彼此之間不會有任何影響,解耦一級棒
特別這裏重點說一下第三次做業,第三次做業代表上是實現了四個新功能,可是細看,除了連通塊之外,其餘三個功能的實現方式都基本是同樣的,這裏筆者的作法是Dijstra+拆點,咱們能夠發現,其實計算三種維度下的最優路線的主體方式基本都是一致的,就是Dijstra的算法框架,惟一不一樣的是路徑權值的計算方式,因此,能夠將這些計算邏輯分離出來,單獨造成一個類,這裏對應筆者的ComputeCore
,而三個繼承得來的實體類只須要實現計算路徑權值的方法就能夠了,這樣作到了代碼最大程度的複用,nice!
在此特別插播一件筆者本人的親身經歷,筆者最開始使用Dijstra算法的時候,沒有使用堆優化,致使隨機6000條指令的運行時間能夠達到50s+,後來在加堆優化的時候,由於筆者的設計將全部的公共運算邏輯都分離了出來,因此只用在ComputeCore
類中進行添加堆優化算法,就順利解決了這個問題,沒有引入任何亂七八糟的神奇bug,這就是這樣設計的好處,筆者也是真正親身體驗了一把什麼叫作易於修改和維護。
而後差很少就是這樣了,不行,這樣太水了,會被罵的,下面就說說一些設計細節吧。
首先是拆點,首先感謝xwl大師提出的Pair
概念,筆者在此稍微提供一點小技巧。
在筆者實現和使用Pair
類的時候,發現反覆new
新的Pair
會出現同路線上的同個站點擁有多個不一樣的Pair
的狀況,在此,筆者強推單例模式,同個在Pair
類中維護全部已生成的Pair
對象的緩存,從而避免產生多個重複的Pair
,保證了Pair
的惟一性
其次就是建議單獨分離出建模層,也就是數據層,而後計算層使用建模層的數據來進行相關的計算。
具體體現就是上面的BaseGraph
和SplitPointGraph
這兩個類,這兩個類分別服務於第二次和第三次做業中新添加的功能,而使用者只需從中獲取相應的數據而無需關心數據是如何被組織起來的,只用知道數據已經被組織好了就足夠了,依然是能夠進一步解耦,更加知足單一職責原則。
而關於緩存,筆者這裏單獨實現了一個GraphCache
類,專門用來實現緩存,其本質就是一個HashMap
,加了一些判斷緩存是否命中,添加緩存以及緩存失效的方法,實現起來是很簡單的,可是用起來還挺好使的。
至於別的,特別是算法方法,筆者連堆優化都不知道加就不在這裏賣醜了。
下面給出整個設計的度量
由於類和方法太多,就不一一列舉了,這裏只給出總體結果
ev(G) | iv(G) | v(G) | OCavg | WMC | |
---|---|---|---|---|---|
Total | 169.0 | 167.0 | 215.0 | 209 | |
Average | 1.58 | 1.56 | 2.01 | 1.95 | 12.29 |
能夠看到,總體的複雜度都是很低的,在詳細的統計中,也只有個別的方法複雜度較高,不過也每每是因爲經典算法自己不可避免的緣由致使,因此總體結果仍是比較可觀的。
三次做業中第一次做業強測過程當中出現過bug
主要問題出如今TLE上,緣由是每次查詢不一樣點的個數的時候都要暴力遍歷整個容器,在頻繁查詢的壓力測試下,時間上的表現並很差
解決方法也很簡單,就是添加一個HashMap來維護每一個點出現的次數,每次詢問的時候,直接返回HashMap的鍵的個數便可
又到了我瞎BB的時間了~~
這三次做業其實總體難度不算難,但也不算簡單,特別是對於像筆者這樣算法水平比較差的人,第三次做業沒有討論區是不可能的,這輩子作做業都不能沒有討論區的,確實是比較有挑戰。
不過這個系列做業的迭代設計確實是好,尤爲是第三次若是使用方法得當的話,代碼複用的效果更好,可是建議或許能夠給你們一些算法上的啓示,這樣應該更有利於你們搭建出架構良好的系統。
不過說了這麼多,好像這個單元的主題是規格額,不過這也無法說,畢竟也不能讓同窗手寫規格,而後助教大大們目力給分不是,不過既然是一個團隊開發技術,那麼來年嘗試一下組隊開發大一些的項目是否是會更好,讓你們能夠切身體會到用規格而不是用嘴撕逼的好處。
差很少就是這樣了,怎麼說是又學了一個從未接觸過的領域,收穫頗豐。