《重構:改善既有代碼的設計》讀書筆記

前言: 捧讀像這一類的書對於本身來講總帶着一些神聖感,感謝本身並無被這麼宏大的主題嚇退,看完了這裏分享輸出一下本身的筆記。javascript

1、理解重構


什麼是重構?

按書中 P45 中的說法,重構這個概念被分紅了動詞和名詞的方面被分別闡述:java

  • 重構(名詞): 對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本。
  • 重構(動詞): 使用一系列重構手法,在不改變軟件可觀察行爲的前提下,調整其結構。

在過去的幾十年時間裏,重構這個詞彷佛被用來代指任何形式的代碼清理,但上面的定義所指的是一種特定的清理代碼的方式。重構的關鍵在於運用大量微小且保持軟件行爲的步驟,一步一步達成大規模的修改。git

每一次的重構要麼很小,要麼包含了若干個小步驟,即便重構沒有完成,也應當能夠在任什麼時候刻停下來,因此若是有人說它們的代碼在重構過程當中有一兩天時間不可用,基本上能夠肯定,他們作的事不是重構。程序員

與性能優化的區別

重構與性能優化有不少類似的地方:二者都須要修改代碼,而且二者都不會改變程序的總體功能。github

二者的差異在於起目的:編程

  • 重構是爲了讓代碼 「更容易理解,更容易修改」。這可能使程序運行得更快,也可能使程序運行的更慢。
  • 性能優化則只關心程序是否運行的更快。對於最終獲得的代碼是否容易理解和維護就不知道了。

爲何重構?

重構不是包治百病的靈丹妙藥,也絕對不是所謂的「銀彈」。重構只是一種工具,可以幫助你始終良好的控制代碼而已。使用它,可能基於下面的幾個目的。性能優化

這裏有一個有意思的科普(引用自百度百科:沒有銀彈
):在民俗傳說裏,全部能讓咱們充滿夢靨的怪物之中,沒有比狼人更可怕的了,由於它們會忽然地從通常人變身爲恐怖的怪獸,所以人們嘗試着查找可以奇蹟似地將狼人一槍斃命的銀彈。咱們熟悉的軟件項目也有相似的特質(以一個不懂技術的管理者角度來看),日常看似單純而率直,但極可能一轉眼就變成一隻時程延誤、預算超支、產品充滿瑕疵的怪獸,因此,咱們聽到了絕望的呼喚,渴望有一種銀彈,可以有效下降軟件開發的成本,就跟電腦硬件成本能快速降低同樣。微信

1. 改進軟件的設計

當人們只爲短時間目的而修改代碼時,他們常常沒有徹底理解架構的總體設計。因而代碼逐漸失去了本身的結構。程序員愈來愈難以經過閱讀代碼來理解原來的設計。代碼結構的流失有累積效應。越難看出代碼所表明的設計企圖,就越難以保護其設計,因而設計就腐敗得越快。架構

完成一樣一件事,設計欠佳的程序每每須要更多代碼,這經常是由於代碼在不一樣的地方使用徹底相同的語句作一樣的事情,所以改進設計的一個重要方向就是消除重複代碼。消除重複代碼,我就能夠肯定全部事物和行爲在代碼中只表述一次,這正是優秀設計的根本。編程語言

2. 使軟件更容易理解

所謂程序設計,很大程度上就是與計算機對話:我編寫代碼告訴計算機作什麼,而它的響應是按照個人指示精確行動。一言以蔽之,我所作的就是填補「我想要它作什麼」和「我告訴它作什麼」之間的縫隙。編程的核心就在於「準確說出我想要的」。

然而別忘了,除計算機以外,源碼還有其餘讀者,而且很大機率仍是幾個月後的本身,如何更清晰地表達我想要作的,這可能就須要一些重構的手法。

這裏我聯想到了軟件設計的 KISS 原則:KISS 原則,Keep It Simple and Stupid ,簡單的理解這句話就是,要把一個系統作的連白癡都會用。

3. 幫助找到 BUG

對代碼的理解,能夠幫助找到系統中存在的一些 BUG。搞清楚程序結構的同時,也能夠對本身的假設作一些驗證,這樣一來 BUG 想不發現都難。

Kent Beck 常常形容本身的一句話是:「我不是一個特別好的程序員,我只是一個有着一些特別好的習慣的還不錯的的程序員。」重構可以幫助咱們更有效地寫出健壯的代碼。

4. 提升編程速度

