狗 S 同樣的代碼!快,重構我!

圖片

來源:jianshu.com/p/3f04b6aebad2算法


  • (一)重構原則
  • (二)代碼的壞味道
  • (三)從新組織函數
  • (四)在對象之間搬移特性
  • (五)從新組織數據
  • (六)簡化條件表達式
  • (七)簡化函數調用
  • (八)處理歸納關係

(一)重構原則

一、何謂重構

對軟件內部結構的一種調整,目的是在不改變軟件可觀察行爲的前提下,提升其可理解性,下降其修改爲本。編程

另外一種解釋是:使用一系列重構手法,在不改變軟件可觀察行爲的前提下,調整其結構。數組

重構不止是代碼整理,它提供了一種高效且受控的代碼整理技術app

二、爲什麼重構

  • 改進軟件設計:若是沒有重構,程序的設計會逐漸變質,重構很像是在整理代碼,你所作的就是讓全部的東西回到應處的位置上。
  • 幫助找到bug:對代碼進行重構,能夠深刻理解代碼的做爲,在搞清楚程序結構的同時,想不把bug揪出來都難。
  • 提升編程速度:良好的設計是快速開發的根本,改善設計、提升可讀性,減小錯誤,這些都是提升質量。

三、什麼時候重構

任何狀況下我都反對專門撥出時間進行重構。重構原本就不是一件應該特別撥出時間作的事情,重構應該隨時隨地的進行。ide

三次法則函數

第一次作某件事情是隻管去作;第二次作相似的事情會產生反感;第三次再作相似的事,你就應該重構測試

  • 最多見的重構時機是想給軟件添加新特性的時候;

重構的另個一原動力是:代碼的設計沒法幫助我輕鬆的添加所須要的特性編碼

  • 修改錯誤的時候
  • review代碼的時重構

間接層和重構url

計算機科學是這樣一門科學:它相信全部的問題均可以經過增長一個間接層來解決。spa

大多數重構都爲程序引入了更多的間接層,重構每每把大型的對象拆成多個小型的對象,把大型的函數拆成多個小型的函數。

可是,間接層是一把雙刃劍。每次把一個東西分紅兩份,你就須要多管理一個東西。若是某個對象委託另外一個對象,後者又委託另外一個對象,程序會越發難以閱讀。

什麼時候不應重構:有時候既有代碼實在太混亂,重構它還不如從新寫一個來得簡單。

重寫而非重構的一個清楚訊號是:現有代碼根本不能正常運做。

(二)代碼的壞味道

一、重複代碼

若是你在一個以上的地點看到相同的程序結構,那麼能夠確定:設法將它們合二爲一,程序會變得更好 。

  1. 同一個類中有相同的表達式:提煉出重複的代碼,而後讓兩個地方都調用被提煉出來的那一段代碼;
  2. 兩個互爲兄弟的子類內含有相同的表達式:提煉出相同代碼,將它推入超類內;
  3. 兩個絕不相干的類中出現:將重複的代碼提煉到一個獨立的類中。

二、過長的類

擁有短函數的對象活得比較好、比較長。 間接層所能帶來的所有利益——解釋能力、共享能力、選擇能力——都是由小型函數支持的。

每當感受須要以註釋來講明點什麼的時候,咱們就把須要說明的東西寫進一個獨立的函數中。

如何肯定提煉哪一段代碼?尋找註釋是一個很好的技巧。它們一般能指出代碼用途和實現手法之間的語義距離。若是代碼前方有一行註釋,就是提醒你:能夠將這段代碼替換成一個函數。

條件表達式和循環經常也是提煉的信號。

三、過大的類

若是想利用單個類作太多的事情,其內每每就會出現太多實力變量。

類內若是有太多代碼,也是代碼重複、混亂病最終走向死亡的源頭。

四、過長參數列

太長的參數列難以理解,太多的參數會形成先後不一致、不容易使用,並且一旦你須要更多數據,就不得不修改它。若是將對象傳遞給函數,大多數修改都將沒有必要。

