《程序员修炼之道:通向务实的最高境界(第2版)》

(美)David Thomas(大卫·托马斯),Andrew Hunt(安德鲁·亨特) 81个笔记


第一版前言

◆ 提示1 关注你的技艺我们觉得,如果你不关心怎么把软件开发好,那么软件开发领域就再也没什么好谈的事情了

1 人生是你的

◆ 我活着不是为了满足你的期望,正如你也不是因为我的期望而活着。——李小龙

2 我的源码被猫吃了

◆ 在所有弱点中,最大的弱点就是害怕出现弱点。—— J.B.Bossuet,Politics from Holy Writ,1709

◆ 责任意味着你对某事积极认同。你保证事情能搞定,并为之做出承诺,但你不必直接掌控事情的每个方面。除了个人尽力做好,你必须分析超出你控制范围的风险情况。如果责任的伦理内涵过于含糊,或是面对无法实现的情况,抑或风险过大,你都有权不承担责任。你必须根据自己的价值观和判断做出决定。当你决定对一个结果承担责任时,要明白这意味着你将承接相关的义务。当你犯了错误(就像我们所有人一样),或是做出了错误的判断时,诚实地承认它,并尝试给出选择。不要把问题归咎于别人或其他什么事情上,也不要寻找借口。不要把所有问题都归咎于供应商、编程语言、管理或是同事。这些因素都可能是问题的一部分。它们的确会对解决方案造成影响,但不是给你的借口。

◆ 当你意识到自己在说“我不知道”时,一定要接着说“——但是我会去搞清楚”。用这样的方式来表达你不知道是非常好的,因为接着你就可以像一个专家一样承担起责任。

3 软件的熵

◆ 虽然软件开发不受绝大多数物理法则的约束,但我们无法躲避来自熵的增加的重击。熵是一个物理学术语,它定义了一个系统的“无序”总量。不幸的是,热力学法则决定了宇宙中的熵会趋向最大化。当软件中的无序化增加时,程序员会说“软件在腐烂”。有些人可能会用更乐观的术语来称呼它,即“技术债”,潜台词是说他们总有一天会偿还的——恐怕不会还了。不过不管叫什么名字,债务和腐烂都可能失控地蔓延开。

◆ 一扇破窗——一段设计糟糕的代码,一个让团队在整个项目周期内都必须要遵守的糟糕管理决定——就是一切衰退的开始。如果你发现自己正处在有几扇破窗的项目中,就非常容易陷入这样的想法——“反正代码所有其他部分都是一坨屎,我只是随大流而已。”项目运作在这个时间点前是不是一直良好并不重要。

4 石头做的汤和煮熟的青蛙

◆ 有时我们称之为“筹备期的劳累”。这个时候,就该拿出石头了——找出你合理的请求,然后不断完善。一旦有成果产出,展示给人们看,让他们大吃一惊。现在可以用上“当然了,它还可以更好,只要我们再加点……”这句话,而且要假装你并不在意。这时先坐下来,等他们开始问你要不要加些你原本想要的功能。人们都觉得,加入一个推进中的成功项目更容易一些。因为只要一窥未来,大家就能团结在一起。

5 够好即可的软件

◆ 为了追求更好,我们毁损了原已够好的。——莎士比亚《李尔王1.4》

◆ 不要让过度的修饰和精炼侵蚀掉一个完好的程序。继续前行,让代码在它该有的位置驻留一段时间。它或许并不完美,不要紧的——它就算永不完美也没关系。

6 知识组合

◆ 低买高卖在一项新兴技术变得流行之前就开始学习,可能和发现一只被低估的股票一样困难,但是所得到的收获会和此类股票的收益一样好。在Java刚发明的时候就去学习,可能有很大风险,不过当Java流行后,那些早期用户都获得了相当丰厚的回报。