聽起來可能有些反直覺,由於重構可能會花大量的時間改善設計、提升閱讀性、修改 BUG,難道不是在下降開發速度嘛?

軟件開發者交談時的故事:一開始他們進展很快,但現在想要添加一個新功能須要的時間就要長得多。他們須要花愈來愈多的時間去考慮如何把新功能塞進現有的代碼庫,不斷蹦出來的bug修復起來也愈來愈慢。代碼庫看起來就像補丁摞補丁,須要細緻的考古工做才能弄明白整個系統是如何工做的。這份負擔不斷拖慢新增功能的速度,到最後程序員巴不得從頭開始重寫整個系統。

下面這幅圖能夠描繪他們經歷的困境。

但有些團隊的境遇則大相徑庭。他們添加新功能的速度愈來愈快,由於他們能利用已有的功能,基於已有的功能快速構建新功能。

兩種團隊的區別就在於軟件的內部質量。須要添加新功能時,內部質量良好的軟件讓我能夠很容易找到在哪裏修改、如何修改。良好的模塊劃分使我只須要理解代碼庫的一小部分,就能夠作出修改。若是代碼很清晰,我引入 BUG 的可能性就會變小,即便引入了 BUG,調試也會容易得多。理想狀況下,代碼庫會逐步演化成一個平臺,在其上能夠很容易地構造與其領域相關的新功能。

這種現象被做者稱爲「設計耐久性假說」:經過投入精力改善內部設計,咱們增長了軟件的耐久性,從而能夠更長時間地保持開發的快速。目前還沒法科學地證實這個理論,因此說它是一個「假說」。

20年前,行業的陳規認爲:良好的設計必須在開始編程以前完成,由於一旦開始編寫代碼,設計就只會逐漸腐敗。重構改變了這個圖景。如今咱們能夠改善已有代碼的設計,所以咱們能夠先作一個設計,而後不斷改善它,哪怕程序自己的功能也在不斷髮生着變化。因爲預先作出良好的設計很是困難,想要既體面又快速地開發功能,重構必不可少。

何時重構?

  • 三次法則:
    第一次作某件事時只管去作;第二次作相似的事會產生反感,但不管如何仍是能夠去作;第三次再作相似的事,你就應該重構。

何時不該該重構?

重構並非必要,固然也有一些不那麼須要重構的狀況:

  • 不須要修改,那些醜陋的代碼能隱藏在一個 API 之下。 只有當我須要理解其工做原理時,對其進行重構纔會有價值;
  • 重寫比重構容易。 這可能就須要良好的判斷力和豐富的經驗纔可以進行抉擇了。

2、重構的幾種姿式


預備性重構:讓添加新功能更容易

重構的最佳時機就在添加新功能以前。在動手添加新功能以前,我會看看現有的代碼庫,此時常常會發現:若是對代碼結構作一點微調,個人工做會容易得多。也許已經有個函數提供了我須要的大部分功能,但有幾個字面量的值與個人須要略有衝突。若是不作重構,我可能會把整個函數複製過來,修改這幾個值,但這就會致使重複代碼—若是未來我須要作修改,就必須同時修改兩處(更麻煩的是,我得先找到這兩處)。並且,若是未來我還須要一個相似又略有不一樣的功能,就只能再複製粘貼一次,這可不是個好主意。

這就好像我要往東去100千米。我不會往東一頭把車開進樹林,而是先往北開20千米上高速,而後再向東開100千米。後者的速度比前者要快上3倍。若是有人催着你「趕快直接去那兒」,有時你須要說:「等等,我要先看看地圖,找出最快的路徑。」這就是預備性重構於個人意義。

——Jessica Kerr

修復bug時的狀況也是同樣。在尋找問題根因時,我可能會發現:若是把3段如出一轍且都會致使錯誤的代碼合併到一處,問題修復起來會容易得多。或者,若是把某些更新數據的邏輯與查詢邏輯分開,會更容易避免形成錯誤的邏輯糾纏。用重構改善這些狀況,在一樣場合再次出現一樣bug的機率也會下降。

幫助理解的重構:使代碼更易懂

我須要先理解代碼在作什麼,而後才能着手修改。這段代碼多是我寫的,也多是別人寫的。一旦我須要思考「這段代碼到底在作什麼」,我就會自問:能不能重構這段代碼,令其一目瞭然?我可能看見了一段結構糟糕的條件邏輯,也可能但願複用一個函數,但花費了幾分鐘才弄懂它到底在作什麼,由於它的函數命名實在是太糟糕了。這些都是重構的機會。