五、發散式變化

若是某個類常常由於不一樣的緣由在不一樣的方向上發生變化,那麼此時也許將這個對象分紅兩個會更好,這麼一來每一個對象就能夠只由於一種變化而須要修改。

六、散彈式修改

若是沒遇到某種變化,你都必須在許多不一樣的類內作出許多小修改,你所面臨的壞味道就是散彈式修改。若是須要修改的代碼散佈四處,你不但很難找到它們,也很容易忘記某個重要的修改。

把全部須要修改的代碼放進同一個類中,若是眼下沒有合適的類能夠安置這些代碼就創造一個。

七、依戀情結

對象技術的要點在於:將數據和對數據的操做行爲包裝在一塊兒

有一種經典的氣味是:函數對某個類的興趣高過對本身所處類的興趣。某個函數爲了計算某個值,從另外一個對象那調用幾乎半打的取值函數。

一個函數每每會用到幾個類的功能,那麼它該置於何處?咱們的原則是:判斷哪一個類擁有最大被此函數使用的數據,而後就把這個函數和那些數據放在一塊兒。

八、數據泥團

不少地方看到相同的三四項數據一塊兒出現。這些老是綁在一塊兒出現的數據應該擁有屬於他們本身的對象。

首先找到這些數據以字段形式出現的地方,將它們提煉到一個獨立的對象中。這麼作的直接好處是能夠將不少參數列縮短簡化函數調用。

九、基本類型偏執

對象的一個極大價值在於:它們模糊了橫旦與基本數據和體積較大的類之間的界限

對象技術的新手一般不肯意在小任務上運用小對象——結合數值和比重的money類、有一個起始值和一個結束值組成的range類。將本來單獨存在的數值替換成對象,從而走出傳統的洞窟,進入煊赫一時的對象世界。

十、switch驚悚現身

面向對象的一個最明顯的特徵是:少用switch語句

一看到switch語句,就應該考慮以多態來替換它。

若是隻是在單一函數中有些選擇實例,且並不想改動它們,那麼多態就有點殺雞用牛刀了。

十一、平行集成體系

每當你爲某個類增長一個子類,必須也爲另外一個類相應增長一個子類。

消除這種重複性的通常策略是:讓一個繼承體系的實例引用另外一個繼承體系的實例。

十二、冗餘類

某個類本來對得起本身的身價,但重構使它身形縮水,再也不作那麼多工做,這個時候請讓這個類莊嚴赴義吧。

1三、誇誇其談將來性

企圖以各類各樣的鉤子和特殊狀況來處理一些非必要的事情,這種懷味道就出現了。若是用到了那就值得去作,若是用不到那就不值得,只會擋你的路,因此把它挪開吧。

若是你的某個抽象類其實沒有起到太大的做用,函數上的某些參數未被使用...能夠移除它們了。

1四、使人迷惑的暫時字段

某個實例變量僅爲某種特定的狀況而設。這樣的代碼讓人不易理解。在變量未被使用的狀況下猜想當初其設置目的,會讓你發瘋的。

1五、過分耦合消息鏈

若是你看到用戶向一個對象請求另外一個對象,而後再向後者請求另外一個對象,而後再請求另個一對象........這就是消息鏈。採用這種方式,意味着客戶代碼將與查找過程當中的導航結構緊密耦合。一旦對象間的關係發生任何變化,客戶端就不得不作出相應的修改。

1六、中間人

封裝每每伴隨着委託。你也許會看到某個類接口有一半的函數都委託給其餘類,這樣就是過分運用。

1七、狎暱關係

有時會看到兩個類過於親密,話費太多的時間去探究彼此的private成分。過度狎暱的類必須拆散,幫它們劃清界線,從而減小狎暱行徑。

繼承每每形成過分親密,由於子類對超類的瞭解老是超事後者的主觀願望。若是你以爲該讓孩子獨立生活了,讓他離開繼承。

1八、殊途同歸的類

兩個函數作同一件事,卻有着不一樣的簽名。

