這是編碼簡單性系列中的其中一篇,之前幾篇包括代碼篇/函數篇/語義篇。 因為要積累案例,會随時更新。
之前提到:編碼簡單性的“心法”就是:隻要螢幕上有任何兩部分代碼看上去相似,則一定有合并辦法。而說起相似,沒有比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. }
為了能夠拆開這個函數,需要聲明
01.
02.public interface IUDC
03. {
04. ……
05.
06. MvcHtmlString Display { get; }
07. ……
08.
09. }
然後讓一個類(這個類一般都存在了)繼承這個接口,并實作其方法:
02.public partial class UDCText100 : IUDC
06. public MvcHtmlString Display { get { return new MvcHtmlString((Value != null) ? Value.ToString() : "NULL"); } }
而顯示方式也從@Html.Display(udc)變成@udc.Display。
這段代碼整個修改後,原來的5個switch-case隻剩下1個,代碼明顯更内聚了,也就是每次增加一個類型,基本隻需要在一個地方(partial class)内完成修改,就可以了;修改的成果在編譯時就能确認是否充分和正确(而在其中switch-case中漏掉一個類型,隻有在運作的時候case找不到才知道)。案例2 2011-05-29 泛型函數
02.private IUDC GetOrDefaultUDC<T, V>(Table<T> table, ..., V defaultValue) where T : class, IUDC, new()
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;
這個泛型函數從一類表中取出一條記錄,如果沒有,則建立它。由于建立的時候需要new 一個新的紀錄,而且新紀錄依據table的不同,某些字段的預設值也不同。用傳統的方法需要編寫很多函數,而且每當出現一種新的這類表,又需要編寫新的函數。本文開頭提到的的那個65個函數就是是以而産生的。
處理這類情況的心法是:如果發現由于類型的差異一些看起來很像的代碼無法合并為函數,那麼就應該用泛型了。
這種情況下用泛型需要掌握兩種主要技術:new()和Ixxx(某Interface)。new的目的是讓T可以建立(如果函數中不建立就不需要了),而Interface的目的是保證編譯時刻就能確定未來的新類型依然可以使用這個函數(C++的時代隻有用到新的類型的時候才可以确認)。
本文轉自火星人陳勇 51CTO部落格,原文連結:http://blog.51cto.com/cheny/1100096