这是编码简单性系列中的其中一篇,之前几篇包括代码篇/函数篇/语义篇。 因为要积累案例,会随时更新。
之前提到:编码简单性的“心法”就是:只要屏幕上有任何两部分代码看上去相似,则一定有合并办法。而说起相似,没有比switch - case的各段代码更相似的了。如果细数一下自己产品中最长的函数,里边几乎肯定的有一个switch - case,或者一堆if -else if(两者其实等同)。一般各段代码看似相同,又有点不同,既不能变成函数,也不能变成类,怎么办呢?
解决臭长的switch-case的最好方法,就是泛型(在C++时代叫做模板)。泛型不好学,但是却很重要。02年左右的时候曾经作为过程改进人员进行代码审查,无意中发现一段在pagedown的时候做波浪状起伏的代码,观察发现其中有65个函数其实可以缩减为1个函数(没错,65:1),其内容是在5种int常数下,处理13种不同的变量,而处理方法完全相同。这段代码共有4000行,已经耗时一个月,编程者月薪7000(那是10年前,7000元可以购买5平米小产权房,或2平米大产权房);当天下午它们就变成了1个函数,长度不足55行。据此推断,每年因为不能灵活使用泛型而造成的经济损失,可能达到亿元以上。
案例1 2011-05-27 一个简单泛型类
下面一段代码很简短,显得很不值得改造,但其实除了Display之外还有很多函数,其中一些case体很长,而且各种type日后层出不穷,因此不得不进行改造。
Display的目的,是让各种各样的udc们以自己的方式显示器Value中的数值:
- [csharp] view plaincopy
- 01.
- 02.public static MvcHtmlString Display(this HtmlHelper htmlHelper, UDC udc)
- 03. {
- 04. switch (udc.Type)
- 05. {
- 06. case "Text100":
- 07. return new MvcHtmlString((udc.Value != null) ? udc.Value.ToString() : "NULL");
- 08. case "Text20":
- 09. return new MvcHtmlString((udc.Value != null) ? udc.Value.ToString() : "NULL");
- 10. case "Date":
- 11. return new MvcHtmlString(((DateTime)udc.Value).ToShortDateString());
- 12. default: return new MvcHtmlString(string.Format("Unknown UDC type: {0}", udc.Type));
- 13. }
- 14. }
为了能够拆开这个函数,需要声明
- [csharp] view plaincopy
- 01.
- 02.public interface IUDC
- 03. {
- 04. ……
- 05.
- 06. MvcHtmlString Display { get; }
- 07. ……
- 08.
- 09. }
然后让一个类(这个类一般都存在了)继承这个接口,并实现其方法:
- [csharp] view plaincopy
- 01.
- 02.public partial class UDCText100 : IUDC
- 03. {
- 04. ……
- 05.
- 06. public MvcHtmlString Display { get { return new MvcHtmlString((Value != null) ? Value.ToString() : "NULL"); } }
- 07. ……
- 08.
- 09. }
而显示方式也从@Html.Display(udc)变成@udc.Display。
这段代码整个修改后,原来的5个switch-case只剩下1个,代码明显更内聚了,也就是每次增加一个类型,基本只需要在一个地方(partial class)内完成修改,就可以了;修改的成果在编译时就能确认是否充分和正确(而在其中switch-case中漏掉一个类型,只有在运行的时候case找不到才知道)。案例2 2011-05-29 泛型函数
- [csharp] view plaincopy
- 01.
- 02.private IUDC GetOrDefaultUDC<T, V>(Table<T> table, ..., V defaultValue) where T : class, IUDC, new()
- 03. {
- 04. var t = table.SingleOrDefault(...);
- 05. if (t == null)
- 06. {
- 07. t = new T();
- 08. ...
- 09. t.OValue = defaultValue;
- 10. ...
- 11.
- 12. }
- 13. return t;
- 14. }
这个泛型函数从一类表中取出一条记录,如果没有,则创建它。由于创建的时候需要new 一个新的纪录,而且新纪录依据table的不同,某些字段的缺省值也不同。用传统的方法需要编写很多函数,而且每当出现一种新的这类表,又需要编写新的函数。本文开头提到的的那个65个函数就是因此而产生的。
处理这类情况的心法是:如果发现由于类型的差异一些看起来很像的代码无法合并为函数,那么就应该用泛型了。
这种情况下用泛型需要掌握两种主要技术:new()和Ixxx(某Interface)。new的目的是让T可以创建(如果函数中不创建就不需要了),而Interface的目的是保证编译时刻就能确保未来的新类型依然可以使用这个函数(C++的时代只有用到新的类型的时候才可以确认)。
这里只谈泛型的应用思想,关于泛型的应用技术,在MSDN有一篇很好的文章可供参考:http://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx。此文将整个C#泛型的发展历程从头到尾讲了一遍。
本文转自火星人陈勇 51CTO博客,原文链接:http://blog.51cto.com/cheny/1100096