1 我的源码让猫给吃了
注重实效的程序员的特征是什么?我们觉得是他们处理问题、寻求解决方案时的态度、风格、哲学。他们能够越出直接的问题去思考,总是设法把问题放在更大的语境中,总是设法注意更大的图景。毕竟,没有这样的更大的语境,你又怎能注重实效?你又怎能做出明智的妥协和有见识的决策?
他们成功的另一关键是他们对他们所做的每件事情负责,关于这一点,我们将在“我的源码让猫给吃了”中加以讨论。因为负责,
注重实效的程序员不会坐视他们的项目土崩瓦解。在“软件的熵”中,我们将告诉你怎样使你的项目保持整洁。
大多数人发现自己很难接受变化,有时是出于好的理由,有时只是因为固有的惰性。在“石头汤与煮青蛙”中,我们将考察一种促成变化的策略,并(出于对平衡的兴趣)讲述一个忽视渐变危险的两栖动物的警世传说。
理解你的工作的语境的好处之一是,了解你的软件必须有多好变得更容易了。有时接近完美是惟一的选择,但常常会涉及各种权衡。我们将在“足够好的软件”中探究这一问题。
当然,你需要拥有广泛的知识和经验基础才能赢得这一切。学习是一个持续不断的过程。在“你的知识资产”中,我们将讨论一些策略,让你“开足马力”。
最后,我们没有人生活在真空中。我们都要花大量时间与他人打交道。在“交流!”中列出了能让我们更好地做到这一点的几种途径。
注重实效的编程源于注重实效的思考的哲学。本章将为这种哲学设立基础。
1 我的源码让猫给吃了
在所有弱点中,最大的弱点就是害怕暴露弱点。
——J. B. Bossuet, Politics from Holy Writ, 1709
依据你的职业发展、你的项目和你每天的工作,为你自己和你的行为负责这样一种观念,是注重实效的哲学的一块基石。
注重实效的程序员对他或她自己的职业生涯负责,并且不害怕承认无知或错误。这肯定并非是编程最令人愉悦的方面,但它肯定会发生——即使是在最好的项目中。尽管有彻底的测试、良好的文档以及足够的自动化,事情还是会出错。交付晚了,出现了未曾预见到的技术问题。
发生这样的事情,我们要设法尽可能职业地处理它们。这意味着诚实和坦率。我们可以为我们的能力自豪,但对于我们的缺点——还有我们的无知和我们的错误——我们必须诚实。
负责
责任是你主动担负的东西。你承诺确保某件事情正确完成,但你不一定能直接控制事情的每一个方面。除了尽你所能以外,你必须分析风险是否超出了你的控制。对于不可能做到的事情或是风险太大的事情,你有权不去为之负责。你必须基于你自己的道德准则和判断来做出决定。
如果你确实同意要为某个结果负责,你就应切实负起责任。当你犯错误(就如同我们所有人都会犯错误一样)、或是判断失误时,诚实地承认它,并设法给出各种选择。不要责备别人或别的东西,或是拼凑借口。不要把所有问题都归咎于供应商、编程语言、管理部门、或是你的同事。也许他(它)们全体或是某几方在其中扮演了某种角色,但你可以选择提供解决方案,而非寻找借口。
如果存在供应商不能按时供货的风险,你应该预先制定一份应急计划。如果磁盘垮了——带走了你的所有源码——而你没有做备份,那是你的错。告诉你的老板“我的源码让猫给吃了”也无法改变这一点。
提示3
|
|
Provide Options, Don’t Make Lame Excuses
提供各种选择,不要找蹩脚的借口
|
在你走向任何人、告诉他们为何某事做不到、为何耽搁、为何出问题之前,先停下来,听一听你心里的声音。与你的显示器上的橡皮鸭交谈,或是与猫交谈。你的辩解听起来合理,还是愚蠢?在你老板听来又是怎样?
在你的头脑里把谈话预演一遍。其他人可能会说什么?他们是否会问:“你试了这个吗……”,或是“你没有考虑那个吗?”你将怎样回答?在你去告诉他们坏消息之前,是否还有其他你可以再试一试的办法?有时,你其实知道他们会说什么,所以还是不要给他们添麻烦吧。
要提供各种选择,而不是找借口。不要说事情做不到;要说明能够做什么来挽回局面。必须把代码扔掉?给他们讲授重构的价值(参见重构,184页)。你要花时间建立原型(prototyping),以确定最好的继续前进的方式(参见原型与便笺,53页)?你要引入更好的测试(参见易于测试的代码,189页;以及无情的测试,237页)或自动化(参见无处不在的自动化,230页),以防止问题再度发生?又或许你需要额外的资源。不要害怕提出要求,也不要害怕承认你需要帮助。
在你大声说出它们之前,先设法把蹩脚的借口清除出去。如果你必须说,就先对你的猫说。反正,如果小蒂德尔丝(Tiddles,BBC在1969~1974年播出的喜剧节目“Monty Python's Flying Circus”中的著名小母猫——译注)要承受指责……
相关内容:
l 原型与便笺,53页
l 重构,184页
l 易于测试的代码,189页
l 无处不在的自动化,230页
l 无情的测试,237页
挑战
l 如果有人——比如银行柜台职员、汽车修理工或是店员——对你说蹩脚的借口,你会怎样反应?结果你会怎样想他们和他们的公司?
2 软件的熵
尽管软件开发几乎不受任何物理定律的约束,熵(entropy)对我们的影响却很大。熵是一个来自物理学的概念,指的是某个系统中的“无序”的总量。遗憾的是,热力学定律保证了宇宙中的熵倾向于最大化。当软件中的无序增长时,程序员们称之为“软件腐烂”(software rot)。
有许多因素可以促生软件腐烂。其中最重要的一个似乎是开发项目时的心理(或文化)。即使你的团队只有你一个人,你开发项目时的心理也可能是非常微妙的事情。尽管制定了最好的计划,拥有最好的开发者,项目在其生命期中仍可能遭遇毁灭和衰败。而另外有一些项目,尽管遇到巨大的困难和接连而来的挫折,却成功地击败自然的无序倾向,设法取得了相当好的结果。
是什么造成了这样的差异?
在市区,有些建筑漂亮而整洁,而另一些却是破败不堪的“废弃船只”。为什么?犯罪和城市衰退领域的研究者发现了一种迷人的触发机制,一种能够很快将整洁、完整和有人居住的建筑变为破败的废弃物的机制[WK82]。
破窗户。
一扇破窗户,只要有那么一段时间不修理,就会渐渐给建筑的居民带来一种废弃感——一种职权部门不关心这座建筑的感觉。于是又一扇窗户破了。人们开始乱扔垃圾。出现了乱涂乱画。严重的结构损坏开始了。在相对较短的一段时间里,建筑就被损毁得超出了业主愿意修理的程度,而废弃感变成了现实。
“破窗户理论”启发了纽约和其他大城市的警察部门,他们对一些轻微的案件严加处理,以防止大案的发生。这起了作用:管束破窗户、乱涂乱画和其他轻微违法事件减少了严重罪案的发生。
提示4
|
|
Don’t Live with Broken Windows
不要容忍破窗户
|
不要留着“破窗户”(低劣的设计、错误决策、或是糟糕的代码)不修。发现一个就修一个。如果没有足够的时间进行适当的修理,就用木板把它钉起来。或许你可以把出问题的代码放入注释(comment out),或是显示“未实现”消息,或是用虚设的数据(dummy data)加以替代。采取某种行动防止进一步的损坏,并说明情势处在你的控制之下。
我们看到过整洁、运行良好的系统,一旦窗户开始破裂,就相当迅速地恶化。还有其他一些因素能够促生软件腐烂,我们将在别处探讨它们,但与其他任何因素相比,置之不理都会更快地加速腐烂的进程。
你也许在想,没有人有时间到处清理项目的所有碎玻璃。如果你继续这么想,你就最好计划找一个大型垃圾罐,或是搬到别处去。不要让熵赢得胜利。
灭火
作为对照,让我们讲述Andy的一个熟人的故事。他是一个富得让人讨厌的富翁,拥有一所完美、漂亮的房子,里面满是无价的古董、艺术品,以及诸如此类的东西。有一天,一幅挂毯挂得离他的卧室壁炉太近了一点,着了火。消防人员冲进来救火——和他的房子。但他们拖着粗大、肮脏的消防水管冲到房间门口却停住了——火在咆哮——他们要在前门和着火处之间铺上垫子。
他们不想弄脏地毯。
这的确是一个极端的事例,但我们必须以这样的方式对待软件。一扇破窗户——一段设计低劣的代码、团队必须在整个项目开发过程中加以忍受的一项糟糕的管理决策——就足以使项目开始衰败。如果你发现自己在有好些破窗户的项目里工作,会很容易产生这样的想法:“这些代码的其余部分也是垃圾,我只要照着做就行了。”项目在这之前是否一直很好,并没有什么关系。在最初得出“破窗户理论”的一项实验中,一辆废弃的轿车放了一个星期,无人理睬。而一旦有一扇窗户被打破,数小时之内车上的设备就被抢夺一空,车也被翻了个底朝天。
按照同样的道理,如果你发现你所在团队和项目的代码十分漂亮——编写整洁、设计良好,并且很优雅——你就很可能会格外注意不去把它弄脏,就和那些消防员一样。即使有火在咆哮(最后期限、发布日期、会展演示,等等),你也不会想成为第一个弄脏东西的人。
相关内容:
l 石头汤与煮青蛙,7页
l 重构,184页
l 注重实效的团队,224页
挑战
l 通过调查你周边的计算“环境”,帮助增强你的团队的能力。选择两或三扇“破窗户”,并与你的同事讨论问题何在,以及怎样修理它们。
l 你能否说出某扇窗户是何时破的?你的反应是什么?如果它是他人的决策所致,或者是管理部门的指示,你能做些什么?
3 石头汤与煮青蛙
三个士兵从战场返回家乡,在路上饿了。他们看见前面有村庄,就来了精神——他们相信村民会给他们一顿饭吃。但当他们到达那里,却发现门锁着,窗户也关着。经历了多年战乱,村民们粮食匮乏,并把他们有的一点粮食藏了起来。
士兵们并未气馁,他们煮开一锅水,小心地把三块石头放进去。吃惊的村民们走出来望着他们。
“这是石头汤。”士兵们解释说。“就放石头吗?”村民们问。“一点没错——但有人说加一些胡萝卜味道更好……”一个村民跑开了,又很快带着他储藏的一篮胡萝卜跑回来。
几分钟之后,村民们又问:“就是这些了吗?”
“哦,”士兵们说:“几个土豆会让汤更实在。”又一个村民跑开了。
接下来的一小时,士兵们列举了更多让汤更鲜美的配料:牛肉、韭菜、盐,还有香菜。每次都会有一个不同的村民跑回去搜寻自己的私人储藏品。
最后他们煮出了一大锅热气腾腾的汤。士兵们拿掉石头,和所有村民一起享用了一顿美餐,这是几个月以来他们所有人第一次吃饱饭。
在石头汤的故事里有两层寓意。士兵戏弄了村民,他们利用村民的好奇,从他们那里弄到了食物。但更重要的是,士兵充当催化剂,把村民团结起来,和他们一起做到了他们自己本来做不到的事情——一项协作的成果。最后每个人都是赢家。
你常常也可以效仿这些士兵。
在有些情况下,你也许确切地知道需要做什么,以及怎样去做。整个系统就在你的眼前——你知道它是对的。但请求许可去处理整个事情,你会遇到拖延和漠然。大家要设立委员会,预算需要批准,事情会变得复杂化。每个人都会护卫他们自己的资源。有时候,这叫做“启动杂役”(start-up fatigue)。
这正是拿出石头的时候。设计出你可以合理要求的东西,好好开发它。一旦完成,就拿给大家看,让他们大吃一惊。然后说:“要是我们增加……可能就会更好。”假装那并不重要。坐回椅子上,等着他们开始要你增加你本来就想要的功能。人们发现,参与正在发生的成功要更容易。让他们瞥见未来,你就能让他们聚集在你周围。
提示5
|
|
Be a Catalyst for Change
做变化的催化剂
|
村民的角度
另一方面,石头汤的故事也是关于温和而渐进的欺骗的故事。它讲述的是过于集中的注意力。村民想着石头,忘了世界的其余部分。我们都是这样,每一天。事情会悄悄爬到我们身上。
我们都看见过这样的症状。项目慢慢地、不可改变地完全失去控制。大多数软件灾难都是从微不足道的小事情开始的,大多数项目的拖延都是一天一天发生的。系统一个特性一个特性地偏离其规范,一个又一个的补丁被打到某段代码上,直到最初的代码一点没有留下。常常是小事情的累积破坏了士气和团队。
提示6
|
|
Remember the Big Picture
记住大图景
|
我们没有做过这个——真的,但有人说,如果你抓一只青蛙放进沸水里,它会一下子跳出来。但是,如果你把青蛙放进冷水里,然后慢慢加热,青蛙不会注意到温度的缓慢变化,会呆在锅里,直到被煮熟。
注意,青蛙的问题与第2节讨论的破窗户问题不同。在破窗户理论中,人们失去与熵战斗的意愿,是因为他们觉察到没有人会在意。而青蛙只是没有注意到变化。
不要像青蛙一样。留心大图景。要持续不断地观察周围发生的事情,而不只是你自己在做的事情。
相关内容:
l 软件的熵,4页
l 靠巧合编程,172页
l 重构,184页
l 需求之坑,202页
l 注重实效的团队,224页
挑战
l 在评阅本书的草稿时,John Lakos提出这样一个问题:士兵渐进地欺骗村民,但他们所催生的变化对村民完全有利。但是,渐进地欺骗青蛙,你是在加害于它。当你设法催生变化时,你能否确定你是在做石头汤还是青蛙汤?决策是主观的还是客观的?
4 足够好的软件
欲求更好,常把好事变糟。
——李尔王 1.4
有一个(有点)老的笑话,说一家美国公司向一家日本制造商订购100 000片集成电路。规格说明中有次品率:10 000片中只能有1片。几周过后订货到了:一个大盒子,里面装有数千片IC,还有一个小盒子,里面只装有10片IC。在小盒子上有一个标签,上面写着:“这些是次品”。
要是我们真的能这样控制质量就好了。但现实世界不会让我们制作出十分完美的产品,特别是不会有无错的软件。时间、技术和急躁都在合谋反对我们。
但是,这并不一定就让人气馁。如Ed Yourdon发表在
IEEE Software上的一篇文章[You95]所描述的,你可以训练你自己,编写出足够好的软件——对你的用户、对未来的维护者、对你自己内心的安宁来说足够好。你会发现,你变得更多产,而你的用户也会更加高兴。你也许还会发现,因为“孵化期”更短,你的程序实际上更好了。
在继续前进之前,我们需要对我们将要说的话进行限定。短语“足够好”并非意味着不整洁或制作糟糕的代码。所有系统都必须满足其用户的需求,才能取得成功。我们只是在宣扬,应该给用户以机会,让他们参与决定你所制作的东西何时已足够好。
让你的用户参与权衡
通常你是为别人编写软件。你常常需要记得从他们那里获取需求[2]。但你是否常问他们,他们想要他们的软件有多好?有时候选择并不存在。如果你的工作对象是心脏起搏器、航天飞机、或是将被广泛传播的底层库,需求就会更苛刻,你的选择就更有限。但是,如果你的工作对象是全新的产品,你就会有不同的约束。市场人员有需要信守的承诺,最终用户也许已基于交付时间表制定了各种计划,而你的公司肯定有现金流方面的约束。无视这些用户的需求,一味地给程序增加新特性,或是一次又一次润饰代码,这不是有职业素养的做法。我们不是在提倡慌张:许诺不可能兑现的时间标度(time scale),为赶上最后期限而削减基本的工程内容,这些同样不是有职业素养的做法。
你所制作的系统的范围和质量应该作为系统需求的一部分规定下来。
提示7
|
|
Make Quality a Requirements Issue
使质量成为需求问题
|
你常常会处在须要进行权衡的情形中。让人惊奇的是,许多用户宁愿在今天用上有一些“毛边”的软件,也不愿等待一年后的多媒体版本。许多预算吃紧的IT部门都会同意这样的说法。今天的了不起的软件常常比明天的完美软件更可取。如果你给用户某样东西,让他们及早使用,他们的反馈常常会把你引向更好的最终解决方案(参见曳光弹,48页)。
知道何时止步
在某些方面,编程就像是绘画。你从空白的画布和某些基本原材料开始,通过知识、艺术和技艺的结合去确定用前者做些什么。你勾画出全景,绘制背景,然后填入各种细节。你不时后退一步,用批判的眼光观察你的作品。常常,你会扔掉画布,重新再来。
但艺术家们会告诉你,如果你不懂得应何时止步,所有的辛苦劳作就会遭到毁坏。如果你一层又一层、细节复细节地叠加,绘画就会迷失在绘制之中。
不要因为过度修饰和过于求精而毁损完好的程序。继续前进,让你的代码凭着自己的质量站立一会儿。它也许不完美,但不用担心:它不可能完美(在第6章,171页,我们将讨论在不完美的世界上开发代码的哲学)。
相关内容:
l 曳光弹,48页
l 需求之坑,202页
l 注重实效的团队,224页
l 极大的期待,255页
挑战
l 考察你使用的软件工具和操作系统的制造商。你能否发现证据,表明这些公司安于发布他们知道不完美的软件吗?作为用户,你是会(1)等着他们清除所有bug,(2)拥有复杂的软件,并接受某些bug,还是会(3)选择缺陷较少的更简单的软件?
l 考虑模块化对软件交付的影响。与以模块化方式设计的系统相比,整体式(monolithic)软件要达到所需质量,花费的时间更多还是更少?你能找到一个商业案例吗?
5 你的知识资产
知识上的投资总能得到最好的回报。
——本杰明·富兰克林
噢,好样的老富兰克林——从不会想不出精练的说教。为什么,如果我们能够早睡早起,我们就是了不起的程序员——对吗?早起的鸟儿有虫吃,但早起的虫子呢?
然而在这种情况下,Ben确实命中了要害。你的知识和经验是你最重要的职业财富。
遗憾的是,它们是有时效的资产(expiring asset)。随着新技术、语言及环境的出现,你的知识会变得过时。不断变化的市场驱动力也许会使你的经验变得陈旧或无关紧要。考虑到“网年”飞逝的速度,这样的事情可能会非常快地发生。
随着你的知识的价值降低,对你的公司或客户来说,你的价值也在降低。我们想要阻止这样的事情,决不让它发生。
你的知识资产
我们喜欢把程序员所知道的关于计算技术和他们所工作的应用领域的全部事实、以及他们的所有经验视为他们的知识资产(Knowledge Portfolios)。管理知识资产与管理金融资产非常相似:
1. 严肃的投资者定期投资——作为习惯。
2. 多元化是长期成功的关键。
3. 聪明的投资者在保守的投资和高风险、高回报的投资之间平衡他们的资产。
4. 投资者设法低买高卖,以获取最大回报。
5. 应周期性地重新评估和平衡资产。
要在职业生涯中获得成功,你必须运用同样的指导方针管理你的知识资产。
经营你的资产
l
定期投资。就像金融投资一样,你必须定期为你的知识资产投资。即使投资量很小,习惯自身也和总量一样重要。在下一节中将列出一些示范目标。
l
多元化。你知道的不同的事情越多,你就越有价值。作为底线,你需要知道你目前所用的特定技术的各种特性。但不要就此止步。计算技术的面貌变化很快——今天的热门技术明天就可能变得近乎无用(或至少是不再抢手)。你掌握的技术越多,你就越能更好地进行调整,赶上变化。
l
管理风险。从高风险、可能有高回报,到低风险、低回报,技术存在于这样一条谱带上。把你所有的金钱都投入可能突然崩盘的高风险股票并不是一个好主意;你也不应太保守,错过可能的机会。不要把你所有的技术鸡蛋放在一个篮子里。
l
低买高卖。在新兴的技术流行之前学习它可能就和找到被低估的股票一样困难,但所得到的就和那样的股票带来的收益一样。在Java刚出现时学习它可能有风险,但对于现在已步入该领域的顶尖行列的早期采用者,这样做得到了非常大的回报。
l
重新评估和平衡。这是一个非常动荡的行业。你上个月开始研究的热门技术现在也许已像石头一样冰冷。也许你需要重温你有一阵子没有使用的数据库技术。又或许,如果你之前试用过另一种语言,你就会更有可能获得那个新职位……
在所有这些指导方针中,最重要的也是最简单的:
提示8
|
|
Invest Regularly in Your Knowledge Portfolio
定期为你的知识资产投资
|
目标
关于何时以及增加什么到你的知识资产中,现在你已经拥有了一些指导方针,那么什么是获得智力资本、从而为你的资产提供资金的最佳方式呢?这里有一些建议。
l
每年至少学习一种新语言。不同语言以不同方式解决相同的问题。通过学习若干不同的方法,可以帮助你拓宽你的思维,并避免墨守成规。此外,现在学习许多语言已容易了许多,感谢可从网上自由获取的软件财富(参见267页)。
l
每季度阅读一本技术书籍。书店里摆满了许多书籍,讨论与你当前的项目有关的有趣话题。一旦你养成习惯,就一个月读一本书。在你掌握了你正在使用的技术之后,扩宽范围,阅读一些与你的项目无关的书籍。
l
也要阅读非技术书籍。记住计算机是由人——你在设法满足其需要的人——使用的,这十分重要。不要忘了等式中人这一边。
l
上课。在本地的学院或大学、或是将要来临的下一次会展上寻找有趣的课程。
l
参加本地用户组织。不要只是去听讲,而要主动参与。与世隔绝对你的职业生涯来说可能是致命的;打听一下你们公司以外的人都在做什么。
l
试验不同的环境。如果你只在Windows上工作,就在家玩一玩Unix(可自由获取的Linux就正好)。如果你只用过makefile和编辑器,就试一试IDE,反之亦然。
l
跟上潮流。订阅商务杂志和其他期刊(参见262页的推荐刊物)。选择所涵盖的技术与你当前的项目不同的刊物。
l
上网。想要了解某种新语言或其他技术的各种特性?要了解其他人的相关经验,了解他们使用的特定行话,等等,新闻组是一种很好的方式。上网冲浪,查找论文、商业站点,以及其他任何你可以找到的信息来源。
持续投入十分重要。一旦你熟悉了某种新语言或新技术,继续前进。学习另一种。
是否在某个项目中使用这些技术,或者是否把它们放入你的简历,这并不重要。学习的过程将扩展你的思维,使你向着新的可能性和新的做事方式拓展。思想的“异花授粉”(cross-pollination)十分重要;设法把你学到的东西应用到你当前的项目中。即使你的项目没有使用该技术,你或许也能借鉴一些想法。例如,熟悉了面向对象,你就会用不同的方式编写纯C程序。
学习的机会
于是你狼吞虎咽地阅读,在你的领域,你站在了所有突破性进展的前沿(这不是容易的事情)。有人向你请教一个问题,答案是什么?你连最起码的想法都没有。你坦白地承认了这一点。
不要就此止步,把找到答案视为对你个人的挑战。去请教古鲁(如果在你们的办公室里没有,你应该能在Internet上找到:参见下一页上的方框)。上网搜索。去图书馆。
如果你自己找不到答案,就去找出能找到答案的人。不要把问题搁在那里。与他人交谈可以帮助你建立人际网络,而因为在这个过程中找到了其他不相关问题的解决方案,你也许还会让自己大吃一惊。旧有的资产也在不断增长……
所有阅读和研究都需要时间,而时间已经很短缺。所以你需要预先规划。让自己在空闲的片刻时间里总有东西可读。花在等医生上的时间是抓紧阅读的好机会——但一定要带上你自己的杂志,否则,你也许会发现自己在翻阅1973年的一篇卷角的关于巴布亚新几内亚的文章。
批判的思考
最后一个要点是,批判地思考你读到的和听到的。你需要确保你的资产中的知识是准确的,并且没有受到供应商或媒体炒作的影响。警惕声称他们的信条提供了惟一答案的狂热者——那或许适用、或许不适用于你和你的项目。
不要低估商业主义的力量。Web搜索引擎把某个页面列在最前面,并不意味着那就是最佳选择;内容供应商可以付钱让自己排在前面。书店在显著位置展示某一本书,也并不意味着那就是一本好书,甚至也不说明那是一本受欢迎的书;它们可能是付了钱才放在那里的。
提示9
|
|
Critically Analyze What You Read and Hear
批判地分析你读到的和听到的
|
遗憾的是,几乎再没有简单的答案了。但拥有大量知识资产,并把批判的分析应用于你将要阅读的技术出版物的洪流,你将能够理解复杂的答案。
随着Internet在全球普及,古鲁们突然变得像你的Enter键一样贴近。那么,你怎样才能找到一个古鲁,怎样才能找一个古鲁和你交谈呢?
我们找到了一些简单的诀窍。
l 确切地知道你想要问什么,并尽量明确具体。
l 小心而得体地组织你的问题。记住你是在请求帮助;不要显得好像是在要求对方回答。
l 组织好问题之后,停下来,再找找答案。选出一些关键字,搜索Web。查找适当的FAQ(常见问题的解答列表)。
l 决定你是想公开提问还是私下提问。Usenet新闻组是与专家会面的美妙场所,在那里可以讨论几乎任何问题,但有些人对这些新闻组的公共性质有顾虑。你总是可以用另外的方法:直接发电子邮件给古鲁。不管怎样,要使用有意义的主题(“需要帮助!!!”无益于事)。
l 坐回椅子上,耐心等候。人们很忙,也许需要几天才能得到明确的答案。
最后,请一定要感谢任何回应你的人。如果你看到有人提出你能够解答的问题,尽你的一份力,参与解答。
|
挑战
l 这周就开始学习一种新语言。总在用C++编程?试试Smalltalk[URL 13]或Squeak[URL 14]。在用Java?试试Eiffel[URL 10]或TOM[URL 15]。关于其他自由编译器和环境的来源,参见267页。
l 开始阅读一本新书(但要先读完这一本!)。如果你在进行非常详细的实现和编码,就阅读关于设计和架构的书。如果你在进行高级设计,就阅读关于编码技术的书。
l 出去和与你的当前项目无关的人、或是其他公司的人谈谈技术。在你们公司的自助餐厅里结识其他人,或是在本地用户组织聚会时寻找兴趣相投的人。
6 交流
我相信,被打量比被忽略要好。
——Mae West,
Belle of the Nineties,1934
也许我们可以从West女士那里学到一点什么。问题不只是你有什么,还要看你怎样包装它。除非你能够与他人交流,否则就算你拥有最好的主意、最漂亮的代码、或是最注重实效的想法,最终也会毫无结果。没有有效的交流,一个好想法就只是一个无人关心的孤儿。
作为开发者,我们必须在许多层面上进行交流。我们把许多小时花在开会、倾听和交谈上。我们与最终用户一起工作,设法了解他们的需要。我们编写代码,与机器交流我们的意图;把我们的想法变成文档,留给以后的开发者。我们撰写提案和备忘录,用以申请资源并证明其正当性、报告我们的状态、以及提出各种新方法。我们每天在团队中工作,宣扬我们的主意、修正现有的做法、并提出新的做法。我们的时间有很大一部分都花在交流上,所以我们需要把它做好。
我们汇总了我们觉得有用的一些想法。
知道你想要说什么
在工作中使用的更为正式的交流方式中,最困难的部分也许是确切地弄清楚你想要说什么。小说家在开始写作之前,会详细地构思情节,而撰写技术文档的人却常常乐于坐到键盘前,键入“1. 介绍……”,并开始敲入接下来在他们的头脑里冒出来的任何东西。
规划你想要说的东西。写出大纲。然后问你自己:“这是否讲清了我要说的所有内容?”提炼它,直到确实如此为止。
这个方法不只适用于撰写文档。当你面临重要会议、或是要与重要客户通电话时,简略记下你想要交流的想法,并准备好几种把它们讲清楚的策略。
了解你的听众
只有当你是在传达信息时,你才是在交流。为此,你需要了解你的听众的需要、兴趣、能力。我们都曾出席过这样的会议:一个做开发的滑稽人物在发表长篇独白,讲述某种神秘技术的各种优点,把市场部副总裁弄得目光呆滞。这不是交流,而只是空谈,让人厌烦的(annoying)空谈。
要在脑海里形成一幅明确的关于你的听众的画面。下一页的图1.1中显示的WISDOM离合诗(acrostic)可能会对你有帮助。
假设你想提议开发一个基于Web的系统,用于让你们的最终用户提交bug报告。取决于听众的不同,你可以用不同的方式介绍这个系统。如果可以不用在电话上等候,每天24小时提交bug报告,最终用户将会很高兴。你们的市场部门可以利用这一事实促销。支持部门的经理会因为两个原因而高兴:所需员工更少,问题报告得以自动化。最后,开发者会因为能获得基于Web的客户-服务器技术和新数据库引擎方面的经验而感到享受。通过针对不同的人进行适当的修正,你将让他们都为你的项目感到兴奋。
选择时机
这是星期五的下午六点,审计人员进驻已有一周。你的老板最小的孩子在医院里,外面下着滂沱大雨,这时开车回家肯定是一场噩梦。这大概不是向她提出PC内存升级的好时候。
为了了解你的听众需要听到什么,你需要弄清楚他们的“轻重缓急”是什么。找到一个刚刚因为丢失源码而遭到老板批评的经理,向她介绍你关于源码仓库的构想,你将会拥有一个更容易接纳的倾听者。要让你所说的适得其时,在内容上切实相关。有时候,只要简单地问一句“现在我们可以谈谈……吗?”就可以了。
|
What do you want them to learn?
What is their
interest in what you’ve got to say?
How
sophisticated are they?
How much
detail do they want?
Whom do you want to
own the information?
How can you
motivate them to listen to you?
|
你想让他们学到什么?
他们对你讲的什么感兴趣?
他们有多富有经验?
他们想要多少细节?
你想要让谁拥有这些信息?
你如何促使他们听你说话?
|
选择风格
调整你的交流风格,让其适应你的听众。有人要的是正式的“事实”简报。另一些人喜欢在进入正题之前高谈阔论一番。如果是书面文档,则有人喜欢一大摞报告,而另一些人却喜欢简单的备忘录或电子邮件。如果有疑问,就询问对方。
但是,要记住,你也是交流事务的一方。如果有人说,他们需要你用一段话进行描述,而你觉得不用若干页纸就无法做到,如实告诉他们。记住,这样的反馈也是交流的一种形式。
让文档美观
你的主意很重要。它们应该以美观的方式传递给你的听众。
太多程序员(和他们的经理)在制作书面文档时只关心内容。我们认为这是一个错误。任何一个厨师都会告诉你,你可以在厨房里忙碌几个小时,最后却会因为饭菜糟糕的外观而毁掉你的努力。
在今天,已经没有任何借口制作出外观糟糕的打印文档。现代的字处理器(以及像LaTeX和troff这样的排版系统)能够生成非常好的输出。你只需要学习一些基本的命令。如果你的字处理器支持样式表,就加以利用(你的公司也许已经定义了你可以使用的样式表)。学习如何设置页眉和页脚。查看你的软件包中包含的样本文档,以对样式和版式有所了解。检查拼写,先自动,再手工。毕竟,有一些拼写错误是检查器找不出来的(After awl, their are spelling miss streaks that the chequer can knot ketch)。
让听众参与
我们常常发现,与制作文档的过程相比,我们制作出的文档最后并没有那么重要。如果可能,让你的读者参与文档的早期草稿的制作。获取他们的反馈,并汲取他们的智慧。你将建立良好的工作关系,并很可能在此过程中制作出更好的文档。
做倾听者
如果你想要大家听你说话,你必须使用一种方法:听他们说话。即使你掌握着全部信息,即使那是一个正式会议,你站在20个衣着正式的人面前——如果你不听他们说话,他们也不会听你说话。
鼓励大家通过提问来交谈,或是让他们总结你告诉他们的东西。把会议变成对话,你将能更有效地阐明你的观点。谁知道呢,你也许还能学到点什么。
回复他人
如果你向别人提问,他们不做出回应,你会觉得他们不礼貌。但当别人给你发送电子邮件或备忘录、请你提供信息、或是采取某种行动时,你是否经常忘记回复?在匆忙的日常生活中,很容易忘记事情。你应该总是对电子邮件和语音邮件做出回应,即使内容只是“我稍后回复你。”随时通知别人,会让他们更容易原谅你偶然的疏忽,并让他们觉得你没有忘记他们。
提示10
|
|
It’s Both What You Say and the Way You Say It
你说什么和你怎么说同样重要
|
除非你生活在真空中,你才不需要能交流。交流越有效,你就越有影响力。
我们所说的关于书面交流的所有东西都同样适用于电子邮件。现在的电子邮件已经发展成为公司内部和公司之间进行交流的主要手段。它被用于讨论合约、调解争端,以及用作法庭证据。但因为某种原因,许多从不会发出低劣的书面文档的人却乐于往全世界乱扔外观糟糕的电子邮件。
我们关于电子邮件的提示很简单:
l 在你按下SEND之前进行校对。
l 检查拼写。
l 让格式保持简单。有人使用均衡字体(proportional font)阅读电子邮件,所以你辛苦制作的ASCII艺术图形在他们看来将像是母鸡的脚印一样乱七八糟。
l 只在你知道对方能够阅读rich-text或HTML格式的邮件的情况下使用这些格式。纯文本是通用的。
l 设法让引文减至最少。没有人喜欢收到一封回邮,其中有100行是他原来的电子邮件,只在最后新添了三个字:“我同意”。
l 如果你引用别人的电子邮件,一定要注明出处。并在正文中进行引用(而不是当做附件)。
l 不要用言语攻击别人(flame),除非你想让别人也攻击你,并老是纠缠你。
l 在发送之前检查你的收件人名单。最近《华尔街日报》上有一篇文章报道说,有一个雇员通过部门的电子邮件散布对老板的不满,却没有意识到老板也在收件人名单里。
l 将你的电子邮件——你收到的重要文件和你发送的邮件——加以组织并存档。
如Microsoft和Netscape的好些雇员在1999年司法部调查期间所发现的,e-mail是永久性的。要设法像对待任何书面备忘录或报告一样小心对待e-mail。
|
总结
l 知道你想要说什么。
l 了解你的听众。
l 选择时机。
l 选择风格。
l 让文档美观。
l 让听众参与。
l 做倾听者。
l 回复他人。
7 重复的危害
有些提示和诀窍可应用于软件开发的所有层面,有些想法几乎是公理,有些过程实际上普遍适用。但是,人们几乎没有为这些途径建立这样的文档,你很可能会发现,它们作为零散的段落写在关于设计、项目管理或编码的讨论中。
在这一章里,我们将要把这些想法和过程集中在一起。头两节,“重复的危害”与“正交性”,密切相关。前者提醒你,不要在系统各处对知识进行重复,后者提醒你,不要把任何一项知识分散在多个系统组件中。
随着变化的步伐加快,我们越来越难以让应用跟上变化。在“可撤消性”中,我们将考察有助于使你的项目与其不断变化的环境绝缘的一些技术。
接下来的两节也是相关的。在“曳光弹”中,我们将讨论一种开发方式,能让你同时搜集需求、测试设计、并实现代码。这听起来太好,不可能是真的?的确如此:曳光弹开发并非总是可以应用。“原型与便笺”将告诉你,在曳光弹开发不适用的情况下,怎样使用原型来测试架构、算法、接口以及各种想法。
随着计算机科学慢慢成熟,设计者正在制作越来越高级的语言。尽管能够接受“让它这样”(make it so)指令的编译器还没有发明出来,在“领域语言”中我们给出了一些适度的建议,你可以自行加以实施。
最后,我们都是在一个时间和资源有限的世界上工作。如果你善于估计出事情需要多长时间完成,你就能更好地在两者都很匮乏的情况下生存下去(并让你的老板更高兴)。我们将在“估算”中涵盖这一主题。
在开发过程中牢记这些基本原则,你就将能编写更快、更好、更强健的代码。你甚至可以让这看起来很容易。
7 重复的危害
给予计算机两项自相矛盾的知识,是James T. Kirk舰长(出自Star Trek,“星际迷航”——译注)喜欢用来使四处劫掠的人工智能生命失效的方法。遗憾的是,同样的原则也能有效地使你的代码失效。
作为程序员,我们收集、组织、维护和利用知识。我们在规范中记载知识、在运行的代码中使其活跃起来并将其用于提供测试过程中所需的检查。
遗憾的是,知识并不稳定。它变化——常常很快。你对需求的理解可能会随着与客户的会谈而发生变化。政府改变规章制度,有些商业逻辑过时了。测试也许表明所选择的算法无法工作。所有这些不稳定都意味着我们要把很大一部分时间花在维护上,重新组织和表达我们的系统中的知识。
大多数人都以为维护是在应用发布时开始的,维护就意味着修正bug和增强特性。我们认为这些人错了。程序员须持续不断地维护。我们的理解逐日变化。当我们设计或编码时,出现了新的需求。环境或许变了。不管原因是什么,维护都不是时有时无的活动,而是整个开发过程中的例行事务。
当我们进行维护时,我们必须找到并改变事物的表示——那些嵌在应用中的知识胶囊。问题是,在我们开发的规范、过程和程序中很容易重复表述知识,而当我们这样做时,我们是在向维护的噩梦发出邀请——在应用发布之前就会开始的噩梦。
我们觉得,可靠地开发软件、并让我们的开发更易于理解和维护的惟一途径,是遵循我们称之为
DRY的原则:
系统中的每一项知识都必须具有单一、无歧义、权威的表示。
我们为何称其为
DRY?
提示11
|
|
DRY –
Don’t
Repeat
Yourself
不要重复你自己
|
与此不同的做法是在两个或更多地方表达同一事物。如果你改变其中一处,你必须记得改变其他各处。或者,就像那些异形计算机,你的程序将因为自相矛盾而被迫屈服。这不是你是否能记住的问题,而是你何时忘记的问题。
你会发现
DRY原则在全书中一再出现,并且常常出现在与编码无关的语境中。我们觉得,这是
注重实效的程序员的工具箱里最重要的工具之一。
在这一节我们将概述重复的问题,并提出对此加以处理的一般策略。
重复是怎样发生的
我们所见到的大多数重复都可归入下列范畴:
l
强加的重复(
imposed duplication
)。开发者觉得他们无可选择——环境似乎要求重复。
l
无意的重复(
inadvertent duplication
)。开发者没有意识到他们在重复信息。
l
无耐性的重复(
impatient duplication
)。开发者偷懒,他们重复,因为那样似乎更容易。
l
开发者之间的重复(
interdeveloper duplication
)。同一团队(或不同团队)的几个人重复了同样的信息。
让我们更详细地看一看这四个以“
i
”开头的重复。
强加的重复
有时,重复似乎是强加给我们的。项目标准可能要求建立含有重复信息的文档,或是重复代码中的信息的文档。多个目标平台各自需要自己的编程语言、库以及开发环境,这会使我们重复共有的定义和过程。编程语言自身要求某些重复信息的结构。我们都在我们觉得无力避免重复的情形下工作过。然而也有一些方法,可用于把一项知识存放在一处,以遵守
DRY原则,同时也让我们的生活更容易一点。这里有一些这样的技术:
信息的多种表示。在编码一级,我们常常需要以不同的形式表示同一信息。我们也许在编写客户-服务器应用,在客户和服务器端使用了不同的语言,并且需要在两端都表示某种共有的结构。我们或许需要一个类,其属性是某个数据库表的schema(模型、方案)的镜像。你也许在撰写一本书,其中包括的程序片段,也正是你要编译并测试的程序。
发挥一点聪明才智,你通常能够消除重复的需要。答案常常是编写简单的过滤器或代码生成器。可以在每次构建(build)软件时,使用简单的代码生成器,根据公共的元数据表示构建多种语言下的结构(示例参见图3.4,106页)。可以根据在线数据库schema、或是最初用于构建schema的元数据,自动生成类定义。本书中摘录的代码,由预处理器在我们每次对文本进行格式化时插入。诀窍是让该过程成为主动的,这不能是一次性转换,否则我们就会退回到重复数据的情况。
代码中的文档。程序员被教导说,要给代码加上注释:好代码有许多注释。遗憾的是,没有人教给他们,代码为什么需要注释:糟糕的代码才需要许多注释。
DRY法则告诉我们,要把低级的知识放在代码中,它属于那里;把注释保留给其他的高级说明。否则,我们就是在重复知识,而每一次改变都意味着既要改变代码,也要改变注释。注释将不可避免地变得过时,而不可信任的注释比完全没有注释更糟(关于注释的更多信息,参见全都是写,248页)。
文档与代码。你撰写文档,然后编写代码。有些东西变了,你修订文档、更新代码。文档和代码都含有同一知识的表示。而我们都知道,在最紧张的时候——最后期限在逼近,重要的客户在喊叫——我们往往会推迟文档的更新。
Dave曾经参与过一个国际电报交换机项目的开发。很容易理解,客户要求提供详尽的测试规范,并要求软件在每次交付时都通过所有测试。为了确保测试准确地反映规范,开发团队用程序方式、根据文档本身生成这些测试。当客户修订他们的规范时,测试套件会自动改变。有一次团队向客户证明了,该过程很健全,生成验收测试在典型情况下只需要几秒种。
语言问题。许多语言会在源码中强加可观的重复。如果语言使模块的接口与其实现分离,就常常会出现这样的情况。C与C++有头文件,在其中重复了被导出变量、函数和(C++的)类的名称和类型信息。Object Pascal甚至会在同一文件里重复这些信息。如果你使用远地过程调用或CORBA[URL 29],你将会在接口规范与实现它的代码之间重复接口信息。
没有什么简单的技术可用于克服语言的这些需求。尽管有些开发环境通过自动生成头文件、隐藏了对头文件的需要,而Object Pascal允许你缩写重复的函数声明,你通常仍受制于给予你的东西。至少对于大多数与语言有关的问题,与实现不一致的头文件将会产生某种形式的编译或链接错误。你仍会弄错事情,但至少,你将在很早的时候就得到通知。
再思考一下头文件和实现文件中的注释。绝对没有理由在这两种文件之间重复函数或类头注释(header comment)。应该用头文件记载接口问题,用实现文件记载代码的使用者无须了解的实际细节。
无意的重复
有时,重复来自设计中的错误。
让我们看一个来自配送行业的例子。假定我们的分析揭示,一辆卡车有车型、牌照号、司机及其他一些属性。与此类似,发运路线的属性包括路线、卡车和司机。基于这一理解,我们编写了一些类。
但如果Sally打电话请病假、我们必须改换司机,事情又会怎样呢?Truck和DeliverRoute都包含有司机。我们改变哪一个?显然这样的重复很糟糕。根据底层的商业模型对其进行规范化(normalize)——卡车的底层属性集真的应包含司机?路线呢?又或许我们需要第三种对象,把司机、卡车及路线结合在一起。不管最终的解决方案是什么,我们都应避免这种不规范的数据。
当我们拥有多个互相依赖的数据元素时,会出现一种不那么显而易见的不规范数据。让我们看一个表示线段的类:
class Line {
public:
Point start;
Point end;
double length;
};
第一眼看上去,这个类似乎是合理的。线段显然有起点和终点,并总是有长度(即使长度为零)。但这里有重复。长度是由起点和终点决定的:改变其中一个,长度就会变化。最好是让长度成为计算字段:
class Line {
public:
Point start;
Point end;
double length() { return start.distanceTo(end); }
};
在以后的开发过程中,你可以因为性能原因而选择违反
DRY原则。这经常会发生在你需要缓存数据,以避免重复昂贵的操作时。其诀窍是使影响局部化。对
DRY原则的违反没有暴露给外界:只有类中的方法需要注意“保持行为良好”。
class Line {
private:
bool changed;
double length;
Point start;
Point end;
public:
void setStart(Point p) { start = p; changed = true; }
void setEnd(Point p) { end = p; changed = true; }
Point getStart(void) { return start; }
Point getEnd(void) { return end; }
double getLength() {
if (changed) {
length = start.distanceTo(end);
changed = false;
}
return length;
}
};
这个例子还说明了像Java和C++这样的面向对象语言的一个重要问题。在可能的情况下,应该总是用访问器(accessor)函数读写对象的属性。这将使未来增加功能(比如缓存)变得更容易。
无耐性的重复
每个项目都有时间压力——这是能够驱使我们中间最优秀的人走捷径的力量。需要与你写过的一个例程相似的例程?你会受到诱惑,去拷贝原来的代码,并做出一些改动。需要一个表示最大点数的值?如果我改动头文件,整个项目就得重新构建。也许我应该在这里使用直接的数字(literal number),这里,还有这里,需要一个与Java runtime中的某个类相似的类?源码在那里(你有使用许可),那么为什么不拷贝它、并做出你所需的改动呢?
如果你觉得受到诱惑,想一想古老的格言:“欲速则不达”。你现在也许可以节省几秒钟,但以后却可能损失几小时。想一想围绕着Y2K惨败的种种问题。其中许多问题是由开发者的懒惰造成的:他们没有参数化日期字段的尺寸,或是实现集中的日期服务库。
无耐性的重复是一种容易检测和处理的重复形式,但那需要你接受训练,并愿意为避免以后的痛苦而预先花一些时间。
开发者之间的重复
另一方面,或许是最难检测和处理的重复发生在项目的不同开发者之间。整个功能集都可能在无意中被重复,而这些重复可能几年里都不会被发现,从而导致各种维护问题。我们亲耳听说过,美国某个州在对政府的计算机系统进行Y2K问题检查时,审计者发现有超出10,000个程序,每一个都有自己的社会保障号验证代码。
在高层,可以通过清晰的设计、强有力的技术项目领导(参见288页“注重实效的团队”一节中的内容)、以及在设计中进行得到了充分理解的责任划分,对这个问题加以处理。但是,在模块层,问题更加隐蔽。不能划入某个明显的责任区域的常用功能和数据可能会被实现许多次。
我们觉得,处理这个问题的最佳方式是鼓励开发者相互进行主动的交流。设置论坛,用以讨论常见问题(在过去的一些项目中,我们设置了私有的Usenet新闻组,用于让开发者交换意见,进行提问。这提供了一种不受打扰的交流方式——甚至跨越多个站点——同时又保留了所有言论的永久历史)。让某个团队成员担任项目资料管理员,其工作是促进知识的交流。在源码树中指定一个中央区域,用于存放实用例程和脚本。一定要阅读他人的源码与文档,不管是非正式的,还是进行代码复查。你不是在窥探——你是在向他们学习。而且要记住,访问是互惠的——不要因为别人钻研(乱钻?)你的代码而苦恼。
提示12
|
|
Make It Easy to Reuse
让复用变得容易
|
你所要做的是营造一种环境,在其中要找到并复用已有的东西,比自己编写更容易。如果不容易,大家就不会去复用。而如果不进行复用,你们就会有重复知识的风险。