1. 程式人生 > >《重構:改善既有程式碼的設計》-學習筆記二(+實戰解析)

《重構:改善既有程式碼的設計》-學習筆記二(+實戰解析)

我不是個偉大的程式設計師;我只是個有著一些優秀習慣的好程式設計師而己

本人比較直接,不說虛的,直接上乾貨。

 目錄

  Long Parameter List(過長引數列)  Divergent Change(發散式變化)  Shotgun Surgery(散彈式修改)  Feature Envy(依戀情結)  Data Clumps(資料泥團)  Primitive Obsession(基本型別偏執)  Switch Statements(switch驚悚現身)

Long Parameter List(過長引數列)

上一節有提過,當函式的入參過多時,可以用第三招,引數物件化,把引數封裝成物件,然後引數物件當成函式的入參,達到減少引數的作用。除了引數物件化,還可以使用另一種方法來處理。這種方法叫做:Replace Parameter with Method(以函式取代引數)

優化思路

前提,這個引數是隻被賦值一次的1、如果有必要,將引數的計算過程提煉到一個獨立函式中。2、將函式內有使用引數的地方替換成獨立函式。3、每次替換後,測試。4、全部替換完成後,最後把這個引數刪除。eg:未優化的程式碼
public double getPrice() {

      int basePrice = _quantity * _itemPrice;
      int discountLevel;
      if (_quantity > 100) discountLevel = 2;
      else discountLevel = 1;
      double finalPrice = discountedPrice (basePrice, discountLevel);
      return finalPrice;

  }

  private double discountedPrice (int basePrice, int discountLevel) {
      if (discountLevel == 2) return basePrice * 0.1;
      else return basePrice * 0.05;
  }

優化 1,2,3步驟 優化引數basePrice

public double getPrice() {

      int basePrice = getBasePrice();
      int discountLevel;
      if (_quantity > 100) discountLevel = 2;
      else discountLevel = 1;
      double finalPrice = discountedPrice ( discountLevel);
      return finalPrice;

  }

  private double discountedPrice ( int discountLevel) {
      if (discountLevel == 2) return getBasePrice() * 0.1;
      else return getBasePrice() * 0.05;
  }
  
  private int getBasePrice(){
    return _quantity * _itemPrice;
  }

優化4步驟 去掉引數basePrice

public double getPrice() {

      int discountLevel;
      if (_quantity > 100) discountLevel = 2;
      else discountLevel = 1;
      double finalPrice = discountedPrice ( discountLevel);
      return finalPrice;

  }

  private double discountedPrice ( int discountLevel) {
      if (discountLevel == 2) return getBasePrice() * 0.1;
      else return getBasePrice() * 0.05;
  }
  
  private int getBasePrice(){
    return _quantity * _itemPrice;
  }

優化1,2,3,4步驟,去掉discountLevel引數,獨立函式返回值要為discountLevel 最後賦值的值

public double getPrice() {

      double finalPrice = discountedPrice ();
      return finalPrice;

  }

  private double discountedPrice () {
      if (getDiscountLevel() == 2) return getBasePrice() * 0.1;
      else return getBasePrice() * 0.05;
  }
  
  private double getDiscountLevel(){
      if (_quantity > 100) return 2;
      else return 1;
        
  }  
  
  private double getBasePrice(){
    return _quantity * _itemPrice;
  }

從上述程式碼可看出,getPrice主函式finalPrice引數已經可以直接優化了。

public double getPrice() {

      return discountedPrice ();

  }
可以發現getPrice函式直接呼叫discountedPrice 函式,所以可用Inline Method(將函式內聯化)合併這兩個函式
public double getPrice() {
    if (getDiscountLevel() == 2) return getBasePrice() * 0.1;
      else return getBasePrice() * 0.05;

  }
  
  private double getDiscountLevel(){
      if (_quantity > 100) return 2;
      else return 1;
        
  }  
  
  private double getBasePrice(){
    return _quantity * _itemPrice;
  }
我們只關心主函式的計算過程,一些過程性的計算,像上述這樣,獨立函數出來。程式碼邏輯會十分清晰,可讀性很好。存在一個重要的例外。如果明顯不希望封裝的物件與主物件之間存在某種依賴關係,可以把引數資料從封裝物件中抽出來,當成函式的引數。也是合理的。但是要注意,當引數列太多或者引數變化頻繁時,就要考慮優化了。

Divergent Change(發散式變化)

