[翻译]C# BAD PRACTICES: Learn how to make a good code by bad example—C#:怎么着将坏的代码重新编写翻译为好的代码

投机的前言表明:

 本文原版的书文者:Radoslaw
Sadowski
,原版的书文链接为:C#
BAD PRACTICES: Learn how to make a good code by bad
example

本类别还有别的小说,后续将日益翻译。

 

引言:

自家的名字叫Radoslaw
Sadowski,小编今后是一个微软技术开发职员。笔者从上马工作时就直接接触的微软技术.

在办事一年后,作者看齐的身分很差的代码的数目基本上都能够写成一整本书了。

那一个经历让笔者变成了3个想要清洁代码的网瘾病人。

写那篇作品的指标是为着通过展现质量很差的类的例证来验证什么下笔出干净的、可延伸的和可保证的代码。作者会通过好的书写方式和设计形式来分解坏的代码带来的题材,以及替换他的好的消除格局。

第3局地是针对性这么些具有C#基础知识的开发职员——我会议及展览示一些广大的荒谬,然后再显示一些让代码变得可读性的格局与技术。高级部分首要针对那些至少存有设计格局概念的开发职员——作者将会来得完全绝望的、单元可测试的代码。

为了能够明白那篇文章你要求至少通晓以下四个部分的基本知识:

  • C#语言
  • 借助注入、工厂设计方式、策略设计格局

正文中所涉及的例证都将会是现实中远近有名的实际的个性,而不是用装饰方式来做披萨或然用政策方式来做总计器那样的演示。

(ps解释:看过设计情势相关的书籍的人应该会通晓许多那方面包车型客车书本都以用那种例子,只是为了帮扶读者通晓设计情势)

                           
  图片 1 
     
  图片 2

因为自身发觉那系列型的成品不佳用来表明,相反这几个理论性的例证却是万分适合用来在本文中做解释的。

大家通常会听到说不要用这一个,要用那叁个,不过却不掌握那种替换的理由。今日自家将会努力解释和认证那么些好的书写习惯以及设计形式是的确是在帮衬大家的开产生活!

 提示:

  •  在本文中笔者不会花时间来讲解C#的性状和涉嫌格局等等(小编也解释不完),网上有成都百货上千有关那地点的好的理论的例证。笔者将汇总讲述如何在大家平常工作中动用这个事物。
  • 事例是一种相比易于的崛起我们要注脚的题目标主意,不过仅限于描述的难题——因为作者意识当自家在求学怎样包罗注重庆大学代码的例未时,小编发以往知晓作品的完整考虑方面会有困难。
  •  小编不是说自家文中说的办法是惟一的缓解措施,作者只是能担保这么些方式将会是让您的代码变得更高质量的途径。
  • 自作者并不关怀上边那么些代码的什么错误处理,日志记录等等。笔者要抒发的只是用来消除一般编码一些标题标方法。

那就起初吧….

这3个不好透了的类…

下边包车型地铁例子是大家现实中的类:

 1 public class Class1
 2 {
 3   public decimal Calculate(decimal amount, int type, int years)
 4   {
 5     decimal result = 0;
 6     decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100; 
 7     if (type == 1)
 8     {
 9       result = amount;
10     }
11     else if (type == 2)
12     {
13       result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
14     }
15     else if (type == 3)
16     {
17       result = (0.7m * amount) - disc * (0.7m * amount);
18     }
19     else if (type == 4)
20     {
21       result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
22     }
23     return result;
24   }
25 }

下边那一个例子真的是一种分外差的书写形式。你能通晓那么些类是用来干嘛的么?那几个东西是用来做一些意外的运算的么?我们小说就从他起来入手来教学吧…

今昔小编来报告你,刚刚这些类是用来当顾客在网上买东西的时候为她们计算对应折扣的折扣计算和保管的类。

-难以置信吧!

-不过那是真的!

这种写法真的是将难以阅读、难以保障和麻烦增添那二种集合在一起了,而且装有着太差的书写习惯和错误的形式。

除却还有其余什么难题么?

