笔记本强力风扇:高效程序员的45个习惯

来源:百度文库 编辑:中财网 时间:2024/05/09 04:40:59

高效程序员的45个习惯

 


内容简介

 

本书总结并生动地阐述了成为高效的开发人员所需具备的45个习惯、思想观念和方法,涵盖了软件开发进程、编程和调试工作、开发者态度、项目和团队管理以及持续学习等几个方面。本书适合所有程序员阅读。

本书简明实用、见解深刻,总结了高效程序员在开发过程中的45个个人习惯、思想观念和方法,有助于开发人员在开发进程、编码工作、开发者态度、项目和团队管理,以及持续学习等5个方面积极修炼。通过学习这些内容,养成这些好的习惯,你可以极大地提升自己的编程实力,更快速、更可靠地交付更高质量的软件,从而成为真正的高效程序员。


目录


第1章 敏捷——高效软件开发之道 1 
第2章 态度决定一切 10 
1 做事 12 
2 欲速则不达 15 
3 对事不对人 18 
4 排除万难,奋勇前进 23 
第3章 学无止境 26 
5 跟踪变化 28 
6 对团队投资 31 
7 懂得丢弃 34 
8 打破砂锅问到底 37 
9 把握开发节奏 40 
第4章 交付用户想要的软件 43 
10 让客户做决定 45 
11 让设计指导而不是操纵开发 48 
12 合理地使用技术 52 
13 保持可以发布 55 
14 提早集成,频繁集成 58 
15 提早实现自动化部署 61 
16 使用演示获得频繁反馈 64

17 使用短迭代,增量发布 69 
18 固定的价格就意味着背叛承诺 73 
第5章 敏捷反馈 76 
19 守护天使 78 
20 先用它再实现它 82 
21 不同环境,就有不同问题 87 
22 自动验收测试 90 
23 度量真实的进度 93 
24 倾听用户的声音 96 
第6章 敏捷编码 98 
25 代码要清晰地表达意图 100 
26 用代码沟通 105 
27 动态评估取舍 110 
28 增量式编程 113 
29 保持简单 115 
30 编写内聚的代码 117 
31 告知,不要询问 121 
32 根据契约进行替换 124 
第7章 敏捷调试 128 
33 记录问题解决日志 129 
34 警告就是错误 132 
35 对问题各个击破 136 
36 报告所有的异常 139 
37 提供有用的错误信息 141 
第8章 敏捷协作 146 
38 定期安排会面时间 148 
39 架构师必须写代码 152 
40 实行代码集体所有制 155 
41 成为指导者 157 
42 允许大家自己想办法 160 
43 准备好后再共享代码 162 
44 做代码复查 165 
45 及时通报进展与问题 168 
第9章 尾声:走向敏捷 170 
9.1 只要一个新的习惯 170 
9.2 拯救濒临失败的项目 171 
9.3 引入敏捷:管理者指南 172 
9.4 引入敏捷:程序员指南 174 
9.5 结束了吗 175 
附录A 资源 176 
索引 182

 


译者序

 

"武功者,包括内功、外功、武术技击术之总和。有形的动作,如支撑格拒,姿式回环,变化万千,外部可见,授受较易,晨操夕练,不难熟练。而无形的内功指内部之灵惠素质,即识、胆、气、劲、神是也,此乃与学练者整个内在世界的学识水平密切相关,是先天之慧根悟性与后天智能的总成,必需寻得秘籍方可炼成。"

--摘自《武林秘籍大全》

公元21世纪,软件业江湖动荡,人才辈出,各大门派林立,白道黑帮,都欲靠各自门派的武功称霸武林。

在那些外家功门派(传统的瀑布开发方法、CMM、ISO和RUP等)和非正统教(中国式太极敏捷UDD等)当道之际,一股新势力正在崛起--以敏捷方法为总称的一批内家功门派。

下面的歌诀是对内家武功招数的概述:

迭代开发,价值优先

分解任务,真实进度

站立会议,交流畅通

用户参与,调整方向

结对编程,代码质量

测试驱动,安全可靠

持续集成,尽早反馈

自动部署,一键安装

定期回顾,持续改进

不断学习,提高能力

上面的每种招式,都可寻得一本手册,介绍其动作要领和攻防章法。几乎每个内家功门派都有自己的拳法和套路。

但,正所谓"练拳不练功,到老一场空"。学习招数和套路不难,难的是如何练就一身真功夫。内家功,以练内为主,内外结合,以动作引领内气,以内气催领动作,通过后天的修炼来弥补先天的不足。

本书是一本内功手册。它注重于培养软件开发者的态度、原则、操守、价值观,即识、胆、气、劲、神是也。

敏捷的实践者Venkat Subramaniam和Andy Hunt携手著下此书。望有志之士有缘得到此书,依法修习,得其精要;由心知到身知,入筋、入骨、入髓,修炼得道。而后,匡扶正义,交付高质量的软件,为人类造福。

安 川
2008年4月于北京


推荐序一

 

仅仅还在几年前,XP还被认为是方法异教,FDD属于黑客程序方法。如今,敏捷俨然已经成为主流学说,敏捷方法成为人们学习和讨论的热点。敏捷方法的应用也更加广泛,以至于不少外包项目都要求采用某种敏捷方法。它不仅仅是小团队和小项目在使用,甚至连微软都开始使用Scrum。

敏捷已经成为一种炙手可热的新时尚。

因为火热,各种不同的说法就多起来;因为时尚,原本有些不认同敏捷的人也开始追捧起来。人们反复地讨论敏捷方法,涉及从哲学思想到实现细节的各个层面。人们不断地推出各种不同版本的敏捷方法,甚至有些方法显得如此矛盾、如此不同。

同时,一些误解也一直在坊间流行。一般误认为敏捷就是快,越快就是越敏捷--字典上的名词解释是其依据。岂不知它本来要以"lightweight processes"(轻量级过程)命名,只不过有些参会者不喜欢被看做是在拳台上跳来跳去的轻量级拳手,所以才用了"敏捷"这个词。还有其他一些误解是,敏捷就是只写代码不写文档;敏捷需要重构而无需设计;敏捷迭代就是尽量做到最小,以至于一个小时就好几次;敏捷需要天才的程序员才能应用,其他人都会水土不服;如此这般。

可以看到,市面上以敏捷为题目的图书俯拾皆是,似乎软件开发的书不加上敏捷这个词就是落伍一样。敏捷体系下存在多种方法,介绍每种方法的图书就有一大堆。再加上每种方法采用不同的技术,每本书采用不同的组织形式,存在这么多书也不奇怪,就更不用提那些仅仅为了跟风而敏捷的作品了。

面对如此百花齐放、百家争鸣的现象,你该从什么地方开始呢?有没有一本图书可以作为入门的第一读物呢?

这本书就可以胜任这样的角色!

这是一本很容易理解并掌握,不需要太多基础就可以阅读的书。不管你是开发人员,还是管理人员、财务等后勤人员、学生、编程爱好者,只要你对敏捷有兴趣,就可以读懂这本书。你不会被众多的概念和曲折的逻辑所迷惑,不会被高难度技巧所困扰。这本书为你打开了了解和学习敏捷方法的一扇大门,并指出继续前进的道路。

你会很悠闲自在地读完这本小书,然后说:"原来敏捷就是这么一回事啊!"

自由软件顾问 刘新生


推荐序二

 

我很喜欢本书的中文书名--高效程序员的45个习惯,比直译成"敏捷开发者实践"含蓄多了。敏捷不是目的,只是手段。只要某个手段适合某个场景,有助于提升质量,提高交付能力,提高开发者水平……总而言之,有好处的事情,我们尽管做就是了,何必冠以敏捷之名?

记得第一次读本书还是两年前。这时又细细读来,越来越觉得"习惯"一词比"实践"更有味道。所谓"流水不腐,户枢不蠹",厨房脏了就擦一下,总比满墙都是油烟以后再去清理的代价小得多。有价值的东西--比如回顾、测试、重构,一切有利于团队建设、提高生产力的实践都应该频繁且持续做,然后日积月累就养成了习惯。

有些习惯很容易养成,有些则很难。我们大都常常许愿,做计划,比如要做一个至少100人同时在线的成熟应用,参加义工活动,每周至少一篇博客……然后在计划落空的时候,用各种理由来安慰自己。

