敏捷开发“松结对编程”系列之十七:L型代码结构(编程篇之二)(中)
2013-02-02 14:04
330 查看
续前文。
本文是“松结对编程”系列的第十七篇。(松结对编程栏目目录)
上一篇文章基本上把Controller中Action和与Action直接相关的复用讲到了,下面讲讲比较困难的部分。
在纯C#/C++/Java代码中(不讨论html),虚函数是用来做这个的。Team和Product都从Item派生,相同的部分共用函数,然后重载Item.ShowBody()函数,显示不同的东西即可。用到html的时候,则有两种方法实现。第一种就是使用虚函数,不过是一个虚的Helper,用Helper生成要显示的东西。不过这个方法比较麻烦,还得编写函数,几乎从来没用过。第二种是自动匹配不同的view。也就是说,根据Team和Product的不同(比如team.GetType()和product.GetType()的不同),自动找到一个View来显示。个人比较喜欢这个。我们的每个Item都有两个字段,一个叫做What,做大的分类,比如部门Program和团队Team的What是DEPT(广义的部门);第二个叫做Type,做小的分类,比如与Team的What相同而Type不同的还有企业Corporation/分公司Branch/Program(这个是我们常说的“部门”)/团队Team/小组SubTeam等。用这两个字段,就能找到具体的View。实际代码如下(在_SubItem.cshtml中):
如果大家的类设计中没有What/Type这些字段,至少GetType是不同的,就可以这样使用。
returnUrl完成了重新定向的工作。当然,需要Items/ItemHorizentalList配合(在ItemsController.ItemHorizentalList()中:):
returnUrl被传入ItemHorizentalList.cshtml中,显示新建按钮的时候会用到:
然后是ItemsController/Create():
注意这里附带处理了没有指定returnUrl的情况(会转向到Edit);returnUrl有很多用处,除了返回某个页面/自动导向某个页面,还可以重复指向Create以便不断创建多个。
前者的坏处是要额外写一个View。但好处是会保留Url,也就是看上去还在Teams/Index,但现实的内容却是Items/ItemHorizentalList的内容。这样如果要根据Url来安排主菜单或相关链接,则比较方便。
后者因为已经完全转向到别处了,正好相反。
这个事情困扰了我们一段时间,逐渐发现一个规律:如果一个页面是“长期页面”,比如Index,也就是说所有业务都围绕其开展,所有相关链接都在这个页面上,则写一个View保持不跳转;如果一个页面是“短期页面”,比如Create/Edit/Delete,也就是来看完后,几乎不会停留在这个页面上,则直接在Controller里边转向,加一个returnUrl办完事后回到长期页面就可以了。
原因包括:1. 团队中的人员分工造成各自为战,没有意识到有可复用的部分。比如如果把刚才提到的Team和Product分两个人独立做,基本上就永远也想不起来要复用了。不复用损失的还不只是上面提到的几个函数和页面,还包括Item处理存储(都存储在一个表里边)、父子关系索引、创建、编辑、删除、隐藏、排序……,以及UDCable(这是Item的基类)的自定义字段……的所有功能。如果能形成松结对编程,师傅级别的人能同时看到3~5个人的工作,就能找到相似的业务。如果师傅之上再有师傅,经常下来查看工作,就能看到整个团队的相似的业务,从而大大提升复用效率。2. 如何让高手向新手传递知识师傅制度其实也有分工,但主要是“师傅做复杂的事情,徒弟做简单的事情”,不过好处是师傅做复杂事情的时候,会拉着徒弟一起做,徒弟会目睹全过程。而生产出来的“复杂代码”,与徒弟的代码不是平行的而是交叉的(比如本文及上一篇文章中看到的这些代码),徒弟在不断交叉引用的过程中,一点点就理解了设计的原因和方法。在平行分工中这就很难做到,因为大家不太有机会看到、用到别人的代码。偶然去“参考一下”,绝对没有新手和高手交叉引用代码的效果好:高手会看到新手不对的地方帮助改好(最好当面一起改,结对编程),新手可以看到高手的实现方法并学习。
本文是“松结对编程”系列的第十七篇。(松结对编程栏目目录)
上一篇文章基本上把Controller中Action和与Action直接相关的复用讲到了,下面讲讲比较困难的部分。
几个关键问题
1. 如何实现整体结构相同,而局部不同的复用
比如我们的业务需求中,整体上都是一个横向的结构(请参考下图),每个单元上面都是标题,下面都是增删改查之类的按钮,唯独中间的部分不同。如果是团队,要显示成员,如果是产品,要显示最近的发布:在纯C#/C++/Java代码中(不讨论html),虚函数是用来做这个的。Team和Product都从Item派生,相同的部分共用函数,然后重载Item.ShowBody()函数,显示不同的东西即可。用到html的时候,则有两种方法实现。第一种就是使用虚函数,不过是一个虚的Helper,用Helper生成要显示的东西。不过这个方法比较麻烦,还得编写函数,几乎从来没用过。第二种是自动匹配不同的view。也就是说,根据Team和Product的不同(比如team.GetType()和product.GetType()的不同),自动找到一个View来显示。个人比较喜欢这个。我们的每个Item都有两个字段,一个叫做What,做大的分类,比如部门Program和团队Team的What是DEPT(广义的部门);第二个叫做Type,做小的分类,比如与Team的What相同而Type不同的还有企业Corporation/分公司Branch/Program(这个是我们常说的“部门”)/团队Team/小组SubTeam等。用这两个字段,就能找到具体的View。实际代码如下(在_SubItem.cshtml中):
<div class = "item-hierarchy-title"> @item.Link() </div> <div class = "item-hierarchy-body"> @MFCUI.TryRenderPage(this, "~/Areas/DLC/Views/MFC/Items/ItemHorizentalListViews/" + item.What + "/_" + item.Type + ".cshtml", "~/Areas/DLC/Views/MFC/Items/ItemHorizentalListViews/_" + item.What + ".cshtml", item) <div class="item-hierarchy-footer"> @MFCUI.ImageLink("删除", "/MFC/Items/Hide?id=" + item.ID, cssClass: "hide hoverbodyof" + item.ID, showText: false, returnTo: this) @MFCUI.ImageLink("修改名称", "/MFC/Items/EditTitle?id=" + item.ID, cssClass: "hide right hoverbodyof" + item.ID, showText: false, returnTo: this) @item.ShowMoveLeftRight(this) </div> </div>中间那个_TryRenderPage是我们封装的方法,可以理解为就是两次RenderPage,先找名为"/大类/_小类.cshtml“的View,找到不就找"_大类.cshtml"的文件;找到就传入item。这样如果所有部门都这么显示,那么就只写_DEPT.cshtml,否则则为所有的分别写。这样设计是有原因的,实际上,Team和SubTeam多半会直接显示成员;但部门Program和分公司Branch这样显示就没意义了。
如果大家的类设计中没有What/Type这些字段,至少GetType是不同的,就可以这样使用。
2. 新建好team要转向成员分配(/TeamMembers/Index),新建好product要转向创建发布(/Products/ReleaseSchedule)
这种一般是传入一个returnUrl参数,比如(在Teams的Index.cshtml中):Html.RenderAction("ItemHorizentalList", "Items", new { area = "MFC", rootID = programID, what = SystemItemWhat.Deaprtment, type = ItemWhattype.DepartmentTypeTeam, returnUrl = HttpUtility.UrlEncode("/Site/TeamMembers/Index?programID=" + programID) });
returnUrl完成了重新定向的工作。当然,需要Items/ItemHorizentalList配合(在ItemsController.ItemHorizentalList()中:):
public ActionResult ItemHorizentalList(int rootID, string what, string type, string returnUrl) { var root = _repMFC.ReadAt<Item>(rootID); IEnumerable<Item> subItems = root.SubItems().Where(i => i.What == what && i.Type == type); return MFCDefaultView(root, subItems, what, type, returnUrl); }
returnUrl被传入ItemHorizentalList.cshtml中,显示新建按钮的时候会用到:
@MFCUI.ImageLink("新建", "/MFC/Items/Create?fatherID=" + root.ID + "&what=" + what + "&type=" + type + "&returnUrl=" + returnUrl, imgUrl: "/MFC/Items/Create48.png", showText: false)
然后是ItemsController/Create():
[HttpPost, UrlLog] public ActionResult Create(int fatherID, string what, string type, FormCollection collection, string returnUrl) { ViewBag.Father = _repMFC.ReadAt<Item>(fatherID); try { Item item = ……; returnUrl = String.IsNullOrEmpty(returnUrl) ? "~/MFC/Items/Edit?id=" + item.ID + "&createNotice=off" : returnUrl.Replace("[id]", item.ID.ToString()); Notice.CreateNotice(Notice.NoticeTypes.ItemCreated, User.Identity.Name, item, new[] {Notice.RecipientTypes.DeptTeam, Notice.RecipientTypes.UserCurrentOwner}, repMFC: _repMFC); return Redirect(returnUrl); } catch (Exception e) { ModelState.ReportException(e); return View(MFCWebViewPage.ViewFailed, e); } }
注意这里附带处理了没有指定returnUrl的情况(会转向到Edit);returnUrl有很多用处,除了返回某个页面/自动导向某个页面,还可以重复指向Create以便不断创建多个。
3. 哪些Action需要写View
哪些Action需要写View,然后在View里边RenderAction(比如这个Teams/Index);哪些不用写View,直接在Controller的Action里边RedirectToAction到ItemsController的操作?(这样简单,连View都不用谢,但有局限就是Url会跳转)?前者的坏处是要额外写一个View。但好处是会保留Url,也就是看上去还在Teams/Index,但现实的内容却是Items/ItemHorizentalList的内容。这样如果要根据Url来安排主菜单或相关链接,则比较方便。
后者因为已经完全转向到别处了,正好相反。
这个事情困扰了我们一段时间,逐渐发现一个规律:如果一个页面是“长期页面”,比如Index,也就是说所有业务都围绕其开展,所有相关链接都在这个页面上,则写一个View保持不跳转;如果一个页面是“短期页面”,比如Create/Edit/Delete,也就是来看完后,几乎不会停留在这个页面上,则直接在Controller里边转向,加一个returnUrl办完事后回到长期页面就可以了。
4. 这些编码方法与松结对编程有何关系?
虽然看起来也不算是很复杂的技术,但常常因为几个原因造成推广困难。这就是为什么从2000年就开始谈论“复用”“面向对象”,但到现在有几个企业或团队有自己的可复用库呢?原因包括:1. 团队中的人员分工造成各自为战,没有意识到有可复用的部分。比如如果把刚才提到的Team和Product分两个人独立做,基本上就永远也想不起来要复用了。不复用损失的还不只是上面提到的几个函数和页面,还包括Item处理存储(都存储在一个表里边)、父子关系索引、创建、编辑、删除、隐藏、排序……,以及UDCable(这是Item的基类)的自定义字段……的所有功能。如果能形成松结对编程,师傅级别的人能同时看到3~5个人的工作,就能找到相似的业务。如果师傅之上再有师傅,经常下来查看工作,就能看到整个团队的相似的业务,从而大大提升复用效率。2. 如何让高手向新手传递知识师傅制度其实也有分工,但主要是“师傅做复杂的事情,徒弟做简单的事情”,不过好处是师傅做复杂事情的时候,会拉着徒弟一起做,徒弟会目睹全过程。而生产出来的“复杂代码”,与徒弟的代码不是平行的而是交叉的(比如本文及上一篇文章中看到的这些代码),徒弟在不断交叉引用的过程中,一点点就理解了设计的原因和方法。在平行分工中这就很难做到,因为大家不太有机会看到、用到别人的代码。偶然去“参考一下”,绝对没有新手和高手交叉引用代码的效果好:高手会看到新手不对的地方帮助改好(最好当面一起改,结对编程),新手可以看到高手的实现方法并学习。
相关文章推荐
- 敏捷开发“松结对编程”系列之十六:L型代码结构(编程篇之二)(上)
- 敏捷开发“松结对编程”系列之十五:L型代码结构(编程篇之一)
- 敏捷开发“松结对编程”系列之十二:L型代码结构(质量篇之一)
- 敏捷开发“松结对编程”系列之十二:L型代码结构(质量篇之一)
- 敏捷开发“松结对编程”系列之十:L型代码结构(技术篇之一)
- 敏捷开发松结对编程系列:L型代码结构案例StatusFiltersDropdownList(中)
- 敏捷开发“松结对编程”系列之十:L型代码结构(技术篇之一)
- 敏捷开发“松结对编程”系列之十一:L型代码结构(团队篇之一)
- 敏捷开发“松结对编程”系列之十一:L型代码结构(团队篇之一)
- 敏捷开发“松结对编程”系列之十:L型代码结构(技术篇之一) .
- 敏捷开发松结对编程系列:L型代码结构案例StatusFiltersDropdownList(中)
- 敏捷开发“松结对编程”系列之十一:L型代码结构(团队篇之一) .
- 敏捷开发松结对编程系列之十:代码审查最佳实践
- 敏捷开发松结对编程系列之十:代码审查最佳实践
- 敏捷外包工程系列之二:人员结构(敏捷外包工程,敏捷开发,产品负责人,客户价值)
- 敏捷开发松结对编程系列之十:代码审查最佳实践
- 敏捷外包工程系列之二:人员结构(敏捷外包工程,敏捷开发,产品负责人,客户价值)
- 敏捷外包工程系列之二:人员结构(敏捷外包工程,敏捷开发,产品负责人,客户价值)