1.命名格局-从源代码中我们可以连蒙带猜猜测出来这几个计算方法和出口结果是何等。而且大家想要从这些类中提取总计算法将会是一件十分费力的作业。

如此带来的祸害是:

最沉痛的题材是:浪费时间,

图片 3

 

假设大家须要满意客户的商业咨询,要像他们来得算法细节,只怕咱们必要修改那段代码,那将开支大家十分短的日子去领会大家的计量方法的逻辑。即便我们不记录她或重构代码,下次大家/别的开发人士再看那段代码的时候,依然要求开支相同的大运来斟酌这几个代码是干嘛的。而且在改动的还要还易于失误,导致原本的盘算全体失误。

 2.魔法数字

 图片 4

在那么些事例中type是变量,你能猜到它象征着客户账户的等级么?If-else
if
言语是用来完成怎么着挑选计算出产品价格折扣的艺术。

现行反革命我们不领悟怎么着的账户是1,2,3或4。未来想像一下,当您不得不为了那些有价值的VIP客户改变他们的折扣总计办法的时候,你试着从那多少个代码中找出修改的艺术—这一个进程可能会花费你很短的时辰不说,还很有或者犯错以至于修改那个基础的貌似的客户的账户,毕竟像2恐怕3那几个用语毫无描述性的。然则在大家犯错未来,这1个一般的客户却很快乐,因为她们赢得了VIP客户的折扣。:)

3.并未明确的bug

因为大家的代码质量很差,而且可读性非常差,所以我们也许随便就大意掉很多卓殊主要的事情。想象一下,今后忽然在系统中加进一种新的客户类型-金卡用户,而在我们的类别中别的一种新的账户类型最后取得的价格将是0元。为啥吧?因为在我们的if-else
if
语句中没有任何情状是满足新的意况的,所以要是是未处理过的账户类型,最终再次来到值都将变成0。一旦我们的业主发现那件事,他将会老羞成怒-毕竟她已经免费卖给这么用户很多过多东西了!

图片 5

4.从没可读性

大家必须承认上边那段代码的可读性是真的不得了。

她让大家费用了太多的年华去精晓这段代码,同时期码隐藏不当的可能率太大了,而那正是绝非可读性的最珍视的概念。

 5.魔法数字(再度)

您从代码中能知道好像0.1,0.7,0.5这几个数字的趣味么?好的,小编承认自身不清楚。只有我们温馨编写那个代码大家才精晓那是怎样看头,外人是力不从心知晓的。

你尝试想想要是让您改改上边那句代码,你会如何:

result = (amount – (0.5m * amount)) – disc * (amount – (0.5m *
amount));

因为这么些办法完全不可读,所以您改改的进度中不得不尝试着把第四个0.5改成0.4而保持第3个0.5不懂。那说不定会是2个bug,可是却是最好的最合适的修改章程。因为这一个0.5什么样都没有告知大家。

一样的事也设有将years变量转换成disc变量的更换进度中:

decimal disc = (years > 5) ? (decimal)5/100 : (decimal)years/100;

那是用来计算折扣率的,会通过账户在大家系统的岁月的百分比来获取。好的,那么今后题材来了,假诺时间刚刚好正是5啊?

6.精简-不要频仍做无用功

虽说第叁即时的时候不不难看出来,可是仔细钻探一下就会发觉:我们的代码里有无数再一次的地点。例如:disc *
(amount – (0.1m * amount));

而与之有平等效果的还有(只是变了三个参数而已):disc * (amount –
(0.5m * amount))

在那三个算术中,唯一的界别就只是贰个静态参数,而大家完全能够用贰个可变的参数来代替。

假定大家不试着在写代码的时候从一向ctri+c,ctrl+v中解脱出来,那咱们将赶上的题材正是我们只能修改代码中的部分作用,因为大家不知底有多少地方供给修改。下面的逻辑是计算出在大家系统中种种客户对应年限获得的折扣,所以若是大家只是贸然修改两到三处,很容易造成任什么地点方的前后分歧。