李笑来老师在《把时间当作朋友》一书中提到:"所有学习上的成功,都只靠两件事:策略和坚持,而坚持本身就应该是最重要的策略之一。"那么,为什么我们会在某些事情上坚持不下去?或者换个角度来看,哪些事情是容易坚持下去的?

以前我是标准的宅男,CS、网络小说、魔兽世界几乎是休闲的全部,等到后来得了腰肌劳损,又得了颈椎病,这才痛定思痛,开始游泳锻炼身体。每天游两千米,一个月以后,游泳就成了习惯。再举个例子,我老婆生完孩子以后体型变化很大,立志想要减肥。为了坚持下去,她把怀孕前的照片放在电脑桌面上,时时督促自己。后来,减肥也就变成了一种生活方式。

从我的个人体验来看,难以坚持下去的事情,基本都是因为没有迫切的欲望和激情。单说锻炼身体,无论是为了减肥、祛病,还是塑形美体等,做这些事情至少都有明确的目的,这样才能驱使着人们一直坚持下去。没有动机,没有欲望,哪里来的毅力呢?

那么,当我们决定做一件事情的时候,首先就要多问问自己:为什么要做这件事情?它所带来的好处是什么?如果不做它又会有哪些坏处?有了清晰的目的和思路后再去做事,遇到变化时就知道孰轻孰重,该怎么调整计划,同时也不至于被重复和乏味消磨了一时的意气。翻开本书之后,你同样也该对自己提问:"为什么要有自动验收测试,有了足够的单元测试是不是就能保证质量了?""写自动验收测试有哪些成本,会带来哪些收益?"只有明白了"为什么做",才能够解决"如何做"的问题。

本书的两名译者与我都是故交。钱安川是我的同事,是ThoughtWorks资深咨询师,有丰富的敏捷实施经验。郑柯与我同是InfoQ中文站敏捷社区的编辑,一起翻译过数十篇稿件。他翻译的《项目管理修炼之道》也即将由图灵公司出版。这次二人联手的作品,定会给读者以赏心悦目的阅读体验。我有幸已经从样章中感受到了这一点。

希望你能够带着问题,踏上愉快的阅读之旅。

希望你能够养成好习惯。

李 剑
ThoughtWorks咨询师
2009.10.10


【45个习惯简述】

 

态度篇

 

1. 做实事

不要抱怨,发牢骚,指责他人,找出问题所在,想办法解决。对问题和错误,要勇于承担。

2. 欲速则不达

用小聪明、权宜之计解决问题,求快而不顾代码质量,会给项目留下要命的死角。

3. 对事不对人

就事论事,明智、真诚、虚心地讨论问题,提出创新方案。

4. 排除万难,奋勇前进

勇气往往是克服困难的唯一方法。

 

学习篇

5. 跟踪变化

新技术层出不穷并不可怕。坚持学习新技术,读书,读技术杂志,参加技术活动,与人交流。要多理解新词背后的所以然,把握技术大趋势,将新技术用于产品开发要谨慎。

6. 对团队投资

打造学习型团队,不断提高兄弟们的平均水平。

7. 懂得丢弃

老的套路和技术,该丢,就得丢。不要固步自封。

8. 打破砂锅问到底

不断追问,真正搞懂问题的本质。为什么?应该成为你的口头禅。

9. 把握开发节奏

控制好时间,养成好习惯,不要加班。

 

开发流程篇

10. 让客户做决定

让用户在现场,倾听他们的声音,对业务最重要的决策应该让他们说了算。

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

设计是前进的地图,它指引的是方向,而不是目的本身。设计的详略程度应该适当。

12. 合理地使用技术

根据需要而不是其他因素选择技术。对各种技术方案进行严格地追问,真诚面对各种问题。

13. 让应用随时都可以发布

通过善用持续集成和版本管理,你应该随时都能够编译、运行甚至部署应用。

14. 提早集成,频繁集成

集成有风险,要尽早尽量多地集成。

15. 提早实现自动化部署

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

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

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

估算应该基于实际的工作不断变化。

 

用户篇

19. 守护天使

自动化单元测试是你的守护天使。

20. 先用它再实现它

测试驱动开发其实是一种设计工具。

21. 不同环境,就有不同问题

要重视多平台问题。

22. 自动验收测试

23. 度量真实的进度

在工作量估算上,不要自欺欺人。

24. 倾听用户的声音

每一声抱怨都隐藏着宝贵的真理。

 

编程篇

25. 代码要清晰地表达意图免费样章链接

代码是给人读的,不要耍小聪明。

26. 用代码沟通

注释的艺术。

27. 动态地进行取舍(免费样章链接)

记住,没有最佳解决方案。各种目标不可能面面俱到,关注对用户重要的需求。

28. 增量式编程

写一点代码就构建、测试、重构、休息。让代码干净利落。

29. 尽量简单

宁简勿繁。如果没有充足的理由,就不要使用什么模式、原则和特别的技术。

30. 编写内聚的代码

类和组件应该足够小,任务单一。

31. 告知,不要询问

多用消息传递,少用函数调用。

32. 根据契约进行替换

委托往往优于继承。

 

调试篇

33. 记录问题解决日志免费样章链接

不要在同一地方摔倒两次。错误是最宝贵的财富。

34. 警告就是错误

忽视编译器的警告可能铸成大错。

35. 对问题各个击破免费样章链接

分而治之是计算机科学中最重要的思想之一。但是,要从设计和原型阶段就考虑各部分应该能够很好地分离。

36. 报告所有的异常

37. 提供有用的错误信息免费样章链接

稍微多花一点心思,出错的时候,将给你带来极大便利。

 

团队协作篇

38. 定期安排会面时间

常开会,开短会。

39. 架构师必须写代码免费样章链接

不写代码的架构师不是好架构师。好的设计都来自实际编程。编程可以带来深入的理解。

40. 实行代码集体所有制

让开发人员在系统不同区域中不同的模块和任务之间轮岗。

41. 成为指导者

教学相长。分享能提高团队的总体能力。

42. 让大家自己想办法免费样章链接

指引方向,而不是直接提供解决方案。让每个人都有机会在干中学习。

43. 准备好后再共享代码

不要提交无法编译或者没有通过单元测试的代码!

44. 做代码复查

复查对提高代码质量、减少错误极为重要。

45. 及时通报进展与问题免费样章链接

主动通报,不要让别人来问你。


【45个习惯详述】

 

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

 


2章 态度决定一切

1.做事

2.快速修复变成了快速流沙

3.对事不对人

4.排除万难,奋勇前进


第3章 学无止境

5.跟踪变化

6.对团队投资

7.懂得丢弃

8.打破砂锅问到底

9.把握开发节奏


第4章 交付用户想要的软件

10.让客户做决定

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

12.合理地使用技术

13.保持可以发布

14.提早集成,频繁集成

15.提早实现自动化部署

16.频繁地演示获得用户反馈

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

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


第5章 敏捷反馈

19.守护天使

20.先用它再实现它

21.不同环境,就有不同问题

22.自动验收测试

23.度量真实的进度

24.倾听用户的声音


第6章 敏捷编码

 

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

  “可以工作而且易于理解的代码挺好,但是让人觉得聪明更加重要。别人给你钱是因为你脑子好使,让我们看看你到底有多聪明。”

 

Hoare 谈软件设计

C.A.R. Hoare

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

我们大概都见过不少难以理解和维护的代码,而且(最坏的是)还有错误。当开发人员们像一群旁观者见到UFO一样围在代码四周,同样也感到恐惧、困惑与无助时,这个代码的质量就可想而知了。如果没有人理解一段代码的工作方式,那这段代码还有什么用呢?

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

例如,如果默认参数或可选参数会影响代码可读性,使其更难以理解和调试,那最好明确地指明参数,而不是在以后让人觉得迷惑。

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

明白地告诉阅读程序的人,代码都做了什么,这是让其便于理解的一种方式。让我们看一些例子。

coffeeShop.PlaceOrder(2);

通过阅读上面的代码,可以大致明白这是要在咖啡店中下一个订单。但是,2到底是什么意思?是意味着要两杯咖啡?要再加两次?还是杯子的大小?要想搞清楚,唯一的方式就是去看方法定义或者文档,因为这段代码没有做到清晰易懂。

所以我们不妨添加一些注释。

