《重构-改善既有代码设计》案例之C#版(1)

《重构-改善既有代码设计》是被众多程序员推荐的一本经典。但问题是其中的一些案例是js写的。作为一个c#开发人员,看起来不太习惯。所以特意抄袭了一版C#代码。

  我想重构的一个重要的好处就是方便理解,易于修改。所以我就不说这段代码的功能是啥了(大概就是剧团根据 费用清单 计算金额,积分,并输出结果字符串)。好的代码应该就是一眼就能看出是干啥的。如果一段代码理解起来非常困难,那就说明这段代码需要重构了。

  不再废话,直接COPY代码(未重构的原始代码),之后会将重构的代码慢慢放出来。重现书中重构的手法以及效果。作为自己学习的记录。

剧目类

Play.cs

1     class Play
2     {
3         public string name;
4 
5         public string type;
6     }
View Code

演出类

Performance.cs

1     class Performance
2     {
3         public string playID;
4 
5         public int audience;
6     }
View Code

费用清单类  

Invoice.cs

1     class Invoice
2     {
3         public string Customer;
4 
5         public List<Performance> performances=new List<Performance>();
6     }
View Code

剧团类

OperaTroupe.cs 在构造函数中的是演示数据

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Globalization;
 4 
 5 namespace Refactoring
 6 {
 7     public class OperaTroupe
 8     {
 9         private readonly Dictionary<string, Play> _plays;
10 
11         private readonly Invoice _invoice;
12 
13         public OperaTroupe()
14         {
15             _invoice = new Invoice {Customer = "BigCo"};
16             _invoice.performances.Add(new Performance {playID = "hamlet", audience = 55});
17             _invoice.performances.Add(new Performance {playID = "as-like", audience = 35});
18             _invoice.performances.Add(new Performance {playID = "othello", audience = 40});
19             _plays = new Dictionary<string, Play>();
20             _plays.Add("hamlet", new Play {name = "hamlet", type = "tragedy"});
21             _plays.Add("as-like", new Play {name = "As You Like It", type = "comedy"});
22             _plays.Add("othello", new Play {name = "Othello", type = "tragedy"});
23         }
24 
25         public string Statement()
26         {
27             int totalAmount = 0;
28             int volumeCredits = 0;
29             string result = $"Statement for {_invoice.Customer} \n";
30             NumberFormatInfo nfi = new CultureInfo("en-US").NumberFormat;
31             nfi.CurrencyDecimalDigits = 2;
32 
33             foreach (var perf in _invoice.performances)
34             {
35                 Play play = _plays[perf.playID];
36                 int thisAmount;
37                 switch (play.type)
38                 {
39                     case "tragedy":
40                         thisAmount = 40000;
41                         if (perf.audience > 30)
42                         {
43                             thisAmount += 1000 * (perf.audience - 30);
44                         }
45 
46                         break;
47                     case "comedy":
48                         thisAmount = 30000;
49                         if (perf.audience > 20)
50                         {
51                             thisAmount += 1000 + 500 * (perf.audience - 20);
52                         }
53 
54                         break;
55                     default:
56                         throw new Exception($"unknown type:{play.type}");
57                 }
58 
59                 //add  volume credits
60                 volumeCredits += Math.Max(perf.audience - 30, 0);
61                 //add extra credit for every ten comedy attendees
62                 if ("comedy" == play.type)
63                 {
64                     volumeCredits += perf.audience / 5;
65                 }
66 
67                 //print line for this order
68                 result += $"{play.name}: {string.Format(nfi, "{0:C}", thisAmount / 100)}({perf.audience}seats)\n";
69                 totalAmount += thisAmount;
70             }
71 
72             result += $"Amount owed is {string.Format(nfi, "{0:C}", totalAmount / 100)}\n";
73             result += $"You earned {volumeCredits} credits \n";
74             return result;
75         }
76     }
77 }
View Code

最后就是main函数啦

 1 using System;
 2 
 3 namespace Refactoring
 4 {
 5     internal class Program
 6     {
 7         private static void Main(string[] args)
 8         {
 9             string result = new OperaTroupe().Statement();
10             Console.Write(result);
11             Console.Read();
12         }
13     }
14 }
View Code

