第一次做業我只使用了兩個類,正像下面的類圖所表示的那樣,分別是Poly和ComputePoly。Poly類是不可變的,能保存一個多項式,能夠進行加、減運算。ComputePoly是程序的主類,可以讀取一個多項式加減運算表達式的字符串,並輸出計算結果。parseExpression方法經過調用parsePoly方法和parseOperator方法將輸入的字符串轉換爲Poly對象和運算符列表。compute方法從polyList和opList取出多項式和運算符進行計算,返回計算結果。正則表達式
總的代碼量是248行(後面的度量分析圖中能夠看到),其中ComputePoly類佔167行,Poly類81行;程序總共有28個方法,Poly佔10個,而ComputePoly佔18個。因爲寫Poly類的時候參考了教材的寫法,Poly還算比較合適,不管是從兩個類的代碼量仍是方法個數來看,ComputePoly都顯得不太協調。事實上也如此,ComputePoly作了除計算外全部的事情:處理輸入(包括錯誤處理,提取多項式),將多項式和運算符們存到數組裏,調用compute方法得出結果,最後輸出結果。這是典型的面向過程式的思路,很有一點用C語言寫程序的味道。數組
再仔細一想,ComputePoly作這麼多事情合適嗎?就這個不那麼複雜的程序而言,管理ComputePoly所作的事情仍是可以接受的,可是再實現一個乘法功能呢?很明顯,上面的兩個類都須要作修改,對於Poly而言,只需增長一個計算乘法的方法就能夠了,可是對ComputePoly來講,第1,4,5,6共4個方法都須要修改。假如按照建議設計那樣將ComputePoly進一步分紅3個類InputHandler,PolyManager,PolyArithmetic,一樣地,輸入處理InputHandler要作修改,PolyManager也須要修改,但PolyArithmetic不須要修改。實際該改的都得改,說白了仍是前面所說的第1,4,5,6個方法。可是在這樣一種設計下進行修改的複雜性就下降了,修改InputHandler時只須要記得正則表達式改一下能匹配乘號,而不需關心乘法和加法在一塊兒時的誰先計算誰後計算的問題;而修改PolyManager時也無需關注輸入是否處理好了,只用專心實現calculate方法和appendOperator方法(以下圖)。這樣一來不用在ComputePoly長長的代碼中苦苦尋找某個方法,上改下改,減小出錯的可能性,二來也不會被總體的複雜性所煩擾,分解成兩個問題後可單獨實現。app
不管如何,在初識面向對象設計前我仍是帶着面向過程式的思惟,幸虧可以從教材上學習到如何編寫Poly類(很是感謝第一次做業時有這麼好一個範例),初步領略了OO之美。下圖是用eclipse的metrics插件對第一次做業代碼分析獲得的結果。重點關注一下第一個McCabe Cyclomatic Complexity,它中文名叫圈複雜度,是流程控制圖中獨立路徑的數目,主要由分支和循環個數決定,越大代表越複雜,測試時所需考慮的狀況也就越多(由於每一個路徑都要被測試),當它很是大的時候,程序的測試就變得十分複雜。這裏的最大值是10,還能接受。仔細查看,形成最大值的方法是ComputePoly裏的parsePoly方法,緣由多是須要進行輸入檢查,涉及的不一樣錯誤輸入種類數較多。eclipse
Nested Block Depth是if,while,for的嵌套深度。這裏是最大值3,處於正常範圍。最大值一個來自Poly的add方法,考慮到實現兩個多項式加法的細節,是能夠接受的,另外一個一樣來自parsePoly方法,緣由一樣是涉及輸入錯誤處理。學習
再看一下Method Lines of Code,方法的代碼行數,這裏最大值是26,不算太大。測試
這次做業的bug在於正則表達式的匹配,我使用一個正則表達式試着去匹配整個輸入的字符串,潛在的危險是堆棧溢出。在分類樹上是有「壓力測試」這一項的,並且助教也說過注意不要爆棧,可是當時沒想明白什麼會致使棧溢出,並且是第一次使用正則表達式,主觀上也認爲壓力測試沒什麼用,就沒有構造相應的測試樣例,致使了這個bug的產生。bug出如今isCorrectFormat方法中,這個方法檢查了輸入的字符串是否符合規定的格式。要解決這個問題可採用分段匹配的方式,整個字符串是多個多項式,中間用加減號鏈接,所以能夠分別匹配每一個多項式。優化
測試同窗代碼時,我使用了每一個分支樹結點的對應測試用例(除了壓力測試)。同窗的代碼中存在相似以「f」,「ff」,「fff」命名的變量,我嘗試着去理解同窗的意圖,再加上大量的分支循環嵌套,實在是難以揣測。編碼
第二次做業一開始我花了一天的時間理解做業指導書,從頭至尾讀了好幾遍才弄清楚。到第三次做業也是如此,我試着邊讀邊用本身的語言去表達指導書的規定,而且舉出例子,而後分條把以爲重要的點寫在紙上,這樣作有些笨拙和繁瑣,不過的確能幫助我理解指導書的意圖。插件
課件中給出了提示,要構造電梯類、調度類、請求類、請求隊列類和樓層類共5個類(以下圖)。要怎樣肯定該哪一個類該作什麼,這是除了理解指導書外另外一件頭疼的事情。冥思苦想,苦思冥想,難以劃分各個類的職責。另外一個問題是調度器類的command和schedule方法,弄得我一頭霧水,寫完後都沒能參透其中奧祕,直到看了互測同窗活生生的代碼,才恍然大悟。設計
第二次做業的要點是如何把程序功能均衡地分配給各個類,如何讓多個類之間協同工做,要避免出現Idiot Class和God Class。從下面的類圖中能夠看到Floor類就是比較白癡的一個類,它只知道樓層頂樓和底樓的編號。其實,能夠考慮讓樓層類知道更多的信息,好比某層樓是否有電梯到達。
除了出現了一個Idiot Class外,另外一個缺點是,在main方法裏展開對輸入的處理,這與第一次做業相同,因爲本身沒能意識到這種作法的壞處,在第2、三次做業時仍沒有加以改正。
本次做業的設計是否均衡呢?下面就再用定量的方法分析一下。
每一個類的屬性個數、方法個數、代碼行數以下面的表格所示,其中方法個數包括了構造方法。從數據上看,方差較大。代碼行數最多的類是ElevatorSystem,多是由於在這個類裏作了輸入處理,若是把輸入的處理分開來,應該會更均衡一些。
類 | Elevator | ElevatorSystem | Floor | Request | RequestQuue | Scheduler | 均值 | 方差 |
屬性個數 | 7 | 4 | 2 | 4 | 2 | 1 | 3.3 | 4.6 |
方法個數 | 14 | 2 | 4 | 9 | 10 | 6 | 7.5 | 19.1 |
類代碼行數 | 95 | 107 | 18 | 48 | 38 | 55 | 60.2 | 1170.2 |
一個比較大的數據是電梯類的方法個數14,其中用於狀態查詢的方法佔了一半。因爲事先未規劃好,在編碼的時候爲了方便,新增了一些方法。仔細分析會發現一些方法是冗餘的,好比getStatus方法,事實上這個方法也從未被調用過。另外一個緣由多是題目要求的電梯狀態是定義在左開右閉區間上的,有時候爲了方便我會使用左閉右開區間,這也增長了必定的複雜性。
再看一下類的職責是否明確。
拿電梯類舉例,它總共有14個方法,方法總數佔到整個程序約1/3,但仔細看,只發現可以讓電梯改變狀態的只有前兩個方法readyToGotoFloor和run,run方法是讓電梯運行到0.5s後的狀態,而前者肯定電梯的下一個目標。也就是說,別的類只能告訴電梯下一次去哪一個樓層,電梯只管去,而且本身決定方向,其餘類不能干涉電梯的運動方向。假設其餘類能直接修改電梯的方向,那麼在這個設計中,若是調度器讓電梯向下走,但又是去往樓層數高的地方,這明顯是不合適的。電梯內部不存在請求隊列,不管什麼時候,電梯都只有一個目標,它不用操心有多少請求在隊列中等着它執行,只用遵從調度類的指揮就能夠了。
從功能的角度上看,電梯的職責是明確而單一的。可是這樣作的複雜性在於,電梯調度類須要精心地設計,在每次給電梯發送命令前,須要使用電梯類提供的一系列狀態查詢方法檢查電梯狀態(之因此要檢查是由於調用readyToGoToFloor會當即改變電梯的運動狀態,例如當電梯向上運行時,調用方法讓電梯去往比當前樓層數小的樓層,運動方向就會忽然改變)。所以,調度類必須充分了解電梯各個狀態的含義(儘管它不須要了解電梯是怎樣肯定本身的狀態的)和一些內部細節,不然就可能會致使電梯出故障。這就在必定程度上增長了電梯類與調度類的耦合性,一是使編碼時複雜性增長,二來修改、新增功能時容易出錯(例如我在寫第三次做業的時候就在這上面犯了不少錯誤)。
下圖是第二次做業的度量分析結果。能夠明顯地看到標紅的圈複雜度較高,最大值是17。進一步細看(圖中未給出)能夠發現高複雜度的來源主要是ElevatorSystem類的parseRequest方法和main方法,以及Elevator類的run方法。前者是因爲輸入的錯誤狀況較多,我的寫得比較凌亂,判斷邏輯複雜,main方法裏也作了輸入的處理。後者是電梯運行時的邏輯稍微複雜,分支較多,也有兩層嵌套的狀況。
再看一下每一個方法的行數(圖中最後一行),最大值是46,與第一次做業相比有所增長,其來源一樣是parseRequest和main方法。若是將輸入處理部分單獨封裝在一個類中,而且優化一下錯誤處理的邏輯,應該能使總體設計均衡一些。
此次做業的bug是在時間很大時運行的時間較長,須要十幾秒,這是因爲實現採用了每0.5s進行一次操做的方式。
本次做業在前一次做業的基礎上增長了捎帶功能,用繼承的方式實現了ALSScheduler,對其餘的類也作了一些調整。
因爲保留了第二次做業的大部份內容,本次做業在均衡性上沒有改進,反而因爲增長捎帶功能後變差。尤爲是ALSScheduler,代碼行數最多,邏輯也較複雜。
類 | Elevator | ElevatorSystem | Floor | Request | RequestQuue | Scheduler | ALSScheduler | 均值 | 方差 |
屬性個數 | 8 | 4 | 2 | 4 | 2 | 3 | 4 | 3.9 | 4.1 |
方法個數 | 17 | 2 | 4 | 12 | 11 | 5 | 12 | 9 | 29.3 |
類代碼行數 | 108 | 108 | 18 | 59 | 48 | 55 | 141 | 76.7 | 1857.9 |
細心的讀者可能會發現此次的圈複雜度降低了1,但這不是由於進行了優化,只是作了點微調。這裏最大值16也不是前面提到的輸入處理帶來的,而是來自ALSScheduler的command方法。爲了實現捎帶功能,我增長了不少條件判斷,既難寫,又難以理解和修改。
上面的3點都是給我互測的同窗發現的,這裏要感謝這位同窗。
最後再分析一下第二、3個bug與設計結構的關係。這兩個bug都位於ALSScheduler類中,具體在多個方法中都有體現,究其緣由,是使用了Java標準類庫中的優先隊列。這個隊列專用於捎帶隊列,我按照到達樓層的時間(前後)做爲各個請求的優先級,可以最先達到的,排在隊首,晚到的,排在後面。問題出在同一時刻進隊的請求,在出隊時可能失去了輸入時的順序以及請求發出的時間順序,這就致使了第2個bug的產生。另外一方面,當主請求執行結束時,處在捎帶隊列隊首的請求未必是按照請求發出時間最先的。
我能想到的解決辦法是專門實現捎帶請求隊列類,兼顧到達時間順序與請求時間順序。在下一次做業中我會嘗試着改正。