之一,之二
合并顯而易見的代碼
所謂顯而易見的代碼,就是看上去和別處相同的代碼。
在這個例子中,就是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職業生涯系列文章。