在最近的一個月的課程中,筆者對於規格化編程進行了深刻的學習。運用面向對象抽象思想對編寫的程序進行過程抽象、異常處理、數據抽象、類的層次規格與迭代等等規格設計,使得程序結構化程度提升,具備更好的可維護性和複用性。本文經過分析並總結近三次做業規格設計狀況,分享我在規格化程序設計上的看法與體會。java
編號 | 類型 | 所在類 | 方法名稱 | 代碼行數 | 詳細 |
---|---|---|---|---|---|
1 | 前置條件不規範 | InputHandler | parseOrderReq | 5 | 未使用形式語言 |
2 | 前置條件不規範 | InputHandler | parseRoadChangeReq | 5 | 未使用形式語言 |
3 | 前置條件不規範 | InputHandler | parseSearchTaxiReq | 4 | 未使用形式語言 |
4 | 前置條件不規範 | InputHandler | parseSearchStateReq | 7 | 未使用形式語言 |
5 | 後置條件爲實現算法 | LoadFileUnit | setFlow | 23 | 未使用形式語言表示調用者看到的變化 |
6 | 後置條件爲實現算法 | LoadFileUnit | setTaxi | 34 | 未使用形式語言表示調用者看到的變化 |
7 | 後置條件爲實現算法 | LoadFileUnit | setReq | 31 | 未使用形式語言表示調用者看到的變化 |
8 | 後置條件爲實現算法 | Map | setTaxi | 1 | 未使用形式語言表示調用者看到的變化 |
9 | 後置條件爲實現算法 | ReqHandler | markServableTaxi | 4 | 未使用形式語言表示調用者看到的變化 |
10 | 後置條件爲實現算法 | ReqHandler | assignTaxi | 18 | 未使用形式語言表示調用者看到的變化 |
11 | 後置條件爲實現算法 | ReqHandler | run | 12 | 未使用形式語言表示調用者看到的變化 |
12 | 後置條件邏輯錯誤 | TaxiAction | run | 85,91,931 | 後置條件未描述方法全部的影響 |
類型 | 總計 | 平均代碼行數 | 最大代碼行數 |
---|---|---|---|
前置條件不規範 | 4 | 5 | 7 |
後置條件爲實現算法 | 6 | 18.5 | 34 |
後置條件邏輯錯誤 | 1 | 89.7 | 93 |
總計 | 11 | 33.6 | 93 |
在第九次做業中,根據需求須要加入道路開關功能以及增長用於初始化系統的文件讀取指令,而且爲全部方法補充過程規格。在此次做業中因爲絕大部分代碼都是來自上一次做業,許多方法在實現前僅考慮了SOLID原則2而未考慮規格設計,每一個方法的規格都是在實現後補充上去的(這在順序上是倒置的)。有一小部分的方法在功能與產生的做用比較繁雜,難以用形式語言進行描述,最後只能用天然語言做爲替換。但對於這些方法使用天然語言也不太好說清楚其後置條件,最後致使了後置條件爲算法是實現的過程的錯誤。此外,在使用形式語言描述的時候,對於一些方法的處理邊界的描述上存在一些缺陷。
在完成此次做業前,筆者學習了使用異常拋出來區分正常狀況與異常狀況。但因爲在此以前設計代碼時未考慮使用反射機制來處理異常狀況,而使用形如null
等變量來看成異常狀況的返回。若是要加入這一功能須要重構大部分方法中的討論狀況。因爲時間關係,在此次做業中只在新加入的部分使用了異常處理機制。於是在舊代碼的部分的規格設計中對於異常狀況沒有顯示錶示,而是返回一些無心義的數據(從調用者的角度來看是不友好的)。
在測試別人的程序時發現的規格問題基本與個人類似,基本是方法的冗雜導致規格的後置條件爲實現過程或者後置條件有遺漏。正則表達式
在此次做業中,根據需求僅需增長路口的紅綠燈功能,工做量較少(雖然計算時間和流量比較困難),所以筆者將以前寫的代碼根據過程抽象原則進行了優化。對功能較多的方法進行了重構,將其功能進行了分割,分散至不一樣類或者方法當中。在此以後程序中絕大多數的方法都可以用較爲簡潔的形式語言描述。所以在此次做業的測試階段,對方未報告規格錯誤。
此外,需求要求補充每一個類的類規格、抽象函數以及對象有效性驗證方法3。因爲最初設計時着重考慮了SOLID原則,程序中每一個類的功能是比較明確的。於是增長類規格的困難不大,在互測階段也沒被報告錯誤。算法
在此次做業中,根據需求須要對出租車種類進行擴充,增長一種能知足新需求(不贅述)的出租車,以及實現迭代輸出服務記錄的功能。在繼承前一種出租車的同時要着重考慮里氏替換原則,實現有效的子類設計,而且還需附有有效性論證。具體體如今子類方法與父類方法的前置條件與後置條件的空間上。在個人程序中,子類重寫父類方法過程時僅對後置條件進行了擴充,使其能知足里氏替換原則。於是在互測階段未被報告錯誤。在我測試的程序中,設計者將全部父類的方法複製到類子類中,並對細節進行了改動。雖然這種作法有不少贅餘,但經過論證也未發現問題。編程
優化前:緩存
public synchronized void setTaxi(int index, int locaX, int locaY, TaxiState state) /** * @REQUIRES: 0<=index<100;0<=locaX<=79;0<=locaY<=79; * @MODIFIES: gui * @EFFECTS: (在GUI中將編號爲index的出租車設置爲state狀態,移至(locaX,locaY)位置); * @THREAD_REQUIRES:\locked(this); */
優化後:安全
public synchronized void setTaxi(int index, int locaX, int locaY, TaxiState state) /** * @REQUIRES: (0<=index<100);(0<=locaX<=79);(0<=locaY<=79); * @MODIFIES: gui * @EFFECTS: (gui.taxi[index].locaX==locaX)&&(gui.taxi[index].locaY==locaY)&&(gui.taxi[index].state==state); * @THREAD_REQUIRES: \locked(this); */
優化前:app
public RoadChangeReq parseRoadChangeReq(String input, long time){ /** * @REQUIRES: input符合道路更改請求格式; * @EFFECTS: \result==解析後的道路更改請求對象; */ Matcher roadReqMatcher = this.roadReqPattern.matcher(input); if(roadReqMatcher.matches()){ return new RoadChangeReq(roadReqMatcher.group(1), roadReqMatcher.group(2), roadReqMatcher.group(3), roadReqMatcher.group(4), roadReqMatcher.group(5), time); } else return null; }
優化後:ide
public RoadChangeReq parseRoadChangeReq(String input, long time) throws Exception{ /** * @REQUIRES: input!=null; * @EFFECTS: (!roadReqPattern.match(input))==>(\result==解析後的道路更改請求對象); * (!roadReqPattern.match(input))==>exception_behavior(Exception); */ Matcher roadReqMatcher = this.roadReqPattern.matcher(input); if(roadReqMatcher.matches()){ return new RoadChangeReq(roadReqMatcher.group(1), roadReqMatcher.group(2), roadReqMatcher.group(3), roadReqMatcher.group(4), roadReqMatcher.group(5), time); } throw new Exception("不符合道路更改請求格式");// 能夠自定義異常。 }
優化前:函數
public void setTaxi(int taxiNo, TaxiState state, int credit, int locaX, int locaY){ /** * @REQUIRES: 0<=taxiNo<100;credit>=0;0<=locaX<79;0<=locaY<79; * @MODIFIES: this.set,gui * @EFFECTS: 更新出租車位置。 */ this.set[taxiNo].setTaxiInfo(state, credit, locaX, locaY); }
優化後:學習
public void setTaxi(int taxiNo, TaxiState state, int credit, int locaX, int locaY){ /** * @REQUIRES: 0<=taxiNo<100;credit>=0;0<=locaX<79;0<=locaY<79; * @MODIFIES: this.set,gui * @EFFECTS: this.set[taxiNo].locaX==locaX; * this.set[taxiNo].locaY==locaY; * this.set[taxiNo].credit==credit; * this.set[taxiNo].state==state; * gui.taxi[taxiNo].locaX==locaX; * gui.taxi[taxiNo].locaY==locaY; */ this.set[taxiNo].setTaxiInfo(state, credit, locaX, locaY); }
優化前:
class InputListener implements Runnable { private ReqBuffer reqBuffer; private TaxiInfoSet taxis; private Map map; ...... @Override public void run() { /** * @MODIFIES: this.map,this.reqBuffer,this.taxis,System.out * @EFFECTS: (監控到乘客請求)==>(解析並將請求對象加入reqBuffer); * (監控到道路更改請求)==>(解析並更改map中的道路); * (監控到出租車搜索請求)==>(解析並System.out相應信息); * (監控到出租車狀態搜索請求)==>(解析並System.out相應信息); */
優化後:
class InputListener implements Runnable { private ReqBuffer reqBuffer; private TaxiInfoSet taxis; private Map map; /** (省略正則表達式) public static String REQREGEX; public static String ROADREQREGEX; public static String SEARCHTAXIREGEX; public static String SEARCHSTATEREGEX; */ ...... @Override public void run() { /** * @MODIFIES: this.map,this.reqBuffer,System.out * @EFFECTS: (System.in.match(REQREGEX)) * ==>(this.reqBuffer.contains(new Request(System.in))); * (System.in.match(ROADREQREGEX)) * ==>(this.map.road.status==System.in.status); * (System.in.match(SEARCHTAXIREGEX)) * ==>(System.out==this.taxis[System.in.taxiNo].info); * (System.in.match(SEARCHSTATEREGEX)) * ==>(\all Taxi taxi; * this.taxis.contains(taxi) * &&taxi.state==System.in.taxiState; * System.out.contains(taxi.info)); */
優化前:
private void markServableTaxi(ReqWin reqWin) /** * @ REQUIRES: reqWin!=null; * @ MODIFIES: rewWin; * @ EFFECTS: 將符合搶單條件的出租車加入至reqWin的taxiSet中; */
優化後:
private void markServableTaxi(ReqWin reqWin) /** * @ REQUIRES: reqWin!=null; * @ MODIFIES: rewWin; * @ EFFECTS: (\all TaxiInfo taxi; * this.taxiSet.contains(taxi) * &&taxi.isin(reqWin.district); * reqWin.taxiSet.contains(taxi)); */
在此次做業中筆者被報告瞭如下三個錯誤:
public class SysMain { public static void main(String[] argv) { ...... LoadFileUnit loadFileUnit = new LoadFileUnit(); // 構造文件讀取器 loadFileUnit.checkLoad(); // 檢查指令合法性 ...... ReqBuffer reqBuffer = new ReqBuffer(); // 構造請求緩存區 ...... ...... // 構造調度器 ReqHandler reqHandler = new ReqHandler(reqBuffer, taxiSet, map); loadFileUnit.setReq(reqBuffer, map); // 逐個加入請求 new Thread(reqHandler).start(); // 啓動調度線程 ...... }
在此之中請求緩存區的容量爲1。進而很明顯當請求數大於1時因爲此時調度線程還未啓動,沒有線程可以消耗緩存區的請求,致使了主線程一直等待緩存區爲空。而這種狀況不會發生,最後致使了死鎖。
----------分割線----------
地圖不連通,程序退出
並結束程序。----------分割線----------
// 請求窗口集合類的插入方法 public void append(Object req) { Request newReq = (Request)req; System.out.println(newReq); for(int i= 0; i < this.length; i++) { int[] loca = this.list[i].getReq().getLoca(); int[] aim = this.list[i].getReq().getAim(); long time = this.list[i].getReq().getMakeTime(); if(newReq.getLoca()[0] == loca[0] && newReq.getLoca()[1] == loca[1] && newReq.getAim()[0] == aim[0] && newReq.getAim()[1] == aim[1] && newReq.getMakeTime() == time) { System.out.println("相同請求 : " + newReq); // 標記 // } } // 在末尾插入。 this.list = Arrays.copyOf(this.list, ++this.length); this.list[this.length - 1] = new ReqWin(newReq); }
在對集合中已有的請求進行遍歷並判斷爲相同請求後爲停止方法,使相同請求也能被插入至請求隊列。
return;
。----------分割線----------
在第十與十一次做業時筆者未被報告程序錯誤。
在測試別人程序的過程當中,筆者測試的這三位同窗的程序都沒法正常地運行多條乘客請求。在第九次做業時,被測試的程序在運行多條請求時會出現請求間信息錯位的現象,初步認定是請求共享部分的互斥工做存在缺陷。在第十次做業時,被測試的程序在運行3至6條程序時會異常地中止運行,無任何反應,但不會崩潰退出。在閱讀代碼後大體因爲路徑計算時耗時過多,以至多輛出租車同時計算長距離請求時延遲較大。當運行超過7條請求時,程序會有崩潰的可能,概率隨請求數的增長而增長,崩潰的緣由是堆棧溢出。在第十一次做業的時候,被測試的程序在運行多條請求時會出現嚴重的延遲現象。因爲這位同窗的代碼的可讀性實在太差,筆者沒能找出致使錯誤的代碼。
在通過這三次做業以後,我對於規格化程序設計的重要性有了親身體會。在編寫面向對象程序前就應當對程序中的數據和處理過程進行抽象,定義出對數據的操做以及數據管理的方式,概括出程序中須要進行的行爲,限定各個操做的邊界以及用戶可見的內容。再結合上個階段學的面向對象程序設計原則,對於程序中的類設計作出相應限定,使得編寫的類具備更好的延展性、可維護性與魯棒性。
通過總結,對於程序中方法的設計過程大體分爲如下幾步:
1. 明確方法存在的意義。
2. 明確方法結果正確的斷定條件。
3. 明確方法對調用者提出的條件,以保證結果正確。
4. 明確方法執行期間修改的數據。
5. 按照要求的方式整理前置條件、修改數據、後置條件。
通過了短暫的一個月的實踐,筆者雖對這些思想有了很多的體會,但還有待更多的實踐深化。