當前位置:
首頁 > 科技 > 你為什麼不敢重構?

你為什麼不敢重構?

一說起重構,就總是有一種聲音:重構吃力不討好;模塊太多太複雜,重構風險太大;項目太著急,能上線就不錯了,重構這事兒就等等再說吧……甚至很多程序員將重構視為一場災難!重構真得這麼可怕嗎?不妨聽聽下面這位技術人士怎麼說。

1

重構:一項常常被忽略的基本功

一個老讀者的自白

作為一個開發者,2012 年初識本書的時候,我在寫 Java;2019 年本書再版,我在寫 JavaScript。真是應了那句老話兒:「凡是可以用 JavaScript 來寫的應用,最終都會用 JavaScript 來寫。」

JavaScript 特別適合重構,因為它很容易寫的無法維護。(手動滑稽)

當然這只是個玩笑,實際上作者也解釋過:重構背後的理念和架構適用於任何編程語言,選擇 JavaScript 只是因為它應用的比較廣泛。無論使用哪種編程語言都可以寫出優秀的或者糟糕的代碼,同樣也都可以以本書的思路和技巧進行重構。

使用 JavaScript 展示代碼範例,並不意味這本書中介紹的技巧只適用於 JavaScript。

對比新舊兩版,作者「重構」了這本書:前幾章有所擴展,後幾章結構調整較大,移除了原來的 12-14 章。總的來說,重構後的第 2 版更接地氣、更適應時代:不再有「大型重構」,更多地聚焦操作的細節。

「Fowler 先生不僅沒有拔高,反而把功夫做得更紮實了。」——摘自譯者序

雖然本書的副標題是「改善既有代碼的設計」,但通讀全書之後,我覺得這本書對於設計新系統時如何避免「壞味道」也是很有指導意義的。

重構和敏捷開發是一對親兄弟

提重構就不能不提敏捷開發,馬丁·福勒本身就是敏捷開發的發起者之一。敏捷作為「當紅炸子雞」,與重構有著很多相似的地方。

一是,這兩者都容易成為「掛羊頭,賣狗肉」中的「羊頭」,很多情況下,所謂的重構就是抽出時間來重寫現有的幾乎無法維護的代碼,就如同很多「敏捷」只做到了「不拒絕需求變更」而沒有真正做到響應變化;二是,它們實現起來都是一定難度且它們的實踐過程可以是交叉的——它們都著眼於具體細節而不是空架子,都歡迎變化,都強調小步快走、持續改進;三是,敏捷開發很重要的兩個環節就是設計與重構,兩者相輔相成,彼此互補,在實踐的過程中保持較強的適應力。

重構的技巧

可以說,我在重構過程中遇到的問題大多都能在本書中找到答案。

我們看看作者對重構的定義:

重構(名詞):對軟體內部結構的一種調整,目的是在不改變軟體可觀察行為的前提下,提高其可理解性,降低其修改成本。

重構(動詞):使用一系列重構手法,在不改變軟體可觀察行為的前提下,調整其結構。

為何重構、如何重構、重構的原則與手法,都可以在這本書中找到。從第 5 章起作者提供了多達 300 頁的重構名錄、60 余項重構的具體技巧(老版本是 70 多項,新版本移除了大規模項目的重構)。我覺得這一份非常詳盡的重構手法清單更接近於字典,適合粗讀之後在用到的時候再具體查閱。

至於什麼時候能夠用到這份名錄,作者在第 3 章也有介紹:當代碼有了「壞味道」就可以著手進行重構了。所謂「壞味道」,我認為並非是一程不變的準則,而是需要根據團隊、項目、採用的技術棧等各方面綜合得出的一種無法定量描述的經驗。所以,作者用了「味道」這樣一種體驗來代指需要重構的地方。在作者列出的每種「壞味道」中,都給出了對應的重構手法。雖然作者羅列的 20 多種「壞味道」覆蓋面很廣,但是你和你的團隊仍然可以總結出自己的經驗來指導重構。實際上,與第 1 版相比,第 2 版中的「壞味道」增加了「神秘命名」「全局數據」「循環語句」,刪除了「不完美的庫類」。

我認為本書最重要也最容易被忽略的章節就是第 4 章——構築測試體系。在第 4 章中,作者通過一個生產計劃的示例一步一步的構建了一個完整的單元測試體系。顯然,掌握單元測試是有一定成本的,這就導致有些開發者(尤其是前端領域)完全不注重單元測試。他們認為測試是 QA 的職責,自己只需要保證冒煙測試通過即可。然而反直覺的是,良好的單元測試不但是重構的先決條件和好幫手,而且能幫我們整理設計的思路,從而更好的寫出優秀的代碼。因為在寫單元測試的時候,我們會假設自己是一個「代碼破壞者」,思考如何破壞代碼的運行、尋找那些可能出錯的邊界條件。單元測試的編寫和運行可以在寫完代碼後進行,也可以在寫代碼之前動手。先寫單元測試再寫代碼的技巧叫作測試驅動開發(TDD),也是敏捷開發的基石之一。關於 TDD 的技藝,作者的好友 Kent Beck 專門寫了一本書,即《測試驅動開發》。