◆ 每年学习一门新语言不同的语言以不同的方式解决相同的问题。多学习几种不同的解决方法,能帮助自己拓宽思维,避免陷入陈规。此外,要感谢丰富的免费软件,让我们学习多种语言非常容易。每月读一本技术书虽然网络上有大量的短文和偶尔可靠的答案,但深入理解还是需要去读长篇的书。浏览书店页面后[10]挑选和你当前项目主题相关的技术图书。一旦你养成习惯,就一个月读一本。在你掌握了当前正在使用的所有技术后,扩展你的领域,学习一些和你的项目不相关的东西。还要读非技术书记住,计算机是由人来使用的,你做的事情是为了满足人的需要,这非常重要。和你一起工作的是人,雇佣你的也是人,黑你的还是人。不要忘记方程式中人的那一面,它需要完全不同的技能集(我们称这些为软技能,听起来很容易,但实际上它们很硬核,难以掌握)。上课在本地大学或是网上找一些有趣的课程,或许也能在下一场商业会展或是技术会议上找到。加入本地的用户组和交流群不要只是去当听众,要主动参与。独来独往对你的职业生涯是致命的;了解一下公司之外的人们都在做什么。尝试不同的环境如果你只在Windows下工作,那么就花点时间在Linux上。如果你只使用简单的编辑器和Makefile,那就试试最新的炫酷复杂的IDE,反之亦然。与时俱进关心一下和你当前项目不同的技术,阅读相关的新闻和技术帖。这是一种很好的方式,可以了解用到那些不同技术的人的经验及他们所用的特殊术语,等等。

◆ 持续投资非常重要。一旦你进入了对某个新语言或新技术的舒适期,向前走,再学一个。你是否在项目中使用过这些技术并不重要,甚至要不要把它们放在你的简历中也不重要。学习的过程将会扩展你的思维,为你打开全新可能性的大门,让你领悟新的做事方式。想法的交叉传授是很重要的;试着把你领悟到的东西应用到你当前的项目中。即使项目没有用到某项技术,你也可以借鉴一些想法。

7 交流!

◆ 如果想让别人听你说话,有一个技巧必须掌握:听他们说。即使你掌握了全部信息,甚至是在一个正式的会议上站在20个西装革履的人面前——如果你不听他们的,他们也不会听你的。通过提问鼓励人们交谈,试着让他们总结你的发言。把会议变成一场对话,你将更有效地表达你的观点。说不定你还可以学到一些东西。

◆ 当代码已经展示了事情怎样完成时,注释是多余的——因为这违反了DRY原则。

8 优秀设计的精髓

◆ 能适应使用者的就是好的设计。对代码而言,就是要顺应变化。因此要信奉ETC原则(Easier To Change,更容易变更)——就该如此。据我们所知,无论是什么设计原则,都是ETC的一个特例。为什么解耦很好?因为通过隔离关注焦点,可让每一部分都容易变更——此谓ETC。为什么单一职责原则很有用?因为一个需求变化仅体现为某个单一模块上的一个对应变化——此谓ETC。为什么命名很重要?因为好的命名可以使代码更容易阅读,而你需要通过阅读来变更代码——此谓ETC!

9 DRY——邪恶的重复

◆ 在一个系统中,每一处知识都必须单一、明确、权威地表达。

◆ 无论什么时候,只要模块暴露出数据结构,就意味着,所有使用这个数据结构的代码和模块的实现产生了耦合。但凡有可能,都应采用一组访问器函数来读写对象的属性。

10 正交性

◆ 但凡编写正交的系统,就能获得两个主要的收益:提高生产力及降低风险。

◆ 邮政编码、社会保险号或是身份证号,电子邮件地址以及域名,都是外部标识符,你无法完全控制,它们都可能因为某些原因而改变。不要依赖那些你无法控制的东西。

◆ 正交(迄今为止)还很难用言语表达。使用DRY时,你追求最小化系统中的重复。反之,在使用正交时,则要去减少系统组件之间的相互依赖。“正交”的字面意思可能不太好懂,但是采用正交性原则,并与DRY原则紧密结合,的确可以让系统变得更灵活、更容易理解,并且更容易调试、测试和维护。

11 可逆性

◆ 如果某个想法是你唯一的想法,那就没有比它更危险的东西了。——埃米尔-奥古斯特·沙尔捷(阿兰)Propos sur la religion,1938

12 曳光弹

◆ 原型生成的是一次性代码;曳光代码虽然简单但是完整,它是最终系统框架的组成部分。可以将原型制作看作是在发射一颗曳光弹之前进行的侦察和情报收集工作。

13 原型与便签

◆ 你会选择用原型来研究什么类型的东西呢?答案是,任何有风险的东西,任何之前没有尝试过或对最终系统来说很关键的东西,任何未经证实、实验性或可疑的东西,以及任何让你不舒服的东西。你可以为下列事物做原型:· 架构· 已存在的系统中的新功能· 数据结构或外部数据的内容· 第三方工具或组件· 性能问题· 用户界面设计原型设计是为了学习经验。它的价值不在于产生的代码,而在于吸取的教训。这正是原型的意义所在。

