拿什么拯救你趴乡,我的代碼--c#編碼規(guī)范實(shí)戰(zhàn)篇

此文為譯文对省,原文地址請(qǐng)點(diǎn)擊
本文通過(guò)重構(gòu)一個(gè)垃圾代碼晾捏,闡述了如何寫(xiě)出優(yōu)秀的代碼蒿涎。開(kāi)發(fā)人員及代碼審核人員需按照此規(guī)范開(kāi)發(fā)和審核代碼。此規(guī)范以C#為例惦辛,JAVA的童鞋一并參考劳秋,C++的童鞋自行腦補(bǔ)吧。


簡(jiǎn)介

這篇文章的目的是展示如何將一段垃圾代碼重構(gòu)成一個(gè)干凈的、可擴(kuò)展性和可維護(hù)的代碼玻淑。我將解釋如何通過(guò)最佳實(shí)踐和更好的設(shè)計(jì)模式來(lái)改寫(xiě)它嗽冒。

閱讀本文你需要有以下基礎(chǔ):

  • c# 基礎(chǔ)
  • 依賴注入,工廠模式补履,策略模式

此文中的例子源于實(shí)際項(xiàng)目添坊,這里不會(huì)有什么使用裝飾模式構(gòu)建的披薩,也不會(huì)使用策略模式的計(jì)算器箫锤,這些例子是非常好的說(shuō)明贬蛙,但是它們很難被在實(shí)際項(xiàng)目中使用。

一段垃圾代碼

在我們真實(shí)的產(chǎn)品中有這么一個(gè)類:

public class Class1
{
  public decimal Calculate(decimal amount, int type, int years)
  {
decimal result = 0;
decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100; 
if (type == 1)
{
  result = amount;
}
else if (type == 2)
{
  result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
}
else if (type == 3)
{
  result = (0.7m * amount) - disc * (0.7m * amount);
}
else if (type == 4)
{
  result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
}
return result;
  }
}

這是一段非常糟糕的代碼(二胡:如果你沒(méi)覺(jué)得這段代碼很糟糕谚攒,那你目前狀態(tài)可能很糟糕了)阳准,我們不太清楚這個(gè)類的目的是什么。它可能是一個(gè)網(wǎng)上商城的折扣管理類馏臭,負(fù)責(zé)為客戶計(jì)算折扣溺职。

這個(gè)類完全具備了不可讀、不可維護(hù)位喂、不可擴(kuò)展的特點(diǎn),它使用了很多壞的實(shí)踐和反模式的設(shè)計(jì)乱灵。

下面我們逐步分析這里到底有多少問(wèn)題塑崖?

  • 命名問(wèn)題 - 我們只能通過(guò)猜測(cè)這個(gè)類到底是為了計(jì)算什么。這實(shí)在是在浪費(fèi)時(shí)間痛倚。
    如果我們沒(méi)有相關(guān)文檔或者重構(gòu)這段代碼规婆,那我們下一次恐怕需要花大量的時(shí)間才能知道這段代碼的具體含義。

  • 魔數(shù) - 在這個(gè)例子中蝉稳,你能猜到變量type是指客戶賬戶的狀態(tài)嗎抒蚜。通過(guò)if-else來(lái)選擇計(jì)算優(yōu)惠后的產(chǎn)品價(jià)格。
    現(xiàn)在耘戚,我們壓根不知道賬戶狀態(tài)1嗡髓,2,3收津,4分別是什么意思饿这。
    此外,你知道0.1撞秋,0.7谒麦,0.5都是什么意思嗎痰娱?
    讓我們想象一下,如果你要修改下面這行代碼:
    result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));

  • 隱藏的BUG - 因?yàn)榇a非常糟糕,我們可能會(huì)錯(cuò)過(guò)非常重要的事情跃捣。試想一下喊括,如果我們的系統(tǒng)中新增了一類賬戶狀態(tài),而新的賬戶等級(jí)不滿足任何一個(gè)if-else條件。這時(shí)卧蜓,返回值會(huì)固定為0。

  • 不可讀 - 我們不得不承認(rèn)声功,這是一段不可讀的代碼烦却。不可讀=更多的理解時(shí)間+增加產(chǎn)生錯(cuò)誤的風(fēng)險(xiǎn)

  • DRY – 不要產(chǎn)生重復(fù)的代碼
    我們可能不能一眼就找出它們,但它們確實(shí)存在先巴。
    例如:disc *(amount - (0.1m * amount));
    同樣的邏輯:
    disc *(amount - (0.5m * amount));
    這里只有一個(gè)數(shù)值不一樣其爵。如果我們無(wú)法擺脫重復(fù)的代碼,我們會(huì)遇到很多問(wèn)題伸蚯。比如某段代碼在五個(gè)地方有重復(fù)摩渺,當(dāng)我們需要修改這部分邏輯時(shí),你很可能只修改到了2至3處剂邮。

  • 單一職責(zé)原則
    這個(gè)類至少具有三個(gè)職責(zé):
    1 選擇計(jì)算算法
    2 根據(jù)賬戶狀態(tài)計(jì)算折扣
    3 根據(jù)賬戶網(wǎng)齡計(jì)算折扣
    它違反了單一職責(zé)原則摇幻。這會(huì)帶來(lái)什么風(fēng)險(xiǎn)?如果我們將要修改第三個(gè)功能的話,會(huì)影響到另外第二個(gè)功能挥萌。這就意味著绰姻,我們每次修改都會(huì)改動(dòng)我們本不想修改的代碼。因此引瀑,我們不得不對(duì)整個(gè)類進(jìn)行測(cè)試狂芋,這實(shí)在很浪費(fèi)時(shí)間。

