代碼重構新手教程:如何將爛代碼變成好代碼?

作者 | 王莉敏

作爲有幾年工作經驗的程序員,都會對 bad code 不滿意。如何將爛代碼變成好代碼,本文將由淺入深、一步步帶你理解重構的奧祕,讓你對重構有個基本的瞭解。本文基於文章《The Simple Ways to Refactor Terrible Code》編譯整理而成。

說了這麼多,讀者朋友可能會有一個想法,是否有一些方法,能讓我享受重構的好處,又能避免上面提到的風險。

幸運的是還真有!

下面我將從最簡單、基本不會破壞已有代碼、花費很少時間的重構方法入手,逐步深入,讓大家對重構有一個基本瞭解,在對方法的介紹中,我將按照《InfoQ 編程語言 2 月排行榜結果出爐》中的調研情況,選取用戶掌握最多的編程語言 Java,以及該語言使用最多的 IDE 環境 Eclipse,進行舉例。

爲了消除恐懼,讓我們從最簡單的重構方法入手

當你發現代碼縮進層次不齊,代碼塊中缺少 {} 等問題時,就需要考慮代碼格式化了,現在的 IDE 工具已經對格式化提供了很好的支持,以 eclipse 爲例,選中要格式化的代碼,點擊以下菜單項就能完成代碼格式化。

此外很多源代碼管理網站,也提供了格式化工具,如圖所示:

在團隊開發中,爲了保證開發代碼樣式統一,需要建立編碼規範。我們並不需要重頭建立編碼規範,可以在大廠的編碼規範基礎上進行定製,比如在 Java 領域可採用阿里、華爲、Google Java Style Guide 等編碼規範。該編碼規範可以與 IDE 進行結合,如在 eclipse 中,打開 Window->Preferences->Java->Code Style 導入編碼規範:

重要的是,不管選擇何種規範,要堅持下去,並讓每個團隊成員都用起來。

在代碼開發中,好的註釋可以提高程序的可讀性,壞的註釋可能會畫蛇添足,甚至起反作用。作者提到好的註釋要做到和代碼相關、及時更新。很多時候,代碼剛開始編寫時,註釋和代碼是一致,後期因爲間隔時間過長或其他人接手修改代碼,沒有對註釋及時修改,就會造成註釋和代碼漸行漸遠。

儘量減少不必要的註釋。如很多函數或者類,如果設計架構清晰,通過命名就能知道他們做什麼,註釋不是必須的。還有一種情況是暫時不用的代碼,很多人會覺得以後會用到,會加個註釋,作者給出的建議是刪掉它,如果你將來真的用到了,可以到 git(一種代碼管理工具)的歷史記錄中查找。

對於邏輯混亂的代碼,如在循環中隨意使用 break,複雜的 if 語句嵌套等,你要做的是理清邏輯,重構代碼,而不是讓註釋替你補鍋。正如《重構》中提到的 “當你感覺需要撰寫註釋,請先嚐試重構,試着讓所有註釋都變得多餘。”

隨着系統版本的不斷迭代,有一些函數或類不再使用後,我們應該將它及時刪除,否則隨着時間流逝,會造成代碼庫臃腫,程序可讀性變差。而且如果還發生人員的變動,慢慢會成爲誰也不敢動的代碼,因爲都不知道有啥用和在哪用到。

多先進的 IDE 工具都對查找代碼的調用提供了支持,以 eclipse 爲例,查找函數是否被調用,可以使用調用層次圖功能,或者直接使用高級搜索功能,如圖所示:

在調用層次圖(Call Hierarchy)中可以看到 getInstance() 函數被什麼地方調用。

如果使用類似於 spring 的自動裝配功能,在 xml 中定義了調用關係,可以使用高級搜索功能查看 xml 或 properties 文件中定義的同名函數進行篩選。

  1. 變量命名

就像我們人一樣,一個好名字對變量、常量、函數和類都很重要,一個好的名字會讓其他開發人員很容易明白其功能是什麼。以下是命名的一些注意事項:

如果你要對已有代碼中錯誤的命名方式進行修改,eclipse 提供了很好地支持:選擇要修改的類、函數或變量,選擇 Refactor——》Rename 可以同時修改該變量在聲明和使用處的名稱,如下圖所示:

  1. 常量命名

常量的命名除了要遵守上一小節提到的通用方法外,還有一類魔法數字(magical numbers)的情況,如使用 0,1 來代表男女。更恰當的做法是定義常量名來代替魔法數字,如在 Java 中:

final int static FEMALE=0,MALE=1;
  1. 負值條件的重構

在條件或循環語句中,使用負值條件,會讓代碼難以理解、容易出錯,比如判斷是否爲男性,條件寫成了 "! isNotFemale(gender)"

重構方法是將條件改成正值,並調換 if/else 語句代碼塊的順序。

  1. {} 作爲單獨的一行

正確的 {} 格式已經在 1 部分中提到,這裏再強調下,如果你沒有將括號作爲單獨的一行,如下所示:

catch (IOException e) { e.printstackTrace(); }

你得到的好處只是減少了一行代碼,但是當你設置斷點調試時,斷點將不能精確定位到你想調試的部分。

  1. 變量定義和使用距離太遠

變量的定義和使用不要離得太遠,一般不要超過 20 行,函數也類似。如果你意識到這個問題,但並不能縮短定義和使用的距離,那代表這是個大函數(big function),你需要對函數做拆分。

以上是對入門級重構方法的介紹,在進行重構時,最重要的規則是:每次只做微小修改,並保證測試能正確運行(小步快跑)。

重構進階

現在我們對重構已經有了基本的瞭解,並建立了初步的信心。讓我們下面關注一些稍微複雜的重構內容。

  1. 重複代碼

當你發現相同的代碼塊在三個地方都出現時,你就需要考慮重構代碼了。對於同一個類中重複的代碼塊,可使用提取方法(extract method:將重複代碼提取出單獨的函數)來完成;對於一組相關類如父類、子類 A、子類 B 中的重複函數,通過上移方法(pull method:將子類中的方法移入父類中)和模板方法(template method:父類方法定義模板,子類編寫不同實現)來完成。

Eclipse 提供了相關功能,如圖所示:

  1. 函數參數

原始的:

int fun(int val) { val=32; }

修改後:

int fun(int val) { int temp=val; temp=32; }
  1. 變量多餘

當定義的變量沒太多含義,而且沒有賦值操作,如下代碼:

double basePrice = anOrder.basePrice(); return (basePrice > 1000);

其中的 basePrice 完全是多餘的變量,完全可以用函數本身來替代它,如下代碼:

return (anOrder.basePrice() > 1000);
  1. 缺少變量

某個臨時變量被賦值超過一次,它既不是循環變量,也不被用於收集計算結果。如果它們被賦值超過一次,就意味着它們在函數中承擔了一個以上的職責。如果臨時變量承擔多個責任,它就應該被替換爲多個臨時變量,每個變量只承擔一個責任。

重構方法:針對每次賦值,創造一個獨立、對應的臨時變量

  1. 複雜條件

我們都見過由 && || 構成的複雜的多行條件。複雜條件可讀性很差,調試和修改也很麻煩。

一個簡單的重構方式是:將這塊代碼抽取出來,變成一個單獨的判斷函數,如下代碼:

double fetchSalary(double money, int day) { if(money>10000 && day>30 ) { return money
day/365
0.2; }else { return money
30.0/365
0.1; } }

其中的條件 money>10000 && day>30 可重構爲:

boolean isHigherSalary(double money,int day) { return (money>10000 && day>30 ); }

老舊代碼的重構

在進行代碼重構時,需要考慮測試代碼是否能覆蓋重構的功能,如果沒有,需要增加測試用例覆蓋所做的修改,否則重構可能會破壞已有的功能。在前面的章節,作者假設已有足夠的測試用例,並且重構完成後測試可以正確運行。

但是如何重構測試用例沒有完全覆蓋的代碼呢,如老舊代碼?作者的建議是隻做必要的重構,如當需要修正 bug 或者增加新的功能,這種情況下,先爲遺留代碼編寫測試用例,在理解的基礎上重構代碼,爲代碼修改做好準備,然後進行代碼修改。

從這點上來說,你可以進行任何類型代碼的重構:一次只做一步重構,從小的容易的重構做起,並頻繁測試。

利用工具

重構代碼需要花費時間,當項目工期很緊時,很難下定決心去做重構。爲了讓重構變得更容易,市面上提供了大量相關工具,如 pylint( Python 代碼分析工具)、Checkstyle(代碼規範工具)、Sonarqube(代碼質量管理的開源工具)

此外,你要保證你的測試用例跑的足夠快,否則你會沒有耐心等待測試運行結果,或者直接就不運行了。

理想情況下,程序在構建後部署到測試環境前,可以藉助 CI/CD(持續集成 / 持續部署)工具實現代碼質量檢查、代碼樣式檢查、潛在 bug 監測等模塊的自動化運行。

總    結

這篇文章並沒有窮盡重構的所有內容,更多的重構清單和實例,請參考鮑勃大叔編寫的《代碼整潔之道》(Clean Code),尤其是第 17 章的味道與啓發(Smells and Heuristics)和馬丁 · 福勒(Martin Fowler)編寫的重構(Refactoring)一書 。不管你打算以哪本書爲主,在實踐過程中,都會殊途同歸——沉澱出幾條簡單的規則。

不要專門花費大量的時間去進行重構,利用小塊時間,每次只做一部分,只要保證代碼質量比之前有進步就可以了。不要想着以後再做,這個以後很可能是永遠不,最終你將面對一系列可怕的遺留代碼,然後你就深刻理解了 “出來混遲早是要還的” 這句話的涵義。

參考文章:

The Simple Ways to Refactor Terrible Code


本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/UHSqj2eguS_VkL0Ia6VXzA