本月初的時候朋友和我說《重構》出第 2 版了,我興沖沖地下單,花了一個禮拜時間一口氣把它讀完後,纔有了這篇書評。掩卷沉思,我無比贊同豆瓣網友「天心一」的評論:前端
這本書雖然很流行,可是應該看它而沒有看的人,仍是太多太多了。程序員
做爲一個開發者,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」的原則,供你們參考:
有些朋友對「重構」是不支持甚至是深惡痛絕的。
他們以爲重構是「給飛行中的飛機修引擎」,有可能出現不少問題卻帶不來多少拿得出手的成績;重構老是會在「不經意間」破壞原有功能,帶來的麻煩不少,投入與收益徹底不成比例,也不多會是面試的重點,花精力在這上面實在是費力不討好。
在創業公司裏基本不會有重構的呼聲,緣由無須贅言;而在一些大企業裏,leader們也不是都喜歡重構,由於花時間重構意味着佔用了開發新功能的時間,在代碼還能跑起來甚至看起來跑得還不錯的時候去重構無疑是多此一舉;與重構帶來的風險相比,重構帶來的好處就不是那麼有說服力了。
代碼的變更意味着須要進行迴歸測試,而敏捷當道的時代,每一個迭代中QA的關注重點都在新功能上,可以分配給迴歸測試的精力頗有限,而在測試經過後的重構極有可能致使這次變動對QA不透明,無形中增長了上線的風險。
我認爲以上幾種反對重構的場景都是不恰當的重構致使的。
你們只是愈來愈接納「重構」這個詞,由於這個詞聽起來很好,有一種積極應對變化的感受,但真正在作的仍是跟之前同樣,毫無規矩的修改。
在實踐中,重構的要求是很高的:它須要有足夠詳盡的單元測試,須要有持續集成的環境,須要隨時隨地在「小步伐地永遠讓代碼處於可工做狀態」下去進行改善。正是由於許多項目的「重構」是在並不知足以上條件也沒有通過成本估算、策略規劃的狀況下進行的,天然很容易致使失敗。
實際上,還有一部分開發者雖然認識到了重構是提高代碼質量的有效手段,是諸如「在當下努力工做,以避免往後有更多的活兒」此類觀念的具現。然而在某種程度上說,這在當前996.icu大環境下是不適用的。關於這一點就只能見仁見智、本身衡量了。
沒有銀彈
最後,我想說一句: 沒有銀彈。
重構和設計模式同樣,是對於最佳實踐的提煉,是一系列技巧的集合,它不是打通任督二脈的靈丹妙藥。若是你是一個有追求但卻歷來沒有系統地瞭解太重構的程序員(固然我不相信世界上會有這種程序員),那你會發現,你在平常工做中不經意間已經用過了這本書中提到的各類重構手法。
重構是注重實踐的技藝,僅僅瞭解其理念而忽視實踐則有如摶沙做飯,白費心思;而企圖把它當作「萬金油」來解決全部問題也只會陷入不恰當重構的陷阱,最終得不償失。只有在合適的場景下恰當的實踐,纔會實現其應有的價值。