7.每一个类具有太多的纷纭的权力和权利区域

笔者们写的类至少背负了多少个任务:

  1. 挑选总计的运算法则
  2. 为各种差别景况的账户总结折扣率
  3. 依据每种客人的为期总结出相应的折扣率

以此背离了十足权利标准。那么那会带来什么样损伤呢?假若我们想要改变上诉1个特征中的几个,那就象征或许会碰触到有些其它的我们并不想修改的特色。所以在修改的时候大家只能再次测试全体的类,那么那就招致了很重的时日的浪费。

这就起来重构吧…

在接下去的几个步骤中自己将向您出示大家如何防止上诉难题来创设1个彻底的易维护,同时又利于单元测试的看起来一目领会的代码。

 

I:命名,命名,命名

恕作者直言,那是代码中最要害的一步。大家只是修章/参数/变量那一个的名字,目前后我们能够直观的打听到上边那个类代表怎样看头。

 1 public class DiscountManager
 2 {
 3   public decimal ApplyDiscount(decimal price, int accountStatus, int timeOfHavingAccountInYears)
 4   {
 5     decimal priceAfterDiscount = 0;
 6     decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100; 
 7     if (accountStatus == 1)
 8     {
 9       priceAfterDiscount = price;
10     }
11     else if (accountStatus == 2)
12     {
13       priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
14     }
15     else if (accountStatus == 3)
16     {
17       priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
18     }
19     else if (accountStatus == 4)
20     {
21       priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
22     }
23  
24     return priceAfterDiscount;
25   }
26 }

固然这么,大家依旧不知情1,2,3,4表示着什么样,那就一而再往下呢!

II:魔法数

在C#中幸免出现不知晓的魔法数的章程是透过枚举来代替。笔者通过枚举方法来代表在if-else if 语句中冒出的替代账户状态的魔法数。

1 public enum AccountStatus
2 {
3   NotRegistered = 1,
4   SimpleCustomer = 2,
5   ValuableCustomer = 3,
6   MostValuableCustomer = 4
7 }

近日在看大家重构了的类,大家能够很简单的揭露那多少个总计法则是用来依照不用状态来计算折扣率的。将账户状态弄混的可能率就大幅度收缩了。

 1 public class DiscountManager
 2 {
 3   public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
 4   {
 5     decimal priceAfterDiscount = 0;
 6     decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
 7  
 8     if (accountStatus == AccountStatus.NotRegistered)
 9     {
10       priceAfterDiscount = price;
11     }
12     else if (accountStatus == AccountStatus.SimpleCustomer)
13     {
14       priceAfterDiscount = (price - (0.1m * price)) - (discountForLoyaltyInPercentage * (price - (0.1m * price)));
15     }
16     else if (accountStatus == AccountStatus.ValuableCustomer)
17     {
18       priceAfterDiscount = (0.7m * price) - (discountForLoyaltyInPercentage * (0.7m * price));
19     }
20     else if (accountStatus == AccountStatus.MostValuableCustomer)
21     {
22       priceAfterDiscount = (price - (0.5m * price)) - (discountForLoyaltyInPercentage * (price - (0.5m * price)));
23     }
24     return priceAfterDiscount;
25   }
26 }

III:越多的可读性

在这一步中大家将经过将if-else
if
 语句改为switch-case 语句,来充实小说的可读性。

与此同时,作者也将七个十分长的持筹握算格局拆分为两句话来写。今后我们将“
通过账户状态来计量折扣率”与“通过账户定期来测算折扣率”那五头分别来计算。

例如:priceAfterDiscount = (price – (0.5m * price)) –
(discountForLoyaltyInPercentage * (price – (0.5m * price)));

我们将它重构为:priceAfterDiscount = (price – (0.5m * price));
priceAfterDiscount = priceAfterDiscount –
(discountForLoyaltyInPercentage * priceAfterDiscount);

