的代码
所谓显而易见的代码,就是看上去和别处相同的代码。
在这个例子中,就是View‘中初始页面显示的内容与未来刷新的内容重复;Controller中初始显示的运算和刷新的相同。
Controller好办,如此:
private void PrepareAssignItemsData(int sprintID) { var sprint = ... var team = ... var overTimes = ...; var itemsTreeInSprint = ... ViewBag.AssignItemsViewModel = ... ViewBag.ViewModel = ... } public ActionResult AssignItems(int sprintID) { PrepareAssignItemsData(sprintID); return View("~/Areas/SFC/Views/Items/ItemTree.cshtml"); } public ActionResult AjaxRefreshAssignItemsLeftPad(int sprintID) { PrepareAssignItemsData(sprintID); return View("~/Areas/Agile/Views/PlanningMeetings/AjaxRefreshAssignItemsLeftPad.cshtml"); }
而cshtml页面中,只需要添加一个在页面初始化时自动刷新的函数,就能把<div id = "leftpad">变成空的,由刷新页面来填充。代码变成:
<script type = "text/javascript"> $(document).ready(function () { refreshLeft(); }); function refreshLeft() { $("#refreshLeft").click(); }; </script> <div style = "display: no1ne"> @SFCUI.Link("refresh", "/Agile/PlanningMeetings/AjaxRefreshAssignItemsLeftPad?sprintID=" + assignItemsViewModel.Sprint.ID, ajaxUpdateTargetID: "leftpad", ajaxOnSuccess: "refreshAll", id: "refreshLeft") </div> <div id = "leftpad"> </div>
$(document).ready是多出来的代码,负责第一次初始化时填充<div id = "leftpad>的内容。
封装多余的技术代码
何为多余的技术代码?之前在AjaxValue系列中曾经提到过接口封装的两个原则:
最小信息原则:方法接口应只传递最必须的业务信息。
包括两个层面:
1. 技术数据不要传递
2. 业务数据不能重复
现在,先从业务角度分析,这个函数接口设计中,到底哪些信息是必须的;让我们来用这一原则,把上面最后的一段cshtml代码,变成一行代码。
1. 如果要刷新,应该调用什么函数(一般是一个JS函数,提供给左边的红框,红框运行成功,就调用这个JS函数)
2. 用于刷新页面的Ajax Url(上述JS函数被调用后,应该到哪个Url获取刷新内容)
3. 刷新成功后,要继续执行什么操作(另外一个JS函数,比如刷新的内容“错过了document.Ready”要重新套用一下——尝试了live功能,不知道为什么无效;或者要串联刷新多个区域,本例中没有)
没了(后面还会看到一些,不过是为了别的功能)。有几个东西是多余的:
1. $(document).ready.....,因为每个AjaxPageLoad都执行,所以是固定的,不用作为参数传入接口。
2. function refreshLeft() ...{ $"#refreshLeft")....,因为这个按钮是多余的,并不需要人手去点击,只是函数实现中的一个步骤而已。换言之这个按钮叫什么都无所谓,只要ready / function / SFCUI.Link(id: ...)中出现的值相同就行,无需人工指定。
3. <div id = "leftpad"></div>也是多余的!因为既然在这里写下那一行代码,就在代码处LoadPage,至于Load到什么东西里边,ID叫什么,都无所谓。
所以,接口调用应该是:
@SFCUI.AjaxLoadPage("/Agile/PlanningMeetings/AjaxRefreshAssignItemsLeftPad?sprintID=" + assignItemsViewModel.Sprint.ID, refreshFunction: "refreshLeft", ajaxOnSuccess: "refreshAll")
这句话是一个helper,将负责生产前面代码中提到的<script>及其中的两个函数ready和refreshLeft、中间的<div>@SFCUI.Link中的Ajax调用</div>、最后的<div id = "leftpad">
下面是Helper的源代码,里边多了一些参数,最后解释:
public const string AJAX_LOAD_PAGE_CLASS = "ajaxloadpage"; private static Random _rand = new Random(); public static MvcHtmlString AjaxLoadPage(string pageLink, string refreshFunction = null, string ajaxOnSuccess = null, string style = null, int timeout = 0) { int id = _rand.Next(); string html = SFCUI.Link("refresh", "/SFC/Ajax/AjaxLoadPage?pageLink=" + HttpUtility.UrlEncode(pageLink), ajaxUpdateTargetID: id.ToString() + "Body", ajaxOnSuccess: ajaxOnSuccess, cssClass: "hide " + AJAX_LOAD_PAGE_CLASS + " ", id: id.ToString()).ToString(); TagBuilder script = new TagBuilder("script"); script.MergeAttribute("type", "text/javascript"); script.InnerHtml = "$(document).ready(function () { setTimeout(function () { $(\"#" + id.ToString() + "\").click(); }, " + timeout + "); });"; if (refreshFunction != null) script.InnerHtml += "function " + refreshFunction + "() { $(\"#" + id.ToString() + "\").click(); };"; html += script.ToString(); TagBuilder pageContainer = new TagBuilder("div"); pageContainer.MergeAttribute("id", id.ToString() + "Body"); pageContainer.MergeAttribute("style", style); html += pageContainer.ToString(); return new MvcHtmlString(html); }
1. 先看突然跳出来的int id = _rand.next()
之前提到,我们有很多“随便”的变量,比如那个"refreshLeft",叫什么都行,外界不关心。但是如果设为常数,则如果在同一个页面上放两个LoadPage的时候,会打架,所以用个Random生成ID。为什么用static 的呢?因为Random的生成机制,是利用调用时的系统时间作为“种子”,从种子产生下一个随机数。如果调用时间间隔为“0”,就会产生出相同的种子进而相同的随机数。static就没有这个问题了,大家用一个,顺着排。
总之,随机的id代替了"refreshLeft"这个本来需要传进来的“变量”。
2. TagBuilder script则直接在代码上方产生一段script,免去了编写script的工作。
为什么要判断refreshFunction为NULL的情况呢?因为有一种场景不会重复刷新。
在我的项目中,菜单极其复杂,产生菜单的时间,甚至比页面本身都长。但我们也不像牺牲强大的菜单换取性能,怎么办呢?子菜单延时产生!
由于人们在页面出现后的1~5秒内都不会操作菜单(要离开这个页面时才会操作),所以我们设置所有繁重的子菜单都使用AjaxLoadPage加载,而且注意最后一个参数timeout,它们多数在1000毫秒后才加载,那时候页面早就爽快地展示在用户面前了。
这种场景,不会有人刷新菜单了,所以就不用RefreshFunction这个参数。
3. TagBuilder pageContainer 代替了原来最后的<div id = "leftpad">,它的id也是随机生成的,但是由于1、2、3中保持了id的互相照应,虽然外界看不到,但是内部却顺畅运行。
4. 最后多了个style,是我们加载菜单时的需要,这里就不多说了。
尾声
合并显而易见的代码是初级程序员的基本功,也是产品代码的基本要求;封装多余的技术代码难度较大,但是只要用心,上述提到的原则也没有做不到的。
为什么要费劲封装呢?散装的代码对程序员要求低,只要好好测试,功能相同,也不会出现缺陷,不也一样吗?
在16年的IT从业中我发现,所有最后开发面临崩溃的软件,很少有受到单个技术难题或缺陷困扰的,多数都百病缠身;而百病缠身的主要问题,是可维护性差;而可维护性差的主要原因,是代码臃肿重复,尤其是似重复而不重复。
封装后,则:
1. 总是使用同一段代码,易于维护。
2. 总是重复使用,代码的质量有保障。
3. 一个地方发现缺陷,修改代码后可以同时避免多个地方的缺陷。
4. 在无需深入到技术层面的时候,可以方便地在业务层面阅读代码。
5. 把时间花费在深究代码的封装上,比重复码字要有趣得多。
……
在之前的IT职业生涯的“危险职业”系列中有很多人提到:“我一直在做重复劳动,没有积累,是不是很危险?怎么办?”其实万事万物看似重复,其实不重复。本人从事Web编程只有一年多(其中只有6个月的编程量超过总工作量的50%),Jquery上上个月刚大致弄明白,但是我相信很多Web编程很久的程序员都不会封装这些代码的,而是“重复地”拷贝粘贴代码。
所以实际上,没有重复的工作,只有重复的工作心态。
之后我会写一篇关于“重复劳动中如何提高”的IT职业生涯系列文章。