1

书名:高效程序员的 45 个习惯——敏捷开发之道
作者:(美) Venkat Subramaniam,(美) Andy Hunt
译者:钱安川,郑柯

敏捷——高效软件开发之道

软件开发就像是在冲浪,一直处在动态、不断变化的环境中。在项目研发过程中出现的需求变化和挑战就是你在冲浪时要应对的海浪。不管是预料之外的波浪冲击,还是预想不到的设计失败,敏捷开发在两种情况下都要求能够做到快速地适应变化。

我们都见过了因为开发过程的冗余、笨重,以及繁杂而失败的项目。世界上应该有一种更好的软件开发方法——只关注真正需要的事情,而少关心那些占用大量时间却无甚裨益的不重要的事情。

敏捷开发就是一个在高度协作的环境中,不断地使用反馈进行自我调整和完善。敏捷方法可以快速地响应变化,它强调团队合作,人们专注于具体可行的目标。这一方法打破了那种基于计划的瀑布式软件开发方法,将软件开发的实际重点转移到一种更加自然和可持续的开发方式上。

敏捷开发宣言

  • 个体和交互胜过过程和工具;

  • 可工作的软件胜过面面俱到的文档;

  • 客户协作胜过合同谈判;

  • 响应变化胜过遵循计划。

虽然右项也有价值,但我们认为左项具有更大的价值。

态度决定一切

软件依赖人,而不是依赖于项目的甘特图和里程表。

1. 做事

找到问题的罪魁祸首是错误发生后的第一要务吗?不,解决问题才是。把矛头对准问题的解决办法,而不是人。

2. 欲速则不达

拙劣的代码工人会这样不假思索地改完代码,然后快速转向下一个问题;而优秀的程序员会想要深挖一层,尽力去理解为什么这里必须进行修改,更重要的是,他会明白这样做会产生什么影响。

一个项目经历了常年的开发,代码中有成千上万的 +1 和 -1 修正,在这样的代码中添加新的功能或者修复 Bug,注定难逃厄运。所以,不要急于修复一段没能真正理解的代码,要解决真正的问题,不要治标不治本。

不要坠入快速地简单修复之中。要投入时间和精力保持代码的整洁、敞亮。

3. 对事不对人

面对明显的错误,往往会有以下几类反应:

  • 否定个人能力;

  • 指出明显的缺点,并否定其观点;

  • 询问队友,并提出自己的顾虑。

请避免在事态发生的时候表现出前两种反应。在一个需要紧密合作的开发团队中,如果能稍加注意礼貌地对待他人,将会有益于整个团队关注真正有价值的问题,而不会勾心斗角、误入歧途。

我们每个人都会有更好的想法,也会有不对的想法,团队中的每个人都需要自由地表达观点。不要害怕受到批评,也不要轻易地批评他人。

你不需要很出色才能起步,但是你必须起步才能变得出色。
能容纳自己并不接受的想法,表明你的头脑足够有学识。

4. 排除万难

你深知怎么做才是正确的,或者至少知道目前的做法是错误的。要有勇气向其他的项目成员、老板或客户解释你的不同观点。当然,这并不容易。也许你会拖延项目的进度,冒犯项目经理,甚至惹恼投资人。但你都要不顾一切,向着正确的方向前进。

学无止境

敏捷需要持续不断地学习和充电。软件开发行业是一个不停发展和永远变化的领域。许多新技术都基于现有的技术和思想。它们会加入一些新的东西,这些新东西是逐步加入的量。如果我们不跟踪变化,技术就会逐渐变的难以应付。

5. 跟踪变化

赫拉克利特说过:『唯有变化是永恒的』。历史已经证明了这句真理,在当今快速发展的 IT 时代尤是如此。我们从事的是一项充满激情且不断变化的工作。如果你毕业于计算机相关专业,并觉得自己已经学完了所有知识,那就大错特错了。

那么如何才能跟上技术变化的步伐呢?以下是一些建议:

  • 迭代和增量式的学习;

  • 了解最新行情;

  • 参加本地的用户组活动;

  • 参加研讨会议;

  • 阅读;

  • 跟踪技术变化。

我们不可能精通每一项技术,没有必要做这样的尝试。我们只需要在某一方面成为专家,就能够用同样的方法,更容易地成为新领域的专家。要正确地把握自己投入的精力。