那便是修改后的代码:

 1 public class DiscountManager
 2 {
 3   public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
 4   {
 5     decimal priceAfterDiscount = 0;
 6     decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
 7     switch (accountStatus)
 8     {
 9       case AccountStatus.NotRegistered:
10         priceAfterDiscount = price;
11         break;
12       case AccountStatus.SimpleCustomer:
13         priceAfterDiscount = (price - (0.1m * price));
14         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
15         break;
16       case AccountStatus.ValuableCustomer:
17         priceAfterDiscount = (0.7m * price);
18         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
19         break;
20       case AccountStatus.MostValuableCustomer:
21         priceAfterDiscount = (price - (0.5m * price));
22         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
23         break;
24     }
25     return priceAfterDiscount;
26   }
27 }

IV:没有明白的bug

笔者们到底找到大家隐藏的bug了!

因为自个儿正要提到的大家的点子中对于不符合的账户状态会在导致对于全数商品最终都重临0。固然很衰颓,但却是真的。

那大家该怎么修复那个题材吧?那就只有经过没有不当提示了。

图片 6

你是否会想,这么些会不会是开发的不等,应该不会被提交到错误提示中去?不,他会的!

当大家的格局通过获取账户状态作为参数的时候,大家并不想程序让大家不得预言的趋向前行,造成不可预测的失误。

 那种景况是相对不允许出现的,所以我们务必经过抛出尤其来预防这种情形。

下边包车型客车代码就是经过抛出尤其后修改的以幸免出现不满意条件的景色-修章是将抛出十三分防止 switch-case语句中的default 句中。

 1 public class DiscountManager
 2 {
 3   public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
 4   {
 5     decimal priceAfterDiscount = 0;
 6     decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
 7     switch (accountStatus)
 8     {
 9       case AccountStatus.NotRegistered:
10         priceAfterDiscount = price;
11         break;
12       case AccountStatus.SimpleCustomer:
13         priceAfterDiscount = (price - (0.1m * price));
14         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
15         break;
16       case AccountStatus.ValuableCustomer:
17         priceAfterDiscount = (0.7m * price);
18         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
19         break;
20       case AccountStatus.MostValuableCustomer:
21         priceAfterDiscount = (price - (0.5m * price));
22         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
23         break;
24       default:
25         throw new NotImplementedException();
26     }
27     return priceAfterDiscount;
28   }
29 }

V:分析盘算情势

在我们的例子中大家有四个概念给客户的折扣率的正规化:

  1. 账户状态;
  2. 账户在我们系统中存在的期限

对于年限的一个钱打二1三个结折扣率的法门,全体的测算方法都有点类似:

(discountForLoyaltyInPercentage * priceAfterDiscount)

当然,也照旧存在差别的:0.7m * price

就此大家把那一个改成这么:price – (0.3m * price)

 1 public class DiscountManager
 2 {
 3   public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
 4   {
 5     decimal priceAfterDiscount = 0;
 6     decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;
 7     switch (accountStatus)
 8     {
 9       case AccountStatus.NotRegistered:
10         priceAfterDiscount = price;
11         break;
12       case AccountStatus.SimpleCustomer:
13         priceAfterDiscount = (price - (0.1m * price));
14         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
15         break;
16       case AccountStatus.ValuableCustomer:
17         priceAfterDiscount = (price - (0.3m * price));
18         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
19         break;
20       case AccountStatus.MostValuableCustomer:
21         priceAfterDiscount = (price - (0.5m * price));
22         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
23         break;
24       default:
25         throw new NotImplementedException();
26     }
27     return priceAfterDiscount;
28   }
29 }

今昔我们将整治全体通过账户状态的盘算方法改为同一种格式:price –
((static_discount_in_percentages/100) * price)

VI:通过别的格局再摆脱魔法数

接下去让我们的眼光放在通过账户状态总计折扣率的盘算办法中的静态变量:(static_discount_in_percentages/100)

接下来带入上边数字距离试试:0.1m,0.3m,0.5m

这几个数字其实也是一系列型的魔法数-他们也并未从来告诉大家他们代表着什么。

大家也有雷同的情状,比如将“有账户的时刻”折价为“忠诚折扣”。

decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears
> 5) ? (decimal)5/100 : (decimal)timeOfHavingAccountInYears/100;

数字5让大家的代码变得神秘了起来。

大家必须做些什么让那些变得更具表现性。

本身会用别的一种方法来制止魔法数的发布的产出-也正是C#中的常量(关键词是const),笔者强烈建议在大家的应用程序中等专业学校门定义一个静态类来存储那几个常量。

在大家的例证中,笔者是成立了上面包车型地铁类:

1 public static class Constants
2 {
3   public const int MAXIMUM_DISCOUNT_FOR_LOYALTY = 5;
4   public const decimal DISCOUNT_FOR_SIMPLE_CUSTOMERS = 0.1m;
5   public const decimal DISCOUNT_FOR_VALUABLE_CUSTOMERS = 0.3m;
6   public const decimal DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS = 0.5m;
7 }

经过一定的修改,咱们的DiscountManager类就成为了那样了:

 1 public class DiscountManager
 2 {
 3   public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
 4   {
 5     decimal priceAfterDiscount = 0;
 6     decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100;
 7     switch (accountStatus)
 8     {
 9       case AccountStatus.NotRegistered:
10         priceAfterDiscount = price;
11         break;
12       case AccountStatus.SimpleCustomer:
13         priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price));
14         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
15         break;
16       case AccountStatus.ValuableCustomer:
17         priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price));
18         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
19         break;
20       case AccountStatus.MostValuableCustomer:
21         priceAfterDiscount = (price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price));
22         priceAfterDiscount = priceAfterDiscount - (discountForLoyaltyInPercentage * priceAfterDiscount);
23         break;
24       default:
25         throw new NotImplementedException();
26     }
27     return priceAfterDiscount;
28   }
29 }

自家盼望您也承认自身那几个方法会愈来愈使代码本身变得更富有表达性:)

VII:不要再另行啦!

图片 7

 

笔者们能够透过分拆算法的法门来运动大家的揣测方法,而不是仅仅不难的复制代码。

我们会因而扩大方法。

率先大家会创造四个扩张方法。

 1 public static class PriceExtensions
 2 {
 3   public static decimal ApplyDiscountForAccountStatus(this decimal price, decimal discountSize)
 4   {
 5     return price - (discountSize * price);
 6   }
 7  
 8   public static decimal ApplyDiscountForTimeOfHavingAccount(this decimal price, int timeOfHavingAccountInYears)
 9   {
10      decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100;
11     return price - (discountForLoyaltyInPercentage * price);
12   }
13 }

正如方法的名字一般,笔者不再须求独自解释一次他们的效率是怎么样。未来就初阶在大家的例子中运用这个代码吧:

 

 1 public class DiscountManager
 2 {
 3   public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
 4   {
 5     decimal priceAfterDiscount = 0;
 6     switch (accountStatus)
 7     {
 8       case AccountStatus.NotRegistered:
 9         priceAfterDiscount = price;
10         break;
11       case AccountStatus.SimpleCustomer:
12         priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS)
13           .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
14         break;
15       case AccountStatus.ValuableCustomer:
16         priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS)
17           .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
18         break;
19       case AccountStatus.MostValuableCustomer:
20         priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS)
21           .ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
22         break;
23       default:
24         throw new NotImplementedException();
25     }
26     return priceAfterDiscount;
27   }
28 }

扩展方法让代码看起来更为友善了,可是这么些代码依然静态的类,所以会让您单元测试的时候遇到困难,甚至不也许。那么出于摆脱那么些难点的打算大家在终极一步来化解那么些标题。作者将显得这几个是怎么简化大家的干活生活的。可是对于自个儿个人而言,小编喜欢,可是并不算是热衷粉。

不顾,你未来同意我们的代码看起来友善多了这点么?

那大家就继续下去吧!

VIII:移除那么些多余的代码

在写代码的时候条件上是大家的代码越是精简越好。精简的代码的代表,越少的谬误的大概,在翻阅明白代码逻辑的时候成本的年华越少。