coffeeShop.PlaceOrder(2 /* large cup */);

现在看起来好一点了,但是注释有时候是用来对写得很差的代码进行补偿的(见第105 页中习惯26:用代码沟通)。

Java 5与.NET中有枚举值的概念,我们不妨使用一下。使用C#,我们可以定义一个名为CoffeeCupSize的枚举,如下所示。

public enum CoffeeCupSize

{

  Small,

  Medium,

  Large

        }

接下来就可以用它来下单要咖啡了。

        coffeeShop.PlaceOrder(CoffeeCupSize.Largxe);

这段代码就很明白了,我们是要一个大杯[]的咖啡。

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

Line 1   public int compute(int val)

     -  {

     -     int result = val << 1;

     -    //... more code ...

     5     return result;

     -  }

第3行中的位移操作符是用来干什么的?如果善于进行位运算,或者熟悉逻辑设计或汇编编程,就会明白我们所做的只是把val的值乘以2。

 

      PIE 原则

      所写的代码必须明确表达你的意图,而且必须富有表现力。这样可以让代码更易于被别人阅读和理解。代码不让人迷惑,也就减少了发生潜在错误的可能。代码要清晰地表达意图。

但对没有类似背景的人们来说,又会如何——他们能明白吗?也许团队中有一些刚刚转行做开发、没有太多经验的成员。他们会挠头不已,直到把头发抓下来[②]。代码执行效率也许很高,但是缺少明确的意图和表现力。

用位移做乘法,是在对代码进行不必要且危险的性能优化。result=val*2看起来更加清晰,也可以达到目的,而且对于某种给定的编译器来说,可能效率更高(积习难改,见第34页的习惯7)。不要表现得好像很聪明似的,要遵循PIE原则:代码要清晰地表达意图。

要是违反了PIE原则,造成的问题可就不只是代码可读性那么简单了——它会影响到代码的正确性。下列代码是一个C#方法,试图同步对CoffeeMaker中MakeCoffee()方法进行调用。

Public void MakeCoffee()

{

    lock(this)

    {

      // ... operation

    }

}

这个方法的作者想设置一个临界区(critical section)——任何时候最多只能有一个线程来执行operation中的代码。要达到这个目的,作者在CoffeeMaker实例中声明了一个锁。一个线程只有获得这个锁,才能执行这个方法。(在Java中,会使用synchronized而不是lock,不过想法是一样的。)

对于Java或.NET程序员来说,这样写顺理成章,但是其中有两个小问题。首先,锁的使用影响范围过大;其次,对一个全局可见的对象使用了锁。我们进一步来看看这两个问题。

假设Coffeemaker同时可以提供热水,因为有些人希望早上能够享用一点伯爵红茶。我想同步GetWater()方法,因此调用其中的lock(this)。这会同步任何在CoffeeMaker上使用lock 的代码,也就意味着不能同时制作咖啡以及获取热水。这是开发者原本的意图吗?还是锁的影响范围太大了?通过阅读代码并不能明白这一点,使用代码的人也就迷惑不已了。

同时,MakeCoffee()方法的实现在CoffeeMaker对象上声明了一个锁,而应用的其他部分都可以访问CoffeeMaker对象。如果在一个线程中锁定了CoffeeMaker对象实例,然后在另外一个线程中调用那个实例之上的MakeCoffee()方法呢?最好的状况也会执行效率很差,最坏的状况会带来死锁。

让我们在这段代码上应用PIE原则,通过修改让它变得更加明确吧。我们不希望同时有两个或更多的线程来执行MakeCoffee()方法。那为什么不能为这个目的创建一个对象并锁定它呢?

Private object makeCoffeeLock = new Object();

Public void MakeCoffee()

{

    lock (makeCoffeeLock)

    {

      // ... operation

    }

}

这段代码解决了上面的两个问题——我们通过指定一个外部对象来进行同步操作,而且更加明确地表达了意图。

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

要编写清晰的而不是讨巧的代码

向代码阅读者明确表明你的意图。可读性差的代码一点都不聪明。

 

切身感受

应该让自己或团队的其他任何人,可以读懂自己一年前写的代码,而且只读一遍就知道它的运行机制。

平衡的艺术

  • 现在对你显而易见的事情,对别人可能并不显然,对于一年以后的你来说,也不一定显然。不妨将代码视作不知道会在未来何时打开的一个时间胶囊。
  • 不要明日复明日。如果现在不做的话,以后你也不会做的。
  • 有意图的编程并不是意味着创建更多的类或者类型。这不是进行过分抽象的理由。

使用符合当时情形的耦合。例如,通过散列表进行松耦合,这种方式适用于在实际状况中就是松耦合的组件。不要使用散列表存储紧密耦合的组件,因为这样没有明确表示出你的意图。


[①]对星巴克的粉丝来说,这是指venti

[②]没错,那不是一块秃顶,而是一个编程机器的太阳能电池板。

 

26.用代码沟通

 

27.习惯27:动态评估取舍

“性能、生产力、优雅、成本以及上市时间,在软件开发过程中都是至关重要的因素。每一项都必须达到最理想状态。”

可能曾经身处这样的团队:管理层和客户将很大一部分注意力都放在应用的界面展示上。也有这样的团队,其客户认为性能表现非常重要。在团队中,你可能会发现,有这样一个开发主管或者架构师,他会强调遵守“正确”的范式比其他任何事情都重要。对任何单个因素如此独断地强调,而不考虑它是否是项目成功的必要因素,必然导致灾难的发生。

强调性能的重要性情有可原,因为恶劣的性能表现会让一个应用在市场上铩羽而归。然而,如果应用的性能已经足够好了,还有必要继续投入精力让其运行得更快一点吗?大概不用了吧。一个应用还有很多其他方面的因素同样重要。与其花费时间去提升千分之一的性能表现,也许减少开发投入,降低成本,并尽快让应用程序上市销售更有价值。

举例来说,考虑一个必须要与远程Windows服务器进行通讯的.NET Windows应用程序。可以选择使用.NET Remoting 技术或Web Services来实现这个功能。现在,针对使用Web Services的提议,有些开发者会说:“我们要在Windows之间进行通信,通常此类情况下,推荐使用.NET Remoting。而且,Web Services很慢,我们会遇到性能问题。”嗯,一般来说确实是这样。

然而,在这个例子中,使用Web Services很容易开发。对Web Services的性能测试表明XML文档很小,并且相对应用程序自己的响应时间来讲,花在创建和解析XML上的时间几乎可以忽略不计。使用Web Services不但可以在短期内节省开发时间,且在此后团队被迫使用第三方提供的服务时,Web Services也是个明智的选择。

Andy 说。。。

过犹不及

我曾经遇到这样一个客户,他们坚信可配置性的重要性,致使他们的应用有大概10 000个可配置变量。新增代码变得异常艰难,因为要花费大量时间来维护配置应用程序和数据库。但是他们坚信需要这种程度的灵活性,因为每个客户都有不同的需求,需要不同的设置。

可实际上,他们只有19个客户,而且预计将来也不会超过50个。他们并没有很好地去权衡。

考虑这样一个应用,从数据库中读取数据,并以表格方式显示。你可以使用一种优雅的、面向对象的方式,从数据库中取数据,创建对象,再将它们返回给UI层。在UI层中,你再从对象中拆分出数据,并组织为表格方式显示。除了看起来优雅之外,这样做还有什么好处吗?

也许你只需要让数据层返回一个dataset或数据集合,然后用表格显示这些数据即可。这样还可以避免对象创建和销毁所耗费的资源。如果需要的只是数据展示,为什么要创建对象去自找麻烦呢?不按书上说的OO方式来做,可以减少投入,同时获得性能上的提升。当然,这种方式有很多缺点,但问题的关键是要多长个心眼儿 ,而不是总按照习惯的思路去解决问题。

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

当然,这也不完全对。如果硬件需求非常庞大,需要一个巨大的计算机网格以及众多的支持人员才能维持其正常运转(比如类似Google那样的需求),那么考虑就要向天平的另一端倾斜了。

但是谁来最终判定性能表现已经足够好,或是应用的展现已经足够“炫”了呢?客户或是利益相关者必须进行评估,并做出相关决定(见第45页中习惯10)。如果团队认为性能上还有提升的空间,或者觉得可以让某些界面看起来更吸引人,那么就去咨询一下利益相关者,让他们决定应将重点放在哪里。