6. 对团队投资

团队中的开发者们各自具有不同的能力、经验和技术。每个人都各有所长,不同才能和背景的人混在一起,是一个非常理想的学习环境。

但是,在一个团队之中,如果只是个人技术很好还远远不够。一个学习型的团队才是较好的团队。

「午餐会议」是在团队中分享知识的非常好的方式。在一周中挑选一天进行技术分享,可以是任意主题。这种会议非常有用,它促进了整个团队对于这个行业的了解,自己也可以从其他人身上学到很多东西,这将直接有助于自己的职业生涯。

7. 懂得丢弃

敏捷的根本之一就是拥抱变化。既然变化是永恒的,我们便不能一直使用不变的技术和工具。这也就是说,我们需要不断的学习新技术和新方法,同时也要学会丢弃。

打破旧习惯很难,更难的是自己还没有意识到这一问题。丢弃的第一步,就是要意识到自己还在使用过时的方法。思维定式是经过多年摸爬滚打才构建形成的,它已经根深蒂固,没有人可以轻易的丢弃它们。

我们应该力求尽可能完全的转入到新的开发环境中。只有更少的被旧习惯牵绊,才能更容易养成新的习惯。

8. 打破沙锅问到底

不能仅满足于别人告诉自己的表面现象。要不停的提问,直到自己明白问题的根源所在。这样既有利于自己清楚问题的真相,也可能会在不经意间避免了一次巨大的灾难。

9. 把握开发节奏

在许多不成功的项目中,工作计划都是被随便安排,没有任何地规律。但是敏捷项目应该具有一个节奏和循环,并使其伴随整个生命周期。

在敏捷开发中,保证迭代和固定周期,并保证自己能够按时在下班前完成自己的任务。

交付用户想要的软件

没有任何计划在遇敌后还能继续执行。

我们真正的敌人不是客户、不是用户,也不是队友,而是变化。为了正确的应对变化,及时为用户交付需要的软件,我们需要做出相应的工作。

10. 让客户做决定

在设计方面,做决定的时候必须有开发者参与。但在一个项目之中,他们不应做出所有的决定,特别是那些与业务相关的部分。

开发者及项目经理需要做的一个最为重要的决定就是:判断哪些是自己决定不了的,应该把这些交由企业主决定。

11. 让设计指导而不是操纵开发

严格的需求——设计——代码——测试开发流程源于理想化的瀑布式开发方法,它导致了在前期进行了过多的设计。在这样的项目的生命周期中,更新和维护这些详细的设计文档成为了主要的工作。

敏捷方法建议在开发初期就进行编码。但这并不意味着就没有设计。绘制关键的工作图,如 UML,是必不可少的,因为要使用类及其交互关系来描述系统是如何组织的。在做这些设计的时候,我们需要时间去考虑各种不同选择的利弊,并进行权衡。

设计可以分为两层:战略和战术。前期的设计属于战略,通常只有在没有深入理解需求的时候才会需要这样的设计。换句话说,它应该只描述总体战略,而不应该涉及到具体的细节。而那些如程序方法、参数、字段和对象交互顺序等细节部分,应该留到战术设计阶段再具体展开。

12. 合理的使用技术

在考虑引入新技术或框架之前,需要首先把亟待解决的问题找出来,之后考虑如下问题:

  • 这个技术框架真的可以解决这个问题吗?

  • 被它困住吗?

  • 维护成本是多少?

框架和新技术不应成为项目的绊脚石,在选择技术的同时,避免「简历驱动型开发」。

新技术就应该像是新的工具,可以帮助你更好地工作,而不应该成为你的工作。

13. 保持可以发布

在团队中,修改一些东西的时候务必谨慎。要保证每次代码提交都可以使系统处于可发布状态。为了防止提交破坏系统的代码,我们可以遵循以下工作流程:

  1. 在本地运行测试;

  2. 检出最新的代码;

  3. 提交代码。

14. 提早集成

敏捷的一个主要特点就是持续开发,而不是三天打渔两天晒网式的工作。在产品的开发过程中,集成是一个主要的风险区域。让自己的子系统不断的增长而不做集成,就等于一步步将自己置于越来越大的风险之中。若要规避这个风险,只有提早集成,保持持续而有规律地进行集成。