重構(gòu)

通過(guò)以下9布憨栽,我會(huì)告訴你們?nèi)绾伪苊馍鲜鲲L(fēng)險(xiǎn)并實(shí)現(xiàn)一個(gè)干凈的帜矾、可維護(hù)的、可測(cè)試的代碼屑柔。

  1. 命名屡萤,命名,命名
    這是良好代碼的最重要方面之一掸宛。我們只需要改變方法死陆,參數(shù)和變量的命名。現(xiàn)在唧瘾,我們可以確切的知道下面的類是負(fù)責(zé)什么的了翔曲。

    public decimal ApplyDiscount(decimal price, int accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
        if (accountStatus == 1)
        {
            priceAfterDiscount = price;
        }
        else if (accountStatus == 2)
        {
            priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
        }
        else if (accountStatus == 3)
        {
            priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
        }
        else if (accountStatus == 4)
        {
            priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
        }
    
        return priceAfterDiscount;
    }
    

但是我們?nèi)稳徊恢蕾~戶狀態(tài)1,2劈愚,3到底是什么意思瞳遍。

  1. 魔數(shù)
    在C#中避免魔數(shù)我們一般采用枚舉來(lái)替換它們。這里使用AccountStatus 枚舉來(lái)替換if-else中的魔數(shù)菌羽。
    public enum AccountStatus { NotRegistered = 1, SimpleCustomer = 2, ValuableCustomer = 3, MostValuableCustomer = 4 }
    現(xiàn)在我們來(lái)看看重構(gòu)后的類掠械,我們可以很容易的說(shuō)出哪一個(gè)賬戶狀態(tài)應(yīng)該用什么算法來(lái)計(jì)算折扣。混合賬戶狀態(tài)的風(fēng)險(xiǎn)迅速的下降了猾蒂。

     public class DiscountManager
     {
         public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
         {
             decimal priceAfterDiscount = 0;
             decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
    
             if (accountStatus == AccountStatus.NotRegistered)
             {
                 priceAfterDiscount = price;
             }
             else if (accountStatus == AccountStatus.SimpleCustomer)
             {
                 priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
             }
             else if (accountStatus == AccountStatus.ValuableCustomer)
             {
                 priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
             }
             else if (accountStatus == AccountStatus.MostValuableCustomer)
             {
                 priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
             }
             return priceAfterDiscount;
         }
     }
    
  2. 更多的代碼可讀性
    在這一步中均唉,我們使用switch-case 來(lái)替換 if-else if來(lái)增強(qiáng)代碼可讀性。
    同時(shí)肚菠,我還將某些長(zhǎng)度很長(zhǎng)的語(yǔ)句才分成兩行舔箭。比如:**priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));**
    被修改為:
    **priceAfterDiscount = (price - (0.5m * price));priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);**
    以下是完整的修改:

     public class DiscountManager
     {
         public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
         {
             decimal priceAfterDiscount = 0;
             decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
             switch (accountStatus)
             {
                 case AccountStatus.NotRegistered:
                     priceAfterDiscount = price;
                     break;
                 case AccountStatus.SimpleCustomer:
                     priceAfterDiscount = (price - (0.1m * price));
                     priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                     break;
                 case AccountStatus.ValuableCustomer:
                     priceAfterDiscount = (0.7m * price);
                     priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                     break;
                 case AccountStatus.MostValuableCustomer:
                     priceAfterDiscount = (price - (0.5m * price));
                     priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                     break;
             }
             return priceAfterDiscount;
         }
     }
    
  3. 消除隱藏的BUG
    正如我們之前提到的,我們的ApplyDiscount方法可能將為新的客戶狀態(tài)返回0蚊逢。
    我們?cè)鯓硬拍芙鉀Q這個(gè)問(wèn)題层扶?答案就是拋出NotImplementedException。
    當(dāng)我們的方法獲取賬戶狀態(tài)作為輸入?yún)?shù)烙荷,但是參數(shù)值可能包含我們未設(shè)計(jì)到的未知情況镜会。這時(shí),我們不能什么也不做终抽,拋出異常是這時(shí)候最好的做法戳表。

        public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
        {
            decimal priceAfterDiscount = 0;
            decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
            switch (accountStatus)
            {
                case AccountStatus.NotRegistered:
                    priceAfterDiscount = price;
                    break;
                case AccountStatus.SimpleCustomer:
                    priceAfterDiscount = (price - (0.1m * price));
                    priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                    break;
                case AccountStatus.ValuableCustomer:
                    priceAfterDiscount = (0.7m * price);
                    priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                    break;
                case AccountStatus.MostValuableCustomer:
                    priceAfterDiscount = (price - (0.5m * price));
                    priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                    break;
                default:
                    throw new NotImplementedException();
            }
            return priceAfterDiscount;
        }
    
  4. 分析算法
    在這個(gè)例子中,我們通過(guò)兩個(gè)標(biāo)準(zhǔn)來(lái)計(jì)算客戶折扣:

  • 賬戶狀態(tài)

  • 賬戶網(wǎng)齡
    通過(guò)網(wǎng)齡計(jì)算的算法都類似這樣:
    (discountForLoyaltyInPercentage * priceAfterDiscount)
    但是對(duì)于賬戶狀態(tài)為ValuableCustomer的算法卻是:
    0.7m * price
    我們把它修改成和其他賬戶狀態(tài)一樣的算法:
    price - (0.3m * price)

          public class DiscountManager
         {
         public decimal ApplyDiscount(decimal price, AccountStatus     accountStatus, int timeOfHavingAccountInYears)
         {
         decimal priceAfterDiscount = 0;
         decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5 / 100 : (decimal)timeOfHavingAccountInYears / 100;
         switch (accountStatus)
         {
             case AccountStatus.NotRegistered:
                 priceAfterDiscount = price;
                 break;
             case AccountStatus.SimpleCustomer:
                 priceAfterDiscount = (price - (0.1m * price));
                 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                 break;
             case AccountStatus.ValuableCustomer:
                 priceAfterDiscount = (price - (0.3m * price));
                 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                 break;
             case AccountStatus.MostValuableCustomer:
                 priceAfterDiscount = (price - (0.5m * price));
                 priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                 break;
             default:
                 throw new NotImplementedException();
         }
         return priceAfterDiscount;
         }
         }
    
  1. 消除魔數(shù)的另一種方法
    使用靜態(tài)常量來(lái)替換魔數(shù)昼伴。0.1m,0.2m,0.3m,我m匾旭,我們并不知道它們是什么意思。
    此外decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;中圃郊,數(shù)字5也非常神秘季率。
    我們必須讓它們更具有描述性,這時(shí)使用常量會(huì)是一個(gè)比較好的辦法描沟。
    我們來(lái)定義一個(gè)靜態(tài)類:

     public static class Constants
     {
     public const int MAXIMUM_DISCOUNT_FOR_LOYALTY = 5;
     public const decimal DISCOUNT_FOR_SIMPLE_CUSTOMERS = 0.1m;
     public const decimal DISCOUNT_FOR_VALUABLE_CUSTOMERS = 0.3m;
     public const decimal DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS = 0.5m;
     }
    

