主题:如何用更少的代码写相同的功能,怎样的团队结构可以推进这一点。
如果你也希望只用国际先进水平的1/4代码量,就实现相同的功能,欢迎阅读本文。
本文会多次切换技术和管理两个平行线,所以结构会复杂一些。
一直对代码有效性比较感兴趣,所以今天早上重新排布了功能树后,仔细计数了功能点和代码行,大致如下:
代码9883行,来自VS的统计数据(只计算CS文件),加上cshtml中的1420个分号计数(此方法低于VS的统计方法约10%),大约是11200行;
功能点740个,包含33个ILF和127操作(没有区分EI/EO/EQ,每个统一按4FP计算,会略低于标准数值)(关于功能点的计算,请参考http://cheny.blog.51cto.com/3988930/1102184 及其下面的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)。
当然,后面还会提到更少的代码到底好还是不好的问题。
先解决一个基本问题:如何把代码编得更少呢?
技术手段不是我们今天说的重点。因为技术手段受限于开发者的水平,极难一下子就提升多少;而且所从事的业务本身也会限制技术手段的发挥。
不过还是大致说几点(另外一些以后再分享):
火星人中包含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行代码整个翻一遍。
这个好处包括:
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级别的垃圾代码来编写吧?毕竟应该改变的是新手,而非高手。
其实自己看看,限制函数规模和信息隐匿是一回事情,前者导致了后来。
前面说的技术方法都不太难,但问题是为什么很多团队做不到呢?下一个问题来了:
高手可以这样写代码,新手呢?总不能要求他们也都这样吧?这个,是下篇(管理篇之一)的内容。