14 领域语言

◆ 语言之界限,即是一个人世界之界限。——路德维希·维特根斯坦

17 Shell游戏

◆ 如果使用图形界面去完成所有工作,就会错失环境的全部能力。你将无法把常见的任务自动化,或是无法充分利用工具所能提供的强大功能。并且,你也无法通过组合你的工具来创建定制的宏工具。图形工具的好处在于WYSIWYG ——所见即所得;弱势之处是WYSIAYG——所见即全部。GUI环境通常局限于其设计者所期望的功能。非要超越设计人员提供的模型,往往会遭遇挫折——而且,需要超越模型的时候的确要多得多。

20 调试

◆ 海军少将格蕾丝·赫柏博士,COBOL 的发明者,被认为观察到第一个计算机 Bug——字面意义的Bug,一只飞进早期计算机系统中的蛾子。当被要求解释那台机器为什么不能正常工作时,技术人员报告“在系统中发现了一只 Bug”,并且很尽职地把它订在了记录本中,包括翅膀等所有部分。

22 工程日记

◆ 日记本有三大好处。· 它比记忆更可靠。人们可能会问:“你上周打电话问的那个有电力供应问题的公司叫什么名字?”你只需翻回一页左右,说出名字和号码。· 它为你提供了一个地方,用来保存与当前任务无关的想法。这样你就可以继续专注于正在做的事情,并知道这个伟大的想法不会被遗忘。· 它就像一种橡皮鸭(在第96页讨论过)。当你停下来,把东西写上去的时候,大脑可能会换档,几乎就像在和某人说话一样——这是一个反思的好机会。你可能在开始做笔记的时候,突然意识到刚刚做的事情,也就是笔记的主题,是完全错误的。还有一个额外的好处。你能时不时地回想起多年以前你在做什么,会想到那些人、那些项目,以及那些糟糕的衣服和发型。所以,试着拥有一本工程日记。

23 契约式设计

◆ 伯特兰·迈耶(《面向对象软件构造》[Mey97])在 Eiffel 语言中发明了契约式设计的概念。[1]这是一种简单但功能强大的技术,侧重于文档化(并约定)软件模块的权利和责任,以确保程序的正确性。什么是正确的程序?不多也不少,正好完成它主张要做的事情的程序。文档化及对主张进行检验是契约式设计(缩写为 DBC)的核心。

◆ 在编写代码之前,简单地列出输入域的范围、边界条件是什么、例程承诺要交付什么——或者更重要的是,没有承诺要交付什么——这些对编写更好的软件来说,是一个巨大的飞跃。不说清楚这些内容,就回到了巧合式编程(参见第204页的讨论),这是许多项目开始、结束、最终失败的地方。

◆ 通过在sqrt例程的前置条件里表达清楚平方根函数的处理范围,你可以将保证正确性的责任转移到调用者身上——这正是它的职责所在。然后,可以基于“输入将在范围内”这一知识,来设计sqrt例程的安全性。在问题发生的地方尽早崩溃,能让找到问题和诊断问题更加容易。

24 死掉的程序不会说谎

◆ Erlang 和 Elixir语言信奉这种哲学。乔·阿姆斯特朗,Erlang 的发明者,《Erlang 程序设计》[Arm07]的作者,有一句反复被引用的话:“防御式编程是在浪费时间,让它崩溃!”在这些环境中,程序被设计成允许出故障,但是故障会由监管程序掌控。监管程序负责运行代码,并知道在代码出故障时该做什么,这可能包括在代码出错后做清理工作、重新启动等。当监管程序本身出错时会发生什么?它自己还有一个监管程序来管理这些事件,从而形成一种由监管程序树构成的设计。该技术非常有效,有助于解释这些语言在高可用性、容错性系统中的用法。

◆ 一旦代码发现本来不可能发生的事情已发生,程序就不再可靠。从这一时刻开始,它所做的任何事情都是可疑的,所以要尽快终止它。一个死掉的程序,通常比一个瘫痪的程序,造成的损害要小得多。

27 不要冲出前灯范围

