《代码之殇》(原书第2版)——第2章 过程改进,没有灵丹妙药 2004年10月1日...

    xiaoxiao2021-07-21  170

    2004年10月1日:“精益:比五香熏牛肉还好”

    曾经走在一个公共场所,比如机场的出入口通道或公园,是否突然有一群狂徒冲你而来,想要教化你或者恐吓你甚至就因为你貌似傲慢无礼而要揍你。跟他们当中的任何一个人讲理,逻辑和推理都失去意义。在他们眼里,只有疯狂的信仰和不容辩驳的真理。即使你完全赞同他们,也不容你质疑或理论。你必须相信,你不能怀疑,哪怕一点点。

    五香熏牛肉(Pastrami)是一种加入各种各样的香料熏制成的牛肉。——译者注

    这让我很不爽。我的意思是说我真的巨不爽。我有我自己的头脑,我想完全独立地思考。不是只在聚会或者社交场合,而是所有情境下。问一下“为什么”并搞清楚“怎么做”,这才是人性独立的要义。

    你可能会认为,我这样的见解应该成为不明就里而无法进行软件调试的开发者的标准。但就像一些致力于宗教、政治斗争和环境保护的人所具有的狂热一样,软件开发者也强烈地推崇一些新兴的软件开发方法,比如极限编程(eXtreme Programming,XP)、敏捷方法和团队软件过程(Team Software Process,TSP)。

    做任何事情都要适中

    我非常喜欢这些开发模式所提倡的思想和方法。但面对一个虔诚的追随者,如果我问为什么要这样做,或者当我建议对规则或实践做一个很小的改动,以使它更适用于我的实际工作的时候,那就不一样了!这好比把一只魔戒摆在哈比人的面前——他顿时露出獠牙,发根直立。对于一些开发者来说,极限编程和敏捷方法已经成为一种迷信。而对于另外一些开发者来说,团队软件过程是一种忠诚度的风向标——你跟我们非友即敌。

    请原谅我的务实,原谅我使用自己的头脑,原谅我不迷信灵丹妙药,而坚持去做实用的事。我不会因为“你必须得这么做”而去做。我的行为原则是,要对这样做会成功而用其他方法就会失败这两方面都能有相当充分的理由。

    作者注:这样的“夸夸其谈”经常使大家笃信我在文章中论及的主题思想,我以前总以此为傲,但这次不会了。只有极端主义者才说“迷信”无害,我可不是极端分子。

    俭则不匮

    是什么让我关注“精益”呢?是的,本栏目的标题。虽然在极限编程、敏捷方法和团队软件过程中有很多奇妙的东西,但至少有一个概念它们是共同的:减少无为的浪费。这恰恰是精益设计和制造的重点,而精益是源自于丰田汽车公司的一个概念,它比极限编程、敏捷方法和团队软件过程要早30多年。但是,极限编程、敏捷方法及团队软件过程使用不同的路子处理浪费问题,通过了解精益模式,我们可以更好地理解这些模式方法是怎么实现的。

    因此,冒着触动一些狂热者敏感神经的危险,听我娓娓道来。精益的精髓就是以最少的投入换取向用户提供最大的价值。它采用一种“拉模式”及“持续改进”的方法来做到这一点。拉模式其实很简单:“不需要,就不做”,这就减少了无用的、不必要的、无价值的工作;而持续改进则重在减少浪费和提供一种平稳的客户价值流。

    作者注:非常感谢Corey Ladas,因为是他首先把我引入了精益(lean)、公理设计(Axiomatic Design)、Scrum、质量功能展开(Quality Function Deployment,QFD)、集合设计(SetBased Design)、Kaizen改善、普氏概念选择法(Pugh Concept Selection)的世界。除了这些理论,他还有很多很多其他出色的想法。我们曾经在一起共事了两年,后来他离开了团队,之后他的位置再也没人能够补上。他现在和另一位出色的前团队成员Bernie Thompson在一起,维护一个精益软件工程的网站。

    精益理论对有损于客户价值流的浪费归纳为以下7种类型: 过量生产 运输 多余动作 等待 过程不当 库存 缺陷

    这些显然是制造业的术语,是吗?它们不可能跟软件有关联,是吗?显然你太天真了。所有这7种浪费都直接跟软件开发有关,我视其为软件开发“7宗罪”。以下我将谈谈如何通过极限编程、敏捷方法、团队软件过程及一般性常识来规避它们。

    过量生产

    第一种浪费是供过于求,但愿这永远也不要发生。是否有一种软件产品符合需求规范而无需删减任何功能模块呢?是否有一种软件产品存在客户从来用不着的功能模块呢?这些问题既复杂又平常,即宽泛又耐人寻味,既似无关紧要又劳人伤神……过量生产太恐怖了,它会导致难以置信的浪费。

    极限编程通过短而紧凑的循环周期来解决这个问题。它注重与客户的持续交流,以及开发者之间的持续沟通。这保证了所有人都知道其他人在干什么,并且总是有较高的客户认可度。结果,几乎所有完成的工作都是对客户有价值的。当然,微软的客户是庞大的,因此微软的很多团队都使用敏捷方法。

    敏捷方法是各种精益方法的一个总称,它包括极限编程。确切地说,敏捷方法不是指某项具体的技术,而是一种整体的方法论,它为软件开发提供了很多有趣的方法。其中之一称为Scrum项目管理方法(Scrum的命名来自于一个橄榄球术语)。开发团队定期地跟客户代表碰头,通常每30天一次,以展示工作进展、重新安排工作的优先次序以及进行过程改进。跟极限编程一样,团队成员也每天开会更新各自的进度和讨论工作中遇到的难题。

    通过每月对工作优先次序的重新安排和每日对工作的重新组织,一个Scrum团队把自身的关注点锁定在了客户看重的东西上面,几乎没有任何工作是多余的。通过定期性的过程改进,价值流可以不断地被优化。

    深入探讨

    当然,你也可能只会死板地运用Scrum和极限编程:你先在“基础框架”上花费工夫,而让客户苦苦等候着他们想要的功能。为了定期获得客户的反馈,快捷的周期循环要有个基本前提:开发应该“深度优先”,而不是“广度优先”。

    确切地说,广度优先是指对每一个功能进行定义,然后对每个功能进行设计,接着对每个功能进行编码,最后对所有功能一起进行测试。而深度优先意味着对单个功能完整地进行定义、设计、编码和测试,而只有当这个功能完成了之后,你才能去做下一个功能。当然,两个极端都是不好的,但深度优先要好得多。对于大部分团队来说,应该做一个高层的广度设计,然后马上转到深度优先的底层设计和实现上面去。

    这正是微软的Office功能小组的工作方式。首先,团队对他们需要哪些功能以及如何使这些功能协调工作做出计划。然后大家被分成各司其职的多个小型团队,每个团队自始至终一次只负责一个功能。最终结果是,一个运行良好且稳定的产品快速地交付给用户进行试用。

    作者注:当然,功能小组(Feature Crew)的想法并不新鲜。然而,在一个像Office这样庞大而活跃的生产环境中,能够找到一种方法去实现精益软件开发已经是一个重大的成就了。你要知道的是,Office系统现在已经拥有多种桌面应用、服务器应用和大型的在线服务。

    深度优先通过把注意力放在将被使用到的工作上,而不是可能永远不会被客户关注或者面面俱到而永远得不到定论的“基础框架”上,这样就能减少过量生产。另一个出色的深度优先开发方法是“测试驱动开发”(TestDriven Development,TDD),但我想在后面“过度开发”那一节中再展开讨论它。

    运输

    第二个极度浪费是期望万事俱备。在制造业中,这通常是指零部件的运输问题。对于软件来说,这个“运输”指的是团队之间可交付成果的传递。这里有3个令人厌恶的运输问题之源:创建、分支和Email。

     创建:创建花费的时间越长,浪费的时间就越多,这是我想让你引以为戒的。极限编程和敏捷方法都坚持每天创建,而这条规则他们很可能是从微软学去的。但对于大型团队来说,每天创建越来越不现实。幸运的是,我们已经安排了优秀的人才来解决这个问题,但这确实是个大问题。这点不用多说。

     分支:我喜欢Source Depot。它对整个公司的价值是巨大的。但它也像一只宠物象一样让人失去兴趣:当它还小的时候非常可爱,但经过几年的喂养后,灵活性就逐渐丧失。建立代码分支是个很好的主意,因此很多大的团队都这么做了。现在假设你在A2B3C1分支上工作,而你的伙伴在A3B1C2上实现了一个关键功能或者修复了一个重大Bug,那他们首先需要把C2横向集成到B1里,再将B1横向集成到A3,而你必须将A3纵向集成到A2,再将A2纵向集成到B3,将B3纵向集成到C1。天哪!!!等所有这些集成都做完了,黄花菜都凉了。这种做法还仅仅是你当前周期内产品生产线的一个分支。

    作者注:Source Depot是微软用于管理上亿行源代码和工具的大规模资源控制系统,包括版本控制和分支管理。

     Email:最后一个运输噩梦是Email通知单。项目经理告诉开发和测试人员规范书准备好了;开发人员告诉测试人员说编码完成了;测试人员告诉开发人员说他们的代码有Bug;开发人员告诉项目经理说他们的设计变更有问题;还有我的个人问题:与客户、依赖方或者零售商之间的沟通,特别是越洋沟通。极限编程和敏捷方法通过废除这种形形色色的岗位角色和每日召开会议来解决这个Email通知单问题。但对于外地零售商和依赖方,这行不通。我们只有在可行的情况下才使用自动通知功能,在必要时才用Live Meeting,以及通过Email给对方一个明确答复来减少Email的恣意蔓延。

    行为

    第三个极度浪费是仅仅为了找出问题而花时间。在制造业中,这是对机械和人工行为的一种浪费。在软件行业中,就是把时间花在研究做什么、怎么做及怎么调整原先方案上。糟糕的搜索技术是行为浪费的一个典型例子。不可测试、无法维护、无法管理的代码同样是一种浪费。

    设立中断及调试参数有助于快速找出Bug及减少浪费。设计复审、代码复审、代码分析和单元测试都能达到减少浪费的目的。极限编程居然建议“结对编程”(Pair Programming),但我个人认为,这样做是对资源的一种浪费(除非开发人员是在一起学习一个新的代码库)。团队软件过程可用于检测你所有工作及弊病,你可以清楚地了解到你的时间是怎样花掉的,因而你也能大大地减少你的行为浪费。

    作者注:在一些陌生领域,为了开发一些新鲜的玩意儿,我的团队已经采用了“结对编程”法。它相当有效。

    一个特别讨厌但能避免的行为浪费是复制Bug修复信息,因为代码注释需要同时变更,Source Depot需要重新集成,Product Studio需要重新整合以及收到邮件后要相应地进行工程改进。每个人都浪费劳力去管理Bug和项目进度数据的多个副本。有一些工具可以让这类事情变得简单,信息只需输入一次,便能自动被传播到其他所有的地方。这类工具可以有助于减少无谓的行为。

    等待

    第四个极度浪费是等待。前面谈到的运输问题只涵盖了创建、分支集成和及时沟通方面存在的一大部分等待问题。但等待远远不止这些地方。最常见的盲区是,团队在功能的优先次序上达不成一致,或者即使达成了一致但没按照既定的顺序去实施。也就是说,项目经理如果胡乱地写规范书,开发人员就不得不等待;如果开发人员胡乱写代码,则测试人员只能等待;如果测试人员胡乱测试,那么所有人都必须等待。

    极限编程、敏捷方法和团队软件过程都强调要求团队确定一个统一的工作次序,并且得到客户或者负责人的认可,然后再按照这个顺序依次开展工作,直到他们决定重新审定工作次序为止。团队软件过程在这方面尤为严格,但如果没有一个临机应变的带头人,这样做也不会有可持续性。

    此外,不稳定的代码也会导致等待。只要代码不稳定,测试团队就不得不等待,就像其他需要等待用户反馈的事情一样。极限编程和敏捷方法特别注重经得起考验的稳定代码,这是深度优先策略的又一个要点。

    作者注:其他形式的等待是因为部署新的服务或有其他类似事件的发生,整个服务系统环境需要稳定化或进行同步。一种避免浪费这种等待时间的最好方法是公开操作过程,并逐步更新部署。我将在本章后面的专栏“生产第一”中讨论这个内容。过度开发第五个极度浪费是开发过度。你会经常遇到这样一种情况:编制过于复杂的功能,在已经运行良好或并不称得上瓶颈问题之处对性能要求过于吹毛求疵。这种浪费跟过量生产有关,但这里更强调在具体功能的实现上面。药方:测试驱动开发。这是一种为实现既定设计方案的极限编程及敏捷开发方法,它在实现了单元测试的同时实现了代码全覆盖,一石二鸟。其过程相当地简单:1创建API或者公共类方法。作者注:这是我和敏捷社区的一些成员起争执的一个地方。你是在写单元测试之前还是之后写API或者公共类方法?偏执狂说之后写,但我认为要之前写。两者不同之处在于,前期方案设计的工作量,以及你的代码依赖方与你之间的关系。我会在其他栏目再次谈及前期方案设计,我相信,在10万行代码级别的项目中适度的前期方案设计是成功的关键要素。2.按需求写一个API或类的单元测试。3.编译并创建你的程序,然后运行单元测试并确认它失败。(如果成功了,则跳过第4步。)4.不断修改代码直至其通过为止。(同时要保证以前所有的单元仍然能够测试通过。)5.重复第2步至第4步,直到所有符合需求的API或者类都测试通过。

    很自然,当你掌握了这种方法的窍门之后,你可以一次为多个需求写多个单元测试。但当你刚刚起步的时候,最好还是一次只做一个。这样能够养成良好的习惯。

    当你使用测试驱动开发方法时,你就不必写过多的代码。你也自然而然地得到了很容易测试的代码,并且这些代码通常还是高内聚、低耦合、少冗余的——所有这些都是真正的好东西。哦,我曾提到你也能获得代码全覆盖的单元测试了吗?你还有什么不满意的吗?

    库存

    第六个极度浪费是没有交付的工作产品。这跟削减功能有关,但它也包括那些正在进展中的工作。当你采取宽度优先的开发方法时,你所有的工作同时开展,直到代码编写完成并完成稳定化。所有完成的规范书、设计和等待通过测试的代码都属于库存。它们的价值都尚未实现。

    尚未实现的价值是种浪费,因为你不能把价值演示给客户和合作伙伴看。你不能得到他们的反馈。你不能改进和优化客户的价值流。甚至,如果产品计划改变了,这些尚未实现的库存通常就变成了巨大的工作浪费。

    精益拉模型强调只做需要做的事情,因此它的结果就是低库存,这在Scrum和测试驱动开发方法中得到了很好的验证。Scrum特别关注正在进展中的工作,时时跟踪并努力减少浪费。Scrum同时注重于定期改进和优化你传递价值的方式。测试驱动开发方法要求你只实现满足需求的代码,多则无益。

    质量缺陷

    第七个极度浪费是返工。这是最明显的一个,也是我过去批判得最多的一个(参见第5章)。极限编程和敏捷方法通过各种方法来减少Bug和返工,这些方法不仅仅包括测试驱动开发、每日创建、持续的代码复审和设计复审,等等。

    然而,极限编程和敏捷方法也以一种更为微妙的方法来减少Bug——在边干边学中建立起一种框架。在你为整个产品完成设计和编码之前,通过深度优先开发方法,一步步勾勒出整个项目的整体框架。这避免了严重的架构问题:这种架构问题隐藏得很深,等到被发现的时候已经太晚,不容许再调整了。听起来很熟悉吧?

    减少缺陷是团队软件过程的专长。使用这种方法的团队能够使他们的Bug率下降到行业平均水平的千分之一。第5章的“软件发展之路——从手工艺到工程”会详细论述如何通过团队软件过程进行缺陷预测、跟踪、消除。虽然团队软件过程本质上来说不是精益,但它也并不排斥深度优先的开发方法。

    合作共生

    下面我该激怒极限编程、敏捷方法和团队软件过程的虔诚追随者了。因为没有理由认为对这些方法的综合运用会比简单地将它们合并的效果更差。使用Scrum来完成一个精益、深度优先、灵活、优化的开发计划。使用测试驱动开发方法来创建一个精益实现。使用团队软件过程来分析你的缺陷和工作,这将大大减少你的Bug及无谓劳动。这些在某些人听起来可能会怪怪的,但在我看来却是再合理不过的了。

    现在我如果能找到一些纯正的五香熏牛肉就好了。

    作者注:我在纽约长大。在雷德蒙很难找到纯正的五香熏牛肉了。

    相关资源:七夕情人节表白HTML源码(两款)

    最新回复(0)