没有适宜所有状况的最佳解决方案。你必须对手上的问题进行评估,并选出最合适的解决方案。每个设计都是针对特定问题的——只有明确地进行评估和权衡,才能得出更好的解决方案。

没有最佳解决方案 (No best solution)

 

动态评估权衡

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

 

切身感受

即使不能面面俱到,你也应该觉得已经得到了最重要的东西——客户认为有价值的特性。

平衡的艺术

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

 

28.增量式编程

29.保持简单

30.编写内聚的代码

31.告知,不要询问

32.根据契约进行替换


第7章 敏捷调试

 

你也许会对木匠那毫无差错的工作印象深刻。但我向你保证,事实不是这样的。真正的高手只是知道如何亡羊补牢。

--Jeff Miller,家具制造者、作家

即使是运作得最好的敏捷项目,也会发生错误。bug、错误、缺陷--不管被称作什么,它们总会发生。

在调试时面对的真正问题,是无法用固定的时间来限制。可以规定设计会议的持续时间,并在时间截止时决定采用最佳的方案。但是调试所耗费的时间,可能是一个小时、一天,甚至一周过去了,还是没有办法找到并解决问题。

对于一个项目来说,这种没有准确把握的时间消耗是不可接受的。不过,我们可以使用一些辅助技术,涵盖的范围包括:保留以前的问题解决方案,以及提供发生问题时的更多有用细节。

要想更加有效地重用你的知识和努力,记录问题解决日志是很有用的,我们会在下一页看到如何具体操作。当编译器警告有问题的时候,要假定警告就是错误,并且马上把它们解决掉(第132页)。

想在一个完整的系统中跟踪问题非常困难--甚至是不可能的。如果可以对问题各个击破,正如我们在第136页中看到的那样,就更容易找到问题了。不同于某些欲盖弥彰的行为,应该报告所有的异常,如第139页所述。最后,在报告某些事情出错之时,必须要考虑用户的感受,并且提供有用的错误信息。我们会在第141页看到这是为什么。

 

33.习惯33:记录问题解决日志

 “在开发过程中是不是经常遇到似曾相识的问题?这没关系。以前解决过的问题,现在还是可以解决掉的。”

面对问题(并解决它们)是开发人员的一种生活方式。当问题发生时,我们希望赶紧把它解决掉。如果一个熟悉的问题再次发生,我们会希望记起第一次是如何解决的,而且希望下次能够更快地把它搞定。然而,有时一个问题看起来跟以前遇到的完全一样,但是我们却不记得是如何修复的了。这种状况时常发生。

不能通过Web搜索获得答案吗?毕竟互联网已经成长为如此令人难以置信的信息来源,我们也应该好好加以利用。从Web上寻找答案当然胜过仅靠个人努力解决问题。可这是非常耗费时间的过程。有时可以找到需要的答案,有时除了找到一大堆意见和建议之外,发现不了实质性的解决方案。看到有多少开发人员遇到同样的问题,也许会感觉不错,但我们需要的是一个解决办法。

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

不要在同一处跌倒两次                     Don't get burned twice

 

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

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

要将日志保存为可供计算机搜索的格式,就可以进行关键字搜索以快速查找细节。图 7-1 展示了一个简单的例子,其中带有超链接以提供更多信息。

图7-1带有超链接的解决方案条目示例

如果面临的问题无法在日志中找到解决方案,在问题解决之后,要记得马上将新的细节记录到日志中去。

要共享日志给其他人,而不仅仅是靠一个人维护。把它放到共享的网络驱动器中,这样其他人也可以使用。或者创建一个Wiki,并鼓励其他开发人员使用和更新其内容。

维护一个问题及其解决方案的日志。

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

切身感受

解决方案日志应该作为思考的一个来源,可以在其中发现某些特定问题的细节。对于某些类似但是有差异的问题,也能从中获得修复的指引。

平衡的艺术

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

 

34.习惯34:警告就是错误

“编译器的警告信息只不过是给过分小心和过于书呆子气的人看的。它们只是警告而已。如果导致的后果很严重,它们就是错误了,而且会导致无法通过编译。所以干脆忽略它们就是了。”

 

当程序中出现一个编译错误时,编译器或是构建工具会拒绝产生可执行文件。我们别无选择——必须要先修正错误,再继续前行。

 

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

有些警告是过于挑剔的编译器的良性副产品,有些则不是。例如,一个关于未被使用的变量的警告,可能不会产生什么恶劣影响,但却有可能是暗示某些变量被错误使用了。

 

最近在一家客户那里,Venkat发现一个开发中的应用有多于300个警告。其中一个被开发人员忽略的警告是这样:

 

Assignment in conditional expression is always constant;

did you mean to use == instead of = ?

条件表达式中的赋值总为常量,你是否要使用==而不是=?

 

相关代码如下:

 

if (theTextBox.Visible = true)

...

也就是说,if 语句总是会评估为true,无论不幸的theTextBox变量是什么状况。看到类似这样真正的错误被当作警告忽略掉,真是令人感到害怕。

 

看看下面的C#代码:

 

public class Base

{

  public virtual void foo()

  {

    Console.WriteLine("Base.foo ");

  }

}

 

public class Derived : Base

{

    public virtual void foo()

  {

    Console.WriteLine("Derived.foo ");

  }

}

 

class Test

{

  static void Main(string[] args)

  {

    Derived d = new Derived();

    Base b = d;

    d.foo();

    b.foo();

  }

}

 

在使用Visual Studio 2003默认的项目设置对其进行编译时,会看到如此信息“构建:1个成功,0失败,0 跳过”显示在Output窗口的底部。运行程序,会得到这样的输出:

 

Derived.foo

Base.foo

 

但这不是我们预期的结果。应该看到两次对Derived类中foo方法的调用。是哪里出错了?如果仔细查看[ 输出]窗口,可以发现这样的警告信息:

 

Warning. Derived.foo hides inherited member Base.foo

To make the current member override that implementation,

add the override keyword. Otherwise, you' d add the new keyword.

 

这明显是一个错误——在 Derived 类的foo()方法中,应该使用override 而不是 virtual。[1]想象一下,有组织地忽略代码中类似这样的错误会导致什么样的后果。代码的行为会变得无法预测,其质量会直线下降。

可能有人会说优秀的单元测试可以发现这些问题。是的,它们可以起到帮助作用(而且也应该使用优秀的单元测试)。可如果编译器可以发现这种问题,那为什么不利用它呢?这可以节省大量的时间和麻烦。

要找到一种方式让编译器将警告作为错误提示出来。如果编译器允许调整警告的报告级别,那就把级别调到最高,让任何警告不能被忽略。例如, GCC编译器支持 -Werror 参数,在 Visual Studio 中,开发人员可以改变项目设置,将警告视为错误。

 对于一个项目的警告信息来说,至少也要做到这种地步。然而,如果采取这种方式,就要对创建的每个项目去进行设置。如果可以尽量以全局化的方式来进行设置就好了。

比如,在Visual Studio中,开发人员可以修改项目模板(查看.NET Gotchas[Sub05]获取更多细节),这样在计算机上创建的任何项目,都会有同样的完整项目设置。在当前版本的Eclipse中,可以按照这样的顺序修改设置:Windows→Preferences→Java→Compiler→Errors/Warnings。如果使用其他的语言或IDE,花一些时间来找出如何在其中将警告作为错误处理吧。

在修改设置的时候,要记得在构建服务器上使用的持续集成工具中,修改同样的设置选项。(要详细了解持续集成,查看在第87页上的习惯21。)这个小小的设置,可以大大提升团队签入到源码控制系统中的代码质量。

在开始一个项目的时候,要把相关的设置都准备好。在项目进行到一半的时候,突然改变警告设置,有可能会带来颠覆性的后果,导致难以控制。

编译器可以轻易处理警告信息,可是你不能。

 

将警告视为错误

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

 

切身感受

警告给人的感觉就像……哦,警告。它们就某些问题给出警告,来吸引开发人员的注意。

平衡的艺术

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

 


[1]这对C++程序员来讲是一个潜伏的陷阱。在C++中代码可以按预期方式工作。

 

35.习惯35:对问题各个击破

