重構改善既有的代碼設計(代碼的壞味道)

壞的味道:指的是應該被修改,被重構的代碼,不具備可讀性,複用性,判斷邏輯複雜,冗餘代碼。應該使用各類重構的手法去改變它!

Duplicated Code(重複代碼)

若是你在一個以上的地點看到相同的程序結構,那麼能夠確定的:設法將他們合而爲一,程序會變得更好。
  • 同一個類的兩個函數含有相同的表達式
  • 兩個互爲兄弟子類內含相同表達式
  • 若是兩個絕不相關的類出現Duplicated Code 應該考慮對其中一個將重複代碼提煉到一個獨立類種,而後在另外一個類內使用這個新類。

Long Method(過長函數)

咱們應當遵循這樣一條原則:每當感受須要以註釋來講明點什麼的時候,咱們就須要說明的東西寫進一個獨立的函數中,並以器用途(而非實現手法)命名。
  • 咱們能夠對一組甚至短短一行代碼作這件事。哪怕替換後的函數調用動做比函數自身還長,只要函數名稱可以解釋其用途,咱們也該絕不猶豫得那麼作。關鍵不在於函數得長度,而在於函數:「作什麼」和「如何作」之間得語義距離。
  • 如何肯定該提煉哪一段代碼呢?一個很好得技巧是:尋找註釋。它們一般能指出代碼用途和實現手法之間的語義距離。若是代碼前方有一行註釋,就是再提醒你L能夠將這段代碼替換成一個函數,並且能夠在註釋得基礎上給這個函數命名。就算只有一行代碼,若是他須要以註釋來講明,那也值得將它提煉導獨立函數去。
  • 條件表達式和循環經常也是提煉得信號。可使用Decompose Conditional處理條件表達是。至於循環,你應該將循環和期內的代碼提煉導一個獨立的函數中

Large Class(過大的類)

  • 若是類中有太多的實例變量,能夠將幾個變量提煉至新類內。提煉時應該選擇類內彼此相關的變量,將它們放在一塊兒
  • 類內若是有太多代碼,把多餘的東西消弭於類內部。若是有五個「百行代碼」,它們之中不少代碼都相同,那麼或許你能夠把它們變成五個「十行函數」和十個提煉出的「雙行函數」。

Long Parameter List(過長的參數列)

若是已有的對象發出一條請求就能夠取代一個參數,那麼你應該激活重構手法Replace Paramter with Method。在這裏,「已有的對象」可能時函數所屬類內一個字段,也多是另外一個參數。你也能夠運用Preserve Whole Object未來自同一個對象的一堆數據收集起來,並以該對象替換它們。若是某些數據缺少合理的對象歸屬,可使用Introduce Parameter Object 爲它們製造出一個「參數對象」。安全

Divergent Change(發散式變化)

一旦須要修改,咱們但願可以跳到系統的某一點, 只在該處作修改。若是不能作到這點,你就嗅出兩種相關的刺鼻味道中的一種了。
  • 針對某一外界變化的全部相應修改,都只應發生在單一類中,而這個新類內的全部內容都應該反應此變化,爲此,你應該找出某特定緣由而形成的全部變化,而後運用Extract Class將它們提煉導另外一個類中。

Shotgun Surgery(散彈式修改)

遇到某種變化,你都必須在不一樣類內作出許多小修改,你所面臨的壞味道就是shotgun Surgery,若是須要修改的代碼散佈四處,你不但很難找到它們,也很容易忘記某個重要的修改
  • 這種狀況下應該使用Move MethodMove Field把全部須要修改的代碼放進同一個類。若是眼下沒有合適的類能夠安置這些代碼,就創造一個。一般能夠運用inline class把一系列相關行爲放進同一個類。
  • Divergent change 是指「一個類受多種變化影響」,shotgun surgery則是指「一種變化引起多個類相應修改」。者兩種狀況下你都會但願整理代碼,使「外界變化」與「須要修改的類」趨於一一對應。

Feature Envy(依戀情結)

函數對某個類的興趣高過對本身所處類的興趣,一般焦點即是數據,某個函數爲了計算某個值,從另外一個對象那調用幾乎半打的額取值函數。
  • 療法顯而易見:把這個函數移至另外一個地點。
  • 一個函數每每會用到幾個類的功能,那麼它究竟該被置於何處呢?咱們的原則是判斷哪一個類擁有最多被此函數使用的數據,而後把這個函數和那些數據擺在一塊兒。

Data Clumps(數據泥團)

  • 兩個類中相同的字段、許多函數簽名中相同的參數。這些老是綁在一塊兒出現的數據真應該擁有屬於它們本身的對象
  • 一個好的評判方法是:刪掉衆多數據中的一項。這麼作,其餘數據有沒有於是失去意義?若是它們不在有意義,這就是個明確信號:你應該爲它們產生一個新對象

Primitive Obsession(基本類型偏執)

對象的一個極大的價值在於:它們模糊(甚至打破)了橫亙於基本類型數據和體積較大類之間的界限
  • 能夠運用Replace Data Vlaue with Object將本來單獨存在的數據值替換爲對象
  • 若是想要替換的數據值是類型碼,而它並不影響行爲,則能夠運用Replace type Code with class 將它替換
  • 若是有與類型碼有關的條件表達式,可運用 Replace Type Code with Subclass 或 Replace Type Code with State/Strategy

Switch Statements(switch 驚悚現身)

大多數時候,一看到switch語句,你就應該考慮以多態來替換它。問題是多態應該出如今哪兒?switch語句經常根據類型碼進行選擇,你要的是「與該類型碼相關的函數或類session