15. 提早实现自动化部署

系统需要运行在开发者、测试人员,以及客户的机器上。这就意味着,我们需要有一种可重复和可靠的方式,实现在目标机器上进行应用的部署。因而,我们需要实现自动化部署,以保证在整个项目的开发过程之中,可以更容易地适应变化。

16. 使用演示获得频繁反馈

不加修正的航线将难以一直按照既定的轨道和方向延伸,软件开发也是如此,给客户演示所完成功能的时间与得到客户需求的时间间隔越长,我们最终就会离最初需求越远。

如果我们能够与客户频繁的协商,根据他们的反馈进行开发,每个人都可以从中受益。客户会清楚自己的开发进度。反过来,他们也会提炼需求,并及时反馈到自己的开发团队之中。在协商过程之中,维护一个开发人员和客户都能理解的项目术语表是非常重要的。

17. 使用短迭代,增量发布

给我一份详细的长期计划,我就会给你一个注定完蛋的项目。

大型系统的开发是一件非常危险的事情。敏捷方法提倡使用迭代和增量开发。每一次迭代都是基于前一次的功能,增加为产品增值的新功能。在一次迭代之中,开发小组需要完成分析、设计、实现、测试和反馈。每一次迭代结束都是一次里程碑。

在迭代过程中,应确保每次迭代的任务量不会过多或过少,且需要询问用户哪些是不可缺少的、优先级较高的功能。不要为所有可能需要的华丽功能而分心,不要沉迷于想象和猜测。保持合适的迭代周期和正确的迭代任务,确保不会脱离用户的需求。

18. 固定的价格就意味着背叛承诺

固定价格的合同会是敏捷团队的一大难题。敏捷的工作方式是使用迭代和增量来适应变化,这与固定的合约形成了冲突,尽管从客户角度看来,这是理所当然的。

在这种情况下,应该与用户达成合适的协议:在每次迭代结束时,让用户决定是否开启下一个迭代周期,即选择是否继续支付费用。这种情况下,客户可以控制项目,精确地掌握项目的开销,并降低风险。

敏捷反馈

在敏捷项目中,我们将会小步前进,不停地收集反馈并时刻矫正自己。那么,这些反馈从何而来呢?

19. 守护天使

敏捷就是管理变化的,而最频繁变化的东西就是代码。为了应对代码的变化,我们需要持续获得代码健康状态的反馈。因而,我们需要自动化单元测试。

敏捷式的单元测试一旦到位,我们就可以放心地对代码进行重构而不必担心会有意外破坏功能。以下是使用单元测试的一些理由:

  • 单元测试能够及时地提供反馈;

  • 单元测试可以让自己的代码更为健壮;

  • 单元测试是有力的设计工具;

  • 单元测试是使我们自信的后台;

  • 单元测试是解决问题时的探测器;

  • 单元测试是可信的文档;

  • 单元测试是学习工具。

20. 先使用再实现

在实现代码之前,应该先编写测试。这是一种被称之为 TDD(Test Driven Development,测试驱动开发)的技术。

先写测试,有助于消除过度复杂的设计,还可以使我们从用户(使用者)的角度思考设计的效果。

21. 不同环境存在不同问题

我们应该使用持续集成和自动化构建工具实现自动地在多个平台上进行集成和测试。要积极地寻找问题,而不是等待问题找到自己。

22. 自动验收测试

关键业务逻辑必须进行独立地严格测试,且最后一定要通过客户的审批。使用 FIT(集成测试框架)可以帮助客户定义带有新功能的使用样本,方便客户进行结果验收,也方便了测试人员测试更多数据。

23. 度量真实的进度

判断工作进度的最好方式是看实际花费的时间,而不是估计的时间。

如果能够一直让下一步工作可见,就会有助于进度度量。最好的做法就是使用待办事项。通过妥善处理的待办事项,可以随时指导下一步是什么。与此同时,自己的评估技巧会不断地改进,也会越来越清楚一个项目或任务需要花费的准确时间。

清楚项目的真实进度,是一项强大的技术。

24. 倾听用户的声音

