本文是“松結對程式設計”系列的第十五篇。(松結對程式設計欄目目錄)
之前的L型代碼結構的前三篇提到過,L型代碼結構的微觀計劃和估算過程會與一般的程式設計方法不同,今天正好要編寫一些新代碼,邊寫邊記錄整個過程。如果中間卡殼了,我也會盡量記錄下來。
業務需求
這是《火星人》中的一個功能,以往使用者故事是使用故事樹來展示的(就是有父子關系的使用者故事),故事樹隸屬于一個産品Product。 但是最近要發版了,感覺一個以前認為暫時用不上的功能,現在變得很急迫,那就是在目前這個版别Edition(比如“線上版”)的目前版本Version(比如“簡化試用版”)的下一個釋出Release(比如“R20120331A”)中,到底有哪些功能? 如果某些功能在這個釋出中,就要通過自動化測試(某些功能現在有自動化測試用例,但如果運作可能不通過,因為這些功能暫時不釋出)和人工測試。 看起來功能條目化一下大約如此: 1. 先在版别Edition上做與使用者故事的對應 2. 然後在版本Version上做(受到版别的限制) 3. 然後在釋出Release上做(受到版本的限制) 一般實作的做法大約包括這些工作: 1. 三張資料庫表做對應連結(如果是一張表,則會多一個字段,表明是版别、版本還是釋出),按7或21功能點估算,需要7或21人天(在OA類項目開發中,1功能點大約需要7~9小時,産品研發應該略長)。 2. (展示對應關系,加入一個故事,挪出一個故事)×3,按12功能點估算,大約需要4×3=12人天 這裡的人天包括了從需求分析到測試、釋出(含維護一段時間至穩定)的總工時,實際開發大約占50%,也就是大約10~15人天左右。
L型代碼結構的做法
步驟1:找到相似的業務
這裡的相似,指業務邏輯相似,比如都是“二叉樹”或都是“查詢”之類的。 我們之前做過一個“團隊與産品的對應關系”功能,即安排哪些團隊可以通路哪些産品(反之亦然)。 界面如下(很醜啊呵呵,以後有機會再改,現在整個界面風格都在修改):

這種業務,在火星人的底層稱之為Item to Item Link,所謂Item就是任何以父子關系存在的東西;而Link則是任何這兩種東西之間的連結。 圖中也是是部門樹與産品樹的關系;而馬上要建立的,是産品樹(版本等屬于産品樹)與使用者故事之間的關系。
步驟2:找到業務代碼
團隊在左邊,産品在右邊的(就是上圖,“團隊能通路哪些産品”):
public ActionResult LinkTeam2Product(int focusedDepartmentID = 0)
{
ViewBag.ItemTreeViewModel = new ItemTreeViewModel("團隊-産品映射", ProductLine.ProductRootID, SystemItemWhat.Product, whattypes: ItemWhattype.ProductProductline + "_" + ItemWhattype.ProductProduct + "_" + ItemWhattype.ProductEdition);
focusedDepartmentID = focusedDepartmentID == 0 ? Department.DepartmentRootID : focusedDepartmentID;
ViewBag.LinkItem2ItemsViewModel = new LinkItem2ItemsViewModel(ProductLine.ProductRootID, SystemItemWhat.Product, Department.DepartmentRootID, SystemItemWhat.Deaprtment, focusedDepartmentID, whatTypes: ItemWhattype.ProductProductline + "_" + ItemWhattype.ProductProduct + "_" + ItemWhattype.ProductEdition, leftPadWhatTypes: ItemWhattype.DeprtmentProgram + "_" + ItemWhattype.DeprtmentTeam);
return View(ItemTree.ViewPath);
}
ItemTreeViewModel是負責産生右邊的樹的(注意首級目錄是橫向排列的,以便利用好寬屏的空間);LinItem2ItemsViewModel是負責處理連結關系及顯示左邊的樹的。
這是另外一個函數,和前面那個差不多,但産品在左邊,團隊在右邊的,“産品能被哪些團隊通路”,這個好處是左邊不用動了:
public ActionResult LinkProduct2Team(int focusedProductID = 0)
{
ViewBag.ItemTreeViewModel = new ItemTreeViewModel(
"産品-團隊映射", Department.DepartmentRootID, SystemItemWhat.Deaprtment, ItemWhattype.DeprtmentProgram + "_" + ItemWhattype.DeprtmentTeam);
focusedProductID = focusedProductID == 0? ProductLine.ProductRootID : focusedProductID;
ViewBag.LinkItem2ItemsViewModel = new LinkItem2ItemsViewModel(
Department.DepartmentRootID, SystemItemWhat.Deaprtment, ProductLine.ProductRootID,
SystemItemWhat.Product, focusedProductID, whatTypes: ItemWhattype.DeprtmentProgram + "_" + ItemWhattype.DeprtmentTeam,
leftPadWhatTypes: ItemWhattype.ProductProductline + "_" + ItemWhattype.ProductProduct + "_" + ItemWhattype.ProductEdition);
return View(ItemTree.ViewPath);
}
這兩個代碼其實差不多,用後面這個改比較友善。
步驟3:修改出新的業務代碼
Controller裡邊就這幾行,二話不說照葫蘆畫瓢改出一個來,隻要修改一下參數:
public ActionResult LinkProduct2Story(int productID, int? focusedItemID)
{
ViewBag.ItemTreeViewModel = new ItemTreeViewModel(
"産品-故事映射", productID, SystemItemWhat.Story);
ViewBag.LinkItem2ItemsViewModel = new LinkItem2ItemsViewModel(
productID, SystemItemWhat.Story, null,
ProductLine.ProductRootID, SystemItemWhat.Product, null, focusedItemID);
return View(ItemTree.ViewPath);
}
步驟4:必要時,修改底層代碼
這個不寫了,原來的new LinkItem2ItemsViewModel中有兩個問題: 1. 原來的程式員不會用int?,是以在Action中使用了=0的預設變量 改為用int?。 2. 處理=0(現在是=null了)的代碼也就是
focusedProductID = focusedProductID == 0? ProductLine.ProductRootID : focusedProductID;
被寫在Action裡邊了,是以每次都要寫一遍。
改為直接傳入new ViewModel,讓裡邊處理。 3. 變量順序有交叉,不得不用:把容易看錯的地方辨別出來 重新調整了順序,變成對稱的了,也就不用寫:了。 是以新寫的那個函數比原來兩個(也改了)少了一行,還少兩個:及其變量名。 如果在這時候修改了代碼,先測試原來的功能是否正常,再調試新功能。否則兩種錯誤複合在一起,很難定位。 L型代碼的一個很大的好處是,每次改進都不是以加法形式存在的,而是乘法。隻要有一個人能把一個地方改好,很多人的很多地方都能改好。 這樣就能以集體的最高能力作為團隊的整體能力。而在傳統的團隊中,往往是最低能力,決定了産品的最終品質。
步驟5:調試
實際上什麼也沒調試,對兩個partial view做了兩次拷貝粘貼外加重命名,結果出來了:
已經能把“檢視所有故事”加入到“最簡版本”這個釋出裡邊了。 什麼View也沒寫呢,怎麼就完了呢?下面這句話:
return View(ItemTree.ViewPath);
通向一個可複用的View,它的結構就是左邊一個Pad,右邊一個首級目錄橫置的樹(也可以不是),它會在目前目錄下尋找一個叫做"_[Action]LeftPad.cshtml"的View來顯示左邊的Pad和"_[Action]TreeNode.cshtml"的檔案來顯示右邊的每一個節點,這是剛才拷貝粘貼和重命名View的原因。 Partial View和函數一樣,都可以視為底層,能複用就複用。
總結
一些沒講到的地方
1. 資料庫表哪去了? 因為LinkItem2Items這個表早就有了,是以不用建表。
殘留問題
不過到此為止,還有點問題: 1. 業務需求中,應該是如果一個使用者故事沒有被加入到版本Edition中,就不能加入到下面的釋出Release中;或反之,若被加入了釋出中Release中,則應該自動被加入到版本Version。這個需求還沒有實作。
這個預計需要10多行代碼。 2. 剛才拷貝粘貼重命名的檔案,沒有經過任何改動居然工作了。這聽起來是個好事,但也說明有兩段代碼是相同的,需要再次複用一次,否則日後的任何改動,都需要改動多處。 這個預計需要5行左右的代碼。
L型代碼的好處
1. 代碼減少
這些功能大約有20~30個功能點(取決于建幾個資料庫表),按QTM上的國際資料,C#每個功能點需要59行,是以59×(20~30)=1200~1500行,但如果有L型代碼結構,就隻需要20多行代碼。
2. 工作量下降
剛才的工作從13:30開始,到現在16:12,扣除寫部落格實際花費了大約1小時不到,再加上殘留問題修修補補的時間也就1天。是以1天能幹10~15天的活。
這個甚至不需要個人能力,即使是新手,熟悉了這個架構,速度也差不多。
3. 品質上升
每次底層都被多次調用,隻要一次失敗就會被發現;而每次底層改動都會幫助多個功能優化,修改Bug也是如此。
4. 新手上手快
一般“代碼減少”“代碼優化”常常面臨的問題就是:新手能看懂和維護嗎?
其實,别看這個體系好像挺複雜的,但新手看這20行代碼的速度,還是比擺弄1200~1500行快多了。
例子中的所有代碼,除了步驟3裡邊的新函數,其他的比如被我重構的LInkItem2ItemViewModel和幾個Action,外加拷貝粘貼重命名的LeftPad/TreeNode等代碼,其實都是另外一個人編寫的。他在編寫這些代碼的時候程式設計經驗隻有半年(之前作為技術支援工作過4年,大學學過C++,之後從來沒動過代碼),實際編寫的代碼行數估計隻有1000行(我們一共隻有11000行代碼)。當然,他編寫這些代碼的時候,又是參考了我之前的另外一個“疊代計劃”頁面的設計。