看代碼時,我會在腦海裏造成一些理解,但個人記性很差,記不住那麼多細節。正如 Ward Cunningham 所說,經過重構,我就把腦子裏的理解轉移到了代碼自己。隨後我運行這個軟件,看它是否正常工做,來檢查這些理解是否正確。若是把對代碼的理解植入代碼中,這份知識會保存得更久,而且個人同事也能看到。

重構帶來的幫助不只發生在未來——經常是立竿見影。是我會先在一些小細節上使用重構來幫助理解,給一兩個變量更名,讓它們更清楚地表達意圖,以方便理解,或是將一個長函數拆成幾個小函數。當代碼變得更清晰一些時,我就會看見以前看不見的設計問題。若是不作前面的重構,我可能永遠都看不見這些設計問題,由於我不夠聰明,沒法在腦海中推演全部這些變化。Ralph Johnson說,這些初步的重構就像掃去窗上的塵埃,使咱們得以看到窗外的風景。在研讀代碼時,重構會引領我得到更高層面的理解,若是隻是閱讀代碼很難有此領悟。有些人覺得這些重構只是毫無心義地把玩代碼,他們沒有意識到,缺乏了這些細微的整理,他們就沒法看到隱藏在一片混亂背後的機遇。

撿垃圾式重構

幫助理解的重構還有一個變體:我已經理解代碼在作什麼,但發現它作得很差,例如邏輯沒必要要地迂迴複雜,或者兩個函數幾乎徹底相同,能夠用一個參數化的函數取而代之。這裏有一個取捨:我不想從眼下正要完成的任務上跑題太多,但我也不想把垃圾留在原地,給未來的修改增長麻煩。若是我發現的垃圾很容易重構,我會立刻重構它;若是重構須要花一些精力,我可能會拿一張便箋紙把它記下來,完成當下的任務再回來重構它。

固然,有時這樣的垃圾須要好幾個小時才能解決,而我又有更緊急的事要完成。不過即使如此,稍微花一點工夫作一點兒清理,一般都是值得的。正如野營者的老話所說:至少要讓營地比你到達時更乾淨。若是每次通過這段代碼時都把它變好一點點,聚沙成塔,垃圾總會被處理乾淨。重構的妙處就在於,每一個小步驟都不會破壞代碼——因此,有時一塊垃圾在好幾個月以後才終於清理乾淨,但即使每次清理並不完整,代碼也不會被破壞。

有計劃的重構和見機行事的重構

上面的例子——預備性重構、幫助理解的重構、撿垃圾式重構——都是見機行事的:我並不專門安排一段時間來重構,而是在添加功能或修復 BUG 的同時順便重構。這是我天然的編程流的一部分。無論是要添加功能仍是修復 BUG,重構對我當下的任務有幫助,並且讓我將來的工做更輕鬆。這是一件很重要而又常被誤解的事:重構不是與編程割裂的行爲。你不會專門安排時間重構,正如你不會專門安排時間寫 if 語句。個人項目計劃上沒有專門留給重構的時間,絕大多數重構都在我作其餘事的過程當中天然發生。

還有一種常見的誤解認爲,重構就是人們彌補過去的錯誤或者清理骯髒的代碼。固然,若是趕上了骯髒的代碼,你必須重構,但漂亮的代碼也須要不少重構。在寫代碼時,我會作出不少權衡取捨:參數化須要作到什麼程度?函數之間的邊界應該劃在哪裏?對於昨天的功能徹底合理的權衡,在今天要添加新功能時可能就再也不合理。好在,當我須要改變這些權衡以反映現實狀況的變化時,整潔的代碼重構起來會更容易。

長久以來,人們認爲編寫軟件是一個累加的過程:要添加新功能,咱們就應該增長新代碼。但優秀的程序員知道,添加新功能最快的方法每每是先修改現有的代碼,使新功能容易被加入。因此,軟件永遠不該該被視爲「完成」。每當須要新能力時,軟件就應該作出相應的改變。越是在已有代碼中,這樣的改變就越顯重要。

不過,說了這麼多,並不表示有計劃的重構老是錯的。若是團隊過去忽視了重構,那麼經常會須要專門花一些時間來優化代碼庫,以便更容易添加新功能。在重構上花一個星期的時間,會在將來幾個月裏發揮價值。有時,即使團隊作了平常的重構,仍是會有問題在某個區域逐漸累積長大,最終須要專門花些時間來解決。但這種有計劃的重構應該不多,大部分重構應該是不起眼的、見機行事的。