◆ 似乎已听到你在叫嚣——我们不是应该为将来的维护做设计吗?没错,不过要适可而止:别超过你能看见的范围。越是必须预测未来会怎样,就越有可能犯错。与其浪费精力为不确定的未来做设计,还不如将代码设计成可替换的。当你想要丢弃你的代码,或将其换成更合适的时,要让这一切无比容易。使代码可替换,还有助于提高内聚性、解耦和DRY,从而实现更好的总体设计。

29 在现实世界中抛球杂耍

◆ 把状态保存在外部存储器中,并使用这些状态来驱动状态机,这是处理此类工作流需求的好方法。状态机是一个开始状态机并没有被开发人员充分利用,我们鼓励你找机会多用用。

◆ 发布/订阅(pubsub)推广了观察者模式,同时解决了耦合和性能问题。在 pubsub模式中,我们有发布者和订阅者。它们是通过信道连接在一起的。信道在单独的代码块中实现:有时是库,有时是进程,有时是分布式基础设施。所有这些实现细节对代码来说都是隐藏的。每个信道都有一个名字。订阅者注册感兴趣的一个或多个具名信道,发布者向信道写入事件。与观察者模式不同,发布者和订阅者之间的通信是在代码之外处理的,并且可能是异步的。

30 变换式编程

◆ 所有程序其实都是对数据的一种变换——将输入转换成输出。然而,当我们在构思设计时,很少考虑创建变换过程。相反,我们操心的是类和模块、数据结构和算法、语言和框架。

◆ 如果我们只能建立线性链,那么怎样添加错误检查所需的所有条件逻辑?有许多方法可以做到这一点,但是所有方法都依赖于一个基础约定:永远不在变换之间传递原始值。取而代之的是,将值封装在一个数据结构(或类型)中,该结构可以告知我们所包含的值是否有效。

31 继承税

◆ 继承就是耦合。不仅子类耦合到父类,以及父类的父类等,而且使用子类的代码也耦合到所有祖先类。

◆ 针对传统的类继承的三个替代方案:· 接口与协议· 委托· mixin与特征无论你的目的是共享类型信息、添加功能,还是共享方法,在不同的场景下,都会有一个方案更合适。与编程中的任何事情一样,选一个最能表达你意图的技术。

32 配置

◆ 虽然静态配置很常见,但目前我们倾向于另一种做法。我们仍然希望配置数据保持在应用程序外部,但不直接放在文件中,也不放在数据库里;而是储存在一个服务 API之后。这样做有很多好处:· 在身份认证和访问权限控制将多个应用程序的可见内容阻隔开的情况下,让多个应用程序可以共享配置信息· 配置的变更可以在任何地方进行· 配置数据可以通过专有 UI 维护· 配置数据变得动态最后一点,配置应该是动态的,这在我们转向高可用性应用程序时至关重要。为了改变单个参数就必须停下来重启应用程序,这样的想法已完全脱离当前的现实

第6章 并发

◆ 为什么编写并发和并行代码如此困难?原因之一是,我们一直使用顺序系统来学习编程,所用的语言都有一些在顺序使用时相对安全的特性;但是,一旦两件事情同时发生,这些特性却会拖后腿。此处的罪魁祸首之一是共享状态。这里不单单是指全局变量:任何时候,只要两个或多个代码块持有对同一个可变数据块的引用,就已经共享了状态。

33 打破时域耦合

◆ 时间对我们来说有两个重要的方面:并发性(在同一时刻发生的多件事情)以及次序(事情在时间轴上的相对位置)。我们通常不会在编程时考虑这两个方面。当人们刚开始坐下来设计架构或编写程序时,倾向于将事情线性化。这符合绝大多数人的思考方式——先做这个,再做那个。但这样的思考方式会导向时域耦合——在时间范畴上产生耦合:A 方法必须在 B 方法之前调用;一次只能运行一个报告;在按钮按下前必须等屏幕先重绘;“嘀嘀”一定在“嗒嗒”之前发生。这个方法不太灵活,也不太符合现实。

34 共享状态是不正确的状态

◆ 随机故障通常是并发问题

第7章 当你编码时

◆ 传统观点认为,一旦项目到了编码阶段,就几乎只剩一些机械工作:只是把设计翻译成可运行的代码段而已。我们认为这种态度是软件项目失败的最重要的原因。这导致许多系统最终变得丑陋、低效、结构糟糕、不可维护,或者根本就是错误的。编码不是机械工作。否则,早在20世纪80年代,人们曾寄予厚望的那些CASE(电脑辅助软件工程)工具,就已经取代程序员的工作了。每一分钟都有需要做出的决定——如果想让最终的程序长寿,并在运作期间保持准确高效,那么这些决定都需要经过仔细的思考和判断。

