敏捷开发“松结对编程”系列之十:L型代码结构(技术篇之一)

本文是“松结对编程”系列的第十篇。(松结对编程栏目目录

主题:如何用更少的代码写相同的功能,怎样的团队结构可以推进这一点。

如果你也希望只用国际先进水平的1/4代码量,就实现相同的功能,欢迎阅读本文。

本文会多次切换技术和管理两个平行线,所以结构会复杂一些。

前言

一直对代码有效性比较感兴趣,所以今天早上重新排布了功能树后,仔细计数了功能点和代码行,大致如下:

代码9883行,来自VS的统计数据(只计算CS文件),加上cshtml中的1420个分号计数(此方法低于VS的统计方法约10%),大约是11200行;

功能点740个,包含33个ILF和127操作(没有区分EI/EO/EQ,每个统一按4FP计算,会略低于标准数值)(关于功能点的计算,请参考http://blog.csdn.net/cheny_com/article/details/7692917 及其下面的4篇文章,火星人产品中未来有这个功能)。

也就是说每个功能点,需要花费15行C#代码。在QSM的网站上,C#的这个数字大约是60(http://www.qsm.com/resources/function-point-languages-table),考虑到能提供这些数据(尤其是基于功能点的数据)的企业都不是等闲之辈,所以15这个数值还是很惊人的(培训课上我曾经提到过大致是19~23,但那时候没仔细整理,是个粗略值)。

代码行/功能点,也就是平均多少行代码可以实现一个功能点,是国际通行的评价编程效率有效性的方法。在MS推出C#的时候,为了证明C#的效率更高,用C#编写了Java的一个例程PetShop(也称PetStore),结果是只使用了Java代码的1/4。Java后来又予以反击,以大致相当的代码效率重写了PetShop(http://www.clintonbegin.com/downloads/JPetStore-1-2-0.pdf)。

当然,后面还会提到更少的代码到底好还是不好的问题。

先解决一个基本问题:如何把代码编得更少呢?

技术手段

技术手段不是我们今天说的重点。因为技术手段受限于开发者的水平,极难一下子就提升多少;而且所从事的业务本身也会限制技术手段的发挥。

不过还是大致说几点(另外一些以后再分享):

1. 限制每个函数的规模

火星人中包含2125个Public关键字,姑且将函数的数量大致估算为2000个左右,所以每个函数内的代码,平均低于5行。

小函数有些显而易见的好处:

a. 更容易抽出可复用的部分

先看看火星人手册中提到过的这三个界面:




产生这三个界面(以及另外大约10多个类似的界面),都是下面这段代码:

        private void PrepareIndexData(int rootID, string whats, string whattypes)
        {
            ViewBag.ItemTreeViewModel = new ItemTreeViewModel(
                "查看用户故事树",
                rootID, whats ?? SystemItemWhat.Story, whattypes, Product.ProductsAccessibleToUserIDs(User.Identity.Name),
                showPopupOperationMenu: true,
                showMoveLeftRight: true,
                showMoveUpDown: true, 
                enableInternalDrop: true,
                subItemsTreeColumnWidth: 290);
        }
        [UrlLog]
        public ActionResult Index(int rootID, string whats, string whattypes)
        {
            PrepareIndexData(rootID, whats, whattypes);
            //ViewTester.IsViewTesting = true;
            return View(ItemTree.ViewPath);
        }

        [HttpPost, UrlLog]
        public ActionResult Index(int rootID, string whats, string whattypes, FormCollection collection)
        {
            try
            {
                Item.OnItemDropped(collection[MFCUI.DragHistories]);
            }
            catch (Exception e)
            {
                ModelState.ReportException(e);
            }
            PrepareIndexData(rootID, whats, whattypes);
            return View(ItemTree.ViewPath);
        }
当whattypes变化时,产生了上面的三个不同界面。

若PrepareXXXData中产生的ViewModel不同,则会产生另外10多个界面。
图中有一个不起眼的小函数叫做Item.OnItemDropped,能处理故事树(包含上面提到的多种子树)/部门树/自定义字段树/故事板/向导……等的所有拖拽,每次都是这一行代码。

b. 更易读,易维护

有时候经常会听到一种说法:“更少的代码,有时候反而很不易于理解”。

不知道大家感觉如何,反正我觉得上面这些代码,比散装的500行代码要容易读一些。如果有一天让人来维护,也更容易。

当然下一个问题:如果要动底层,会不会很难?怎么办?

其实,如果打开这几个函数,会发现他们也是一层一层形成的,并不需要真的面对500行代码;而且到了某一层,找到了要维护的地方,就无需深入下去了。这样一层一层找下去,每次维护可能只需要看50行代码(要10层调用,才会用到50行)不到,而不是把500行代码整个翻一遍。

2. 信息隐匿,暴露其用法,而隐藏其实现方法

这个好处包括:

a. 函数可以自解释

经常听到有人说:不应该写注释,应该写自解释的代码。

不过有个问题,代码要解释什么?解释自己在做什么,还是解释自己是怎么做的?如果只能选择一个,我一定会选择前者。

比如这四行代码:

            try
            {
                Item.OnItemDropped(collection[MFCUI.DragHistories]);
            }
            catch (Exception e)
            {
                ModelState.ReportException(e);
            }
            PrepareIndexData(rootID, whats, whattypes);
            return View(ItemTree.ViewPath);

看上去应该是说:处理拖拽行为,如果发现异常则报告,根据链接参数准备好数据,重新生成页面。

如果每个函数都是这么干干净净写的,即使我们不知道他里边怎么实现的,发生了问题,也很容易追查进去,找到具体的地方再慢慢读。

b. 更容易让新手上手

如果这个产品中,突然又要一个类似的界面,也要树形结构,也要拖拽,甚至加上Ajax操作……如果是500行代码,确定能让新手上手吗?估计很难。

但现在容易多了。一位之前从来没有编过程序的技术支持人员,在跟我学编程不到一年后(有效工时折算成5×8的模式,可能只有3~4个人月),就直接照葫芦画瓢做了2个类似界面,并帮我维护了很多其他细节问题,因为这些工作看上去更像是搭积木的活动,而不是真的要对软件、编程了如指掌。

下面就是一段他搭建的“积木代码”:

        public ActionResult LinkProduct2Team(int focusedProductID = 0)
        {
            ViewBag.ItemTreeViewModel = new ItemTreeViewModel(
                "产品-团队映射", Department.DepartmentRootID, SystemItemWhat.DEAPRTMENT, ItemWhattype.DeaprtmentDepartment + "_" + ItemWhattype.DeaprtmentTeam); 
            focusedProductID = focusedProductID == 0? ProductLine.ProductRootID : focusedProductID;
            ViewBag.LinkItem2ItemsViewModel = new LinkItem2ItemsViewModel(
                Department.DepartmentRootID, SystemItemWhat.DEAPRTMENT, ProductLine.ProductRootID, 
                SystemItemWhat.Product, focusedProductID, whatTypes: ItemWhattype.DeaprtmentDepartment + "_" + ItemWhattype.DeaprtmentTeam, 
                leftPadWhatTypes: ItemWhattype.ProductProductline + "_" + ItemWhattype.ProductProduct + "_" + ItemWhattype.ProductEdition);
            return View(ItemTree.ViewPath);
        }

为了生成树状结构,只需要第一行代码,剩下的代码,是处理树状结构的树枝行为的。

在这个界面上如果树枝被点击,将会发生一次Ajax调用,并刷新树枝的样式。我没有时间在3~4个人月教会一个从未编程的人C# + Ajax,不过他也不需要学,而只需要写下:

    var ajaxLink = "/MFC/LinkItem2Items/AjaxNode?currentItemID=" + Model.ID + "&leftPadFocusedItemID=" + leftPadFocusedItemID + "&leftPadRootID=" + leftPadViewModel.LeftPadRootID + "&leftPadWhat=" + leftPadViewModel.LeftPadWhat + "&leftPadWhatTypes=" + leftPadViewModel.LeftPadWhatTypes;
    MvcHtmlString link = Model.Link(outerLink: ajaxLink, updateTargetId: Model.ID.ToString(), onSuccess: "refreshAll(); ");
    
    <div id = "LinkedItem@(Model.ID)" class = "@(divclass) toggleLinkedItem @(hideclass) originalLinkedItem hovershowLinkedItemReverse">
        @link 
    </div>

Ajax操作、刷新所需的js脚本会被Model.Link自动生成,而所调用的函数AjaxNode里边也只有9行代码。

所以,不需要担心新手看不懂、不会用的问题,短小而高度封装的代码,反而更容易看懂和使用。

反过来说,我们总不能因为新手看不懂,而让高手都用ABC级别的垃圾代码来编写吧?毕竟应该改变的是新手,而非高手。

其实自己看看,限制函数规模和信息隐匿是一回事情,前者导致了后来。


前面说的技术方法都不太难,但问题是为什么很多团队做不到呢?下一个问题来了:

高手可以这样写代码,新手呢?总不能要求他们也都这样吧?这个,是下篇(管理篇之一)的内容。




  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值