- 注重实效的团队:一旦参与项目嘚人员超过一个你就需要建立一些基本原则,并相应的派任务我们将说明怎样在遵循注重实效哲学的同时做到这一点。
- 无处不在的自動化:使项目级活动保持一致和可靠的一个最重要的因素是使你的各种工作流程自动化
- 无情的测试:进一步讨论项目范围的测试哲学和笁具——特别是在你没有大量QA人员可以随叫随到时。
- 全都是写:与测试相比开发者更不喜欢的唯一一件事情是撰写文档。不管你是有技術文档撰写者帮助还是要自己撰写。
- 极大的期望:成功取决于旁观者——项目出资人——的眼睛重要的是成功的感觉,在“极大的期朢”中我们将向你说明一些能使每个项目的出资人高兴的诀窍
- 傲慢与偏见:鼓励你在作品上签名,并未你所作的事情而自豪
质量是一個团队问题。最勤勉的开发者如果被派到不在乎质量的团队里会发现自己很难保持修正琐碎问题所需的热情。如果团队主动鼓励开发者鈈要把时间花费在这样的修正上问题就会进一步恶化
团队作为一个整体,不应该容忍破窗户——那些小小的、无人修正的不完美团队必须为产品的质量负责,支持那些了解我们在“软件的熵”中描述的“不要留破窗户”哲学的开发者并鼓励那些还不了解这种哲学的人。
质量只可能源于全体团队成员都做出自己的贡献
还记得“石头汤与煮青蛙”一节中,汤锅中那只可怜的青蛙吗它没有注意到周围环境的渐变,最终被煮熟了同样的事情也会发生在不警醒的人身上。在项目开发高涨的热度里很难用一只眼睛注意周围的环境。
作为整體的团队甚至更容易被煮熟大家认为,另外有人在处理某个问题或是团队领导一定已经批准了用户要求做出的某项改动。即使是目的朂明确的团队对项目中的重大改动可能也是健忘的
与之战斗。确保每个人都主动地监视环境的变化可以指定一个“首席水清检测员”。让这个人持续地检查范围的扩大、时间标度的缩减、新增特性、新环境——任何不在最初的约定中的东西对新需求进行持续额度量。團队无需拒绝无法控制的变化——你只需要注意到他们正在发生否则,你就会置于热水中
显然,团队中的开发者必须相互交谈我们茬“交流!”中给出了一些促进交流的建议。但是人们很容易忘记,团队本身也存在于更大的组织中团队作为实体需要同外界明晰地茭流。
对外界而言看上去沉闷寡言的项目团队是最糟糕的团队。他们举行无章次的会议在回上没有人想说话。他们的文档混乱:没有兩份文档有相同的外观每一份都使用不同的术语。
在“重复的危害”中我们讨论了消除团队成员之间的重复工作的困难这样的重复会造荿工作的浪费并且可能会带来维护的噩梦。显然良好的交流可以有所帮助,但有时还需要另外的一些东西
有些团队指定某个成员担任项目资料的管理员,负责协调文档和代码仓库其他团队成员在查早资料时,可以首先找这个人通过阅读正在处理的材料,好的资料管理员还能发现正在迫近的重复
当项目对一个资料管理员来说太大时(或是在无人愿意担任这一职务时),可以指定多人负责工作的各個方面如果有人想要讨论日期处理,他们知道应该去找Mary如果有数据库schema问题,去找Fred
有些开发文化把事情推向极端,实施严格的责任划汾:编码员不许与测试员交谈、后者又无须与首席架构师交谈等等。于是有些组织通过让不同的子团队沿不同的管理链进行报告对问題进行隔离。
认为项目的各种活动——分析、设计、编码、测试——会孤立地发生这是一个错误,它们不会孤立发生它们是看待同一問题的不同方式,人为地分割它们会带来许多麻烦离代码的用户有两三层远的程序员不太可能注意到他们的工作的应用语境。他们将无法做出有见识的决策
围绕功能、而不是工作职务进行组织
我们喜欢按照功能划分团队。把你的人划分成小团队分别负责最终系统的特萣方面的功能。让各团队按照各人的能力在内部自行进行组织。每个团队都按照他们约定的承诺对项目中的其他团队负有责任。承诺嘚确切内容随项目而变化团队间的人员分配也是如此。
确保一致和准确的一种很好是使团队所做的每件事情自动化如果你的编辑器能洎动在你输入时安排代码的布局,为什么要手工进行呢如果夜间构建能够自动运行各种测试,为什么要手工完成测试表单呢
自动化是烸个项目团队的必要组成部分——重要得足以让我们从下一页开始,专用一节加以讨论为了确保事情得以自动化,指定一个或多个团队荿员担任工具构建员构造和部署使项目中的苦差事自动化的工具。让它们制作makefile、shell脚本、编辑器模板使用程序等等。
要记住团队是由個体组成。让每个成员都能以他们自己的方式闪亮给他们足够的空间,以支持他们并确保项目的交付能够符合需求。然后要像“最夠好的软件”中的画家一样,抵抗不断画下去的诱惑
在汽车时代的破晓时分,启动一辆T型福特车的操作说明有两页还不止而驾驶现代汽车,你只需要转动钥匙——启动过程是自动的十分简单。遵循一串指令进行操作的人可能会撑掉引擎而自动启动器却不会。
尽管计算行业仍处在T型福特车的阶段我们无法承受为一些常用操作而反复阅读两页长的操作说明。无论是构建和发布流程、是书面的代码复查笁作、还是其他任何在项目中反复出现的任务都必须是自动的。我们也许在一开始就构建启动器和喷油但他们一旦完成,我们从此只需要转动钥匙就可以了
此外,我们想要确保项目的一致性和可重复性人工流程不能保证一致性,也无法保证可重复性特别是在不同嘚人对流程的各个方面有不同解释时。
在某个客户的开发现场我们曾看到所有开发者都在使用同一IE。他们的系统管理员给了每个开发者┅套说明告诉他们怎样把附加软件包***到IDE上。说明有许多页——到处都写着点击这里卷动那里,拖这个、双击那个、以及再做一遍
并不奇怪,每个开发者的机器里的内容有着轻微的不同当不同的开发者运行相同的代码时,应用的行为会出现微妙的差异bug会在一台機器上出现,在其他机器上却不出现追踪任何一个组件的版本差异通常会揭示出一个令人意想不到的情况
不要使用手工流程 人的可重复性并不像计算机那么好。我们也不应期望他们能那样shell脚本或批处理文件能以相同的次序、反复执行相同的指令,他们能被置于源码控制の下你因而也可以检查历程的修改历史。
另一个受欢迎的自动化工具是cron它允许我们安排无人照管的任务周期性地运行——通常是在午夜。例如下面的crontab文件指定,每天午夜过后5分钟运行项目的nightly命令每个工作日的凌晨3:15进行备份,每个月第一天的午夜运行expense——reports
使用cron我們可以自动安排备份,夜间构建、网站维护、以及其它任何可以无人照管地完成的事情
项目编译是一件应该可靠、可重复地进行琐碎工莋。我们通常通过makefile编译项目、即使是在使用ide环境时使用makefile有若干好处。它是脚本化、自动化的流程我们可以增加挂钩,让其为我们生成玳码并自动运行回归测试。IDE有自身的优势但只是IDE,可能很难获得我们寻求的自动化程度我们想用一条命令就完成签出、构建、测试囷发布。
在“重复的危害”中我们提倡生成代码,以根据公共来源派生知识我们可以利用make的依赖分析机制让这一过程变得更容易。给makefile增加规则根据其他来源自动生成文件,是一件相当简单的事情例如,假定我们想要根据一个XML文件生成Java文件并编译所得
我们还可以用哃样的一组规则,根据其他形式的文件自动生成源代码、头文件、或是文档。
你还可以让makefile为你运行回归测试或是针对整个子系统。只偠在源码树顶部发出一条命令你就可以情锁地测试整个项目;也可在单个目录中发出同样的命令,测试单个模块关于回归测试。
构建昰这样一个过程:取一个空目录(和一个已知的编译环境)从头开始构建项目,产生所有你希望产生的、最终交付的东西——例如CD-ROM主映像或自解开的存档。在典型情况下项目构建包括以下几个步骤:
- 从头开始构建项目,在典型情况下从顶层的makefile开始每次构建都会标注某种形式的发布或版本号,或是日期戳
- 创建可以分发映像。这个过程可能需要确定文件所有权和权限并严格按照发运时所需的格式,產生所有例子、文档、README文件以及将随同产品发运的任何东西。
对于大多数项目这一层面的构建是在每天夜间自动运行的。在典型情况丅与在构建项目的某个具体部分运行的测试相比,在这样的夜间构建中运行的测试要更完整要点在于,要让完全构建(full build)运行所有可鼡的测试你想要知道今天对代码做出的一处改动是否是某个回归测试失败的原因。通过在靠近问题源头的地方确定问题你更有可能找箌并修正它。
如果泥没有定期运行测试你可能会发现,应用因为三个月之前的改动失败但愿你有足够的好运气找到它。
你想要作为产品发运的最终构建可能具有与常规的夜间构建不同的需求,最终构建可能需要锁住仓库或是标上发布号;要求设置不同的优化和调试標志,等等我们喜欢使用一个单独的make目标(比如 make final),一次完成所有这些参数设置
如果程序员能够把他们所有的时间投入实际编程,那鈈是很好吗遗憾的是,难得有这样的情况有e-mail 要回复,有书面工作要完成有文档要发布到Web上等等。你可以决定创建一个shell脚本来完成一些烦人的工作但你仍然要记得在需要运行这个脚本。
因为记忆是随着你年龄的增长而丧失的第二种东西我们不想过分依赖它。我们可鉯运行脚本让它们基于源码和文档的内容,自动为我们完成各种流程我们的目标是维持自动、无人照管、内容驱动的工作流。
许多开發团队用内部网站来进行项目交流我们认为这是一个很好的想法。但我们不想花费太多的时间去维护网站也不想让它变得陈旧或过时。误导人的信息比完全没有信息还要糟糕
从代码、需求分析、设计文档中提取的文档以及任何图片、图表或图形都需要定期发布到网站仩。
无论它是怎样完成的Web内容都应该根据仓库中的信息自动生成,并且无需人的干预就发布出去这其实是DRY原则的另一种应用:信息以簽入代码和文档这一种形式存在,从Web浏览器的角度看出来的视图——只是视图你应该不必手工维护该视图
夜间构建生成的任何信息都应該在开发网站上访问:构建自身的结果。构建结果可以通过一页摘要的方式给出其中包括编译警告,错误以及当前状态)回归测试,性能统计编码度量,以及任何其他的静态分析等等。
有些项目具有各种必须遵循的管理工作流例如,需要安排代码或设计复查需偠批准,等等我们可以使用自动化——特别是网站——帮助减轻书面工作负担
假定你想要使代码复查和批准自动化,你可以在每个源文件里放置一个特殊标记:
可以用一个简答的脚本检查所有源码并查找具有needs——review状态的所有文件——这表明它们已经准备好接受复查。随後你可以把这些文件的列表作为网页发布出去、或是自动发送email 给适当的人员、甚或是使用某种日程软件自动安排一次会议
你可以在网页上設置一个表单用于让复查者登记文件是否通过了复查。在复查之后状态可自动变为reviewed。是否与所有参与者一起进行检查取决于你你仍嘫可以自动完成书面工作。
鞋匠的孩子没鞋穿软件开发人员常常会使用最糟糕的工具来完成工作。
但我们有制作更好额工具所需的所有原材料我们有cron。在Windows和Unix平台上我们都有make我们还有Perl和其他一些高级脚本语言,可以用于快速开发自制的工具、网页生成器、代码生成器、測试装备、等等
让计算机去做重复、庸常的事情——它会做得比我们更好。我们有更重要、更困难得事情要做
大多数开发者都讨厌测試。他们往往会温和地测试下意识地知道代码会在哪里出问题,并避开那些薄弱的地方注重实效的程序员与此不同。我们受到驱迫現在就要找到我们的bug,以免以后经受由别人找到我们的bug所带来的羞耻
寻找bug有点像是用网捕鱼。我们用纤小的网(单元测试)捕捉小鱼鼡粗大的网(集成测试)捕捉吃人的鲨鱼。有时鱼会设法逃跑所以为了抓住我们的项目池塘里游动的、越来越狡猾的缺陷,我们要补上峩们发现的任何漏洞
早测试、常测试、自动测试 一旦我们有了代码,我们就想开始进行测试那些小鱼苗有飞快地变成吃人的大鲨鱼的鈳恶习惯,而抓住鲨鱼会困难许多但沃恩又不想手工进行所有这些测试。
许多团队都会为了他们项目精心制订测试计划有时他们甚至會使用这些计划。但我们发现
使用自动测试的团队成功机会要打的多。与呆在架子上的测试计划相比随每次构建运行的测试要有效的哆。
bug被发现的越早进行修补的成本就越低“编一点,测一点”是Smalltalk世界流行的一句话我们可以把这句话当作我们自己的座右铭,在编写產品代码的同时编写测试代码
事实上,好的项目拥有的测试代码可能比产品代码还要多编写这些测试代码所花的时间是值得的。长远來看他最后会便宜得多,而你实际上有希望制作出近零缺陷的产品
此外,知道你通过了测试将给你高度的自信:一段代码已经“完成”了
要到通过全部测试,编码才算完成 你写出了一段代码并不意味着你可以告诉你的老板或客户,说它已经完成不是这样的。首先代码从不会真正完成。更重要的是在它通过所有可用的测试之前,你不能声称它已经可供使用
我们须要查看项目范围测试的三个主偠方面:测试什么、怎样测试、以及何时测试。
你需要进行的测试的主要类型有:
这份列表绝不是完整的有些特殊的项目还需要各种其怹类型的测试。但它给了我们一个很好的出发点
你使用的所有模块都必须通过他们自己的单元测试,然后才能继续前进
集成测试说明組成项目的主要子宫能系统工作,并且能很好地协同如果在适当的地方有好的合约,并且进行了良好的测试我们就可以轻松地检测到任何集成问题。否则集成就会变成肥沃的bug繁殖地。事实上它常常是系统的bug来源中最大的一个。
一旦你有了可执行的用户界面或原型伱需要回答一个最重要的问题:用户告诉了你他们的需要什么,但那是他们需要的吗
它满足系统的功能需求吗?这也需要进行测试没囿bug、但回答的问题本身是错误的,这样的系统不会太有用要注意用户的访问模式、以及这些模式与开发者所用的测试数据不同。
现在你巳经很清楚系统在理想条件下将会正确运行,你要知道的是它在现实世界的条件下将如何运行。在现实世界中你的程序没有无线资源;它们会把资源耗尽。你的代码可能会遇到一些限制包括:
性能测试、压力测试或负载测试也可能是项目的一个重要方面
问问你自己,软件是否能满足现实世界的条件下的性能需求——预期的用户数、连接数、或每秒事务数它可以伸缩吗?
对于有些应用你可能需要鼡专门的测试硬件或软件模拟现实情况下的负载。
可用性测试与到目前为止讨论过的其他测试类型不同它是真正的用户、在真是的情况丅进行的。
我们已经考察了要测试什么现在我们将把注意力转向怎样测试,包括:
回归测试把当前测试输出与先前的值进行对比我们鈳以确定我们今天对bug的修正没有破坏昨天可以工作的代码。这是一个重要的安全网它能减少令人不快的意外。
我们到目前为止提到过的所有测试都可以作为回归测试运行确保我们在开发新代码没有损失任何领地。我们可以运行回归测试对性能、合约、有效性等进行校驗。
我们从何处获取运行所有这些数据所需的数据数据又两种:现实世界的数据和合成的数据,实际上我们两者都需要因为这两类数據的不同性质将会揭示出我们软件中的不同bug
现实世界的数据来自某种实际来源。它可能收集自己有系统、竞争者的系统、或是某种原型咜代表典型的用户数据。当你发现典型意味着什么时你会大吃一惊,这最有可能揭示出需求分析中的缺陷和误解
合成数据是人工生成嘚数据——或许受限于特定的统计约束。处于以下任何原因你都有可能使用合成数据。
- 你需要大量数据可能超过了任何现实世界的样夲所能提供的数量。你也许可以使用现实世界的数据做种子生成更大的样本集,并且调整特定的有独特需要的字段
- 你需要能强调边界條件的数据。这些数据完全由人工合成:含有1999年2月29日的日期字段、分厂长的记录、或是带有外国邮政编码的地址
- 你需要能展现出特定的統计属性的数据。想要看到如果每第三个事务都失败会发生什么还记得在给预先拍好顺序的数据时会慢的像蜗牛的排序算吗?用来暴漏這种弱点
用专门写好的脚本进行GUI测试
因为我们不可能编写完美的软件,所以我们也不可能编写完美的测试软件我们需要对测试进行测試。
通过蓄意破坏测试你的测试
一旦你自信你的测试是正确的并且正在找出你制造的bug,你怎么知道你已经彻底地对代码库进行了测试呢
最简单的回答是“你不知道”。
测试状态覆盖而不是代码覆盖
许多项目往往会把测试留到最后一分钟——最后期限马上来临时,我们需要比早得多地开始测试任何产品一旦存在,就需要进行测试
大多数测试应该自动完成。注意到这一点很重要:我们所说的“自动”意味着对测试结果也进行自动解释
如果有bug漏过了现有的测试,你需要增加新的测试以在下一次抓住它。
每个bug只出现一次以后最好不偠再让出现
在典型情况下,开啊者不会太关注文档在最好的情况下,它是一件倒霉的差事;在最坏的情况下它就会被会当作低优先级嘚任务,希望管理部门会在项目结束时忘掉它
注重实效的程序员会把文档当作整个开发过程的完整组成部分加以接受。不进行重复劳动不浪费时间,并且把文档放在手边——如果可能就放在代码本身当中,文档的撰写就可以变得更容易
这些并不完全是独创的或新奇嘚想法;把代码和文档紧密地结合在一起的思想早已出现。
把英语当作又一种编程语言 为项目制作的文档基本上有两种:内部文档和外部攵档内部文档包括源码注释、设计与测试文档,等等外部文档是发运或发布到外界的任何东西,比如用户手册但不管目标受众是谁,也不管作者的角色是什么所有的文档都是代码的反映,如果有歧义代码才是最要紧——无论好坏。
把文档建在里面不要拴在外面
根据源码中的注释和声明产生格式化文档相当直接了当,但首先我们必须确保在代码中确实有注释代码应该有注释,但太多的注释可能囷太少一样糟
一般而言,注释应该讨论为何要做某事、它的目的和目标代码已经说明了它是怎样完成的,所以再为此加上注释是多余嘚——而且违反了DRY原则
下列是不应该出现在源码注释中的一些内容:
-
文件中的代码导出的函数的列表。有些程序可以为你分析源码使鼡它们,列表就保证是最新的
-
修订历史。这是源码控制系统的用途所在但是,在注释中包括最后更改日期和更改人的信息可能是有用嘚
-
该文件使用的其他文件的列表。使用自动工具可以更准确地确定这些信息
-
文件名如果在文件中必须出现文件名,不要手工进行维护RCS和类似的系统可以自动使这一信息保持最新。如果你移动文件或更改文件名你不会希望必须记得编辑头注释。
假定我们有一个规范偠列出某个数据库表中的各个列。然后我们要用另一组SQL命令在数据库中创建实际的表还可能创建某种编程语言的记录结构,用以存放表Φ的一行的内容同样的信息重复了三次。更改这三个信息源中的任何一个其他两个就会立刻过时。这显然违反了DRY原则
到现在为止,峩们谈论的只是内部文档——由程序员自己撰写的文档但如果在项目中有专业的技术文档撰写者又会怎样呢?情况常常是程序员只是紦材料“扔过隔板”,给那边的技术文档撰写者闭关让他们自己设法制作用户手册、宣传材料,等等
这是一个错误,程序员不撰写这些文档并不意味着我们可以放弃注重实效的原则。我们想让文档撰写者和注重实效的程序员一样接受同样的基本原则——特别是遵守DRY原则、正交性、模型——视图概念、以及自动化和脚本的使用。
某公司宣布利润创纪录其股价却下跌了20%,当晚的金融新闻解释说该公司没有实现分析家预期的业绩。一个小孩打开昂贵的圣诞礼物却大哭起来——这不是他想要的廉价洋娃娃。某个项目团队奇迹般地实现叻一个极其复杂的应用但却遭到用户的抵制,因为该应用没有帮助系统
在抽象的意义上,应用如果能正确实现其规范就是成功的。遺憾的是这只能付抽象的帐。
在现实中项目的成功是由它在多大程度啥满足了用户的期望来衡量的。不符合用户预期的项目注定是失敗的不管交付的产品在绝对的意义上有多好。但是像希望得到廉价洋娃娃的小孩的父母一样,你走得太远也会失败
温和地超出用户嘚期望 但是,执行这条提示需要做一些工作
用户在一开始就会带着他们对所需要的东西的想象来到你面前。那可能不完整、不一致、或昰在技术上不可能做到但那时他们的,而且就像过圣诞节的小孩一样,他们也在其中投入了一些感情你不能简单地忽视它。
随着你對他们的需要的理解的发展你会发现他们的有些期望无法满足,或是他们的有写期望过于保守你的部分角色就是要此进行交流。与你嘚用户一同工作以使它们正确地理解你将要交付的产品。并且要在整个开发过程中进行这样的交流决不要忘了你的应用要解决的商业問题。
有些顾问称这一过程称为“管理期望”——主动控制用户对他们能从系统中得到什么应该抱什么应该抱有的希望我们认为这是一個有点高人一等的想法。我们的角色不是控制用户的希望而是要与他们一同工作,达成对开发过程和最终产品、以及他们尚未描述出来嘚期望的共同理解如果团队能与外界通畅地交流,这个过程就几乎是自动的;每个人都应该理解所期望的是什么以及它将被怎样构建出來
有一些重要技术可用于促进这一过程。其中“曳光弹”和“原型与便笺”是最为重要的技术两者都让团队构造用户能看见的东西。兩者都是与用户交流你对他们的需求的理解的理想途径并且两者都让你和用户习惯于相互交流。
如果你和用户紧密协作分享他们的期朢,并同他们交流你正在做的事情那么当项目交付时,就不会发生多少让人吃惊的事情了
这是一件糟糕的事情。要设法让你的用户惊訝请注意,不是惊吓他们而是要让他们高兴。
给他们的东西要比他们期望的多一点给系统增加某种面向用户的特性所需的一点额外努力将一次又一次在商誉上带来回报。
随着项目的进展听取你的用户的意见,了解什么特性会使他们真的高兴你可以相对容易地增加、并让一般用户觉得很好的特性包括:
- 气球式帮助或工作提示帮助
- 作为用户手册的补充材料的快速参考指南
- 用于检查系统完整性的工具
- 运荇系统的多个版本、以进行培训的能力
- 为他们的机构定制的splash屏幕
所有这些特性都是相对表面的,而且实际上不会因为特性肿胀而给系统带來过度的负担但是,每一项特性都告诉你的用户开发团队想要开发出老不起的系统,要用于实际的系统只是要记住,不要因为增加這些新的特性而破坏系统
注重实效的程序员不会逃避责任。相反我们乐于接受挑战,乐于使我们的专业知识广为人知如果我们在负責一项设计,或是一段代码我们是在做可以引以为自豪的工作。
在你的作品上签名 过去时代的手艺人为能在他们的作品上签名而自豪伱应该也如此。
但是项目团队仍然是由人组成的,这条规则可能会带来麻烦在有些项目里,代码所有权的概念可能会造成协作上的问題人们可能会变得有地盘意识,或是不愿再公共的基础设施上工作项目最后可能会变得像一些相互隔绝的小“采邑”。你变得怀有偏見只欣赏自己的代码,排斥自己的同事.
那不是我们想要的你不应该怀着猜忌心阻止要查看你的代码的人;处于同样的原则,你应该带著尊重对待他人的代码“黄金法则”和开发者间的相互尊重是使上面的提示行之有效的关键所在。
匿名(尤其是在大型项目中)可能会為邋遢、错误、懒惰和糟糕的代码提供繁殖地只把自己 看作齿轮上的一个齿、在无休无止的状况报告中制造蹩脚的借口、而不去编写优良的代码,那太容易了
我们想要看到对所有权的自豪。“这是我编写的我对自己的工作负责。”你的签名应该被视为质量的保证当囚们在一段代码上看到你的名字时,应该期望它是可靠的、用心编写的、测试过的和有文档的一个真正的专业作品,由真正的专业人员編写