長期重構

大多數重構能夠在幾分鐘—最多幾小時—內完成。但有一些大型的重構可能要花上幾個星期,例如要替換一個正在使用的庫,或者將整塊代碼抽取到一個組件中並共享給另外一支團隊使用,再或者要處理一大堆混亂的依賴關係,等等。

即使在這樣的狀況下,我仍然不肯讓一支團隊專門作重構。可讓整個團隊達成共識,在將來幾周時間裏逐步解決這個問題,這常常是一個有效的策略。每當有人靠近「重構區」的代碼,就把它朝想要改進的方向推進一點。這個策略的好處在於,重構不會破壞代碼—每次小改動以後,整個系統仍然照常工做。例如,若是想替換掉一個正在使用的庫,能夠先引入一層新的抽象,使其兼容新舊兩個庫的接口。一旦調用方已經徹底改成使用這層抽象,替換下面的庫就會容易得多。(這個策略叫做Branch By Abstraction[mf-bba]。)

複審代碼時重構

至於如何在代碼複審的過程當中加入重構,這要取決於複審的形式。在常見的pull request模式下,複審者獨自瀏覽代碼,代碼的做者不在旁邊,此時進行重構效果並很差。若是代碼的原做者在旁邊會好不少,由於做者能提供關於代碼的上下文信息,而且充分認同複審者進行修改的意圖。對我我的而言,與原做者肩並肩坐在一塊兒,一邊瀏覽代碼一邊重構,體驗是最佳的。這種工做方式很天然地導向結對編程:在編程的過程當中持續不斷地進行代碼複審。

3、壞代碼長什麼樣?


這讓我想起以前在捧讀《阿里巴巴 Java 開發手冊》時學習的代碼規範的問題(傳送門
,只不過當時學習的是好的代碼應該長什麼樣,而如今討論的事情是:壞的代碼長什麼樣?

其實大部分的狀況應該做爲程序員的咱們都有必定的共識,因此我以爲簡單列一下書中提到的狀況就足以說明:

  • 神祕命名

  • 重複代碼

  • 過長函數

  • 過長參數列表

  • 全局數據: 全局數據的問題在於,從代碼庫的任何一個角落均可以修改它,並且沒有任何機制能夠探測出到底哪段代碼作出了修改。一次又一次,全局數據形成了一些詭異的 BUG,而問題的根源卻在遙遠的別處。

  • 可變數據: 對數據的修改常常致使出乎意料的結果和難以發現的 BUG。我在一處更新數據,卻沒有意識到軟件中的另外一處指望着徹底不一樣的數據。

  • 發散式變化: 模塊常常由於不一樣的緣由在不一樣的方向上發生變化。

  • 散彈式修改: 每遇到某種變化,你都必須在許多不一樣的類內作出許多小修改。

  • 依戀情結: 所謂模塊化,就是力求將代碼分出區域,最大化區域內部的交互、最小化跨區域的交互。但有時你會發現,一個函數跟另外一個模塊中的函數或者數據交流格外頻繁,遠勝於在本身所處模塊內部的交流。

  • 數據泥團: 你常常在不少地方看到相同的三四項數據:兩個類中相同的字段、許多函數簽名中相同的參數。

  • 基本類型偏執: 不少程序員不肯意建立對本身的問題域有用的基本類型,如錢、座標、範圍等。

  • 重複的 switch: 在不一樣的地方反覆使用相同的 switch 邏輯。問題在於:每當你想增長一個選擇分支時,必須找到全部的 switch,並逐一更新。

  • 循環語句: 咱們發現,管道操做(如 filter 和 map)能夠幫助咱們更快地看清被處理的元素一級處理它們的動做。

  • 冗餘的元素

  • 誇誇其談通用性: 函數或類的惟一用戶是測試用例。

  • 臨時字段: 有時你會看到這樣的類:其內部某個字段僅爲某種特定狀況而定。這樣的代碼讓人不理解,由於你一般認爲對象在全部時候都須要它的全部字段。在字段未被使用的狀況下猜想當初設置它的目的,會讓你發瘋。

  • 過長的消息鏈

  • 中間人: 過分運用委託。

  • 內幕交易: 軟件開發者喜歡在模塊之間築起高牆,極其反感在模塊之間大量交換數據,由於這會增長模塊間的耦合。在實際狀況裏,必定的數據交換不可避免,但咱們必須儘可能減小這種狀況,並把這種交換都放到明面上來。

  • 過大的類

  • 殊途同歸的類

  • 純數據類: 所謂純數據類是指:他們擁有一些字段,以及用於訪問(讀寫)這些字段的函數,除此以外一無長物。純數據類經常意味着行爲被放在了錯誤的地方。也就是說,只要把處理數據的行爲從客戶端搬移到純數據類裏來,就能使狀況大爲改觀。

  • 被拒絕的遺贈: 拒絕繼承超類的實現,咱們不介意:但若是拒絕支持超類的接口,這就難以接受了。

  • 註釋: 當你感受須要纂寫註釋時,請先嚐試重構,試着讓全部註釋都變得多餘。

4、重構的一些方法


書中花了大量的章節介紹咱們應該如何重構咱們的程序,有幾個關鍵的點是我本身可以提煉出來的:找出代碼中不合理的地方、結構化、容易理解、測試確保正確。總之圍繞這幾個點,書中介紹了大量的方法,下面結合本身的一些理解來簡單概述一下吧。

結構化代碼

結構化的代碼更加便於咱們閱讀和理解,例如最常使用的重構方法:提煉函數

  • 動機:把意圖和實現分開
void printOwing(double amount) {
     printBanner();
     //print details
     System.out.println ("name:" + _name);
     System.out.println ("amount" + amount);
 }

=>

void printOwing(double amount) {
     printBanner();
     printDetails(amount);
 }
 void printDetails (double amount) {
     System.out.println ("name:" + _name);
     System.out.println ("amount" + amount);
 }

更清楚的表達用意

要保持軟件的 KISS 原則是不容易的,可是也有一些方法能夠借鑑,例如:引入解釋性變量

動機:用一個良好命名的臨時變量來解釋對應條件子句的意義,使語義更加清晰。

if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
     (browser.toUpperCase().indexOf("IE") > -1) &&
      wasInitialized() && resize > 0 )
{
     // do something
}

