重構:改善既有代碼的設計(第二版讀書筆記) - 重構、壞代碼、寫好代碼

偶然發現重構這本書推出了js版,果斷入手,名書之一,尤爲仍是js版本,相較於java版來講,確定更適合前端閱讀,購買來自當當。html

本書做者 馬丁·福勒,主要著做有:分析模式---可重用對象模型、Kent Beck. 規劃極限編程、 UML精粹---標準對象建模語言簡明指南(第三版)、 企業應用架構模式以及本書。本書內容以各類代碼的「壞味道」,來推動合適的重構手法,和初版內容相比,有一些部分是更新了(那些被淘汰的代碼、不合適的例子)。但主體思想仍是沒有變,總而言之是一本值得讀的好書前端

重構,改善既有代碼設計(第二版)java

下面來分享本文章核心內容,讀書筆記。程序員

讀書中感悟最深的名言

  1. 原文:重構技術就是以微小的步伐修改程序。若是你犯下錯誤,很容易即可發現它。es6

    感悟:細碎的步子前進,能夠使得咱們避免bug,在實際中,咱們應當是以稍大的步驟來改進,當遇到問題時,撤銷咱們的更改 轉而走向細碎的步子推動算法

  2. 原文:重構前,先檢查本身是否擁有一套可靠的測試集,這些測試必須有自我檢視能力。編程

    感悟:和鮑勃大叔在代碼整潔之道(clean code)中的觀點一致,先編寫測試,才能再開發。重構亦如此json

  3. 原文:一些重構手法也會顯著地影響性能。但即使如此,我一般也不去管它,繼續重構,由於有了一份結構良好的代碼,回頭調優其性能也容易得多設計模式

    感悟:不要由於性能問題而不敢重構,一份好的代碼再去調優是很容易的,更況且在如今各類緩存、壓縮、瀏覽器的優化等加持下,真正影響性能的每每只是咱們項目中的某一小塊代碼數組

  4. 原文:每當感受須要以註釋來講明點什麼的時候,咱們就把須要說明的東西寫進一個獨立函數中,並以其用途(而非實現手法)命名。咱們能夠對一組甚至短短一行代碼作這件事。哪怕替換後的函數調用動做比函數自身還長,只要函數名稱可以解釋其用途,咱們也該絕不猶豫地那麼作。關鍵不在於函數的長度,而在於函數「作什麼」和「如何作」之間的語義距離。

    感悟:一個最好的程序就是不須要任何註釋,本身自己就已經說明了程序的運轉流程,這和在初入行時,老師讓咱們多寫註釋,最好每一行都寫的不太同樣,但也不算老師或者馬丁大叔說錯了,初入行時,咱們什麼都不懂,常常編寫匪夷所思的代碼,因此註釋很必要,可是更好的註釋是咱們自身的代碼。這個在不少代碼規範中或者一些優秀的代碼也都是這麼作的。

tips:好的代碼中,註釋應該是短小精悍的,應該只是咱們須要描述一些很是規作法的說明,或者隱患的說明以及咱們某些時候冒出的一些對程序有用但沒有作的想法

  1. 原文:事實上,撰寫測試代碼的最好時機是在開始動手編碼以前。當我須要添加特性時,我會先編寫相應的測試代碼。聽起來離經叛道,其實否則。編寫測試代碼其實就是在問本身:爲了添加這個功能,我須要實現些什麼?編寫測試代碼還能幫我把注意力集中於接口而非實現(這永遠是一件好事)。預先寫好的測試代碼也爲個人工做安上一個明確的結束標誌:一旦測試代碼正常運行,工做就能夠結束了

    感悟:編寫測試代碼的最佳時機是在開始編寫以前,將業務最終效果編爲測試代碼,有利於咱們明白咱們爲何要作這個功能,須要實現什麼樣的東西。在做者看來,不管是新增功能、修改功能、bug fix都應該測試先行。這是一種正確的思路,也是咱們如今不少程序員中所缺乏的。大部分人作的都是先寫功能,在寫測試。甚至一些公司連測試代碼都沒有

注: 本書經典語句比較多,只挑選了其中我以爲感悟最深入的五點寫出

兩頂帽子的概念

Kent Beck提出了「兩頂帽子」的比喻。使用重構技術開發軟件時,我把本身的時間分配給兩種大相徑庭的行爲:添加新功能和重構。添加新功能時,我不該該修改既有代碼,只管添加新功能。經過添加測試並讓測試正常運行,我能夠衡量本身的工做進度。重構時我就不能再添加功能,只管調整代碼的結構。此時我不該該添加任何測試(除非發現有先前遺漏的東西),只在絕對必要(用以處理接口變化)時才修改測試軟件開發過程當中,我可能會發現本身常常變換帽子。首先我會嘗試添加新功能,而後會意識到:若是把程序結構改一下,功能的添加會容易得多,因而我換一頂帽子,作一下子重構工做。程序結構調整好後,我又換上原先的帽子,繼續添加新功能。新功能正常工做後,我又發現本身的編碼形成程序難以理解,因而又換上重構帽子……整個過程或許只花10分鐘,但不管什麼時候我都清楚本身戴的是哪一頂帽子,而且明白不一樣的帽子對編程狀態提出的不一樣要求。

  • [x] 時刻牢記本身正在作什麼,不要混在一塊兒。這也是我本身所欠缺的東西

讀書內容詳解

重構的定義

  • [x] 所謂重構(refactoring)是這樣一個過程:在不改變代碼外在行爲的前提下,對代碼作出修改,以改進程序的內部結構。重構是一種經千錘百煉造成的有條不紊的程序整理方法,能夠最大限度地減少整理過程當中引入錯誤的機率。本質上說,重構就是在代碼寫好以後改進它的設計。

應該重構的緣由

  1. 需求變化

    需求的變化使重構變得必要。若是一段代碼能正常工做,而且不會再被修改,那麼徹底能夠不去重構它。能改進之固然很好,但若沒人須要去理解它,它就不會真正妨礙什麼。

  2. 新需求(預備性重構)

    重構的最佳時機在添加新功能以前,在動手添加新功能以前,我會看看現有的代碼庫,此時常常會發現:若是對代碼結構作一點微調,工做會變的容易不少(舊代碼重構來擴展新功能)

  3. 幫助理解的重構

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

  4. 撿垃圾式重構

    當我在重構過程當中或者開發過程當中,發現某一塊很差,若是很容易修改能夠順手修改,但若是很麻煩,我又有緊急事情時候,能夠選擇記錄下來(但不表明我就一點都作不到把他變好)。就像野營者的老話:至少讓營地比你到達時更乾淨,長此以往,營地就很是乾淨(來自營地法則)

  5. 見機行事的重構

    重構常常發生在咱們平常開發中,隨手可改的地方。當咱們發現很差的味道,就要將他重構

  6. 長期的重構

    能夠在一個團隊內,達成共識。當你們遇到時候,就改正它例如,若是想替換一個正在使用的庫,能夠先引入一層新的抽象,使其兼容新舊兩個庫的接口,而後一旦調用方徹底改成了使用這層抽象,替換下面的庫就會如容易的多

  7. 複審代碼(code review)時的重構

    開發者與審查者保持持續溝通,使得審查者可以深刻了解邏輯,使得開發者能充分認同複審者的修改意見(結對編程)

不知道什麼時候該重構,那就遵循三次法則(來自書中)

Don Roberts給了我一條準則:第一次作某件事時只管去作;第二次作相似的事會產生反感,但不管如何仍是能夠去作;第三次再作相似的事,你就應該重構正如老話說的:事不過三,三則重構