接著修改DiscountManager類:

    public class DiscountManager
    {
    public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY / 100 : (decimal)timeOfHavingAccountInYears / 100;
        switch (accountStatus)
        {
            case AccountStatus.NotRegistered:
                priceAfterDiscount = price;
                break;
            case AccountStatus.SimpleCustomer:
                priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price));
                priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                break;
            case AccountStatus.ValuableCustomer:
                priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price));
                priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                break;
            case AccountStatus.MostValuableCustomer:
                priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price));
                priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
                break;
            default:
                throw new NotImplementedException();
        }
        return priceAfterDiscount;
    }
    }
  1. 消除重復(fù)的代碼
    為了消除重復(fù)的代碼,這里將部分算法提取出來(lái)鞭光。首先吏廉,我們建立兩個(gè)擴(kuò)展方法:

     public static class PriceExtensions
     {
     public static decimal ApplyDiscountForAccountStatus(this decimal price, decimal discountSize)
     {
         return price - (discountSize * price);
     }
    
     public static decimal ApplyDiscountForTimeOfHavingAccount(this decimal price, int timeOfHavingAccountInYears)
     {
         decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY / 100 : (decimal)timeOfHavingAccountInYears / 100;
         return price - (discountForLoyaltyInPercentage * price);
     }
     }
    