“逐行检查代码库中的代码确实很令人恐惧。但是要调试一个明显的错误,只有去查看整个系统的代码,而且要全部过一遍。毕竟你不知道问题可能发生在什么地方,这样做是找到它的唯一方式。”

单元测试(在第76页,第5章)带来的积极效应之一,是它会强迫形成代码的分层。要保证代码可测试,就必须把它从周边代码中解脱出来。如果代码依赖其他模块,就应该使用mock对象,来将它从其他模块中分离开。这样做不但让代码更加健壮,且在发生问题时,也更容易定位来源。

否则,发生问题时有可能无从下手。也许可以先使用调试器,逐行执行代码,并试图隔离问题。也许在进入到感兴趣的部分之前,要运行多个表单或对话框,这会导致更难发现问题的根源。你会发现自己陷入整个系统之中,徒然增加了压力,而且降低了工作效率。

大型系统非常复杂——在执行过程中会有很多因素起作用。从整个系统的角度来解决问题,就很难区分开,哪些细节对要定位的特定问题产生影响,而哪些细节没有。

答案很清晰:不要试图马上了解系统的所有细节。要想认真调试,就必须将有问题的组件或模块与其他代码库分离开来。如果有单元测试,这个目的就已经达到了。否则,你就得开动脑筋了。

比如,在一个时间紧急的项目中(哪个项目的时间不紧急呢),Fred和George发现他们面对的是一个严重的数据损毁问题。要花很多精力才能知道哪里出了问题,因为开发团队没有将数据库相关的代码与其他的应用代码分离开。他们无法将问题报告给软件厂商,当然不能把整个代码库用电子邮件发给人家!

于是,他们俩开发了一个小型的原型系统,并展示了类似的症状;然后将其发送给厂商作为实例,并询问他们的专家意见,使用原型帮助他们对问题理解得更清晰。

而且,如果他们无法在原型中再现问题的话,原型也可以告诉他们可以工作的代码示例,这也有助于分离和发现问题。

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

    分离原型                          Prototype to isolate

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

对问题各个击破,这样做有很多好处:通过将问题与应用其他部分隔离开,可以将关注点直接放在与问题相关的议题上;可以通过多种改变,来接近问题发生的核心——你不可能针对正在运行的系统来这样做。可以更快地发现问题的根源所在,因为只与所需最小数量的相关代码发生关系。

隔离问题不应该只在交付软件之后才着手。在构建系统原型、调试和测试时,各个击破的战略都可以起到帮助作用。

对问题各个击破

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

切身感受

面对必须要隔离的问题时,感觉就像在一个茶杯中寻找一根针,而不是大海捞针。

平衡的艺术

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

 

36.习惯36:报告所有的异常

"不要让程序的调用者看到那些奇怪的异常。处理它们是你的责任。把你调用的一切都包起来,然后发送自己定义的异常--或者干脆自己解决掉。"

从事任何编程工作,都要考虑事物正常状况下是如何运作的。不过更应该想一想,当出现问题--也就是事情没有按计划进行时,会发生什么。

在调用别人的代码时,它也许会抛异常,这时我们可以试着对其处理,并从失败中恢复。当然,要是在用户没有意识到的情况下,可以恢复并继续正常处理流程,这就最好不过了。要是不能恢复,应该让调用代码的用户知道,到底是哪里出现了问题。

不过也不尽然。Venkat曾经在使用一个非常流行的开源程序库(这里就不提它的名字了)时倍受打击。他调用的一个方法本来应该创建一个对象,可是得到的却是null引用。涉及的代码量非常少,而且没有其他代码发生联系,也很简单。所以从他自己写的这块代码的角度来看,不太可能出问题,他摸不到一点头绪。

幸好这个库是开源的,所以他下载了源代码,然后带着问题检查了相关的方法。这个方法调用了另外的方法,那个方法认为他的系统中缺少了某些必要的组件。这个底层方法抛出了带有相关信息的异常。但是,上层方法却偷偷地用没有异常处理代码的空catch代码块,把异常给忽略掉了,然后就抛出一个null。Venkat所写的代码根本不知道到底发生了什么,只有通过阅读程序库的代码,他才能明白这个问题,并最后安装了缺失的组件。

像Java中那样的检查异常会强迫你捕捉异常,或是把异常传播出去。可是有些开发人员会采取临时的做法:捕捉到异常后,为了不看到编译器的提示,就把异常忽略掉。这样做很危险--临时的补救方式很容易被遗忘,并且会进入到生产系统的代码中。必须要处理所有的异常,倘若可以,从失败中恢复再好不过。如果不能处理,就要把异常传播到方法的调用者,这样调用者就可以尝试对其进行处理了(或者以优雅的方式将问题的信息告诉给用户,见习惯37)。

听起来很明白,是吧?其实不像想象得那么容易。不久前有一条新闻,提到一套大型航空订票系统中发生了严重的问题。系统崩溃,飞机停飞,上千名旅客滞留机场,整个航空运输系统数天之内都乱作一团。原因是什么?在一台应用服务器上发生了一个未检查异常。

也许你很享受CNN新闻上提到你名字的感觉,但是你不太可能希望发生这样的情形。

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

切身感受

当出现问题时,心里知道能够得到抛出的异常。而且没有空的异常处理方法。

平衡的艺术

决定由谁来负责处理异常是设计工作的一部分。

不是所有的问题都应该抛出异常。

报告的异常应该在代码的上下文中有实际意义。在前述的例子中,抛出一个NullPointerException看起来也许不错,不过这就像抛出一个null对象一样,起不到任何帮助作用。

如果代码中会记录运行时调试日志,当捕获或是抛出异常时,都要记录日志信息;这样做对以后的跟踪工作很有帮助。

检查异常处理起来很麻烦。没人愿意调用抛出31种不同检查异常的方法。这是设计上的问题:要把它解决掉,而不是随便打个补丁就算了。

要传播不能处理的异常。

 

37.习惯37:提供有用的错误信息

“不要吓着用户,吓程序员也不行。要提供给他们干净整洁的错误信息。要使用类似‘用户错误。替换,然后继续。’这样让人舒服的词句。”

当应用发布并且在真实世界中得到使用之后,仍然会发生这样那样的问题。比如计算模块可能出错,与数据库服务器之间的连接也可能丢失。当无法满足用户需求时,要以优雅的方式进行处理。

 类似的错误发生时,是不是只要弹出一条优雅且带有歉意的信息给用户就足够了?并不尽然。当然了,显示通用的信息,告诉用户发生了问题,要好过由于系统崩溃造成应用执行错误的动作,或者直接关闭(用户会因此感到困惑,并希望知道问题所在)。然而,类似“出错了”这样的消息,无法帮助团队针对问题做出诊断。用户在给支持团队打电话报告问题时,我们希望他们提供足够多且好的信息,以帮助尽快识别问题所在。遗憾的是,用很通用的错误消息,是无法提供足够的数据的。

针对这个问题,常用的解决方案是记录日志:当发生问题时,让应用详细记录错误的相关数据。错误日志最起码应该以文本文件的形式维护。不过也许可以发布到一个系统级别的事件日志中。可以使用工具来浏览日志,产生所有日志信息的RSS feed,以及诸如此类的辅助方式。

 记录日志很有用,可是单单这样做是不够的:开发人员认真分析日志,可以得到需要的数据;但对于不幸的用户来说,起不到任何帮助作用。如果展示给他们类似下图中的信息,他们还是一点头绪都没有——不知道自己到底做错了什么,应该怎么做可以绕过这个错误,或者在给技术支持打电话时,应该报告什么。

如果你注意的话,在开发阶段就能发现这个问题的早期警告。作为开发人员,经常要将自己假定为用户来测试新功能。要是错误信息很难理解,或者无助于定位错误的话,就可以想想真正的用户和支持团队,遇到这个问题时会有多么困难了(见图7-2)。


图 7-2 无用的异常信息

 