为此未来开首精简大家的代码吧。

笔者们能够肆意发现大家二种客户账户下拥有一样的点子:

.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);

大家可不得以只写贰回啊?我们事先将未注册的用户放在了抛出特别中,因为我们的折扣率只会一个钱打二16个结注册用户的时间限制,并不曾给未注册用户留有成效设定。所以,大家应该给未注册用户设定的时刻为多少吧?
-0年

那么相应的折扣率也将变成0了,那样大家就足以高枕无忧的将折扣率交付给未注册用户使用了,那就初阶吧!

 1 public class DiscountManager
 2 {
 3   public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
 4   {
 5     decimal priceAfterDiscount = 0;
 6     switch (accountStatus)
 7     {
 8       case AccountStatus.NotRegistered:
 9         priceAfterDiscount = price;
10         break;
11       case AccountStatus.SimpleCustomer:
12         priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS);
13         break;
14       case AccountStatus.ValuableCustomer:
15         priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS);
16         break;
17       case AccountStatus.MostValuableCustomer:
18         priceAfterDiscount = price.ApplyDiscountForAccountStatus(Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS);
19         break;
20       default:
21         throw new NotImplementedException();
22     }
23     priceAfterDiscount = priceAfterDiscount.ApplyDiscountForTimeOfHavingAccount(timeOfHavingAccountInYears);
24     return priceAfterDiscount;
25   }
26 }

大家还能将这一行移除到switch-case语句外面。好处正是:更少的代码量!

IX:提升-最终的获得彻底清洁的代码

好了,以往大家得以像阅读一本书一样方便来审视我们的代码了,可是那就够了么?大家能够将代码变得最佳不难的!

图片 8

好的,那就开首做一些改成来兑现那么些指标呢。大家得以使用重视注入和使用政策情势那三种办法。

那就是我们后天最终整理出来的代码了:

 1 public class DiscountManager
 2 {
 3   private readonly IAccountDiscountCalculatorFactory _factory;
 4   private readonly ILoyaltyDiscountCalculator _loyaltyDiscountCalculator;
 5  
 6   public DiscountManager(IAccountDiscountCalculatorFactory factory, ILoyaltyDiscountCalculator loyaltyDiscountCalculator)
 7   {
 8     _factory = factory;
 9     _loyaltyDiscountCalculator = loyaltyDiscountCalculator;
10   }
11  
12   public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
13   {
14     decimal priceAfterDiscount = 0;
15     priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
16     priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
17     return priceAfterDiscount;
18   }
19 }

 1 public interface ILoyaltyDiscountCalculator
 2 {
 3   decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears);
 4 }
 5  
 6 public class DefaultLoyaltyDiscountCalculator : ILoyaltyDiscountCalculator
 7 {
 8   public decimal ApplyDiscount(decimal price, int timeOfHavingAccountInYears)
 9   {
10     decimal discountForLoyaltyInPercentage = (timeOfHavingAccountInYears > Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY) ? (decimal)Constants.MAXIMUM_DISCOUNT_FOR_LOYALTY/100 : (decimal)timeOfHavingAccountInYears/100;
11     return price - (discountForLoyaltyInPercentage * price);
12   }
13 }

 1 public interface IAccountDiscountCalculatorFactory
 2 {
 3   IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus);
 4 }
 5  
 6 public class DefaultAccountDiscountCalculatorFactory : IAccountDiscountCalculatorFactory
 7 {
 8   public IAccountDiscountCalculator GetAccountDiscountCalculator(AccountStatus accountStatus)
 9   {
10     IAccountDiscountCalculator calculator;
11     switch (accountStatus)
12     {
13       case AccountStatus.NotRegistered:
14         calculator = new NotRegisteredDiscountCalculator();
15         break;
16       case AccountStatus.SimpleCustomer:
17         calculator = new SimpleCustomerDiscountCalculator();
18         break;
19       case AccountStatus.ValuableCustomer:
20         calculator = new ValuableCustomerDiscountCalculator();
21         break;
22       case AccountStatus.MostValuableCustomer:
23         calculator = new MostValuableCustomerDiscountCalculator();
24         break;
25       default:
26         throw new NotImplementedException();
27     }
28  
29     return calculator;
30   }
31 }

 1 public interface IAccountDiscountCalculator
 2 {
 3   decimal ApplyDiscount(decimal price);
 4 }
 5  
 6 public class NotRegisteredDiscountCalculator : IAccountDiscountCalculator
 7 {
 8   public decimal ApplyDiscount(decimal price)
 9   {
10     return price;
11   }
12 }
13  
14 public class SimpleCustomerDiscountCalculator : IAccountDiscountCalculator
15 {
16   public decimal ApplyDiscount(decimal price)
17   {
18     return price - (Constants.DISCOUNT_FOR_SIMPLE_CUSTOMERS * price);
19   }
20 }
21  
22 public class ValuableCustomerDiscountCalculator : IAccountDiscountCalculator
23 {
24   public decimal ApplyDiscount(decimal price)
25   {
26     return price - (Constants.DISCOUNT_FOR_VALUABLE_CUSTOMERS * price);
27   }
28 }
29  
30 public class MostValuableCustomerDiscountCalculator : IAccountDiscountCalculator
31 {
32   public decimal ApplyDiscount(decimal price)
33   {
34     return price - (Constants.DISCOUNT_FOR_MOST_VALUABLE_CUSTOMERS * price);
35   }
36 }

