以前写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>
<%}
else
{ %>
<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>
<%= techDebt.Type %>-<%= techDebt.Title %><br />
</div>
<%} %>
<%} %>
变成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>
<a href="/Agile/TechDebts/Edit/@techDebt.TechDebtID" target = "_blank" ><img title = "编辑" alt = "编辑" class = "icons" src="../../../../Resouces/Images/XXX/TechDebts/@img" /></a>
@techDebt.Type-@techDebt.Title<br />
</div>
}
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>
<td>
<ol id = "@Model.DropableID("SAME")" class = "dragable">
@foreach (var view in SFCView.SameViews())
{
<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>
<td>
<ol id = "@Model.DropableID("NEXT")" class = "dragable">
@foreach (var view in SFCView.NextViews())
{
<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>
解决方法是使用Model传入三个IEnumerable:
<td>
<ol id = "@Model.DroppableID(SFCView.PREV)" class = "dragable">
@{Html.RenderPartial("_ViewList", Model.RelatedViews(SFCView.PREV));}
<br />
</ol>
</td>
<td>
<ol id = "@Model.DroppableID(SFCView.SAME)" class = "dragable">
@{Html.RenderPartial("_ViewList", Model.RelatedViews(SFCView.SAME));}
<br />
</ol>
</td>
<td>
<ol id = "@Model.DroppableID(SFCView.NEXT)" class = "dragable">
@{Html.RenderPartial("_ViewList", Model.RelatedViews(SFCView.NEXT));}
<br />
</ol>
</td>
这里就固定三种情况,基本上这样可读性和可维护性就可以了。
如果重复的次数更多,就要考虑把td一起搬进一个更大的循环体中。
案例4 2011-05-05:推迟分支
大致意思就是不要写:
if (...)
{
A();
B();
}
else
{
A();
C();
}
而是要写
A();
if (...)
{
B();
}
else
{
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