例如,假定登录UI调用了应用的中间层,后台向数据访问层发送了一个请求。由于无法连接数据库,数据访问层抛出一个异常。这个异常被中间层用自己的异常包裹起来,并继续向上传递。那么UI层应该怎么做呢?它至少应该让用户知道发生了系统错误,而不是由用户的输入引起的。

 接下来,用户会打电话并且告诉我们他无法登录。我们怎么知道问题的实质是什么呢?日志文件可能有上百个条目,要找到相关的细节非常困难。

 实际上,不妨在显示给用户的信息中提供更多细节。好比说,可以看到是哪条SQL查询或存储过程发生了错误;这样可以很快找到问题并且修正,而不是浪费大把的时间去盲目地碰运气。不过另一方面,在生产系统中,向用户显示数据连接问题的特定信息,不会对他们有多大帮助。而且有可能吓他们一跳。

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

 下面是一种同时实现上述两个目的方式:图中显示了清晰的错误说明信息。该错误信息不只是简单的文本,还包括了一个超链接。用户、开发人员、测试人员都可以由此链接得到更多信息,如图7-3、图7-4所示。

图7-3 带有更多细节链接的异常信息

 

图 7-4 供调试用的完整详细信息

进入链接的页面,可以看到异常(以及所有嵌套异常)的详细信息。在开发时,我们可能希望只要看到这些细节就好了。不过,当应用进入生产系统后,就不能把这些底层细节直接暴露给用户了,而要提供链接,或是某些访问错误日志的入口。支持团队可以请用户点击错误信息,并读出错误日志入口的相关信息,这样支持团队可以很快找到错误日志中的特定细节。对于独立系统来说,点击链接,有可能会将错误信息通过电子邮件发送到支持部门。

除了包括出现问题的详细数据外,日志中记录的信息可能还有当时系统状态的一个快照(例如 Web 应用的会话状态)。

使用上述信息,系统支持团队可以重建发生问题的系统状态,这样对查找和修复问题非常有效。

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

展示有用的错误信息

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

区分错误类型

程序缺陷。这些是真正的bug,比如NullPointerException、缺少主键等。用户或者系统管理员对此束手无策。

环境问题。该类别包括数据库连接失败,或是无法连接远程Web Services、磁盘空间满、权限不足,以及类似的问题。程序员对此没有应对之策,但是用户也许可以找到变通的方法,如果提供足够详细的信息,系统管理员应该可以解决这些问题。

用户错误。程序员与系统管理员不必担心这些问题。在告知是哪里操作的问题后,用户可以重新来过。

通过追踪记录报告的错误类型,可以为受众提供更加合适的建议。

切身感受

错误信息有助于问题的解决。当问题发生时,可以详细研究问题的细节描述和发生上下文。

平衡的艺术

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

[①]有些安全敏感的信息不应该被暴露,甚至不可以记录到日志中去,这其中包括密码、银行账户等。


第8章 敏捷协作

 

我不仅发挥了自己的全部能力,还将我所仰仗的人的能力发挥到极致。

--伍德罗·威尔逊,美国第28任总统(1856-1924)

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

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

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

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

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

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

 

38.习惯38:安排有规律的会面时间

"会议安排得越多越好。实际上,我们要安排更多的会议,直到发现为什么工作总是完不成。"

也许你个人很讨厌开会,但是沟通是项目成功的关键。我们不只要跟客户谈话,还应该与开发人员进行良好的沟通。要知道其他人在做什么--如果Bernie知道如何解决你的问题,你肯定希望早点搞清楚她是怎么做的,不是吗?

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

Andy曾遇到一个客户,他和Dave Thomas通过电话远程参与客户的站立会议。一切都看起来很顺利,直到有一天,会议时间比平时多了一倍。你猜怎么着?客户那边,与会者都挪到了会议室,舒舒服服地坐在扶椅上开会。

坐着开的会议通常会持续更久,大部分人不喜欢站着进行长时间的谈话。

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

昨天有什么收获?

今天计划要做哪些工作?

面临着哪些障碍?

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

通常,立会都是在每个工作日的早些时候,且大家都在上班时举行。但是不要把它安排为上班后的第一件事。要让大家有机会从刚才混乱的交通状况中恢复状态,喝点咖啡,删除一些垃圾邮件什么的。要保证会议结束后有足够的时间,让大家在午餐之前做不少工作,同时也不要开始得过早,让每个人都巴不得赶紧结束会议,去喝点东西。一般来说,在大家到公司之后的半个小时到一个小时之内举行,是个不错的选择。

猪与鸡

Scrum将团队成员与非团队成员这两种角色命名为猪和鸡。团队成员是猪(自尊何在啊),非团队成员(管理层、支持人员、QA等)是鸡。这两个用语来自一个寓言,讲的是农场里的动物们打算一起开饭店,并且准备用熏肉和鸡蛋作为早餐提供。对于鸡来说,当然是要参与进来了,可对于猪来讲,可就是放血投入了。

只有"猪"才允许参与Scrum的每日立会。

参加会议的人要遵守一些规则,以保证彼此不会分神,而且会议也不会跑题。这些规则有:只有团队成员--开发人员、产品所有者和协调者可以发言(查看上面对"猪"和"鸡"的描述)。他们必须回答上面的3个问题,而且不能展开深入讨论(讨论可以安排在会后进行)。管理层可以把要解决的问题记下来,但是不能试图将会议从每个人要回答的三个问题引开。

每日立会有诸多好处。

让大家尽快投入到一天的工作中来。

如果某个开发人员在某一点上有问题,他可以趁此机会将问题公开,并积极寻求帮助。

帮助团队带头人或管理层了解哪些领域需要更多的帮助,并重新分配人手。

让团队成员知道项目其他部分的进展情况。

帮助团队识别是否在某些东西上有重复劳动而耗费了精力,或者是不是某个问题有人已有现成的解决方案。

通过促进代码和思路的共享,来提升开发速度。

鼓励向前的动力:看到别人报告的进度都在前进,会对彼此形成激励。

使用厨房计时器

开发者Nancy Davis告诉我们她使用厨房计时器召开立会的经验。

"我们使用了妹妹去年圣诞节送给我的一个厨房计时器。它在运行时不会发出'嘀哒'的声音,只会在时间到达后发出'叮'的一声。如果计时器停止了,我们就再加两分钟,并让下一个成员发言。有时会忘掉计时器的存在,并让会议持续需要的时间,但是大部分情况下,我们都会遵守计时器的提醒。"

采取立会的形式需要管理层的承诺和参与。不过,团队中的开发人员可以帮助推行这个实践。如果开发人员无法说服管理层的参与,他们自己可以用非正式的形式召开立会。

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

切身感受

大家都盼望着立会。希望彼此了解各自的进度和手上的工作,而且不怕把各自遇到的问题拿出来公开讨论。

平衡的艺术

会议会占用开发时间,所以要尽量保证投入的时间有较大的产出。立会的时间最长不能超出30分钟,10~15分钟比较理想。

如果要使用需提前预定的会议室,就把预定的时间设定为一个小时吧。这样就有机会在15分钟的立会结束后,马上召开更小规模的会议。

虽然大多数团队需要每天都碰头,但对于小型团队来说,这样做可能有点过头了。不妨两天举行一次,或者一周两次,这对小团队来说足够了。

要注意报告的细节。在会议中要给出具体的进度,但是不要陷入细节之中。例如,"我在开发登录页面"就不够详细。"登录页面目前接受guest/guest作为登录用户名和密码,我明天会连接数据库来做登录验证",这样的详细程度才行。

迅速地开始可以保证会议短小。不要浪费时间等着会议开始。

如果觉得立会是在浪费时间,那可能是大家还没有形成真正的团队意识。这并不是坏事,有利于针对问题进行改进。

 

39.习惯39:架构师必须写代码

“我们的专家级架构师Fred会提供设计好的架构,供你编写代码。他经验丰富,拿的薪水很高,所以不要用一些愚蠢的问题或者实现上的难点,来浪费他的时间。”

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

不可能在PowerPoint 幻灯片中进行编程                You can’t code in PowerPoint

这些架构师通常在项目开始时介入,绘制各种各样的设计图,然后在重要的代码实现开始之前离开。有太多这种“PowerPoint 架构师”了,由于得不到反馈,他们的架构设计工作也不会有很好的收效。

 一个设计要解决眼前面临的特定问题,随着设计的实现,对问题的理解也会发生改变。想在开始实现之前,就做出一个很有效的详细设计是非常困难的(见第48页上的实践11)。因为没有足够的上下文,能得到的反馈也很少,甚至没有。设计会随着时间而演进,如果忽略了应用的现状(它的具体实现),要想设计一个新的功能,或者完成某个功能的提升是不可能的。

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

