【前端閱讀】——《代碼整潔之道》摘記之整潔代碼、命名、函數、注釋


這本書提出一種觀念:代碼質量與其整潔度成正比。干凈的代碼,既在質量上較為可靠,也為后期維護、升級奠定了良好基礎。(作者認為書可以有另一個名字:《如何在意代碼》)

讀這本書,促使我思考代碼中何謂正確,何謂錯誤。更重要的是,它還可以促使自己重新評估自己的專業價值觀,以及對自己技藝的承諾。

1、整潔編程

  • 混亂風險:制造混亂無助於趕上期限。混亂只會立刻拖慢你,叫你錯過期限,趕上期限的唯一方法——做得快的唯一方法——就是始終盡可能的保持代碼整潔。
  • 代碼感:寫整潔代碼,需要遵循大量的小技巧,貫徹刻苦習得的“整潔感”。這種“代碼感”就是關鍵所在。缺乏“代碼感”的程序員,看混亂是混亂,無處着手。有“代碼感”的程序員能從混亂中看出其他的可能與變化。“代碼感”幫助程序員選出最好的方案,並指導程序員制訂修改行動計划,按圖索驥。
  • “整潔的代碼只做好一件事”——Bjarne Stroustrup(C++語言發明者)。軟件設計的許多原則最終都會歸結為這句警語。糟糕的代碼想做太多事,它意圖混亂、目的含混。整潔的代碼力求集中。每個函數、每個類和每個模塊都全神貫注於一事,完全不受四周細節的干擾和污染。

    整潔的代碼應可由作者之外的開發者閱讀和增補。它應當有單元測試和驗收測試。它使用有意義的命名。它只提供一種而非多種做一件事的途徑。它只有盡量少的依賴關系,而且要明確地定義和提供清晰、盡量少的API。代碼應通過其字面表達含義,因為不同的語言導致並非所有必需信息均可通過代碼自身清晰表達。——Dave Thomes(OTI公司創始人,Eclipse戰略教父)

  • 貝克的簡單代碼規則:消除重復並提高表達力,提早構建簡單抽象
  1. 能通過所有測試
  2. 沒有重復代碼
  3. 體現系統中的全部設計理念
  4. 包括盡量少的實體,比如類、方法、函數等

 

2、有意義的命名

  • 名副其實
  • 避免誤導
  • 做有意義的區分
  • 使用讀的出來的名稱
  • 使用可搜索的名稱
  1. 單字母名稱用於短方法中的本地變量。名稱長短應與其作用域大小相對應。若變量或常量可能在代碼中多處使用,則應賦其以便於搜索的名稱。
  • 避免使用編碼
  1. 不必用m_前綴來標明成員變量。應當把類和函數做的足夠小,消除對成員前綴的需要。(人們會很快學會無視前綴或后綴,只看到名稱中有意義的部分。代碼讀的越多,眼中就越沒有前綴。最終,前綴變作了不入法眼的廢料,變作了舊代碼的標志物)
  • 避免思維映射
  1. 單字母變量名就是個問題。在多數除循環計數器之外的其他情況下,單字母名稱不是個好選擇,讀者必須在腦中將它映射為真實概念。(僅僅是因為有了a和b,就要取名為c,實在並非像樣的理由。)
  2. 聰明程序員和專業程序員之間的區別在於:專業程序員了解,明確是王道。專業程序員善用其能,編寫其他人能理解的代碼。
  • 類名
  1. 類名和對象名應該是名詞或名詞短語,如Customer、WikiPage、Account和AddressParser
  2. 避免使用Mannager、Processor、Data、或Info這樣的類名。
  • 方法名
  1. 方法名應當是動詞或動詞短語,如postPayment、deletePage或save
  2. 屬性訪問器、修改器和斷言應該根據其值命名,並依Javabean標准加上get、set和is前綴。
    string name = employee.getname(); customer.setName("mike"); if (paycheck.isPosted())...
  • 別扮可愛
  1. 言到意到,意到言到。
  • 每個概念對應一個詞
  1. 給每個抽象概念選一個詞,並且一以貫之。(對於那些會用到你代碼的程序員,一以貫之的命名法簡直就是天降福音)
  • 別用雙關語
  1. 避免將同一單詞用於不同目的。同一術語用於不同概念,基本上就是雙關語了。
  2. 比如,在多個類中都有add方法,該方法通過增加或連接兩個現存值來獲得新值。假設要寫個新類,該類中有一個方法,把單個參數放到群集(collection)中。如果把這個方法叫做add,貌似和其他add方法保持了一致,但實際上語義卻不同,應該用insert或append之類詞來命名才對。(把該方法命名為add,就是雙關語了)
  • 使用解決方案領域名稱
  1. 記住,只有程序員才會讀你的代碼。所以,盡管去用那些計算機科學術語、算法名、模式名、數學術語吧
  2. 比如,對於熟悉訪問者(VISITOR)模式的程序員來說,名稱AccountVisitor富有意義。(程序員要做太多技術性工作,給這些事取個技術性的名稱,通常是最靠譜的做法)
  • 使用源自所涉問題領域的名稱
  1. 如果不能用程序員熟悉的術語來給手頭的工作命名,就采用從所涉問題領域而來的名稱吧。至少,負責維護代碼的程序員就能去請教領域專家了。
  2. 優秀的程序員和設計師:其工作之一就是分離解決方案領域和問題領域的概念。與所涉問題領域更為貼近的代碼,應當采用源自問題領域的名稱。
  • 添加有意義的語境
  1. 很少有名稱是能自我說明的——多數都不能。反之,你需要用到良好命名的類、函數或名稱空間來放置名稱,給讀者提供語境。如果沒這么做,給名稱添加前綴就是最后一招了。
  2. 比如,對孤零零的一個state變量來說,可以添加前綴addrFirstName、addrLastName、addrState等,以此提供語境。至少,讀者會明白這些變量是某個更大結構的一部分。當然,更好的方案是創建名為Address的類。這樣,即便是編譯器也會知道這些變量隸屬某個更大的概念了。
  3. 語境的增強,也讓算法能夠通過分解為更小的函數而變得更為干凈利落。
  4. //語境不明確的變量
    private void printGuessStatistics(char candidate,int count){ String number; String verb; String pluralModifier; ... } //有語境的變量 //創建GuessStaticsMessage類,把三個變量做成該類的成員字段
    public class GuessStaticsMessage{ String number; String verb; String pluralModifier; ... }
  • 不要添加沒用的語境
  1. 設若有一個名為“加油站豪華版”(Gas Station Deluxe)的應用,在其中給每個類添加GSD前綴就不是什么好點子。
  2. 只要短命稱足夠清楚,就要比長名稱要好。
  3. 對於Address類的實體來說,accountAddress和customerAddress都是不錯的名稱,不過用在類名上就不太好了。Address是個好類名。如果需要與MAC地址、端口地址和Web地址相區別,我會考慮使用PostalAddress、MAC和URI。這樣的名稱更為准確,而精確正是命名的要點

 

