《重构-改善既有代码设计》案例之C#版(1)
《重构-改善既有代码设计》是被众多程序员推荐的一本经典。但问题是其中的一些案例是js写的。作为一个c#开发人员,看起来不太习惯。所以特意抄袭了一版C#代码。
我想重构的一个重要的好处就是方便理解,易于修改。所以我就不说这段代码的功能是啥了(大概就是剧团根据 费用清单 计算金额,积分,并输出结果字符串)。好的代码应该就是一眼就能看出是干啥的。如果一段代码理解起来非常困难,那就说明这段代码需要重构了。
不再废话,直接COPY代码(未重构的原始代码),之后会将重构的代码慢慢放出来。重现书中重构的手法以及效果。作为自己学习的记录。
剧目类
Play.cs
1 class Play 2 { 3 public string name; 4 5 public string type; 6 }
演出类
Performance.cs
1 class Performance 2 { 3 public string playID; 4 5 public int audience; 6 }
费用清单类
Invoice.cs
1 class Invoice 2 { 3 public string Customer; 4 5 public List<Performance> performances=new List<Performance>(); 6 }
剧团类
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 }
最后就是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 }
执行结果
话说,像我这种不求上进的百度程序猿。一开始觉得这些代码没啥问题啊。甚至还觉得命名挺规范=.=
想要学好重构,就必须拥有一些程序猿的直觉,能够嗅出代码的坏味道。
首先这个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 }
这还不够,对于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 }
编译,启动,OK通过。每次做一点点修改,小步快跑
本篇完成...待续....