这就像是尝试仅仅通过查看地图来指挥一场战役——一旦开打,仅有计划是不够的。战略上的决策也许可以在后方进行,但是战术决策——影响成败的决策——需要对战场状况的明确了解。

可 逆 性

“程序员修炼之道”丛书中指出不存在所谓的最终决策 。没有哪个决策做出之后,就是板上钉钉了。实际上,就时间性来看,不妨把每个重要的决策,都看作沙上堆砌的城堡,它们都是在变化之前所做出的预先规划。

新系统的设计者  Donald E. Knuth

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

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

有一句泰米尔谚语说:“只有一张蔬菜图无法做出好的咖喱菜。”与之类似,纸上的设计也无法产生优秀的应用。设计应该被原型化,经过测试,当然还有验证——它是要进化的。实现可用的设计,这是设计者或者说架构师的责任。

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

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

优秀的设计从积极的程序员那里开始演化

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

切身感受

架构、设计、编码和测试,这些工作给人的感觉就像是同一个活动——开发——的不同方面。感觉它们彼此之间应该是不可分割的。

平衡的艺术

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

 

40.习惯40:实行代码集体所有制

"不用担心那个烦人的bug,Joe下周假期结束回来后会把它解决掉的。在此之前先想个权宜之计应付一下吧。"

任何具备一定规模的应用,都需要多人协作进行开发。在这种状况下,不应该像国家宣称对领土的所有权一样,声明个人对代码的所有权。任何一位团队成员,只要理解某段代码的来龙去脉,就应该可以对其进行处理。如果某一段代码只有一位开发人员能够处理,项目的风险无形中也就增加了。

相比找出谁的主意最好、谁的代码实现很烂而言,解决问题,并让应用满足用户的期望要更为重要。

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

在团队中实行任务轮换制,让每个成员都可以接触到不同部分的代码,可以提升团队整体的知识和专业技能。当Joe接过Sally的代码,他可以对其进行重构,消除待处理的问题。在试图理解代码的时候,他会问些有用的问题,尽早开始对问题领域的深入理解。

另一方面,知道别人将会接过自己的代码,就意味着自己要更守规矩。当知道别人在注意时,一定会更加小心。

可能有人会说,如果一个开发者专门应对某一个领域中的任务,他就可以精通该领域,并让后续的开发任务更加高效。这没错,但是眼光放长远一点,有好几双眼睛盯着某一段代码,是一定可以带来好处的。这样可以提升代码的整体质量,使其易于维护和理解,并降低出错率。

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

切身感受

项目中绝大部分的代码都可以轻松应对。

平衡的艺术

不要无意间丧失了团队的专家技能。如果某个开发人员在某个领域中极其精通,不妨让他作为这方面的驻留专家,而且系统的其他部分代码也对他开放,这样对团队和项目都很有帮助。

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

开发人员不必了解项目每一部分的每个细节,但是也不能因为要处理某个模块的代码而感到惊恐。

有些场合是不能采用代码集体所有制的。也许代码需要某些特定的知识、对特定问题域的了解,比如一个高难度的实时控制系统。这些时候,人多了反而容易误事。

任何人都可能遭遇到诸如车祸等突发的灾难事故,或者有可能被竞争对手雇佣。如果不向整个团队分享知识,反而增加了丧失知识的风险。

 

41.习惯41:成为指导者

"你花费了大量的时间和精力,才达到目前的水平。对别人要有所保留,这样让你看起来更有水平。让队友对你超群的技能感到恐惧吧。"

我们有时会发现自己在某些方面,比其他团队成员知道得更多。那要怎么对待这种新发现的"权威地位"呢?当然,可以用它来质疑别人,取笑他人做出的决策和开发的代码--有些人就是这样做的。不过,我们可以共享自己的知识,让身边的人变得更好。

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

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

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

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

成为指导者,并不意味着要手把手教团队成员怎么做(见第160页习惯42),也不是说要在白板前进行讲座,或是开展小测验什么的,可以在进行自备午餐会时展开讨论。多数时候,成为指导者,是指在帮助团队成员提升水平的同时也提高自己。

这个过程不必局限于自己的团队。可以开设个人博客,贴一些代码和技术在上面。不一定是多么伟大的项目,即使是一小段代码和解释,对别人也可能是有帮助的。

成为指导者意味着要分享--而不是固守--自己的知识、经验和体会。意味着要对别人的所学和工作感兴趣,同时愿意为团队增加价值。一切都是为了提高队友和你的能力与水平,而不是为了毁掉团队。

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

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

切身感受

你会感到给予别人教导,也是提升自己学识的一种方式,并且其他人亦开始相信你可以帮助他们。

平衡的艺术

如果一直在就同一个主题向不同的人反复阐述,不妨记录笔记,此后就此主题写一篇文章,甚至是一本书。

成为指导者是向团队进行投资的一种极佳的方式。(见第31页习惯6。)

结对编程(见第165页习惯44)是一种进行高效指导的、很自然的环境。

如果总是被一些懒于自己寻找答案的人打扰(查看下一页习惯42)。

为团队成员在寻求帮助之前陷入某个问题的时间设定一个时限,一个小时应该是不错的选择。

 

42.习惯42:允许大家自己想办法

“你这么聪明,直接把干净利落的解决方案告诉团队其他人就好了。不用浪费时间告诉他们为什么这样做。”

“授人以鱼,三餐之需;授人以渔,终生之用。”告诉团队成员解决问题的方法,也要让他们知道如何解决问题的思路,这也是成为指导者的一部分。

     了解上个实践——成为指导者——之后,也许有人会倾向于直接给同事一个答案,以继续完成工作任务。要是只提供一些指引给他们,让他们自己想办法找到答案,又会如何?

 这并不是多么麻烦的事情;不要直接给出像“42”这样的答案,应该问你的队友:“你有没有查看在事务管理者与应用的锁处理程序之间的交互关系?”

     这样做有下面几点好处。

  • 你在帮助他们学会如何解决问题。
  • 除了答案之外,他们可以学到更多东西。
  • 他们不会再就类似的问题反复问你。
  • 这样做,可以帮助他们在你不能回答问题时自己想办法。
  • 他们可能想出你没有考虑到的解决方法或者主意。这是最有趣的——你也可以学到新东西。

如果有人还是没有任何线索,那就给更多提示吧(或者甚至是答案)。如果有人提出来某些想法,不妨帮他们分析每种想法的优劣之处。如果有人给出的答案或解决方法更好,那就从中汲取经验,然后分享你的体会吧。这对双方来说都是极佳的学习经验。

作为指导者,应该鼓励、引领大家思考如何解决问题。前面提到过亚里士多德的话:“接纳别人的想法,而不是盲目接受,这是受过教育的头脑的标志。”应该接纳别人的想法和看问题的角度,在这个过程中,自己的头脑也得到了拓展。

如果整个团队都能够采纳这样的态度,可以发现团队的知识资本有快速的提升,而且将会完成一些极其出色的工作成果。

给别人解决问题的机会

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

 

切身感受

感觉不是在以填鸭式的方式给予别人帮助。不是有意掩饰,更非讳莫如深,而是带领大家找到自己的解决方案。

平衡的艺术

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

 

43.习惯43:准备好后再共享代码

"别管是不是达到代码签入的要求,要尽可能频繁地提交代码,特别是在要下班的时候。"

让你猜个谜语:相对不使用版本控制系统,更坏的状况是什么?答案是:错误地使用了版本控制系统。使用版本控制系统的方式,会影响生产力、产品稳定性、产品质量和开发日程。特别地,诸如代码提交频率这样简单的东西都会有很大影响。

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

很明显,每周或每月一次提交代码,并不是令人满意的做法--这样源代码控制系统就不能发挥其作用了。也许总有种种原因来为这种懒散的做法解释。有人说开发人员是采取异地开发(off-site)或离岸开发(offshore)的方式,访问源代码控制系统的速度很慢。这就是环境黏性(environmental viscosity)的例子--把事情做糟要比做好更容易。很明显,这是一个亟待解决的简单技术问题。

另一方面,如果在任务完成之前就提交代码又会如何?也许你正在开发一些至关重要的代码,而且你想在下班回家晚饭之后再继续开发。要想在家里得到代码,最简单的方式就是将其提交到源代码控制系统,到家之后再把代码签出。

