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

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

之前的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行代码)。当然,他编写这些代码的时候,又是参考了我之前的另外一个“迭代计划”页面的设计。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值