以前寫C++的時候曾經在自己網站上發表過一個編碼“簡單性”之文章,現在編寫C#了才發現自己無意之間就會寫下一些浪費螢幕的代碼。
下面是自己編碼中偶然發現的一些案例,歡迎中等水準的程式設計者參考。因為要積累案例,是以随時更新。
編碼簡單性的“心法”就是:隻要螢幕上有任何兩部分代碼看上去相似,則一定有合并辦法。
無論在微觀還是宏觀層面上這一點都适合。在02年的時候,我們曾在2小時内把一個程式員的4000多行的65個函數變為一個函數,相當于一個月的工作量被取代;04年則令人發指地發生了1個人用1.5年重新編寫了13個人編寫了9年的程式的事件。
是以,應随時關注代碼中的“不簡潔”現象,一旦放任其發生,軟體将很難維護。
案例1 合并相似代碼
以前遇到過的最極端的例子是:
switch (n)
{
case 1: return 1;
case 2: return 2;
case 3: return 3;
case 4: return 4;
case 5: return 5;
case 6: return 6;
default: return n;
}
這段代碼其實相當于:return n;
這可不是個笑話,是01年一位還不錯的同僚編寫的,原封未動就是這個樣子6個整數。當我們指出來的時候,她忍不住笑了……人就這樣,總有走火入魔的時候。
2011-07-31:今天不小心自己寫的一段代碼:
if (!result.Contains("true"))
_repSFC.GrantAuthorityToRole(authority, role, false);
if (result.Contains("true"))
_repSFC.GrantAuthorityToRole(authority, role, true);
其實就是:
_repSFC.GrantAuthorityToRole(authority, role, result.Contains("true"));
要發現它們,隻需要牢記心法:隻要螢幕上有任何兩部分代碼看上去相似,則一定有合并辦法。
案例1.5 多用?+:文法
另一個小案例:
if (Misc == null)
return SFCCatches.LinkP2Cs.Where(i => i.P == p && i.C == c);
else
return SFCCatches.LinkP2Cs.Where(i => i.P == p && i.C == c && i.Misc == Misc);
改為:
return SFCCatches.LinkP2Cs.Where(i => i.P == p && i.C == c && (Misc == null ? true : i.Misc == Misc));
有時候感覺這種寫法有點花哨,但是習慣以後,實際可讀性要高得多,尤其如果單行代碼挺長的時候。
案例2 推遲分支(請先看案例4)
這個長一些,原來的代碼是aspx,在重構成Razor的時候發現可以簡化。
<% foreach ( var techDebt in Model.TechDebts)
{ %>
<% if (techDebt.IsOpen == true)
<div class = "Black">
<a href="/Agile/TechDebts/Edit/<%= techDebt.TechDebtID %>" target = "_blank" ><img title = "編輯" alt = "編輯" class = "icons" src="../../../../Resouces/Images/XXX/TechDebts/TechDebtOpening.png" /></a>
<%= techDebt.Type %>-<%= techDebt.Title %><br />
</div>
<%}
<div class = "Gray">
<a href="/Agile/TechDebts/Edit/<%= techDebt.TechDebtID %>" target = "_blank" ><img title = "編輯" alt = "編輯" class = "icons" src="../../../../Resouces/Images/XXX/TechDebts/TechDebtsClosed.png" /></a>
<%} %>
變成Razor後等同于:
@foreach ( var techDebt in Model.TechDebts)
{
string color = techDebt.IsOpen ? "Black" : "Gray"; //也可以放到下面的@color那裡,但不太直覺。
string img = techDebt.IsOpen ? "TechDebtOpening.png" : "TechDebtsClosed.png"; //同上。
<div class = @color>
@[email protected]<br />
19行代碼變成9行。雖然看起來不起眼,但同比例放大10000倍看:把9萬行代碼編寫成19萬行,絕對是一個災難。
同時注意color/img的處理,因為若把他們嵌入下面的代碼中,代碼會顯得不直覺,是以無需處理。畢竟看似多了2行“無用代碼”,但他們與别處并無重複,了解起來也更簡單。是以簡單性是資訊的簡單性/無重複性,而不是簡單地删除分号。
案例3
下面一段代碼中,三個TD中的三個函數PrevViews/SameViews/NextViews破壞了簡單的合并:
<td>
<ol id = "@Model.DropableID("PREV")" class = "dragable">
@foreach (var view in Model.PrevViews())
<li class = "black" id = "@view.DragableID()">
@Html.ImageLink("../../Resouces/Images/" + @view.Area + "/" + @view.Controller + "/" + @view.Action + "16.png",
view.Title, "imagelink", false, view.Action, view.Controller, view.Area, new { }, new { }, new { })
@(view.Title + "(" + view.Area + "/" + view.Controller + "/" + view.Action + ")")<br />
</li>
<br />
</ol>
</td>
<ol id = "@Model.DropableID("SAME")" class = "dragable">
@foreach (var view in SFCView.SameViews())
<ol id = "@Model.DropableID("NEXT")" class = "dragable">
@foreach (var view in SFCView.NextViews())
解決方法是使用Model傳入三個IEnumerable:
<ol id = "@Model.DroppableID(SFCView.PREV)" class = "dragable">
@{Html.RenderPartial("_ViewList", Model.RelatedViews(SFCView.PREV));}
<ol id = "@Model.DroppableID(SFCView.SAME)" class = "dragable">
@{Html.RenderPartial("_ViewList", Model.RelatedViews(SFCView.SAME));}
<ol id = "@Model.DroppableID(SFCView.NEXT)" class = "dragable">
@{Html.RenderPartial("_ViewList", Model.RelatedViews(SFCView.NEXT));}
這裡就固定三種情況,基本上這樣可讀性和可維護性就可以了。
如果重複的次數更多,就要考慮把td一起搬進一個更大的循環體中。
案例4 2011-05-05:推遲分支
大緻意思就是不要寫:
if (...)
A();
B();
C();
而是要寫
心法是:任何兩個地方看上去相似,就可以簡化。技法是:相同部分放在分支前或後,不同部分才是分支。
這個案例看起來是愚蠢的,因為誰能看不出來呢,但實際結果不然,比如下面這段今天剛要寫的代碼(在一個url後面加上新的參數zoom=1):
url = (uri.Query.Count() == 0) ? uri.PathAndQuery + "?zoom=" + level : uri.PathAndQuery + "&zoom=" + level;
其實應該是:
uri.PathAndQuery + (uri.Query.Count() == 0 ? "?" : "&") + "zoom=" + level;
更隐蔽的是案例2。
本文轉自火星人陳勇 51CTO部落格,原文連結:http://blog.51cto.com/cheny/1100081