你發現你想要修改的一個函式,卻必須同時修改諸多不相關的函式,例如,當你想要新增一個新的產品型別,你需要同步修改對產品查詢,顯示,排序的函式。有以上這些情況的話,就需要優化程式碼了針對某一外界 變化的所有相應修改,都只應該發生在單一class中,而這個新class內的所有內容都應該反應該外界變化。通過提煉類的方式,找出因著某特定原因而造成的所有變化,獨立類。問題原因:通常,這種發散式修改是由於程式設計結構不合理或者“複製-貼上式程式設計”。

優化思路

運用提煉類拆分類的行為。如果不同的類有相同的行為,你可以考慮通過繼承來合併類和提煉子類。效果:提高程式碼組織結構減少重複程式碼

Shotgun Surgery(散彈式修改)

-- 注意霰彈式修改 與 發散式變化 區別 : 發散式變化是在一個類受多種變化影響, 每種變化修改的方法不同, 霰彈式修改是 一種變化引發修改多個類中的程式碼;

優化思路

1、程式碼集中到某個類中 : 使用 Move Method(搬移函式) 和 Move Field(搬移欄位) 把所有需要修改的程式碼放進同一個類中;2、 程式碼集中到新建立類中 : 沒有合適類存放程式碼, 建立一個類, 使用 Inline Class(內聯化類) 方法將一系列的行為放在同一個類中;3、造成分散式變化 : 上面的兩種操作會造成 Divergent Change(分散式變化), 使用Extract Class 處理分散式變化;

Feature Envy(依戀情結)

函式對某個class的興趣高過對自己所處之 class的興趣。無數次經驗裡,我們看到某個函式 為了計算某值,從另一個物件那兒呼叫幾乎半打的取值函式。影響:資料和行為不在一處,修改不可控。解決方案:讓資料和行為在一起,通過 Extract Method(提煉函式)和Move Method(搬移函式)的方法來處理,這函式到該去的地方。例子:參考一個優秀博主提供的例子

優化思路

1、函式全部資料來自另外一個類      做法:將資料提煉到一個獨立函式中  Move method。2、函式部分資料來自另外一個類      做法:將“部分資料”提煉到一個函式中 Move method。3、函式的資料來自不同類      做法:將資料分類,分別提煉各自的獨立的函式,在將這些函式移到各自屬於的類中。

Data Clumps(資料泥團)

資料泥團指的是經常一起出現的資料,比如每個方法的引數幾乎相同,處理方式與過長引數列的處理方式相同,用Introduce Parameter Object(引入引數物件)將引數封裝成物件。

優化思路

1、觀察經常一起出現的資料;2、通過提煉類的方法,放到屬於他們的物件中;3、用物件來代替這些資料;4、編譯測試。eg:未優化程式碼例子參考一個優秀博主提供的例子
public class Car{
    // 賓士
    public void printBenz(String brand, String model, Integer price, double power) {
        printBasicInfo(brand, model, price, power);
        getTax(power, price);
    }

    // 寶馬
    public void printBmw(String brand, String model, Integer price, double power) {
        printBasicInfo(brand, model, price, power);
        getTax(power, price);
    }

    // 提煉列印基本資訊方法
    private void printBasicInfo(String brand, String model, Integer price, double power) {
        System.out.println("品牌" + brand);
        System.out.println("型號:" + model);
        System.out.println("動力:" + power);
        System.out.println("價格:" + price);
    }

    // 提煉計算稅費的方法
    private double getTax(double power, Integer price){
        double salePrice = price;
        if (price > 200000) {
            salePrice = price * 0.98;
        }

        if (power <= 1.6) {
            return salePrice * 0.05;
        } else {
            return salePrice * 0.1;
        }
    }
}

優化1,2步驟上面程式碼方法中,我們發現方法的引數大致相同,這時候我們可以用引數物件化來處理。
public class CarEntity {

    private String brand;
    private String model;
    private Integer price;
    private Double power;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Double getPower() {
        return power;
    }

    public void setPower(Double power) {
        this.power = power;
    }

}

優化3,4步驟

// 賓士
    public void printBenz(CarEntity carEntity) {
        printBasicInfo(carEntity);
        // 計算稅費
        getTax(carEntity);
    }

    // 寶馬
    public void printBmw(CarEntity carEntity) {
        printBasicInfo(carEntity);
        getTax(carEntity);
    }

    private void printBasicInfo(CarEntity carEntity) {
        System.out.println("品牌" + carEntity.getBrand());
        System.out.println("型號:" + carEntity.getModel());
        System.out.println("動力:" + carEntity.getPower());
        System.out.println("價格:" + carEntity.getPrice());
    }
    // 計算稅費
    private double getTax(CarEntity carEntity) {
        // 打折後價格
        double salePrice = carEntity.getPrice();
        if (carEntity.getPrice() > 200000) {
            salePrice = carEntity.getPrice() * 0.98;
        }

        if (carEntity.getPower() <= 1.6) {
            return salePrice * 0.05;
        } else {
            return salePrice * 0.1;
        }
    }