Parallel Inheritance Hierarchies(平行繼承體系)

Shotgun Surgery的特殊狀況,在這種狀況下,每當你爲某個類增長一個子類,也必須爲另外一個類相應的增長一個子類。若是你發現某個繼承體系的名稱前綴和另外一個繼承體系名稱前綴徹底相同,即是聞到了這種壞味道

消除這種重複性的通常策略是:讓一個繼承體系的實例引用另外一個繼承體系的實例。再運用Move Method 和Move Field,就能夠就將引用端的繼承體系消弭於無形app

Lazy Class(冗贅類)

若是一個類的所得不值其身價,它就該消失

若是某些子類沒有作足夠的工做,試試Collapse Hierarchy。對於幾乎沒用的組件,你應該以Inline Class對付它們ide

Speculative Generality(誇誇其談的將來性)

不要過度對的爲將來考慮
若是你的某個抽象類其實沒有太大做用,請運用 Collapse Hierarchy。沒必要要的委託可運用 Inline Class除掉。若是函數的某些參數違背用上,可對它實施 Remove Parameter。若是函數名稱帶有多餘的抽象意味,應該對它實施 rename Method,讓它現實一點

Temporary Field (使人迷惑的暫時字段)

某個實例變量僅爲某種特定的狀況而設。這樣的代碼讓人不易理解,由於你一般認爲對象在全部時候都須要它的全部變量,在未被使用的狀況下猜想當初其設置目的,會讓你發瘋的。把全部和這個變量相關的代碼新建一個類放入。函數

Message Chains(過分耦合的消息鏈)

若是你看到用戶一個對象請求另外一個對象,而後後者請求另外一個對象,而後再請求另外一個對象這就是消息鏈

先觀察消息鏈最終獲得的對象用來幹什麼的,看看可否以Extract Method 把使用該對象的代碼提煉到一個獨立的函數中再運用Move Method 把這個函數推入消息鏈code

Middle Man(中間人)

人們可能過分運用委託,會有某個接口有一半的函數都委託給其餘類,這樣就是過分運用, 使用 Remove Middle Man ,直接和真正負責的對象打交道

Inappropriate Intimacy(狎暱關係)

  • 過度狎暱的類必須拆散. 你能夠採用Move MethodMove Field 幫它們劃分界限, 從而減小狎暱行徑. 你能夠能夠看看是否能夠運用Change Bidirectional Association to Unidirectional (將雙向關聯改成單向關聯)讓其中一個類對另外一個斬斷情絲.
  • 若是兩個類實在是情投意合, 能夠運用Extract Class 把二者的共同點提煉到一個安全的地點,讓它們坦蕩地使用這個新類. 或者也能夠嘗試運用Hide Delegate讓另外一個類來爲它們傳遞相思情.
  • 繼承每每形成過分親密, 由於子類對超類的瞭解老是超過超類的主管願望. 若是你覺的該讓這個孩子獨立生活了, 請運用Replace Inheritance with Delegation 讓它離開繼承體系.

Alternative Class with Different Interfaces(殊途同歸的類)

若是兩個函數作同一件事,卻有着不一樣的簽名,請運用Rename Method根據它們的用途從新命名。但這每每不夠,請反覆運用 Move Method將某些行爲移入類,直到2者的協議一致爲止。若是你必須反覆而贅餘的移入代碼才能完成這些,或許可運用Extract SuperClass對象

Incomplete Library Class(不完美的類庫)

若是隻想修改類庫的一兩個函數,能夠運用Introduce Foreign Method 若是想要添加一大堆額外行爲,就得運用Introduce Local Extension繼承

Data Class(純稚的數據類)

所謂Data Class是指:它們擁有一些值域(fields),以及用於訪問(讀寫〕這些值域的函數,除此以外一無長物。
  • 這樣的classes只是一種「不會說話的數據容器」,它們幾乎必定被其餘classes過份細瑣地操控着。這些classes早期可能擁有public值域,果然如此你應該在別人注意到它們以前,馬上運用Encapsulate Field (封裝值域)將它們封裝起來。若是這些classes內含容器類的值域(collection fields),你應該 檢査它們是否是獲得了恰當的封裝;若是沒有,就運用 Encapsulate Collection(封裝羣集) 把它們封裝起來。對於那些不應被其餘classes修改的值域,請運用 Remove Setting Method(移除設置函數)。
  • 而後,找出這些「取值/設值」函數(getting and setting methods)被其餘classes運用的地點。嘗試以Move Method(搬移函數) 把那些調用行爲搬移到Data Class來。若是沒法搬移整個函數,就運用 Extract Method(提煉函數) 產生一個可被搬移的函數。不久以後你就能夠運用Hide Method (隱藏某個函數)把這些「取值/設值」函數隱藏起來了。

Refused Bequest(被拒絕的遺贈)

指的是一個子類,不須要父類中的過多方法

這樣咱們能夠爲這個子類建立一個兄弟類,把父類中不須要的方法下移到兄弟類中去。 若是子類複用了超類的行爲(實現),卻又不肯意支持超類的接口。不過即便不肯意繼承接口,也不要胡亂修改繼承體系,應該運用Replace Inheritance with Delegation 來達到目的接口

Comments(過多的註釋)

引用做者的一句話"當你感受須要撰寫註釋,請先嚐試重構,試着讓全部註釋都變得多餘。"

並非說寫註釋很差,而是當你寫一段很長的註釋來講明代碼邏輯的時候,說明這段代碼真的很糟糕,你就要考慮重構了。 ci

相關文章
相關標籤/搜索