我们花费了很大的精力从单元测试之类的代码中获得反馈,但却容易忽略最终用户的反馈。我们不仅需要和真实用户进行交谈,也需要耐心地倾听。

每一个抱怨的背后都隐藏了一个事实。找出真相,修复真正的问题。

敏捷编码

新项目刚开始着手开发时,它的代码往往容易理解与上手。然而随着开发过程的推进,项目不知不觉演变为一个庞然怪物。那么,如何在项目开发过程中对其进行掌控呢?

25. 代码要清晰地表达意图

设计软件有两种方式。一种是设计得尽量简单,并且明显没有缺陷。另一种方式是设计得尽量复杂,并且没有明显的缺陷。

开发代码时,应该更注重可读性,而不是只图自己方便。代码阅读的次数要远远超过编写的次数,所以在编写的时候值得花功夫使其更易于阅读。实际上,从衡量标准来看,代码清晰程度的优先级应该排在执行效率之前。

在编码时,应该遵循 PIE (Program Intently and Expressively,即意图清楚而表达明确地编程)原则。代码必须能够说出自己的意图,并且必须富有想象力。这样的代码更易于被人阅读和理解。代码不会使人迷惑,也就减少了发生潜在错误的可能。好的编码规范可以让代码变得易于理解,同时减少不必要的注释和文档。

26. 用代码沟通

建立代码文档无非两种方式:利用代码本身与利用注释。那么应该文档化自己所有的代码吗?从某种意义上说,是的。但这并不意味着要注释绝大部分的代码。源代码可以被读懂,不是因为其中的注释,而应该是由于它本身优雅而清晰——变量名的运用正确、空格使用得当、逻辑分离清晰,以及表达式简洁等。

foo 是一个具有历史意义的临时变量名称。在许多编程语言中,i 通常用来表示循环变量,s 通常用来表示一个字符串。这在很多情况下都是惯用用法,我们没有必要费尽心机去替换大家已经习惯的变量名称。

对显而易见的代码增加注释也会存在这样的问题,比如在类的构造方法后加上 // Constructor

注释可以用来为读者指定一条正确的代码访问路线图。对于类中的每一个方法,应该说明下列信息:

  1. 为什么需要这个方法?

  2. 方法需要什么输入,对象需要处于什么状态?

  3. 方法执行后,会有什么返回值,对象会处于什么状态?

  4. 可能会发生什么问题?会抛出什么异常?

注意,注释不能替代优秀的代码。

27. 动态评估取舍

考虑性能、便利性、生产力、成本和上市时间。如果性能表现足够,就将注意力放在其他因素上。不要为了感觉上的性能提升或者设计的优雅,而将设计复杂化。如果不能面面俱到,请将客户认为有价值的特性优先考虑。

28. 增量式编程

采用小步幅的、增量式的编程和测试,会使得自己更倾向于构建更小的、更内聚的代码,并及时地获得反馈,而不是埋头盲目地一次性编写大堆的代码。

29. 保持简单

「简单性」这个词汇被人们大大误解了。它并不意味着简陋、业余或是能力不足。恰恰相反,相比于一个复杂、拙劣的解决方案,简单的方案通常更难获得。

切记在开发之中,除非有不可辩驳的理由,否则不要使用模式、原则和高难度技术之类的东西。

30. 编写内聚的代码

类和组件的功能十分相似:每个类或组件只做一件事情,而且要做的很好。要避免创建很大的类或者组件,也不要创建无所不包的大杂烩类。

31. 告知,不要询问

与该主题相关的一个很有用的技术是:命令与查询相分离模式。就是要将功能和方法分为「命令」和「查询」两类,并在源码之中记录下来。

注意,一个常规的「命令」可能会改变对象的状态,而且可能会返回一些有用的价值。而一个「查询」仅仅提供给开发人员对象的状态,并不会对其外部的可见状态进行修改。这也就是说,从外部来看,「查询」不应该具有任何副作用,这是很好的编码实践,因为开发人员可以在单元测试中自由使用它们,在断言或者调试器中调用它们,而不会改变应用的状态。

32. 根据契约进行替换

根据李氏替换原则,任何继承后得到的派生类对象,必须可以替换任何被使用的基类对象,而且调用者不必知道任何差异。如果违反了李氏替换原则,继承层次可能仍然可以提供代码的可重用性,但是将会失去可扩展性。

