優秀程序員需要掌握的 Clean Code


優秀程序員需要掌握的 Clean Code


王國的工匠師

​在一個古老的王國裡,有兩個建築工匠——風師與土師。就實操經驗而言,他們的技藝相差無幾。差別在於,風師以“效率”聞名,若你要修一棟房子,找他僅需一月時間便可完成;土師以“穩定”聞名,他總是將東西歸置整齊後,方才破土動工,風師只需一月完成的工程,土師需要三個月。

風師有一個大麻袋,這是他的百寶箱,他所有的工具都在裡面。他把錘子命名為 1 號,斧子命名為 2 號。這樣他工作時,只需叫出簡短的名字:1 號拿來固定,2 號拿來伐木……用完之後,將所有工具收回麻袋。風師總是揹著麻袋來,揹著麻袋去。不拘泥於形式,不管哪裡都瀟灑走一回。風師常說:這就是“效率”的秘訣。

土師有非常多的工具箱,每種工具都放在自己的類別裡。他從不給工具起別名或簡稱,錘子便叫錘子,斧子便叫斧子,甚至更為詳細。向下又細分為羊角錘、檢驗錘、開山斧、板斧等等。土師開工前總是興師動眾,但他的工作從未出過大的紕漏,倒也令人欽佩不已。

風師每天總是業務繁忙,一個接一個的工程甚至排到了明年。百姓修房子,就圖一個“快”字。相比之下,土師的生意就顯得門可羅雀了。經常是在風師的活接不過來時,才有人請土師來幫忙建造。久而久之,人們已經默認風師的技藝高過土師,大戶人家動工時,都以請到風師為榮,又因為風師總是揹著他的大麻袋,坊間便尊稱其為“金麻袋”。風師賺得盆滿缽滿,已經成為王國的小富人家了。但土師仍然是不慌不忙,細緻地做著自己的工作。

王國幾十年來風調雨順,國庫日漸充盈。國王大喜,這日,國王決定擴建皇宮。百官向國王舉薦了風師與土師。

國王請來兩位工匠,命其各修建一座涼亭,作為考驗。

風師扛來他的大麻袋,準備好建材後,風風火火開始修建。10 號和砂石,15 號打樁……風師操作行雲流水,但由於他的工具編號只有自己才熟悉,其他人很少能幫上忙。國王看著風師的操作,漸漸皺起了眉頭。

土師將其工具箱一一打開,當他的工具都準備好時,風師已經開始打地基了。然後土師才開始建造涼亭。他指揮著建築工用砂漿機混合砂漿,龍門架起吊……看著土師有條不紊的指揮,國王眉頭方才漸漸舒展開,眼中露出讚許之色。

最終土師仍然使用了三倍於風師的時間方才完成涼亭的建造,但國王決定,任命土師為皇宮建造的御用工程師。

百官不解,只聽國王說到:風師、土師的技藝巧奪天工,都是王國的能工巧匠。但風師做事隨性,工具雜亂。皇宮擴建工程浩大,當人員逐漸增加,此法必亂。相比之下,風師只可偏居一隅,土師方可擔此大任。

後來土師的表現恰好印證了國王的話,在土師的指揮下,皇宮的擴展只用了一年的時間。新建的皇宮恢弘大氣,安全可靠。百姓在震驚之餘,方才想起:由土師經手的項目,無論大小,似乎從未超過一年。風師雖以快聞名,但負責一箇中等建築時,常常會延期至兩年左右。負責小型建築尚可,當格局擴大後,土師才是最有“效率”的人。

故事的寓意很簡單,工程質量與效率離不開好的建築習慣。當我們負責一個小型項目時,我們追求速度,力求快速出成果,這時可以率性而為。當項目逐漸擴大,規範就會逐步顯出它的重要性。