通過(guò)方法名稱,我們就可以知道它的職責(zé)是什么惰许,現(xiàn)在修改我們的例子:

    public class DiscountManager
    {
    public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        switch (accountStatus)
        {
            case AccountStatus.NotRegistered:
                priceAfterDiscount = price;
                break;
            case AccountStatus.SimpleCustomer:
                priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS)
                  .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
                break;
            case AccountStatus.ValuableCustomer:
                priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS)
                  .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
                break;
            case AccountStatus.MostValuableCustomer:
                priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS)
                  .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
                break;
            default:
                throw new NotImplementedException();
        }
        return priceAfterDiscount;
    }
    }
  1. 刪除沒(méi)用的代碼
    我們應(yīng)該寫(xiě)出簡(jiǎn)短的代碼席覆,因?yàn)楹?jiǎn)短的代碼=減少BUG發(fā)生的機(jī)率,并且也讓我們縮短理解業(yè)務(wù)邏輯的時(shí)間汹买。
    我們發(fā)現(xiàn)佩伤,這里三種狀態(tài)的客戶調(diào)用了相同的方法:
    .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
    這里可以合并代碼:

     public class DiscountManager
     {
     public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
     {
         decimal priceAfterDiscount = 0;
         switch (accountStatus)
         {
             case AccountStatus.NotRegistered:
                 priceAfterDiscount = price;
                 break;
             case AccountStatus.SimpleCustomer:
                 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS);
                 break;
             case AccountStatus.ValuableCustomer:
                 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS);
                 break;
             case AccountStatus.MostValuableCustomer:
                 priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS);
                 break;
             default:
                 throw new NotImplementedException();
         }
         priceAfterDiscount = priceAfterDiscount.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
         return priceAfterDiscount;
     }
     }
    

9.最后,得到干凈的代碼
最后晦毙,讓我們通過(guò)引入依賴注入和工廠方法模式來(lái)得到最終的版本吧生巡。
先來(lái)看卡最終結(jié)果:

    public class DiscountManager
    {
    private readonly IAccountDiscountCalculatorFactory _factory;
    private readonly ILoyaltyDiscountCalculator _loyaltyDiscountCalculator;

    public DiscountManager(IAccountDiscountCalculatorFactory factory, ILoyaltyDiscountCalculator loyaltyDiscountCalculator)
    {
        _factory = factory;
        _loyaltyDiscountCalculator = loyaltyDiscountCalculator;
    }

    public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
        priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
        return priceAfterDiscount;
    }
    }

    public interface ILoyaltyDiscountCalculator
    {
    decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears);
    }

    public class DefaultLoyaltyDiscountCalculator : ILoyaltyDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears)
    {
        decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY / 100 : (decimal)timeOfHavingAccountInYears / 100;
        return price - (discountForLoyaltyInPercentage * price);
    }
    }

    public interface IAccountDiscountCalculatorFactory
    {
    IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus);
    }

    public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory
    {
    public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus)
    {
        IAccountDiscountCalculator calculator;
        switch (accountStatus)
        {
            case AccountStatus.NotRegistered:
                calculator = new NotRegisteredDiscountCalculator();
                break;
            case AccountStatus.SimpleCustomer:
                calculator = new SimpleCustomerDiscountCalculator();
                break;
            case AccountStatus.ValuableCustomer:
                calculator = new ValuableCustomerDiscountCalculator();
                break;
            case AccountStatus.MostValuableCustomer:
                calculator = new MostValuableCustomerDiscountCalculator();
                break;
            default:
                throw new NotImplementedException();
        }

        return calculator;
        }
    }

    public interface IAccountDiscountCalculator
    {
    decimal ApplyDiscount(decimal price);
    }

    public class NotRegisteredDiscountCalculator : IAccountDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price)
    {
        return price;
    }
    }

    public class SimpleCustomerDiscountCalculator : IAccountDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price)
    {
        return price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price);
    }
    }

    public class ValuableCustomerDiscountCalculator : IAccountDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price)
    {
        return price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price);
    }
    }

    public class MostValuableCustomerDiscountCalculator : IAccountDiscountCalculator
    {
    public decimal ApplyDiscount(decimal price)
    {
        return price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price);
    }
    }