1九、不完美的類庫

類庫函數構造的不夠好,又不能修改它們:

  1. 若是隻想修改類的一兩個函數,能夠引入外加函數。
  2. 若是想要添加一大堆額外行爲,創建一個新類包含這些額外行爲,讓其成爲子類。

20、純稚的數據類

純稚的數據類是指:它們擁有一些字段,以及用於訪問(讀寫)這些字段的函數,除此以外一無長物。

封裝public字段;

恰當封裝容器類字段;

移除不該修改的字段的設置函數;

提煉調用函數以隱藏取值/設值函數;

2一、被拒絕的遺贈

子類只運用了父類的一部分函數和數據。爲子類創建一個兄弟類,將全部用不到的字段/函數下移至兄弟類,保證超類的純粹;

2二、過多的註釋

註釋之因此存在是由於代碼很糟糕 。註釋的最高境界——代碼即註釋。

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

(三)從新組織函數

一、提煉函數

動機:看到一個過長的函數或者一段須要註釋才能讓人理解用途的代碼,將這段代碼放一個獨立的函數中;

作法

  • 創造一個新函數,根據這個函數的意圖來命名它;

    只要新函數的名稱可以以更好的方式昭示代碼意圖,你也應該提煉它。但若是想不到一個更有意義的名稱就別動

  • 將提煉的代碼從原函數複製到新建的目標函數中;

  • 將被提煉代碼段中須要讀取的局部變量,看成參數傳遞給目標函數;

  • 在源函數中,將被提煉代碼段替換爲目標函數調用。

二、內聯函數

一個函數的本體與名稱一樣清楚易懂。在函數調用點插入函數本體,而後移除該函數。

動機:

  • 一羣組織不甚合理的函數。你能夠將它們都內聯到一個大函數中,再從中提煉出組織合理的小型函數。
  • 使用的太多的間接層,使得系統中的全部函數都彷佛只是對另外一個函數的簡單委託,形成在委託動做之間暈頭轉向。

作法:

  • 檢查函數,肯定不具有多態;

    若是子類繼承了這個函數,就不要將此函數內聯,由於子類沒法複寫一個根本不存在的函數。

  • 找出這個函數的全部調用點;

  • 將這個函數的全部調用點都替換成函數本體。

三、內聯臨時變量

有一個臨時變量,只被一個簡單的表達是賦值一次,而它妨礙了其餘重構手法。將全部對該變量的引用動做,替換爲對它賦值的那個表達式自身

double basePrice = anOrder.basePrice();
return (base > 10000 );

替換爲:

return (anOrder.basePrice > 1000);

四、以查詢取代臨時變量

你的程序以一個臨時變量保存某一表達式的運算結果。將這個表達式提煉到一個獨立的函數中。將這個臨時變量的全部引用點替換爲對新函數的調用。此後,新函數就可被其餘函數使用。

