1. 程式人生 > >程式碼之美——《重構》、《程式碼整潔之道》

程式碼之美——《重構》、《程式碼整潔之道》

什麼樣的程式碼才是美的程式碼?一千個coders可能會給出一千個答案。今天,讓我從一個簡單的角度來談談對於程式碼之美的理解。

 

可讀性高的程式碼才有可能是美的程式碼 

相信大家都有過這樣的經歷:接手一個專案要修復bug或者開發新功能的時候,發現程式碼可讀性非常差。哪怕是在有說明文件的情況下,都不太敢提交程式碼,唯恐引入新的bug或者直接導致系統崩潰。

 

軟體系統在程式碼方面的成本分為兩部分,第一部分是開發成本,第二部分是維護成本。

 

開發成本很好理解,就是把一個系統開發出來需要支付的各種成本,其中最大一塊很可能是人力成本。任何一個大型軟體系統都不可能沒有bug。系統開始運作之後,這些bug可能會導致業務出錯、執行緩慢,甚至是宕機,需要程式設計師找出漏洞並且修復上線。在這一階段的成本,我們稱之為維護成本。

 

運維成本往往會超過開發成本。我畢業後的第一份工作是在一家ERP公司做二次開發,最早的程式碼註釋時間有十幾年前的,常年從事維護開發工作的人員超過十個人。開發需要的時間遠比維護的時間要短得多,維護人員的數量並不會比開發人員的數量要少太多。

 

如果適當增加開發成本可以大幅減少維護成本,那麼對節省整體成本有著極大的幫助。

 

在進入到網際網路時代的今天,像ERP這樣開發完成之後變化不大的“重”系統已經不多了,模組化、服務化的“輕”系統已經佔到絕大多數。這些現代軟體系統強調的是迭代開發,以月或者周為單位更新版本,可以說幾乎永遠不會進入到所謂的“維護”階段。因此,如何平衡版本間的成本佔比,是程式設計師和管理者必須要關心的主題。

 

《重構》裡有這麼一段話:“任何一個傻瓜都能寫出計算機可以理解的程式碼。唯有寫出人類容易理解的程式碼,才是優秀的程式設計師。”如果只是寫出可以執行的程式碼,卻不具備可讀性或可讀性很差,負責接手專案的下一位程式設計師就很難在原有的基礎上再進行開發工作。這樣做固然初始的開發成本會比較低,但是後續維護和再開發的成本就很容易變得不可接受地高。

 

哪怕有人抱著“我走後,哪管它洪水滔天”的想法,卻仍要考慮更普遍的一種情況。在更多的時候,需要讀懂這些糟糕的程式碼並且付出代價的人,往往就是幾個月後已經忘記這段程式碼為什麼要這麼寫的你自己。

 

不要寫過長函式 

可讀性差的程式碼有很多特徵,其中最典型的就是存在大量過長的函式。

 

過長的函式之所以會導致難以理解,主要是因為裡面做了很多件事,而且容易導致抽象層次不一致。在完全陌生的情況下去讀這些程式碼,我們經常會讀著讀著就不知道某幾行程式碼是在做什麼、為什麼要這麼做,往往要花很大的功夫去蒙去猜。

 

剛入門的程式設計師很容易問出這樣的問題:“到底多少行程式碼的函式才算過長呢?”我們可以從Martin Fowler那裡得到答案,“關鍵不在於函式的長度,而在於函式‘做什麼’和‘如何做’之間的語義距離”。這句話換個角度理解,首先你要確保你的函式做完且只做一件事,然後這個函式裡的程式碼都只為這件事服務。

 

如果函式已經過長了應該怎麼辦?不要猶豫,立刻切分它。我們先要讀出面前這個過長函式到底做了多少件事,然後逐一地按照事務來提取程式碼,以子函式的形式對其進行抽象。

 

有人可能會擔心,生怕這樣會導致一個類裡面的函式過多,會導致其他不良後果。這種憂慮其實毫無必要,因為實際經驗告訴我們:只要切分是恰當的,不但不會有壞處,還會有除了提升程式碼可讀性的其他好處。

 

其中一個最明顯的好處就是可複用性會增強。一段程式碼如果沒有提取出來成為獨立的函式,其他地方如果要想用到就必須複製貼上,這不但體現不出程式碼可複用的好處,還會引發程式碼一個新的壞味道:重複程式碼。

 

註釋是一把雙刃劍

對於註釋,我們的第一印象往往是正面的。註釋並不是程式碼,對軟體系統本身並不會起任何作用。它是為了讓讀原始碼的人更好地理解原作者的意圖而存在的,因此我們在閱讀一段很難理解的程式碼的時候總是希望能有註釋。

 

正式參加工作之後,我們對註釋的看法可能會發生一些轉變。首先,有些人的註釋寫得很糟糕,表達出來的意思常常讓人云裡霧裡,甚至還不如不看這些註釋。然後,有些人的程式碼寫得很糟糕,註釋雖然有所幫助,但是這就成了程式設計師逃避被要求寫出整潔程式碼的法寶。更何況,還有一部分人的程式碼和註釋都寫得很糟糕。

 

