摘要:在敏捷中,讓設計簡單化,必須讓設計從簡單開始,然後變得成熟。要做到這一點,重構是唯一的出路。
本文分享自華為雲社群《敏捷技術實踐之重構》,作者:華為雲PaaS服務小智 。
前言
極限程式設計(XP)的創始人之一Ron Jeffries說道:“在敏捷中,讓設計簡單化,必須讓設計從簡單開始,然後變得成熟。要做到這一點,重構是唯一的出路。”
什麼是重構
重構是指改變程式碼的結構,而不是程式碼的行為。舉個例子:假設一個程式中有兩個方法,每個方法都包含幾行相同的程式碼,那麼這幾行相同的程式碼可以從原來的兩個方法中抽取出來,放到一個新的方法中,在原來放置這幾行程式碼的地方替換為呼叫這個新的方法。這個重構稍微改善了程式的可讀性和可維護性,因為現在一些程式碼明顯被複用,並且重複的程式碼被移到了一個地方。程式碼結構發生了改變,但是行為並沒有變。
重構不僅對TDD(測試驅動開發)的成功至關重要,同時也有助於防止程式碼腐爛。程式碼腐爛是由重複的程式碼、無數的補丁、糟糕的分類和其他程式設計差異造成的。團隊的開發人員以他們自己的風格編寫程式碼也會導致程式碼腐爛。產品釋出後,程式碼腐爛是典型的綜合症,如果允許程式碼腐爛,過不了幾年,系統就要全部重寫。透過不斷的重構,並且在一些小問題變成大問題之前不斷地修復它們,我們能夠保持我們的應用程式不會腐爛。Robert C.Martin稱之為童子軍規則。
童子軍裡有一個規定:“離開的時候,總要讓露營地比你來的時候更乾淨。”應用於軟體開發中就是:提交一個模組程式碼的時候,要讓它比下載的時候更簡潔一些,不論誰是這段程式碼的原作者。如果我們都遵循這個簡單的規定我們的系統最終會變得越來越好,是整個團隊照料整個系統,而非個人各自只關心他們那一小部分,這樣就符合程式碼集體所有制原則,同時還可以培養團隊成員的主人翁精神和責任感。
何時重構
Kent Beck提出了“程式碼壞味道”的說法,這種壞味道,就是發出了重構的強烈資訊,在《重構》一書中提到了24類,在這裡我們重點討論以下幾類:
1.神秘命名
猜謎這件事如果發生在閱讀程式碼的時候,就不是一個好的體驗。程式碼整潔最重要的一項就是好的命名,儘量做到見名知意。每個團隊都有自己統一的命名規範,常見命名方法有駝峰命名法如userName,蛇形命名法如user_name,脊柱命名法如user-name等,不論哪一種,就是不要出現theList,var1這種寫法。
在程式碼編寫過程中,我們應該深思熟慮如何給函式、模組、變數和類命名,使他們能夠清晰地表明自己的功能和用法。改名是最常用的重構手段,包括修改函式宣告、變數改名、欄位改名等,好的名字能夠節省未來在猜謎上的大把時間。如果想不出一個好的名字,說明背後很可能潛在更深的設計問題,為一個名字所付出的糾結,常常能推動我們對程式碼進行精簡。
2.重複程式碼
重複程式碼,顧名思義就是兩段程式碼看上去幾乎相同或者兩段程式碼都是實現相同的功能。當程式中存在大量重複的程式碼,會造成程式碼長度過長,不易閱讀,遇到相同或相似的程式碼段需要仔細分辨,如果需要修改某一處的時候,為了避免遺漏需要查詢出所有的相同程式碼段進行修改,不易維護。
如果是同一個類的兩個函式含有相同的程式碼,如文章開頭的例子,這時候可以用提煉函式來提煉出重複的程式碼,然後讓這兩個地點都呼叫被提煉出的那段程式碼。如果重複程式碼只是相似而不是相同,可以先嚐試用移動語句重組程式碼順序,把相似的部分放在一起以便於提煉。
3.過長引數列表
函式通常都需要接收引數,然後去完成某些處理,在《程式碼整潔之道》中提出,一個函式的引數應該儘可能短,最好只有一個,其次是兩個,如果出現三個以上的引數,就需要注意了。函式的引數過多不易於維護和修改,可讀性也差,容易出錯。
如果一些引數來自同一個物件,那麼就不要單獨取出這些數作為函式引數傳遞,而是直接將這個物件傳遞過去。如果某些引數值可以透過呼叫已知的一個方法得到,那麼就要把這個引數退換為在方法體內呼叫取值的那個已知方法。還有引數不來自同一個物件,如果有必要,我們也要毫不猶豫的為這些引數新建一個不可變的類,強制將它們放在一起
4.過長函式
在程式設計早期,由於呼叫子函式需要產生額外的開銷,所以大家都不喜歡小函式,一個函式動輒幾百多行,滑鼠的捲軸要滾好一會才能到方法末尾,更不用說一行一行的去閱讀,去理解,可以想象別人來修改這段程式碼的時候他有多麼痛苦。現代的開發環境已經完全免除了程式內的函式呼叫開銷。所以要保證自己的函式短小精悍。通常一個函式長度的底線是要在一個螢幕內完全顯示。
當然有時候一個函式開始的時候並不是那麼長,只是隨著每次需求的變更,每次一點點的加程式碼而沒有注意重構,僥倖的認為一點點根本不算什麼,積少成多最後終於不可收拾了。
過長函式應該積極的進行函式分解,提煉函式,找到函式中適合集中在一起的部分,把他們提煉出來形成一個新的函式。哪怕有時候是對一行程式碼進行這樣的提取也是值得的行動,因為關鍵不在於函式的長度,而是在於函式做了什麼。
5.全域性資料
在開始學習程式設計的時候,老師應該講過避免使用全域性變數,因為它太不可控了,在程式碼的任何一個角落都可以修改它,而且沒有任何機制能夠探測出來到底哪段程式碼做出了修改。全域性變數一次次造成的那種詭異bug,足夠讓你刻骨銘心。
對於全域性資料要避免使用,函式內的就放在函式體內,程式碼段內的就放在程式碼段中。無處可放,需要單獨存在的全域性資料就將其封裝在函式中,這樣就可以找到在哪個地方進行了修改,還要將這個函式放在某個類中,限定那些方法可以使用它,讓資料的修改過程可控可追溯。
6.註釋
程式碼中如果出現了大段的註釋用來解釋程式碼的含義,而原因是程式碼寫的太糟糕了,不得不依賴於註釋,這就是一種壞味道。
當你感覺需要寫註釋的時候,可以先試試重構,試著讓所有註釋都變得多餘。如果需要註釋來解釋一段程式碼做了什麼,可以試試提煉函式;如果函式已經提煉出來,但是還是需要註釋來解釋做了什麼,可以試試為函式宣告改名;如果註釋是說明某些系統的需求規格,試試引入斷言。
不是提倡不要寫註釋,而是要寫合適的註釋,對於註釋,我們應該遵循這樣一條原則:“註釋的使用不是為了說明這段程式碼能做什麼而是應該說明為什麼要這麼做”。
7.死程式碼
死程式碼泛指在程式執行過程中執行不到的程式碼,或者執行的到但沒有任何作用的程式碼,也就是無用程式碼。當程式中的變數變成不依賴於外部傳過來的資料,被定義成常量時,便無法適應外界其他的變化,就會產生死程式碼。這些程式碼通常是由於需求變更或者程式碼修改,變得用不到了,如果發現,就及時清除它們。
寫在最後
重構越晚,需要修改的程式碼越多,就會形成技術債務。除了上面提到的幾類重構的場景,還有很多其他的場景,重構雖然不是包治百病的靈丹妙藥,但是重構是非常有價值的,可以幫助你始終良好地控制自己的程式碼。尤其是作為敏捷開發團隊,重構是團隊成員應該熟練掌握的,任何團隊都應建立重構技能。同時,Scrum培訓師Stefan Wolpers表示,Scrum團隊應該考慮將15%~20%的資源分配給每個Sprint週期的重構程式碼和修復錯誤。由此可見,重構這件事情是循序漸進的作為一項改進活動持續進行。