讀《代碼不朽:編寫可維護軟體的10大要則》C# 版

文/張善友 騰訊高級工程師,微軟11年MVP

這本書特別針對沒有接受過計算機科學或軟體工程專業學習的軟體開發人員,這類人員除了熟悉所用語言語法和語義之外,很少接受其他專業培訓,對軟體工程中的一些概念理解欠缺。軟體設計方面考慮較少。如果要成為一個專業的程序員,就需要學習已形成工業化的軟體構建方式。

可維護性解釋

這本書解釋了可維護軟體中的「維護」的意思:可維護性是軟體質量的一個標準,代表一個系統可被修改的難易程度。所以它是面向程序員的,假設兩個軟體完成相同的功能,但一個軟體的源碼,讓其他人或者一段時間之後的自己,很難理解,更不用提修改了,就說明這個軟體的可維護性比另一個差。軟體維護有4種方式:發現並糾正bug(糾正性維護);適應操作系統或運行環境的改變(適應性維護);根據需求增加新的功能(完善性維護);改進代碼質量預防bug產生(預防性維護)。

三個基本理論

本書提供了10條可以實現高可維護性的指導原則,這些原則背後有三個理論:

1、堅持簡單的原則最有助於提高可維護性

2、可維護性不是項目開發完後才去考慮的,而應該是在項目開發的一開始就加以考慮。每個人的貢獻都應當計算在內

3、不同原則的違例會帶來不同的影響,有些嚴重程度甚至於他。一個軟體系統越遵守原則,可維護性越高。

對10大要則的理解

按照從小到大,從細微到宏觀的層次,這本書提取了編寫可維護軟體中10大編程原則,小到程序開發者應當時刻注意的代碼規範,大到系統架構師應該考慮的系統重構、組件和及介面的設計準則。

編寫短小的代碼單元

代碼單元即面向對象編程里的方法或函數。這個原則要求每個函數的長度不應超過15行。

動機

小的函數的好處?作者提出,小的函數容易重用,因為一個巨型的方法會包含很多細節,導致很難有一模一樣的場景使用這個方法。作者提出,小的方法更易理解和進行單元測試。若超過15行,則意味著方法可以被拆分了。

如何使用本原則

拆分重構的方式有提取方法和將方法替換為方法對象。

提取方法很容易理解,即從一個函數中提取一段代碼,寫成一個新的方法。但如果提取方法時發現,這個方法訪問了很多局部變數,如果都作為新方法的參數的話,勢必會導致參數列表過長。還有返回值的問題,如果這個方法會產生不止一個結果變數。一個重構技巧是將這個方法替換成一個方法對象,將不同的局部變數和結果變數作為類的成員,然後調用類方法。

編寫簡單的代碼單元

這裡的「簡單」體現代碼單元的分支點,所以這個原則可量化為:限制每個代碼單元分支點的數量不超過4個。C# 中常見的分支點代碼就是if和switch語句。

動機

讓代碼單元保持簡單基於兩個原因,一是簡單的代碼更容易修改,二是簡單的代碼更容易測試,分支點過多,意味著要有更多的測試用例。

如何使用本原則

複雜的代碼單元可能是因為其中包含很多互不相關的代碼塊,這種情況可以採用「提取方法」

若是其它複雜的情況,比如碰到鏈式的條件語句,如下判斷國旗的語句:

...

List result;

switch(nationality) {

case CHINA:

result= new List{Color.RED,Color.YELLOW};

break;

case FRENCH:

result= new List{Color.BLUE,Color.WHITE,Color.RED};

break;

...

}

第一種方法是引入Map數據結構,將國家映射到指定的FLAG對象上;

第二種方法是使用「使用多態來代替條件判斷」,實現同一個介面,代表廣泛的國旗類型,然後為每個國家的國旗實現一個類。

再比如碰到嵌套的條件語句,為了使代碼簡單,可以使用「使用衛語句來代替嵌套的條件語句」的重構技巧,即標識出各種獨立的情況,並插入return語句來代替嵌套式的條件語句。

例如

