负责
注重实效的程序员对他或她自己的职业生涯负责,并且不害怕无知或错误。
如果你确实同意要为某个结果负责,你就应切实负起责任。当你犯错误(就如同我们所有人都会犯错误一样),或是判断失误时,诚实地承认它,并设法给出各种选择。不要责备别人或别的东西,或是拼凑接口。不要把所有问题都归咎于供应商,编程语言,管理部门,或是你的同事。也许他(它)们全体或是某几方在其中扮演了某种角色,但你可以选择提供解决方案,而非寻找借口。
提供各种选择,不要找蹩脚的借口
在你走向任何人,告诉他们为何某事做不到,为何耽搁,为何出问题之前,先停下来,听一听你心里的声音。与你的显示器上的橡皮鸭交谈,或是与猫交谈。你的辩解听起来合理,还是愚蠢?在你老板听来又是怎样?
要提供各种选择,而不是找借口。不要说事情做不到,要说明能够做什么来挽回局面。
软件的熵
有许多因素可以促生软件腐烂,其中最重要的一个似乎是开发项目时的心理(或文化)。尽管你的团队只有你一个人,你开发项目时的心理也可能是非常微妙的事情。
破窗户
一扇破窗户,只要有那么一段时间不修理,就会渐渐给建筑的居民带来一种废弃感—-一种职能部门不关心这座建筑的感觉。于是又一扇窗户破了。人们开始乱扔垃圾。出现了乱涂乱画。严重的结构损坏开始了,在相对短的一段时间里,建筑被损毁得超出了业主愿意修理的程度,而废弃感变成了现实。
不要容忍破窗户
不要留着“破窗户”(低劣的设计,错误的决策,或者糟糕的代码)不修,发现一个就修一个,如果没有足够的时间进行适当的修理,就用木板把它钉起来。
如果你发现你所在团队和项目的代码十分漂亮—-编写整洁,设计良好,并且很优雅—-你就很可能会格外注意不去把它弄脏。
做变化的催化剂
设计出你可以合理要求的东西,好好开发它。一旦完成,就拿给大家看,让他们大吃一惊。然后说:“要是我们增加…..可能就会更好”。假装那并不重要,坐回椅子上,等着他们开始要你增加你本来就想要的功能。人们发现,参与正在发生的功能要更容易,让他们看见未来,你就能让他们聚集在你周围。
足够好的软件
你可以训练你自己,编写出足够好的软件—-对你的用户,对未来的维护者,对你自己内心的安宁来说足够好。你会发现,你变得更多产,而你的用户也会更加高兴。你也许还会发现,因为“孵化期”更短,你的程序实际上更好了。
使质量称为需求问题
如果你给用户某种东西,让他们及早使用,他们的反馈常常会把你引向更好的最终解决方案。
知道何时止步
不要因为过度修饰和过于求精而毁损完好的程序。继续前进,让你的代码凭着自己的质量站立一会。它也许不完美,但不用担心:它不可能完美。
你的知识资产
随着你的知识的价值降低,对你的公司或客户来说,你的价值也在降低。我们想要阻止这样的事情,决不让它发生。
管理知识资产与管理金融资产非常相似:
- 严肃的投资者定期投资—-作为习惯
- 多元化是长期成功的关键
- 聪明的投资者在保守的投资和高风险,高回报的投资之间平衡他们的资产。
- 投资者设法低买高卖,以获取最大回报
- 应周期性的重新评估和平衡资产。
经营你的资产
1:定期投资:就像金融投资一样,你必须定期为你的知识资产投资。即使投资量很小,习惯自身也和总量一样重要。
2:多元化:你知道的不同的事情越多,你就越有价值。作为底线,你需要知道你目前所用的特定技术的各种特性,但不要就此止步。计算技术的面貌变化很快——今天的热门技术明天就可能变得近乎无用(或至少是不再抢手)。你掌握的技术越多,你就越能更好地进行调整,赶上变化。
3:重新评估和平衡:这是一个非常动荡的行业。你上个月刚开始研究的热门技术现在也许已像石头一样冰冷。也许你需要重温你有一阵子没有使用的数据库技术,又或许,如果你之前试用过另一种语言,你就会更有可能获得那个新职位。
定期为你的知识资产投资
1:每年至少学习一种新语言:不同语言以不同形式解决相同的问题,通过学习若干不同的方法,可以帮助你拓宽你的思维,并避免墨守成规。
2:每季度阅读一本技术书籍:书店里摆满了许多数据,讨论与你当前的项目有关的有趣话题。一旦你养成习惯,就一个月读一本书。在你掌握了你正在使用的技术之后,扩宽范围,阅读一些与你的项目无关的书籍。
3:也要阅读非技术书籍:记住计算机是由人—-你在设法满足其需要的人—使用的,这十分重要。
持续投入十分重要。一旦你熟悉了某种新语言或新技术,继续前进,学习另一种。
是否在某个项目中使用这些技术,或者是否把它们放入你的简历,这并不重要。学习的过程将扩展你的思维,使你向着新的可能性和新的做事方式扩展。思想的“异花授粉”十分重要,设法把你学到的东西应用到你当前的项目中。即使你的项目没有使用该技术,你或许也能借鉴一些想法。
批判的思考
批判地思考你读到的和听到的。你需要确保你的资产中的知识是准确的,并且没有受到供应商或媒体炒作的影响。警惕声称他们的信条提供了唯一答案的狂热者—–那也许有用,或许不适用于你和你的项目。
不要低估商业主义的力量,Web搜索引擎把某个页面列在最前面,并不意味着那就是最佳选择,内容供应商可以付钱让自己排在前面。
交流
除非你能够与他人交流,否则就算你拥有最好的注意,最漂亮的代码,或是最注重实效的想法,最终也会毫无结果。没有有效的交流,一个好想法就是一个无人关注的孤儿。
知道你想要说什么
规划你想要说的东西,写出大纲,然后问你自己:“这是否讲清了我要说的所有内容?”,提炼它,直到确实如此为止。
了解你的听众
只有当你是在传达信息时,你才是在交流,为此,你需要了解你的听众的需要,兴趣,能力。
选择时机
为了了解你的听众需要听到什么,你需要弄清楚他们的“轻重缓急”是什么。
让文档美观
你的主意很重要,它们应该以美观的方式传送给你的听众。
太多程序员(和他们的经理)在制作书面文档时只关心内容。我们认为这是一个错误。任何一个厨师都会告诉你,你可以在厨房里忙碌几个小时,最后却会因为饭菜糟糕的外观而毁坏你的努力。
做倾听者
如果你想要大家听你说话,你必须使用一种方法:听他们说话。即使你掌握的全部信息,即使那是一个正式会议,你站在20个衣着正式的人面前—-如果你不听他们说话,他们也不会听你说话。
鼓励大家通过提问来交谈,或是让他们总结你告诉他们的东西。把会议变成对话,你将能更有效地阐明你的观点。谁知道呢,你也许还能学到点什么。
回复他人
你应该总是对电子邮件和语音邮件做出回应,即使内容只是“我稍后回复你。”随时通知别人,会让他们更容易原谅你偶然的疏忽,并让他们觉得你没有忘记他们。
注重实效的途径
我们都是在一个时间和资源有限的世界上工作。如果你善于估计出事情需要多长时间完成,你就能更好地在两者都很匮乏的情况下生存下去(并让你的老板更高兴)。
不要重复你自己
系统中的每一项知识都必须具有单一,无歧义,权威的表示。
与此不同的做法是在两个或更多地方表达同一事物。如果你改变其中一处,你必须记得改变其他各处。或者,就像那些异形计算机,你的程序将因为自相矛盾而被迫屈服。这不是你是否能记住的问题,而是你何时忘记的问题。
重复是怎样发生的
我们见到的大多数重复可归于下列范畴
- 强加的重复:开发者觉得他们别无选择—环境要求重复
- 无意的重复:开发者没有意识到他们在重复信息
- 无耐性的重复:开发者偷懒,他们重复,因为那样似乎更容易
- 开发者之间的重复:同一团队(或不同团队)的几个人重复了同样的信息
让复用变得容易
你所要做的是营造一种环境,在其中要找到并复用已有的东西,比自己编写更容易。如果不容易,大家就不会去复用。而如果不进行复用,你们就会有重复知识的风险。
正交性
“正交性”是从几何学中借来的术语。如果两条直线相交成直角,它们就是正交的,比如图中的坐标轴。用向量术语说,这两条直线互不依赖。沿着某一条直线移动,你投影到另一条直线上的位置不变。
在计算技术中,该术语用于表示某种不相依赖性或是解耦性。如果两个或更多事物中的一个发生变化,不会影响其他事物,这些事物就是正交的。在设计良好的系统中,数据库代码与用户界面是正交的:你可以改动界面,而不影响数据库;更换数据库,而不用改动界面。
非正交系统
你正乘坐直升机游览科罗拉多大峡谷,驾驶员——他显然犯了一个错误,在吃鱼,他的午餐——突然呻吟起来,晕了过去。幸运的是,他把你留在了离地面100英尺的地方。你推断,升降杆[2]控制总升力,所以轻轻将其压低可以让直升机平缓降向地面。然而,当你这样做时,却发现生活并非那么简单。直升机的鼻子向下,开始向左盘旋下降。突然间你发现,你驾驶的这个系统,所有的控制输入都有次级效应。压低左手的操作杆,你需要补偿性地向后移动右手柄,并踩右踏板。但这些改变中的每一项都会再次影响所有其他的控制。突然间,你在用一个让人难以置信的复杂系统玩杂耍,其中每一项改变都会影响所有其他的输入。你的工作负担异常巨大:你的手脚在不停地移动,试图平衡所有交互影响的力量。
直升机的各个控制器断然不是正交的。
正交的好处
如直升机的例子所阐明的,非正交系统的改变与控制更复杂是其固有的性质。当任何系统的各组件互相高度依赖时,就不再有局部修正(local fix)这样的事情。
消除无关事物之间的影响
我们想要设计自足的组件:独立,具有单一,良好定义的目的。如果组件是相互隔离的,你就知道你能够改变其中之一,而不用担心其余组件。只要你不改变组件的外部接口,你就可以放心:你不会造成波及整个系统的问题。
如果你编写正交的系统,你得到两个主要好处:提高生产率与降低风险。
提高生产率
改动得以局部化,所以开发时间和测试时间都得以降低。与编写单个的大块代码相比,编写多个较小的,自足的组件更为容易。你可以设计,编写简单的组件,对其进行单元测试,然后把它们忘掉—-当你增加新代码时,无须不断改动已有的代码。
如果你对正交的组件进行组合,生产率会有相当微妙的提高。假定某个组件做M件事情,而另一个组件做N件事情。如果它们是正交的,而你把它们组合在一起,结果就能做M*N件事情。但是,如果这两个组件是非正交的,它们就会重叠,结果能做的事情就更少。通过组合正交的组件,你的每一份努力都能得到更多的功能。
降低风险
正交的途径能降低任何开发中固有的风险。有问题的代码区域被隔离开来。
如果某个模块有毛病,它不大可能把病症扩散到系统的其余部分。要把它切掉,换成健康的新模块也更容易。所得系统更健壮。
对特定区域做出小的改动与修正,你所导致的任何问题都将局限在该区域中。正交系统很可能能得到更好的测试,因为设计测试、并针对其组件运行测试更容易。
设计
对于正交设计,有一种简单的测试方法。一旦设计好组件,问问你自己:如果我显著地改变某个特定功能背后的需求,有多少模块会受影响?在正交系统中,答案应该是“一个”。
可撤销性
如果某个想法是你唯一的想法,再没有什么比这更危险的事情了。
要实现某种东西,总有不止一种方式,而且通常有不止一家供应商可以提供第三方产品。如果你参与的项目被短视的,认为只有一种实现方式的观念所牵绊,你也许就会遇到让人不悦的意外之事。
要把决策视为是写在沙滩上的,而不要把它们刻在石头上,大浪随时可以到来,把它们抹去。
估算
关于估算,一件有趣的事情是,你使用的单位会对结果的解读造成影响。
如果你说,某事需要130个工作日,那么大家会期望它在相当接近的时间里完成。
但是,如果你说“哦,大概要6个月”,那么大家知道它会在从现在开始的五到七个月完成。
这两个数字表示相同的时长,但“130”天却可能暗含了比你的感觉更高的精确程度。
我们建议你这样度量时间估算:
在被要求进行估算时说什么。你说:“我等会儿回答你”。
基本工具
每个工具在开始其职业生涯时,都会准备一套品质良好的基本工具。
木匠可能需要尺,计量器,几把锯子,几把好刨子、精良的凿子、钻孔器和夹子、锤子还有钳子。
这些工具将经过认真挑选、打造得坚固耐用、并用于完成很少与其他工具重合的特定工作。
工具放大你的才干,你的工具越好,你越是能更好地掌握它们的用法,你的生产力就越高。
从一套基础的通用工具开始,随着经验的获得,随着你遇到一些特殊需求,你将会在其中增添新的工具。要与工匠一样,想着定期增添工具,要总是寻找更好的做事方式。
如果你遇到某种情况,你觉得现有的工具不能解决问题,记得去寻找可能会有帮助的其他工具或更强大的工具。
为了确保不会丢失先前的任何工作成果,我们应该总是使用源码控制系统—-即使是像我们的个人地址簿这样的东西。
shell游戏
作为注重实效的程序员,你不断地想要执行某种特别的操作—-GUI可能不支持的操作。当你想要快速地组合一些命令,以完成一次查询或某种其他的任务时,命令行更为适宜。
调试的心理学
发现了他人的bug之后,你可以花费时间和精力去指责让人厌恶的肇事者。在有些工作环境中,这是文化的一部分,并且可能是“疏通剂”。但是,在技术竞技场上,你应该专注于修正问题,而不是发出指责。
bug是你的过错还是别人的过错,并不是真的很有关系,它仍然是你的问题。
不要恐慌
人很容易恐慌,特别是如果你正面临最后期限的到来,或者正在设法找出bug的原因,有一个神经质的老板或客户在你的脖子后面喘气。但非常重要的事情是,要后退一步,实际思考什么可能造成你认为表征了bug的那些症状。
如果你目睹bug或见到bug报告时的第一反应是“那不可能”,你就完全错了。一个脑细胞都不要浪费在以“但那不可能发生”起头的思路上,因为很明显,那不仅可能,而且已经发生了
再现bug
开始修正bug的最佳途径是让其可再现,毕竟,如果你不能再现它,你又怎么知道它已经被修正了呢?
橡皮鸭
找到问题的原因的一种非常简单,却又特别有用的技术是向别人解释它。他应该越过你的肩膀看着屏幕,不断点头(像澡盆里上下晃动的橡皮鸭)。他们一个字也不需要说;你只是一步步解释代码要做什么,常常就能让问题从屏幕上跳出来,宣布自己的存在。
这听起来很简单,但在向他人解释问题时,你必须明确地陈述那些你在自己检查代码时想当然的事情。因为必须详细描述这些假定中的一部分,你可能会突然获得对问题的新洞见。
不要假定,要证明
当你遇到让人吃惊的bug时,除了只是修正它而外,你还需要确定先前为什么没有找出这个故障。考虑你是否需要改进单元测试或其他测试,以让它们有能力找出这个故障。
代码生成器
当木匠面临一再地重复制作同一样东西的任务时,他们会取巧,他们给自己建造夹具或模板,一旦他们做好了夹具,他们就可以反复制作某样工件。夹具带走了复杂性,降低了出错的机会,从而让工匠可以自由地专注于质量问题。
以与木匠在夹具上投入的时间相同的方式,程序员可以构建代码生成器。一旦构建好,在整个项目生命期内都可以使用它,实际上没有任何代价。
你不可能写出完美的软件
每个人都知道只有他们自己是地球上的好司机。所有其他的人都等在那里要对他们不利,这些人乱冲停车标志、在车道之间摇来摆去、不作出转向指示、打电话、看报纸、总而言之就是不符合我们的标准。于是我们防卫性地开车。我们在麻烦发生之前小心谨慎、预判意外之事、从不让自己陷入无法解救自己的境地。
编码的相似性相当明显。我们不断地与他人的代码接合——可能不符合我们的高标准的代码——并处理可能有效、也可能无效的输入。所以我们被教导说,要防卫性地编码。如果有任何疑问,我们就会验证给予我们的所有信息。我们使用断言检测坏数据。我们检查一致性,在数据库的列上施加约束,而且通常对自己感到相当满意。
但注重实效的程序员会更进一步。他们连自己也不信任。知道没有人能编写完美的代码,包括自己,所以注重实效的程序员针对自己的错误进行防卫性的编码
解耦与得墨忒耳法则
间谍、反政府人员、革命者以及类似的人常常会组成小组,称为“cell”(最小组织单位)。尽管每个最小组织单位内部的人员可能相互认识,但他们却不认识其他最小组织单位的人。如果某个最小组织单位被发现了,再多的麻醉药也无法使该最小组织单位外部的人的姓名泄漏。切断最小组织单位之间的交往能保护每一个人。
我们觉得,这也是适用于编码的好原则。把你的代码组织成最小组织单位(模块),并限制它们之间的交互。如果随后出于折中必须替换某个模块,其他模块仍能够继续工作。
将耦合减至最少
让模块相互了解有什么问题?原则上没有—–我们不需要像间谍或反政府人员那样偏执。但是,你确实需要注意你与多少其他模块进行交互,而且更重要的事,你是怎样开始与它们交互的。
假定你在改建你的房子,或是从头修建一所房子。典型的安排涉及到找一位“总承包人”。你雇用承包人来完成工作,但承包人可能会、也可能不会亲自进行建造;他可能会把工作分包给好几个子承包人。但作为客户,你不用直接与这些子承包人打交道——总承包人会替你承担那些让人头疼的事情。
我们想要在软件中遵循同样的模型。当我们要求某个对象完成特定服务时,我们想要它替我们完成该服务。我们不希望这个对象给我们一个第三方对象、我们必须对其加以处理才能获得所需服务。
动态配置
细节会弄乱我们整洁的代码—特别是如果它们经常变化。每当我们必须去改动代码,以适应商业逻辑,法律或管理人员个人一时的口味的某种变化时,我们都有破坏系统——或引入新bug——的危险。
我们想要让我们的系统变得高度可配置,不仅是像屏幕颜色和提示文本这样的事物,而且也包括诸如算法、数据库产品、中间件技术和用户界面风格之类更深层面的选择。这些选择应该作为配置选项、而不是通过集成或工程(engineering)实现。
时间耦合
时间有两个方面对我们很重要:并发(时间在同一事情发生)和次序(事情在时间中的相对位置)。
我们在编程时,通常并没有把这两个放在心上。但人们最初坐下来开始设计架构,或者编写程序时,事情往往是线性的。那是大多数人的思考方式—-总是先做这个,然后在做那个。但这样思考会带来时间耦合:在时间上的耦合。方法A总是在方法B之前调用;同时只能运行一个报告;在接收到按钮点击之前,你必须等待屏幕重画。“嘀”必须在“嗒”之前发生。
我们需要容许并发,并考虑解除任何时间或次序上的依赖。这样做,我们可以获得灵活性,并减少许多开发领域中的任何基于时间的依赖:工作流分析、架构、设计、还有部署。
你可以使用动作图,通过找出本来可以、但却没有并行执行的动作,使并行度最大化。
不要靠巧合编程
不主动思考他们的代码的开发者是在靠巧合编程——代码也许能工作,但却没有特别的理由说明它们为何能工作。
我们应该避免靠巧合编程——依靠运气和偶然的成功——而要深思熟虑地编程。
不要盲目地编程。试图构建你不完全理解的应用,或是使用你不熟悉的技术,就是希望自己被巧合误导。
依靠可靠的事物。不要依靠巧合或假定。如果你无法说出各种特定情形的区别,就假定是最坏的。不要做历史的奴隶。
不要让已有的代码支配将来的代码。如果不再适用,所有的代码都可被替换。
即使是在一个程序中,也不要让你已经做完的事情约束你下一步要做的事情——准备好进行重构。这一决策可能会影响项目的进度。我们的假定是其影响将小于不进行改动造成的影响。
算法速率
大多数并非微不足道的算法都要处理某种可变的输入——排序n个字符串、对m×n矩阵求逆、或是用n位的密钥解密消息。通常,这些输入的规模会影响算法:输入越多,运行时间就越长,或者使用的内存就越多。
我们发现,只要我们编写的是含有循环或递归调用的程序,我们就会下意识地检查运行时间和内存需求。这很少是形式过程,而是快速地确认我们在做的事情在各种情形下是有意义的。但是,有时我们确实会发现自己在进行更为详细的分析。那就是用上O()表示法的时候了。
重构
随着程序的演化,我们有必要重新思考早先的决策,并重写部分代码。这一过程非常自然。代码需要演化;它不是静态的事物。
重写、重做和重新架构代码合起来,称为重构(refactoring)。
你应在何时进行重构
当你遇到绊脚石——代码不再合适,你注意到有两样东西其实应该合并或是其他任何对你来说是“错误”的东西——不要对改动犹豫不决。应该现在就做。
无论代码具有下面的哪些特征,你都应该考虑重构代码:
- 重复。你发现了对DRY原则的违反(重复的危害)
- 非正交的设计。你发现有些代码或设计可以变得更为正交(正交性)
- 过时的知识。事情变了,需求转移了,你对问题的了解加深了。代码需要跟上这些变化。
- 性能。为改善性能,你须要把功能从系统的一个区域移到另一个区域。
怎样进行重构
就其核心而言,重构就是重新设计。你或你们团队的其他人设计的任何东西都可以根据新的事实、更深的理解、变化的需求、等等,重新进行设计。但如果你无节制地撕毁大量代码,你可能会发现自己处在比一开始更糟的位置上。
显然,重构是一项需要慎重、深思熟虑、小心进行的活动。关于怎样进行利大于弊的重构,MartinFowler给出了以下简单提示
- 不要试图在重构的同时增加功能。
- 在开始重构之前,确保你拥有良好的测试。尽可能经常运行这些测试。这样,如果你的改动破坏了任何东西,你就能很快知道。
不要在盒子外面思考——要找到盒子
在面对棘手的问题时,列出所有在你面前的可能途径。不要排除任何东西,不管它听起来有多无用或愚蠢。现在,逐一检查列表中的每一项,并解释为何不能采用某个特定的途径。你确定吗?你能否证明?
一定有更容易的方法!
有时你会发现,自己在处理的问题似乎比你以为的要难得多。感觉上好像是你走错了路——一定有比这更更容易的方法!或许现在你已落在了进度表后面,甚或失去了让系统工作起来的信心,因为这个特定的问题是“不可能解决的”。
这正是你退回一步,问问自己以下问题的时候:
- 有更容易的方法吗?
- 你是在设法解决真正的问题,还是被外围的技术问题转移了注意力?
- 这件事情为什么是一个问题?
- 是什么使它如此难以解决?
- 它必须以这种方式完成吗?
- 它真的必须完成吗?
很多时候,当你设法回答这些问题时,你会有让自己吃惊的发现。很多时候,对需求的重新诠释能让整个问题全都消失
注重实效的团队
质量是一个团队问题。最勤勉的开发者如果被派到不在乎质量的团队里,会发现自己很难保持修正琐碎问题所需的热情。如果团队主动鼓励开发者不要把时间花费在这样的修正上,问题就会进一步恶化。
团队作为一个整体,不应该容忍破窗户——那些小小的、无人修正的不完美。团队必须为产品的质量负责,支持那些了解我们在“软件的熵”中描述的“不要留破窗户”哲学的开发者,并鼓励那些还不了解这种哲学的人。
围绕功能、而不是工作职务进行组织
我们喜欢按照功能划分团队。把你的人划分成小团队,分别负责最终系统的特定方面的功能。让各团队按照各人的能力,在内部自行进行组织。每个团队都按照他们约定的承诺,对项目中的其他团队负有责任。承诺的确切内容随项目而变化,团队间的人员分配也是如此。
这里的功能并不必然意味着最终用户的用例。数据库访问层是功能,帮助子系统也是功能。我们是在寻求内聚的、在很大程度上自足的团队——和我们在使代码模块化时应该使用的标准完全一样
早测试,常测试,自动测试。
一旦我们有了代码,我们就想开始进行测试。那些小鱼苗有飞快地变成吃人的大鲨鱼的可恶习惯,而抓住鲨鱼会困难许多。但我们又不想手工进行所有这些测试。
许多团队都会为他们的项目精心制订测试计划。有时他们甚至会使用这些计划。但我们发现,使用自动测试的团队成功的机会要大得多。与呆在架子上的测试计划相比,随每次构建运行的测试要有效得多。
bug被发现得越早,进行修补的成本就越低。“编一点,测一点”是Smalltalk世界里流行的一句话,我们可以把这句话当作我们自己的曼特罗,在编写产品代码的同时(甚至更早)编写测试代码。