窩窩第三單元總結
慣例會有劍三沙雕圖
這是想要調戲天策的一週
JML
理論基礎
出現意義
記得在CO
課上高老闆提到過,咱們設計系統必須強調層次。每一個單元作好本身的事情,管理這些單元的上一級作好管理的事情,逐層管理,層次清晰,便於梳理。這就要求咱們在設計的時候,給各個單元進行明確的分工,即明確它會接收的輸入範圍,以及輸入輸出之間的對應關係。在CO
的時候咱們的設計採用的是引腳、接口定義輸入輸出,經過天然語言描述輸入輸出之間的關係,這樣顯然有失嚴謹性。JML
是一個規範的描述單元模塊功能的語言,它沒有天然語言的二義性,從而更加準確。java
JML
語法
本人以爲語法這個東西實在不必死記硬背,用的時候查手冊就行了。點擊就送語法手冊(嘻~node
規格化設計與契約式編程
本單元工做
這一單元中,咱們的工做主要是根據給定的JML
規格描述,實現相應的接口。我認爲,這是屬於已經規格設計完成,契約已經訂好以後的一個履行契約的過程,而不是契約的制定。(其實前兩個單元能作到規格化設計,能夠極大程度上避免出BUG
)算法
爲何要契約式編程
這裏,不談那些神必的理論問題,姑且談談以前看別人代碼的一些感想。編程
亂是最大的感覺,通常狀況下代碼的混亂程度和BUG
數量是正相關的。常常一個類寫到一半就忘了本身這個類是要作什麼的,和別的類應該怎麼交互。數組
在OOP
中,對類的功能設計是基本,若是寫着寫着就把類的功能給弄混了,整個工程基本上就是一坨混沌。數據結構
契約式編程等於說是在想好各個類的功能以後就明確的把這個功能給肯定下來,立一個契約。接下來要作的就是按照先前立下的契約將模塊各個擊破。架構
固然若是自己的規格設計就有問題,那應該是救不了了。併發
做業梳理
第一次JML
做業
結構圖
此次做業中,因爲結構很簡單,因此,個人設計很是的樸素,就實現了官方要求的兩個類。經過三hash的方法管理路徑和節點。eclipse
代碼統計分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
Main.main(String[]) | 1 | 1 | 1 |
SillyPath.SillyPath(int[]) | 1 | 2 | 2 |
SillyPath.compareTo(Path) | 3 | 4 | 4 |
SillyPath.containsNode(int) | 3 | 2 | 3 |
SillyPath.equals(Object) | 6 | 2 | 6 |
SillyPath.getDistinctNodeCount() | 1 | 1 | 1 |
SillyPath.getNode(int) | 1 | 1 | 1 |
SillyPath.hashCode() | 1 | 1 | 1 |
SillyPath.isValid() | 1 | 1 | 1 |
SillyPath.iterator() | 1 | 1 | 1 |
SillyPath.size() | 1 | 1 | 1 |
SillyPath.toString() | 1 | 1 | 1 |
SillyPathContainer.SillyPathContainer() | 1 | 1 | 1 |
SillyPathContainer.addPath(Path) | 3 | 2 | 4 |
SillyPathContainer.containsPath(Path) | 1 | 1 | 1 |
SillyPathContainer.containsPathId(int) | 1 | 1 | 1 |
SillyPathContainer.getDistinctNodeCount() | 1 | 1 | 1 |
SillyPathContainer.getPathById(int) | 2 | 1 | 2 |
SillyPathContainer.getPathId(Path) | 4 | 1 | 4 |
SillyPathContainer.nodesCntDecrease(Path) | 1 | 3 | 3 |
SillyPathContainer.nodesCntIncrease(Path) | 1 | 3 | 3 |
SillyPathContainer.removePath(Path) | 1 | 1 | 1 |
SillyPathContainer.removePathById(int) | 2 | 1 | 2 |
SillyPathContainer.size() | 1 | 1 | 1 |
Class | OCavg | WMC | |
Main | 1 | 1 | |
SillyPath | 1.91 | 21 | |
SillyPathContainer | 1.92 | 23 | |
Package | v(G)avg | v(G)tot | |
1.96 | 47 | ||
Module | v(G)avg | v(G)tot | |
Project9 | 1.96 | 47 | |
Project | v(G)avg | v(G)tot | |
project | 1.96 | 47 |
因爲此次對功能的需求比較簡單,因此沒怎麼設計架構,但複雜度也很低。ide
BUG
我方
本次因爲功能需求很簡單,因此沒有測出BUG
互測屋
一個互測屋的代碼基本上同樣,沒有找出BUG
第二次JML
做業
結構圖
衆所周知OO
是一門代碼重構課程(暴論
本次做業中,爲了防止出現TLE
的狀況,選擇了放棄架構的美觀,經過面向數據的編程,追求規定數據範圍內的更快速度。
架構上,SillyPath
類照搬第九次做業。FloydGraph
則經過直接在第九次做業的代碼的基礎上添加新功能代碼來實現,因此架構很醜陋。
爲了防止本身使用迪傑斯特拉算法實現cache
的時候出現錯誤,本次做業選擇用實現起來較爲簡單的Floyd
算法規避算法難度。同時,因爲本次做業數據量較小,加上我使用靜態數組實現Floyd
,因此其複雜度高的缺陷並無暴露,反而其常數小、實現簡單的優點在這次做業中表現出色。
代碼統計分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
graph.BfsGraph.BfsGraph() | 1 | 1 | 2 |
graph.BfsGraph.addPath(Path) | 2 | 3 | 4 |
graph.BfsGraph.allocVirtualNode() | 1 | 1 | 2 |
graph.BfsGraph.containsEdge(int,int) | 2 | 2 | 3 |
graph.BfsGraph.containsNode(int) | 1 | 1 | 1 |
graph.BfsGraph.containsPath(Path) | 1 | 1 | 1 |
graph.BfsGraph.containsPathId(int) | 1 | 1 | 1 |
graph.BfsGraph.getDistinctNodeCount() | 1 | 1 | 1 |
graph.BfsGraph.getPathById(int) | 2 | 1 | 2 |
graph.BfsGraph.getPathId(Path) | 4 | 1 | 4 |
graph.BfsGraph.getShortestPathLength(int,int) | 3 | 1 | 3 |
graph.BfsGraph.initVisited() | 1 | 1 | 2 |
graph.BfsGraph.isConnected(int,int) | 3 | 1 | 3 |
graph.BfsGraph.nodesCntDecrease(Path) | 1 | 3 | 3 |
graph.BfsGraph.nodesCntIncrease(Path) | 1 | 3 | 3 |
graph.BfsGraph.nodesMapDecrease(Path) | 1 | 4 | 4 |
graph.BfsGraph.nodesMapIncrease(Path) | 3 | 2 | 3 |
graph.BfsGraph.pathDecreaseUpdate(Path) | 1 | 1 | 1 |
graph.BfsGraph.pathIncreaseUpdate(Path) | 1 | 1 | 1 |
graph.BfsGraph.removePath(Path) | 1 | 1 | 1 |
graph.BfsGraph.removePathById(int) | 2 | 1 | 2 |
graph.BfsGraph.resetMatrix() | 1 | 4 | 9 |
graph.BfsGraph.size() | 1 | 1 | 1 |
graph.BfsGraph.updateDistance() | 1 | 2 | 2 |
graph.BfsGraph.updateOneNodeShortestDistance(int) | 4 | 3 | 4 |
graph.FloydGraph.FloydGraph() | 1 | 1 | 1 |
graph.FloydGraph.addPath(Path) | 3 | 2 | 4 |
graph.FloydGraph.allocVirtualNode() | 1 | 1 | 2 |
graph.FloydGraph.containsEdge(int,int) | 3 | 2 | 4 |
graph.FloydGraph.containsNode(int) | 1 | 1 | 1 |
graph.FloydGraph.containsPath(Path) | 1 | 1 | 1 |
graph.FloydGraph.containsPathId(int) | 1 | 1 | 1 |
graph.FloydGraph.getDistinctNodeCount() | 1 | 1 | 1 |
graph.FloydGraph.getPathById(int) | 2 | 1 | 2 |
graph.FloydGraph.getPathId(Path) | 4 | 1 | 4 |
graph.FloydGraph.getShortestPathLength(int,int) | 4 | 1 | 4 |
graph.FloydGraph.isConnected(int,int) | 4 | 1 | 4 |
graph.FloydGraph.nodesCntDecrease(Path) | 1 | 3 | 3 |
graph.FloydGraph.nodesCntIncrease(Path) | 1 | 3 | 3 |
graph.FloydGraph.nodesMapDecrease(Path) | 1 | 4 | 4 |
graph.FloydGraph.nodesMapIncrease(Path) | 3 | 2 | 3 |
graph.FloydGraph.pathDecreaseUpdate(Path) | 1 | 1 | 1 |
graph.FloydGraph.pathIncreaseUpdate(Path) | 1 | 1 | 1 |
graph.FloydGraph.removePath(Path) | 1 | 1 | 1 |
graph.FloydGraph.removePathById(int) | 2 | 1 | 2 |
graph.FloydGraph.resetMatrix() | 1 | 3 | 6 |
graph.FloydGraph.size() | 1 | 1 | 1 |
graph.FloydGraph.updateDistance() | 7 | 6 | 10 |
graph.Main.main(String[]) | 1 | 1 | 1 |
graph.SillyPath.SillyPath(int[]) | 1 | 2 | 2 |
graph.SillyPath.compareTo(Path) | 3 | 4 | 4 |
graph.SillyPath.containsNode(int) | 3 | 2 | 3 |
graph.SillyPath.equals(Object) | 6 | 2 | 6 |
graph.SillyPath.getDistinctNodeCount() | 1 | 1 | 1 |
graph.SillyPath.getNode(int) | 1 | 1 | 1 |
graph.SillyPath.hashCode() | 1 | 1 | 1 |
graph.SillyPath.isValid() | 1 | 1 | 1 |
graph.SillyPath.iterator() | 1 | 1 | 1 |
graph.SillyPath.size() | 1 | 1 | 1 |
graph.SillyPath.toString() | 1 | 1 | 1 |
Class | OCavg | WMC | |
graph.BfsGraph | 2.36 | 59 | |
graph.FloydGraph | 2.65 | 61 | |
graph.Main | 1 | 1 | |
graph.SillyPath | 1.91 | 21 | |
Package | v(G)avg | v(G)tot | |
graph | 2.5 | 150 | |
Module | v(G)avg | v(G)tot | |
Project10 | 2.5 | 150 | |
Project | v(G)avg | v(G)tot | |
project | 2.5 | 150 |
其中BfsGraph
是我實現的一個測試版本,寫的比較醜,故複雜度很高。
由上可見,Floyd
在實現複雜度上也是比較低的。即便是我這種兩次做業直接寫一塊的屎架構,仍然能將複雜度控制在可接受的範圍內。
BUG
我方
因爲Floyd
實現很是簡單,本次做業成功規避了算法實現上的錯誤。同時因爲面向數據的編程,個人程序運行效率較高,沒有出現TLE
的狀況。
互測屋
本次互測你們水平相近,代碼功能要求不復雜,且時間比較寬鬆,故而你們都和平度過。
第三次JML
做業
結構圖
本次做業我採用的是分層Floyd
的算法。因爲此次須要計算的最短路徑種類較多,每一個各寫一個Floyd
過於麻煩,因此我實現了一個計算類FloydHelper
用於封裝Floyd
算法。這次做業因爲處於各類ddl
的併發期,我沒能很好地去設計架構,更多的是直接一股腦地塞進了一個SillyRailwaySystem
類中,使得這個類的長度幾近500行,這是設計上的失敗。
代碼統計分析
Method | ev(G) | iv(G) | v(G) |
---|---|---|---|
railway.FloydHelper.FloydHelper(int,int) | 1 | 1 | 1 |
railway.FloydHelper.calculate(int[][]) | 5 | 6 | 8 |
railway.Main.main(String[]) | 1 | 1 | 1 |
railway.SillyPath.SillyPath(int[]) | 1 | 2 | 2 |
railway.SillyPath.compareTo(Path) | 3 | 4 | 4 |
railway.SillyPath.containsNode(int) | 3 | 2 | 3 |
railway.SillyPath.equals(Object) | 6 | 2 | 6 |
railway.SillyPath.getDistinctNodeCount() | 1 | 1 | 1 |
railway.SillyPath.getNode(int) | 1 | 1 | 1 |
railway.SillyPath.getUnpleasantValue(int) | 2 | 1 | 2 |
railway.SillyPath.hashCode() | 1 | 1 | 1 |
railway.SillyPath.isValid() | 1 | 1 | 1 |
railway.SillyPath.iterator() | 1 | 1 | 1 |
railway.SillyPath.size() | 1 | 1 | 1 |
railway.SillyPath.toString() | 1 | 1 | 1 |
railway.SillyRailwaySystem.SillyRailwaySystem() | 1 | 2 | 2 |
railway.SillyRailwaySystem.addPath(Path) | 4 | 2 | 4 |
railway.SillyRailwaySystem.addPathTwoMapInformation(Path,int) | 1 | 3 | 3 |
railway.SillyRailwaySystem.allocVirtualNode() | 1 | 1 | 2 |
railway.SillyRailwaySystem.containsEdge(int,int) | 3 | 2 | 4 |
railway.SillyRailwaySystem.containsNode(int) | 1 | 1 | 1 |
railway.SillyRailwaySystem.containsPath(Path) | 1 | 1 | 1 |
railway.SillyRailwaySystem.containsPathId(int) | 1 | 1 | 1 |
railway.SillyRailwaySystem.getConnectedBlockCount() | 1 | 1 | 1 |
railway.SillyRailwaySystem.getDistinctNodeCount() | 1 | 1 | 1 |
railway.SillyRailwaySystem.getGlabolMap(int[][],HashMap<Integer, int[][]>) | 1 | 6 | 6 |
railway.SillyRailwaySystem.getLeastTicketPrice(int,int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.getLeastTransferCount(int,int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.getLeastUnpleasantValue(int,int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.getPathById(int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.getPathId(Path) | 4 | 1 | 4 |
railway.SillyRailwaySystem.getShortestPathLength(int,int) | 4 | 1 | 4 |
railway.SillyRailwaySystem.getUnpleasantValue(Path,int,int) | 1 | 1 | 1 |
railway.SillyRailwaySystem.isConnected(int,int) | 4 | 1 | 4 |
railway.SillyRailwaySystem.nodesCntDecrease(Path) | 1 | 3 | 3 |
railway.SillyRailwaySystem.nodesCntIncrease(Path) | 1 | 3 | 3 |
railway.SillyRailwaySystem.nodesMapDecrease(Path) | 1 | 4 | 4 |
railway.SillyRailwaySystem.nodesMapIncrease(Path) | 3 | 2 | 3 |
railway.SillyRailwaySystem.pathDecreaseUpdate(Path) | 1 | 1 | 1 |
railway.SillyRailwaySystem.pathIncreaseUpdate(Path) | 1 | 1 | 1 |
railway.SillyRailwaySystem.removePath(Path) | 1 | 1 | 1 |
railway.SillyRailwaySystem.removePathById(int) | 2 | 1 | 2 |
railway.SillyRailwaySystem.removePathTwoMapInformationById(int) | 1 | 1 | 1 |
railway.SillyRailwaySystem.resetMatrix() | 1 | 3 | 6 |
railway.SillyRailwaySystem.search(int,int) | 2 | 2 | 4 |
railway.SillyRailwaySystem.size() | 1 | 1 | 1 |
railway.SillyRailwaySystem.upDateConnectBlock() | 4 | 1 | 6 |
railway.SillyRailwaySystem.upDatePriceMap() | 1 | 1 | 7 |
railway.SillyRailwaySystem.upDateUnpleasantMap() | 1 | 1 | 7 |
railway.SillyRailwaySystem.updateDistance() | 7 | 6 | 10 |
railway.SillyRailwaySystem.updateTransferMap() | 1 | 5 | 5 |
Class | OCavg | WMC | |
railway.FloydHelper | 4.5 | 9 | |
railway.Main | 1 | 1 | |
railway.SillyPath | 1.92 | 23 | |
railway.SillyRailwaySystem | 3.03 | 109 | |
Package | v(G)avg | v(G)tot | |
railway | 2.86 | 146 | |
Module | v(G)avg | v(G)tot | |
Project11 | 2.86 | 146 | |
Project | v(G)avg | v(G)tot | |
project | 2.86 | 146 |
如圖可見,本次個人SillyRailwaySystem
類因爲是直接由上一次的FloydGraph
強行塞進新功能獲得的,因此複雜度極高,層次極差。
BUG
我方
因爲Floyd
隱式存在的正確性優點,因此此次我在正確性上面仍然沒有出錯。同時因爲此次的圖的節點數比第二次做業還要少,因此Floyd
的常數小的優點更加明顯,因此此次做業也沒有出現TLE
的狀況。
互測屋
咱們組的Lancer
在各類對拍中比其餘人慢許多,我意識到他的算法可能有問題,可是因爲時間緣由,此次並無去構造一個強樣例卡他的TLE
,可是咱們組確實有人作到了,只能說,我技不如人吧。
OpenJML
初體驗
整體感受,體驗較差,首先,資料太少,官方ReadMe
寫得比較屎。
其次,各類神祕的版本不兼容問題。
再者,idea
上沒有相應的、成熟的插件。
而且各類語法要求很神祕,課程組給的JML
沒辦法直接運行上去。
體驗
本着體驗的態度,我裝了個eclipse
來運行OpenJML
插件。
初始代碼以下
public class Mult { //@ ensures \result == a * b; public static int mult(int a,int b) { return a * b; } public static void main(String[] args) { System.out.println(mult(2,7)); } }
靜態檢查結果以下
能夠看到,咱們的代碼出問題了。
問題在於 a == 1061067 && b == 1121128
的時候出現了算術溢出的狀況。
修改規格後,代碼以下
public class Mult { //@ requires ( a < 10 && a > -10 && b < 10 && b > -10 ); //@ ensures \result == a * b; public static int mult(int a,int b) { return a * b; } public static void main(String[] args) { System.out.println(mult(2,7)); } }
靜態檢查結果
限制了數據範圍後,可以經過測試。
小結
OpenJML
這個工具自己很是有意義,用邏輯驗證來驗證正確性,是很是可靠的。
可是,這個工具在推廣上尚有不足。使用過於繁瑣,兼容性差,文檔很迷。
做爲一個用戶,我是不太傾向於使用這麼一個軟件的。
JMLUnit
初體驗
因爲課程組給的規格沒法直接使用,本着體驗工具的態度,我本身寫了個很是簡單的程序用以體驗。
package Miao; public class Main { //@ requires true; //@ ensures \result == a * b; public static int mult(int a,int b) { return a * b; } public static void main(String[] args) { mult(2,7); } }
生成的樣例以下
能夠看出,JMLUnit
的樣例主要針對各類邊界條件的檢查。實際上,在通常的開發中,邊界條件由於其容易出錯,會被格外重視,反而不容易出錯。
可以意識到的BUG
都不會成爲BUG
,咱們的測試更多地應該放在各類分支的遍歷上。
評測機一些神必問題
這個單元中個人對拍器出現了神必問題。在往被測程序的進程中寫入時會卡死,我認爲多是緩衝區寫滿的問題,但照理來講,被測程序也一直在讀取,不該該出現這個問題。最後我沒法解決。
因而我直接改爲命令行重定向輸入,從文本輸入。問題得以解決。
單元感想
正面
- 規格化設計和契約式編程是很是好的一個教學重點。
OO
不該該只教怎麼設計各個對象的分工,也應該教導根據已有的分工去履行職責,這一點很是重要。 - 利用
JML
語言描述功能必定程度上消除了天然語言的二義性。
負面
- 過於
DS
。說實在話,這個單元個人重點已經不在規格和架構上了,更多的是在想算法和數據結構的問題,這個單元的DS
難度確實有點大到喧賓奪主的地步了。不過這也是一個很矛盾的問題,課程須要一個區分度,若是把區分度加在JML
的理解上,就必然要讓JML
變得複雜,這也就意味着這個類的功能設計的很複雜、很神必,這顯然是個"屎"設計。那麼區分度就只能在DS
上了。 OpenJML
這個工具當然是個很強大的工具,可是它的侷限性和可用性並無咱們預期的效果。比起用一個很複雜的工具,我相信多數用戶仍是傾向於本身搭一個簡易的對拍器同時本身經過邏輯分析來保證正確性。
遺憾
- 因爲近期
ddl
高發,沒有時間充分地體驗Junit
的測試,甚是遺憾。
建議
- 但願課程平臺增長在官方評測機上運行本身測試樣例的功能,這樣就解決了環境不同帶來的一些爭議。