程序员修炼之道-从小工到专家(The Pragmatic Programmer: From Journeyman to Master)

https://book.douban.com/subject/1152111/

注重实效的程序员的特征

不要留着破窗户(低劣的设计,错误决策,或者是糟糕的代码)不修,发现一个就修一个。如果没有足够的时间进行适当的修理,就用木板把它钉起来:或许你可以把出问题的代码放入注释,或是显示“未实现”消息,或是用虚设的数据(dummy data)加以代替,采取某种行动防止进一步的损坏,并说明形势处在你的控制之下。我们看到过整洁运行良好的系统,一旦窗户开始破裂,就相当迅速的地恶化。还有其他一些因素能够促使软件腐烂,我们将在别处探讨他们,但与其他任何因素相比,置之不理都会更快的加速腐烂的进程。你也许在想,没有人有时间到处清理项目的所有碎玻璃。如果你继续这么想,你就最好计划找一个大型垃圾罐,或搬到别处去。不要让熵取得胜利。

要是我们真的能这样控制质量就好了。但现实世界不会让我们制作出十分完美的产品,特别是不会有无错的软件。时间,技术和急躁都在合谋反对我们。 但是这并不一定就让人气馁。如Ed Yourdon在IEEE Software上的一篇文章所描述的,你可以训练你自己,编写出足够好的软件。对你的用户,对未来的维护者,对你自己内心的安宁来说足够好。你会发现你变得更多产,而你的用户也会更加高兴。你也许还会发现因为“孵化期”更短,你的程序实际上更好了。

在某些方面编程就像绘画:你从空白的画布和某些基本原材料开始,通过知识,艺术和技艺的结合去确定用前者做些什么。你勾勒出全景,绘制背景,然后填入各种细节。你不时后退一步,用批判的眼光来观察你的作品,常常你会扔掉画笔重新再来。但艺术家会告诉你,如果你不懂得何时止步,所有的辛苦劳作就会遭到毁坏。如果你一层又一层,细节附细节的叠加,绘画就会迷失在绘制之中。不要因为过度修饰和过于求精而损毁完好的程序,继续前进,让你的代码凭着自己的质量站一会儿。也许它不完美,但不用担心,它不可能完美。

你可以选择通过原型来研究什么样的事物呢?任何带有风险的事物,以前没有试过的事物,或者是对于最终系统及装关键的事物,任何从未被证明的实验性的,或有疑问的事物,任何让你觉得不舒服的事物。你可以为下面事物制作原型:

  1. 架构
  2. 已有系统中的新功能
  3. 外部数据的结构或内容
  4. 第三方工具或组件
  5. 性能问题
  6. 界面设计

原型制作是一种经验学习。其价值并不在于所产生的代码,而在于所学到的经验教训,那才是原型制作的要点所在。

可是软件的工作方式与此并不怎么相似。与建筑相比,软件更像是园艺,它比混凝土更有机。你根据最初的计划和各种条件,在花园里种植许多花木。有些花木茁壮成长,另一些注定要成为堆肥。你可能会改变植株的相对位置,以有效利用光影风雨的相互作用。过度生长的植株会被分栽或修剪,颜色不协调的会被移栽到从美学上看更宜人的地方。你拔除野草,并给需要额外照料的植株施肥。你不断关注花园的兴旺,并按照需要对土壤指出,布局作出调整。商业人士喜欢修建建筑的比喻,它比园艺更科学,它可以重复,具有严格的管理层次报告等等。但是我们不是在修建摩天大楼,我们不用受物理和现实世界的各种限制的约束。

我们采集的是石头,但是必须时刻展望未来的大教堂。(即使对于不是处于高层的开发者,我们也必须时刻展望着你所处的整体,因为这样不仅有动力,而且能够让你在一些问题上面做出更好的判断)

持续做一些小改进,几年之后你会惊奇地发现你的经验得到了怎么样的发展,你的技能得到了怎样的提升

名称的内涵(在这里我所想到的就是,对于一个项目,我们必须对于一些关键概念作一些名称的定义,比如什么叫做用户处理请求单元,什么叫最小申请时间等这些更具开发的项目不同而含义不同的名词,应该进行统一的定义和规范,这样才能够很好在组内进行交流)

注重实效的程序员的特征是什么?我觉得是他们处理问题,寻求解决方案时的态度,风格,哲学。他们能够越出直接的问题去思考,总是设法把问题放在更大的语境中,总是设法注意更大的图景。毕竟,没有这样大的图景,你又怎么能够注重实效?你又怎么能够做出明智的妥协和有见识的决策呢?

在所有的弱点中,最大的弱点就是害怕暴露弱点(所以尽量所得暴露弱点并且去完善它,这样才会有进步)。为你的东西负责,提供各种选择,不要找蹩脚的借口