作者在第 1 章的示例中提到:「小步快走,代碼永遠處於可工作狀態。」而且作者特意強調:「每當我要進行重構的時候,第一個步驟永遠相同:我得確保即將修改的代碼擁有一組可靠的測試。」

對於單元測試,我有一點小小的心得可以與大家分享:盡量編寫純函數。純函數是沒有副作用的函數,給出同樣的參數值,純函數總是返回同樣的結果,它不依賴於參數以外的值。顯然,純函數更便於單元測試。

當然單元測試也不是萬能的,它不可能檢出所有的 bug,而且單元測試集的覆蓋率也是一個見仁見智的指標,具體需要寫多少單元測試,覆蓋多少代碼,都是需要我們在開發中結合實際情況自己權衡的。無論如何,單元測試一直是一中非常重要卻常常被忽視的技能。

另外,我在開發實踐中堅持一個「432」的原則,供大家參考:

一個類包括注釋代碼不要超過 400 行;

一個純函數最好不要超過 30 行;

函數內循環嵌套最多 2 層。

重構的現狀

有些朋友對「重構」是不支持甚至是深惡痛絕的。

一部分開發者不願意把精力「浪費」在重構上

他們覺得重構是「給飛行中的飛機修引擎」,有可能出現很多問題卻帶不來多少拿得出手的成績;重構總是會在「不經意間」破壞原有功能,帶來的麻煩很多,投入與收益完全不成比例,也很少會是面試的重點,花精力在這上面實在是費力不討好。

許多 leader 反對盲目重構

在創業公司里基本不會有重構的呼聲,原因無須贅言;而在一些大企業里,leader 們也不是都喜歡重構,因為花時間重構意味著佔用了開發新功能的時間,在代碼還能跑起來甚至看起來跑得還不錯的時候去重構無疑是畫蛇添足;與重構帶來的風險相比,重構帶來的好處就不是那麼有說服力了。

大部分 QA 對重構持謹慎的質疑態度

代碼的變動意味著需要進行回歸測試,而敏捷當道的時代,每個迭代中 QA 的關注重點都在新功能上,能夠分配給回歸測試的精力很有限,而在測試通過後的重構極有可能導致此次變更對 QA 不透明,無形中增加了上線的風險。

我認為以上幾種反對重構的場景都是不恰當的重構導致的。

大家只是越來越接納「重構」這個詞,因為這個詞聽起來很好,有一種積極應對變化的感覺,但真正在做的還是跟以前一樣,毫無規矩的修改。

在實踐中,重構的要求是很高的:它需要有足夠詳盡的單元測試,需要有持續集成的環境,需要隨時隨地在「小步伐地永遠讓代碼處於可工作狀態」下去進行改善。正是因為許多項目的「重構」是在並不滿足以上條件也沒有經過成本估算、策略規劃的情況下進行的,自然很容易導致失敗。

水土不服

實際上,還有一部分開發者雖然認識到了重構是提升代碼質量的有效手段,是諸如「在當下努力工作,以免日後有更多的活兒」此類觀念的具現。然而在某種程度上說,這在當前 996.icu 大環境下是不適用的。關於這一點就只能見仁見智、自己衡量了。

沒有銀彈

最後,我想說一句: 沒有銀彈。

重構和設計模式一樣,是對於最佳實踐的提煉,是一系列技巧的集合,它不是打通任督二脈的靈丹妙藥。如果你是一個有追求但卻從來沒有系統地了解過重構的程序員(當然我不相信世界上會有這種程序員),那你會發現,你在日常工作中不經意間已經用過了這本書中提到的各種重構手法。

重構是注重實踐的技藝,僅僅了解其理念而忽視實踐則有如摶沙作飯,白費心思;而企圖把它當做「萬金油」來解決所有問題也只會陷入不恰當重構的陷阱,最終得不償失。只有在合適的場景下恰當的實踐,才會實現其應有的價值。

《重構:改善既有代碼的設計(第2版)》

作者:[美]馬丁·福勒(Martin Fowler)

譯者:熊節 林從羽

這本備受關注的第 2 版有何特點?

第 2 版在第 1 版的基礎上做了全面修訂,反映了編程領域業已發生的許多變化。書中給出了 60 多個可行的重構,每個重構都介紹了經過驗證的代碼變換手法的動機和技術。第 2 版中介紹的重構列表更加內聚,並用 JavaScript 語言重寫了代碼範例。此外,第 2 版中還新增了與函數式編程相關的重構範例,旨在教會讀者如何在沒有類的環境下開展重構。本書提出的重構準則將幫助開發人員小步地修改代碼,從而減少了開發過程中的風險。

重構,一言以蔽之,就是在不改變外部行為的前提下,有條不紊地改善代碼。20 多年前,正是《重構:改善既有代碼的設計》第 1 版的出版,使重構終於從編程高手們的小圈子走出,成為眾多普通程序員日常開發工作中不可或缺的一部分。如今,Martin Fowler 的《重構:改善既有代碼的設計》一書已經成為全球有經驗的程序員手中的利器,既可用來改善既有代碼的設計、提升軟體的可維護性,又可用於使既有代碼更易理解、煥發出新的活力。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 InfoQ 的精彩文章:

2019年了,PHP已不再是當年那個「設計糟糕」的語言
解密大數據發展「七年之癢」,中國信通院將發布6大研究成果

TAG:InfoQ |