另外,註釋也有維護成本。註釋依附於程式碼,所以如果程式碼有所改動,註釋也要相應有所更新,這裡就產生了成本。如果更新不及時或者沒有更新,註釋就會過時,產生誤導程式碼閱讀者的風險。

 

因此,除了涉及到比較複雜的演算法或者不得不特別加以說明的約定等情況,我並不建議優先使用註釋。命名良好的獨立函式應該成為優秀程式設計師的第一選擇。

 

如果你在閱讀自己寫的或者是需要重構的程式碼時,發現某一個地方需要用註釋來說明,這就表明這裡很有可能應該提取出成一個獨立函式。新的獨立函式需要有一個良好的命名,要做到讓人可以輕易地“望文生義”,這才是最好的“註釋”。

 

註釋不是程式碼,要想提升程式碼可讀性,最好的途徑還是應該通過程式碼本身去實現。

 

測試!測試!

無論我們怎麼努力,也很難一下子就寫出可讀性很強的程式碼。這就像寫文章一樣,我們的大部分精力都放在表達思想上面,文從字順有的時候就不太顧得上。寫程式碼,第一要務是能執行,能實現軟體系統的功能。

 

《程式碼整潔之道》的作者寫道:“我沒指望你能夠一次過寫出整潔、漂亮的程式。如果說我們從過去幾十年裡面學到什麼東西的話,那就是程式設計是一種技藝甚於科學的東西。要編寫整潔程式碼,必須先寫骯髒的程式碼,然後再清理它。”

 

很多人寫出了可以執行的、“骯髒”的程式碼,或者說接手了一個可讀性比較差的系統,往往不願意去重構它們。他們的理由看上去是十分充分的,那就是容易引入新bug。業界有名言可以作為依據:如果沒壞,就別去修它(If ain’t broke, don’t fix it)。

 

我最近才犯了一個錯誤:在沒有經過測試的情況下,把一段重構過的程式碼上線到生產環境。結果就是導致一個重要功能不可用,最終要通過緊急版本進行修復。

 

事後反省時,我並沒有賭咒發誓以後再也不重構程式碼,而是在重構之外加上一個必要的環節——測試。

 

《重構》一書開頭便寫道:“每當我要進行重構的時候,第一個步驟永遠相同:我得為即將修改的程式碼建立一組可靠的測試環境。這些測試是必要的,因為儘管遵循重構手法可以使我避免絕大多數引入bug的情形,但我畢竟是人,畢竟有可能犯錯。所以我需要可靠的測試。”

 

測試有很多種型別,其中單元測試和功能測試最為常用。對於Java程式設計師,我們可以使用JUnit等測試框架去寫單元測試,往往可以避免相當一部分引入bug的情形。然後,針對不同的業務場景,我們還需要做一些簡單的功能測試。

 

重構和測試當然都需要成本,還要承擔不可能完全避免的引入bug的風險。但是我相信,為了提升程式碼的質量,為了降低維護和後續開發的成本,這些都是值得付出的代價。

 

Coding進階之道 

對於程式碼的理解,不同人能到達不同的層次。剛開始學習,停留在行級。入門之後,進步到方法級。工作之後,可以到達類級和模組級。經驗積累足夠多了,會精進到架構級,甚至是更高的級別。

 

但是無論如何,任何進步都是從一點一滴的努力中得來的。如果你自我評價還沒有到達很高的級別,那就不妨一起來嘗試學習和實踐重構,讓經過自己手的程式碼變得更美。

 

《重構》裡有一段話非常有啟發性:“一開始我所做的重構都像這樣停留在細枝末節上。隨著程式碼漸趨簡潔,我發現自己可以看到一些以前看不到的設計層面的東西。如果不對程式碼做這些修改,也許我永遠看不見它們,因為我的聰明才智不足以在腦子裡把這一切都想象出來。Ralph Johnson把這種‘早期重構’描述成‘擦掉窗戶上的汙垢,使你看得更遠’。研究程式碼時我發現,重構把我帶到更高的理解層次上。如果沒有重構,我達不到這種層次。”

 

技術的精進要靠什麼?有的人說是天賦,這是老天爺賞飯吃,一般人都達不到。有的人說是經驗,一定要有多個大型專案的積累才有可能成為專家。有的人說是學習,必須把原理研究得很深很透才能把握技術的本質。然而,這些都不是一個腳踏實地的好答案。

 

技術的精進,靠的應該是一個又一個經過時間檢驗的、得到業界認可的工具。從業人員通過使用這些工具優化自己的工作輸出,日積月累,最終成為各自領域的佼佼者。重構,就是這麼一個經典的、優秀的、實用的工具。

 

學習重構、實踐重構、掌握重構,能夠幫助我們成為更好的coder。

 

 

 

我有一個公眾號,經常會發一些看書思考過後寫的文章,還有作為一個程式設計師的工作經驗分享。

如果你喜歡我的文字,可以用微信搜尋“李文業的思考筆記”或者掃描二維碼關注。

 

   

我有一個公眾號,經常會發一些看書思考過後寫的文章,還有作為一個程式設計師的工作經驗分享。

如果你喜歡我的文字,可以用微信搜尋“李文業的思考筆記”或者掃描二維碼關注。