人们发现,参与正在发生的成功要更容易一些,让他们瞥见未来,你就能让他们聚集在你的周围。看来我也知道有时候应该做些什么事情了。偶尔时候展望一下未来,会让别人也觉得有信心。做一个项目的变化催化剂。

使质量成为需求问题。只有当质量成为一个需求问题,质量才会有明显的提升。

我们把程序员所知道关于计算技术和他们所工作的领域全部事实以及他们所有经验视为他们的知识资产(knowledge portfolios),管理知识资产与金融资产非常相似

制定你的学习目标:

与他人交谈还可以帮助你建立人际网络,而因为在这个过程中找到了其他不相关问题的解决方案,旧友的资产也在不断增长。

给予计算机两项自相矛盾的知识,是Captain James T.Kirk(from Star Trek)喜欢用来使四处劫掠的人工智能生命失效的方法。重复是有很大危害的,使得代码修改起来不方便就是不容易维护。但是在实际的商业商品中,软件可用也是一个很重要的问题,很多软件里面存在着很多重复但是没有人愿意去修改:-)

对于注释的编写,头文件最好就是编写接口的作用,而源文件就是编写具体的实现。

如果两个或者是更多的事物其中一个发生变化不会影响到其他事物,这些事物就是正交的。良好的系统数据库代码和界面代码正交,修改任何一项不会影响另外一项。

错误在于假定决策是浇铸在石头上的,同时还在于没有为可能出现的意外事件做好准备。要把决策视为写在沙滩上的,而不要把它们刻在石头上。大浪随时可能到来,把它们抹去。

原形制作生成用过就丢的代码。曳光弹代码虽然简约,但是却很完整,并且最终构成了系统的骨架一部分。你可以把原形制作视为第一次发射曳光弹之前的侦查和情报搜集工作.原形制作可以忽略那些细节1.正确性 2.完整性 3.强壮性 4.风格. 算法原形语言可以考虑Perl Python或者是Tcl而界面原形部分可以考虑Tcl/tk,Visual Basic,PowerBuilder或是Delphi。感觉脚本语言在不断的推出库的原因一方面是为了方便原形制作,同时也为语言成为非原形做好强力的准备。如果你觉得在你所在的环境或者文化中,原形代码的目的很有可能被误解,最好还是采用曳光弹的方法。你最后将得到一个坚实的框架,为将来的开发奠定基础。

语言的界限就是一个人的世界的界限-维特根斯坦.对于一个问题的描述,最好使用一门特定的语言进行描述。这种语言无需是可执行的。一开始它只是用于捕捉用户需求的一种方式或者是一种规范,但是如果你想跟进一步实现该语言,你的规范变成为了可执行文件。这或许大概就是一门语言的形成过程

对于估算是一个很重要的能力,特别对于一些应用级的开发,估算是十分必要的。对于估算,下面是一个形式化的步骤,但是却很有效:-)

工具放大你的才干。你的工具越好,你越是能够刚好地掌握他们的用法,你的生产力就越高。从一套基本的通用工具开始,随着经验的获得,随着你遇到一些特殊的需求,你将会在其中增添新的工具。要与工匠一样,定期增添工具。总是寻找更好的做事方式。

纯文本并非意味着文本无结构,XML,SGML,HTML都是具有良好定义结构的纯文本。

GUI的好处是WYSIWYG,但缺点是WYSIAYG(what you see is all you get)

选择一种编辑器,彻底了解它,并将其用于所有的编辑任务。如果你用一种编辑器进行所有的文本编辑活动,你就不必停下来思考怎么样完成文本操作:必须的击键将成为本能反应。编辑器将成为你双手的延伸。

如果你目睹bug或者见到bug报告时的第一个反应是:”那不可能”,你就完全错了。一个脑细胞都不要浪费在“但那不可能发生”起头的思路上,因为很明显,那不仅可能,而且已经发生了注重实效的程序员会更进一步,他们连自己都不信任。知道没有人能够编写完美的代码,包括自己,所以注重实效的程序员针对自己的错误进行防卫性的编码

在自责中有一种满足感。当我们责备自己时,会觉得再没有人有权责备我们。奥斯卡·王尔德(或许这就是懦夫存在的原因)

嵌套的分配.对于一次需要使用不只一个资源的例程时,可以对资源分配的基本模式进行扩展。另外有两个建议

不管我们在使用的是何种资源,事务,内存,文件,线程,窗口等,都满足上面的建议:-)

再多的天才也无法胜过对于细节的关注 Levy’s Eighth Law(所以引入了抽象和模块)