=>

final boolean isMacOs     = platform.toUpperCase().indexOf("MAC") > -1;
   final boolean isIEBrowser = browser.toUpperCase().indexOf("IE")  > -1;
   final boolean wasResized  = resize > 0;
   if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
       // do something
   }

另外因爲 lambda 表達式的盛行,咱們如今有一些更加優雅易讀的方法使咱們的代碼保持可讀:以管道取代循環就是這樣一種方法。

const names = [];
   for (const i of input) {
      if (i.job === "programer")
         names.push(i.name);
   }

=>

const names = input
      .filter(i => i.job === "programer")
      .map(i => i.name)
   ;

合理的組織結構

例如上面介紹的提煉函數的方法,當然是一種很好的方式,但也應該避免過分的封裝,若是別人使用了太多間接層,使得系統中的全部函數都彷佛只是對另外一個函數的簡單委託(delegation),形成我在這些委託動做之間暈頭轉向,而且內部代碼和函數名稱一樣清晰易讀,那麼就應該考慮內聯函數。

動機:①去處沒必要要的間接性;②能夠找出有用的間接層。

int getRating() {
     return (moreThanFiveLateDeliveries()) ? 2 : 1;
 }
 boolean moreThanFiveLateDeliveries() {
     return _numberOfLateDeliveries > 5;
 }

=>

int getRating() {
     return (_numberOfLateDeliveries > 5) ? 2 : 1;
 }

合理的封裝

封裝可以幫助咱們隱藏細節而且,可以更好的應對變化,當咱們發現咱們的類太大而不容易理解的時候,能夠考慮使用提煉類的方法。

動機:類太大而不容易理解。

class Person {
     get officeAreaCode() { return this._officeAreaCode; }
     get officeNumber() { return this._officeNumber; }
 }

=>

class Person {
     get officeAreaCode() { return this._telephoneNumber.areaCode; }
     get officeNumber() { return this._telephoneNumber.number; }
 }
 class TelephoneNumber {
     get areaCode() { return this._areaCode; }
     get number() { return this._number; }
 }

反過來,若是咱們發現一個類再也不承擔足夠責任,再也不有單獨存在的理由的時候,咱們會進行反向重構:內斂類