◆ 测试不是关于找Bug的工作,而是一个从代码中获取反馈的过程,涉及设计的方方面面,以及API、耦合度等。这意味着,测试的主要收益来自于你思考和编写测试期间,而不是运行测试那一刻。

37 听从蜥蜴脑

◆ 本能就是我们的无意识大脑对模式的一种直接反应,有些是天生的,有些是通过不断重复学习到的。当你作为程序员积累了经验后,大脑就会逐渐形成一层又一层的隐性知识:这样可以工作,那样不能工作,导致某种类型错误的原因,所有在日常生活中注意到的事情。这部分大脑会在你停下来和别人聊天时,按下保存文件的按键,即使你没有意识到正在这么做。无论直觉是怎么来的,都有一个共同点:无法用语言表达。直觉让你感觉,而不是思考。因此,当一种直觉被触发时,你不会看到闪烁耀眼的提示条。而是会感到紧张反胃,或是警觉到巨大的工作量扑面而来。

40 重构

◆ 重构并不是一种特殊的、隆重的、偶尔进行的活动。为了重新种植而在整个花园中翻耕,重构不是这样的活动。重构是一项日复一日的工作,需要采取低风险的小步骤进行,它更像是耙松和除草这类活动。这是一种有针对性的、精确的方法,有助于保持代码易于更改,而不是对代码库进行自由的、大规模的重写。为了保证外部行为没有改变,你需要良好的自动化单元测试来验证代码的行为。

◆ 重构的核心是重新设计。你或团队中的其他人设计的任何东西,都可以根据新的事实、更深的理解、更改的需求等重新设计。但是,如果你执拗地非要将海量的代码统统撕毁,可能会发现,自己所处的境地,比开始时更加糟糕。显然,重构是一项需要慢慢地、有意地、仔细地进行的活动。马丁·福勒提供了一些简单技巧,可以用来确保进行重构不至于弊大于利:[8]1.不要试图让重构和添加功能同时进行。2.在开始重构之前,确保有良好的测试。尽可能多地运行测试。这样,如果变更破坏了任何东西,都将很快得知。3.采取简短而慎重的步骤:将字段从一个类移动到另一个类,拆分方法,重命名变量。重构通常涉及对许多局部进行的修改,这些局部修改最终会导致更大范围的修改。如果保持小步骤,并在每个步骤之后进行测试,就能避免冗长的调试。

41 为编码测试

◆ 我们看到了 TDD对于从测试着手做事的人的主要好处。只要遵循 TDD 工作流程,就能保证代码始终都有测试。这意味着你会一直处于考虑测试的状态。然而,我们也看到人们成为 TDD 的奴隶。这表现在许多方面:· 他们花费了过多的时间来确保总是有 100% 的测试覆盖率。· 他们做了很多冗余的测试。例如,在第一次编写类之前,许多 TDD 的信徒会先编写一个失败的测试,仅仅只是简单地引用一下类的名称。测试失败了,然后再编写一个空的类定义,以让测试通过。但现在,你有的是一个完全不做任何事的测试;下一个编写的测试也将引用该类,因此第一个测试就变得多余了。如果以后类名发生变更,还需要修改更多内容。这只是一个简单的例子。· 他们的设计倾向于从底层开始,然后逐步上升。(参见下方的“自上而下与自下而上之争,以你应该用的方式去做”)。务必实践一下 TDD。但真这样做时,不要忘记时不时停下来看看大局。人们很容易被“测试通过”的绿色消息所诱惑,从而编写大量的代码,但实际上这些代码并不能让你离解决方案更近。

◆ 在一个老笑话中有人问道:“怎样吃掉一头大象?”回答很妙:“一次咬一口。”当你不能理解整个问题时,就应小步前进,一次一个测试。这个想法经常被吹捧为TDD 的一个优点。然而,这种方法可能会误导你,它鼓励人们专注于不断优化简单的问题,而忽略编码的真正动因。

◆ 这两个学派实际上都没成功,因为它们都忽略了软件开发中最重要的一个方面:我们不知道开始时在做什么。自上而下学派认为可以提前表达整个需求,然而他们做不到。自下而上学派假设他们能构建出一系列的抽象,这串抽象最终会将他们带到一个单一的顶层解决方案,但是当不知道方向时,如何决定每一层的功能呢?