执行结果

 话说,像我这种不求上进的百度程序猿。一开始觉得这些代码没啥问题啊。甚至还觉得命名挺规范=.= 

 想要学好重构,就必须拥有一些程序猿的直觉,能够嗅出代码的坏味道。

 首先这个Statement方法体太长了。。。如果整个项目都是这种代码,我敢保证最多看半个小时,就觉得累了。。。

 所以这里就有 代码的坏味道之: 过长函数

 初一看,这个switch分支处理代码就可以提取出来单独作为一个方法,这个技巧就是提炼函数.然后我们就给这个方法取一个方法名,取名自古就是一个讲究的活。最好能够根据名字就知道这个方法是要干什么的。如果项目里面的方法名都是methodA这样随意的话,那阅读这些代码太浪费时间和精力了。还没看两个方法就累了。。这还怎么玩。。。这就是另外一种代码的坏味道:神秘命名。说干就干

 

 1         public string Statement()
 2         {
 3             int totalAmount = 0;
 4             int volumeCredits = 0;
 5             string result = $"Statement for {_invoice.Customer} \n";
 6             NumberFormatInfo nfi = new CultureInfo("en-US").NumberFormat;
 7             nfi.CurrencyDecimalDigits = 2;
 8 
 9             foreach (var perf in _invoice.performances)
10             {
11                 Play play = _plays[perf.playID];
12                 int thisAmount = AmountFor(perf, play);
13 
14                 //add  volume credits
15                 volumeCredits += Math.Max(perf.audience - 30, 0);
16                 //add extra credit for every ten comedy attendees
17                 if ("comedy" == play.type)
18                 {
19                     volumeCredits += perf.audience / 5;
20                 }
21 
22                 //print line for this order
23                 result += $"{play.name}: {string.Format(nfi, "{0:C}", thisAmount / 100)}({perf.audience}seats)\n";
24                 totalAmount += thisAmount;
25             }
26 
27             result += $"Amount owed is {string.Format(nfi, "{0:C}", totalAmount / 100)}\n";
28             result += $"You earned {volumeCredits} credits \n";
29             return result;
30         }
31 
32         private int AmountFor(Performance perf, Play play)
33         {
34             int thisAmount;
35             switch (play.type)
36             {
37                 case "tragedy":
38                     thisAmount = 40000;
39                     if (perf.audience > 30)
40                     {
41                         thisAmount += 1000 * (perf.audience - 30);
42                     }
43 
44                     break;
45                 case "comedy":
46                     thisAmount = 30000;
47                     if (perf.audience > 20)
48                     {
49                         thisAmount += 1000 + 500 * (perf.audience - 20);
50                     }
51 
52                     break;
53                 default:
54                     throw new Exception($"unknown type:{play.type}");
55             }
56 
57             return thisAmount;
58         }
View Code

 

 这还不够,对于AmountFor这个方法,还要做两处小的修改.perf这样的缩写对于理解没有任何帮助,所以要把它改回原名aPerformance(不要缩写也是代码美学之一).在方法内部我也觉得thisAmount改为result这样更便于理解。如此一来代码就成了这样

 1         public string Statement()
 2         {
 3             int totalAmount = 0;
 4             int volumeCredits = 0;
 5             string result = $"Statement for {_invoice.Customer} \n";
 6             NumberFormatInfo nfi = new CultureInfo("en-US").NumberFormat;
 7             nfi.CurrencyDecimalDigits = 2;
 8 
 9             foreach (var perf in _invoice.performances)
10             {
11                 Play play = _plays[perf.playID];
12                 int thisAmount = AmountFor(perf, play);
13 
14                 //add  volume credits
15                 volumeCredits += Math.Max(perf.audience - 30, 0);
16                 //add extra credit for every ten comedy attendees
17                 if ("comedy" == play.type)
18                 {
19                     volumeCredits += perf.audience / 5;
20                 }
21 
22                 //print line for this order
23                 result += $"{play.name}: {string.Format(nfi, "{0:C}", thisAmount / 100)}({perf.audience}seats)\n";
24                 totalAmount += thisAmount;
25             }
26 
27             result += $"Amount owed is {string.Format(nfi, "{0:C}", totalAmount / 100)}\n";
28             result += $"You earned {volumeCredits} credits \n";
29             return result;
30         }
31 
32         private int AmountFor(Performance aPerformance, Play play)
33         {
34             int result;
35             switch (play.type)
36             {
37                 case "tragedy":
38                     result = 40000;
39                     if (aPerformance.audience > 30)
40                     {
41                         result += 1000 * (aPerformance.audience - 30);
42                     }
43 
44                     break;
45                 case "comedy":
46                     result = 30000;
47                     if (aPerformance.audience > 20)
48                     {
49                         result += 1000 + 500 * (aPerformance.audience - 20);
50                     }
51 
52                     break;
53                 default:
54                     throw new Exception($"unknown type:{play.type}");
55             }
56 
57             return result;
58         }
View Code

编译,启动,OK通过。每次做一点点修改,小步快跑

本篇完成...待续....

下一篇:《重构-改善既有代码设计》案例之C#版(2)

 

posted @ 2023-03-11 18:14  吾乃零陵上将军邢道荣  阅读(94)  评论(0编辑  收藏  举报