if(...) {n if(...) {n //TODO CASE1n } else {n //TODO CASE2n }n} else {n //TODO CASE3n}n

可以改寫成

if(...) {n //TODO CASE1n return;n}nif(...) {n //TODO CASE2n return;n}nif(...) {n //TODO CASE3n return;n}n

可以看到分支點並未減少,然後可以再用「提取方法」減少複雜度。

不寫重複代碼

對重複代碼的定義是,一段至少6行都相同的代碼。

動機

如果複製代碼,相同的代碼出現在不同的地方,不利於源碼的定位;如果需要修改的地方正是重複的代碼,意味著要做很多重複性的工作,而且容易出錯。

如何使用本原則

首先想到的是提取方法;但若是一個方法是另一個類的私有方法怎麼辦?這時應當將提取的方法放到一個工具類中。

如果重複代碼(6行以上完全相同)已不存在,但代碼相似,具有相同的邏輯,這時應該考慮提取父類。

保持代碼單元的介面簡單

限制每個代碼單元的參數不能超過4個。

動機

較少的介面參數能夠保持簡單的上下文,易於重用、理解和修改。

如何使用本原則

將多個參數包裝成對象,比如輸入坐標參數,x與y,可以包裝成一個點對象。

使用「使用方法對象替換方法」的重構技巧,此處和前面有重合。

分離模塊之間的關注點

模塊對應類的概念。

實際上就是要求類要保持小的體積,不要過大過複雜。

動機

小的體積的類帶來了類之間的松耦合,松耦合意味著類能更靈活的適應將來的變化。如果一個類做了很多事情,其耦合度會越來越緊,積攢大量代碼,導致代碼很難閱讀和修改。

如何使用本原則

第一種方法:根據功能將大類拆分為很小的類。一個類一開始可能很小,只是實現單一功能,但都不可避免負責越來越多的職責,當意識到這個類承擔了不止一個職責時,就應該將這個類進行拆分。

第二種方法:提取一個介面,實現松耦合。比如一開始為一台相機設計了簡單的相機類,只具備拍照,閃光燈打開和關閉3個方法。後來這個類的使用擴展到新的移動設備上,增加了定時功能。這時類變大,而且只有一個類,還需要檢查舊設備上的代碼有沒有受影響。為了降低耦合度,可以使用一個介面,它只定義所有相機都需要實現的功能。

第三種方法:使用第三方庫和框架來替代自定義的實現。

架構組件松耦合

組件是比模塊(類)更高一層的單元,設計到系統的架構。此原則要求儘可能減少當前模塊暴露給(例如,被調用)其它組件中模塊的相關代碼。

動機

獨立的組件可以單獨進行維護,方便劃分職責,讓測試變得容易。

如何使用本原則

使用抽象工廠設計模式,簡單的講就是類的實例不能直接被創建(new一個),而是通過工廠類的方法返回。這種通用的工廠介面背後,隱藏了具體產品的創建過程。在這個環境下,產品通常都不止有一種類型。如果要使用其中的邏輯,需要通過創建通用的工廠對象調用類方法成員。

註:抽象工廠不同於工廠模式,簡單理解就是抽象工廠的類型不止一個,所以產品至少有兩個。

保持架構組件之間的平衡

保持源代碼中的組件數量接近於9。

動機

好的組件平衡讓查找和分析代碼更容易,提供清晰的功能邊界,分離維護職責。

如何使用本原則

軟體系統的開發有兩種組織模式:

基於功能領域劃分的系統:好處是可以從高層功能的角度來分析代碼,壞處是技術人員需要了解多個技術棧

基於技術劃分的系統:根據技術專長來劃分,可能會有前端,後端,介面、日誌等組件。

軟體架構師需要選擇如何組合功能的合適原則。明確系統的領域並堅持下去。

保持小規模代碼庫

動機

大型系統更加難以維護,易出現更密集的缺陷,以大型代碼庫為目標的項目更容易失敗。

如何使用本原則

功能層面:控制需求蔓延,功能標準化

技術層面:不要複製黏貼代碼,重構代碼,使用第三方庫和框架(這同樣是前面提到的準則)

自動化開發部署和測試

測試包含單元測試、集成測試、端對端測試、回歸測試、驗收測試。不同類型的測試需要不同的自動化框架。

動機

自動化測試可重複,有效率;自動化測試里的斷言(assert)可以充當注釋;通過編寫測試可以反過來推促編寫可測試的代碼,提高代碼質量。

如何使用本原則

使編寫單元測試成為每個開發人員的職責,比如使用C#中的單元測試框架Xunit.net

使用像moq或者mocking這樣的技術。stub即測試樁。需要測試樁是因為有些影響測試結果的測試條件是易變、無法統一的。比如拍照,兩次拍攝的環境不可能完全相同,結果無法驗證,所以需要一個假對象,即測試樁。mocking(模擬)是因為測試中某些函數是沉默的,不包含任何結果,可以在函數中添加計數來驗證函數執行過。mock技術有自動化的框架。

建議生產代碼和測試代碼一比一,提高覆蓋率。

編寫簡潔的代碼

給程序開發人員總結了7條「童子軍軍規」:

1、編寫單元級別的良好代碼

2、不要編寫不好的注釋

3、不要注釋代碼

4、不要保留廢棄代碼

註:包括3,同時還有其它的形式,比如不可能執行到的代碼、無用的私有方法、注釋中的代碼

5、不要使用過長的標識符名稱

6、不要使用魔術常量

註:指表達式中突兀出現的數字,應該先定義。

7、不要使用未正確處理的異常

註:包括以下情況,捕獲異常卻不處理(catch為空),直接捕獲通用異常(比如Exception異常,這些異常不會提供觸發失敗的狀態或事件信息,所以沒意義),將異常信息展示給終端用戶(避免用戶困惑或暴露信息,應該先轉換為通用信息)

相關閱讀:

榨乾運營的帶寬優化技巧:一億之後再省兩億

精細化容量管理的設備成本優化之路 - 騰雲閣 - 騰訊雲

如果你有一定的技術分享習慣,也想和鵝廠前端、後台技術、安全大牛們深度交流。

那推薦你加入:騰訊雲社區技術作者扶持計劃 - 騰雲閣 - 騰訊雲,和大神們同群搶紅包..


推薦閱讀:

Logging,Metrics 和 Tracing
理解架構版本
如何成為一名軟體架構師?
使用 caddy 作為微服務的 API gateway
是時候想想該怎麼刪代碼了

TAG:C# | 编程 | 软件架构 |