1. 程式人生 > >重構改善既有的程式碼設計(程式碼壞的味道)

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

目錄

重構

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

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語句常常根據型別碼進行選擇,你要的是“與該型別碼相關的函式或類

Parallel Inheritance Hierarchies(平行繼承體系)

Shotgun Surgery的特殊情況,在這種情況下,每當你為某個類增加一個子類,也必須為另一個類相應的增加一個子類。如果你發現某個繼承體系的名稱字首和另一個繼承體系名稱字首完全相同,便是聞到了這種壞味道

消除這種重複性的一般策略是:讓一個繼承體系的例項引用另一個繼承體系的例項。再運用Move Method 和Move Field,就可以就將引用端的繼承體系消弭於無形

Lazy Class(冗贅類)

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

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

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

不要過分對的為未來考慮

如果你的某個抽象類其實沒有太大作用,請運用Collapse Hierarchy。不必要的委託可運用Inline Class除掉。如果函式的某些引數違背用上,可對它實施Remove Parameter。如果函式名稱帶有多餘的抽象意味,應該對它實施rename Method,讓它現實一點

Temporary Field (令人迷惑的暫時欄位)

某個例項變數僅為某種特定的情況而設。這樣的程式碼讓人不易理解,因為你通常認為物件在所有時候都需要它的所有變數,在未被使用的情況下猜測當初其設定目的,會讓你發瘋的。把所有和這個變數相關的程式碼新建一個類放入

Message Chains(過度耦合的訊息鏈)

如果你看到使用者一個物件請求另一個物件,然後後者請求另一個物件,然後再請求另一個物件這就是訊息鏈

先觀察訊息鏈最終得到的物件用來幹什麼的,看看能否以Extract Method 把使用該物件的程式碼提煉到一個獨立的函式中再運用Move Method 把這個函式推入訊息鏈

Middle Man(中間人)

人們可能過度運用委託,會有某個介面有一半的函式都委託給其他類,這樣就是過度運用, 使用Remove Middle Man直接和真正負責的物件打交道

Inappropriate Intimac(狎暱關係)

  • 過分狎暱的類必須拆散. 你可以採用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(過多的註釋)

引用作者的一句話"當你感覺需要撰寫註釋,請先嚐試重構,試著讓所有註釋都變得多餘。"

並不是說寫註釋不好,而是當你寫一段很長的註釋來說明程式碼邏輯的時候,說明這段程式碼真的很糟糕,你就要考慮重構了。