代碼整潔之道

柴树藩 2024-04-09 06:00 13次浏览 0 条评论 taohigo.com

第一章:整潔代碼

混亂的代價

隻要你幹過兩三年編程,就有可能曾被某人的糟糕代碼絆倒過,如果你編程不止兩三年,也可能被這種代碼拖過後腿。進度延緩都程度會很嚴重。有些團隊在項目初期進展迅速,但又那麼一兩年的時間卻慢如蝸行。對代碼的每次修改都影響到其他多處代碼,最後這團亂麻越來越大,再也無法理清,束手無策。隨著混亂的增加,團隊生產力持續下降,趨向於零,管理層隻能添加更多人手帶項目中,可新人並不清楚系統的設計,也不瞭解設計意圖,而且團隊中的其他人都背負著提升生產力的可怕壓力,於是制造瞭更多的混亂。

什麼是整潔的代碼

優雅、藝術

第二章:有意義的命名

名副其實

如果名稱需要註釋來補充,那這個命名就不算是名副其實

避免誤導

避免使用與本意相悖的詞避免對不同變量使用外形極為相似的名稱

做有意義的區分

要區分名稱,就要以讀者能鑒別不同之處的方式來區分,accountData與account沒區別,theMessage也與message沒區別

使用讀得出來的名稱

命名時避免使用讀不出來的傻乎乎的自造詞,而是使用恰當的英語詞

使用可搜索的名稱

單個字母和數字常亮的問題就是很難在一大篇文字中找出來,比如字母e可能出來在任何地方,所以,通常來說,長名稱勝於短名稱

類名

類名應當是名詞或短語,如Customer,Account避免使用Manager,Data,Info這樣的類名類名不應該是動詞

方法名

方法名應當是動詞或動詞短語,如postPayment,deletePage

一以貫之

DeviceManager和ProtoolController之間,為什麼不全用Controller或這Manager,所以在程序中,通常命名方式應該一以貫之

第三章:函數

短小

函數的第一規則是要短小,第二規則還是要短小理論上通過抽象更小的函數,可以讓函數的縮進層級不多於兩層。

隻做一件事

函數應該隻做同一抽象層級上的步驟,且隻做一件事,判斷函數是否不止做瞭一件事,可以看是否還能再拆出一個函數,從而改變目前函數的抽象層級。

每個函數一個抽象層級

讓代碼讀起來像是自頂向下。

使用描述性的名稱

函數越短小,功能越集中,就越便於起一個好名字,更好的描述函數做的事。

參數

應盡量避免定義三個以上參數的函數,如果一些函數需要三個及以上的參數,就說明其中一些參數鶯歌被封裝城類瞭函數參數應與函數處於同一個抽象層級,如果參數多於兩個,則對於測試的覆蓋會是一個災難。避免使用標識參數,例如,函數傳入一個bool值,如果為true將這樣做,如果為false則那樣做,這無異於大聲宣佈這個函數不止做瞭一件事;當必須存在這個標識時,應該通過重構,將標識限制在上層范圍內。

無副作用

避免在函數中做承諾以外的事,比如對其他變量做出預期外的改動。

將指令與詢問分開

函數要麼修改某對象的狀態,要麼返回該對象的有關信息,兩樣都幹時會導致混亂。

抽離錯誤處理代碼塊

避免把錯誤處理與正常流程混為一談,比如把錯誤處理單獨抽離出來,這樣代碼會更加易於理解和修改。

別重復自己

某個邏輯被重復多次會導致代碼臃腫,且修改時需要修改多處地方。

第四章:註釋

註釋存在的時間越久,合理性就越低,沒有人可以堅持的維護註釋;當發現自己需要寫註釋時,想想看是否可以用代碼來表達。

註釋不能美化糟糕的代碼

寫註釋的常見動機是糟糕代碼的存在。帶有少量註釋的整潔代碼,要比帶有大量註釋的復雜的代碼好得多。隻要多想幾秒鐘,就可以用代碼解釋你的大部分意圖,簡單到隻需要創建一個命名與註釋所描述的相符的函數即可。