向代码库中提交仍在开发的代码,会带来很多风险。这些代码可能还有编译错误,或者对其所做的某些变化与系统其他部分的代码不兼容。当其他开发者获取最新版本的代码时,也会受到这些代码的影响。

通常情况下,提交的文件应该与一个特定的任务或是一个bug的解决相关。而且应该是同时提交相关的文件,并注有日志信息,将来也能够知道修改了哪些地方,以及为什么要做修改。一旦需要对变更采取回滚操作,这种"原子"提交也是有帮助的。

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

代码不执行提交操作的其他安全选择

如果需要将尚未完成的源代码传输或是保存起来,有如下选择。

使用远程访问。将代码留在工作地点,然后在家里使用远程访问获取,而不是将完成了一半的代码提交,再从家里签出。

随身携带。将代码复制到U盘、CD或DVD中,以达到异地开发的目的。

使用带有底座扩展的笔记本电脑。如果是由于在多台电脑上开发造成的延续性问题,不妨考虑使用带有底座扩展的笔记本电脑,这样就可以带着代码到处走了。

使用源代码控制系统的特性。Microsoft Visual Team System 2005有一个"shelving"特性,因为有些产品的某些代码在提交之前,需要被其他部分调用。在CVS和Subversion中,可以将尚未允许合并到主干的代码,设定为开发者的分支(查看[TH03]和[Mas05])。

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

切身感受

感觉好像整个团队就在源代码控制系统的另一端盯着你。要知道一旦提交代码,别人就都可以访问了。

平衡的艺术

有些源代码控制系统会区分"提交"和"可公开访问"两种代码权限。此时,可以进行临时的提交操作(比如在工作地点和家之间来回奔波时),不会因为完全提交未完成的代码,而让团队的其他成员感到郁闷。

有些人希望代码在提交之前可以进行复查操作。只要不会过久拖延提交代码的时间就没有问题。如果流程的某个部分产生了拖延,那就修正流程吧。

仍然应该频繁提交代码。不能用"代码尚未完成"作为避免提交代码的借口。

 

44.习惯44:做代码复查

"用户是最好的测试人员。别担心--如果哪里出错了,他们会告诉我们的。"

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

代码复查和缺陷移除

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

--Capers Jones的《估算软件成本》[Jon98]

正如Capers Jones指出的,代码复查或许是找到并解决问题的最佳方式。然而,有时很难说服管理层和开发人员使用它来完成开发工作。

管理层担心进行代码复查所耗费的时间。他们不希望团队停止编码,而去参加长时间的代码复查会议。开发人员对代码复查感到担心,允许别人看他们的代码,会让他们有受威胁的感觉。这影响了他们的自尊心。他们担心在情感上受到打击。

作者参与过的项目中,只要实施了代码复查,其成果都是非常显著的。

Venkat最近参与了一个日程安排非常紧凑的项目,团队不少成员都是没有多少经验的开发者。通过严格的代码复查过程,他们可以提交质量极高而且稳定的代码。当开发人员完成某项任务的编码和测试后,在签入源代码控制系统之前,会有另一名开发人员对代码做彻底的复查。

这个过程修复了很多问题。噢,代码复查不只针对初级开发者编写的代码--团队中每个开发人员的代码都应该进行复查,无论其经验丰富与否。

那该如何进行代码复查呢?可以从下面这些不同的基本方式中进行选择。

通宵复查。可以将整个团队召集在一起,预定好美食,每个月进行一次"恐怖的代码复查之夜"。但这可能不是进行代码复查最有效的方式(而且听起来也不太敏捷)。大规模团队的复查会议很容易陷入无休止的讨论之中。大范围的复查不仅没有必要,而且有可能对整个流程造成损害。我们不建议这种方式。

捡拾游戏。当某些代码编写完成、通过编译、完成测试,并已经准备签入时,其他开发人员就可以"捡拾"起这些代码开始复查。类似的"提交复查"是一种快速而非正式的方式,保证代码在提交之前是可以被接受的。为了消除行为上的惯性,要在开发人员之间进行轮换。比如,如果Joey的代码上次是由Jane复查的,这次不妨让Mark来复查。这是一种很有效的技术。

结对编程。在极限编程中,不存在一个人独立进行编码的情况。编程总是成对进行的:一个人在键盘旁边(担任司机的角色),另一个人坐在后面担任导航员。他们会不时变换角色。有第二双眼睛在旁边盯着,就像是在进行持续的代码复查活动,也就不必安排单独的特定复查时间了。

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

代码能否被读懂和理解?

是否有任何明显的错误?

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

是否存在重复的代码(在复查的这部分代码中,或是在系统的其他部分代码)?

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

此外,还可以考虑使用诸如Similarity Analyzer或Jester这样的代码分析工具。如果这些工具产生的静态分析结果对项目有帮助,就把它们集成到持续构建中去吧。

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

切身感受

代码复查随着开发活动持续进行,而且每次针对的代码量相对较少。感觉复查活动就像是项目正在进行的一部分,而不是一种令人畏惧的事情。

平衡的艺术

不进行思考、类似于橡皮图章一样的代码复查没有任何价值。

代码复查需要积极评估代码的设计和清晰程度,而不只是考量变量名和代码格式是否符合组织的标准。

同样的功能,不同开发人员的代码实现可能不同。差异并不意味着不好。除非你可以让某段代码明确变得更好,否则不要随意批评别人的代码。

如果不及时跟进讨论中给出的建议,代码复查是没有实际价值的。可以安排跟进会议,或者使用代码标记系统,来标识需要完成的工作,跟踪已经处理完的部分。

要确保代码复查参与人员得到每次复查活动的反馈。作为结果,要让每个人知道复查完成后所采取的行动。

 

45.习惯45:及时通报进展与问题

“管理层、项目团队以及业务所有方,都仰仗你来完成任务。如果他们想知道进展状况,会主动找你要的。还是埋头继续做事吧。”

 接受一个任务,也就意味着做出了要准时交付的承诺。不过,遇到各种问题从而导致延迟,这种情形并不少见。截止日期来临,大家都等着你在演示会议上展示工作成果。如果你到会后通知大家工作还没有完成,会有什么后果?除了感到窘迫,这对你的事业发展也没有什么好处。

如果等到截止时间才发布坏消息,就等于是为经理和技术主管提供了对你进行微观管理(micromanagement)的机会。他们会担心你再次让他们失望,并开始每天多次检查你的工作进度。你的生活就开始变得像呆伯特的漫画一样了。

 假定现在你手上有一个进行了一半的任务,由于技术上的难题,看起来不能准时完成了。如果这时积极通知其他相关各方,就等于给机会让他们提前找出解决问题的方案。也许他们可以向另外的开发人员寻求帮助,也许他们可以将工作重新分配给更加熟悉相关技术的人,也许他们可以提供更多需要的资源,或者调整目前这个迭代中要完成的工作范围。客户会愿意将这个任务用其他同等重要的任务进行交换的。

 及时通报进展与问题,有情况发生时,就不会让别人感到突然,而且他们也很愿意了解目前的进展状况。他们会知道何时应提供帮助,而且你也获得了他们的信任。

 发送电子邮件,用即时贴传递信息,或快速电话通知,这都是通报大家的传统方式。还可以使用Alistair Cockburn提出的“信息辐射器”。[①]信息辐射器类似于墙上的海报,提供变更的信息。路人可以很方便地了解其中的内容。以推送的方式传递信息,他们就不必再来问问题了。信息辐射器中可以展示目前的任务进度,和团队、管理层或客户可能会感兴趣的其他内容。

 也可以使用海报、网站、Wiki、博客或者RSS。只要让人们可以有规律地查看到需要的信息,这就可以了。

 整个团队可以使用信息辐射器来发布他们的状态、代码设计、研究出的好点子等内容。现在只要绕着团队的工作区走一圈,就可以学到不少新东西,而且管理层也就可以知道目前的状况如何了。

 

及时通报进展与问题

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

切身感受

当经理或同事来询问工作进展、最新的设计,或研究状况时,不会感到头痛。

平衡的艺术

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

 


[①]查看 http://c2.com/cgi-bin/wiki?InformationRadiator。


第9章 尾声:走向敏捷
 9.1 只要一个新的习惯
 9.2 拯救濒临失败的项目
 9.3 引入敏捷:管理者指南
 9.4 引入敏捷:程序员指南
 9.5 结束了吗


附录A 资源


索引