在軟件開發中也是一樣,乾淨的地板能減少事故發生,歸置到位的工具能提升生產力。軟件質量,不但依賴於架構及項目管理,而且跟代碼質量息息相關。代碼質量與其整潔度成正比。乾淨的代碼,既在質量上較為可靠,也為後期維護、升級奠定了良好的基礎。


​破窗理論與童子軍軍規

犯罪心理學中有一個破窗理論,以一幢有少許破窗的建築為例,如果那些窗不被及時修理好,可能會有破壞者破壞更多的窗戶。最終他們甚至會闖入建築內,如果發現無人居住,也許就在那裡定居或者縱火。一面牆,如果出現一些塗鴉沒有被清洗掉,很快地,牆上就佈滿了亂七八糟、不堪入目的東西;一條人行道有些許紙屑,不久後就會有更多垃圾,最終人們會理所當然地將垃圾順手丟棄在地上。

在編程中也經常出現這樣的問題,當我們接手一份混亂的代碼後,如果不及時加以整理,最終將會導致代碼越來越混亂。這是一種基礎的價值謎題,之前的混亂與期限的壓力讓開發者繼續製造混亂。當我們 review 代碼時,聽到最多的辯解就是:前一個人就是這麼寫的。事實上,問題的癥結在於:他們沒有花時間讓自己做得更快。

美國童子軍有一條簡單的軍規:讓營地比你來時更乾淨


代碼整潔之道

清理代碼也許只是改好一個變量名,拆分一個有點過長的函數,消除一點點重複代碼,清理一個嵌套 if 語句。當梳理代碼時,堅守此軍規:每次 review 代碼讓代碼比你發現它時更整潔

優秀程序員需要掌握的 Clean Code

一、謹慎命名

​給函數和變量取個好名字是優秀程序員的基本功,取名的基本要求是 名副其實見文知意。如果名稱需要註釋來補充,那就不算是個好名字。

<code>var d // 日期/<code>

​修改為:

<code>var date/<code>

取名的第二個要求是 避免誤導。比如數據本身不是一個 list,那就別用 list 來命名,因為 list 一詞對程序員有特殊的含義。

優秀程序員需要掌握的 Clean Code

修改為:


優秀程序員需要掌握的 Clean Code

取名的第三個要求是 去掉冗餘。Variable一詞永遠不應當出現在變量名中,Table一詞永遠不應當出現在表名中。nameString 會比 name 好嗎?ProductInfo 、 ProductData 和 Product 有什麼區別?更糟糕的是,如果代碼中同時存在 Article 和 ArticleInfo 類,程序員怎麼知道該調用哪個類呢?多個意義含混的冗餘詞彙只會讓閱讀者困惑,

要區分名稱就要以讀者能鑑別不同之處的方式來區分

例如:開始你用了 address 變量表示用戶居住地,如上海、北京。後來又要求更詳細地描述用戶的居住地。如上海黃浦區、浦東區,北京海淀區、朝陽區。用什麼來命名這個詳細地址呢?detailAdress?smallAddress?還是 anotherAddress?這些統統都不是好的做法,牢記上述法則:以讀者能鑑別不同之處的方式來區分,這時比較好的做法是修改之前的 address 變量名字為 city,再將區域的地址命名為 district。

取名的第四個要求是:嚴謹,不要俏皮。筆者曾經接手一位外國同事寫的代碼,在一個類中,這位外國同事使用了 wearClothes、wearPants 命名函數,之後又出現一個 startParty 函數。仔細理解後,筆者才發現,這是代表軟件系統的第一步準備、第二步準備,然後正式啟動這三個流程。或許這位同事寫這個類時心情不錯,將其比喻成了一個 party 的流程,但對於讀者來說,梳理這三個函數的意思著實要費一番心思。事實上,此時我們最好將其命名為 firstStepOfPreparation、secondStepOfPreparation、systemBoot,寧可明確,毋為好玩。


二、函數和類

函數和類應該堅持 單一權責原則。保持高內聚,低耦合。隔離會讓系統每個元素的理解變得容易。