重構的意義

  1. 改進軟件的設計(也能夠說是增長程序的健壯、耐久)

    經過投入精力改善內部設計,咱們增長了軟件的耐久性,從而能夠更長時間地保持開發的快速

  2. 使得代碼更容易理解

  3. 找到潛在bug

  4. 提升編程速度

重構的挑戰

  1. 延緩新功能開發

    實際上,這只是一部分不理解重構真正緣由的人的想法,重構是爲了從長效上見到收益,一段優秀的代碼能讓咱們開發起來更順手,要權衡好重構與新功能的時機,好比一段不多使用的代碼。就不必對他重構

  2. 代碼全部權

    有時候咱們常常會遇到,接口發佈者與調用者不是同一我的,而且甚至多是用戶與咱們團隊的區別,在這種狀況下,須要使用函數更名手法,重構新函數,而且保留舊的對外接口來調用新函數,而且標記爲不推薦使用。

  3. 分支的差別

    常常會有長期不合並的分支,一旦存在時間過長,合併的可能性就越低,尤爲是在重構時候,咱們常常要對一些東西進行更名和變化,因此最好仍是儘量短的進行合併,這就要求咱們儘量的將功能顆粒化,若是遇到還沒開發完成且又沒法細化的功能,咱們能夠使用特性開關對其隱藏

  4. 缺少一組自測試的代碼

    一組好的測試代碼對重構頗有意義,它能讓咱們快速發現錯誤,雖然實現比較複雜,但他頗有意義

  5. 遺留代碼

    不可避免,一組別人的代碼使得咱們很煩惱,若是是一套沒有合理測試的代碼則使得咱們更加苦惱。這種狀況下,咱們須要增長測試,能夠運用重構手法找到程序的接縫,再接縫處增長測試,雖然這可能有風險,但這是前進所必需要冒的風險,同時不建議一氣呵成的把整個都改完,更傾向於可以逐步地推動

什麼時候不該該重構

  1. 不須要修改的代碼
  2. 隱藏在一個API之下,只有當我須要理解其工做原理時,對其進行重構纔有價值
  3. 重寫比重構還容易

重構與其餘的關係

  1. 開發:短時間會耽誤必定的開發事件,但從長期來看,重構使得新功能會更容易開發
  2. 性能:會影響部分性能,但在大多數的加持下,顯得微不足道,而且重構有利於性能優化的點集中於某一處或者幾處
  3. 架構:相輔相成
  4. 需求:需求推進重構前進

代碼的壞味道

本書之中的核心之一:簡單來講就是碰到什麼樣子的代碼,你就須要警戒起來,須要進行重構了!