第五章:格式

空白行

通過空白行標識出新的邏輯段。

函數調用

若同一個文件中某個函數調用瞭另一個函數,那調用方應該放到被調方的上面。

空格

賦值語句的等號左右兩邊應該用空格隔開,函數參數之間也應該用空格隔開。

團隊規范

同一個團隊中的開發者應該遵守同一種格式風格。

第六章:對象和數據結構

對象暴露行為,隱藏數據,便於添加新對象類型而無需修改既有行為,但同時難以再既有對象中添加新行為。數據結構暴露數據,沒有明顯的行為,便於向既有數據結構添加新行為,同時難以向既有函數添加新數據結構。在任何系統中,我們有時希望能夠靈活的添加新數據類型,所以更喜歡在這部分使用對象,在另外一些時候,我們希望能靈活的添加新行為,這時我們更喜歡使用數據類型和過程。

第七章:錯誤處理

錯誤處理很重要,但如果它搞亂瞭代碼邏輯,就是錯誤的做法。

拋出異常

在支持拋出異常的編程語言中盡量在遇到錯誤時跑出異常,而不是返回一個錯誤碼。

適當使用不可控異常

盡量避免在上層抽象中明確的捕獲底層的錯誤類型,如果底層函數發生瞭修改,返回瞭新的異常類型,則上層函數需要全部修改去新增對應異常類型的捕獲邏輯,這種做法明顯破壞瞭代碼的封裝性。

異常細節

任何捕獲異常的地方都要明確給出異常的上下文信息。

異常封裝

當需要捕獲被調函數多個不同類型的異常時,為瞭避免冗餘的代碼以及後續由於被調函數異常的修改而去改動多處代碼,盡量將可能拋出的多個異常封裝成一個異常類。

拋出異常而不是返回null值

避免返回null值,如果你打算在方法中返回null值不如拋出異常或返回特例對象;如果你調用的第三方API中可能返回null值,則可以考慮用新方法打包這個調用,在新方法中拋出異常或返回特例對象;否則返回null值基本上就是給自己增加工作量,也是在給調用者添亂,隻要有一處沒檢查null值,應用程序就會失控。

避免傳入null值

避免在方法參數中傳入null值,雖然可以在方法中對參數做null值校驗,但禁止傳入null值可以避免出現很多無心之失。

第八章:邊界

避免暴露邊界

當使用第三方代碼的邊界接口時,避免直接使用其API的返回值,而是將其進行封裝,以便於對應用程序隱藏邊界接口,並且能夠後續做出更靈活的修改。

保留未知

當使用部分細節未知的邊界接口時,最好的做法同樣是將其進行封裝,這樣可以盡量避免不被未知的部分所困擾。

第九章:測試

避免出現臟測試

測試代碼和生產代碼一樣重要,臟測試=沒測試,測試代碼必須隨著生產代碼的演進而修改,否則測試代碼越亂,就越需要花更多的時間加入新的測試。

測試規則

測試代碼要遵循 構造-操作-檢驗 三個步驟。

第十章:類

短小

類應該保持短小,並不是指代碼行數,而是指“權責”。

單一權責原則

系統應該由許多短小的類而不是少量巨大的類組成,每個小類封裝一個權責,並與其他類一起協同達到期望的目的。

內聚

類應該隻有少數的內置變量,類中的方法應該和變量相互依賴並結合成一個邏輯整體。保持內聚性有利於得到一個短小的類

抽象類

具體類包含實現細節,而抽象類則隻呈現概念,依賴於具體細節的實現類,我們可以借助接口和抽象類來隔離這些細節帶來的影響。

第十一章:系統

第十二章:迭代

運行所有的測試

全面測試並是吃通過所有測試的系統,才是可以部署的系統

盡可能減少類和方法的數量

重構過程中,應該時刻關註變化之後的設計,在出現退步時及時消除重復。保證代碼的表達力

第十三章:並發編程

並發防禦

建議分離並發代碼和其他代碼;限制並發數據的作用域;避免共享數據;先使非並發代碼正常工作