◆ 我们坚信,构建软件的唯一方法是增量式的。构建端到端功能的小块,一边工作一边了解问题。应用学到的知识持续充实代码,让客户参与每一个步骤并让他们指导这个过程。

◆ 在大多数情况下,测试先行,包括测试驱动设计,可能是最佳选择,因为它能确保测试的进行。但它也不是总那么方便和有效,所以在编码期间进行测试是一个很好的后备方案——编写一些代码,尽情修改,为它编写测试,然后继续在下一个部分如法炮制。最糟糕的做法基本可以统称为“以后再测”。开什么玩笑,“以后再测”实际上意味着“永不测试”。

◆ 毫无疑问,测试是编程的一部分,不该留给其他部门的人去做。测试、设计、编码——都是在编程。

43 出门在外注意安全

◆ 特定的开发和部署环境,将有其自己的围绕安全性的需求,但是你应该始终牢记一些基本原则:1.将攻击面的面积最小化2.最小特权原则3.安全的默认值4.敏感数据要加密5.维护安全更新

◆ 一定要记住,当涉及密码学的问题时,常识可能会让你失望。当涉及加密时,第一条也是最重要的一条规则是,永远不要自己做

44 事物命名

◆ 在计算机科学中只有两件难事:缓存失效和命名。

45 需求之坑

◆ 许多书籍和教程都将采集需求放在项目的早期阶段。“采集”一词似乎隐喻着,一群快乐的分析师,他们在周遭的土地上寻找智慧的金块,而背景音乐正在轻柔地演奏着田园交响曲。“采集”意味着需求已经在那里了——你只需要找到它们,将其放在篮子里,然后愉快地上路。但事实并非如此。需求很少停留在表面。通常情况下,它们被埋在层层的假设、误解和政治之下。更糟糕的是,需求通常根本不存在。

◆ 现实世界是混乱的、矛盾的、未知的。在现实世界中,得到任何事物的精确规范,即使不是完全不可能,也是非常罕见的。这就是我们程序员的用武之地。我们的工作是帮助人们了解他们想要什么。事实上,这可能是我们最有价值的属性,因而值得一再重申:提示76 程序员帮助人们理解他们想要什么

◆ 当某些事情看起来很简单的时候,我们却会去寻找那些边缘情况,并就其不胜其烦地问人。很可能客户已经想到了其中的一些问题,并假定实现将以某种方式工作。问这类问题只是把信息明确下来。但有些问题可能客户之前并没有考虑到。这就是事情变得有趣之处,也能让好的开发人员从此处事老练。

◆ 我们相信,最好的需求文档,或许也是唯一的需求文档,就是可以工作的代码。但这并不意味着,你可以不记录对客户需求的理解就扬长而去。这只是意味着,这些文档不必交付:它们不是需要交给客户签字的东西;相反,只是帮助指导实现过程的路标。

◆ 就需求而言,最简单最能准确反映业务需求的语句是最好的。这并不意味着可以摸棱两可——必须将底层语义的不变式作为需求来紧抓不放,并将特定的或当前的工作实践作为策略记录下来。需求不是架构;需求无关设计,也非用户界面;需求就是需要的东西。

◆ 许多项目失败,都可以归咎于不断扩大涉及范围——也称为功能膨胀、特性泛滥或需求蠕变。

47 携手共建

◆ 与用户密切合作的建议贯穿本书;用户是你团队的一部分。在共同工作的第一个项目中,我们一起实践了现在被称为结对编程或群体编程的方法:一个人输入代码,而一个或多个团队成员一起评论、思考和解决问题。这是一种强大的合作方式,超越了没完没了的会议、备忘录和冗长的法律文件。这就是我们所说的“一起工作”的真正含义:不仅仅是提问、讨论、做笔记,还要在真正编码的同一时刻提问和讨论。

◆ 结对编程有很多好处。不同的人有不同的背景和经验,有不同的解决问题的技巧和方法,对任何特定的问题有不同的关注点。充当打字员的开发者必须专注于语法和编码风格的底层细节,而另一个人可以自由地在更高层次的范畴考虑问题。虽然这听起来像是一个小小的区别,但请记住,我们人类的大脑带宽有限。天马行空地输入编译器勉强能接受的深奥单词和符号,就已占用了我们相当大的处理能力。在执行任务的过程中,有另一个开发人员的完整大脑可用,将带来更多的脑力供我们支配。第二个人带来的同伴压力,有助于克服脆弱的瞬间,以及避免把变量起名为 foo 这样的坏习惯等。当有人盯着的时候,也不太可能去走那些让你事后尴尬的捷径,这也会导致软件质量的提高。

