敏捷开发松结对编程系列之十四:一些重构而不影响他人的编程诀窍

注:虽然是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,其中两个似乎也可以用虚函数避免。

其他的还有很多手段来防止维护代码影响之前的代码调用,但这三个是最常用的,可以说在团队代码有交叉的时候,每个人都必备上述技能才可能和谐工作。
刚开始一般只有师傅具备完全使用上述功能的能力,但随着徒弟们的能力提升,可以逐步尝试让徒弟也掌握这些技能,从而可以帮助师傅维护底层库。

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