敏捷開發松結對編程系列之十四:一些重構而不影響他人的編程訣竅

注:雖然是javascript, 但想法對其它語言也適用。


本文是“松結對編程”系列的第十三篇。松結對編程欄目目錄

松結對編程包括L型代碼結構的一個很大的問題,是在於由於人們複用了太多的代碼,以至於所有代碼牽一髮而動全身。這很容易導致一個底層庫改動後,很多地方編譯不通過,或編譯通過但運行不通過。

本人曾經擔任過底層庫的編寫者,在修改和維護底層庫的過程中遇到了很多問題,也發現了一些訣竅,下面是一些編碼級別的處理訣竅。這些訣竅其實都寫在教科書上,學習和使用起來也很簡單,只是到用的時候,被很多人忽略了。

1. 缺省參數

如果要給一個函數增加一個新的變量,因這個變量的不同而產生新的行爲方式,那麼缺省參數是一個很好的避免之前的代碼需要相應改動的方法。
比如:
  1.   
  1. <SPAN style="FONT-SIZE: 18px"> </SPAN><SPAN style="FONT-SIZE: 14px">       public static MvcHtmlString ImageLink(string text, string url, bool showInNewWindow = falsebool showText = true,  
  2.                                               bool showImage = truestring title = nullstring outerLink = null,  
  3.                                               string imgUrl = nullstring imgSuffix = "16.png",  
  4.                                               string imgCssClass = "icons"string updateTargetId = null,  
  5.                                               string display = null,  
  6.                                               string onSuccess = nullbool runOnSuccessWhileLeft = false,  
  7.                                               string id = nullstring imgStyle = nullstring cssClass = null,  
  8.                                               string textColor = null,  
  9.                                               bool displayAsText = falsestring displayAsTextSuffix = nullstring displayAsLinkSuffix = null,  
  10.                                               WebViewPage returnTo = nullstring returnUrl = null)</SPAN><SPAN style="FONT-SIZE: 18px">  
  11. </SPAN>  
        public static MvcHtmlString ImageLink(string text, string url, bool showInNewWindow = false, bool showText = true,
                                              bool showImage = true, string title = null, string outerLink = null,
                                              string imgUrl = null, string imgSuffix = "16.png",
                                              string imgCssClass = "icons", string updateTargetId = null,
                                              string display = null,
                                              string onSuccess = null, bool runOnSuccessWhileLeft = false,
                                              string id = null, string imgStyle = null, string cssClass = null,
                                              string textColor = null,
                                              bool displayAsText = false, string displayAsTextSuffix = null, string displayAsLinkSuffix = null,
                                              WebViewPage returnTo = null, string returnUrl = null)

這個被維護過無數次甚至有點過度設計的函數,能顯示爲文字鏈接,能自動搜索圖標,能作爲在Ajax連接使用,能根據當前值選擇顯示爲文字還是鏈接……但最樸素的用法,始終保持爲Text和Url兩個參數,比如:
  1. @MFCUI.ImageLink(item.Title, "/MFC/Items/Edit?id=" + item.ID)  
 @MFCUI.ImageLink(item.Title, "/MFC/Items/Edit?id=" + item.ID)
這樣初期的、簡單的調用就不用修改。

2. 重載或重寫一個函數

如果變化太大,重載乃至重寫一個新的函數會更好一些。
下面這一大堆,都是火星人中爲了解決個人對產品的可訪問性問題的,但具體應用場景有很小的變化。爲了防止每次都多那麼幾行代碼產生變化,乾脆把差別封裝在函數內部。
  1. public static IEnumerable<Product> ProductsAccessibleToUser(string user)  
  2. {  
  3.     var productsAccessibleToUser = new List<Product>();  
  4.     foreach (var teamID in Department.TeamsContainUser(user).Select(i => i.ID).ToList())  
  5.     {  
  6.         productsAccessibleToUser.AddRange(ProductsAccessibleToTeam(teamID));  
  7.     }  
  8.     return productsAccessibleToUser.Distinct();  
  9. }  
  10.   
  11. public static IEnumerable<int> ProductsAccessibleToUserIDs(string userName)  
  12. {  
  13.     return ProductsAccessibleToUser(userName).Select(i => i.ID);  
  14. }  
  15.   
  16. public static string ProductsAccessibleToUserIDsString(string userName)  
  17. {  
  18.     return ProductsAccessibleToUserIDs(userName).Aggregate<intstring>(null, (current, id) => current + (id.ToString() + "_"));  
  19. }  
        public static IEnumerable<Product> ProductsAccessibleToUser(string user)
        {
            var productsAccessibleToUser = new List<Product>();
            foreach (var teamID in Department.TeamsContainUser(user).Select(i => i.ID).ToList())
            {
                productsAccessibleToUser.AddRange(ProductsAccessibleToTeam(teamID));
            }
            return productsAccessibleToUser.Distinct();
        }

        public static IEnumerable<int> ProductsAccessibleToUserIDs(string userName)
        {
            return ProductsAccessibleToUser(userName).Select(i => i.ID);
        }

        public static string ProductsAccessibleToUserIDsString(string userName)
        {
            return ProductsAccessibleToUserIDs(userName).Aggregate<int, string>(null, (current, id) => current + (id.ToString() + "_"));
        }

不過,重載和缺省參數之間要做一個平衡,比如上面那個ImageLink,很多參數都是交叉的,就不適合重載。當初曾經想把普通的LInk和AjaxLink分開,但發現分開或不分開,都需要textColor之類的參數,所以後來實際的做法是把裏邊的代碼再行分別建立函數,以減少單個函數的長度。

3. 虛函數(多態,virtual,override)

虛函數的目的,是爲了“讓今天的代碼,調用明天的功能”。
當年剛剛聽說虛函數有這麼大本事,大喫一驚。如果你正在擔心明天增加了新的功能,還得修改今天的代碼,那麼肯定就需要求助虛函數了。
比如下面這個場景:程序員A正在寫一個顯示各種Notice的界面,而設計“各種Notice”的是B,而且他經常增加新的類型。這時候虛函數就可以派上用場。
  1. public class NoticeItemAssigned : Notice  
  2. {  
  3.     public override MvcHtmlString Information(string user)  
  4.     {  
  5.         var result = base.Information(user).ToString();  
  6.         result = result.Replace("[P1]", UDCValue.ShowAsUsersLink(P1).ToString());  
  7.         result = result.Replace("[P2]", UDCValue.ShowAsUsersLink(P2).ToString());  
  8.         return new MvcHtmlString(result);  
  9.     }  
  10. }  
    public class NoticeItemAssigned : Notice
    {
        public override MvcHtmlString Information(string user)
        {
            var result = base.Information(user).ToString();
            result = result.Replace("[P1]", UDCValue.ShowAsUsersLink(P1).ToString());
            result = result.Replace("[P2]", UDCValue.ShowAsUsersLink(P2).ToString());
            return new MvcHtmlString(result);
        }
    }
比如上面的NoticeItemAssigned及其他類型的Notice,要顯示它們的Information,都只需要調用@notice.Information(user);但至於顯示出來的結果是什麼,則由其override後的函數來決定。

"爲什麼我很少用虛函數?"很可能是用了太多的switch-case所致。
如果用switch-case來處理各種notice的顯示結果,那麼就需要通過增加新的分支,來處理新的顯示方法。這個最大的壞處是:剛纔說的A和B兩個程序員就要經常交叉工作。而一旦A離職了,B每次增加新的類型,都要去到A的代碼裏邊去改動,極其危險。
火星人的11000行代碼中,只有6個switch-case,其中兩個似乎也可以用虛函數避免。

其他的還有很多手段來防止維護代碼影響之前的代碼調用,但這三個是最常用的,可以說在團隊代碼有交叉的時候,每個人都必備上述技能纔可能和諧工作。
剛開始一般只有師傅具備完全使用上述功能的能力,但隨着徒弟們的能力提升,可以逐步嘗試讓徒弟也掌握這些技能,從而可以幫助師傅維護底層庫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章