經過以上的優化,程式碼就更加健壯了。

Primitive Obsession(基本型別偏執)

寫程式碼時總喜歡用基本型別來當引數,而不喜歡用物件。當要修改需求和擴充套件功能時,複雜度就增加了。

優化思路

1、如果你有一組應該總是被放在一起的屬性或引數,可以用提煉類的方式來處理;2、如果你在引數列中看到多個基本型資料,可以引用引數物件;3、如果你發現自己正從array中挑選資料,可以用物件取代陣列。優化步驟1和2之前的例子說明了很多次,不再重複。優化步驟3用物件取代陣列:你有一個數組(array),其中的元素各自代表不同的東西。就可以用物件來表示陣列。eg:
String[] row = new String[3];
  row [0] = "Liverpool";
  row [1] = "15";

//物件取代陣列
  Performance row = new Performance();
  row.setName("Liverpool");
  row.setWins("15");
Performance 物件裡包含name屬性和wins屬性,且這兩個屬性被定義為private ,同時擁有get方法和set方法。

Switch Statements(switch驚悚現身)

面向物件程式的一個最明顯特徵就是:少用switch (或case)語句。從本質上說, switch語句的問題在於重複(duplication)。

優化思路

這種情況我們可以引用工廠 + 策略模式。用工廠把重複的switch提煉到一起構建成一個工廠類,策略模式把switch分支中執行的動作提煉成單獨的類。例子參考一個優秀博主提供的例子更多精彩內容,請等待後續更新。文章也同步到了部落格園:https://www.cnblogs.com/zenghw/p/9114428.html***************************************************************************

作者:小虛竹
歡迎任何形式的轉載,但請務必註明出處。
限於本人水平,如果文章和程式碼有表述不當之處,還請不吝賜教。

我不是個偉大的程式設計師,我只是個有著一些優秀習慣的好程式設計師而己


相關推薦

重構改善程式碼設計》-學習筆記+實戰解析

我不是個偉大的程式設計師;我只是個有著一些優秀習慣的好程式設計師而己本人比較直接,不說虛的,直接上乾貨。 目錄  Long Parameter List(過長引數列)  Divergent Change(發散式變化)  Shotgun Surgery(散彈式修改)  Feat

重構改善程式碼設計 讀書筆記

重構,絕對是程式設計師職業生活中最重要的事情之一,如果一個程式設計師從來沒做過重構,絕對不是合格的程式設計師;       如果一個人沒有聽說過《重構》這本書,那麼他一定不敢說自己是程式設計師;如果一個人沒有閱讀過《重構》這本書,那麼很難想象他會是一名優秀的程式設計師

重構改善程式碼設計》第一章整理筆記

1.2重構第一步:測試環境 重構前,先檢查自己是否有一套可靠的測試機制,這些測試必須有自我檢驗能力 也就是在做修改之前,先設計一些測試資料,用於測試修改完的程式碼是否有bug 1.3 分解並重組 儘量將大的程式碼塊分解成小的程式碼塊 先找出程式碼中整塊的邏輯程式

重構改善程式碼設計這本書怎麼樣

重構:改善既有程式碼的設計 噹噹上購買關於重構:改善既有程式碼的設計 評論讀後感:空白太多了 排版太大方了 感覺紙不用錢讀後感:都買了幾次了書還沒運到的,我無語讀後感:給同事買的,書寫的很好。不過有的地方俺不認同 呵呵。讀後感:最近剛看完這本書,書的內容不錯,有許多可以借鑑的

重構-改善有的程式碼設計-重新組織函式6-2