3、函數

  •  短小
  1. 代碼塊和縮進:if語句、else語句、while語句等,其中的代碼塊應該只有一行。該行大抵應該是一個函數調用語句。這樣不但能保持函數短小,而且,因為塊內調用的函數擁有較具說明性的名稱,從而增加了文檔上的價值。
  2. 這也意味着函數不應該大到足以容納嵌套結構。所以,函數的縮進層不該多於一層或兩層。(這樣的函數易於閱讀和理解)
  • 只做一件事
  1. 函數應該做一件事。做好這件事。只做這一件事。
  2. 問題在於很難知道那件該做的事是什么?(其實,有時候一件事也很容易被看作是三件事或很多具體細化的步驟)如果函數只是做了該函數名下同一抽象層上的步驟,則函數還是只做了一件事
  3. 要判斷函數是否不止做了一件事,還有一個方法,就是看是否能再拆出一個函數,該函數不僅只是單純地重新詮釋其實現。
  4. 函數中的區段:只做一件事的函數無法被合理的切分為多個區段。(這也是函數做事太多的明顯征兆)
  • 每個函數一個抽象層級
  1. 函數中混雜不同抽象層級,往往讓人迷惑。讀者可能無法判斷某個表達式是基礎概念還是細節。更惡劣的是,就像破損的窗戶,一旦細節與基礎概念混雜,更多的細節就會在函數中糾結起來。
  2. 自項向下讀代碼:向下規則。這是保持函數短小、確保只做一件事的要訣。讓代碼讀起來像是一系列自項向下的TO起頭段落是保持抽象層級協調一致的有效技巧。

          

  • switch語句
  1. 問題:寫出短小的switch語句很難(包括if/else在內),寫出只做一件事的switch語句也很難,Switch天生要做N件事。
  2. 解決:利用多態,確保每個switch都埋藏在較低的抽象層級,而且永遠不重復。
  3. 如下代碼:將switch語句埋到抽象工廠底下,不讓任何人看到。該工廠使用switch語句為Employee的派生物創建適當的實體,而不同的函數,如calculatePay、isPayday和deliverPay等,則藉由Emplyee接口多態地接受派遣。
  4. 對於switch語句,(作者的)規矩是如果只出現一次,用於創建多態對象,而且隱藏在某個繼承關系中,在系統其他部分看不到,就還能容忍。當然也要就是論事,有時也會部分或全部違反這條規矩。

        

  • 使用描述性的名稱
  1. 沃德原則:“如果每個例程都讓你感到深合己意,那就是整潔代碼。”函數越短小、功能越集中,就越便於取個好名字。
  2. 別害怕長名稱。長而具有描述性的名稱,要比短而令人費解的名稱好。長而具有描述性的名稱,要比描述性的長注釋好。
  3. 選擇描述性的名稱能理清你關於模塊的設計思路,並幫你改進之。追索好名稱,往往導致對代碼的改善重構。
  4. 命名方式要保持一致。使用與模塊名一脈相承的短語、名詞和動詞給函數命名。例如:includeSTeardownPages、includeSetuoPages、includeSuiteSetupPage等
  • 函數參數
  1. 最理想的參數數量是零(零參數函數),其次是一(單參數函數),再次是二(雙參數函數),應盡量避免三(三參數函數)。有足夠特殊的理由才能用三個以上參數(多參數函數)——所以無論如何也不要這樣做。
  2. 閱讀模塊所講述的故事時,includeSetupPage()要比includeSetupPageInto(newPage-Content)易於理解。參數與函數名處在不同的抽象層級,它要求了解目前不是特別重要的細節(即那個Stringbuffer
  3. 測試的角度:要編寫能確保參數的各種組合運行正常的測試用例,是一件非常困難的事情。
  4. 輸出參數比輸入參數還要難以理解。
  • 無副作用
  1. 副作用是一種謊言。函數承諾只做一件事,但還是會做其他被藏起來的事。有時,它會對自己類中的變量做出未能預期的改動。有時,它會對自己類中的變量做出未能預期的改動。有時,它會把變量搞成向函數傳遞的參數或是系統全局變量。無論哪種情況,都是具有破壞性的,會導致古怪的時序性耦合順序依賴。(如果一定要時序性耦合,就應該在函數名稱里說明)
  2. 輸出參數:參數多數會被自然而然地看作是函數的輸入。普遍而言,應避免使用輸出參數。如果函數必須要修改某種狀態,就修改所屬對象的狀態吧。
  • 分隔指令與詢問
  1. 函數要么做什么事,要么回答什么事,但二者不可得兼。
  2. 函數應該修改某對象的狀態,或是返回該對象的有關信息。兩樣都干常會導致混亂。
  • 使用異常替代返回錯誤碼
  1. 從指令式函數返回錯誤碼輕微違反了指令與詢問分隔的規則。它鼓勵了在if語句判斷中把指令當作表達式使用。另一方面,如果使用異常替代返回錯誤碼,錯誤處理代碼就能從主路徑代碼中分離出來,得到簡化。
  2. 抽離Try/Catch代碼塊:Try/Catch代碼塊丑陋不堪。它們搞亂了代碼結構,把錯誤處理與正常流程混為一談。最好把它的主體部分抽離出來,另外形成函數。如圖:
  3. 錯誤處理就是一件事:函數應該只做一件事,錯誤處理就是一件事,因此,處理錯誤的函數不該做其他事。

       

  • 如何寫出這樣的函數
  1. 一開始都冗長而復雜。然后,會打磨,分解函數、修改名稱、消除重復。縮短和重新安置方法。有時還拆散類。同時保持測試通過。
  2. 並不從一開始就按照規則寫函數。一般沒人做得到。

 

4、注釋

  • 注釋會撒謊。
  1. 注釋存在的越久,就離其所描述的代碼越遠,越來越變得全然錯誤。(原因很簡單,程序員不能堅持維護注釋)
  • 注釋不能美化糟糕的代碼
  1. 帶有少量注釋的整潔而有表達力的代碼,要比帶有大量注釋的零碎而復雜的代碼像樣的多。
  • 用代碼來闡述
  1. 很多時候,簡單到只需要創建一個描述與注釋所言同一事物的函數即可。
  • 好注釋——唯一真正好的注釋是你想辦法不去寫的注釋
  1. 法律信息
  2. 提供信息的注釋
  3. 對意圖的解釋
  4. 闡釋
  5. 警示
  6. TODO注釋(一種程序員認為應該做,但由於某些原因目前還沒做的工作)
  7. 放大
  8. 公共API中的Javadoc
  • 壞注釋
  1. 喃喃自語
  2. 多余的注釋
  3. 誤導性注釋
  4. 循規式注釋
  5. 日志性注釋
  6. 廢話注釋
  7. 可怕的廢話
  8. 能用函數或變量時就別用注釋
  9. 位置標記
  10. 括號后面的注釋(盡管這對於含有深度嵌套結構的長函數可能有意義,但只會給我們更願意編寫的短小、封裝的函數帶來混亂。如果你發現自己想標記右括號,其實應該做的是縮短函數)
  11. 歸屬與署名
  12. 注釋掉的代碼
  13. HTML注釋
  14. 非本地信息
  15. 信息過多
  16. 不明顯的聯系
  17. 函數頭
  18. 非公共代碼中的Javadoc

 

注:轉載請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM