【高效程序员的 45 个习惯】读书笔记

好的习惯像是指南针,即使偶尔偏离航线,仍旧能找回正确的方向。


更新历史

  • 2020.02.18:重新上线
  • 2014.09.11:完成初稿

第1章 敏捷——高效软件开发之道

敏捷开发宣言

我们正通过亲身实践和帮助他人实践,揭示了一些更好的软件开发方法。通过这项工作,我们认为:

  • 个体和交互胜过过程和工具
  • 可工作的软件胜过面面俱到的文档
  • 客户协作胜过合同谈判
  • 响应变化胜过遵循计划
  • 虽然右项也有价值,但我们认为左项具有更大的价值。

敏捷方法可以快速地响应变化,它强调团队合作,人们专注于具体可行的目标(实现真正可以工作的软件),这就是敏捷的精神。它打破了那种基于计划的瀑布式软件开发方法,将软件开发的实际重点转移到一种更加自然和可持续的开发方式上。

它要求团队中的每一个人(包括与团队合作的人)都具备职业精神,并积极地期望项目能够获得成功。它并不要求所有人都是有经验的专业人员,但必须具有专业的工作态度——每个人都希望尽最大可能做好自己的工作。

开发要持续不断,切勿时续时断 Continuous development, not episodic

为什么要进行持续开发呢?因为软件开发是一项非常复杂的智力活动,你遗留下来的任何问题,要么侥幸不会发生意外,要么情况会变得更糟糕,慢慢恶化直到变得不可控制。当问题累积到一定程度的时候,事情就更难解决,最后无法扭转。面对这样的问题,唯一有效的解决办法就是持续地推进系统前进和完善

敏捷开发就是在一个高度协作的环境中,不断地使用反馈进行自我调整和完善。

敏捷工具箱

全书中,我们会涉及一些敏捷项目常用的基本工具。也许一些工具你还很陌生,所以这里做了简单介绍。想要了解这些工具的详细信息,可以进一步去读附录中的有关参考文献。

  • Wiki:Wiki是一个网站,用户通过浏览器,就可以编辑网页内容并创建链接到一个新的内容页面。Wiki是一种很好的支持协作的工具,因为团队中的每一个人都可以根据需要动态地新增和重新组织网页中的内容,实现知识共享。关于Wiki的更多详情,可查阅《Wiki之道》这篇文章[LC01]。
  • 版本控制:项目开发中所有的产物——全部的源代码、文档、图标、构建脚本等,都需要放入版本控制系统中,由版本控制系统来统一管理。令人惊讶的是,很多团队仍然喜欢把这些文件放到一个网络上共享的设备上,但这是一种很不专业的做法。如果需要一个安装和使用版本控制系统的详细说明,可以查阅《版本控制之道——使用CVS》[TH03]或者《版本控制之道——使用Subversion》[Mas05]。
  • 单元测试:用代码来检查代码,这是开发者获得反馈的主要来源。在本书后面会更多地谈到它,但要真正知道框架可以处理大部分的繁琐工作,让你把精力放到业务代码的实现中。想要了解单元测试,可以看《单元测试之道Java版》[HT03]和《单元测试之道C#版》[HT04],你可以在《JUnitRecipes中文版》[Rai04]一书中找到很多写单元测试的实用技巧。
  • 自动构建:不管是在自己的本地机器上实现构建,还是为整个团队实现构建,都是全自动化并可重复的。因为这些构建一直运行,所以又称为持续集成。和单元测试一样,有大量的免费开源产品和商业产品为你提供支持。《项目自动化之道》[Cla04]介绍了所有自动构建的技巧和诀窍(包括使用JavaLamps)
  • 最后,Ship It! [RG05]一书很好地介绍了怎样将这些基本的开发环境实践方法结合到一起。

先难后易。我们首先要解决困难的问题,把简单的问题留到最后。

第2章 态度决定一切

选定了要走的路,就是选定了它通往的目的地。——Harry Emerson Fosdick(美国基督教现代主义神学家)

做事

在敏捷的团队中,大家的重点是做事。你应该把重点放到解决问题上,而不是在指责犯错者上面纠缠。

指责不能修复bug. Blame doesn’t fix bugs

指责不会修复bug。把矛头对准问题的解决办法,而不是人。这是真正有用处的正面效应。

勇于承认自己不知道答案,这会让人感觉放心。一个重大的错误应该被当作是一次学习而不是指责他人的机会。团队成员们在一起工作,应互相帮助,而不是互相指责

平衡的艺术

  • “这不是我的错”,这句话不对。“这都是你的错”,这句话更不对。
  • 如果你没有犯过任何错误,就说明你可能没有努力去工作。
  • 开发者和质量工程师 (QA) 争论某个问题是系统本身的缺陷还是系统增强功能导致的,通常没有多大的意义。与其如此,不如赶紧去修复它。
  • 如果一个团队成员误解了一个需求、一个API调用,或者最近一次会议做的决策,那么,也许就意味着团队的其他成员也有相同的误解。要确保整个团队尽快消除误解。
  • 如果一个团队成员的行为一再伤害了团队,则他表现得很不职业。那么,他就不是在帮助团队向解决问题的方向前进。这种情况下,我们必须要求他离开这个团队。
  • 如果大部分团队成员(特别是开发领导者)的行为都不职业,并且他们对团队目标都不感兴趣,你就应该主动从这个团队中离开,寻找更适合自己发展的团队(这是一个有远见的想法,没必要眼睁睁地看着自己陷入一个“死亡之旅”的项目中[You99])。

欲速则不达

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

防微杜渐 Beware of land mines

不要孤立地编码 Don’t code in isolation

孤立非常危险,不要让开发人员完全孤立地编写代码。如果团队成员花些时间阅读其他同事写的代码,他们就能确保代码是可读和可理解的,并且不会随意加入这些“+1或-1”的代码。阅读代码的频率越高越好。实行代码复审,不仅有助于代码更好理解,而且是发现bug最有效的方法之一。

使用单元测试 Use unit tests

另一种防止代码难懂的重要技术就是单元测试。单元测试帮助你很自然地把代码分层,分成很多可管理的小块,这样就会得到设计更好、更清晰的代码。更深入项目的时候,你可以直接阅读单元测试——它们是一种可执行的文档。有了单元测试,你会看到更小、更易于理解的代码模块,运行和使用代码,能够帮助你彻底理解这些代码。

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

平衡的艺术

  • 你必须要理解一块代码是如何工作的,但是不一定需要成为一位专家。只要你能使用它进行有效的工作就足够了,不需要把它当作毕生事业。
  • 如果有一位团队成员宣布,有一块代码其他人都很难看懂,这就意味着任何人(包括原作者)都很难维护它。请让它变得简单些。
  • 不要急于修复一段没能真正理解的代码。这种+1/-1的病症始于无形,但是很快就会让代码一团糟。要解决真正的问题,不要治标不治本。

所有的大型系统都非常复杂,因此没有一个人能完全明白所有的代码。除了深入了解你正在开发的那部分代码之外,你还需要从更高的层面来了解大部分代码的功能,这样就可以理解系统各个功能块之间是如何交互的。

对事不对人

消极扼杀创新 Negativity kills innovation

我们每个人都会有好的想法,也会有不对的想法,团队中的每个人都需要自由地表达观点。即使你的建议不被全盘接受,也能对最终解决问题有所帮助。不要害怕受到批评。记住,任何一个专家都是从这里开始的。用Les Brown的一句话说就是:“你不需要很出色才能起步,但是你必须起步才能变得很出色。”

下面是一些有效的特殊技术。

  • 设定最终期限。如果你正在参加设计方案讨论会,或者是寻找解决方案时遇到问题,请设定一个明确的最终期限,例如午饭时间或者一天的结束。这样的时间限制可以防止人们陷入无休止的理论争辩之中,保证团队工作的顺利进行。同时(我们觉得)应现实一些:没有最好的答案,只有更合适的方案。设定期限能够帮你在为难的时候果断做出决策,让工作可以继续进行。
  • 逆向思维。团队中的每个成员都应该意识到权衡的必要性。一种客观对待问题的办法是:先是积极地看到它的正面,然后再努力地从反面去认识它。目的是要找出优点最多缺点最少的那个方案,而这个好办法可以尽可能地发现其优缺点。这也有助于少带个人感情。
  • 设立仲裁人。在会议的开始,选择一个仲裁人作为本次会议的决策者。每个人都要有机会针对问题畅所欲言。仲裁人的责任就是确保每个人都有发言的机会,并维持会议的正常进行。仲裁人可以防止明星员工操纵会议,并及时打断假大空式发言。
  • 支持已经做出的决定。一旦方案被确定了(不管是什么样的方案),每个团队成员都必须通力合作,努力实现这个方案。每个人都要时刻记住,我们的目标是让项目成功满足用户需求。客户并不关心这是谁的主意——他们关心的是,这个软件是否可以工作,并且是否符合他们的期望。结果最重要。
  • 设计充满了妥协(生活本身也是如此),成功属于意识到这一点的团队。工作中不感情用事是需要克制力的,而你若能展现出成熟大度来,大家一定不会视而不见。这需要有人带头,身体力行,去感染另一部分人。

对事不对人。让我们骄傲的应该是解决了问题,而不是比较出谁的主意更好

平衡的艺术

  • 尽力贡献自己的好想法,如果你的想法没有被采纳也无需生气。不要因为只是想体现自己的想法而对拟定的好思路画蛇添足。
  • 脱离实际的反方观点会使争论变味。若对一个想法有成见,你很容易提出一堆不太可能发生或不太实际的情形去批驳它。这时,请先扪心自问:类似问题以前发生过吗?是否经常发生?
  • 也就是说,像这样说是不够的:我们不能采用这个方案,因为数据库厂商可能会倒闭。或者:用户绝对不会接受那个方案。你必须要评判那些场景发生的可能性有多大。想要支持或者反驳一个观点,有时候你必须先做一个原型或者调查出它有多少的同意者或者反对者。
  • 在开始寻找最好的解决方案之前,大家对“最好”的含义要先达成共识。在开发者眼中的最好,不一定就是用户认为最好的,反之亦然。
  • 只有更好,没有最好。尽管“最佳实践”这个术语到处在用,但实际上不存在“最佳”,只有在某个特定条件下更好的实践。
  • 不带个人情绪并不是要盲目地接受所有的观点。用合适的词和理由去解释为什么你不赞同这个观点或方案,并提出明确的问题。

排除万难,奋勇前进

有一则寓言叫“谁去给猫系铃铛”(Who Will Bell the Cat)。老鼠们打算在猫的脖子上系一个铃铛,这样猫巡逻靠近的时候,就能预先得到警报。每只老鼠都点头,认为这是一个绝妙的想法。这时一只年老的老鼠问道:“那么,谁愿意挺身而出去系铃铛呢?”毫无疑问,没有一只老鼠站出来。当然,计划也就这样泡汤了

做正确的事。要诚实,要有勇气去说出实情。有时,这样做很困难,所以我们要有足够的勇气。

平衡的艺术

  • 如果你说天快要塌下来了,但其他团队成员都不赞同。反思一下,也许你是正确的,但你没有解释清楚自己的理由。
  • 如果你说天快要塌下来了,但其他团队成员都不赞同。认真考虑一下,他们也许是对的。
  • 如果设计或代码中出现了奇怪的问题,花时间去理解为什么代码会是这样的。如果你找到了解决办法,但代码仍然令人费解,唯一的解决办法是重构代码,让它可读性更强。如果
  • 你没有马上理解那段代码,不要轻易地否定和重写它们。那不是勇气,而是鲁莽。
  • 当你勇敢地站出来时,如果受到了缺乏背景知识的抉择者的抵制,你需要用他们能够听懂的话语表达。“更清晰的代码”是无法打动生意人的。节约资金、获得更好的投资回报,避免诉讼以及增加用户利益,会让论点更有说服力。
  • 如果你在压力下要对代码质量作出妥协,你可以指出,作为一名开发者,你没有职权毁坏公司的资产(所有的代码)。

跟踪变化

如何才能跟上技术变化的步伐呢?幸好,现今有很多方法和工具可以帮助我们继续充电。下面是一些建议。

  • 迭代和增量式的学习。每天计划用一段时间来学习新技术,它不需要很长时间,但需要经常进行。记下那些你想学习的东西——当你听到一些不熟悉的术语或者短语时,简要地把它记录下来。然后在计划的时间中深入研究它。
  • 了解最新行情。互联网上有大量关于学习新技术的资源。阅读社区讨论和邮件列表,可以了解其他人遇到的问题,以及他们发现的很酷的解决方案。选择一些公认的优秀技术博客,经常去读一读,以了解那些顶尖的博客作者们正在关注什么(最新的博客列表请参考pragmaticprogrammer.com)。
  • 参加本地的用户组活动。Java、Ruby、Delphi、.NET、过程改进、面向对象设计、Linux、Mac ,以及其他的各种技术在很多地区都会有用户组。听讲座,然后积极加入到问答环节中。
  • 参加研讨会议。计算机大会在世界各地举行,许多知名的顾问或作者主持研讨会或者课程。这些聚会是向专家学习的最直接的好机会。
  • 如饥似渴地阅读。找一些关于软件开发和非技术主题的好书(我们很乐意为你推荐),也可以是一些专业的期刊和商业杂志,甚至是一些大众媒体新闻(有趣的是在那里常常能看到老技术被吹捧为最新潮流)。
  • 跟踪技术变化。你不需要精通所有技术,但需要清楚知道行业的动向,从而规划你的项目和职业生涯。

平衡的艺术

  • 许多新想法从未变得羽翼丰满,成为有用的技术。即使是大型、热门和资金充裕的项目也会有同样的下场。你要正确把握自己投入的精力。
  • 你不可能精通每一项技术,没有必要去做这样的尝试。只要你在某些方面成为专家,就能使用同样的方法,很容易地成为新领域的专家。
  • 你要明白为什么需要这项新技术——它试图解决什么样的问题?它可以被用在什么地方?
  • 避免在一时冲动的情况下,只是因为想学习而将应用切换到新的技术、框架或开发语言。在做决策之前,你必须评估新技术的优势。开发一个小的原型系统,是对付技术狂热者的一剂良药。

对团队投资

提供你和团队学习的更好平台。通过午餐会议可以增进每个人的知识和技能,并帮助大家聚集在一起进行沟通交流。唤起人们对技术和技巧的激情,将会对项目大有裨益。

  • 不是所有的讲座都能引人入胜,有些甚至显得不合时宜。不管怎么样,都要未雨绸缪;诺亚在建造方舟的时候,可并没有开始下雨,谁能料到后来洪水泛滥呢?
  • 尽量让讲座走入团队中。如果午餐会议在礼堂中进行,有餐饮公司供饭,还要使用幻灯片,那么就会减少大家接触和讨论的机会。
  • 坚持有计划有规律地举行讲座。持续、小步前进才是敏捷。稀少、间隔时间长的马拉松式会议非敏捷也。
  • 如果一些团队成员因为吃午饭而缺席,用美食引诱他们。
  • 不要局限于纯技术的图书和主题,相关的非技术主题(项目估算、沟通技巧等)也会对团队有帮助。
  • 午餐会议不是设计会议。总之,你应专注讨论那些与应用相关的一般主题。具体的设计问题,最好是留到设计会议中去解决。

懂得丢弃

根深蒂固的习惯不可能轻易地就丢弃掉 Expensive mental models aren’t discarded lightly

打破旧习惯很难,更难的是自己还没有意识到这个问题。丢弃的第一步,就是要意识到你还在使用过时的方法,这也是最难的部分。另一个难点就是要做到真正地丢弃旧习惯。思维定式是经过多年摸爬滚打才构建成型的,已经根深蒂固,没有人可以很容易就丢弃它们

学习新的东西,丢弃旧的东西。在学习一门新技术的时候,要丢弃会阻止你前进的旧习惯。毕竟,汽车要比马车车厢强得多。

平衡的艺术

  • 沉舟侧畔千帆过,病树前头万木春。要果断丢弃旧习惯,一味遵循过时的旧习惯会危害你的职业生涯。
  • 不是完全忘记旧的习惯,而是只在使用适当的技术时才使用它。
  • 对于所使用的语言,要总结熟悉的语言特性,并且比较这些特性在新语言或新版本中有什么不同。

打破砂锅问到底

“为什么”是一个非常好的问题。事实上,在一本流行的管理图书《第五项修炼》中,作者建议,在理解一个问题的时候,需要渐次地问5个以上的“为什么”。这听起来就像退回到了4岁,那时对一切都充满着好奇。它是很好的方式,进一步挖掘简单直白的答案,通过这个路线,设想就会更加接近事实真相。

不停地问为什么。不能只满足于别人告诉你的表面现象。要不停地提问直到你明白问题的根源。

平衡的艺术

  • 你可能会跑题,问了一些与主题无关的问题。就好比是,如果汽车启动不了,你问是不是轮胎出了问题,这是没有任何帮助的。问“为什么”,但是要问到点子上。
  • 当你问“为什么”的时候,也许你会被反问:“为什么你问这个问题?”在提问之前,想好你提问的理由,这会有助于你问出恰当的问题。
  • “这个,我不知道”是一个好的起点,应该由此进行更进一步的调查,而不应在此戛然结束。

把握开发节奏

解决任务,在事情变得一团糟之前。保持事件之间稳定重复的间隔,更容易解决常见的重复任务。

平衡的艺术

  • 在每天结束的时候,测试代码,提交代码,没有残留的代码。
  • 不要搞得经常加班。
  • 以固定、有规律的长度运行迭代。也许刚开始你要调整迭代的长度,找到团队最舒服可行的时间值,但之后就必须要坚持。
  • 如果开发节奏过于密集,你会精疲力竭的。一般来说,当与其他团队(或组织)合作时,你需要减慢开发节奏。因此人们常说,互联网时代发展太快,有害健康。
  • 有规律的开发节奏会暴露很多问题,让你有更多鼓起勇气的借口。
  • 就像是减肥一样,一点点的成功也是一个很大的激励。小而可达到的目标会让每个人全速前进。庆祝每一次难忘的成功:共享美食和啤酒或者团队聚餐。

让客户做决定

让你的客户做决定。开发者、经理或者业务分析师不应该做业务方面的决定。用业务负责人能够理解的语言,向他们详细解释遇到的问题,并让他们做决定。

平衡的艺术

  • 记录客户做出的决定,并注明原因。好记性不如烂笔头。可以使用工程师的工作日记或日志、Wiki、邮件记录或者问题跟踪数据库。但是也要注意,你选择的记录方法不能太笨重或者太繁琐。
  • 不要用低级别和没有价值的问题打扰繁忙的业务人员。如果问题对他们的业务没有影响,就应该是没有价值的。
  • 不要随意假设低级别的问题不会影响他们的业务。如果能影响他们的业务,就是有价值的问题。
  • 如果业务负责人回答“我不知道”,这也是一个称心如意的答案。也许是他们还没有想到那么远,也许是他们只有看到运行的实物才能评估出结果。尽你所能为他们提供建议,实现代码的时候也要考虑可能出现的变化。

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

设计满足实现即可,不必过于详细 Design should be only as detailed as needed to implement

如果设计师们把自己的想法绘制成美的文档,然后把它们扔给程序员去编码,那会发生什么

严格的需求—设计—代码—测试开发流程源于理想化的瀑布式开发方法,它导致在前面进行了过度的设计。这样在项目的生命周期中,更新和维护这些详细的设计文档变成了主要工作,需要时间和资源方面的巨大投资,却只有很少的回报。我们本可以做得更好。

设计可以分为两层:战略和战术。前期的设计属于战略,通常只有在没有深入理解需求的时候需要这样的设计。更确切地说,它应该只描述总体战略,不应深入到具体的细节。

好设计是一张地图,它也会进化。设计指引你向正确的方向前进,它不是殖民地,它不应该标识具体的路线。你不要被设计(或者设计师)操纵。

平衡的艺术

  • “不要在前期做大量的设计”并不是说不要设计。只是说在没有经过真正的代码验证之前,不要陷入太多的设计任务。当对设计一无所知的时候,投入编码也是一件危险的事。如果深入编码只是为了学习或创造原型,只要你随后能把这些代码扔掉,那也是一个不错的办法。
  • 即使初始的设计到后面不再管用,你仍需设计:设计行为是无价的。正如美国总统艾森豪威尔所说:“计划是没有价值的,但计划的过程是必不可少的。”在设计过程中学习是有价值的,但设计本身也许没有太大的用处。
  • 白板、草图、便利贴都是非常好的设计工具。复杂的建模工具只会让你分散精力,而不是启发你的工作。

合理地使用技术

盲目地为项目选择技术框架,就好比是为了少交税而生孩子 Blindly picking a framework is like having kids to save taxes

不要开发你能下载到的东西 Don’t build what you can download

根据需要选择技术。首先决定什么是你需要的,接着为这些具体的问题评估使用技术。对任何要使用的技术,多问一些挑剔的问题,并真实地作出回答。

平衡的艺术

  • 也许在项目中真正评估技术方案还为时太早。那就好。如果你在做系统原型并要演示给客户看,也许一个简单的散列表就可以代替数据库了。如果你还没有足够的经验,不要急于决定用什么技术。
  • 每一门技术都会有优点和缺点,无论它是开源的还是商业产品、框架、工具或者语言,一定要清楚它的利弊。
  • 不要开发那些你容易下载到的东西。虽然有时需要从最基础开发所有你需要的东西,但那是相当危险和昂贵的。

保持可以发布

下面是一个简单的工作流程,可以防止你提交破坏系统的代码。

  • 在本地运行测试。先保证你完成的代码可以编译,并且能通过所有的单元测试。接着确保系统中的其他测试都可以通过。
  • 检出最新的代码。从版本控制系统中更新代码到最新的版本,再编译和运行测试。这样往往会发现让你吃惊的事情:其他人提交的新代码和你的代码发生了冲突。
  • 提交代码。现在是最新的代码了,并且通过了编译和测试,你可以提交它们了。
  • 在做上面事情的时候,也许你会遇到这样一个问题——其他人提交了一些代码,但是没有通过编译或者测试。如果发生了这样的事情,要立即让他们知道,如果有需要,可以同时警告其他的同事。当然,最好的办法是,你有一个持续集成系统,可以自动集成并报告集成结果。

保持你的项目时刻可以发布。保证你的系统随时可以编译、运行、测试并立即部署。

平衡的艺术

  • 有时候,做一些大的改动后,你无法花费太多的时间和精力去保证系统一直可以发布。如果总共需要一个月的时间才能保证它一周内可以发布,那就算了。但这只应该是例外,不能养成习惯。
  • 如果你不得不让系统长期不可以发布,那就做一个(代码和架构的)分支版本,你可以继续进行自己的实验,如果不行,还可以撤销,从头再来。千万不能让系统既不可以发布,又不可以撤销。

提早集成,频繁集成

决不要做大爆炸式的集成 Never accept big-bang integration

当早期就进行集成的时候,你会看到子系统之间的交互和影响,你就可以估算它们之间通信

提早集成,频繁集成。代码集成是主要的风险来源。要想规避这个风险,只有提早集成,持续而有规律地进行集成。

平衡的艺术

  • 成功的集成就意味着所有的单元测试不停地通过。正如医学界希波克拉底的誓言:首先,不要造成伤害。
  • 通常,每天要和团队其他的成员一起集成代码好几次,比如平均每天5~10次,甚至更多。但如果你每次修改一行代码就集成一次,那效用肯定会缩水。如果你发现自己的大部分时间都在集成,而不是写代码,那你一定是集成得过于频繁了。
  • 如果你集成得不够频繁(比如,你一天集成一次,一周一次,甚至更糟),也许就会发现整天在解决代码集成带来的问题,而不是在专心写代码。如果你集成的问题很大,那一定是做得不够频繁。
  • 对那些原型和实验代码,也许你想要独立开发,而不要想在集成上浪费时间。但是不能独立开发太长的时间。一旦你有了经验,就要快速地开始集成。

提早实现自动化部署

系统能在你的机器上运行,或者能在开发者和测试人员的机器上运行,当然很好。但是,它同时也需要能够部署在用户的机器上。如果系统能运行在开发服务器上,那很好,但是它同时也要运行在生产环境中。

这就意味着,你要能用一种可重复和可靠的方式,在目标机器上部署你的应用。不幸的是,大部分开发者只会在项目的尾期才开始考虑部署问题。结果经常出现部署失败,要么是少了依赖的组件,要么是少了一些图片,要么就是目录结构有误。

如果现在你还是手工帮助质量保证人员安装应用,花一些时间,考虑如何将安装过程自动化。这样,只要用户需要,你就可以随时为他们安装系统。要提早实现它,这样让质量保证团队既可以测试应用,又可以测试安装过程。如果还是手工安装应用,那么最后把应用部署到生产环境时会发生什么呢?就算公司给你加班费,你也不愿意为不同用户的机器或不同地点的服务器上一遍又一遍地安装应用。

一开始就实现自动化部署应用。使用部署系统安装你的应用,在不同的机器上用不同的配置文件测试依赖的问题。质量保证人员要像测试应用一样测试部署。

平衡的艺术

  • 一般产品在安装的时候,都需要有相应的软、硬件环境。比如,Java或Ruby的某个版本、外部数据库或者操作系统。这些环境的不同很可能会导致很多技术支持的电话。
  • 所以检查这些依赖关系,也是安装过程的一部分。
  • 在没有询问并征得用户的同意之前,安装程序绝对不能删除用户的数据。
  • 部署一个紧急修复的bug应该很简单,特别是在生产服务器的环境中。你知道这会发生,而且你不想在压力之下,在凌晨3点半,你还在手工部署系统。
  • 用户应该可以安全并且完整地卸载安装程序,特别是在质量保证人员的机器环境中。
  • 如果维护安装脚本变得很困难,那很可能是一个早期警告,预示着——很高的维护成本(或者不好的设计决策)。
  • 如果你打算把持续部署系统和产品CD或者DVD刻录机连接到一起,你就可以自动地为每个构建制作出一个完整且有标签的光盘。任何人想要最新的构建,只要从架子上拿最上面的一张光盘安装即可。

使用演示获得频繁反馈

你时常会听到一些人想要冻结需求。但是,现实世界中的需求就像是流动着的油墨。你无法冻结需求,正如你无法冻结市场、竞争、知识、进化或者成长一样。就算你真的冻结了,也很可能是冻结了错的东西。

应该定期地,每隔一段时间,例如一个迭代的结束,就与客户会晤,并且演示你已经完成的功能特性。如果你能与客户频繁协商,根据他们的反馈开发,每个人都可以从中受益。客户会清楚你的工作进度。反过来,他们也会提炼需求,然后趁热反馈到你的团队中。这样,他们就会基于自己进化的期望和理解为你导航,你编写的程序也就越来越接近他们的真实需求。客户也会基于可用的预算和时间,根据你们真实的工作进度,排列任务的优先级。

清晰可见的开发。在开发的时候,要保持应用可见(而且客户心中也要了解)。每隔一周或者两周,邀请所有的客户,给他们演示最新完成的功能,积极获得他们的反馈。

平衡的艺术

  • 当你第一次试图用这种方法和客户一起工作的时候,也许他们被这么多的发布吓到了。所以,要让他们知道,这些都是内部的发布(演示),是为了他们自己的利益,不需要发布给全部的最终用户。
  • 一些客户,也许会觉得没有时间应付每天、每周甚至是每两周的会议。毕竟,他们还有自己的全职工作。所以要尊重客户的时间。如果客户只可以接受一个月一次会议,那么就定一个月。
  • 一些客户的联络人的全职工作就是参加演示会议。他们巴不得每隔1小时就有一次演示和反馈。你会发现这么频繁的会议很难应付,而且还要开发代码让他们看。缩减次数,只有在你做完一些东西可以给他们演示的时候,大家才碰面。
  • 演示是用来让客户提出反馈的,有助于驾驭项目的方向。如果缺少功能或者稳定性的时候,不应该拿来演示,那只能让人生气。可以及早说明期望的功能:让客户知道,他们看到的是一个正在开发中的应用,而不是一个最终已经完成的产品。

使用短迭代,增量发布

统一过程和敏捷方法都使用迭代和增量开发。使用增量开发一次开发应用功能的几个小组。每一轮的开发都是基于前一次的功能,增加为产品增值的新功能。这时,你就可以发布或者演示产品。

迭代开发是,在小且重复的周期里,你完成各种开发任务:分析、设计、实现、测试和获得反馈,所以叫作迭代。

给我一份详细的长期计划,我就会给你一个注定完蛋的项目 Show me a detailed long-term plan, and I’ll show you a project that’s doomed

增量开发。发布带有最小却可用功能块的产品。每个增量开发中,使用1~4周左右迭代周期。

平衡的艺术

  • 关于迭代时间长短一直是一个有争议的问题。Andy曾经遇到这样一位客户:他们坚持认为迭代就是4周的时间,因为这是他们学到的。但他们的团队却因为这样的步伐而垂死挣扎,因为他们无法在开发新的代码的同时又要维护很多已经完成了的代码。解决方案是,在每4周的迭代中间安排一周的维护任务。没有规定说迭代必须要紧挨着下一个迭代。
  • 如果每个迭代的时间都不够用,要么是任务太大,要么是迭代的时间太短(这是平均数据,不要因为一次迭代的古怪情况而去调整迭代时间)。把握好自己的节奏。
  • 如果发布的功能背离了用户的需要,那么多半是因为迭代的周期太长了。用户的需要、技术和我们对需求的理解,都会随着时间的推移而变化,在项目发布的时候,需要清楚地反映出这些变化。如果你发现自己工作时还带有过时的观点和陈腐的想法,那么很可能你等待太长时间做调整了。
  • 增量的发布必须是可用的,并且能为用户提供价值。你怎么知道用户会觉得有价值呢?这当然要去问用户。

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

固定价格的合同会是敏捷团队的一大难题。我们一直在谈论如何用持续、迭代和增量的方式工作。但是现在却有些人跑过来,想提早知道它会花费多少时间及多少成本。从客户方来看,这完全是理所应当的。客户觉得做软件就好比是盖一栋楼房,或者是铺设一个停车场,等等。为什么软件不能像建筑业等其他传统的行业一样呢?

软件项目天生就是变化无常的,不可重复。如果要提前给出一个固定的价格,就几乎肯定不能遵守开发上的承诺。那么我们有什么可行的办法呢?我们能做更精确的评估吗?或者商量出另外一种约定。

(1) 主动提议先构建系统最初的、小的和有用的部分(用建筑来打个比方,就是先做一个车库)。挑选一系列小的功能,这样完成第一次交付应该不多于6~8周。向客户解释,这时候还不是要完成所有的功能,而是要足够一次交付,并能让用户真正使用。

(2) 第一个迭代结束时客户有两个选择:可以选择一系列新的功能,继续进入下一个迭代;或者可以取消合同,仅需支付第一个迭代的几周费用,他们要么把现在的成果扔掉,要么找其他的团队来完成它。

(3)如果他们选择继续前进。那么这时候,应该就能很好地预测下一个迭代工作。在下一个迭代结束的时候,用户仍然有同样的选择机会:要么现在停止,要么继续下一个迭代。

基于真实工作的评估。让团队和客户一起,真正地在当前项目中工作,做具体实际的评估。由客户控制他们要的功能和预算。

平衡的艺术

  • 如果你对答案不满意,那么看看你是否可以改变问题。
  • 如果你是在一个基于计划的非敏捷环境中工作,那么要么考虑一个基于计划且非敏捷的开发方法,要么换一个不同的环境。
  • 如果你在完成第一个迭代开发之前,拒绝做任何评估,也许你会失去这个合同,让位于那些提供了评估的人,无论他们做了多么不切实际的承诺。
  • 敏捷不是意味着“开始编码,我们最终会知道何时可以完成”。你仍然需要根据当前的知识和猜想,做一个大致的评估,解释如何才能到达这个目标,并给出误差范围。
  • 如果你现在别无选择,你不得不提供一个固定的价格,那么你需要学到真正好的评估技巧。
  • 也许你会考虑在合同中确定每个迭代的固定价格,但迭代的数量是可以商量的,它可以根据当前的工作状况进行调整[又名工作条款说明(Statement of Work)]。

守护天使

代码在快速地变化。每当你手指敲击一下键盘,代码就会被改变。敏捷就是管理变化的,而且,代码可能是变化最频繁的东西。

编写能产生反馈的代码 Coding feedback

为了应对代码的变化,你需要持续获得代码健康状态的反馈:它是在做你期望的事情吗?最近一次修改有没有无意中破坏了什么功能?这时,你就带上守护天使,确保所有功能都能正常工作。要做到这样,就需要自动化单元测试。

只要有了单元测试,就要让它们自动运行。也就是每次编译或者构建代码的时候,就运行一次测试。把单元测试的结果看作是和编译器一样——如果测试没有通过(或者没有测试),那就像编译没有通过一样糟糕。

  • 单元测试能及时提供反馈。你的代码会重复得到锻炼。但若修改或者重写了代码,测试用例就会检查你是否破坏了已有的功能。你可以快速得到反馈,并很容易地修复它们。
  • 单元测试让你的代码更加健壮。测试帮助你全面思考代码的行为,帮你练习正面、反面以及异常情况。
  • 单元测试是有用的设计工具。正如我们在实践20中谈论到的,单元测试有助于实现简单的、注重实效的设计。
  • 单元测试是让你自信的后台。你测试代码,了解它在各种不同条件下的行为。这会让你在面对新的任务、时间紧迫的巨大压力之下,找到自信。
  • 单元测试是解决问题时的探测器。单元测试就像是测试印制电路板的示波镜。当问题出现的时候,你可以快速地给代码发送一个脉冲信号。这为你提供了一个很自然的发现和解决问题的方法。
  • 单元测试是可信的文档。当你开始学习新API的时候,它的单元测试是最精确和可靠的文档。
  • 单元测试是学习工具。在你开始学习新API的时候,可以为这个API写个单元测试,从而加深自己的理解。这些学习用的测试,不仅能帮助你理解API的行为,还能帮助你快速找到以后可能引入的、无法与现有代码兼容的变化。

使用自动化的单元测试。好的单元测试能够为你的代码问题提供及时的警报。如果没有到位的单元测试,不要进行任何设计和代码修改。

平衡的艺术

  • 单元测试是优质股,值得投资。但一些简单的属性访问方法或者价值不大的方法,是不值得花费时间进行测试的。
  • 人们不编写单元测试的很多借口都是因为代码中的设计缺陷。通常,抗议越强烈,就说明设计越糟糕。
  • 单元测试只有在达到一定测试覆盖率的时候,才能真正地发挥作用。你可以使用一些测试覆盖率工具,大致了解自己的单元测试的覆盖情况。
  • 不是测试越多质量就会越高,测试必须要有效。如果测试无法发现任何问题,也许它们就是没有测试对路。

先用它再实现它

很多成功的公司都是靠着“吃自己的狗食”活着。也就是说,如果要让你的产品尽可能地好,自己先要积极地使用它。

好的设计并不意味着需要更多的类Good design doesn’t mean more classes

当你开发设计面向对象系统的时候,可能会迫使自己使用对象。有一种倾向认为,面向对象的

先用它再实现它。将TDD作为设计工具,它会为你带来更简单更有实效的设计。

平衡的艺术

  • 不要把测试优先和提交代码之前的测试等同起来。测试先行可以帮助你改进设计,但是你还是需要在提交代码之前做测试。
  • 任何一个设计都可以被改进。
  • 你在验证一个想法或者设计一个原型的时候,单元测试也许并不适合。但是,万一这些代码不幸仓促演变成了一个真正的系统,就必须要为它们添加测试(但是最好能重新开始设计系统)。
  • 单纯的单元测试无法保证好的设计,但它们会对设计有帮助,会让设计更加简单。

不同环境,就有不同问题

不同环境,就有不同问题。使用持续集成工具,在每一种支持的平台和环境中运行单元测试。要积极地寻找问题,而不是等问题来找你。

平衡的艺术

  • 硬件比开发人员的时间便宜。但如果你有很多配置,要支持大量的平台,可以选择哪些平台需要内部测试。
  • 只因为不同的栈层顺序、不同的单词大小写等,就能发现很多平台上的bug。因此,即使运行用Solaris的客户比用Linux的少很多,你仍然要在两个系统上都进行测试。
  • 你不希望因为一个错误而收到5次通知轰炸(这就像是双重征税,会导致电子邮件疲劳症)。可以设置一个主构建平台或者配置,降低其他构建服务器的运行频率,这样在它失败的时候,你就有足够的时间来修复主构建平台。或者汇总所有错误报告信息到一个地方,进行统一处理。

自动验收测试

为核心的业务逻辑创建测试。让你的客户单独验证这些测试,要让它们像一般的测试一样可以自动运行。

平衡的艺术

  • 不是所有客户都能给你提供正确的数据。如果他们已经有了正确的数据,就根本不需要新系统了。
  • 你也许会在旧系统(也许是电脑系统,也许是人工系统)中发现以前根本不知道的bug,或者以前不存在的真正问题。
  • 使用客户的业务逻辑,但是不要陷入无边无际的文档写作之中。

度量真实的进度

如果能一直让下一步工作是可见的,会有助于进度度量。最好的做法就是使用待办事项(backlog)。 待办事项就是等待完成的任务列表。当一个任务被完成了,它就会从列表中移除(逻辑上的,而物理上就是把它从列表中划掉,或者标识它是完成的状态)。当添加新任务的时候,先排列它们的优先级,然后加入到待办事项中。你也可以有个人的待办事项、当前迭代的待办事项或者整个项目的待办事项。

通过代办事项,就可以随时知道下一步最重要的任务是什么。同时,你的评估技巧也在不停地改进,你也会越来越清楚完成一项任务要花费的时间。清楚项目的真实进度,是一项强大的技术

度量剩下的工作量。不要用不恰当的度量来欺骗自己或者团队。要评估那些需要完成的待办事项。

平衡的艺术

  • 6分钟作为一个时间单位,它的粒度实在太细了,这不是敏捷的做法。
  • 一周或者一个月的时间单元,它的粒度太粗了,这也不是敏捷的做法。
  • 关注功能,而不是日程表。
  • 如果你在一个项目中花费了很多时间来了解你所花费的时间,而没有足够的时间进行工作,那么你在了解你所花费的时间上花费的时间就太多了。听懂了吗?
  • 一周工作40个小时,不是说你就有40个小时的编码时间。你需要减去会议、电话、电子邮件以及其他相关活动的时间。

倾听用户的声音

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

平衡的艺术

  • 没有愚蠢的用户。
  • 只有愚蠢、自大的开发人员。
  • “它就是这样的。”这不是一个好的答案。
  • 如果代码问题解决不了,也许可以考虑通过修改文档或者培训来弥补。
  • 你的用户有可能会阅读所有的文档,记住其中的所有内容。但也可能不会。

第6章 敏捷编码

首先,第100页中的习惯是:代码要清晰地表达意图。这样的代码清晰易懂,仅凭小聪明写出的程序很难维护。注释可以帮助理解,也可能导致不好的干扰,应该总是用代码沟通。在工程项目中没有免费的午餐,开发人员必须判断哪些东西更加重要,每个决策会造成什么后果,也就是说要动态评估取舍以得到最佳的决策。

代码要清晰地表达意图

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

在改动代码以修复bug或者添加新功能时,应该有条不紊地进行。首先,应该理解代码做了什么,它是如何做的。接下来,搞清楚将要改变哪些部分,然后着手修改并进行测试。作为第1步的理解代码,往往是最难的。如果别人给你的代码很容易理解,接下来的工作就省心多了。要敬重这个黄金法则,你欠他们一份情,因此也要让你自己的代码简单、便于阅读。

作为一个开发者,应该时常提醒自己是否有办法让写出的代码更容易理解。

PIE原则 代码必须明确说出你的意图,而且必须富有表达力。这样可以让代码更易于被别人阅读和理解。代码不让人迷惑,也就减少了发生潜在错误的可能。一言以蔽之,代码应意图清晰,表达明确。

在编写代码时,应该使用语言特性来提升表现力。使用方法名来传达意向,对方法参数的命名要帮助读者理解背后的想法。异常传达的信息是哪些可能会出问题,以及如何进行防御式编程,要正确地使用和命名异常。好的编码规范可以让代码变得易于理解,同时减少不必要的注释和文档。

要编写清晰的而不是讨巧的代码。向代码阅读者明确表明你的意图。可读性差的代码一点都不聪明。

平衡的艺术

  • 现在对你显而易见的事情,对别人可能并非如此,对于一年以后的你来说,也不一定显而易见。不妨将代码视作不知道会在未来何时打开的一个时间胶囊。
  • 不要明日复明日。如果现在不做的话,以后你也不会做的。
  • 有意图的编程并不是意味着创建更多的类或者类型。这不是进行过分抽象的理由。
  • 使用符合当时情形的耦合。例如,通过散列表进行松耦合,这种方式适用于在实际状况中就是松耦合的组件。不要使用散列表存储紧密耦合的组件,因为这样没有明确表示出你的意图。

用代码沟通

如果必须通读一个方法的代码才能了解它做了什么,那么开发人员先要投入大量的时间和精力才能使用它。反过来讲,只需短短几行注释说明方法行为,就可以让生活变得轻松许多。开发人员可以很快了解到它的意图、它的期待结果,以及应该注意之处——这可省了你不少劲儿。

注释可用来为读者指定一条正确的代码访问路线图。为代码中的每个类或模块添加一个短小的描述,说明其目的以及是否有任何特别需求。对于类中的每个方法,可能要说明下列信息。

  • 目的:为什么需要这个方法?
  • 需求(前置条件):方法需要什么样的输入,对象必须处于何种状态,才能让这个方法工作?
  • 承诺(后置条件):方法成功执行后,对象现在处于什么状态,有哪些返回值?
  • 异常:可能会发生什么样的问题?会抛出什么样的异常?

用注释沟通。使用细心选择的、有意义的命名。用注释描述代码意图和约束。注释不能替代优秀的代码。

平衡的艺术

  • Pascal定理的创始人Blaise Pascal曾说,他总是没有时间写短信,所以只好写长信。请花时间去写简明扼要的注释吧。
  • 在代码可以传递意图的地方不要使用注释。
  • 解释代码做了什么的注释用处不那么大。相反,注释要说明为什么会这样写代码。
  • 当重写方法时,保留描述原有方法意图和约束的注释。

动态评估取舍

总而言之,要想让应用成功,降低开发成本与缩短上市时间,二者的影响同样重要。由于计算机硬件价格日益便宜,处理速度日益加快,所以可在硬件上多投入以换取性能的提升,并将节省下来的时间放在应用的其他方面。

动态评估权衡。考虑性能、便利性、生产力、成本和上市时间。如果性能表现足够了,就将注意力放在其他因素上。不要为了感觉上的性能提升或者设计的优雅,而将设计复杂化。

平衡的艺术

  • 如果现在投入额外的资源和精力,是为了将来可能得到的好处,要确认投入一定要得到回报(大部分情况下,是不会有回报的)。
  • 真正的高性能系统,从一开始设计时就在向这个方向努力。
  • 过早的优化是万恶之源。
  • 过去用过的解决方案对当前的问题可能适用,也可能不适用。不要事先预设结论,先看看现在是什么状况。

增量式编程

在很短的编辑/构建/测试循环中编写代码。这要比花费长时间仅仅做编写代码的工作好得多。可以创建更加清晰、简单、易于维护的代码。

平衡的艺术

  • 如果构建和测试循环花费的时间过长,你就不会希望经常运行它们了。要保证测试可以快速运行。
  • 在编译和测试运行中,停下来想一想,并暂时远离代码细节,这是保证不会偏离正确方向的好办法。
  • 要休息的话,就要好好休息。休息时请远离键盘。
  • 要像重构你的代码那样,重构你的测试,而且要经常重构测试。

保持简单

简单不是简陋 Simple is not simplistic

“简单性”这个词汇被人们大大误解

评价设计质量的最佳方式之一,就是听从直觉。直觉不是魔术,它是经验和技能的厚积薄发之产物。在查看一个设计时,听从头脑中的声音。如果觉得什么地方不对,那就好好想想,是哪里出了问题。一个好的设计会让人觉得很舒服。

开发可以工作的、最简单的解决方案。除非有不可辩驳的原因,否则不要使用模式、原则和高难度技术之类的东西。

平衡的艺术

  • 代码几乎总是可以得到进一步精炼,但是到了某个点之后,再做改进就不会带来任何实质性的好处了。这时开发人员就该停下来,去做其他方面的工作了。
  • 要将目标牢记在心:简单、可读性高的代码。强行让代码变得优雅与过早优化类似,同样会产生恶劣的影响。
  • 当然,简单的解决方案必须要满足功能需求。为了简单而在功能上妥协,这就是过分简化了。
  • 太过简洁不等于简单,那样无法达到沟通的目的。
  • 一个人认为简单的东西,可能对另一个人就意味着复杂。

编写内聚的代码

低内聚性的代码会造成很严重的后果。假设有这样一个类,实现了五种完全不相干的功能。如果这5个功能的需求或细节发生了变化,这个类也必须跟着改变。如果一个类(或者一个组件)变化得过于频繁,这样的改变会对整个系统形成“涟漪效应”,并导致更多的维护和成本的发生。考虑另一个只实现了一种功能的类,这个类变化的频度就没有那么高。类似地,一个更具内聚性的组件不会有太多导致其变化的原因,也因此而更加稳定。根据单一职责原则(查看《敏捷软件开发:原则、模式与实践》[Mar02]),一个模块应该只有一个发生变化的原因。

内聚性会影响一个组件的可重用性。组件粒度是在设计时要考虑的一个重要因素。根据重用发布等价原则([Mar02]):重用的粒度与发布的粒度相同。这就是说,程序库用户所需要的,是完整的程序库,而不只是其中的一部分。如果不能遵循这个原则,组件用户就会被强迫只能使用所发布组件的一部分。很不幸的是,他们仍然会被不关心的那一部分的更新所影响。软件包越大,可重用性就越差。

让类的功能尽量集中,让组件尽量小。要避免创建很大的类或组件,也不要创建无所不包的大杂烩类。

平衡的艺术

  • 有可能会把一些东西拆分成很多微小的部分,而使其失去了实用价值。当你需要一只袜子的时候,一盒棉线不能带给你任何帮助。
  • 具有良好内聚性的代码,可能会根据需求的变化,而成比例地进行变更。考虑一下,实现一个简单的功能变化需要变更多少代码。

告知,不要询问

告知,不要询问。不要抢别的对象或是组件的工作。告诉它做什么,然后盯着你自己的职责就好了。

平衡的艺术

  • 一个对象,如果只是用作大量数据容器,这样的做法很可疑。有些情况下会需要这样的东西,但并不像想象的那么频繁。
  • 一个“命令”返回数据以方便使用是没有问题的(如果需要的话,创建单独读取数据的方法也是可以的)。
  • 绝对不能允许一个看起来无辜的“查询”去修改对象的状态。

根据契约进行替换

通过替换代码来扩展系统。通过替换遵循接口契约的类,来添加并改进功能特性。要多使用委托而不是继承。

平衡的艺术

  • 相对继承来说,委托更加灵活,适应力也更强。
  • 继承不是魔鬼,只是长久以来被大家误解了。
  • 如果你不确定一个接口做出了什么样的承诺,或是有什么样的需求,那就很难提供一个对其有意义的实现了。

记录问题解决日志

要想得到更好的效果,不妨维护一个保存曾遇到的问题以及对应解决方案的日志。这样,当问题发生时,就不必说:“嘿,我曾碰到过这个问题,但是不记得是怎么解决的了。”可以快速搜索以前用过的方法。工程师们已经使用这种方式很多年了,他们称之为每日日志(daylog)。

可以选择符合需求的任何格式。下面这些条目可能会用得上。

  • 问题发生日期。
  • 问题简述。
  • 解决方案详细描述。
  • 引用文章或网址,以提供更多细节或相关信息。
  • 任何代码片段、设置或对话框的截屏,只要它们是解决方案的一部分,或者可以帮助更深入地理解相关细节。

维护一个问题及其解决方案的日志。保留解决方案是修复问题过程的一部分,以后发生相同或类似问题时,就可以很快找到并使用了。

平衡的艺术

  • 记录问题的时间不能超过在解决问题上花费的时间。要保持轻量级和简单,不必达到对外发布式的质量。
  • 找到以前的解决方法非常关键。使用足够的关键字,可以帮助你在需要的时候发现需要的条目。
  • 如果通过搜索Web,发现没人曾经遇到同样的问题,也许搜索的方式有问题。
  • 要记录发生问题时应用程序、应用框架或平台的特定版本。同样的问题在不同的平台或版本上可能表现得不同。
  • 要记录团队做出一个重要决策的原因。否则,在6~9个月之后,想再重新回顾决策过程的时候,这些细节就很难再记得了,很容易发生互相指责的情形。

警告就是错误

即使代码编译时产生了警告,我们还是可以运行程序。那么忽略警告信息继续开发代码,会导致什么状况呢?这样做等于是坐在了一个嘀嗒作响的定时炸弹上,而且它很有可能在最糟糕的时刻爆炸。

将警告视为错误。签入带有警告的代码,就跟签入有错误或者没有通过测试的代码一样,都是极差的做法。签入构建工具中的代码不应该产生任何警告信息。

平衡的艺术

  • 虽然这里探讨的主要是编译语言,解释型语言通常也有标志,允许运行时警告。使用相关标志,然后捕获输出,以识别并最终消除警告。
  • 由于编译器的bug或是第三方工具或代码的原因,有些警告无法消除。如果确实没有应对之策的话,就不要再浪费更多时间了。但是类似的状况很少发生。
  • 应该经常指示编译器:要特别注意别将无法避免的警告作为错误进行提示,这样就不用费力去查看所有的提示,以找到真正的错误和警告。
  • 弃用的方法被弃用是有原因的。不要再使用它们了。至少,安排一个迭代来将它们(以及它们引起的警告信息)安全地移除掉。
  • 如果将过去开发完成的方法标记为弃用方法,要记录当前用户应该采取何种变通之策,以及被弃用的方法将会在何时一起移除。

对问题各个击破

大型系统非常复杂——在执行过程中会有很多因素起作用。从整个系统的角度来解决问题,就很难区分开,哪些细节对要定位的特定问题产生影响,而哪些细节没有。答案很清晰:不要试图马上了解系统的所有细节。要想认真调试,就必须将有问题的组件或模块与其他代码库分离开来。如果有单元测试,这个目的就已经达到了。否则,你就得开动脑筋了。

识别复杂问题的第一步,是将它们分离出来。既然不可能在半空中试图修复飞机引擎,为什么还要试图在整个应用中,诊断其中某个组成部分的复杂问题呢?当引擎被从飞机中取出来,而且放在工作台上之后,就更容易修复了。同理,如果可以隔离出发生问题的模块,也更容易修复发生问题的代码。

可是,很多应用的代码在编写时没有注意到这一点,使得分离变得特别困难。应用的各个构成部分之间会彼此纠结:想把这个部分单独拿出来,其他的会紧随而至。在这些状况下,最好花一些时间把关注的代码提取出来,而且创建一个可让其工作的测试环境。

对问题各个击破,这样做有很多好处:通过将问题与应用其他部分隔离开,可以将关注点直接放在与问题相关的议题上;可以通过多种改变,来接近问题发生的核心——你不可能针对正在运行的系统来这样做。可以更快地发现问题的根源所在,因为只与所需最小数量的相关代码发生关系。隔离问题不应该只在交付软件之后才着手。在构建系统原型、调试和测试时,各个击破的战略都可以起到帮助作用。

对问题各个击破。在解决问题时,要将问题域与其周边隔离开,特别是在大型应用中。

平衡的艺术

  • 如果将代码从其运行环境中分离后,问题消失不见了,这有助于隔离问题。
  • 另一方面,如果将代码从其运行环境中分离后,问题还在,这也有助于隔离问题。
  • 以二分查找的方式来定位问题是很有用的。也就是说,将问题空间分为两半,看看哪一半包含问题。再将包含问题的一半进行二分,并不断重复这个过程。
  • 在向问题发起攻击之前,先查找你的问题解决日志。

报告所有的异常

处理或是向上传播所有的异常。不要将它们压制不管,就算是临时这样做也不行。在写代码时要估计到会发生的问题。

平衡的艺术

  • 决定由谁来负责处理异常是设计工作的一部分。
  • 不是所有的问题都应该抛出异常。
  • 报告的异常应该在代码的上下文中有实际意义。在前述的例子中,抛出一个NullPointerException看起来也许不错,不过这就像抛出一个null对象一样,起不到任何帮助作用。
  • 如果代码中会记录运行时调试日志,当捕获或是抛出异常时,都要记录日志信息;这样做对以后的跟踪工作很有帮助。
  • 检查异常处理起来很麻烦。没人愿意调用抛出31种不同检查异常的方法。这是设计上的问题:要把它解决掉,而不是随便打个补丁就算了。
  • 要传播不能处理的异常。

提供有用的错误信息

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

错误报告对于开发人员的生产率,以及最终的支持活动消耗成本,都有很大的影响。在开发过程中,如果定位和修复问题让人倍受挫折,就考虑使用更加积极主动的错误报告方式吧。调试信息非常宝贵,而且不易获得。不要轻易将其丢弃。

展示有用的错误信息。提供更易于查找错误细节的方式。发生问题时,要展示出尽量多的支持细节,不过别让用户陷入其中。

平衡的艺术

  • 像“无法找到文件”这样的错误信息,就其本身而言无助于问题的解决。“无法打开/andy/project/main.yaml以供读取”这样的信息更有效。
  • 没有必要等待抛出异常来发现问题。在代码关键点使用断言以保证一切正常。当断言失败时,要提供与异常报告同样详细的信息。
  • 在提供更多信息的同时,不要泄露安全信息、个人隐私、商业机密,或其他敏感信息(对于基于Web的应用,这一点尤其重要)。
  • 提供给用户的信息可以包含一个主键,以便于在日志文件或是审核记录中定位相关内容。

第8章 敏捷协作

只要是具备一定规模的项目,就必然需要一个团队。靠单打独斗在车库里面开发出一个完整产品的日子早已不再。然而,在团队中工作与单兵作战,二者是完全不同的。一个人会突然发现,自己的行为会对团队以及整个项目的生产效率和进度产生影响。

项目的成功与否,依赖于团队中的成员如何一起有效地工作,如何互动,如何管理他们的活动。全体成员的行动必须要与项目相关,反过来每个人的行为又会影响项目的环境。

高效的协作是敏捷开发的基石,下面这些习惯将会帮助所有的团队成员全心投入到项目中,并且大家一起向着正确的方向努力。

首先要做的是定期安排会面时间,见第148页。面对面的会议仍然是最有效的沟通方式,所以我们将以此作为本章的开篇。接下来,希望每个人都能投入到开发过程中来。也就是说架构师必须写代码(我们会在第152页看到为什么要这样做)。既然整个团队都是项目工作的一部分,我们希望实行代码集体所有制(见第155页),以保证任何团队成员的缺席不会对项目造成影响。这就是协作的效果,还记得吗?

但是高效的协作并不只是写出代码就好了。随着时间的流逝,团队中每个人都要强化和提高他们的技能,并且推进各自的职业发展。即使一个人刚刚加入团队,他也可以成为指导者,将会在第157页谈到应该怎么做。团队中一个人的知识,经常可以解决另外一名团队成员的问题。只要允许大家自己想办法,就可以帮助团队不断成长,就像在第160页上看到的那样。

最后,由于大家都是在团队中一起工作,每个人就要修改自己的个人编码习惯,来适应团队的其他成员。对于初学者来说,准备好后再共享代码才是有礼貌的做法(见第162页),这样才不会用未完成的工作来给团队成员造成麻烦。当准备好之后,我们应该与其他团队成员一起做代码复查(见第165页)。随着项目的推进,我们会不断地完成旧任务,并且领取新任务。应该及时通报进展与问题,让大家了解彼此的进度、遇到的问题,以及在开发过程中发现的有意思的东西。我们将在第168页讨论该习惯并结束本章。

定期安排会面时间

立会(站着开的会议,Scrum最早引入并被极限编程所强调的一个实践)是将团队召集在一起,并让每个人了解当下进展状况的好办法。顾名思义,参与者们不允许在立会中就坐,这可以保证会议快速进行。一个人坐下来之后,会由于感到舒适而让会议持续更长的时间。

要保证会议议题不会发散,每个人都应该只回答下述三个问题。

  • 昨天有什么收获?
  • 今天计划要做哪些工作?
  • 面临着哪些障碍?

只能给予每个参与者很少的时间发言(大约两分钟)。也许要用计时器来帮助某些收不住话头的人。如果要详细讨论某些问题,可以在立会结束之后,再召集相关人员(在会议中说“我需要跟Fred和Wilma讨论一下数据库”是没有问题的,但是不要深入讨论细节)。

每日立会有诸多好处。

  • 让大家尽快投入到一天的工作中来。
  • 如果某个开发人员在某一点上有问题,他可以趁此机会将问题公开,并积极寻求帮助。
  • 帮助团队带头人或管理层了解哪些领域需要更多的帮助,并重新分配人手。
  • 让团队成员知道项目其他部分的进展情况。
  • 帮助团队识别是否在某些东西上有重复劳动而耗费了精力,或者是不是某个问题有人已有现成的解决方案。
  • 通过促进代码和思路的共享,来提升开发速度。
  • 鼓励向前的动力:看到别人报告的进度都在前进,会对彼此形成激励。

使用立会。立会可以让团队达成共识。保证会议短小精焊不跑题。

平衡的艺术

  • 会议会占用开发时间,所以要尽量保证投入的时间有较大的产出。立会的时间最长不能超出30分钟,10~15分钟比较理想。
  • 如果要使用需提前预定的会议室,就把预定的时间设定为一个小时吧。这样就有机会在15分钟的立会结束后,马上召开更小规模的会议。
  • 虽然大多数团队需要每天都碰头,但对于小型团队来说,这样做可能有点过头了。不妨两天举行一次,或者一周两次,这对小团队来说足够了。
  • 要注意报告的细节。在会议中要给出具体的进度,但是不要陷入细节之中。例如,“我在开发登录页面”就不够详细。“登录页面目前接受guest/guest作为登录用户名和密码,我明天会连接数据库来做登录验证”,这样的详细程度才行。
  • 迅速地开始可以保证会议短小。不要浪费时间等着会议开始。如果觉得立会是在浪费时间,那可能是大家还没有形成真正的团队意识。这并不是坏事,有利于针对问题进行改进。

架构师必须写代码

软件开发业界中有许多挂着架构师称号的人。作为作者的我们不喜欢这个称号,为什么呢?架构师应该负责设计和指导,但是许多名片上印着“架构师”的人配不上这个称号。作为架构师,不应该只是画一些看起来很漂亮的设计图,说一些像“黑话”一样的词汇,使用一大堆设计模式——这样的设计通常不会有效的。

作为设计人员,如果不能理解系统的具体细节,就不可能做出有效的设计。只通过一些高度概括的、粗略的设计图无法很好地理解系统。

正像Knuth说的,好的设计者必须能够卷起袖子,加入开发队伍,毫不犹豫地参与实际编程。真正的架构师,如果不允许参与编码的话,他们会提出强烈的抗议。

Martin Fowler在题为“Who Needs an Architect?”的文章中提到:一个真正的架构师“……应该指导开发团队,提升他们的水平,以解决更为复杂的问题”。他接着说:“我认为架构师最重要的任务是:通过找到移除软件设计不可逆性的方式,从而去除所谓架构的概念。”增强可逆性是注重实效的软件实现方式的关键构成部分。

要鼓励程序员参与设计。主力程序员应该试着担任架构师的角色,而且可以从事多种不同的角色。他会负责解决设计上的问题,同时也不会放弃编码的工作。如果开发人员不愿意承担设计的责任,要给他们配备一个有良好设计能力的人。程序员在拒绝设计的同时,也就放弃了思考。

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

平衡的艺术

  • 如果有一位首席架构师,他可能没有足够的时间来参与编码工作。还是要让他参与,但是别让他开发在项目关键路径上的、工作量最大的代码。
  • 不要允许任何人单独进行设计,特别是你自己。

实行代码集体所有制

当多人同时开发时,代码会被频繁地检查、重构以及维护。如果需要修复bug,任何一名开发人员都可以完成这项工作。同时有两个或两个以上的人,可以处理应用中不同部分的代码,可以让项目的日程安排也变得更为容易。

要强调代码的集体所有制。让开发人员轮换完成系统不同领域中不同模块的不同任务。

平衡的艺术

  • 不要无意间丧失了团队的专家技能。如果某个开发人员在某个领域中极其精通,不妨让他作为这方面的驻留专家,而且系统的其他部分代码也对他开放,这样对团队和项目都很有帮助。
  • 在大型项目中,如果每个人都可以随意改变任何代码,一定会把项目弄得一团糟。代码集体所有制并不意味着可以随心所欲、到处破坏。
  • 开发人员不必了解项目每一部分的每个细节,但是也不能因为要处理某个模块的代码而感到惊恐。
  • 有些场合是不能采用代码集体所有制的。也许代码需要某些特定的知识、对特定问题域的了解,比如一个高难度的实时控制系统。这些时候,人多了反而容易误事。
  • 任何人都可能遭遇到诸如车祸等突发的灾难事故,或者有可能被竞争对手雇佣。如果不向整个团队分享知识,反而增加了丧失知识的风险。

成为指导者

与团队其他人一起共事是很好的学习机会。知识有一些很独特的属性;假设你给别人钱的话,最后你的钱会变少,而他们的财富会增多。但如果是去教育别人,那双方都可以得到更多的知识。

通过详细解释自己知道的东西,可以使自己的理解更深入。当别人提出问题时,也可以发现不同的角度。也许可以发现一些新技巧——听到一个声音这样告诉自己:“我以前还没有这样思考过这个问题。”

与别人共事,激励他们变得更出色,同时可以提升团队的整体实力。遇到无法回答的问题时,说明这个领域的知识还不够完善,需要在这方面进一步增强。好的指导者在为他人提供建议时会做笔记。如果遇到需要花时间进一步观察和思考的问题,不妨先草草记录下来。此后将这些笔记加入到每日日志中。

成为指导者。分享自己的知识很有趣——付出的同时便有收获。还可以激励别人获得更好的成果,而且提升了整个团队的实力。

平衡的艺术

  • 如果一直在就同一个主题向不同的人反复阐述,不妨记录笔记,此后就此主题写一篇文章,甚至是一本书。
  • 成为指导者是向团队进行投资的一种极佳的方式。
  • 结对编程是一种进行高效指导的、很自然的环境。
  • 如果总是被一些懒于自己寻找答案的人打扰。
  • 为团队成员在寻求帮助之前陷入某个问题的时间设定一个时限,一个小时应该是不错的选择。

允许大家自己想办法

给别人解决问题的机会。指给他们正确的方向,而不是直接提供解决方案。每个人都能从中学到不少东西。

平衡的艺术

  • 用问题来回答问题,可以引导提问的人走上正确的道路。
  • 如果有人真的陷入胶着状态,就不要折磨他们了。告诉他们答案,再解释为什么是这样。

准备好后再共享代码

完成一项任务后,应该马上提交代码,不应该让代码在开发机器上多停留一分钟。如果代码不能被别人集成使用,那又有什么用处呢?应该赶紧发布出去,并开始收集反馈。

保证在提交代码之前,所有的单元测试都是可以通过的。使用持续集成是保证源代码控制系统中代码没有问题的一种良好方式。

准备好后再共享代码。绝不要提交尚未完成的代码。故意签入编译未通过或是没有通过单元测试的代码,对项目来说,应被视作玩忽职守的犯罪行为。

平衡的艺术

  • 有些源代码控制系统会区分“提交”和“可公开访问”两种代码权限。此时,可以进行临时的提交操作(比如在工作地点和家之间来回奔波时),不会因为完全提交未完成的代码,而让团队的其他成员感到郁闷。
  • 有些人希望代码在提交之前可以进行复查操作。只要不会过久拖延提交代码的时间就没有问题。如果流程的某个部分产生了拖延,那就修正流程吧。
  • 仍然应该频繁提交代码。不能用“代码尚未完成”作为避免提交代码的借口。

做代码复查

在代码复查中要看什么呢?你可能会制订出要检查的一些特定问题列表(所有的异常处理程序不允许空,所有的数据库调用都要在包的事务中进行,等等),不过这里是一个可供启动的最基本的检查列表。

  • 代码能否被读懂和理解?
  • 是否有任何明显的错误?
  • 代码是否会对应用的其他部分产生不良影响?
  • 是否存在重复的代码(在复查的这部分代码中,或是在系统的其他部分代码)?
  • 是否存在可以改进或重构的部分?

复查所有的代码。对于提升代码质量和降低错误率来说,代码复查是无价之宝。如果以正确的方式进行,复查可以产生非常实用而高效的成果。要让不同的开发人员在每个任务完成后复查代码。

平衡的艺术

  • 不进行思考、类似于橡皮图章一样的代码复查没有任何价值。
  • 代码复查需要积极评估代码的设计和清晰程度,而不只是考量变量名和代码格式是否符合组织的标准。
  • 同样的功能,不同开发人员的代码实现可能不同。差异并不意味着不好。除非你可以让某段代码明确变得更好,否则不要随意批评别人的代码。
  • 如果不及时跟进讨论中给出的建议,代码复查是没有实际价值的。可以安排跟进会议,或者使用代码标记系统,来标识需要完成的工作,跟踪已经处理完的部分。
  • 要确保代码复查参与人员得到每次复查活动的反馈。作为结果,要让每个人知道复查完成后所采取的行动。

及时通报进展与问题

及时通报进展与问题。发布进展状况、新的想法和目前正在关注的主题。不要等着别人来问项目状态如何。

平衡的艺术

  • 每日立会(见第148页习惯38)可以让每个人都能明确了解最新的进展和形势。
  • 在展示进度状况时,要照顾到受众关注的细节程度。举例来说,CEO和企业主是不会关心抽象基类设计的具体细节的。
  • 别花费太多时间在进展与问题通报上面,还是应该保证开发任务的顺利完成。
  • 经常抬头看看四周,而不是只埋头于自己的工作。