交付用户想要的软件
让客户做决定
在设计方面,做决定的时候好必须有开发者参与。可是,在一个项目中,它们不应该做所有决定,特别是业务方面的决定。
Decide what you shouldn’t decide.
开发者(及项目经理)能做的一个最重要的决定就是:判断哪些是自己决定不来的,应该让企业主做决定。你不需要自己给业务上的关键问题做决定。毕竟那不是你的事情。如果遇到了一个问题,会影响到系统的行为或者如何使用系统,把这个问题告诉业务负责人。如果项目领导或经理试图全权负责这些问题,要委婉地劝说他们,这些问题最好还是和真正的业务负责人或客户商议。
当你和客户讨论问题的时候,准备好几种可选择的方案。不是从技术的角度,而是从业务的角度,介绍每种方案的优缺点,以及潜在的成本和利益。和他们讨论每个选择对时间和预算的影响,以及如何权衡。无论他们做出了什么决定,他们必须接受它,所以最好让他们了解一切之后再做这些决定。如果时候他们又想要其他的东西,可以公正地就成本和时间重新谈判。
毕竟,这是他们的决定。
具体技巧
- 记录客户做出的决定,并注明原因。好记性不如烂笔头,但你选择的记录方法不能太笨重或太繁琐。
- 不要用过于具体和没有价值的问题打扰繁忙的业务人员。如果问题对他们的业务没有影响,就应该是没有价值的。
- 不要随意假设具体的问题不会影响他们的业务。如果能影响他们的业务,就是有价值的问题。
- 如果业务负责任回答“我不知道”,这也是一个称心如意的答案。也许是他们还没有想到那么远,也许是他们只有看到运行的实物才能评估出结果。尽你所能为他们提供建议,实现代码的时候也要考虑可能出现的变化。
让设计指导而不是操纵开发
“设计”是软件开发过程不可缺少的步骤。它帮助你理解系统的细节,理解不见和子系统之间的关系,并且指导你的实现。
一些成熟的方法论很强调设计,他们希望在开始编码之前,先有完整的设计和文档。另一方面,敏捷方法建议你早在开发初期就开始编码。是否那就意味着没有设计呢?不,绝对不是,好的设计仍然十分重要。画关键工作图是必不可少的,因为要使用类及其交互关系来描述系统是如何组织的。在做设计的时候,你需要画时间去思考各种不同选择的缺陷和益处,以及如何做权衡。
然后,下一步才考虑是否需要开始编码。如果你在前期没有考虑清楚这些问题,就草草地开始编码,很可能会被很多意料之外的问题搞晕。但是,即使之前已经提交了设计文档,也还会有一些意料之外的情况出现,时刻谨记,此阶段提出的设计知识基于你目前对需求的理解而已。一旦开始了编码,一切都会改变。设计及其代码实现会不停地发展和变化。
Design should be only as detailed as needed to implement.
严格的需求-设计-代码-测试开发流程源于理想化的瀑布式开发方法,它导致在前面进行了过度的设计。这样在项目的生命周期中,更新和维护这些详细的设计文档变成了主要工作,需要时间和资源方面的巨大投资,却只有很少的回报。
如果你自己都不清楚所谈论的东西,就根本不可能精确地描述它。
设计可以分成两层:战略和战术。前期的设计属于战略,通常只有在没有深入理解需求的时候需要这样的设计。更确切地说,它应该只描述总体战略,不应深入到程序方法、参数、字段和对象交互精确顺序的具体细节。那应该留到战术设计阶段,它应该在项目开发的时候再具体展开。这时更适合讨论如何设计类的职责。因为这仍然是一个高层次、面向目标的设计。事实上,CRC(类-职责-协作)卡片的设计方法就是用来做这个事情的。
如何知道一个设计上好的设计,或者正合适?代码很自然地为设计的好坏提供了最好的反馈。如果需求有了小的变化,它仍然容易去实现,那么它就是好的设计。而如果小的需求变化就带来了一大批基础代码的破坏,那么设计就需要改进。
具体技巧
- “不要再前期做大量的设计”并不是说不要设计,只是说在没有经过真正的代码验证之前,不要陷入太多的设计任务。当对设计一无所知的时候,投入编码也是一件危险的事。如果深入编码只是为了学习或创造原型,只要你随后能把这些代码扔掉,那也是一个不错的办法。
- 即使初始的设计到后面不再管用,你仍需设计:设计行为说无价的。“计划说没有价值的,但计划的过程说必不可少的。”在设计过程中学习说有价值的,但设计本身也许没有太大的用处。
- 白板、草图、便利贴都是非常好的设计工具。复杂的建模工具只会让你分散精力,而不是启发你的工作。
合理地使用技术
Blindly picking a framework is the having kids to save taxes.
在考虑引入新技术或框架之前,先要把你需要解决的问题找出来。你的表述方式不同,会让结果有很大差异。找到了需要解决的问题,接下来就要考虑:
- 这个技术框架真能解决这个问题吗?要确保它能解决你的问题,并没有任何的毒副作用。如果需要,先做一个小的原型。
- 你将会被它拴住吗?有些技术缺乏可取消性,当条件能发生变化时,这可能对项目有致命打击。我们要考虑它时开放技术还是专利技术,如果是开发的技术,那又开放到什么程度?
- 维护成本是多少?会不会随着时间的推移,它的维护成本会非常昂贵?毕竟,方案的花费不应该高于要解决的问题,否则就是一次失败的投资。
具体技巧
- 也许在项目中真正评估技术方案还为时太早。那就好。如果你在做系统原型并要演示给客户看,也许一个简单的散列表就可以代替数据库了。如果你还没有足够的经验,不要急于决定用什么技术。
- 每一门技术都会有优点和缺点,无论它是开源的还是商业产品、框架、工具或者语言,一定要清楚它的利弊。
- 不要开发那些你容易下载到的东西。虽然有时需要从最基础开发所有你需要的东西,但那时相当危险和昂贵的。
提早集成,频繁集成
在产品的开发过程中,集成是一个主要的风险区域。让你的子系统不停地增长,不去做系统集成,就等于一步一步把自己置于越来越大的风险中,潜在的分歧会继续增加。相反,尽可能早地集成也更容易发现风险,这样风险及相关的代价就会相当低。而等的时间越长,你也就会越痛苦。
你能集成并且独立
集成和独立不是相互矛盾的,你可以一边进行集成,一边进行独立开发。
使用mock对象来隔离对象之间的依赖关系,这样在集成之前就可以先做测试。用一个mock对象模拟真实的对象(或者子系统)。mock对象就是真实对象的替身,它并不提供真实对象的功能,但是它更容易控制,能够模仿需要的行为,使测试更加简单。
你可以使用mock对象,编写独立的单元测试,而不需要立刻就集成和测试其他系统,只有当你自信它能工作的时候,才开始集成。
当早期就进行集成的时候,你会看到子系统之间的交互和影响,你就可以估算它们之间通信和共享的信息数据。相反,如果你推迟集成的时间,解决这些问题就会变得很难,需要大量和大范围地修改代码,会造成项目延期和一片混乱。
具体技巧
- 成功的集成就意味着所有的单元测试不停地通过。
- 通常每天要和团队成员一起集成代码好几次,比如平均每天5~10次,甚至更多。但不要过于频繁了。
- 如果你集成的问题很大,那一定是集成得不够频繁。
- 对那些原型和实验代码,也许你想要独立开发,而不要想在集成上浪费时间。但是不能独立开发太长时间。一旦你有了经验,就要快速地开始集成。
保持可以发布
Checked-in code is always ready for action.
在团队里工作,修改一些东西的时候必须很谨慎。你要时刻经济,没错改动都会影响系统的状态和整个团队的工作效率。下面是一个简单的工作流程,可以防止你提交破坏系统的代码:
- 在本地运行测试。先保证你完成的代码可以编译,并且能通过所有的单元测试。接着确保系统中的其他测试都可以通过。
- 检出最新的代码。从版本控制系统中更新代码到最新的版本,再变异和运行测试。这样往往会发现让你吃惊的事情:其他人提交的代码和你的代码发生了冲突。
- 提交代码。
最好的办法是你有一个持续集成系统,可以自动集成并报告集成结果。持续集成系统就是在后台不停地检出、构建和测试代码的应用。你可以自己使用脚本快速实现这样的方式,但如果你选择已有的免费、开源的解决方案,它们会提供更多的功能且更加稳定。
具体技巧
- 有时候,做一些大的改动后,你无法花费太多的时间和精力去保证系统一直可以发布。如果总共需要一个月的时间才能保证它一周之内可以发布,那就算了。但这只应该是例外,不能养成习惯。
- 如果你不得不让系统常去不可以发布,那就做一个(代码和架构的)分支版本,你可以继续进行自己的实验,如果不行,还可以撤销,从头再来。千万不能让系统既不可以发布又不可以撤销。
提早实现自动化部署
系统能在你的机器上运行,或者能在开发者和测试人员对机器上运行,当然很好。但是它同时也需要能够部署在用户的机器上,如果系统能运行在开发服务器上,那很好,但是它同时也要运行在生产环境中。
QA should test deployment.
这就意味着,你要能用一种可重复和可靠的方式,在目标机器上部署你的应用。如果现在你还是手工帮助质量保证人员安装应用,花一些时间,考虑如何将安装过程自动化。这样只要用户需要,你就可以随时为它们安装系统。要提早实现它,这样让质量保证团队既可以测试应用,又可以测试安装过程。
有了自动化部署系统后,在项目开发的整个过程中,会更容易适应互相依赖的变化。很可能你在安装系统的时候,会忘记添加需要的库或组建——在任意一台机器上运行自动化安装程序,你很快就会知道什么丢失了。
从第一天起就开始交付
一开始就进行全面部署,而不是等到项目的后期,这会有很多好处。事实上,有些项目在正式开发之前,就设置好了所有的安装环境。
在我们公司,要求大家为预期客户实现一个简单的功能演示——验证一个概念的可行性。即使项目还没有正式开始,我们就有了单元测试、持续集成和基于窗口的安装程序。这样,我们就可以更容易更简单地给用户交互这个演示系统。
在签约之前,就能提供出如此强大的演示,这无疑证明了我们非常专业,具有强大的开发能力。
具体技巧
- 一般产品在安装的时候,都需要有相应的软硬件环境。这些环境的不同很可能导致很多技术支持的电话。所以检查这些依赖关系,也是安装过程的一部分。
- 在没有询问并征得用户的同意之前,安装程序绝对不能删除用户的数据
- 部署一个紧急修复的Bug应该很简单,特别是在生产服务器的环境中。你不想在压力之下,凌晨三点半还在手工部署系统。
- 用户应该可以安全并且完整地卸载安装程序,特别是在质量保证人员的机器环境中。
- 如果维护安装脚本变得很困难,那很可能是一个早起警告,预示着很高的维护成本。
- 如果你打算把持续部署系统和产品CD或者DVD刻录机连接到一起,你就可以自动地为每个构建制作出一个完整且有标签的光盘。任何人想要最新的构建,只要从架子上拿最上面的一张光盘安装即可。
使用演示获得频繁反馈
Requirements are as fluid as ink.
没有人的思想和观点可以及时冻结,特别是项目的客户。就算算他们已经告诉你想要的东西了,他们的期望和想法还是在不停地进化——特别是当他们在使用新系统的部分功能时,他们才开始意识到它的影响和可能发生的问题。这就是人的本性。
此处省略了数值分析中偏微分方程的例子……
应该定期地,每隔一段时间,例如一个迭代,就与客户会晤,并且演示已经完成的功能特性。如果你能与客户频繁协商,根据他们的反馈开发,每个人都可以从中受益。客户会清楚你的工作进度。反过来,他们也会提炼需求,然后趁热反馈到你的团队中。这样,他们就会基于自己进化到期望和理解为你导航,你编写的程序也就越来越接近他们的真实需求。客户也会基于可用的预算和时间,根据你们真实的工作进度,排列任务的优先级。
维护项目术语表(Wiki规格)
不一致的术语是导致需求误解的一个主要原因。企业喜欢用看似普通浅显的词语来表达非常具体、深刻的意义。
团队中的程序员们使用了和用户或者业务人员不同的术语,最后因为“阻抗失调”导致Bug和设计错误。这样的事情经常发生。
为了避免这类问题,需维护一份项目术语表。人们应该可以公开访问它,一般是在企业内部网或者Wiki上。
在项目开发过程中,从术语表中为程序结构——类、方法、模型、变量等选择合适的名字,并且要检查和确保这些定义一直符合用户的期望。
跟踪问题(工作项)
随着项目的进展,你会得到很多反馈——修正、建议、变更要求、功能增强、Bug修复等。要注意的信息很多,随机的邮件和潦草的告示帖上无法应付的。所以,要有一个跟踪系统记录所有这些日志。
具体技巧
- 当你第一次试图用这种方法和客户一起工作的时候,也许他们被这么多的发布吓到了。所以,要让他们知道,这些都是内部的发布(演示),时为了他们自己的利益,不需要发布给全面的最终用户。
- 一些客户,也许会觉得没有时间应付每天、每周甚至时每两周的回忆。毕竟,他们还有自己的全职工作。所以要尊重客户的时间。如果客户只可以接受一个月一次会议,那么就定一个月。
- 一些客户的联络人的全职工作就是参加演示会议。他们巴不得每隔一小时就有一次演示和反馈。你会发现这么频繁的回忆很难应付,而且还要开发代码让他们看。缩减次数,只有在你做完一些东西可以给他们演示的时候,大家才碰面。
- 演示时用来让客户反馈的,有助于驾驭项目的方向。如果缺少功能或者稳定性的时候,不应该拿来演示,那只能让人生气。可以及早说明期望的功能:让客户知道,他们看到的是一个正在开发中的应用,而不是一个最终已经完成的产品。
使用短迭代,增量发布
统一过程和敏捷方法都使用迭代和增量开发。使用增量开发,可一次开发应用功能的几个小组。每一轮的开发都是基于前一次的功能,增加为产品增值的新功能。这时你就可以发布或者演示产品。
迭代开发式是,你在小且重复的周期里完成各种开发任务:分析、设计、实现、测试和获得反馈,所以叫做迭代。
Show me a detailed long-term plan and I will show you a project that’s doomed.
对付大项目最理想的办法就是小步前进,这也是敏捷方法的核心。大步跳跃大大的增加了风险,小步前进才可以帮助你很好地把握平衡。
大部分用户都希望现在就有一个够用的软件,而不是在一年之后,得到一个超级的好软件,确定使产品可用的核心功能,然后把它们放在生产环境中,越早找到用户的手里越好。
询问用户哪些是产品可用且必不可少的核心功能,不要为所有可能需要的华丽功能而分心,不要沉迷于你的想象而去做那些华而不实的用户界面。有一堆的理由,值得你尽快把软件交到用户手中:只要交到用户手里,你就有了收入,这样就有更好的理由,继续为产品投资了。从用户那里得到的反馈会让我们进一步理解什么是用户真正想要的,以及下一步该实现哪些功能。也许你会发现一些过去认为重要的功能,现在已经不再重要了。
使用短迭代和增量开发可以让开发者更加专注于自己的工作,如果别人告诉你有一年的时间来完成系统,你会觉得时间很长,如果目标很遥远,又很难让自己去专注于他。在这个快节奏的社会,我们都希望更快地得到结果,希望更快的见到有形的东西。这不一定是坏事。相反,他会使一件好事,只要把它转换成生产率和正面的反馈。
具体技巧
- 关于迭代时间长短一直是一个非常有争议的问题,没有规定说迭代必须要紧挨着下一个迭代。
- 如果每个迭代的时间都不够用,要么上任务太大,要么是迭代的时间太短,把握好自己的节奏。
- 如果发布的功能背离了用户的需要,多半是因为迭代的周期太长了。用户的需要、技术和我们对需求的理解都会随着时间的推移而变化,在项目发布的时候,需要清楚地反映出这些变化。如果你发现自己工作时还带有过时的观点和陈腐的想法,那么很可能你等待太长时间做调整了。
- 增量的发布必须是可用的,并且能为用户提供价值,你怎么知道用户会觉得有价值呢?这当然要去问用户。
固定的价格就意味着背叛承诺
A fixed price guarantees a broken promise.
固定价格的合同会是敏捷团队的一大难题,我们一直在讨论如何用持续、迭代和增量的方式工作。但是现在却有些人跑过来,想提早知道他会花费多少时间及多少成本。软件项目,天生就是变化无常的,不可重复。如果要提前给出一个固定的价格,就几乎肯定不能遵守开发上的承诺。
根据自己的处境,选择不同的战略。
- 主动提议先构建系统最初的、小的和有用的部分,挑选一系列小的功能,这样完成第一次交付差不多六到八周。向客户解释,这时候还不是要完成所有的功能,而是要足够一次交付,并能让用户真正使用。
- 第一个迭代结束时,客户有两个选择:可以选择一系列新的功能,继续进入下一个迭代;或者可以取消合同,仅需支付第一个迭代的几周费用。他们要么把现在的成果扔掉要么找其他的团队来完成它。
- 如果他们选择继续前进,那么这时候,应该就能很好地预测下一个迭代工作。在下一次迭代结束的时候,用户仍然有同样的选择机会:要么现在停止,要么继续下一个迭代。
对客户来说,这种方式的好处是项目不可能会死亡。他们可以很早的看到工作的进度或者不足之处。他们总是可以控制项目,可以随时停止项目,不需要缴纳任何违约金。他们可以控制先完成哪些功能,并能精确的知道需要花费多少资金。总而言之客户会承担更低的风险。
具体技巧
- 如果你对答案不满意,那么看看你是否可以改变问题。
- 如果你是在一个基于计划的非敏捷环境中中工作,那么要么考虑一个基于计划且非敏捷的开发方法,要么换一个不同的环境。
- 如果你在完成第一个迭代开发之前,拒绝做任何评估,也许你会失去这个合同,让位于那些提供了评估的人,无论他们做了多么不切实际的承诺。
- 敏捷不是意味着开始编码,我们最终会知道何时可以完成,你仍然需要根据当前的知识和猜想,做一个大致的评估,解释如何才能到达这个目标,并给出误差范围。
- 如果你现在别无选择,你不得不提供一个固定的价格,那么你需要学到真正好的评估技巧。
- 也许你会考虑在合同中确定每个迭代的固定价格,但迭代的数量是可以商量的,它可以根据当前的工作状况进行调整。