重構手法之簡化條件表達式【1】

来源:http://www.cnblogs.com/liuyoung/archive/2017/11/26/7868458.html
-Advertisement-
Play Games

返回總目錄 本小節目錄 Decompose Conditional(分解條件表達式) Consolidate Conditional Expression(合併條件表達式) 1Decompose Conditional(分解條件表達式) 概要 你有一個複雜的條件(if-else if-else)語句 ...


返回總目錄

本小節目錄

1Decompose Conditional(分解條件表達式)

概要

你有一個複雜的條件(if-else if-else)語句。

從if、else if、else三個段落中分別提煉出獨立函數。

動機

複雜的條件邏輯往往會導致程式複雜度上升。編寫代碼來檢查不同的條件分支、根據不同的分支做不同的事往往又會導致函數過長。

將條件表達式分解為多個獨立函數,根據每個小塊代碼的用途,為分解而得的新函數命名,並將原函數中對應的代碼改為調用新建函數,從而更清楚地表達自己的意圖。對於條件邏輯,將每個分支條件分解成新函數還可以突出條件邏輯,更清楚地表明每個分支的作用,並且突出每個分支的原因。

範例

假設要計算購買某樣商品的總價,而這個商品在冬天和夏天的單價是不同的:

class Order
{
    public double Quantity { get; set; }

    public double WinterRate { get; set; }

    public double SummerRate { get; set; }

    public double WinterServiceCharge { get; set; }

    public double GetCharge(DateTime date)
    {
        double result;
        if (date.Month < 3 || date.Month > 6)
        {
            result = Quantity * WinterRate + WinterServiceCharge;
        }
        else
        {
            result = Quantity * SummerRate;
        }
        return result;
    }

}

現在把每個分支的判斷條件都提煉到一個獨立函數中:

class Order
{
    public double Quantity { get; set; }

    public double WinterRate { get; set; }

    public double SummerRate { get; set; }

    public double WinterServiceCharge { get; set; }


    public double GetCharge(DateTime date)
    {
        double result;
        if (NotSummer(date))
        {
            result = WinterCharge(Quantity);
        }
        else
        {
            result = SummerCharge(Quantity);
        }
        return result;
    }

    private bool NotSummer(DateTime date)
    {
        return date.Month < 3 || date.Month > 6;
    }
    private double SummerCharge(double quantity)
    {
        return quantity * SummerRate;
    }

    private double WinterCharge(double quantity)
    {
        return quantity * WinterRate + WinterServiceCharge;
    }
}

通過這段代碼可看出整個重構帶來的清晰性。

小結

像上面這樣的情況,很多人都不會去提煉分支條件。因為這些分支條件往往非常短,看上去似乎沒有提煉的必要。但是,儘管這些條件往往很短,在代碼意圖和代碼自身之間往往存在不小的差距。就像上面那樣,NotSummer(date)這個語句比原本的代碼更好地表達自己的意圖。原來的代碼,我必須看看,想一想,才能說出其作用。當然了,這裡看起來似乎很簡單。即使是這樣,提煉出來的函數可讀性也更高一些。

2Consolidate Conditional Expression(合併條件表達式)

概要

你有一系列條件測試,都得到相同結果。

將這些測試合併為一個條件表達式,並將這個條件表達式提煉成為一個獨立函數。

動機

代碼里經常有這樣的檢查:檢查條件各不相同,最終行為卻一致。如果發現這種情況,應該使用“邏輯與”和“邏輯或”將它們合併為一個條件表達式。

之所以合併條件代碼,有兩個原因。(1)合併後的條件代碼會告訴你“實際上只有一次條件檢查,只不過有多個併列條件需要檢查而已”,從而使這一次檢查的用意更清晰。(2)這項重構往往是為了使用Extract Method做好準備。

範例:使用邏輯或

class Amount
{
    public int Seniority { get; set; }

    public int MonthsDisabled { get; set; }

    public bool IsPartTime { get; set; }

    double DisablilityAmount()
    {
        if (Seniority < 2)
        {
            return 0;
        }
        if (MonthsDisabled > 12)
        {
            return 0;
        }
        if (IsPartTime)
        {
            return 0;
        }
        //compute the disability amount
        //your code here
        return 1;
    }
}

這段代碼中,一連串的條件檢查都在做同一件事情。對於這樣的代碼,上述檢查等價於一個以邏輯或連接起來的語句:

class Amount
{
    public int Seniority { get; set; }

    public int MonthsDisabled { get; set; }

    public bool IsPartTime { get; set; }

