《Clean Code》程式碼的整潔之道(一)
《程式碼整潔之道》:細節之中自有天地,整潔成就卓越程式碼
概述
軟體質量,不但依賴於架構及專案管理,而且與程式碼質量緊密相關。這一點,無論是敏捷開發流派還是傳統開發流派,都不得不承認。《程式碼整潔之道》提出一種觀念:程式碼質量與其整潔度成正比。乾淨的程式碼,既在質量上較為可靠,也為後期維護、升級奠定了良好基礎。作為程式設計領域的佼佼者,這些實踐在《程式碼整潔之道》中體現為一條條規則(或稱“啟示”),並輔以來自現實專案的正、反兩面的範例。只要遵循這些規則,就能編寫出乾淨的程式碼,從而有效提升程式碼質量。儘管糟糕的程式碼也能執行,但如果程式碼不整潔,會使整個開發團隊泥足深陷,寫得不好的程式碼每年都要耗費難以計數的時間和資源。然而這種情況並非無法避免。
從《程式碼整潔之道》中可以學到:好程式碼和糟糕的程式碼之間的區別:如何編寫好程式碼,如何將糟糕的程式碼轉化為好程式碼:如何建立好名稱、好函式、好物件和好類;如何格式化程式碼以實現其可讀性的最大化:如何在不妨礙程式碼邏輯的前提下充分實現錯誤處理;如何進行單元測試和測試驅動開發。
現在的軟體系統開發難度主要在於其複雜度和規模,客戶需求也不再像Winston Royce瀑布模型期望那樣在系統編碼前完成所有的設計滿足使用者軟體需求。在這個資訊爆炸技術日新月異的時代,需求總是在不停的變化,隨之在2001年業界17位大牛聚集在美國猶他州的滑雪勝地雪鳥(Snowbird)雪場,提出了“Agile”(敏捷)軟體開發價值觀,並在他們的努力推動下,開始在業界流行起來。在《程式碼整潔之道》(Clean Code),一份整潔的程式碼在質量上是可靠的,為團隊開發,後期維護,重構奠定了良好的基礎。在這本書中作者提出了注重實際開發實踐的細節,而不是站在空洞的理論來談論整潔之道。
第1章 整潔程式碼
糟糕的程式碼
當時趕著推出產品,程式碼寫得亂七八糟。特性越加越多,程式碼也越來越爛,最後再也沒法管理這些程式碼了。是糟糕的程式碼毀了很多公司。
勒布朗(LeBlanc)法則:稍後等於永不(Later equals never)。
混亂的代價
隨著混亂的增加,團隊生產力也持續下降,趨向於零。當生產力下降時,管理層就只有一件事可做了:增加更多人手到專案中,期望提升生產力。可是新人並不熟悉系統的設計。他們搞不清楚什麼樣的修改符合設計意圖,什麼樣的修改違背設計意圖。而且,他們以及團隊中的其他人都揹負著提升生產力的可怕壓力。於是,他們製造更多的混亂,驅動生產力向零那端不斷下降。
花時間保持程式碼整潔不但有關效率,還有關生存。
整潔程式碼的藝術
什麼是整潔程式碼?不同的人會站在不同的角度闡述不同的說法。
Bjarne Stroustrup,C++語言發明者,C++ Programming Language(中譯版《C++程式設計語言》)一書作者闡述:
我喜歡優雅和高效的程式碼。程式碼邏輯應當直截了當,叫缺陷難以隱藏;儘量減少依賴關係,使之便於維護;依據某種分層戰略完善錯誤處理程式碼;效能調至最優,省得引誘別人做沒規矩的優化,搞出一堆混亂來。整潔的程式碼只做好一件事。
Grady Booch(《面向物件分析與設計》作者)闡述:
“整潔的程式碼簡單直接。整潔的程式碼如同優美的散文。整潔的程式碼從不隱藏設計者的意圖,充滿了乾淨利落的抽象和直截了當的控制語句。”
我認為整潔的程式碼:是意圖明確、命名規範、符合所使用語言程式碼規範的;遵守原則、可複用、易擴充套件、便於維護的。整潔的程式碼應具有自我解釋性,而不需要太多多餘的註釋。
美國“童子軍軍規”中有條說道“讓營地比你來時更乾淨”,對映到我們編碼中就是我們每日在提交程式碼的時候要確保程式碼時整潔的,否則就進行重構。好的程式碼是靠大家一點點積累出來的。問題發現的越早,修改的成本就越少。如果每次簽入時,程式碼都比簽出時乾淨,那麼程式碼就不會腐壞。清理並不一定要花多少功夫。
整潔的程式碼就是一種簡約(簡單而不過於太簡單)的設計,閱讀程式碼的人能很清晰的明白這裡在幹什麼,而不是隱澀難懂,整潔的程式碼讀起來讓人感覺到就像閱讀散文-藝術的沉澱,作者是精心在意締造出來。
第2章 有意義的命名
名副其實、見名知意
命名包括變數、函式、引數,類等,一個好的命名能夠很好的表述其所承載的業務,從命名上就已經很好的答覆了為什麼存在,做了什麼事,應該怎麼用等的大部分的問題,閱讀者看到它的時候不必去深究其實現細節,一切都在命名上一目瞭然。一個好的命名必須是名副其實,不存在歧義(雙關語或常見屬於衝突),直接了當(否定語句或者誤導性命名)。
命名小結:
1、當避免使用與本意相悖的詞。例如,hp、aix 和 sco 都不該用做變數名,因為它們都是 UNIX 平臺或類 UNIX 平臺的專有名稱。
2、做有意義的區分。 ProductInfo 或 ProductData 類,那它們的名稱雖然不同,意思卻無區別。
3、使用讀得出來的名稱。人類長於記憶和使用單詞。
4、使用可搜尋的名稱。單字母名稱和數字常量有個問題,就是很難在一大篇文字中找出來。
5、避免使用編碼。
6、類名和物件名應該是名詞或名詞短語,類名不應當是動詞。
7、方法名應當是動詞或動詞短語。
8、命名別用雙關語。
9、命名使用解決方案領域名稱。
取好名字最難的地方在於需要良好的描述技巧和共有文化背景。與其說這是一種技術、商業或管理問題,還不如說是一種教學問題。其結果是,這個領域內的許多人都沒能學會做得很好。
我們有時會怕其他開發者反對重新命名。如果討論一下就知道,如果名稱改得更好,那大家真的會感激你。多數時候我們並不記憶類名和方法名。我們使用現代工具對付這些細節,好讓自己集中精力於把程式碼寫得就像詞句篇章、至少像是表和資料結構(詞句並非總是呈現資料的最佳手段)。改名可能會讓某人吃驚,就像你做到其他程式碼改善工作一樣。別讓這種事阻礙你的前進步伐。
第3章 函式
從彙編/C時代開始的到現在函式一直都存在與我們開發中不可或缺的一部分,結構化組織,重用作為函式式語言的一等公民,所有程式的第一組程式碼。
函式小結:
1. 好的函式必須足夠的小,其次還是足夠的小。很容易想像閱讀上千行的程式碼,是多麼巨大的自我心理挑戰,在實習的時候工作於毫無分層邏輯的WinForm平臺下,完全依賴RAD模式帶來後置cs頁面上千行的程式碼,每次修改都令我惱怒,恨不得重寫整個業務邏輯。
2. 一個函式在於短小精悍,只作一件事情,並做好這件事,只做一件事才能得到更好的利用函式名錶述自己。
3. 每個函式一個抽象層級。
4. 好的函式還應該是CQS(查詢命令分離)無副作用的(不存在隱藏歧義的背後邏輯),並對其他型別不存在“依戀情節(Feature Envy)“(類中的變數被所有的函式使用這是理想的高內聚,萬物皆有其位,而後物盡歸其位)。
5. 函式的引數應該足夠的少,無最好,一次之,再次為二,儘量避免三個以及三個以上,對於太多的引數你可能該採用IntroduceParameterObject(引入引數物件)。
6. 使用異常替代返回錯誤碼。
7. 函式應該只做一件事。
8. 不要重複的程式碼。重複在軟體系統是萬惡的,我們熟悉的分離關注點,面向物件,設計原則…都是為了減少重複提高重用,Don’t repeat yourself!(DRY)。遠離重複,拒絕重複,方法有很多,抽象到基類或放到底層公共類庫中。
寫程式碼和寫別的東西很像。在寫論文或文章時,你先想什麼就寫什麼,然後再打磨它。初稿也許粗陋無序,你就斟酌推敲,直至達到你心目中的樣子。沒有人能一次性就將函式寫的很完美,好的函式是通過重構得到的。
我寫函式時,一開始都冗長而複雜。有太多縮排和巢狀迴圈。有過長的引數列表。名稱是隨意取的,也會有重複的程式碼。不過我會配上一套單元測試,覆蓋每行醜陋的程式碼。然後我打磨這些程式碼,分解函式、修改名稱、消除重複。我縮短和重新安置方法。有時我還拆散類。
第4章 註釋
註釋是一把雙刃劍,好的註釋能夠給我們好的指導,不好的註釋只會將我們誤導。註釋是彌補程式碼子表述不足的一種手段,就像設計模式是用來彌補語言不足一樣。
程式碼是我們獲取資訊的準確來源,註釋隨著專案人員的更替 反覆的修改最終可能詞不達意了,因為很多開發人員在整合程式碼,修改方法的時候並不是總是同步修改註釋。
有時候看到一個函式的程式碼寫的很糟糕,邏輯很混亂,有開發人員可能想,給這個函式加上幾行註釋,這樣有可能起到適得其反的作用,這時要做的是將函式整理乾淨。
並不是寫出完備的註釋就是好的開發人員,如果程式碼清晰的表述自己意圖,那麼註釋反而多餘。在《重構-改善現有程式碼之道》中Martin Fowler指出多餘的註釋是一種程式碼壞味道。就是好的註釋隨著專案的維護不斷的重構很多時候也會變得不那麼適應,而我們很少會去主動維護。再則誤導性的註釋更為使用者所憎恨。當然有時我們也得使用註釋,註釋並不是萬惡的,當我們沒法用程式碼來描述自己的時候,我們需要註釋去描述意圖;多餘有副作用的程式碼給使用者提供警告註釋。TODO開發時進度控制,比如你在進行較大規模領域重構,目前有些邏輯不再適應,不那麼自然,而對它的重構還在任務列表最後,你可以選擇標註在TODO中,最後完成從ToDoList中去掉每一個TODO任務。
程式碼即註釋,很多書和大師都這麼講,意思是我們要用程式碼本身來解釋我們的意圖,那就要求我們要控制好函式只做一件事,函式名和變數名要規範和可讀。
當然也不是所有的註釋都沒有用,像下面幾種型別的註釋是有必要的:
· 具有警示性的註釋;
· 描述一些負責業務場景;
· 有些函式現在還是一個空殼,但在將來可能有用,有必要寫。
當我們不得不寫一些註釋的時候,要確保言簡意賅,能夠很好的表達意思,不要造成誤解,也不要寫多餘的廢話。"Comments Do Not Make Up for Bad Code."(註釋不是對劣質程式碼的補救)。事實上好的程式碼即便沒有註釋也擁有良好的可讀性,但恰當的註釋會讓程式碼變得更可讀、可維護性更高。
日誌式註釋,一般出現在一個類的開始部分,記錄每次修改時的時間、人員名稱和修改內容。隨著時間的推移這類註釋會變得非常冗長。這類註釋在一些專案中很普遍,而且有時會被嚴格要求寫,但書中強調現在的原始碼都會有原始碼工具來進行管理,修改記錄在原始碼工具中有儲存,這種日誌式的註釋應該全部刪除。因為別人下載了你的原始碼之後,很可能會改成他的名稱。
專案程式碼中經常會出現被註釋掉的程式碼,這對後面的維護人員會造成困擾,也會使程式碼變得混亂,這種程式碼同樣可以刪掉。因為閱讀者完全不知道被註釋掉的程式碼,是什麼意圖、原作者為什麼把它註釋,也可能程式碼是被更改了很多次,但註釋依舊保留在那,越積越多。因此作者的建議:果斷刪除。
第5章 格式
或許你認為“讓程式碼能工作”才是專業開發者的第一優先順序。你今天編寫的功能,極有可能在下一版本中被修改,但程式碼的可讀性卻會對以後可能發生的修改行為產生深遠影響。原始程式碼修改之後很久,其程式碼風格和可讀性仍會影響到可維護性和擴充套件性。即便程式碼已不復存在,你的風格和律條仍存活下來。
良好的程式碼格式,會使得我們閱讀更容易,一套共同的格式會讓我們查詢理解更快速。每個團隊都應該遵循一套固定的程式碼格式規範,整個軟體系統的統風格統一,而不是各自為政各成一體。
用什麼樣的程式碼風格不是關鍵,關鍵是整個專案組的成員應當使用相同的程式碼風格,讓多個人編寫的程式碼看起來像一個人書寫的。我個在程式碼中使用的括號風格是1TBS(One True Bracing Style,也叫做K&R風格,這種風格是Kernighan和Ritchie兩位老師在"The C Programming Language"一書中使用的程式碼風格),當然Allman風格(FreeBSD系統的作者之一使用的程式碼風格)也是很好的選擇。
格式小結:
1、合理使用空白行,同一類的有關聯程式碼行放在一起。
2、區域性變數申明儘可能靠近使用的地方。
3、實體變數放在類的頂部,因為設計良好的類,實體變數會被大多數的方法使用。
4、相關函式(A函式呼叫了B函式,B函式應放在A函式的下面)放在一起,也包括的過載的函式。被呼叫的函式應該放在執行呼叫的函式下面,建立一種自頂向下貫穿的良好資訊流。
5、一行程式碼的寬度:120字元之內,最好不要拖動橫向滾動條。
6、橫向空格,比如一個表示式中的符號左邊和右邊與符號之間加空格,這個在VS中的程式碼格式化會自動幫我們做了。
7、縮排,只有一行的也按縮排規則來。
8、向報紙學習:看看寫得很好的報紙。從上到下閱讀,在頂部,有頭條,告訴你故事主題。然後第一段是大綱,接著細節漸次增加。原始碼應當一樣,名稱簡單且一目瞭然。原始檔最頂部應該給出高層次概念和演算法。細節應該往下漸次展開,直到最底層的函式和細節。報紙有許多篇文章,多數短小精悍。假若一份報紙只登一篇長篇故事,沒人會去讀它。
現代IDE(整合開發環境)幾乎都有程式碼格式化程式碼的功能,你只需要設定好你使用的程式碼風格就可以了,其實不只是IDE,很多高階的文字編輯工具也能夠按照指定的風格格式化你的程式碼。
大師級程式設計師把系統當作故事來講,而不是當作程式來寫。記住,程式設計是一種藝術。