本文章中主要分紅三部分進行描述,第一部分爲名字就是它的術語,第二部分爲詳解:它的描述及一些實際場景,第三部分重構:就是他的參考重構手法,但這些手法僅做爲參考,有時咱們可能會須要更多的手法

  1. 神祕命名

    詳解:也包含那些隨意的abc或者漢語拼音,總之一切咱們看不懂的、爛的都算,好的命名能節省咱們很大的時間

    重構:改變函數聲明、變量更名、字段更名

  2. 重複代碼

    詳解:天然這個就好理解了,只要是咱們看到兩段類似的語法均可以肯定爲這段代碼能夠提煉,一般提煉出來會更好,固然這個要看具體狀況,我的感受真的遇到那種只有兩處,且代碼使用地方八杆子打不着,在代碼穩按期間也不用浪費這個時間(這個時間不止體如今改動過程,也包括你可能由於改動致使的隱藏bug,尤爲是系統核心模塊,一旦出現問題只能立刻回滾,不會給你時間去找問題

    重構:提煉函數、移動語句、函數上移等手法

  3. 過長的函數

    描述:短小纔是精悍!好比一些條件分支、一個函數作了不少事情、循環內的處理等等的都是應該重構的

    重構:提煉函數(經常使用)、以查詢取代臨時變量、引入參數對象、保持對象完整性、以命令取代參數(消除一些參數)、分解條件表達式、以多態取代條件表達式(應對分支語句)、拆分循環(應對一個循環作了不少事情)

  4. 過長的參數列表

    描述:正常來講,函數中所需的東西應該以參數形式傳入,避免全局變量的使用,但過長的參數列表其實也很噁心。

    重構:查詢取代參數、保持對象完整、引入參數對象、移除標記參數、函數組合成類

  5. 全局數據

    描述:最多見的就是全局變量,但類變量與單例模式也有這樣的問題,咱們一般沒法保證項目啓動後不被修改,這就很容易形成詭異的bug,而且很難追查到

    重構:封裝變量

  6. 可變數據

    描述:數據的可變性和全局變量同樣,若是我其餘使用者修改了這個值,而引起不可理喻的bug。 這是很難排查的。

    重構:封裝變量,拆分變量,移動語句、提煉函數,查詢函數和修改函數分離,移除設值函數,以查詢取代變量函數組合成類

  7. 發散式變化

    描述:發散式變化是指某個模塊常常由於不一樣的緣由在不一樣的方向上變化了(能夠理解爲某一處修改了,形成其餘模塊方向錯亂)

    重構:拆分階段、搬移函數、提煉函數、提煉類

  8. 霰彈式修改

    描述:和發散式變化接近,卻又相反。咱們每次修改一個功能或者新增一個功能都須要對多處進行修改;而且隨着功能增多咱們可能還須要修改更多。 這樣程序時是很不健康的,其實我我的理解爲:霰彈用來描述發散式變化更好,想一想霰彈是一個點發射出去變成不少。而本條應該用另外一個詞來描述更好,但我還想不到叫什麼詞。或許叫多路並進?僅限我的觀點,每一個人理解可能不同,建議以做者爲準

    重構:搬移函數、搬移字段、函數組合成類、函數組合成變換、拆分階段、內聯函數、內聯字段

  9. 依戀情結

    描述:一個模塊內的一部分頻繁的和外面的模塊進行交互溝通,甚至超過了它與內部的溝通。也就是違反了高內聚低耦合,遇到這種的「叛亂者」,不如就讓他去他想去的地方吧

    重構:搬移函數、提煉函數

  10. 數據泥團

    描述:雜合纏繞在一塊兒的。代碼中也如是,咱們可能常常看到三四個相同的數據,兩個類中相同字段等等。總之像泥同樣,這裏也是這樣那裏也是這樣,就是他了

    重構:提煉類、引入參數對象、保持對象完整性

  11. 基本類型偏執

    描述:一些基本類型沒法表示一個數據的真實意義,例如電話號碼、溫度等,

    重構:以對象取代基本類型、以子類取代類型碼、以多態取代條件表達式

  12. 重複的switch

    描述:不僅是switch,大片相聯的if也應該包含在內,甚至在古老的前端時代,曾經一度無條件反對這樣的寫法。

    重構:多態取代條件表達式

  13. 循環語句

    描述:在js中體現爲傳統的for類循環

    重構:用管道來取代循環(管道:map、forEach、reduce、filter等一系列)

  14. 冗贅的元素

    描述:元素指類和函數,可是這些元素可能由於種種緣由,致使函數過於小,致使沒有什麼做用,以及那些重複的,均可以算做冗贅

    重構:內聯函數、內聯類、摺疊繼承類

  15. 誇誇其談通用性

    描述:爲了未來某種需求而實現的某些特殊的處理,但其實可能致使程序難以維護難以理解,直白來講就是沒個錘子用的玩意,你留下他幹個屁

    重構:摺疊繼承體系、內聯函數、內聯類、改變函數聲明、移除死代碼

  16. 臨時字段

    描述:那些自己就足以說明本身是誰的,不須要名字來描述的

    重構:提煉類、提煉函數、引入特例

  17. 過長的消息鏈

    描述:一個對象請求另外一個對象,而後再向後者請求另外一個對象,而後再請求另外一個對象……這就是消息鏈,舉個例子來講

    new Car().properties.bodyWork.material.composition().start()

    這意味着在查找過程當中,不少的類耦合在一塊兒。我的認爲,不只是結構的耦合,也很難理解。這也包含某類人jq的那一大串的連續調用。都是很難讓人理解的。

    重構: 隱藏委託關係、提煉函數、搬移函數

  18. 中間人

    描述:若是一個類有大部分的接口(函數)委託給了同一個調用類。當過分運用這種封裝就是一種代碼的壞味道

    重構:移除中間人、內聯函數

  19. 內幕交易

    描述:兩個模塊的數據頻繁的私下交換數據(能夠理解爲在程序的鮮爲人知的角落),這樣會致使兩個模塊耦合嚴重,而且數據交換隱藏在內部,不易被察覺

    重構:搬移函數、隱藏委託關係、委託取代子類、委託取代超類

  20. 過大的類

    描述:單個類作了過多的事情,其內部每每會出現太多字段,一旦如此,重複代碼也就接踵而至。這也意味着這個類毫不只是在爲一個行爲負責

    重構:提煉超類、以子類取代類型碼

  21. 殊途同歸的類

    描述:兩個能夠相互替換的類,只有當接口一致纔可能被替換

    重構:改變函數聲明、搬移函數、提煉超類

  22. 純數據類

    描述:擁有一些字段以及用於讀寫的函數,除此以外一無可取的類,通常這樣的類每每半必定被其餘類頻繁的調用(若是是不可修改字段的類,不在此列,不可修改的字段無需封裝,直接經過字段取值便可),這樣的類每每是咱們沒有把調用的行爲封裝進來,將行爲封裝進來這種狀況就能獲得很大改善。

    重構:封裝記錄、移除取值函數、搬移函數、提煉函數、拆分階段

  23. 被拒絕的遺贈

    描述:這種味道比較奇怪,說的是繼承中,子類不想或不須要繼承某一些接口,咱們能夠用函數下移或者字段下移來解決,但不值得每次都這麼作,只有當子類複用了超類的行爲卻又不肯意支持超類的接口時候咱們才應該作出重構

    重構:委託取代子類、委託取代超類

  24. 註釋

    描述:這裏提到註釋並不是是說註釋是一種壞味道,只是有一些人常常將註釋看成「除臭劑」來使用(一段很長的代碼+一個很長的註釋,來幫助解釋)。每每遇到這種狀況,就意味着:咱們須要重構了

    重構:提煉函數、改變函數聲明、引入斷言

重構手法介紹

若是說上面的味道是核心的話,那手法應該就是本書的重中之重。一般咱們發現哪裏味道不對以後,就要選擇使用不一樣的手法進行重構。將他們變得味道好起來。

本文中每一個手法一般包含三個模塊:時機(遇到什麼狀況下使用)、作法(詳細步驟的歸納)、關鍵字(作法的縮影)

提煉函數

  • 時機:
  1. 當咱們以爲一段大函數內某一部分代碼在作的事情是同一件事,而且自成體系,不與其餘摻雜時
  2. 當代碼展現的意圖和真正想作的事情不是同一件時候,如做者提到的例子。想要高亮,代碼意思爲反色,這樣就不容易讓人誤解,印證了做者前面說的:當你須要寫一行註釋時候,就適合重構了
  • 作法:
  1. 一個以他要作什麼事情來命名的函數
  2. 待提煉代碼複製到這個函數
  3. 檢查這個函數內的代碼的做用域、變量
  4. 編譯查看函數內有沒有報錯(js能夠經過eslint協助)
  5. 替換源函數的被提煉代碼替換爲函數調用
  6. 測試
  7. 替換其餘代碼中是否有與被提煉的代碼相同或類似之處
  • 關鍵字:

    新函數、拷貝、檢查、做用域/上下文、編譯、替換、修改細節

內聯函數

  • 時機:

    1. 函數內代碼直觀表達的意思與函數名字相同
    2. 有一堆雜亂無章的代碼須要重構,能夠先內聯函數,再經過提煉函數合理重構
    3. 非多態性函數(函數屬於一個類,而這個類被繼承)
  • 作法:

    1. 檢查多態性(若是該函數屬於某個超類,而且它具備多態性,那麼就沒法內聯)
    2. 找到全部調用點
    3. 將函數全部調用點替換爲函數本體(非一次性替換,能夠分批次替換、適應新家、測試)
    4. 刪掉該函數的定義(也可能會不刪除,好比咱們放棄了有一些函數調用,由於重構爲漸進式,非一次性)
  • 關鍵字:

    檢查多態、找調用並替換、刪除定義

提煉變量

  • 時機:
  1. 一段又臭又長的表達式
  2. 在多處地方使用這個值(多是當前函數、當前類乃至於更大的如全局做用域)
  • 作法:
  1. 確保要提煉的表達式,對其餘地方沒有影響
  2. 聲明一個不可修改的變量,並用表達式做爲該變量的值
  3. 用新變量取代原來的表達式
  4. 測試
  5. 交替使用三、4
  • 關鍵字:

反作用、不可修改的變量、賦值、替換

內聯變量

  • 時機:
  1. 變量沒有比當前表達式有什麼更好的釋義
  2. 變量妨礙了重構附近代碼
  • 作法:
  1. 檢查確認變量賦值的右側表達式不對其餘地方形成影響
  2. 確認是否爲只讀,若是沒有聲明只讀,則要先讓他只讀,並測試
  3. 找到使用變量的地方,直接改成右側表達式
  4. 測試
  5. 交替使用三、4
  • 關鍵字

反作用、只讀、替換變量

改變函數聲明

最好能把大的修改拆成小的步驟,因此若是你既想修改函數名,又想添加參數最好分紅兩步來作。
不論什麼時候,若是遇到了麻煩,請撤銷修改,並改用遷移式作法)
  • 時機:
  1. 函數名字不夠貼切函數所作的事情
  2. 函數參數增長
  3. 函數參數減小
  4. 函數參數概念發生變化
  5. 函數由於某個參數致使的函數應用範圍小(全局有不少相似的函數,在作着相似的事情)
  • 作法(適用於肯定了函數或者參數只在有限的小範圍內使用,而且僅僅更名)
  1. 先肯定函數體內有沒有使用這個參數(針對於參數)
  2. 肯定函數調用者(針對於函數)
  3. 修改函數/參數的聲明,使其達到咱們想要的效果
  4. 找到全部的函數/參數聲明的地方將其更名
  5. 找到全部函數/參數調用的地方將其替換
  • 關鍵字

使用變量者、函數調用者、修改函數、聲明更名、調用替換

  • 作法(標準化作法)
  1. 對函數內部進行重構(若是有必要的話)
  2. 使用提煉函數手法,將函數體提煉成一個新函數,同名的話,能夠改成一個暫時的易於搜索的隨意名字(如:aaa_getData,只要好搜索且惟一便可。),非同名的話,使用咱們想要的名字做爲新函數名字
  3. 在新函數內作咱們的變動(新增參數、刪除參數、改變參數釋義等)
  4. 改變函數調用的地方(若是是新增、修改、刪除參數)
  5. 測試
  6. 對舊函數使用內聯函數來調用或返回新函數
  7. 若是使用了臨時名字,使用改變函數聲明將其改回原來的名字(這時候就要刪除舊函數了)
  8. 測試
  • 關鍵字:

內部重構、提煉新函數、好搜索的臨時名字、變動、改變調用、舊函數使用新函數、改變調用名字