double basePrice = quantity * timePrice;
if(basePrice > 1000){
    return basePrice * 09.5;
else {
    return basePrice * 0.98;
}

替換爲:

if(basePrice() > 1000){
    return basePrice * 09.5;
else {
    return basePrice * 0.98;
}
double basePrice(){
    return quantity * timePrice;
}

臨時變量只在所屬的函數中可見,若是把臨時變量替換爲一個查詢,那麼同一個類中的全部函數都將能夠得到這個份信息,這將帶給你極大的幫助,使你可以爲這個類編寫更清晰的代碼。

五、引入註釋性變量

你有一個複雜的表達式。將該複雜表達式(或其中一部分)的結果放進一個臨時變量,以此變量名稱來解釋表達式用途。

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

替換爲:

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 smothing
}

表達式有可能很是複雜難以理解。這種狀況下,臨時變量能夠幫助你將表達式分解爲比較容易管理的形式。在條件邏輯中,你能夠用這項重構將每一個條件子句提煉出來,以一個良好命名的臨時變量來解釋對應條件子句的意義。另外一種狀況是:在較長的算法中,能夠運用臨時變量來解釋每一步運算的意義。

六、分解臨時變量

你的程序有某個臨時變量被賦值超過一次,它既不是循環變量,也不被用於收集計算結果。針對每次賦值,創造一個獨立、對應的臨時變量。

double temp = 2 * (height + width);
System.out.println(temp);
temp = height * width;
System.out.println(temp);

替換爲:

double perimeter = 2 * (height + width);
System.out.println(perimeter);
double area = height * width;
System.out.println(area);

若是臨時變量被賦值超過一次,就意味着它們在函數中承擔了一個以上的責任。若是臨時變量承擔多個責任,它就應該被替換爲多個臨時變量。每一個變量只承擔一個責任,同一個臨時變量承擔兩件不一樣的事情會令代碼閱讀者糊塗

七、移除對參數的賦值

代碼對一個參數進行復制。以一個臨時變量取代該參數的位置。

int discount (int inputVal, int quantity, int yearToData){
    if(inputVal > 50) inputVal -= 2;
}

替換爲:

int discount (int inputVal, int quantity, int yearToData){
    int result = inputVal;
    if(inputVal > 50) result -= 2;
}

若是代碼的語義是按引用傳遞的,請在調用段檢查調用後是否還使用了這個參數。

八、替換算法

想要把某個算法替換爲另外一個更清晰的算法。將函數本體替換成爲另外一個算法。

String foundPerson(String[] people){
    for(int i = 0;i < people.length; i++){
        if(people[i].equals("Don")){
            return "Don";
        }
        if(people[i].equals("John")){
            return "John";
        }
        if(people[i].equals("Kent")){
            return "Kent";
        }
    }
    return "";
}

替換爲:

String foundPerson(String[] people){
    List candidates = Arrays.asList(new String[]{"Don""John""Kent"});
    for(int i = 0;i < people.length; i++){
        if(candidates.contains(people[i])){
            return prople[i];
        }
    }
    return "";
}

(四)在對象之間搬移特性

在對象設計過程當中,決定把責任放在哪兒是即便不是最重要的事,也是最重要的事之一。經常只使用搬移函數搬移字段簡單地移動對象行爲,就能夠解決這些問題。若是這兩個重構手法都須要用到,我會首先使用搬移字段,再使用搬移方法。若是一個類承擔了太多責任而變得臃腫不堪,這種狀況下會使用提煉類將一部分責任分離出去。若是一個類變得太不負責任,使用將類內聯化將它融入到另外一個類中。

一、搬移函數

你的程序中,有個函數與其所駐類以外的另個一類進行跟過的交流:調用後者或被後者調用。在該函數最長引用的類中創建一個有着相似行爲的新函數。將舊函數變成一個單純的委託函數,或者將舊函數徹底移除。若是一個類有太多行爲,或若是一個類與另外一個類有太多合做而高度耦合,就須要搬移函數。能夠是系統中的類更簡單

二、搬移字段

程序中,某個字段被其所駐類以外的另外一個類更多的用到。在目標類新建一個字段,修改原字段的全部用戶,令他們改用新字段

三、提煉類

某個類作了應該由兩個類作的事。創建一個新類,將相關字段和函數從就類搬到新類。

四、將類內聯化

某個類沒有作太多的事情,不在承擔足夠責任,再也不有的那單獨存在的理由。將這個類的全部特性搬移到另外一個類中,而後移除原類。

五、隱藏「委託關係」

客戶經過一個委託類來調用另外一個對象。在服務類上創建客戶所須要的全部函數,用來隱藏委託關係。封裝意味每一個對象都應該少了解系統的其餘部分。一旦發生變化,須要瞭解這一變化的對象就會比較少。若是某個客戶先經過服務對象的字段獲得另外一個對象,而後調用後者的函數。那麼客戶就必須知曉這一層委託關係。萬一委託關係變化,客戶也要相應變化。

六、移除中間人

某個類作了過多的簡單委託。讓客戶直接調用委託類。每當客戶要使用手委託類的新特性時,你就必須在服務端添加一個簡單委託函數。隨着受委託類的特性愈來愈多,這一過程會讓你很痛苦。

七、引入外加函數

你須要爲提供服務的類增長一個函數,但你沒法修改這個類。在客戶類中創建一個函數,並以第一參數形式傳入一個服務類實例。

 Date newStart = new Date(year, month, date + 1);

替換爲:

Date newStart = nextDay(nowDate);
private static Date nextDay(Date arg){
    retrun new Date(arg.getYear(), arg.getMonth(), arg.getDate() + 1);
}

若是能夠修改源碼,你能夠自行添加一個新函數;若是不能,你就得在客戶端編碼,補足你要的那個函數

八、引入本地擴展

你須要爲服務類踢狗一些額外函數,但你沒法修改這個類。創建一個新類,使它包含這些額外函數。讓這個擴展品成爲源類的子類或包裝類。

(五)從新組織數據

一、自封裝字段

直接訪問一個字段。爲這個字段創建取值/設值函數,而且只以這些函數來訪問字段。

private int low, high;
boolean includes(int arg){
    retrun arg >= low && arg <= high;
}

替換爲:

private int low, high;
boolean includes(int arg){
    retrun arg >= getLow() && arg <= getHigh();
}
int getLow(){
    retrun low;
}
int getHigh(){
    return high;
}

在「字段訪問方式」這個問題上,存在兩種大相徑庭的觀點:

  1. 在該變量定義所在的類中,你能夠自由的訪問。
  2. 即便在這個類中你也應該只使用訪問函數間接訪問。間接訪問的好處是:子類能夠經過複寫一個函數而改變獲取數據的途徑;它支持更靈活的數據管理方式,例如延遲初始化。

二、以對象取代數據值

你有一個數據項,須要與其餘數據和行爲一塊兒使用纔有意義。將數據項變爲對象。一開始你肯能會用一個字符串來表示「電話號碼」概念,可是隨後你會發現,電話號碼須要「格式化」、「區號」之類的行爲。這時候就須要爲帶替換的數值新建一個類。

三、將值對象改成引用對象

你從一個類衍生出許多彼此相等的實例,但願將它們替換爲同一個對象。將這個值對象變成引用對象。

四、將引用對象改成值對象

你有一個引用對象,很小且不可改變,並且不易管理。將它變成一個值對象。

五、以對象取代數組

你有一個數組,其中的元素各自表明不一樣的東西。以對象替換數組。對於數組中的每一個元素,以一個字段來表示

六、複製「被監視數據」

你有一些領域數據置身GUI控件中,而領域函數須要訪問這些數據。將該數據複製到一個領域對象中。創建一個Observer模式,用以同步領域對象和GUI對象內的重複數據。

七、將單向關聯改成雙向關聯

兩個類都須要使用對方特性,但其間只有一條單向鏈接。添加一個反向指針,並使修改函數可以同時更新兩條鏈接。

八、將雙向關聯改成單向關聯

兩個類之間有雙向關聯,但其中一個類現在再也不須要另外一個類的特性。去除沒必要要的關聯。

九、以字面常量取代魔數

你有一個字面數值,帶有特別含義。創造一個常量,根據其意義爲它命名,並將上述的字面數值替換爲常量。

十、封裝字段

你的類中存在一個public字段。將它聲明爲private,並提供相應的訪問函數。

十一、封裝集合

有個函數返回一個集合。讓這個函數返回該集合的一個只讀副本,並在這個類中提供添加/移除集合元素的函數。

(六)簡化條件表達式

一、分解條件表達式

有一複雜的條件語句。從if、then、else三個段落中分別提煉出獨立函數。

二、合併表達式

你有一系列條件測試,都獲得相同結果。將這些測試合併爲一個條件表達式,並將這個條件表達式提煉成一個獨立函數。

三、合併重複的條件代碼

在表達式的每一個分支上都執行了相同的一段代碼。將這段重複代碼搬移到條件表達式以外。

四、移除控制標記

在一系列布爾表達式中,某個變量帶有」控制標記」的做用。以break/return語句取代控制標記。

五、以多態取代條件表達式

有個條件表達式根據對象類型的不一樣而選擇不一樣的行爲。將這個條件表達式的每一個分支放進一個子類內的覆寫函數中,而後將原始函數聲明爲抽象函數

(七)簡化函數調用

一、函數更名

函數的名稱未能揭示其用途。修改函數名稱。

二、添加參數

某個函數須要從調用端獲得更多信息。爲此函數添加一個對象參數,讓該對象帶僅函數所需信息。

三、移除參數

函數本體再也不須要某個參數。去除參數。

四、分離查詢函數和修改函數

某個函數既返回對象狀態值,又修改對象值。創建兩個不一樣函數,其中一個負責查詢,另外一個負責修改。

五、令函數攜帶參數

若干函數作了相似的工做,但在函數本體中包含了不一樣的值。創建單一函數,以參數表達那些不一樣的值。有這樣兩個函數:它們作着相似的工做,但因少數幾個值導致行爲略有不一樣。在這種狀況下,你能夠將這些各自分離的函數同一塊兒來,並經過參數來處理那些變化狀況,用以簡化問題。

六、以明確函數取代參數

你有一個函數,其中徹底取決於參數值而採用不一樣行爲。針對該參數的每個可能值,創建一個獨立函數。若是某個參數有多種可能的值,而函數內又以條件表達式檢查這些參數值,並根據不一樣參數值作出不一樣的行爲,那麼就應該使用本項重構。

七、保持對象完整

從某個對象中取出若干值,將它們做爲某一次函數調用時的參數。改成傳遞整個對象。

八、以函數取代參數

對象調用某個函數,並將所得結果做爲參數,傳遞給另外一函數,而接受該參數的函數自己也可以調用前一個函數。讓參數接受者去除該參數,直接調用前一個函數。

九、引入參數對象

某些參數老是很天然的同時出現。以一個對象取代這些參數。

十、移除設值函數

類中某個字段在對象建立時被設值,而後再也不改變。去掉該字段的全部設值函數。

十一、隱藏函數

某個函數,歷來沒有被其餘任何類用到 。將函數修改成private。

12 、以工廠函數取代構造函數

但願在建立對象時不只僅是作簡單的建構動做 。將構造函數替換爲工廠函數。

(八)處理歸納關係

一、字段上移

兩個子類擁有相同的字段。將該字段移至超類。

2 、函數上移

有些函數在各子類中產生徹底相同的結果。將該函數移至超類。

3 、構造函數本體上移

各個子類中有一些構造函數本體幾乎徹底一致 。在超類中新建一個構造函數,並在子類構造函數中調用它。

四、函數下移

超類中的某個函數只與部分(而非所有)子類用到。將函數移到相關的子類中。

五、字段下移

超類中的某個字段只被部分(而非所有)子類用到。將字段移到須要它的子類中。

六、提煉子類

類中的某些特性只被某些(而非所有)實例用到。新建一個子類,將上述部分的特性移到子類中。

七、提煉超類

兩個類有類似特性。爲這兩個類創建一個超類,將相同特性移至超類。

八、提煉接口

若干客戶使用類接口中的同一子集,或兩個類的接口有部分相同。將相同的子集提煉到一個獨立接口中。

九、摺疊繼承體系

超類和子類之間無太大區別。將它們合爲一體。

十、塑造模板函數

子類中某些函數以相同順序執行相似操做,但各操做細節略有不一樣。將操做放進獨立函數(保持簽名相同),而後將它們移至超類。

十一、以委託取代繼承

某個子類只使用超類接口中的一部分或根本不須要繼承而來的數據。子類新建字段保存超類,調整子類函數爲委託超類,取消繼承關係。

十二、以繼承取代委託

你在兩個類中使用委託關係,並常常爲整個接口編寫許多極簡單的委託函數。讓委託類繼承受託類。

圖片

相關文章
相關標籤/搜索