◆ 提示82 不要一个人埋头钻进代码中

48 敏捷的本质

◆ 我们觉得很多人已经忽视了敏捷的真正含义,因而希望看到人们回归到最基本的东西。记住宣言中的价值观:我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人。由此我们建立了如下价值观:· 个体和互动高于流程和工具· 工作的软件高于详尽的文档· 客户合作高于合同谈判· 响应变化高于遵循计划也就是说,尽管右项有其价值,我们更重视左项的价值。如果有人向你兜售一些东西,而这些东西让你觉得右边的事情比左边的事情更重要,那么这样的人,对于我们和其他宣言作者重视的东西,显然不会认同。

◆ 在开发软件时,并没有单一的计划可以遵循。在敏捷宣言的四条价值观中,有三条谈的都是这一点,都是关于收集和回应反馈的。这些价值观不会告诉你该做什么。当你自己决定要做点什么的时候,它们会告诉你要去追寻什么。

49 务实的团队

◆ 在一些团队方法中,团队会设一个“质量官”——由这个人对交付产品的质量负责。这显然是荒谬的:质量只能来自团队每个成员的独立贡献。质量是内在的,无法额外保证。

◆ 如果团队对改进和创新是认真的,那么就需要将其排入日程表。“只要有空闲时间”就去做,意味着这件事永远不会发生。无论你处理事务用的是待办事项表、任务列表、流程表,还是什么别的工具,都不要将其仅用于功能开发。

◆ 记住团队是由个人组成的。赋予每个成员能力,让他们以自己的方式发光发热。要提供完善的架构来支持他们,并确保项目交付的价值。然后,像够好即可的软件中的画家那样,抵制住多画几笔的诱惑。

50 椰子派不上用场

◆ 软件开发方法论的目的是帮助人们一起工作。正如我们在敏捷的本质中所讨论的,在开发软件时,没有哪一个计划是可以照搬的,更别说另一家公司里某个人提出的一个计划。许多认证课程实际上更加糟糕:它们建立在学生能够记住并遵守规则的基础之上。而你想要的并非如此,你需要有能力超越现有的规则,发掘有利的可能性。

◆ 我们经常听到软件开发的领导者对员工说,“我们应该像 Netflix(或另一家领先的公司)那样运营”。你当然可以这么做。首先,你自己先要有几十万台服务器和几千万用户……

51 务实的入门套件

◆ 文明的进步是以增加那些不需要思考就能完成的重要操作来实现的。——阿尔弗雷德·诺思·怀特海

◆ · 版本控制· 回归测试· 完全自动化这是支撑每个项目的三条腿。

◆ 你可以试试能在测试期间监视代码覆盖率的分析工具,并跟踪哪些代码行已经执行,哪些没有执行。这些工具帮助你大致了解测试有多全面,但是不要期望 100% 的覆盖率。[9]即使你碰巧命中了代码的每一行,也不代表全部。重要的是程序可能具有的状态数。状态和代码行并不等价。

52 取悦用户

◆ 如果你想取悦客户,就和他们建立起某种关系,这样即可积极地帮助他们解决问题。或许你的头衔只是“软件开发者”或“软件工程师”的某种变体,而事实上这个头衔应该是“解决问题的人”。这就是我们所做的,也是一个务实的程序员的本质。我们在解决问题。

◆ 在《人月神话:软件项目管理之道》[Bro96]中,弗雷德里克·布鲁克斯说过:“程序员,就像诗人一样,几乎仅仅工作在单纯的思考中。他们运用自己的想象,来建造自己的城堡。”我们从一张白纸开始,几乎可以创造任何我们能想象到的东西。我们创造的东西可以改变世界。

◆ 对于我们交付的每一段代码,我们有义务问自己两个问题:1.我已经保护好用户了吗?2.我自己会用它吗?

译者跋

◆ 如果你买到本书的原版,且有能力阅读英文(我相信这是一个务实的程序员的必备技能),请直接去读原文。因为与之相较,我的译文可能根本不值得一读。