封裝變量

  • 時機:
  1. 當咱們在修改或者增長使用可變數據的時候
  2. 數據被大範圍使用(設置值)
  3. 對象、數組無外部變更須要內部一塊兒改變的需求時候,最好返回一份副本
  • 作法:
  1. 建立封裝函數(包含訪問和更新函數)
  2. 修改獲取這個變量和更新這個變量的地方
  3. 測試
  4. 控制變量外部不可見(能夠藉助es6類中的get來實現不可變量以及限制可見)
  5. 測試
  • 關鍵字:

新函數、替換調用、不可見

變量更名

  • 時機:
  1. 變量/常量的名字不足以說明字段的意義
  2. 垃圾命名
  • 作法:
  1. 針對普遍使用的

    1.1 先用封裝變量手法封裝

    1.2 找到全部使用該變量的代碼,修改測試(若是是對外已發佈的變量,能夠標記爲不建議使用(做者沒提到,可是我的感受是能夠這樣的)

    1.3 測試

  2. 只做用於某個函數的直接替換便可

  3. 替換過程當中能夠以新名字做爲過渡。待所有替換完畢再刪除舊的名字

  • 關鍵字:

封裝變量手法、替換名字、中間過渡

引入參數對象

  • 時機:
  1. 一組參數總在一塊兒出現
  2. 函數參數過多
  • 作法:
  1. 建立一個合適的數據結構(若是已經有了,能夠略過)

    數據結構選擇:一種是以對象的形式,一種是以類的形式,做者推薦以類的形式,可是在我看來,要根據場景,若是這組數據以及其相關行爲能夠變爲一組方法,如數組類裏面的比較兩個數組是否徹底一致,這就能夠以類來聲明(js中也能夠以export來導出而使用)

  2. 使用改變函數聲明手法給原函數增長一個參數爲咱們新的結構

  3. 測試

  4. 舊數據中的參數傳到新數據結構(變動調用方)

  5. 刪除一項舊參數,並將之使用替換爲新參數結構

  6. 測試

  7. 重複五、6

  • 關鍵字:

新結構、增長參數、入參新結構、刪除舊參數、使用新結構

函數組合成類

  • 時機:
  1. 一組函數(行爲)老是圍繞一組數據作事情
  2. 客戶端有許多基於基礎數據計算派生數據的需求
  3. 一組函數能夠自成一個派系,而放在其餘地方老是顯得不夠完美
  • 作法:
  1. 若是這一組數據還未作封裝,則使用引入參數對象手法對其封裝
  2. 運用封裝記錄手法將數據記錄封裝成數據類
  3. 使用搬移函數手法將已有的函數加入類(若是遇到參數爲新類的成員,則一併替換爲使用新類的成員)
  4. 替換客戶端的調用
  5. 將處理數據記錄的邏輯運用提煉函數手法提煉出來,並轉爲不可變的計算數據
  • 關鍵字:

提煉變量、封裝成類、移入已有函數、替換調用、移入計算數據

函數組合成變換

  • 時機:
  1. 函數組合成變換手法時機等同於組合成類的手法,區別在於其餘地方是否須要對源數據作更新操做。 若是須要更新則使用類,不須要則使用變換,js中推薦類的方式
  • 作法:
  1. 聲明一個變換函數(工廠函數)
  2. 參數爲須要作變換的數據(須要deepclone)
  3. 計算邏輯移入變換函數內(比較複雜的能夠使用提煉函數手法作個過渡)
  4. 測試
  5. 重複三、4
  • 關鍵字:

變換函數、變換入參、搬移計算邏輯

封裝記錄

  • 時機:
  1. 可變的記錄型結構
  2. 一條記錄上有多少字段不夠直觀
  3. 有須要對記錄進行控制的需求(我的理解爲須要控制權限、須要控制是否只讀等狀況)
  4. 須要對結構內字段進行隱藏
  • 作法:
  1. 首先用封裝變量手法將記錄轉化爲函數(舊的值的函數)
  2. 聲明一個新的類以及獲取他的函數
  3. 找到記錄的使用點,在類內聲明設置方法
  4. 替換設置值的方法(es6 set)
  5. 聲明一個取值方法,並替換全部取值的地方
  6. 測試
  7. 刪除舊的函數
  8. 當咱們須要更名時,能夠保留老的,標記爲不建議使用,並聲明新的名字進行返回
  • 關鍵字:

轉化函數、取值函數、設值函數、替換調用者、替換設置者

以對象取代基本類型

  • 時機:
  1. 隨着開發迭代,咱們一個簡單的值已經不只僅只是簡單的值那麼簡單了,他可能還要肩負一些其餘的職責,如比較、值行爲等
  2. 一些關鍵的、非僅僅只有打印的功能的值
  • 作法:
  1. 若是沒被封裝,先使用封裝變量手法
  2. 爲要修改的數據值建立一個對象,併爲他提供取值、設值函數(看需求)
  3. 使用者(多是另一個大類)修改其取值設值函數
  4. 測試
  5. 修改大類中的取值設值函數的名稱,使其更好的語義化
  6. 爲這個新類增長其行爲(多是轉換函數、比較函數、特殊處理函數、操做函數)等
  7. 根據實際需求對新類進行行爲擴展(若是有必要的話)
  8. 修改外部客戶端的使用
  • 關鍵字:

新類、取設值函數、行爲入類、擴展類

以查詢取代臨時變量

  • 時機:
  1. 修改對象最好是一個類(這也是爲何提倡class,由於類能夠開闢一個命名空間,不至於有太多全局變量)
  2. 有不少函數都在將同一個值做爲參數傳遞
  3. 分解過長的冗餘函數
  4. 多個函數中重複編寫計算邏輯,好比講一個值進行轉換(好幾個函數內都須要這個轉換函數)
  5. 若是這個值被屢次修改,應該將這些計算代碼一併提煉到取值函數
  • 作法:
  1. 檢查是否每次計算過程和結果都一致(不一致則放棄)
  2. 若是能改成只讀,就改爲只讀
  3. 將變量賦值取值提煉成函數
  4. 測試
  5. 去掉臨時變量
  • 關鍵字:

只讀、提煉函數、刪變量

提煉類

  • 時機:
  1. 一個大的類在處理多個不一樣的事情(這個類不純潔了)
  • 作法:
  1. 肯定分出去的部分要作什麼事情
  2. 建立一個新的類,表示從舊地方分離出來的責任
  3. 舊類建立時,爲新類初始化
  4. 使用搬移函數手法將須要的方法搬移到新的類(搬移函數時候就將調用地方更名)
  5. 刪除多餘的接口函數,併爲新類的接口取一個適合本身的名字
  6. 考慮是否將新的類開放爲公共類
  • 關鍵字:

職責邊界確認、建立新域、新舊同步初始化、行爲搬家、接口刪除

內聯類

  • 時機:
  1. 一個曾經有不少功能的類,在重構過程當中,已經變成一個毫無單獨職責的類
  2. 須要對兩個類從新進行職責劃分
  • 作法:
  1. 將須要內聯的類中的全部對外可調用函數(也多是字段)在目標類中新建一個對應的中間代理函數
  2. 修改調用者,調用代理方法並測試
  3. 將原函數中的相關方法(字段)搬移到新地方並測試
  4. 原類變爲空殼後就能夠刪除了
  • 關鍵字:

代理、修改調用者、方法搬家、拋棄舊類

隱藏委託關係

  • 時機:
  1. 一個類須要隱藏其背後的類的方法或事件
  2. 一個客戶端調用類的方法時候,必須知道隱藏在後面的委託關係才能調用
  • 作法:
  1. 在服務類(對外的類)中新建一個委託函數,讓其調用受託類(背後的類)的相關方法
  2. 修改全部客戶端調用爲這個委託函數
  3. 重複12直到受託類所有被搬移完畢,移除服務類中返回受託類的函數
  • 關鍵字:

委託函數、替換調用者、刪除委託整個類

移除中間人

  • 時機:
  1. 由於隱藏委託關係(當初多是比較適合隱藏的)手法形成的如今轉發函數愈來愈多
  2. 過分的迪米特法則形成的轉發函數愈來愈多
  • 作法:
  1. 在服務類(對外)內爲受託對象(背後的類)建立一個返回整個委託對象的函數
  2. 客戶端的調用轉爲連續的訪問函數進行調用
  3. 刪除本來的中間代理函數
  • 關鍵字:

委託整個類、修改調用、刪除代理

替換算法

  • 時機:
  1. 舊算法已經不知足當前功能
  2. 有更好的方式能夠完成與舊算法相同的事情(一般是由於優化)
  • 作法:
  1. 保證待替換的算法爲單獨的封裝,不然先將其封裝
  2. 準備好更好的算法,
  3. 替換算法過去
  4. 運行並測試新算法與舊算法對比(必定要對比,也許你選的還不如之前呢)
  • 關鍵字:

算法封裝、編寫新算法、替換算法、比較算法

搬移函數

  • 時機:
  1. 隨着對項目(模塊)的認知過程當中,也多是改造過程當中,一些函數已經脫離了當前模塊的範圍
  2. 一個模塊內的一些函數頻繁的與其餘模塊交互,卻不多和自身內部進行交互(出現了叛變者)
  3. 一個函數在發展過程當中,如今他已經有了更通用的場景
  • 作法:
  1. 查找要搬移的函數在當前上下文中引用的全部元素(先將依賴最少的元素進行搬離)
  2. 考慮待搬移函數是否具備多態性(複寫了超類的函數或者被子類重寫)
  3. 複製函數到目標上下文,調整函數,適應新的上下文
  4. 函數內使用的變量考慮是一塊兒搬移仍是以參數傳遞
  5. 改寫原函數爲代理函數(也能夠內聯)
  6. 檢查新函數是否能夠繼續進行搬離
  • 關鍵字:

肯定關係、肯定繼承、優先基礎、函數搬家、相關部分位置肯定、原址代理、優化新函數

搬移字段

  • 時機:
  1. 隨着業務推動過程當中,原有的數據結構已經不能很好的表示程序的邏輯
  2. 每當調用一個函數時,須要傳入的記錄參數,老是須要傳入另外一條記錄或者他的某些字段一塊兒
  3. 修改(行爲)一條記錄時,老是須要同時改動其餘記錄
  4. 更新(數據)一條字段時,老是須要同時在多個結構中做出修改
  • 作法:
  1. 源字段已經被封裝(若是未封裝,則應該先使用封裝變量手法對其封裝)
  2. 目標對象上建立一個字段,及其訪問函數
  3. 源對象對目標對象的字段作對應的代理
  4. 調整源對象的訪問函數,令其使用目標對象的字段
  5. 測試
  6. 移除源對象的字段
  7. 視狀況而定決定是否須要內聯變量訪問函數
  • 關鍵字:

封裝、新字段、源址代理、代理新址、舊字段移除、肯定是否內聯

搬移語句到函數

  • 時機:
  1. 重複代碼
  2. 每次調用a方法時,b操做也老是每次都執行
  3. 某些語句放在特定函數內更像一個總體
  • 作法:
  1. 將重複代碼使用搬移函數手法到緊鄰目標函數的位置
  2. 若是目標函數緊被惟一一個原函數調用,則只須要將原函數的重複片斷粘貼到目標函數便可
  3. 選擇一個調用點進行提煉函數,將目標語句函數與語句提煉成一個新的函數
  4. 修改函數其餘調用點,令他們調用新提煉的函數
  5. 調整函數的引用點
  6. 內聯函數手法將目標函數內聯到新函數裏
  7. 移除原目標函數
  8. 對新函數應用函數更名手法(改變函數聲明的簡單作法)
  • 關鍵字:

代碼靠近、單點提煉、中間函數、修改引用、函數內聯、原函數刪除、函數更名

函數搬移到調用者

  • 時機:
  1. 隨着系統前進過程當中,函數某一塊的做用發生改變,再也不適合原函數位置
  2. 以前在多個地方表現一致的行爲,現在在不一樣調用點面前表現了不一樣的行爲

tips: 本手法只適合邊界有些許偏移的場景,不適合相差較大的場景

  • 作法:
  1. 簡單狀況下,直接剪切
  2. 將不想搬移的部分提煉成與當前函數同級函數(若是是超類方法,子類也要一塊兒提煉)
  3. 原函數調用新的同級函數
  4. 替換調用點爲新的同級函數和要內聯的語句
  5. 刪除原函數
  6. 使用函數更名手法(改變函數聲明的簡單作法)改回名字
  • 關鍵字:

提煉不變的爲臨時方法、搬移語句、刪除原,更名字

以函數調用替換內聯代碼

  • 時機:
  1. 函數內作的某些事情與已有函數重複
  2. 已有函數與函數之間但願同步變動
  • 作法:
  1. 內聯代碼替換爲函數(可能有參數,就要對應傳遞)
  • 關鍵字:

內聯替換

移動語句

  • 時機:
  1. 移動語句通常用於整合相關邏輯代碼到一處,這是其餘部分手法的基礎
  2. 代碼相關邏輯整合一處方便咱們對這部分代碼優化和重構
  • 作法:
  1. 肯定要移動的語句要移動到哪(調整的目標是什麼、該目標可否達到)
  2. 肯定要移動的語句是否搬移後會使得代碼不能正常工做,若是是,則放棄
  • 關鍵字:

肯定反作用、肯定目標

拆分循環

  • 時機:
  1. 一個循環作了多件不相干事
  • 作法:
  1. 複製循環
  2. 若是有反作用則刪除單個循環內的重複片斷
  3. 提煉函數
  4. 優化內部
  • 關鍵字:

複製循環、行爲拆分、函數提煉

以管道替代循環

  • 時機:
  1. 一組雖然在作相同事情的循環,可是內部過多的處理邏輯,使其晦澀難懂
  2. 不合適的管道(如過濾使用some)
  • 作法:
  1. 建立一個新變量,用來存放每次行爲處理後,參與循環的剩餘集合
  2. 選用合適的管道,將每一次循環的行爲進行搬移
  3. 搬移完全部的循環行爲,刪除整個循環
  • 關鍵字:

新變量、合適的管道、刪除整個循環

移除死代碼

  • 時機:
  1. 代碼隨着迭代已經變得沒用了。
  2. 即便這段代碼未來頗有可能還會使用,那也應該移除,畢竟如今版本控制很實用。
  • 作法:
  1. 若是不能夠外部引用,則放心刪除(若是可能未來極有可能會啓用,在這裏留下一行註釋,標示曾經有過這段代碼,以及它被刪除的那個提交的版本號)
    二、若是外部引用了,則須要仔細確認還有沒有其餘調用點(有eslint規則限制的話。其實能夠先刪了,看有沒有報錯)
  • 關鍵字:

檢查引用

拆分變量

  • 時機:
  1. 一個變量被應用到兩種/多種的做用下
  2. 修改輸入參數的值
  • 作法:
  1. 在變量第一次賦值的地方,爲函數取一個更加有意義的變量名(儘可能聲明爲const)
  2. 在第二次賦值地方聲明該變量
  3. 以該變量第二次賦值動做爲界,修改此前對該變量的全部引用。讓他們引用新的變量
  4. 測試
  5. 重複上述,直到變量拆分完畢
  • 關鍵字:

新變量、賦值時聲明、替換調用

字段更名

  • 時機:
  1. 記錄結構中的字段須要改個名字
  • 作法:
  1. 若是結構簡單,能夠一次性替換
  2. 若是記錄沒有封裝,最好是先封裝記錄
  3. 修改構造時候作兼容判斷(老的值與新的值兼容判斷:this.a = data.a || data.b)
  4. 修改內部設取值函數
  5. 修改記錄數據類中的內部調用
  6. 測試
  7. 修改外部調用初始化時候的數據
  8. 刪除初始化兼容判斷
  9. 使用函數更名手法(改變函數聲明的簡單作法),修改調用處的調用方式及內部取設值函數爲新字段名
  • 關鍵字:

封裝、兼容初始化、內部取設只返回新字段,修改內部調用,測試、刪除兼容、內部取設更名、替換外部調用

以查詢取代派生變量

  • 時機:
  1. 兩個變量相互耦合
  2. 設置一個變量的同時,將另外一個變量與該變量結合,經過計算後給另外一個變量設置值

tips:計算的參考變量,是不可變的,計算結果也是不可變的。能夠不重構(仍是那句話,不可變的數據,咱們就不必理他)

  • 作法:
  1. 肯定能夠引發變量發生變化的全部點(若是有來自其餘模塊變量,須要先用拆分變量手法
  2. 新建一個計算函數,計算變量值
  3. 引入斷言(assert),確保計算函數的值與該變量結果相同
  4. 測試
  5. 修改讀取變量的代碼,用內聯函數手法將計算函數內聯進來)
  6. 移除死代碼手法將舊的更新點的地方清理掉
  • 關鍵字:

來源肯定、結果相同、計算函數、清理更新點

將引用對象改成值對象

  • 時機:
  1. 幾個對象中共享了一個對象,而且要聯動變動的狀況下
  2. 值對象就是每次設置都直接設置這個值,好比:
值對象:a.b=new b(1)
    引用對象:a.b.c=1
  • 作法:
  1. 檢查重構的目標是否爲不可變對象,若是不是的話,則看看是否能夠將其改成不可變對象
  2. 移除設值函數手法去掉第一個設引用值函數(每次都用設置值的方式複寫整個對象)
  3. 測試
  4. 重複二、3
  5. 判斷兩次相同輸入時候,值是否相等
  • 關鍵字:

不可變、替換設置引用值爲設置值

將值對象改成引用對象

  • 時機:
  1. 數據副本在多處使用,而且須要一處變化其餘地方同步更新
  • 作法:
  1. 建立一個倉庫(若是沒有的話),倉庫要支持:每次訪問相同數據都是一個相同的引用對象、支持註冊新數據和獲取同一個引用數據(js能夠在簡單場景下簡單的使用{})
  2. 確保倉庫的構造函數有辦法找到關聯對象的正確實例
  3. 修改調用點,令其從倉庫獲取關聯對象。
  4. 測試
  • 關鍵字:

共享倉庫、單例的引用對象、替換調用點

分解條件表達式

  • 時機:
  1. 條件邏輯內,過長的函數,致使反而難以理解條件邏輯的場景
  2. 單個條件邏輯處理的函數過大
  • 作法:
  1. 對條件判斷的每一個分支分別運用提煉函數手法
  2. 若是條件表達式過長,對條件表達式運用提煉函數手法
  3. 優化當前條件邏輯(如使用三元表達式)
  • 關鍵字:

提煉分支、提煉條件、優化判斷

合併條件表達式

  • 時機:
  1. 無其餘反作用的嵌套if
  2. 無其餘反作用的,且返回一致的並列if
  3. 這些if都是關聯的(能夠用是否能提煉出一個合適的函數名來做爲依據,但也不是絕對,咱們能夠選擇不提煉函數,可是仍是建議是相關的if做爲一組)
  • 作法:
  1. 肯定條件表達式有反作用,先用將查詢函數和修改函數分離的手法對其處理
    二、若是是嵌套函數通常是用邏輯與合併,若是是並列的if通常是用邏輯或合併,若是兩種均有,就要組合使用了(可是我更建議他們應該分離成多個判斷)
    三、測試
  2. 重複二、3
  3. 對合並後的條件表達式進行提煉函數手法(有必要的話)
  • 關鍵字:

分離反作用、合適的邏輯符、提煉條件函數

以衛語句取代嵌套表達式

  • 時機:
  1. 無其餘反作用的嵌套if
  2. 無其餘反作用的,且返回一致的並列if
  3. 這些if都是關聯的(能夠用是否能提煉出一個合適的函數名來做爲依據,但也不是絕對,咱們能夠選擇不提煉函數,可是仍是建議是相關的if做爲一組)
  • 作法:
  1. 選取最外層須要被替換的條件邏輯,將其替換爲衛語句(單獨檢查條件、並在條件爲真時馬上返回的語句,叫作衛語句)
  2. 測試
  3. 重複一、2
  • 關鍵字:

從外而內

以多態取代條件表達式

  • 時機:
  1. 多種並列或者嵌套的條件邏輯,讓人難以理解
  2. switch
  3. 同行爲不一樣類型的判斷
  • 作法:
  1. 肯定現有的條件類是否具備多態性,若是沒有,能夠經過將行爲封裝成類(藉助其餘手法如函數組合成類等)

  2. 在調用方使用工廠函數得到行爲對象的實例

  3. 針對不一樣類型建立子類(至關於在超類在分化)

  4. 調用方此時應當經過一個工廠返回合適的子類

  5. 將超類中針對子類類型所作的判斷,逐一移入對應子類進行復寫(相關子類複寫超類的分支函數),超類只留下默認值

注意:這種手法實際上是在面向對象開發中很經常使用的一種方式,可是若是不是

  1. 在寫一個面向對象很明確的項目
  2. 這個判斷過於大
  3. 能夠明確這些子類抽取出來是有意義的(從後期維護角度來講,須要對其增長一些行爲)
  4. 這個子類能夠自成體系

不如將其經過一個json或者map來進行指責劃分。在js中我以爲更經常使用的是以策略來代替if

  • 關鍵字:

多態、繼承、封裝、行爲拆分

引入特例

  • 時機:
  1. 數據結構的調用者都在檢查某個特殊值,而且這個值每次所作的處理也都相同

  2. 多處以一樣方式應對同一個特殊值

三種狀況
第一種原始爲類,特例元素沒有設置值的操做
第二種原始爲類,特例元素有設置值的操做
第三種 原始就是普通的json

  • 作法:

針對於有本身對應行爲的類

  1. 在原類中爲特例元素增長一個函數,用以標記這個特例的狀況,默認返回一個寫死的就行)

  2. 爲特例建立一個class,用以處理特例的正常邏輯和行爲,須要把特例對象及其全部行爲放到這個類

  3. 將本次特例的條件使用提煉函數手法抽成一個在類中的字段函數返回true

  4. 修改全部調用者爲第3步的函數

  5. 修改第一步建立的類。讓它返回咱們的特例對象

  6. 特例中的其餘字段