6.6.分解臨時變數(Split Temporary Variable) 6.7.移除對引數的賦值(Remove Assignments to Parameter) 6.8.以函式物件取代函式(Replace Method with

重構-改善有的程式碼設計-重新組織函式6-1

6.1.提煉函式(Extract Mothod) 動機:長度不是問題,關鍵在於函式名稱和函式本體之間的語義距離。如果提煉可以強化程式碼的清晰度,那就去做,就算函式名稱比提煉出來的程式碼還長也無所謂。但如果你想不出一個更有意義的名字,就別動。 6.2.行內函數(Inline M

重構-改善有的程式碼設計-處理概括關係11-2

11.6.提煉子類(Extract Subclass) type Employee struct { _rate int } func (e *Employee) getRate() int { return e._rate }

重構-改善有的程式碼設計-處理概括關係11-1

11.1.欄位上移(Pull Up Field) 11.2.函式上移(Pull Up Method) 11.3.建構函式本體上移(Pull Up Constructor Body)   11.4.函

重構-改善程式碼設計-重構原則1

神馬是重構?從兩方面來說: 一個是名詞:對軟體內部結構的一種調整,目的是在不改變軟體可觀察行為的前提下,提高其可理解性,降低其修改成本。 一個是動詞:使用一系列重構手法,在不改變軟體可觀察行為的前提下,調整其結構。 對重構的擴充套件: 1.重構的目的是使軟體更容易被理解和修改。(

從零開始學重構——《重構改善程式碼設計

第0篇,引言 為什麼寫這個系列   想寫這個重構系列的文章已經有一段時間了,至於寫作的動機應該有三個。   首先,是帶領的兩個團隊的所有成員都是剛畢業不久的半新人,都充滿了積極的幹勁和責任心。只是在一些基礎技能上還略有不足,或將成為他們繼續成長的瓶頸,也必然會成為團隊發展的制約。

重構 改善程式碼設計---第三章 程式碼壞味道

3.1 重複程式碼 程式碼重複會讓整個類變得更大,影響程式碼閱讀。 1.同個類:不同方法中多次出現重複的程式碼或者表示式時,可以使用“提煉方法”的方式將重複程式碼或表示式提煉到方法A中,所有使用到這段程式碼或者表示式的方法通過對A方法的呼叫實現功能 2.兩個互為兄弟的類中含有相同的程

重構 改善程式碼設計

經過一個多月的時間,我讀完了這本重構的書籍。與其說是讀完,不如說是掃完的。因為中間關於重構的手法很多,有很多規則我是沒有親手嘗試的,其實也沒有這個必要。 在這本書中,除了中間部分大量的重構手法之外,作者還在書的前後兩部分用大量文字說明如何理解重構和怎樣使用重構。說白了,只學會重構的手法只學會了重構的一半都不

重構-改善有的程式碼設計-簡化條件表示式9

9.1.分解條件表示式(Decompose Conditional) 9.2.合併條件表示式(Consolidate Conditional Expression) 9.3.合成重複的條件片段(Consolidate Duplicate C

系統分析與設計--學習筆記4建模應用

一.按建模練習資料的 Task1 ,完成用例圖 Task1:用例建模(Use Case Modeling)是使用用例的方法來描述系統的功能需求的過程 (1)Use Case Diagram (2)Use Case Specification--Brief Level

微信小程式學習筆記持續更新---小程式網路請求封裝

寫小程式的你是否已經厭倦了傳送網路請求的wx.request?接著看吧。。。 一、目錄結構 在專案同級目錄下utils資料夾裡新建一個fetch.js檔案,(名字看自己喜好) 二、直接上程式碼 // 定義網路請求API地址 const baseURL = 'h

OpenGL學習筆記著色器

OpenGL的著色器語言(GLSL) 在說OpenGL的著色器語言之前先來介紹一下著色器到底是個什麼東西。 在學unity3d的時候就聽說有人說能寫shader和做圖形渲染優化的人都是大神,當時沒學過著色器一聽就感覺不明覺厲啊,先送上膝蓋再說。現在學

python學習筆記pandas基礎

大寫的吐槽:暑假提前結束,實驗室專案越來越緊,略煩躁(不喜歡做的專案),沒啥自由學習的時間了。只有抽些零散的時間去準備資料探勘比賽相關的東西。最近關注的大神 wepon,bryan的部落格,乾貨多多! PS: 實驗室師兄們找工作也是壓力山大,祝他們好運!

西門子PLC學習筆記-工作記錄

今天師傅給講了講做自動化控制的整體的思路,特進行一下記錄,做個備忘。 1.需求分析 本次的專案是對樓宇迴圈供水的控制,整個專案需要完成壓力、壓差、溫度等的獲取及顯示、同時完成電機的控制。 2.設計 使用西門子的Step7工具進行梯形圖程式設計,完成自動化控制。 使用西門子的

讀'重構-改善程式碼設計'學習心得

近日受一位資深程式設計師大牛寫的一篇學習路線建議的部落格影響,開始讀《重構-改善既有程式碼的設計》,《大話設計模式》兩書。此篇部落格為讀《重構-改善既有程式碼的設計》的學習心得筆記,將在每天的 學習中,不斷更新... 一、為什麼要重構 重構的意義在於將眼光放長遠,而不僅限

重構-改善程式碼設計重新組織函式的九種方法

         函式過長或者邏輯太混亂,重新組織和整理函式的程式碼,使之更合理進行封裝。 提煉函式:(由複雜的函式提煉出獨立的函式或者說大函式分解成由小函式組成)你有一段程式碼可以被組織在一起並獨立出來。將這段程式碼放進一個獨立函式,並讓函式名稱解釋該函式的用途。