單一權責原則:在面向對象編程領域中,單一權責原則(Single responsibility principle)規定每個類都應該有一個單一的功能,並且該功能應該由這個類完全封裝起來。一個類或者模塊應該有且只有一個改變的原因。

過長的函數會造成不易理解,如果某天這個函數需要修改的話,一個長長的函數會大大增加理解成本。並且,小函數也能更好地複用。

如果一個函數做了多件事,一個明顯的標誌是無法為它起一個精準的名字。你會覺得需要函數名需要使用 and 連接,比如 calculateAndPrintPrice,這時候最佳做法是將其拆分為 calculatePrice 和 printPrice 兩個小函數。

函數的第二個規範是 儘量不要在參數中傳遞狀態值,狀態值是函數做了多件事的明顯標誌。例如:

優秀程序員需要掌握的 Clean Code

修改為:


優秀程序員需要掌握的 Clean Code

函數的第三個規範是 同一個函數中的代碼應該屬於同一層級。良好的軟件設計要求分離位於不同層級的概念,較低層級概念和較高層級概念不應混雜在一起。


優秀程序員需要掌握的 Clean Code

修改為:


優秀程序員需要掌握的 Clean Code

三、壞註釋與好註釋

註釋並不是越多越好,有的註釋純屬無意義的廢話,例如:

優秀程序員需要掌握的 Clean Code

這些註釋看起來就像是喃喃自語,或許讀者閱讀這些註釋的時間比讀代碼還要長。

好的註釋只應該用在必要時,用於警告其他程序員會出現某種後果的註釋是有用的,例如:

優秀程序員需要掌握的 Clean Code

但,最好的註釋是 沒有註釋,若代碼足夠有表達力,用代碼來展示意圖往往會更好。註釋總是一種失敗。當我們無法找到不用註釋就能表達自我的方法時,我們寫了註釋,這並不值得慶賀。因為註釋常常會撒謊。原因很簡單:

程序員不能堅持維護註釋,尤其是別人寫的註釋。當另一個人修改了代碼後,往往不會去閱讀上一個人寫的註釋,再修改註釋。所以註釋常常會與其所描述的代碼分割開來,孑然飄零,越來越不準確。

寫註釋的常見動機之一是原有代碼混亂。當我們閱讀代碼時,發現已有的代碼令人困擾、亂七八糟。這時我們也許會告訴自己:“等我閱讀清楚後,給它寫點註釋!”別那樣做!最好的做法是把代碼整理乾淨。

優秀程序員需要掌握的 Clean Code

修改為:

優秀程序員需要掌握的 Clean Code

四、良好的格式

在我們閱讀報紙時,在頂部,你期望有個頭條,告訴你故事的主題。然後第一段是整個故事的大綱,給出粗線條概述,但隱藏了故事細節。接著讀下去,細節漸次增加,直至你瞭解所有的細節。

代碼的格式也要像新聞文章一樣,最頂部給出高層次概念,向下漸次展開細節。函數應該緊跟調用處,保證垂直方向上的靠近。如果格式混亂,讀者在閱讀時總會滑上滑下,導致思維跳躍,增加不必要的理解難度。

影響格式的第二個要素是 縮進與間隔,現代化的 IDE 都有格式化代碼快捷鍵,你也可以在設置中搜索"Reformat Code",自定義格式化代碼快捷鍵。隨時格式化,並去掉多餘的空行,讓我們的代碼保持清爽、整潔。


五、數據結構

在有的源代碼中,作者採用長長的鏈式調用,甚至會鼓吹自己只寫了一行代碼便實現了此功能。事實上,絕不應該為了節省變量,寫過長的鏈式調用,否則容易造成"火車失事"。正確的做法是遵守“得墨忒定律”:適當拆解鏈式調用,只和朋友談話,不和朋友的朋友談話,使得代碼閱讀和調試都更方便。