針對於只讀的類

  1. 將上面作法的建立一個b類改成在類內建立一個函數,返回對象便可。把特例所需信息所有返回在js

針對於原始不是類的

  1. 爲特例對象建立一個函數,返回特例對象的深拷貝狀態

  2. 將本次特例的條件使用提煉函數手法抽成一個統一的函數

  3. 對第一步建立的函數返回值作特殊加強。 將須要的特例的值,逐一放進來。

  4. 替換調用者使用函數的返回值

  • 關鍵字:

特例邏輯搬到class、過渡函數、替換調用者、修改新class

將查詢函數和修改函數分離

  • 時機:
  1. 一個函數既有返回值又有設置值
  • 作法:
  1. 複製一份目標函數並更名爲查找函數的名字
  2. 將被複制的函數刪除設置值的代碼
  3. 將調用者替換爲新函數,並在下面調用原函數
  4. 刪除原函數返回值
  5. 將原函數和新函數中的相同代碼進行優化
  • 關鍵字:

新函數爲查找、刪除設置值、替換調用者、刪除返回值、優化

函數參數化

  • 時機:
  1. 有多餘一個函數的邏輯很是類似,只是有一些字面量不一樣(有時候可能會碰到a、b很類似,a、c也很類似,可是b、c差距比較大時候,這種狀況我的觀點爲:將ab、ac中邏輯緊密的抽成一個,不要形式化的就要吧abc抽到一塊兒。反而拔苗助長)
  • 作法:
  1. 從這一組類似函數中,找到一組,一般來講儘量選擇調用比較少的地方
  2. 運用改變函數聲明手法(改變參數)使其在調用時候,將變化的部分以參數形式傳入)
  3. 修改當前這個函數的全部調用點,爲調用新函數,並傳遞參數
  4. 修改新函數,讓它使用新傳進來的參數
  5. 將其餘類似的函數,逐一替換爲這個新函數,每次替換都要測試一下
  • 關鍵字:

調用較少、變化點入參、修改調用、替換使用

移除標記參數

  • 時機:
  1. 一個用來控制函數流程的參數
  • 作法:
  1. 針對參數的每一種可能值,新建一個明確函數(若是參數控制整個流程,則能夠用分解條件表達式手法建立明確函數,若是隻控制一部分函數則建立轉發函數,將這些函數,統一經過這些明確函數進行轉發)

  2. 替換調用者

tips:若是是這個標記即做爲標記,又做爲參數值。則對其進行拆分。

  • 關鍵字:

流程、行爲拆分

保證對象完整的手法

  • 時機:
  1. 從一個代碼中導出幾個值
  2. 調用者將自身的部分參數傳遞
  3. 通常發生在引入參數對象手法以後
  • 作法:
  1. 新建一個空函數(多是新建,也多是用提煉函數),接受完整對象
  2. 新函數體內調用舊函數,而且使用合適的參數列表
  3. 修改舊函數的調用者,令他使用新函數,修改舊函數內部
  4. 使用內聯函數手法將舊函數內代碼搬移到新建的函數
    五、修改新函數的名字爲舊函數
  • 關鍵字:

接受完整對象、新調用老、修改調用、內聯、更名

以查詢取代參數

  • 時機:
  1. 一個函數傳入了多個相同的值(如老是能根據b參數不須要很複雜就能夠查到a參數)
  2. 調用函數傳入了一個函數自己就能夠很容易得到的參數(指的是內部或者計算得到,而非從其餘模塊拿)
  3. 若是目標函數自己就具備引用透明性(函數的返回值只依賴於其輸入值),用查詢後,他去訪問了一個全局變量,則不適合用本重構

