代碼不朽:編寫可維護軟件的10大要則(java版) 因為本科并非軟工和計算機專業,所以對軟件工程中的一些概念理解欠缺。我的編程實踐大部分來自于圖像處理算法程序的編寫,在編程過程中對代碼規范,軟件設計方面考慮較少。如果要成為一個專業的程序員,就需要學習已形成工業化的軟件構建方式。
這本書解釋了可維護軟件中的“維護”的意思:可維護性是軟件質量的一個標準,代表一個系統可被修改的難易程度。所以它是面向程序員的,假設兩個軟件完成相同的功能,但一個軟件的源碼,讓其他人或者一段時間之后的自己,很難理解,更不用提修改了,就說明這個軟件的可維護性比另一個差。維護的工作包括發現并糾正bug(糾正性維護);適應操作系統或運行環境的改變(適應性維護);根據需求增加新的功能(完善性維護);改進代碼質量預防bug產生(預防性維護)。
按照從小到大,從細微到宏觀的層次,這本書提取了編寫可維護軟件中10大編程原則,小到程序開發者應當時刻注意的代碼規范,大到系統架構師應該考慮的系統重構、組件和及接口的設計準則。
代碼單元即面向對象編程里的方法或函數。這個原則要求每個函數的長度不應超過15行。
小的函數的好處?作者提出,小的函數容易重用,因為一個巨型的方法會包含很多細節,導致很難有一模一樣的場景使用這個方法。作者提出,小的方法更易理解和進行單元測試。若超過15行,則意味著方法可以被拆分了。
拆分重構的方式有提取方法和將方法替換為方法對象。 提取方法很容易理解,即從一個函數中提取一段代碼,寫成一個新的方法。但如果提取方法時發現,這個方法訪問了很多局部變量,如果都作為新方法的參數的話,勢必會導致參數列表過長。還有返回值的問題,如果這個方法會產生不止一個結果變量,那么這個方法就無法構建,因為java中一個方法只能有一個返回值。一個重構技巧是將這個方法替換成一個方法對象,將不同的局部變量和結果變量作為類的成員,然后調用類方法。
這里的“簡單”體現代碼單元的分支點,所以這個原則可量化為:限制每個代碼單元分支點的數量不超過4個。Java中常見的分支點代碼就是if和switch語句。
讓代碼單元保持簡單基于兩個原因,一是簡單的代碼更容易修改,二是簡單的代碼更容易測試,分支點過多,意味著要有更多的測試用例。
復雜的代碼單元可能是因為其中包含很多互不相關的代碼塊,這種情況可以采用“提取方法” 若是其它復雜的情況,比如碰到鏈式的條件語句,如下判斷國旗的語句:
...List<Color> result;switch(nationality) { case CHINA: result=Arrays.asList(Color.RED,Color.YELLOW); break; case FRENCH: result=Arrays.asList(Color.BLUE,Color.WHITE,Color.RED); break;...}第一種方法是引入Map數據結構,將國家映射到指定的FLAG對象上; 第二種方法是使用“使用多態來代替條件判斷”,實現同一個接口,代表廣泛的國旗類型,然后為每個國家的國旗實現一個類。 再比如碰到嵌套的條件語句,為了使代碼簡單,可以使用“使用衛語句來代替嵌套的條件語句”的重構技巧,即標識出各種獨立的情況,并插入return語句來代替嵌套式的條件語句。 例如
if(...) { if(...) { //TODO CASE1 } else { //TODO CASE2 }} else { //TODO CASE3}可以改寫成
if(...) { //TODO CASE1 return;}if(...) { //TODO CASE2 return;}if(...) { //TODO CASE3 return;}可以看到分支點并未減少,然后可以再用“提取方法”減少復雜度。
對重復代碼的定義是,一段至少6行都相同的代碼。
如果復制代碼,相同的代碼出現在不同的地方,不利于源碼的定位;如果需要修改的地方正是重復的代碼,意味著要做很多重復性的工作,而且容易出錯。
首先想到的是提取方法;但若是一個方法是另一個類的私有方法怎么辦?這時應當將提取的方法放到一個工具類中。 如果重復代碼(6行以上完全相同)已不存在,但代碼相似,具有相同的邏輯,這時應該考慮提取父類。
限制每個代碼單元的參數不能超過4個。
較少的接口參數能夠保持簡單的上下文,易于重用、理解和修改。
將多個參數包裝成對象,比如輸入坐標參數,
模塊對應類的概念。 實際上就是要求類要保持小的體積,不要過大過復雜。
小的體積的類帶來了類之間的松耦合,松耦合意味著類能更靈活的適應將來的變化。如果一個類做了很多事情,其耦合度會越來越緊,積攢大量代碼,導致代碼很難閱讀和修改。
第一種方法:根據功能將大類拆分為很小的類。一個類一開始可能很小,只是實現單一功能,但都不可避免負責越來越多的職責,當意識到這個類承擔了不止一個職責時,就應該將這個類進行拆分。 第二種方法:提取一個接口,實現松耦合。比如一開始為一臺相機設計了簡單的相機類,只具備拍照,閃光燈打開和關閉3個方法。后來這個類的使用擴展到新的移動設備上,增加了定時功能。這時類變大,而且只有一個類,還需要檢查舊設備上的代碼有沒有受影響。為了降低耦合度,可以使用一個接口,它只定義所有相機都需要實現的功能。 第三種方法:使用第三方庫和框架來替代自定義的實現。
組件是比模塊(類)更高一層的單元,設計到系統的架構。此原則要求盡可能減少當前模塊暴露給(例如,被調用)其它組件中模塊的相關代碼。
獨立的組件可以單獨進行維護,方便劃分職責,讓測試變得容易。
使用抽象工廠設計模式,簡單的講就是類的實例不能直接被創建(new一個),而是通過工廠類的方法返回。這種通用的工廠接口背后,隱藏了具體產品的創建過程。在這個環境下,產品通常都不止有一種類型。如果要使用其中的邏輯,需要通過創建通用的工廠對象調用類方法成員。 注:抽象工廠不同于工廠模式,簡單理解就是抽象工廠的類型不止一個,所以產品至少有兩個。參見抽象工廠模式和工廠模式的區別?-caoglish的回答-知乎
保持源代碼中的組件數量接近于9。
好的組件平衡讓查找和分析代碼更容易,提供清晰的功能邊界,分離維護職責。
軟件系統的開發有兩種組織模式: 基于功能領域劃分的系統:好處是可以從高層功能的角度來分析代碼,壞處是技術人員需要了解多個技術棧 基于技術劃分的系統:根據技術專長來劃分,可能會有前端,后端,接口、日志等組件。 軟件架構師需要選擇如何組合功能的合適原則。明確系統的領域并堅持下去。
大型系統更加難以維護,易出現更密集的缺陷,以大型代碼庫為目標的項目更容易失敗。
功能層面:控制需求蔓延,功能標準化 技術層面:不要復制黏貼代碼,重構代碼,使用第三方庫和框架(這同樣是前面提到的準則)
測試包含單元測試、集成測試、端對端測試、回歸測試、驗收測試。不同類型的測試需要不同的自動化框架。
自動化測試可重復,有效率;自動化測試里的斷言(assert)可以充當注釋;通過編寫測試可以反過來推促編寫可測試的代碼,提高代碼質量。
使編寫單元測試成為每個開發人員的職責,比如使用Java中的單元測試框架jUnit。 使用像stubbing或者mocking這樣的技術。stub即測試樁。需要測試樁是因為有些影響測試結果的測試條件是易變、無法統一的。比如拍照,兩次拍攝的環境不可能完全相同,結果無法驗證,所以需要一個假對象,即測試樁。mocking(模擬)是因為測試中某些函數是沉默的,不包含任何結果,可以在函數中添加計數來驗證函數執行過。mock技術有自動化的框架。 建議生產代碼和測試代碼一比一,提高覆蓋率。
給程序開發人員總結了7條“童子軍軍規”: 1、編寫單元級別的良好代碼 2、不要編寫不好的注釋 3、不要注釋代碼 4、不要保留廢棄代碼 注:包括3,同時還有其它的形式,比如不可能執行到的代碼、無用的私有方法、注釋中的代碼 5、不要使用過長的標識符名稱 6、不要使用魔術常量 注:指表達式中突兀出現的數字,應該先定義。 7、不要使用未正確處理的異常 注:包括以下情況,捕獲異常卻不處理(catch為空),直接捕獲通用異常(比如RuntimeException異常,這些異常不會提供觸發失敗的狀態或事件信息,所以沒意義),將異常信息展示給終端用戶(避免用戶困惑或暴露信息,應該先轉換為通用信息)
新聞熱點
疑難解答