作者:陈勇
出处:blog.csdn.net/cheny_com
这是编码简单性系列中的其中一篇,之前几篇包括代码篇和语义篇。
因为要积累案例,会随时更新。
之前提到:编码简单性的“心法”就是:只要屏幕上有任何两部分代码看上去相似,则一定有合并办法。但在代码层面,无论如何简化,都常常剩下一堆相似代码,这时候就需要编写函数了。
这里指的函数,包括HtmlHelper这类用C#解决Html的函数。
应随时关注代码中的“不简洁”现象,一旦放任其发生,软件将很难维护。
案例1 从Html代码抽取MVC HtmlHelper
为了在屏幕上显示一些大按钮(下方显示其文字),编写了这些代码。为了对齐/消除边框/Hover效果等,动用了几个class来设置格式。按钮很多,所以需要相当数量的拷贝粘贴,其中文字-alt-title还是重复的,每次都要改动三个地方……
这些,都是代码正在变复杂的坏味道。
以其中两次重复为例,说明修改方法:
<td class = "noborder nopaddingv">
<div class = "aligncenter"> <a href="/Home/Index"> <img src="../../Resouces/Images/Agile/Menu/Campass48.png" alt = "站点地图" title = "站点地图" class = "imagelink"/></a><br /> 站点地图 </div> </td> <td class = "noborder nopaddingv"> <div class = "aligncenter"> <a href="/Home/Index" target = "_blank"> <img src="../../Resouces/Images/Agile/Help/Help48.png" alt = "帮助" title = "帮助" class = "imagelink"/></a><br /> 帮助 </div> </td>手段就是做HtmlHelper(以前没做过复杂的,所以推到现在才做),结果是:
@Html.MenuImageButtonV("站点地图", false, "/Home/Index", "../../Resouces/Images/Agile/Menu/Campass48.png")
@Html.MenuImageButtonV("帮助", true, "/Home/Index", "../../Resouces/Images/Agile/Help/Help48.png")Helper的内容就不展示了,网上一查都会。唯一说明的就是class变成了style,因为在函数内部引用class是危险的,不能保证被复用后class也在。
突然想起来当年曾经写过一句话很好地概括了这种技法:当两段代码相同时,可以变成一个函数和两次调用,相同的部分就是函数,不同的部分就是调用参数。
可以看到本例中,只有文字(title/alt)、是否新窗口打开、链接、图片不同,所以就是一个有四个参数的函数。
案例2 2011-05-04 函数接口的简洁性
不是编写了函数就万事大吉了,不好的函数接口依然不简洁。下面是一个刚改好的例子:
@Html.ImageLink("燃烧图", true, "../../Resouces/Images/Agile/CurrentSprint/BurndownData16.png", "icons", false, "/Agile/CurrentSprint/BurndownData", null, null)
@Html.ImageLink("迭代计划", true, "../../Resouces/Images/Agile/Sprints/Index16.png", "icons", false, "/Agile/Sprints/Index", null, null)
...
像这样的函数重复上几回也很让人头痛的,关键臭长的函数还看不到尾部,很容易出错或影响阅读。
这时候的技法是:如果函数在重复调用时有些参数变化,有些参数基本不变("icons", true, false, null....),后者应该变成一些可选参数并设置缺省值。
修改后的结果(另外一段代码):
@Html.ImageLink("迭代计划", "/Agile/Sprints/Index", true) //这个true有些地方不太一样所以留下了。
@Html.ImageLink("迭代故事", "/Agile/Sprints/SprintStories", true)...
唯一的不太好理解的是图片的链接哪去了。我在后台目录做了一套命名规则,若imgPath为缺省值null,则会根据命名规则生成默认图片名称,保证图片可以用Area/Controller/Actoin定位。这样也简化了实现和维护,不用去在脑海里便维护一套图片与链接的对应关系,也是编码简单性的一种体现。案例3 2011-05-04 MS Chart Winform版本与MVC3 Razor Helper的比较
在Winform版本中,一个Chart除了要在aspx中放置控件外,还要这样设置参数:
Chart1.Series["Series1"].ChartType = (SeriesChartType) Enum.Parse( typeof(SeriesChartType);
Chart1.Series["Series2"].ChartType = (SeriesChartType) Enum.Parse( typeof(SeriesChartType); ChartTypeList.SelectedItem.Text, true );
Chart1.Series["Series1"].IsValueShownAsLabel = true;
Chart1.Series["Series2"].IsValueShownAsLabel = true;Chart1.Series["Series1"]["LabelStyle"] = PointLabelsList.SelectedItem.Text;Chart1.Series["Series2"]["LabelStyle"] = PointLabelsList.SelectedItem.Text;...这是非常难读的代码,至少相当的不直观,MVC3的程序员显然要厉害得多(应该是MS意识最好的程序员了,要不做好不MVC),他们的代码长得这个样子:
new Chart(400, 200, ChartTheme.Blue)
.AddTitle(title).DataBindTable(data, "X").Write("png");看来是由于工作量问题,beta版本实际上只实现了很有限的几个函数,但实现得却很漂亮。按照这种风格写上面那个Chart1,应该是(仅展示风格):new Chart()
.AddSeries( new Series("Series1",
ChartType: (SeriesChartType) Enum.Parse( typeof(SeriesChartType)),
IsValueShownAsLabel: true)
.AddLable( new Label(
LableStyle:PointLabelsList.SelectedItem.Text)
....
这种风格很类似JQuery中的风格,是一种“写得少,做得多”的风格,其心法就是:代码里边没有任何两个地方看上去相似。
案例4 2011-05-05 函数接口不简洁的深层原因
01年左右曾经有一家国内排名前3的家电厂商做了一款机顶盒在我们公司联合测试。此机顶盒稳定性很差,其程序员长驻现场有问题就改,而问题也从不停息,比同期测试的其他几家机顶盒差很多。一年后有一个偶然的机会被请去评价一段机顶盒代码是否可用,从函数名称等蛛丝马迹判断就是这个厂家的代码,答案是:不可用。
那些代码外观的第一感觉是像一篇小说,每行都很长,能自成段落的都有,有点像案例3中开始不好的代码且更甚之,最后对代码进行了局部改造,量化评价的结果是实际所需代码大概是已有代码的1/6。
怎么才能变成案例3里边后面那种长得像诗词而不是小说的代码呢?
当时我们分析那段机顶盒代码,问题出在类的封装上,总结一下包括:
1. 类的内聚性不好,多数函数属于哪个类都不好,因为哪个类的变量都不足以支撑计算,只得在单独声明一个函数(当时是用C++,C#不允许了),传入大量类。
2. 类的封装不好,多数类都使用public的内部数据,很容易出现Chart1.Series["Series2"]["LabelStyle"] = ...这种从chart挖到Series,从Series挖到LableStyle才能干活的情况,所以函数调用臭长。
再深入挖掘一下形成这种结果的原因,在于多数程序员(本人也一样)都喜欢先从类的变量而非类的函数开始设计,最终导致类更适合存放数据而不适合做事。一种解决办法就是所谓调用驱动开发,就是先把要调用的代码写出来,打桩,再实现。
点击下载免费的敏捷开发教材:《》