一言以概之:這個函數自身或者經過參數都能獲得另外一個值就能夠使用這個手法

  • 作法:
  1. 若是有必要,能夠將參數計算的過程提煉爲一個只讀變量或者一個函數
    二、將函數體內引用該參數的地方,都改成運用計算函數
    三、去掉該參數(調用者也要去掉)
  • 關鍵字:

提煉變量、參數消除

以參數取代查詢

  • 時機:
  1. 一個函數內部由於引用了全局變量而致使了不透明
  2. 一個函數內部引用了一個即將被刪除的元素
  3. 一個函數內部,過多的依賴了另外一個模塊(這種有兩種作法:一種是本手法,另外一種是搬移函數手法,要根據函數實際做用操做
  • 作法:
  1. 使用提煉變量手法將目標(但願做爲參數傳入的查詢)提煉出來
  2. 把整個函數體提煉,而且單獨放到一個函數內(須要保留計算邏輯,計算邏輯做爲代理函數每次的值以參數傳入函數)
  3. 消除剛纔提煉出來的變量(舊函數應該只剩下一個簡單的調用)
  4. 修改調用方,改成調用新函數,並傳入調用時候計算的計算值
  5. 刪除原函數內的計算代理
  6. 新函數改回舊函數的名字(若是意義發生變化,須要從新起名字)
  • 關鍵字:

變量提煉、函數體換新、舊函數傳參、舊函數調新函數,刪除代理函數、函數更名

移除設值函數

  • 時機:
  1. 類內某個字段有一些設值函數
  2. 類無任何反作用(如:操做渲染html的append、往localstorage寫東西、init調用接口、多處共享引用等)
  3. 很龐大的類(須要先做拆分優化)
  • 作法:
  1. 若是沒法拿到設置變化的值,就經過構造函數的參數傳入
  2. 在構造函數內部調用設值函數進行更新
  3. 移除全部的設置值的函數調用,改成new一個類
  4. 使用內聯函數手法消除設值函數。

tips:能夠批量操做多個設值函數。

  • 關鍵字:

設值替換爲new

以工廠函數取代構造函數

  • 時機:
  1. 構造函數每次都須要new關鍵字,又臭又長(我的觀點是這條不必,除非徹底忍受不了)
  2. 構造函數若是不是default導出的話,這個名字那就是固定的。有時候語義化不明顯
  3. 有時雖然都是調用同一個類。但所處環境不一樣,我調用意義就不一樣
  • 作法:
  1. 新建一個工廠
  2. 工廠調用並返回現有的構造函數
  3. 替換調用者
  4. 儘量縮小構造函數可見範圍(js中很難實現,可能只能藏的深一些)
  • 關鍵字:

工廠函數、調用類、替換調用

以命令取代函數手法

  • 時機:
  1. 在js中,體現爲又臭又長的還無法進行指責劃分的函數(多是它們都屬於同一部分邏輯,也多是由於內部寫法致使很差劃分)
  • 作法:
  1. 新建一個空的類
  2. 搬移函數手法將函數搬移到這個新的類
  3. 給類改個有意義的名字,若是沒什麼好名字就給命令對象的實際具體執行的函數起一個通用的名字,如:execute或者call
  4. 將原函數做爲轉發函數,去構造類
  5. 將函數內的參數,改成構造時候傳入
  6. 若是能夠將其餘字段修改成只讀
  • 關鍵字:

新的類、函數搬家、原類轉發函數、構造入參、只讀

函數上移手法

  • 時機:
  1. 子類中有絕大部分都在複製某個函數
  2. 這些函數函數體都相同或者近似
  • 作法:
  1. 確保待提高函數的行爲徹底一致,不然須要先將他們一致化
  2. 檢查函數體內的全部調用和字段都能從超類中調用(若是有不一致則考慮先把它們提高)
  3. 檢查函數名字所有一致,不一致的話先將他們名字統一
  4. 將函數複製到超類中
  5. 逐一移除子類中的函數。每一次都要測試
  • 關鍵字:

函數體一致化、名字一致化、引用調用先行、提高函數、刪除重寫

字段上移手法

  • 時機:
  1. 子類中有絕大部分都在複製某個字段
  • 作法:
  1. 檢查該字段的全部使用點,確保是在一樣的方式被使用
  2. 若是名字不一樣,先把名字統一化
  3. 移動到父類,並確保子類都能訪問父類的這個字段
  4. 逐一移除子類的該字段
  • 關鍵字:

一樣方式使用、統一名字、字段上移、刪除子類字段

構造函數本體上移

  • 時機:
  1. 子類中有絕大部分都在複製某個構造函數函數
  2. 這些構造函數函數體都相同或者近似
  • 作法:
  1. 若是超類沒有構造函數,就先定義一個,全部子類增長super關鍵字
  2. 使用移動語句將子類的公共語句移動到super緊挨着以後
    三、提高到超類構造函數中
    四、逐一移除子類的公共代碼,若是這個值來自於調用者,則從super上傳給父類
    五、若是要上移的語句有基於子類的字段而設置初始化的值的,查看是否能夠將這個字段上移,若是不能,則使用提煉函數語句,將這句提煉爲一個函數,在構造函數內調用他
    六、函數上移
  • 關鍵字:

構造函數內的語句上移

函數下移、字段下移

  • 時機:
  1. 超類中的函數(字段)只與一部分子類有關(這個範圍須要掌控好,我一般選擇若是使用超過三分之二的,而且在剩餘的三分之一里面,這個函數/字段沒有反作用,就選擇上移,不然下移)
  • 作法:
  1. 將超類中的函數(字段)本體逐一複製到每個須要此函數(字段)的子類中
  2. 刪除超類中的函數(字段)
  • 關鍵字:

按需放置

以子類取代狀態碼

  • 時機:
  1. 一個類中有一些有必要的多態性被隱藏
  2. 根據某個狀態碼來返回不一樣的行爲
  • 作法:

直接繼承超類的

  1. 將類型碼字段進行封裝,改成一個get type()的形式
  2. 選擇其中一個類型碼,爲其建立一個本身類型的子類
  3. 建立一個選擇器邏輯(根據類型,選擇正確的子類)把類型碼複製到新的子類
  4. 測試
  5. 逐一建立、添加選擇邏輯的代碼
  6. 移除構造函數的這個參數
  7. 將與類型相關的代碼重構優化

間接繼承(經過類型的超類而非現有超類進行繼承)

  1. 用類型類包裝類型碼(以對象取代基本類型手法)
  2. 走直接繼承超類的邏輯,惟一不一樣的是,此次要繼承類型超類,而非當前超類
  • 關鍵字:

封裝類型碼、多態化、選擇子類的函數、移除類型參數

移除子類

  • 時機:
  1. 隨着程序發展子類原有行爲被搬離殆盡
  2. 本來是爲了適應將來,而增長子類,可是如今放棄了這部分代碼。
  3. 子類的用處太少,不值得保留
  • 作法:
  1. 檢查子類的使用者,是否根據不一樣子類進行處理
  2. 若是處理了則將處理函數封裝爲一個函數,並將他們搬移到父級
  3. 新建一個字段在超類,用以表明子類的類型
  4. 將選擇哪一個類來實例化的構造函數搬移到超類
  5. 逐步搬移全部的類型
  6. 將本來的類型處理改成使用新建的字段進行判斷處理
  7. 刪除子類
  • 關鍵字:

工廠函數取代子、類型提煉、檢查類型判斷

提煉超類

  • 時機:
  1. 兩個類在作相似的事情
  2. 兩個類隨着程序發展,有一些共同部分須要合併到一塊兒
  • 作法:
  1. 新建超類(可能已經存在)
  2. 調整構造函數(從數據開始)
  3. 調整子類須要的字段
  4. 將多個子類內共同的行爲複製到超類
  5. 檢查客戶端代碼。考慮是否調整爲超類
  • 關鍵字:

相同事情搬移到超類

以委託取代子類

  • 時機:
  1. 類只能繼承一個,沒法多繼承
  2. 繼承給類引入了緊密的關係(超類、子類耦合嚴重)
  • 作法:
  1. 使用以工廠函數取代構造函數將子類封裝
  2. 建立一個委託類、接受全部子類的數據,若是用到了超類,則以一個參數指代超類
  3. 超類中增長一個安放委託類的字段
  4. 增長一個建立子類的工廠,讓他初始化超類中的委託字段
  5. 將子類中的函數搬移至委託類,不要刪除委託代碼(若是用到了其餘元素也要一併搬離)
  6. 若是這個函數被子類以外使用了,把留在子類的委託移動到超類中,並加上衛語句,檢查委託對象初始化
  7. 若是沒有其餘調用者,使用移除死代碼手法去掉沒人使用的委託代碼
  8. 測試
  9. 重複567。直到全部函數都搬到了委託類
  10. 找到調用子類的地方,將其改成使用超類的構造函數
  11. 去掉子類
  • 關鍵字:

工廠函數初始化類、委託類、全部子類數據搬移至委託類、超類增長委託類的字段、子類函數搬移到委託類、刪除子類

以委託取代超類手法

  • 時機:
  1. 錯誤的繼承(如父子不是同一個意義的東西,可是子還想要用超類的一些字段)
  2. 超類不是全部方法都適用於子類
  • 作法:
  1. 在子類中建立一個屬性,指向新建的超類實例
  2. 子類中用到超類的函數,爲他們建立轉發函數(用上面的屬性)
  3. 去除子類與超類的繼承關係
  • 關鍵字:

子類屬性指向超類、轉發函數、去除繼承

讀後感:

收穫最大的莫過於感嘆做者過於謹慎,震驚於做者重構能力之強,對代碼重構理解程度之深,雖然做者有一些「墨跡」,但不能否認,這是極佳的一種方式,雖然工做中咱們不得已沒有那麼多時間去這麼小的步子,咱們能夠步子稍微大一些,當遇到問題時,在回滾並放慢步子。

第二點收穫就爲做者對於代碼好壞的定義,好的代碼就是讓人可以理解,可以讓人很快的找到本身要修改的地方,並能夠高效的規避報錯風險。雖然前期投入時間可能會多一些,但後期的效果倒是讓人可以驚訝的,正如做者所說的:清晰的代碼更容易理解,使你可以發現更深層次的設計問題,從而造成積極正向的循環。

做者一直在強調,重構是一步一步改進的,不是說一會兒就要如何如何,不僅是說單次改進過程要小幅度多測試,也是在說咱們不必定要將代碼中全部都實現到近乎完美的地步,而是應該抉擇一個代碼重構與真實狀況的平衡點,這和大刀闊斧直談架構重構的也不同,代碼是在不斷構築-設計中保持本身的新鮮性,直談大型架構重構,只能是笑談,畢竟架構爲設計、編寫。而直接重構整個架構,除非你想被老闆炒魷魚了。

最後本章做者使用了不少的手法,雖然都只是一些經常使用的什麼提煉函數啊、內聯變量啊之類的,但到處又透露着咱們須要學習的!

重構是爲了代碼能被人讀懂(所謂什麼更好擴展啊、更好的設計模式啊、結構化啊等等等等都是爲了這點。因此我統歸爲爲了能讀懂)能夠選擇犧牲一些(不是說能夠徹底忽略了性能),畢竟在現代瀏覽器、打包工具的加持、緩存的加持下,肉眼看到的問題以及咱們思考的問題也許已經被各類加持下悄悄消失了

從長久來看,重構對於往後的維護往後的開發,隨着時間的流逝確定是一個正收益,可是短時間來講可能要影響咱們一些,咱們要權衡好這些點之間的平衡,畢竟工做是爲了賺錢,公司也是爲了盈利,不可能給咱們無限時間去搞這些,做者同時也提出了重構並非工做日誌中的某一個任務的時機,主要體如今:新功能開發時、爲了代碼可讀性、代碼整合、有計劃的重構代碼以及堅持長期的重構以及review的重構。能夠說是隨時隨地均可以重構,但也不是任何地方任什麼時候機均可以重構,咱們要利用好測試的套件,保證原效果的前提下,結合實際狀況,多維度思考,即便閱讀事後,也應該時常翻開這本書,進行反覆閱讀,以提醒本身。

在本書中我學到了如何甄別壞的代碼,以及怎麼處理他們,學到了開發中應該測試先行以及一些重構的基本知識。

不過做爲jser,我我的以爲雖然做爲一本通用型書籍,確實應該不摻雜不少的語法,不過既然選定了js,自動化重構這塊其實怎麼說呢,寫的都是IDE。可是js中並無這類工具。而後做者也沒有說在js下應該能夠藉助某些功能來幫助重構。因此這塊仍是一片空白,雖然這種事理應本身去研究。可是仍是想免費更好嘛

相關文章
相關標籤/搜索