针对 is-a 关系使用继承;针对 has-a 或 uses-a 关系使用委托(聚合)。

敏捷调试

即使是运作良好的敏捷项目,也会发生错误。在调试时面对真正的问题,是无法用固定的时间来限制的。而对于一个项目来说,这种没有把握的时间消耗是不可接受的。

33. 记录问题解决日志

面对问题并解决问题是开发人员的一种生活方式。但如果一个熟悉的问题再次发生时,我们会希望记起第一次是如何解决它的。然而事实是,我们却不记得当初是如何修复的了。

要想取得更好的效果,不妨维护一个保存曾遇到问题及其解决方案的日志。这样,当问题发生时候,就可以快速搜索之前遇到的方法。

34. 警告就是错误

当程序中出现一个编译错误时,编译器或者该构建工具会拒绝产生可执行文件。我们不得不修改代码才行继续前行。

然而,警告却是另一种状况。如果我们对代码编译警告不闻不问。就有可能会因为某个没有被修改的警告产生恶劣影响。

35. 对问题各个击破

单元测试所带来的积极效益之一,就是它会强迫形成代码的分层,要保证代码可以测试,就必须把它从代码之中解脱出来。

对问题各个击破,这样有很多好处:通过将问题与应用的其他部分隔离开,可以将关注点直接放在与问题相关的议题上;可以通过多种改变,来接近问题发生的核心。

隔离问题不应该只在交付软件之后进行。在构建系统原型、调试和测试时,这一策略也可以起到作用。

36. 报告所有的异常

捕捉到异常后,为了不看到编译器的提示,就把异常忽略掉,这很危险。临时的补救方式很容易被遗忘,并进入到生产系统的代码之中。在面对异常时,不要将它们压制不管,就算是临时这样做也不行。在写代码时要估计到可能发生的问题。

37. 提供有用的错误信息

一方面要提供给用户清晰、易于理解的问题描述和解释,使他们能够寻求变通之法。另一方面,还要提供具备关于错误的详细技术细节给用户,这也方便开发人员寻找代码中真正的问题所在。

敏捷协作

只要是具备一定规模的项目,就必然需要一个团队。依靠单打独斗开发出一个完整产品的日子早已不在。而高效的合作正是敏捷开发的基石。

38. 定期安排会面时间

也许你个人很讨厌开会,但是沟通是项目成功的关键。我们不止要跟客户谈话,还应该与开发人员进行良好的沟通。

立会(即站立会议)是将团队召集在一起,并让每个人了解当下进展情况的好办法。参与者们不允许在立会之中就坐,可以保证会议快速的进行。

若要保证会议的议题不发散,每个人都应该回答以下三个问题:

  • 昨天有什么收获?

  • 今天计划要做哪些工作?

  • 面临哪些障碍?

参加会议的人要遵守一些规则,以保证彼此之间不会分神,也不会导致会议跑题。并切记:只有团队成员——开发人员、产品所有者和协调者可以发言。

39. 架构师必须写代码

作为架构师,不应该只是画一些看起来很漂亮的设计图、说一些像「黑话」一样的词汇、使用一大堆设计模式,这样的设计通常是不会有效的。这些架构师通常会在项目开始时介入,绘制各种各样的设计图,然后在重要的代码实现开始之前离开。有太多这种「PowerPoint 架构师」了,由于得不到反馈,他们的架构设计工作也不会有很好的收效。

切记,新系统的设计者必须要亲自投入到实现之中去。

架构师最重要的任务是:通过找到移除软件设计不可逆性的方式,从而去除所谓架构的概念。

积极的编程可以带来深入的理解。不要使用不愿意编程的架构师——不知道系统的真实情况,是无法展开设计的。

40. 实行代码集体所有制

任何一位团队成员,只要理解某段代码的来龙去脉,就应该可以对其进行处理。如果某一段代码只有一位开发人员能够处理,项目的风险无形之中也就增加了。

在团队之中实行任务轮换制,让每个成员都可以接触到不同部分的代码,可以提升团队整体的知识和专业技能。另一方面,知道别人将会接过自己的代码,就意味着自己要更守规矩。