先是大家摆脱了扩张方法(也等于静态类),之所以要摆脱那种是因为扩张方法与折扣总括格局之间存在了紧耦合的关系。若是大家想要单元测试我们的章程ApplyDiscount的时候将变得不太简单,因为我们亟须联合测试与之牢牢关联的类PriceExtensions。

为了幸免那么些,俺创立了DefaultLoyaltyDiscountCalculator 类,那在那之中含有了ApplyDiscountForTimeOfHavingAccount壮大方法,同事本人经过架空中接力口ILoyaltyDiscountCalculator隐藏了他的实际贯彻。今后,当自个儿想测试我们的类DiscountManager的时候,作者就能够经过 ILoyaltyDiscountCalculator仿照注入虚构对象到DiscountManager类中经过构造函数字突显示测试成效。这里咱们选用的就叫信赖注入情势。

图片 9

在做这么些的同时,我们也将总结折扣率这几个成效安全的移交到另一个不等的类中,若是我们想要修改这一段的逻辑,那我们就只必要修改DefaultLoyaltyDiscountCalculator** **类就好了,而不要求转移别的的地点,那样收缩了在改动他的时候发出破坏别的地方的高风险,同时也不要求再追加单独测试的时辰了。

上边是我们在DiscountManager类中央银行使分其他逻辑类:

priceAfterDiscount =
_loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount,
timeOfHavingAccountInYears);

为了针对账户状态的逻辑来计量折扣率,小编创制了一部分相比复杂的东西。大家在DiscountManager类中有多少个职务必要解释出去。

  1. 基于账户状态如何选用相应的计算格局。
  2. 奇异计算办法的底细

为了将首先个义务移交出去,小编创设了工厂类(DefaultAccountDiscountCalculatorFactory),为了落到实处工厂情势,然后再把那么些隐蔽到虚幻IAccountDiscountCalculatorFactory里面去。

图片 10

俺们的厂子会操纵取舍哪一种计算方法。最终我们通过正视注册格局构造函数将工厂情势注射到DiscountManager类中

上面正是选拔了工厂的DiscountManager类:

priceAfterDiscount =
_factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);

 以上会针对区别的账户状态重返几时的方针,然后调用ApplyDiscount 方法。

首个义务已经被接入出去了,接下去正是第3个了。

 接下来大家就从头谈论策略了…..

图片 11

因为差别的账户状态会有永不的折扣总计办法,所以我们要求不一致的完毕政策。座椅万分适用于政策格局。

在大家的事例中,大家有两种政策:

NotRegisteredDiscountCalculator SimpleCustomerDiscountCalculator MostValuableCustomerDiscountCalculator**

她俩带有了现实的折扣总括办法的完结并被藏在了画个饼来解除饥饿IAccountDiscountCalculator里。

那就同意大家的类DiscountManager行使非常的策略,而不须要掌握具体的贯彻。大家的类只需求精晓与ApplyDiscount方法相关的IAccountDiscountCalculator 接口再次回到的目的的档次。

NotRegisteredDiscountCalculator, SimpleCustomerDiscountCalculator,
MostValuableCustomerDiscountCalculator
那些类富含了现实的经过账户状态采取切合总括的盘算方式的实现。因为我们的那四个政策看起来相似,大家唯一能做的大约就只有针对那两种总结策略创造3个格局然后每一种策略类通过贰个决不的参数来调用她。因为那会让我们的代码变得更为多,所以自个儿将来决定不这么做了。

好了,到方今甘休大家的代码变得可读了,而且各样类都唯有贰个专责了-那样修改他的时候会独自一一对应了:

  1. DiscountManager-管理代码流
  2. DefaultLoyaltyDiscountCalculator-可靠的持筹握算折扣率的法子
  3. DefaultAccountDiscountCalculatorFactory-决定依照账户状态采纳哪位策略来估测计算
  4. **NotRegisteredDiscountCalculator, SimpleCustomerDiscountCalculatorMostValuableCustomerDiscountCalculator **
    依据账户状态总括折扣率

到现在先河相比今后与事先的不二法门:

 1 public class Class1
 2 {
 3     public decimal Calculate(decimal amount, int type, int years)
 4     {
 5         decimal result = 0;
 6         decimal disc = (years > 5) ? (decimal)5 / 100 : (decimal)years / 100;
 7         if (type == 1)
 8         {
 9             result = amount;
10         }
11         else if (type == 2)
12         {
13             result = (amount - (0.1m * amount)) - disc * (amount - (0.1m * amount));
14         }
15         else if (type == 3)
16         {
17             result = (0.7m * amount) - disc * (0.7m * amount);
18         }
19         else if (type == 4)
20         {
21             result = (amount - (0.5m * amount)) - disc * (amount - (0.5m * amount));
22         }
23         return result;
24     }
25 }

那是我们的新的重构的代码:

1 public decimal ApplyDiscount(decimal price, AccountStatus accountStatus, int timeOfHavingAccountInYears)
2 {
3   decimal priceAfterDiscount = 0;
4   priceAfterDiscount = _factory.GetAccountDiscountCalculator(accountStatus).ApplyDiscount(price);
5   priceAfterDiscount = _loyaltyDiscountCalculator.ApplyDiscount(priceAfterDiscount, timeOfHavingAccountInYears);
6   return priceAfterDiscount;
7 }

总结

在本文中,代码被无限简化了,使得全部的技巧和方式的分解更便于了。它显得了何等缓解广大的编制程序难题,以及利用优良的执行和设计形式以适龄、干净的情势消除那个题指标补益。

在自身的工作经历中,作者再三在这篇小说中强调了不良的做法。它们鲜明存在于广大用出席合,而不是在二个类中,如在自笔者的事例中那样,那使得发现它们进一步不便,因为它们隐藏在适宜的代码之间。写那种代码的人两次三番争持说,他们依据的是简约鸠拙的平整。不幸的是,差不离拥有的系统都在成长,变得非常复杂。然后,那些大致的、不可扩大的代码中的每三个修改都是不行首要的,并且带来了惊天动地的高危机。

请牢记,您的代码将短期存在于生产条件中,并将在种种事情要求变动上拓展改动。因而编写过于简短、不可增加的代码十分的快就会爆发严重的后果。最终一点是对开发人士有利,特别是那三个在你协调随后维护你的代码。

尽管你有局地题目依据文章不要犹豫联系笔者!

图片 12PS:

根据作品敲的源代码:

链接:https://pan.baidu.com/s/1i7BH6Krl_vPnwtBCJjpQ8w 密码:9spz

相关文章