class Person {
     get officeAreaCode() { return this._telephoneNumber.areaCode; }
     get officeNumber() { return this._telephoneNumber.number; }
 }
 class TelephoneNumber {
     get areaCode() { return this._areaCode; }
     get number() { return this._number; }
 }

=>

class Person {
     get officeAreaCode() { return this._officeAreaCode; }
     get officeNumber() { return this._officeNumber; }
 }

簡化條件表達式

分解條件式: 咱們能經過提煉代碼,把一段 「複雜的條件邏輯」 分解成多個獨立的函數,這樣就能更加清楚地表達本身的意圖。

if (date.before (SUMMER_START) || date.after(SUMMER_END))
         charge = quantity * _winterRate + _winterServiceCharge;
     else charge = quantity * _summerRate;

=>

if (notSummer(date))
         charge = winterCharge(quantity);
     else charge = summerCharge (quantity);

另一個比較受用的一條建議就是:以衛語句取代嵌套條件式。根據經驗,條件式一般有兩種呈現形式。第一種形式是:全部分支都屬於正常行爲。第二種形式則是:條件式提供的答案中只有一種是正常行爲,其餘都是不常見的狀況。

精髓是:給某一條分支以特別的重視。若是使用 if-then-else 結構,你對 if 分支和 else 分支的重視是同等的。 這樣的代碼結構傳遞給閱讀者的消息就是:各個分支有一樣的重要性。衛語句(guard clauses)就不一樣了,它告訴閱讀者:「這種狀況很罕見,若是它真的發生了,請作 一些必要的整理工做,而後退出。」

「每一個函數只能有一個入口和一個出口」的觀念,根深蒂固於某些程序員的腦海裏。 我發現,當我處理他們編寫的代碼時,我常常須要使用 Replace Nested Conditional with Guard Clauses。現今的編程語言都會強制保證每一個函數只有一個入口, 至於「單一出口」規則,其實不是那麼有用。在我看來,保持代碼清晰纔是最關鍵的:若是「單一出口」能使這個函數更清楚易讀,那麼就使用單一出口;不然就沒必要這麼作。

double getPayAmount() {
   double result;
   if (_isDead) result = deadAmount();
   else {
       if (_isSeparated) result = separatedAmount();
       else {
           if (_isRetired) result = retiredAmount();
           else result = normalPayAmount();
       };
   }
 return result;
 };

=>

double getPayAmount() {
   if (_isDead) return deadAmount();
   if (_isSeparated) return separatedAmount();
   if (_isRetired) return retiredAmount();
   return normalPayAmount();
 };

自我測試代碼

若是認真觀察程序員把最多時間耗在哪裏,你就會發現,編寫代碼其實只佔很是小的一部分。有些時間用來決定下一步幹什麼,另外一些時間花在設計上面,最多的時間則是用來調試(debug)。每一個程序員都能講出「花一成天(甚至更多)時間只找出一隻小小臭蟲」的故事。修復錯誤一般是比較快的,但找出錯誤倒是噩夢一場。當你修好一個錯誤,老是會有另外一個錯誤出現,並且確定要好久之後纔會注意到它。 彼時你又要花上大把時間去尋找它。

「頻繁進行測試」是極限編程( extreme programming XP)[Beck, XP]的重要一 環。「極限編程」一詞容易讓人聯想起那些編碼飛快、自由而散漫的黑客(hackers), 實際上極限編程者都是十分專一的測試者。他們但願儘量快速開發軟件,而他們也知道「測試」可協助他們儘量快速地前進。

在重構以前,先保證一組可靠的測試用例(有自我檢驗的能力),這不只有助於咱們檢測 BUG,其中也有一種以終爲始的思想在裏面,實際上,咱們能夠經過編寫測試用例,更加清楚咱們最終的函數應該長什麼樣子,提供什麼樣的服務。

結束語


感謝您的耐心閱讀,以上就是整個學習的筆記了。

重構不是一個一蹴而就的事,須要長期的實踐和經驗纔可以完成得很好。重構強調的是 Be Better,那在此以前咱們首先須要先動起手來搭建咱們的系統,而不要一味地「完美主義」,近些時間接觸的敏捷式開發也正是這樣的一種思想。

若是有興趣閱讀,這裏只找到一份初版能夠在線閱讀的地方,請自行食用吧:https://www.kancloud.cn/sstd521/refactor/194190


按照慣例黏一個尾巴:

歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz 分享本身的學習 & 學習資料 & 生活 想要交流的朋友也能夠加qq羣:3382693

相關文章
相關標籤/搜索