    double DisablilityAmount()
    {
        if (Seniority < 2 || MonthsDisabled > 12 || IsPartTime)
        {
            return 0;
        }
        //compute the disability amount
        //your code here
        return 1;
    }
}

現在,我們觀察這個新的條件表達式,並運用Extract Method將它提煉成一個獨立函數,以函數名稱表達該語句所檢查的條件:

class Amount
{
    public int Seniority { get; set; }

    public int MonthsDisabled { get; set; }

    public bool IsPartTime { get; set; }

    double DisablilityAmount()
    {
        if (IsNotEligibleForDisability())
        {
            return 0;
        }
        //compute the disability amount
        //your code here
        return 1;
    }

    bool IsNotEligibleForDisability()
    {
        return Seniority < 2 || MonthsDisabled > 12 || IsPartTime;
    }
}

範例:使用邏輯與

class Rate
{
    public double GetRate()
    {
        if (OnVacation())
        {
            if (LengthOfService() > 10)
            {
                return 1;
            }
        }
        return 0.5;
    }
    private bool OnVacation()
    {
        return true;
    }
    private int LengthOfService()
    {
        return 9;
    }
}

這段代碼可以變成這樣:

class Rate
{
    public double GetRate()
    {
        if (OnVacation() && LengthOfService() > 10)
        {
            return 1;
        }
        return 0.5;
    }
    private bool OnVacation()
    {
        return true;
    }
    private int LengthOfService()
    {
        return 9;
    }
}

如果所觀察的部分只是對條件進行檢查並返回一個值,就可以使用三元操作符將這一部分變成一條return語句。因此,下列代碼:

if (OnVacation() && LengthOfService() > 10)
{
    return 1;
}
return 0.5;

就變成了:

return (OnVacation() && LengthOfService() > 1) ? 1 : 0.5;

小結

那我們什麼時候不需要合併表達式呢?

即我們認為這些檢查的確彼此獨立,的確不應該被視為同一次檢查,那就不使用本項重構。

 

 

To Be Continued...


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 打開文檔變肉雞:潛伏17年的“噩夢公式”Office漏洞攻擊分析 微軟的11月份的安全補丁包含了一個隱藏17年之久的Office遠程代碼執行漏洞(CVE-2017-11882),該漏洞影響目前所有流行的Office軟體。 ...
  • 使用 yum install hmaccalc.x86_64 安裝軟體包時,提示:Error Downloading Packages 解決方法: 1、清理本地yum緩存 執行:yum clean all 2、查看軟體包列表 執行:yum list 註意:如果查詢不到軟體包列表,查看yum源是否配置 ...
  • 1.部署步驟 1.1.啟動安裝程式 在啟動頁面上選擇Installation,然後按Enter鍵,這將載入SUSE Linux伺服器安裝程式並以普通模式安裝。 1.2.選擇安裝語言 Language和KeyboardLayout都選擇為English(US),並勾選I Agree to the Li ...
  • 背水一戰 Windows 10 之 控制項(控制項基類 - Control): 基礎知識, 焦點相關, 運行時獲取 ControlTemplate 和 DataTemplate 中的元素 ...
  • 一、前言 假設我們的C#解決方案中有多個程式應用,如:Web應用、控制台程式、WPF程式應用和Windows服務應用。 那麼這些非Windows Service應用程式怎麼在代碼中找到Windows服務應用的執行路徑呢? 二、正文 假設該Windows 服務已經啟動,名稱叫SocketService ...
  • 恢復內容開始 線程同步有兩種實現方式: 基於用戶模式實現和用內核對象實現。前者偏於輕量級,性能也更好,但是只能用於同一進程間的線程同步,後者重量級,性能消耗更大,跨進程。 研讀了一下windows核心編程的線程同步以及C#並行編程高級教程的ThreadPool部分,對線程的理解更深了。線程同步一般來 ...
  • 隱式轉換[自動類型轉換]: 兩種類型要相容,原類型值域要小於目標類型值域,可以簡單的理解為由小轉大。 1 public class Test 2 { 3 private void Start() 4 { 5 int a = 10; 6 float b = a;//int 類型隱式轉換為 float ...
  • 構造方法:用於實例化對象。 一般情況下,類或者結構體中都會存在一個預設的無參構造方法。如果我們在類中手動書寫了有參的構造方法,那麼這個無參構造方法就會被覆蓋掉;但是結構體中卻不會被覆蓋,即使我們在結構體中書寫了有參構造方法,無參構造方法依然可以使用。 【在使用單例模式時,我們可以書寫 private ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...