首先,我們擺脫了擴(kuò)展方法(靜態(tài)類)见妒,如果我們想對(duì)ApplyDiscount方法進(jìn)行單元測(cè)試是比較困難的孤荣,廢除我們對(duì)PriceExtensions擴(kuò)展類也進(jìn)行測(cè)試。
為了避免這個(gè)問(wèn)題,我們創(chuàng)建了DefaultLoyaltyDiscountCalculator 類來(lái)替換ApplyDiscountForTimeOfHavingAccount這個(gè)擴(kuò)展方法盐股,此類還實(shí)現(xiàn)了ILoyaltyDiscountCalculator接口∏恚現(xiàn)在,當(dāng)我們要測(cè)試DiscountManager類時(shí)疯汁,我們只構(gòu)造函數(shù)注入ILoyaltyDiscountCalculator接口的實(shí)現(xiàn)即可牲尺。這里使用了依賴注入。
通過(guò)這樣做幌蚊,我們將網(wǎng)齡折扣的算法遷移到類似DefaultLoyaltyDiscountCalculator 的不同類中谤碳,這樣當(dāng)我們修改某一個(gè)算法不會(huì)覆蓋到其他業(yè)務(wù)。
對(duì)于根據(jù)賬戶狀態(tài)來(lái)計(jì)算折扣的業(yè)務(wù)霹肝,我們需要在
DiscountManager
中刪除兩個(gè)職責(zé):

  • 根據(jù)賬戶狀態(tài)選擇計(jì)算的算法

  • 實(shí)現(xiàn)計(jì)算算法
    這里我們通過(guò)DefaultAccountDiscountCalculatorFactory工廠類來(lái)解決這個(gè)問(wèn)題估蹄,DefaultAccountDiscountCalculatorFactory工廠類實(shí)現(xiàn)了IAccountDiscountCalculatorFactory接口。
    我們的工廠將決定選擇哪一個(gè)折扣算法沫换。接著臭蚁,工廠類被通過(guò)構(gòu)造函數(shù)注入到DiscountManager類中。
    下面我只需要在DiscountManager 中使用工廠:
    priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
    以上讯赏,我們解決了第一個(gè)問(wèn)題垮兑,下面我們需要實(shí)現(xiàn)計(jì)算算法。根據(jù)賬戶狀態(tài)漱挎,提供不同的算法系枪,這正好符合策略模式。我們需要構(gòu)建三個(gè)策略:
    NotRegisteredDiscountCalculator磕谅,SimpleCustomerDiscountCalculator私爷,MostValuableCustomerDiscountCalculator
    已經(jīng)抽象出來(lái)的接口IAccountDiscountCalculator
    好了膊夹,現(xiàn)在我們有可一段干凈可讀的代碼了衬浑,這段代碼中所有的類都只有一個(gè)職責(zé):

  • DiscountManager - 管理

  • DefaultLoyaltyDiscountCalculator - 網(wǎng)齡計(jì)算折扣

  • DefaultAccountDiscountCalculatorFactory - 根據(jù)賬戶狀態(tài)選擇折扣策略

  • NotRegisteredDiscountCalculator,SimpleCustomerDiscountCalculator,MostValuableCustomerDiscountCalculator-計(jì)算折扣算法
    我們來(lái)比較一下修改前后的代碼:

      public class Class1
      {
      public decimal Calculate(decimal amount, int type, int years)
      {
          decimal result = 0;
          decimal disc = (years > 5) ? (decimal)5 / 100 : (decimal)years / 100;
          if (type == 1)
          {
              result = amount;
          }
          else if (type == 2)
          {
              result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
          }
          else if (type == 3)
          {
              result = (0.7m * amount) - disc * (0.7m * amount);
          }
          else if (type == 4)
          {
              result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
          }
          return result;
      }
      }
    