作为开发者,我们也工作在雷区。每天都有成百的陷阱在等着抓住我们。记住士兵的故事,我们应该警惕,不要得出错误结论。我们应该避免靠巧合编程-依靠运气和偶然的成功-而要深思熟虑的编程.怎么样深思熟虑的编程.要想让编写代码所花的时间更少,想要尽可能地在开发周期早期抓住并修正错误,想要一开始就少制造错误。如果我们能够深思熟虑,那对于我们会有帮助

当你遇到绊脚石,代码不再合时,你注意到有两样东西其实应该合并或者其他任何对你来说是“错误”的东西,不要对改动犹豫不决。应该现在就做。无论代码具有下面哪些特征,你都应该考虑重新构造代码 1.重复 2.非正交设计 3.过时的知识。事情便了,需求转移了,你对问题的了解加深了,代码也需要跟上这种变化 4.性能. 重构你的代码-四处移动功能,更新先前的决策-事实上是“痛苦管理”(pain managemen. 显然重构是一项需要慎重考虑,小心进行的活动。关于怎么进行利大于弊的重构,Martin Fowler给出了以下简单的指示

芯片在设计时就考虑了测试,不只是在工厂,安装时,而且在部署现场进行测试。更加复杂的芯片和系统可能拥有完整的Built-In Self Test(BIST)特性,用于在内部运行某种基础级的诊断。或者拥有Test Access Mechanism(TAM),用以提供一种测试装备。允许外部环境提供激励,并收集来自芯片的响应。

构建测试窗口。对于大部分的单元测试工具,最终能够显示那些测试用例通过哪些没有通过并且能够很好的展现出来,但是如果我们需要进一步了解代码的运行状态的话,那么我们可以采用日志的方式看看测试的内容和具体的信息。

问题并不在于你是在盒子里面思考还是在盒子外面思考,而在于找到盒子-真正的约束(找到真正的问题,然后解决它,这才是最重要的.就像TP告诉我为什么脚本语言好,是因为你能够真正的找到问题而不被内存管理,如何实现低级的数据结构所分心。但是我觉得使用低级语言一样,只要我能够站在高层面的角度上思考问题而不被这个语言所限制).这正是你会退一步,问问你自己以下问题的时候

很多时候,当你设法回答这些问题时,你会有让自己吃惊的发现。你所需要的知识真正的约束,令人误解的约束还有区分它们的智慧

你是一个了不起的表演者。你也需要倾听内心的低语声:“等等”如果你坐下来开始敲击键盘,在你的头脑里面反复出现某种疑虑,要注意它(要深思熟虑的编程)。倾听返回出现的疑虑,等你准备好再开始。

有些事情是不适合描述的。尤其是对于一些细节的问题,过度的描述反而容易限制开发者的编写效率。

我们是否应该使用形式方法,绝对应该。但是始终记住,形式开发方法知识工具箱里面的又一种工具。如果在仔细分析之后,你觉得要使用形式方法,那就采用它,但要记住谁是主人,不要变成方法学的奴隶注重实效的程序员批判地看待方法学,并从中提取精华,融合成自己的习惯。

形式方法在开发中肯定有其位置。但是如果你遇到一个项目,其哲学是“类图就是应用,其余的只是机械编码时”你知道,你看到的是一个浸满水的项目团队和一个路途遥远的家。

花30分钟设计一个滑稽的标识,并且把它用在你的备忘录和报告上面,越别人交谈时,大方地使用你团队名字。这听起来很傻,但是能给你的团队一个用于建设的身份标识,并给世界某种难忘的,可以与你们工作相关联的东西(体现团队荣誉感)

这里有一层隐含的关系,按照对你的授权,你越接近用户,你的级别就越高。离代码的用户有两三层远的程序员不大可能注意到它们的工作的应用语境,因此他们也将无法做出有见识的决策。

自动化使每个项目团队的必要组成部分。为了确保事情得以自动化,制定一个或者多个团队成员担任工具构建,构造和部署使项目中的苦差事自动化的工具,让它们制作makefile,shell脚本,编辑器模版和实用程序。

对于一些好的项目拥有的测试代码可能比产品代码还要多。编写这些测试代码所花的时间是值得的。从长远来看,它最后会便宜得多,而你实际上有希望制作出接近零缺陷的产品。

注重实效的程序员会把文档当作整个开发过程的完整组成部分加以接受。不进行重复劳动,不浪费时间,并且把文档放在手边。如果可能,就把文档放在代码中。并且把英语当作另一种编程语言,这样你就会努力去维护你的注释了。

注释代码给你了完美的机会,让你去把项目的那些难以描述,容易忘记却又不能够记载在任何别的地方的东西记载下来。

用户高兴得的特征


下面是 20周年纪念版 的笔记。这个版本是云风翻译的。内容相比第一版变化很大,但是好多建议的本质/核心其实都没有变化,所以我看的特别快。

多年以前Andy当地认识一个土豪,他的房子,富丽堂皇,屋子里摆满了无价的古董,到处陈列着精美的艺术品。有一天,一张挂毯因为离客厅壁炉太近而着火了。消防员奋勇救民于水火,当然主要是火。但是在把巨大的水管拖进屋子前,他们停了下来。尽管里面火势紧急,他们依然选择先在前门和火源之间铺上垫子,因为觉得水管太脏了,他们不想弄坏地毯。现在听起来这很偏激,消防部门的首要任务当然是灭火,何必管过程中的那些附带损害的。但是他们在清醒的评估了形势后,出于对自己控制这场火势能力的绝对自信,还是尽力减少了对财物造成不必要的毁害。这也是软件开发中应该遵循的方法,不要只是因为一些东西非常危急,就去造成附带损害。破窗一扇都嫌太多。

人们很容易受到诱惑,掉入货物崇拜的陷阱。通过投资去造出神器的外观,希望吸引来潜在有效的魔法。但与最初发生在美拉尼西亚的货物崇拜一样,用椰子壳制成的赝品是代替不了真正机场的。例如我们亲眼见过一些团队宣称在使用Scrum,但再仔细盘问之后就会知道,他们每周只做一次每日站立会议,4周迭代经常演变为6周或8周。他们认为这没什么问题,因为正在使用的是流行的“敏捷”调度工具。他们的投资仅仅浮于神器的表面,甚至只限于名字,就好像“站立”或“迭代”这些词是某种邪教的咒语一样。不出所料,他们也没能吸引到真正的魔法。

软件开发方法论的目的是帮助人们一起工作,正如我们在敏捷的本质中所讨论的。在开发软件时没有哪一个计划是可以照搬的,更别说另一家公司里某个人提出的一个计划。许多认证课程实际上更加糟糕,他们建立在学生能够记住并遵守规则的基础之上。而你想要的并非如此,你需要有能力超越现有的规则,发掘有利的可能性。这与“但是Scrum/精益/看板/XP/敏捷是这样做的…”,心态大为不同。实际上你希望获取某种特定方法中最好的部分,并对其进行调整以供使用.没有合适所有情况的方法,而且当前的方法还远远不够完整,因此你不只需要关注某一个流行的方法。

自上而下与自下而上之争,以你应该用的方式去做。在那计算机科学稚嫩的童年,有两种设计学派:自下而上和自上而下。自上而下学派讲的是,应该从试图解决的整个问题开始,把它分解成几块,然后逐步拆分成更小的块,以此类推,直到最后得到小到可以用代码表示的块为止。自下而上学派主张构建代码,就像构建房子一样,他们从底层开始生成一层代码,为这些代码提供一些更接近于目标问题的抽象,然后添加一层具有更高层次的抽象,这个过程会持续下去,直到所要解决问题的抽象出现。这两个学派实际上都没成功,因为他们都忽略了软件开发中最重要的一个方面,我们不知道开始时在做什么。自上而下学派认为可以提前表达整个需求,然而他们做不到,自下而上学派假设他们能构建出一系列的抽象,这将最终会将他们带到一个单一的顶层解决方案,但是当不知道方向时,如何决定每一层的功能呢?我们坚信构建软件的唯一方法是增量式的构建端到端功能的小块,一边工作一边了解问题,应用学到的知识持续充实代码让客户参与每一个步骤,并让他们指导这个过程。

我Dave因告诉别人自己不再编写测试而大出风头,这样做的部分原因是想动摇那些把测试变成宗教的人的信仰,而这样说的部分原因是,这在某种程度上是真的。我已经编写了45年的代码,在30多年中我都写了自动化测试,测试已成为我编写代码方式的组成部分,这种方式令人舒适。而我的个性坚持认为,当开始觉得舒适的时候,就应该去尝试别的东西。因此我决定停止编写测试几个月,看看这样做会对造代码造成什么影响,令人惊讶的是影响不是很大,所以我花了一些时间来找出原因。我相信这个原因就是,对我来说测试的好处更主要来自于思考测试,以及思考测试会对代码造成怎样的影响。在长时间坚持这样做之后,我写不写测试都会这样思考,代码仍然是可测试的,只是无需真的写出测试而已。不过这样做会忽略一个事实,测试也是与其他开发人员进行交流的一种方式,所以我现在还是会在与他人分享代码时为其编写测试,或给有外部依赖的事情写测试。Andy说我不应该加上这一个知识点,他担心这会诱使缺乏经验的开发人员不进行测试。下面是我的折衷方案,应该编写测试吗?要,但等你写了30年后,不妨从容的做些实验,看看它究竟会给你带来什么好处。