但是切记,在大型项目之中,如果每个人都可以随意改变任何代码,一定会把项目弄的一团糟。代码集体所有制并不意味着可以随心所欲、到处破坏。

41. 成为指导者

好的想法并不会因为被许多人了解而削弱。当我听到你的主意时,我得到了知识,你的主意也还是很棒。同样的道理,如果你用你的蜡烛点燃了我的,我在得到光明的同时,也没有让你的周围变暗。好主意就像火,可以引领这个世界,同时不削弱自己。

成为指导者,并不意味着要手把手教别人怎么做,或是开展小测验什么的。多数时候,成为指导者,是指在帮助团队成员的同时提升自己。成为指导者意味着分享——而不是固守——自己的知识、经验和体会。

然而,努力爬到高处,再以蔑视的眼神轻视其他人,这似乎是人类本性。也许在没有任何意识的情况下,沟通的障碍就已经建立起来了。团队中的其他人可能出于畏惧或尴尬,而不愿提出问题,这样就无法完成知识的交换了。这类团队中的专家,就像是拥有无数金银财宝的有钱人,却因为健康问题而无法享受。我们要成为指导别人的人,而不是折磨别人的人。

42. 允许大家自己想办法

作为指导者,应该鼓励、引领大家思考如何解决问题。

接纳别人的想法,而不是盲目接受,这是受过教育的头脑的标志。

我们应该接纳别人的想法和看待问题的角度,在这个过程之中,自己的头脑也得到了拓展。如果整个团队都能采纳这样的态度,可以发现团队的知识资本正在快速提升,而且将会完成一些极其出色的工作成果。

43. 准备好后再共享代码

相对于不使用版本控制系统,更坏的情况是错误地使用版本控制系统。

要确保在提交代码之前,所有的单元测试都是可以通过的。使用持续集成是保证源代码控制系统中代码没有问题的一种良好方式。如果需要将尚未完成的源代码传输或是保存起来,有如下选择:

  1. 通过远程访问;

  2. 使用存储设备随身携带;

  3. 使用部分源代码控制系统的特性。

切记,绝不要提交尚未完成的代码。故意加入编译未通过或是没有通过单元测试的代码,对项目来说,应该被视为玩忽职守的犯罪行为。

44. 做代码复查

代码刚刚完成的时候,是寻找问题的最佳时机。如果放任不管,它也不会变得更好。

要寻找深藏不漏的程序 Bug,正式地进行代码检查,其效果是任何已知形式测试的两倍,而且是移除 80% 缺陷的唯一方法。

代码的复查方式可以从以下几种方式种选择:

  1. 通宵复查。可以将团队召集在一起,进行一次「复查之夜」。但这可能不是进行代码复查的最有效的方式;

  2. 捡拾游戏。当某些代码编写完成、通过编译、完成测试,并已经准备签入时,其他开发人员就可以对这些代码进行复查。类似的「提交复查」是一种快速而非正式的方式,保证代码在提交之前是可以被接受的。

  3. 结对变成。在 XP(极限编程)中,不存在一个人独立进行编码的情况。编程总是成对进行的。有第二双眼睛在旁边盯着,就像是在进行持续的代码复查活动。也就不必安排单独的特定复查时间了。

复查过程之中,基本的检查列表如下:

  • 代码能否易于读懂和理解?

  • 是否有明显的错误?

  • 代码是否会对应用的其他部分产生不利的影响?

  • 是否存在重复的代码?

  • 是否存在可以改进或重构的部分?

45. 及时通报进展与问题

接收到一个任务,就意味着做出了要准时交付的承诺。在截止日期到来之前才通知大家工作没有完成,除了使大家感到窘迫之外,对自己的事业发展也没有任何好处。

如果能够提前告知项目进展,面对项目可能的延期情况,管理人员就可以进行任务的调整,而不至于在项目截止期前束手无策。

经常抬头看看四周,而不是只埋头于自己的工作。

走向敏捷

无论经验是否丰富,不管过去有什么样的成功,遇到什么样的挑战,只要进行一个新的实践,就可以让人头脑清醒,并让自己的工作和生活从此发生改变。使用这些实践的子集,能够拯救濒临失败的项目于水火,也可以使得从此往后的项目变得完全不同。


dailybird
1.1k 声望73 粉丝

I wanna.