修改后:

    public class DiscountManager
    {
    private readonly IAccountDiscountCalculatorFactory _factory;
    private readonly ILoyaltyDiscountCalculator _loyaltyDiscountCalculator;

    public DiscountManager(IAccountDiscountCalculatorFactory factory, ILoyaltyDiscountCalculator loyaltyDiscountCalculator)
    {
        _factory = factory;
        _loyaltyDiscountCalculator = loyaltyDiscountCalculator;
    }

    public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
    {
        decimal priceAfterDiscount = 0;
        priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
        priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
        return priceAfterDiscount;
    }
    }

總結(jié)

本文通過(guò)簡(jiǎn)單易懂的方法重構(gòu)了一段問(wèn)題代碼,它顯示了如何在實(shí)際情況中使用最佳實(shí)踐和設(shè)計(jì)模式來(lái)幫助我們寫(xiě)出干凈的代碼放刨。
就我的工作經(jīng)驗(yàn)來(lái)說(shuō)工秩,本文中出現(xiàn)的不良做法是經(jīng)常發(fā)生的。編寫(xiě)這種代碼的人總是認(rèn)為他們能夠保持這種規(guī)則进统,但不幸的是系統(tǒng)和業(yè)務(wù)往往都會(huì)越來(lái)越復(fù)雜助币,每次修改這類代碼時(shí)都會(huì)帶來(lái)巨大的風(fēng)險(xiǎn)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末螟碎,一起剝皮案震驚了整個(gè)濱河市眉菱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掉分,老刑警劉巖倍谜,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迈螟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡尔崔,警方通過(guò)查閱死者的電腦和手機(jī)答毫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)季春,“玉大人洗搂,你說(shuō)我怎么就攤上這事≡嘏” “怎么了耘拇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宇攻。 經(jīng)常有香客問(wèn)我惫叛,道長(zhǎng),這世上最難降的妖魔是什么逞刷? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任嘉涌,我火速辦了婚禮,結(jié)果婚禮上夸浅,老公的妹妹穿的比我還像新娘仑最。我一直安慰自己,他們只是感情好帆喇,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布警医。 她就那樣靜靜地躺著,像睡著了一般坯钦。 火紅的嫁衣襯著肌膚如雪预皇。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,365評(píng)論 1 302
  • 那天婉刀,我揣著相機(jī)與錄音吟温,去河邊找鬼。 笑死路星,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诱桂。 我是一名探鬼主播洋丐,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挥等!你這毒婦竟也來(lái)了友绝?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤肝劲,失蹤者是張志新(化名)和其女友劉穎迁客,沒(méi)想到半個(gè)月后郭宝,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掷漱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年粘室,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卜范。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衔统,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出海雪,到底是詐尸還是另有隱情锦爵,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布奥裸,位于F島的核電站险掀,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏湾宙。R本人自食惡果不足惜樟氢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望创倔。 院中可真熱鬧嗡害,春花似錦、人聲如沸畦攘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)知押。三九已至叹螟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間台盯,已是汗流浹背罢绽。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留静盅,地道東北人良价。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像蒿叠,于是被迫代替她去往敵國(guó)和親明垢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問(wèn)題, 分享了一些自己做題目的經(jīng)驗(yàn)市咽。 張土汪:刷leetcod...
    土汪閱讀 12,744評(píng)論 0 33
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理痊银,服務(wù)發(fā)現(xiàn),斷路器施绎,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • 如何實(shí)施重構(gòu) 稍微復(fù)雜的重構(gòu)過(guò)程溯革,都是由一系列的基本重構(gòu)手法組成. 《重構(gòu)》一書(shū)中針對(duì)各種重構(gòu)場(chǎng)景贞绳,給出了大量的重...
    MagicBowen閱讀 4,005評(píng)論 0 3
  • 本文約定: 1. Nhibernate簡(jiǎn)寫(xiě)為NHB; 2. 本文例子的開(kāi)發(fā)平臺(tái)為win2000pro+sp4, s...
    壹米玖坤閱讀 533評(píng)論 0 0
  • 大家好冈闭,我是白亦初。江湖人稱“小白”豺裆。 上一次拒秘,我主要是和大家分享了我在寫(xiě)作營(yíng)里學(xué)到的兩大收獲。一是我們要重新地審...
    阿超有話說(shuō)閱讀 542評(píng)論 9 8