得墨忒定律(The Law of Demeter):模塊不應該瞭解它所操作對象的內部情形。

優秀程序員需要掌握的 Clean Code

修改為:


優秀程序員需要掌握的 Clean Code

六、錯誤處理

在處理程序異常時,我們常常會用到 try / catch 代碼塊,而 try / catch 代碼塊醜陋不堪,他們搞亂了代碼結構,把錯誤處理與正常流程混為一談。最好的做法是 把 try 和 catch 代碼塊的主體部分抽離出來,另外形成函數

。錯誤處理就是一件事。

現代化的語言都有異常機制,異常情況如果不及時加以處理,可能會導致程序崩潰。所以有的程序員對於程序的異常情況會選擇返回 0 或者 -1 等錯誤碼,以保持程序不崩潰。

別這樣做,正確的做法是將錯誤碼替換為拋出異常,只有這樣才能保證出現錯誤時立馬就可以發現,而不是讓程序在錯誤的狀態下繼續執行,將來造成更加迷惑的錯誤。

要討論錯誤處理,就一定要提及那些容易引發錯誤的做法。第一項就是返回 null 值。我不想去計算曾經見過多少幾乎每行代碼都在檢查 null 值的應用程序。返回 null 值基本上就是在給自己增加工作量,也是在給調用者添亂。只要有一處沒有檢查 null 值,應用程序就會失控。返回 null 不如拋出 NullPointerException ,或是替換為一個 空對象。讓調用者不再需要檢查 null,代碼也就更整潔了。


​總結

《代碼整潔之道》是軟件大師 Martin 的著作,英文名為《Clean Code》。本文作於筆者第二次閱讀《代碼整潔之道》後,本書講述的大多是一些編程細節和好習慣,並不是那種讓人醍醐灌頂的書,它更像是一個指引,一本避坑指南,Martin 將其從業多年遇到的一些好習慣和壞習慣列舉出來一一對比,告訴讀者哪些習慣應該堅持,哪些習慣應該摒棄。

比如筆者在閱讀之前,確實受到一些流言影響,認為註釋越多越好,越細越好。Martin 告訴我,註釋並不全然的好,程序員維護程序的同時往往不會花時間維護註釋,導致註釋說謊;以及一些喃喃自語或抖機靈的註釋實際上會影響代碼的閱讀。有的源代碼中在大括號 ‘}’ 旁添加註釋,表示它是哪一個條件的閉括號,在閱讀本書之前,我可能認為這是一種好習慣,甚至我可能會效仿,在閱讀本書後我知道這也是一種冗餘的代碼,並不值得堅持。

Martin 在本書中告訴我們正確的代碼、好的代碼應該怎樣怎樣,這樣即使我們在工作中,在公司凌亂不堪的源碼中深陷泥淖,心中仍然知道代碼的伊甸園長什麼樣。有了這樣一個清晰的目標,我們就能一步一步地向著它前進,而不至於走偏方向。或許這也是譯者將其譯作“道”的原因吧。

最重要的是,Martin 向我們傳遞出自己“不向骯髒代碼低頭”,用他近乎偏執的決心清理代碼、重構代碼,保證它的整潔。閱讀本書時,筆者心中充滿了感動與敬畏,真切地感受到一位程序大師的工匠之心,我們要像照顧孩子一樣照顧代碼,像熱愛生命一樣熱愛代碼。

就像王國的兩個工匠師一般,當我們做小項目時,我們可以化身風師,只用保證 邏輯通能運行,但想要走得更遠,我們就需要堅守土師的原則:保持好習慣不出錯。起先是我們養成習慣,後來是習慣造就我們。

以上,就是筆者對《代碼整潔之道》的閱讀分享,也非常推薦你花點時間閱讀這本書。在閱讀本文後你有什麼感想呢?歡迎在評論區留言分享~


本文作者:Alpinist Wang


分享到:


相關文章: