读《代码整洁之道》
什么是整潔代碼
代碼的質量非常重要,糟糕的代碼有可能會毀了一個公司。對于一個很注重代碼質量的人來說呆在一個只關注交付而不關注代碼質量的公司是很痛苦的。
什么是整潔的代碼,不同的人又不同的定義。我認為整潔的代碼應該是符合所使用語言代碼規范的;可復用的;便于維護的;簡潔的。
糟糕的代碼想做太多的事情,意圖混亂;整潔的代碼的每個函數、類和模塊都全神貫注一件事,不受周圍的干擾,也就是設計原則中的單一原則。
命名的藝術
命名隨處可見,變量、函數、類、模塊、命名空間等都需要有好的命名。一個名稱如果能讓閱讀者一看就知道要表達的意思,就可以認為是一個好的名稱。
不要使用單個字母來做變量名,時間一長,自己都不清楚自己當初的命名是什么意思。小方法體,如循環中的計數器除外。
不要使用有誤導性的字母作為變量名,比如小寫字母l和大寫字母O,因為他們和數字的一和零很像,有的字體還比較好區分,但大多數字體很難分辨。
「匈牙利命名法(HN)」:該命名法是一位叫 Charles Simonyi 的匈牙利程序員發明的,后來他在微軟呆了幾年,于是這種命名法就通過微軟的各種產品和文檔資料向世界傳播開了。在當時那個時代編譯器并不做類型檢查,程序員需要使用HN來幫助自己記住類型。現在的高級靜態編程語言具有更豐富的類型系統,編譯器可以很好的做類型檢查,所以使用NH純屬多余。使用NH的幾個弊端:
增加了名稱的長度
使名稱變得不可讀
增加了修改名稱的難度,修改了變量的類型,變量名就要隨著修改,否則會造成誤導。
書中講到對接口的命名不要使用“I”作為前綴,這點我持保留意見,可能因為我一直是從事的.NET上的開發,.NET的類庫中的接口基本都是使用“I”作為前綴的,而且在《NET 設計規范》一書中也強調接口要使用“I”作為前綴。
對于方法名做到每個概念一個詞,應該保持一致,比如對于綁定數據的方法,不要有的地方用BindData,而另一些地方使用DataBind,總之做到在整個代碼中保持風格一致。
最后想說的是命名除了一些通用的法則外,對于一些規范性的問題還是要遵循所使用語言或平臺規定或是約定俗成的慣例。比如方法名C#中推薦使用Passcal風格,而在Java中則是使用Camel風格。
函數
函數一直都被要求要短小,有不少書中都以行數來作為標準,比如一個行數在20行以內被稱為小函數,或是要在5行以內才是小函數。以行數來要求似乎有些苛刻,有一些極端的情況,比如初始化一個Model,里面有幾十個字段,這時這個初始化函數中就有幾十行代碼,而且是無法拆分的,所以我認為,函數只要是在做一件事情就可以了。通常來說如果函數只做一件事,自然就不會很長。我對函數長度的極限是橫豎都不要超過一屏。
函數應該只做一件事,判斷函數時候只做一件事,看函數中是否很能夠拆分,如果可以,就果斷進行重構。
當函數中有Swicth語句的時候,就不可避免的要做多件事情了,而且函數會隨著Switch條件的增加會越來越長。因為函數中做了多件事情,違反了SRP原則,因為隨著增加條件我們要去修改這個函數,違反了OCP原則。這個問題可以通過工廠模式來解決。
函數的名稱要使用描述性的名稱,讓人一看名稱就知道該函數是做什么的。當函數只做一件事情的時候,取名就容易多了。還有比較重要的一點,風格要保持一致。
在一個函數中不要去調用職責之外的另外的函數,尤其是底層的函數,否則給高層調用帶來風險。舉個簡單的例子:比如在用戶登錄的時候我們可能會有一個CheckPassword的方法來驗證登錄的用戶名和密碼,如果在CheckPassword函數中在驗證成功后調用Session.Init()來對Session進行初始化,就會存在隱患。根據名稱來看只是檢查密碼用,如果有人在非登錄的情況下調用了該方法,會更改當前會話。
使用異常代替錯誤返回碼,如果使用錯誤返回碼會要求立即處理錯誤,當在高層調用很多底層方法時,每個方法都要去根據錯誤返回碼進行處理,會造成函數邏輯混亂,如果使用異常處理則只需要在catch中處理即可。
遠離重復,拒絕重復,方法有很多,抽象到基類或放到底層公共類庫中。
沒有人能一次性就將函數寫的很完美,好的函數是通過重構得到的。
注釋
注釋是一把雙刃劍,好的注釋能夠給我們好的指導,不好的注釋只會將我們誤導。注釋是彌補代碼中表述不足的一種手段,就像設計模式是用來彌補語言不足一樣。
代碼是我們獲取信息的準確來源,注釋隨著項目人員的更替 反復的修改最終可能詞不達意了,因為很多開發人員在整合代碼,修改方法的時候常常不會修改注釋。
有時候看到一個函數的代碼寫的很糟糕,邏輯很混亂,有開發人員可能想,給這個函數加上幾行注釋,這樣有可能起到適得其反的作用,這時要做的是將函數整理干凈。
代碼即注釋,很多書和大師都這么講,意思是我們要用代碼本身來解釋我們的意圖,那就要求我們要控制好函數只做一件事,函數名和變量名要規范和可讀。
當然也不是所有的注釋都沒有用,像下面幾種類型的注釋是有必要的:
具有警示性的注釋
描述一些負責業務場景
有些函數現在還是一個空殼,但在將來可能有用,有必要寫
當我們不得不寫一些注釋的時候,要確保言簡意賅,能夠很好的表達意思,不要造成誤解,也不要寫多余的廢話。
還有一種日志被稱為日志式注釋,一般出現在一個類的開始部分,記錄每次修改時的時間、人員名稱和修改內容。隨著時間的推移這類注釋會變得非常冗長。這類注釋在一些項目中很普遍,而且有時會被嚴格要求寫,但書中強調現在的源代碼都會有源代碼工具來進行管理,修改記錄在源代碼工具中有保存,這種日志式的注釋應該全部刪除。
有的開發人員喜歡在注釋中簽上自己的名字,這種做法也沒沒有必要,因為我們有源碼管理工具。
項目代碼中經常會出現被注釋掉的代碼,這對后面的維護人員會造成困擾,也會使代碼變得混亂,這種代碼同樣可以刪掉,因為我們有源碼管理工具。
錯誤處理
錯誤處理簡單來說就是當軟件出現錯誤時還能正常工作。錯誤處理很重要,但不能打亂的原本的代碼邏輯。
使用異常處理而非返回碼,底層往上拋,最上層集中處理。這點在函數相關章節中也提到過,之所以看到有的地方是使用錯誤返回碼,是因為早期的一些語言沒有異常處理機制,現在的語言基本都有異常處理機制。
異常的信息應該足夠充分(包含出錯的位置以及原因)。
不要在catch塊中去實現業務邏輯,就是說當出現異常的時候一定要拋出,而不要改變狀態或是做其他一些操作,這樣會留下很多陷進。
底層的方法不要返回Null值,否則在調用時會添加很多的判斷,可以拋出異?;蚍祷靥乩龑ο?#xff0c;特例對象是指返回一個函數返回值類型的空對象。
在最上層捕獲了異常后,記錄日志,給出相應的友好結果反饋,千萬切記不要再往上拋了,例如:一個控制臺程序,如果在Main函數中捕獲到異常再往上拋出,控制臺程序就崩潰了。
單元測試
我工作以來所經歷的公司中都很少使用單元測試,以致于我現在對單元測試這方面還不是特別熟悉,只是在自己的個人項目中寫過一些單元測試的代碼。在.NET平臺下可以使用VS自帶的單元測試功能或是NUnit。
有一種編程的方法叫TDD(測試驅動開發),意思是先寫單元測試,然后寫對應的代碼,通過修改調試讓寫的代碼通過單元測試。使用TDD,會使測試覆蓋所有的代碼,測試代碼和生產代碼的比例有可能會達到1:1 ,所以也會帶來成本的問題。TDD三定律:
在編寫不能通過的單元測試前,不可以編寫生產代碼
只有編寫剛好無法通過的單元測試,不能編譯也算不通過
只可編寫剛好足以通過當前失敗測試的生產代碼
測試代碼和生產代碼一樣的重要,也需要保持整潔。測試代碼要隨著生產代碼的修改而修改,否則只會產生大量無用的測試代碼,而且也會給生產代碼的修改帶來風險。
單元測試的好處:
有了測試不用擔心對代碼的修改
有了測試可以毫無顧慮的去改進架構和設計
如果您是做項目,快速滿足客戶需求就可以了,沒有必要在項目中添加單元測試,如果是開發產品,單元測試還是非常重要的,因為產品的快速迭代就像在給高速飛行中的飛機加油一樣,不能停還要保證穩定性,不能出任何問題,而單元測試是一個很好的保障。
類
類通常由變量、屬性和方法組成。按照書中所講的Java的約定,類應該由一組變量開始,如果有靜態公共常量,應該放在前面,然后是私有靜態變量和私有實體變量。公共函數跟在變量之后,一些供公共函數調用的私有工具函數在公共函數之后。
和函數一樣,類也應該要盡可能的短小。但和函數不同不是以代碼行數來權衡,而是以職責。如果無法準確的為某個類命名,則有可能是該類的職責過多。
單一職責原則(SRP):類或模塊應該有且只有一條加以修改的理由。
在實際的工作中很多開發人員往往不會思考這么多,他們只想著讓代碼可以工作就可以了,所以經常出現幾千行的大類。系統應該是有許多短小的類而不是少量巨大的類組成。每個小類有單一的職責,只有一個修改的原因,所有這些小類在一起協同工作完成系統的功能。
高內聚:如果一個類中每個變量都被每個方法所使用,則該類有最大的內聚性,保持內聚性得到許多短小的類,內聚性高說明變量和方法相互關聯形成一個邏輯整體。
對于類要要良好的嗅覺,一個類在開始創建的時候,職責還是很單一的,但隨著功能越來越復雜,參與開發的人越來越多,就可能慢慢變得臃腫,需要我們能時刻保持警惕,嗅出壞味道,并做重構。
總結
很多年前看的此書,現在翻翻還是很有幫助,好書就應該時常翻閱下,不同時期會有不同的理解。
總結
- 上一篇: 构建简单的微服务架构
- 下一篇: Web Template Studio