程序员应该知道的97件事(txt+pdf+epub+mobi电子书下载)


发布时间:2020-07-30 13:20:11

点击下载

作者:李军

出版社:电子工业出版社

格式: AZW3, DOCX, EPUB, MOBI, PDF, TXT

程序员应该知道的97件事

程序员应该知道的97件事试读:

前言

Preface

最新式的电脑只是速度上的提升,人际关系中最古老的问题最终还是要面对跟发报机一样的老问题,即说什么,以及怎么说。[1]Edward R.Murrow

程序员的头脑里充斥着很多东西:编程语言、编程技术、开发环境、代码风格、工具、开发过程、最后期限、会议、软件架构、设计模式、团队动态、代码、需求、bug、代码质量,以及其他很多很多。

这是一类艺术、一种技艺,也是一门科学,编程远远超越了程序本身的概念。编程活动将计算机的离散世界和人类活动的连续世界联系起来。程序员就处在有待商谈的、不确定的业务与脆弱的、冷冰冰的数据位、字节及更高的构建类型领域之间。

编程有太多的东西需要知道,有太多的事情需要去做,又有太多的途径做这些事情,没有一个人或一个来源敢于宣称“找到了一条正确的道路”。《程序员必须知道的97件事》带给你的是集体的智慧和经验,它提供给你一个备选的大图景,包含了由集体智慧的马赛克拼嵌成的每个程序员应该知道的事情。这个范围涵盖了从以代码为焦点的建议到文化,从算法的用法到敏捷思考,从落实“如何去做”到职业技能,以及从风格到实质内容。

这些文章并没有像木工榫件一样相互契合,也不带有任何这方面的企图——细论起来,有些还相互对立。每一篇文章的价值体现在其独立性上,而整本书的价值在于每一篇文章是如何倡议的、证实的,甚至相互矛盾的。这里不会有最终的结论,只是让你去回应、反思、串联起所有你阅读到的信息,并基于你自己的环境、知识和经验作出权衡。

许可

Permissions

本书里的每一篇文章都遵循非强制的开源模式。在遵守Creative Commons Attribution 3.0 License的前提下,文章可以在网上自由使用,也就是说只要你保留原作者的署名信息,就可以将任何一篇文章用在你的作品中:

http://creativecommons.org/licenses/by/3.0/us/

如何联系我们

How to Contact Us

请按以下地址给出版商寄去你关于本书的评论和疑问:美国:

O'Reilly Media,Inc

1005 Gravenstein Highway North

Sebastopol,CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们对本书有专门的网页来提供额外的代码和更多的附加信息,你可以访问这个页面地址:

http://www.oreilly.com/catalog/9780596809485(英文版)

http://www.oreilly.com.cn/book.php?bn=9787121117565(中文版)

提错或者有问题请发邮件到:

bookquestions@oreilly.com

关于我们的书籍、会议、资源中心、O’Reilly网络更多信息,请访问我们的站点:

http://www.oreilly.com

http://www.oreilly.com.cn

北京博文视点资讯有限公司(武汉分部)

湖北省 武汉市 洪山区 吴家湾 邮科院路特1号 湖北信息产业科技大厦1402室

邮政编码:430074

电话:(027)87690813 传真:(027)87690813转817

读者服务网页:http://bv.csdn.net

E-mail:reader@broadview.com.cn(读者信箱)

bvtougao@gmail.com(投稿信箱)

致谢

Acknowledgments

有许多人通过直接或间接的方式,把他们的时间和洞察力倾注在这本《程序员必须知道的97件事》里。他们中的所有人都值得这份荣誉。

Richard Monson-Haefel是“97件事”系列的编辑,也是本系列第一本书——《架构师必须知道的97件事》(我也撰稿过)的编辑。我要感谢Richard,是他引领了这系列的概念,并完成了开放撰稿的尝试,同时热心支持我对本书的建议。

我还要感谢那些把时间和精力贡献给本项目的人:包括在本书中刊出文章的撰稿人和文章未能被选中,但是发布在网站上的其他人。大量高质量的稿件让我们最终的选择变得异常艰难——书名上定死的数字意味着没有余地能留给更多同样优秀的作品。我也特别感谢Giovanni Asproni、Paul Colin Gloster和Michael Hunger提供了附加的反馈、评论和建议。

我要感谢O’Reilly对本项目提供的支持,从主办wiki使大家都能看到这一切,一直到以书籍的形式将其出版。我要特别感谢 O’Reilly 里的那些人,他们是Mike Loukides、Laurel Ackerman、Edie Freedman、Ed Stephenson和Rachel Monaghan。

在Web上完成本书的全部内容不是件容易的事:本项目同时在网上宣传和普及。我要感谢那些为本项目一直推特(tweet)、回推特(retweet)和博客发文的人。我还要感谢我的妻子 Carolyn 把我的混乱状态收拾得井井有条,以及我的两个儿子Stefan和Yannick给我的混乱状态火上加油。

我希望本书能给你带来信息、洞察力和灵感。

请享用吧!—Kevlin Henney[1]译注:爱德华 R·莫罗,美国人,1908—1965,电视广播新闻业的先驱,被称为“历史上最伟大的新闻评论员”。谨慎行动

Act with Prudence

勒布·罗斯(Seb Rose)

无论你承诺了什么,都得小心处置,顾及后果—— 无名氏

在一次迭代开始时,各项任务看上去安排得张弛有度,但仍无法避免在某段时间会承受到巨大进度压力。当你发现必须在“干得好”和“干得快”之间作出抉择时,一般都会选择“干得快”,并提醒自己将来再来返工。你对自己、团队和客户许下这个承诺的时候,确实是想这样做的。但经常会出现的情况是,下一轮迭代自有其新问题,你只好把工作重点转移到新问题上。这类久拖不决的工作任务就是所谓的技术债务(technical debt),它可不是你的好朋友。Martin Fowler 在他的技术债务分类法里把它叫做蓄意技术债务(deliberate [1]technical debt),有别于无意技术债务(inadvertent technical debt)。

技术债务就像一笔贷款。在短期内,你能从中得到好处,但是,在清偿之前,你要付出利息。代码里的捷径使得新功能更难于加入,也会影响到代码重构。它们是软件缺陷和失败测试用例的滋生地,放任它们的时间越长,情况就会越糟糕。当你有一天终于想兑现之前的承诺,对它们进行修改时,会发现代码已经难以重构和更正。事实上,往往是情况变得不可收拾时,你才不得不对它们进行修正,而那时你已没有足够的时间,也承担不起由此带来的风险。

难免有几次为了赶上最后期限,或要为一项特性做个简化版本,不得不背上技术债务。请尽量不要陷入这样的境地,如果实在是形势逼人,那就勇敢地去承担。但是(注意,这是加重语气的但是),必须时刻追踪这笔技术债务,尽快地或者当情况急剧恶化的时候,立即将其偿还。每当你迫不得已欠下了技术债务,就要立刻记录到任务卡上或者登记到问题跟踪系统里,以保证其不会被遗忘。

如果在下一轮迭代里偿还了这笔债务,其代价就会减少到最小,如果总是不还,利息就会日益累加。每笔利息都应该被跟踪到,使其代价显而易见。这一措施彰显出技术债务对项目的商业价值的负面影响,从而给“还贷”一个适当的优先级。利息的计算和跟踪方法因项目不同而互有差异,但是,这类跟踪是必须做的。

尽快偿还你的技术债务吧,否则你会为你的轻率而后悔。[1]http://martinfowler.com/bliki/TechnicalDebtQuadrant.html.函数式编程原则的应用

Apply Functional Programming Principles

爱德华·加森(Edward Garson)

函数式编程最近又在主流编程社区里火热起来了,其部分原因是计算机芯片工业正在向“多核”发展,函数式范型(functional paradigm)的特性恰好能应对多核时代的挑战。函数式编程在这方面的应用固然重要,但这并非本文把它推介给你的原因。

掌握函数式编程范型(functional programming paradigm)能极大地提高你的代码质量,不管是在哪类应用环境下。如果你能深入领会并熟练运用函数式范型,那么你的设计就会显示出更高的引用透明性(referential transparency)。

引用透明性是一个非常重要的属性:它意味着函数不管在何时何地被调用,都保持一致的行为,同样的输入得到同样的输出结果。也就是说函数的输出值很少——理想的情况下根本不会——受制于由多变的状态值带来的副作用。

过程式代码(imperative code)最突出的缺点就是状态变量众多。每一个读到这段话的人都曾经有过这样的经历:一些值在某个特殊的位置会变得跟预想的不一样。可见性语义(Visibility semantics)有助于去除一些隐蔽的错误,或者能极大地缩小发生问题的根源范围,但是,罪魁祸首可能还是因为在设计思想里使用了太多变化莫测的状态值。

在原来的思路上,我们当然无法再获得多少帮助。因此,需要引入面向对象编程来提升此类设计,因为在经常展示的面向对象例子里,关联的长寿命对象(long-lived objects)之间可以方便地调用状态因子方法(mutator method)——这可能也会有危险。然而,在灵活的测试驱动设计里,特别是当确信要“模仿角色,不用对象”[1](Mock Roles,not Objects)时,不必要的易变性就能被排除在外了。

最终解决办法是实现一种设计,其典型特征是负责分配数量更多、粒度更细的函数,这些函数依据传入的参数做相应的处理,而不是依赖于易变的成员变量。这样不仅能减少差错,并且在将来也容易调试,因为在这些设计中定位奇怪变量要比在特定场景推断出错误的赋值要容易得多。这样在引用透明性上就提高了一个层次,没有什么能比学习一门函数式编程语言更能使这种感受深入人心了,在函数式编程里,这种计算模型就是标准。

当然,这种方法不是在所有情景下都是最好的选择。举例来说,在面向对象系统里,针对此类问题,领域模型开发(domain model development)(比如用一系列协作来分解复杂的业务规则)好过基于用户界面的开发。

掌握了函数式编程范型之后,你可以把从中学到的知识应用到其他领域。对象系统(举个例子)将会从引用透明性的优点中获益良多,比你所相信的更加符合它们的功能属性。事实上,甚至有些人断言说函数式编程和面向对象的顶点相互映照,如同计算形态里的阴阳两面。[1]http://www.jmock.org/oopsla2004.polf.试问自己“用户会怎么做?”(你不能算是用户)

Ask "What Would the User Do?"(You Are not the User)

吉尔斯·科尔伯恩(Giles Colborne)

我们都爱假设别人的心思跟我们一样,但事实上不是这回事。心理学上把这种心理状态叫做虚假同感偏差(false consensus bias)。当人们的想法和行为跟我们不同时,我们很可能会(在潜意识里)将他们归为“能力低下”的人。

这种偏差解释了为什么程序员难以设身处地为用户着想。用户的思路与程序员不一样。从一开始,他们就不怎么使用电脑。他们不知道也不关心电脑是怎么工作的。这意味着他们无法借鉴程序员熟悉的技术来解决遇到的问题,他们也不会认可程序员常用的界面模式和提示信息。

要想知道用户的想法,最好的办法是观察一个用户,要求用户使用功能类似的小软件完成一项任务,并确保任务是一项真实的业务。比如“把一列数字全部加起来”就很合适,“计算你上个月的开销”这样更好。避免提出太精确的任务,例如“你能选中这些数据表的格子,然后在下面输入一个SUM公式吗?”——该问题里隐藏了很大的暗示。最好是让用户在操作的过程中一边做一边说明,不要打断他,也不用提供什么帮助。你要一直问自己:“为什么他要那么做?为什么她不那样做?”

观察用户时,你注意到的第一件事情就是所有用户完成任务时的核心特点都是相似的。他们尽量用同样的次序来完成任务——会在同样的地方犯同样的错误。你应该围绕这个核心行为做设计。这种方法不同于设计会议,在会上人们都倾向于听到“假如用户想要……”之类的话。这会导致过于详尽的功能点,淹没了用户真正想要的东西,而观察用户可以消除这种混乱。

你也会看到用户在完成任务过程中被卡住了。当你卡住的时候,你会四处张望,寻求帮助;而用户卡住的时候,他们会缩小他们的注意力范围,于是更难以看到在屏幕其他区域上显示的解决办法。这也是为什么在粗劣的用户界面上,提供帮助信息反而是糟糕的解决方法。如果你必须显示操作步骤或帮助消息,那就得让它们出现在问题区域的近旁,因为用户注意力范围狭小,使用工具提示信息(tool tip)的效果胜过点击帮助菜单。

用户都倾向于能用就好。他们一旦找到一种可用的办法,就会一直用下去,不管它有多费劲。因此,提供一条显而易见的操作途径,要好过提供两三条捷径。

你在观察用户的时候,也会发现用户在表述和实际操作之间有着巨大差异。平时通过询问用户来收集用户需求时也很担心此类事情,所以,直接观察用户是获取需求的最佳途径。到底用户想要什么,与其整天闷头猜测,还不如花上一小时去仔细观察。编码标准的自动化

Automate Your Coding Standard

菲利普·冯·莱能(Filip van Laenen)

你可能已经有过这样的经历。在项目开始之初,每个人都抱有许多美好的愿望 ——就称之为“新项目的决议”吧。通常,决议的许多内容会被写入文档里。其中有关代码的部分最终成了项目的编码标准。在项目启动会议上,首席开发人员浏览了这份文档,在最好的情况下,每个人都同意遵循制定的标准。一旦项目进入运作阶段,这些好想法就会被一个接一个地抛弃掉。当项目最后交付时,这些代码看上去是一塌糊涂,没人能说出这到底是怎么来的。

事情到底是在什么时候开始出错的呢?可能就是在项目启动会议上。有几个项目成员没把它放在心上,另外有几个不理解其中的要点,更糟的是,有些人虽然同意这份标准,但私底下已经计划好使用他们自己的编码标准。最后,有一些人领会并同意了这份标准,但是当项目的压力增大时,他们不得不有所放弃。在客户面前,格式良好的代码并不能为你加分,他们需要的更多的功能。更进一步来说,如果它不能自动生成遵循编码标准是件枯燥的活儿。而现实是,你只能自己在凌乱的类代码里手工缩排以便看清它的功用。

既然存在这样的问题,那当初我们为什么还要一份编码标准呢?一个原因是通过对代码做统一的格式化,可以避免某些人用自己特有的编码格式来“占有”一部分代码。我们可能都想着为了避免一些常见的 bug,防止开发人员使用某些反模式(antipattern)。总之,一份编码标准应该使项目更易于开展,从开始至结束保持一定的开发速度。随后,每个人都应该同意这份编码标准——如果一个开发者用三个空格做缩进,而另一个人用四个空格,那么,这份标准就不起什么作用了。

有不少工具可以用来生成代码质量报告,并能对编码标准做归档及维护,但是,这还不是完整的解决方案。它还应该在任何可能的地方自动地强制执行。这里有几条建议:

●确保代码格式是构建过程的一部分,这样,每个人在每次编译代码时都能自动运行。

●使用静态代码分析工具扫描代码,找出不希望有的反模式。如有发现,就中止构建过程。

●学会配置那些工具,这样你就可以自己扫描出特定项目的反模式了。

●不仅要衡量测试覆盖度,还要自动检查它们的结果,如果测试覆盖度过低,就中止构建过程。

尽量去做那些你认为重要的事情,你所关心的每一件事情未必都能够被自动执行。对于那些你无法自动标记或修复的,可以把它们看作一套指导方针,是对已自动化的编码标准的补充,但是,也要承认你和你的同事可能会不乐意接受。

最后,编码标准应该是动态的,不是一成不变的。随着项目的进展,项目需求的改变,一些在刚开始时显得灵活的标准可能在几个月后会变得不够灵活。美在于简单

Beauty Is in Simplicity

乔恩·奥尔姆海姆(Jørn Ølmheim)

柏拉图有这样一句名言,我想所有软件开发人员最好都能知道并牢记在心:风格之美、和谐、优雅及优美的节奏尽在于简单。

在这个句子里,汇集了软件开发者的全部期望。

有许多东西是我们在代码中尽力争取的:

●可读性。

●可维护性。

●开发速度。

●难以捉摸的美的品质。

柏拉图告诉我们,在这些品质中发生作用的因素就是简单。

那么,优美的代码是什么样的呢?这其实是个比较主观的问题。对美的感知极大地依赖于个人的背景,如同我们对其他任何事物的理解那样。在艺术方面有过训练的人们比只受过科学教育的人更接近美的真谛,学艺术的人通过将软件与艺术品比较来发现软件之美,而学理科的人只会讨论对称和黄金分割比,试图把事物归纳成一套公式。根据我的经验,无论在哪一方观点里,简单是它们的基础。

回想一下那些让你受益匪浅的代码。如果你不曾花时间研习他人的优秀代码,那就赶快放下这本书,找些开源代码读一读。真的,我就是这个意思!根据你所在国家的语言,在Web上搜索一个公认的知名专家编写的代码。

你回来了?很好。我们说到哪儿了?嗯,是的……我感觉到代码与我发生了共鸣,优美的代码有许多共同的属性,首要的一点就是简单。一个应用或一个系统无论有多么复杂,其中每个单独的组成部分都保持着它的简洁性:简单的对象承担了单一的职责,包含着同样简单、专一的方法,方法的功能如同其名称描述的一样。有些人认为包含 5~10 行语句的短方法过于极端,在一些编程语言里很难做到,但是,我想这种简短正是我们希望达到的目标。

优美的代码在最低限度上是简单的代码。每一个单独的部分都要简单,不仅是本身职责简单,而且与系统的其他部分也要保持简单的关系。无论经过多少时间,干净、简单、可测试的代码保证了系统的可维护性,也确保了系统在整个生命周期里能快速开发升级。

美来自于简单,亦存在于简单。在你重构之前

Before You Refactor

拉吉斯·阿塔帕图(Rajith Attapattu)

每一个程序员总会有重构已有代码的时候。但是,在你着手重构之前,请考虑以下几点,它们能帮你节省大量的时间(并减轻大量的痛苦):

●重构代码的最佳起点就是清理已有的基础代码和基于这些代码写的测试。这能帮你理解当前代码的优缺点,这样就可以确信自己是在去芜存菁。直到最终没能做得更好——甚至更差之前,我们都觉得自己可以比原有系统做得更好。这是因为我们没有向原有系统中的错误学习。

●避开重写一切的诱惑。尽可能多地重用代码是最好的选择,无论代码是多么的丑陋,毕竟它已经被测试过了,也被审查过了。抛弃旧代码——尤其是存在于产品中的代码——也意味着抛弃了经年累月测试并实战过的代码,以及你还未知晓的周边代码和bug补丁。这会浪费大量的时间、精力和多年积累下来的知识。

●逐步增加的小改动胜过一次性的大改动。逐步改动能使你更容易通过反馈信息来评估改动对系统的影响,例如做一些测试。假如你做了一次更改后就导致一百个测试用例失败,那丝毫不会让你感到开心,挫折感和压力随之而来,结果导致错误的决定。每次只有一两个失败的测试用例就相对容易处理,也更容易掌控进度。

●在每次开发迭代之后,要确保已有的测试用户都已通过。假如已有的测试用例没能覆盖到你所做的修改部分,那就增加新的测试用例。但也不要轻率地丢弃原来基于旧代码的测试用例。从表面上来讲,这些用例中的一部分可能不适用你的新设计了,但是,它在深究为什么要添加这些特殊的测试用例方面还有极高的价值。

●个人好恶和利己主义不能掺杂到开发中来。如果那些代码没出问题,为什么要去修改它们?如果代码的风格或结构不符合你的个人喜好,你也不能把这当作代码重构的正当理由。同样,即使你觉得自己可以比上一个程序员做得更好,也不能将它作为重构的理由。

●新技术不是重构的充分的理由。关于重构的最差劲的理由之一就是当前代码跟我们目前拥有的很酷的技术相比已经远远落后了,我们相信用新的语言或框架来完成这些功能会更加优雅。除非成本效益分析结果表明这种新的语言或框架能在功能性、可维护性或生产力上会有显著的提升,否则,最好还是弃之不用。

●要记住人总是会犯错误的。重构不能一直保证新代码会超过——或相当于——原先的代码。我曾见过,也曾经历过一些失败的重构尝试。这感觉一点都不好,但这就是人。谨防共享

Beware the Share

伍迪·达汉(Udi Dahan)

它是我在那家公司的第一个项目。那时我刚毕业,迫不及待地想要证明自己的能力,我每天干到很晚,一遍遍地检查写过的代码。当完成第一个功能点后,将自己所学到的一切都付诸实施:连接、登录、取出共享的代码放入可能对应的库里面,成功。完成之后就等代码审查了。但是,代码审查的结果却让我大吃一惊:代码无法重用。

怎么会这样?在大学里就已经知道,重用是高品质软件工程里的一个重要方面。我读过的所有文章、教科书及教导过我的经验丰富的软件专家都是这么告诉我的,在这里为什么会出错呢?

看上去好像是我忘记了某些重要的东西。

上下文环境。

事实上系统有两个极不相同的部分通过同样的方式实现某一个逻辑,这是我事先没想到的。在我抽取出共享代码里的库之前,这些部分还都不是相互依赖的,每一部分都能独立发展,能改变它的内在逻辑以适应系统业务环境的变化。这四行类似的代码是一种意外——暂时性失常,纯属巧合。这种情况一直持续到我操作为止。

我创建共享代码库就像是把每只脚上的鞋带捆绑在一起。如果不首先跟其他业务同步一下,一个业务领域里的各步骤就无法进行。这些相互独立的函数的维护成本几乎可以忽略不计,但是,公共库却需要多一个数量级的测试。

当我减少系统中代码的绝对行数时,就会增加相互依赖的代码数量。这些依赖关系的上下文非常关键—— 在本地化时,共享的部分可能已经被校正,并有正确的值。如果这些依赖关系没被抑制住,就会牵扯到系统的更大的利害关系,哪怕代码本身看上去还很好。

这些错误都非常隐蔽,就其本质来看,像是个好主意。当它们在正确的上下文中使用时,这些技术都很有价值;但在一个错误的上下文里,它们产生的成本就会高过其价值。后来,当我遇到已有的代码库时,如果不知道其中有哪些不同的部分会被用到,就会更加小心地处置其中的共享部分。

谨防共享。先检查你的上下文环境,只有完成这一步后,才能继续前行。童子军规则

The Boy Scout Rule

罗伯特·C·马丁(鲍伯大叔)(Robert C.Martin(Uncle Bob))

童子军有这样一条规则:“要让离开时的营地比进入时更加干净。”当你发现地上有脏东西,不管是谁造成的,都要立即把它清理干净,就这样有意识地给下一批露营者一个更好的环境。(事实上,这条规则是童子军之父——Robert Stephenson Smyth Baden-Powell最先提出的,原文是“尽力去做,让你离开时的世界比你找到它时还要好一点。”)

在我们的代码里是不是也可以遵循类似的规则“让模块签入(check In)的时候比签出(check out)的时候更整洁”?无论模块的原作者是谁,我们是否都会付出努力,不论多少,来提高它?如果这么做了,将会是怎么样的一个结果?

我想如果我们都遵守这条规则,我们将看到软件系统里无可挽回的退化局面会终结,而且会逐渐变得更好。我们将看到各团队把系统当成一个整体来维护,不再“各人自扫门前雪”。

我觉得这条规则的要求并不多,你不必在签入之前把每个模块都做得很完美,只要比签出的时候稍微做得好一点就行了。当然,这意味着你加入模块里的任何代码都要很整洁,在代码签回去之前,至少要对自己的代码做一次清理。你可以仅仅提高一些变量名称的可读性,或者把长函数分割成两个更短的函数;你可以打破一个循环依赖(circular dependency),或者增加一个接口解耦策略和实现细节。

坦率地讲,这对我来说是很普通的事情——就像饭前便后要洗手一样,或者像把垃圾扔进垃圾桶而不是乱丢在地板上一样。把代码搞得一团糟的行为也应该像社会上乱丢垃圾行为一样不受待见,它应该是该做的却没有做到的行为。

但是,除此之外,关照你自己的代码是一回事情,关照团队的代码又是另外一回事情。团队要相互帮助,相互清理代码。他们遵从童子军规则,是因为这对每一个人都有好处,而不仅仅是他们自己。在责备别人之前先检查自己的代码

Check Your Code First Before Looking to Blame Others

阿伦·凯利(Allan Kelly)

开发人员——就是我们这些人常常难以相信自己的代码会出错,绝不可能出错,即使有这么一次,那也必定是编译器出问题了。

事实上,由编译器、解释器、OS、应用服务器、数据库、内存管理器或其他系统软件里的一个bug导致代码出错的可能性非常非常小。是的,那里是有一些bug存在,但是这实在是太罕见了,以至于我们难以相信。

我曾碰到过一个真实的问题,编译器把一个循环变量优化掉了。但是,我总想着这是我的编译器或操作系统存在 bug,然后把大量的个人时间、技术支持时间和管理时间花在这上面,直到最后证明这根本是我自己的错误,这才让我感觉到自己有点蠢。

假如使用的工具是一个被广泛使用的、成熟的,并在不同技术领域都在应用的产品,那就几乎没有理由去怀疑它的品质。当然,如果一个工具刚刚推出,或者在世界范围内没几个人使用过,或者是一个版本为 0.1 且下载人数极少的开源软件,那就有怀疑它的理由了(同样的,一个 alpha 版的商业软件也是靠不住的)。

鉴于编译器极少会有 bug,你与其费心地去证明是编译器的问题,还不如把时间和精力用在自己代码的排错上:使用所有常见的调试手段,把问题隔离出来,用桩代码(stub)的方式进行调用,围绕可疑代码编写测试用例;检查调用约定、共享库和版本号;试着向他人咨询;注意堆栈损坏和变量类型不匹配;在不同的机器上运行这段代码,更改不同的构建方式,比如debug和release。

要质疑你自己设定的前提条件,或者其他方面的前提条件。不同的工具生产商会在产品里内置不同的前提条件——同理,出自同一个生产商的不同工具也可能会这样。

如果其他人报告说有问题,而你却无法重现时,那就走过去看看他们到底是怎么操作的。他们可能进行了你未曾预料到的操作,或者采用了不同的次序在操作。

我的个人准则就是如果发现一个 bug,并且无法消除,我就开始假设这是编译器的问题,然后去查看堆栈损坏情况。这种方式在添加了跟踪代码之后特别有效,它能使问题更加突出。

多线程问题是bug的另一个源头,它令多少程序员早生华发,冲着机器大喊大叫。所有建议都说:如果系统是多线程的,那就把简单的代码多复制几份。调试和单元测试也无法发现这类一致性方面的bug。因此,简洁的设计十分重要。

所以,在你打算冲过去斥责编译器之前,回想一下夏洛克·福尔摩斯的建议:“一旦排除了所有的不可能,那么最后剩下的无论有多么的不同寻常,那必定就是真相。”,在Dirk Gentl(另一系列侦探小说里的主角)嘴里就变成是“一旦排除了所有的不同寻常,最后剩下的无论有多么不可能,那必定就是真相。”谨慎选择你的工具

Choose Your Tools with Care

乔瓦尼·阿斯普罗尼(Giovanni Asproni)

现在的应用程序很少从零开始构建的,它们都是由各种已有的工具——包括组件、库和框架组成。有许多明智的理由支持这种做法:

●应用程序在大小、复杂性和综合度上有了大幅提高,而开发人员拥有的时间却越来越少。要想更好地利用开发人员的时间和智力,那就应该让他们更专注于业务领域代码,少操心基础架构代码。

●与自己开发的相比,被广泛使用的组件和框架的bug数量更少。

●在网络上有许多高质量的软件可供免费使用,这意味着更低的开发成本,并且更有可能找到有着必要兴趣和专业技能的开发人员。

●软件的生产和维护是一项人力密集的活动,因此,购买可能会比自己构建还要合算。

然而,给应用程序选择一套搭配良好的工具也是件棘手的事情,需要一些指导思想。事实上,当你作出选择的时候,应该把以下几点牢记在心:

●不同的工具在它们的上下文里有着各不相同的前提条件 —— 例如外围的基础代码、控制模型、数据模型、通信协议等。这会导致应用架构与工具不匹配,从而要在两者身上做些黑客式的工作,增加了不必要的复杂性。

●不同的工具有着不同的生命周期。工具中的任何一个升级都可能异常困难,耗时良久,因为升级之后有功能新增、设计改动或bug修复,从而与其他工具产生兼容性问题。工具越多,随之而来的问题越大。

●有些工具需要做一些配置,这往往只是一个或几个XML文件的事情,但局势很快就会失去控制。最后,整个应用看上去就像由XML加上几行某种编程语言写成的奇怪代码。配置的复杂性使得应用难以维护和扩展。

●当代码严重依赖于某个特定厂商的产品时,就会出现厂商锁定现象,应用在若干个方面被限制住了:如可维护性、性能、扩展能力、价格等。

●如果计划使用免费软件,你可能会发现它根本不免费,很可能需要购买付费的商业支持——这东西不会太便宜。

●许可证条款也是个麻烦,哪怕是免费软件。例如在一些公司里就不接受按GNU许可证发布的软件,因为它们有着“病毒传染”式的要求——也就是说,基于这些软件开发的软件都必须公开发布其源代码。

在这方面,我个人采取的策略是从小范围开始,只采用相对必需的工具。通常,初始目标是消除底层基础编程(和问题)的影响,例如在分布式应用里,使用一些中间件来代替原始的套接字(socket)。如有进一步的需求,才继续增加其他工具。我还通过在业务领域对象里使用接口和分层思想来隔离外部工具的影响,这样就能以最小的代价来更换工具。该方法带来的好处是我最终以比预期更少的外部工具完成了一个规模更小的应用。领域语言里的代码

Code in the Language of the Domain

丹·诺斯(Dan North)

这里展示两份基本代码。在第一份里,你会看到:

这时,你会挠挠头,不明白这代码是做什么用的。看上去它是从一个trader对象里读取ID,然后用它从一个……对了,是从一个由map对象构成的map中读出一个map对象,然后看看从portfolio对象里读到的ID是不是存在于这个内部 map 对象中。想到这里,你又会挠几下头,开始寻找portfolioIdsByTraderId的声明,发现如下代码:

你逐渐意识到这些代码可能跟判断一个 trader 是否有权访问某个特定portfolio 有关。当然,你也会发现同样的查找功能片段——或者说,更有可能的是一段类似的但有着微妙差异的代码——无论何时都能知道一个trader是否有权访问某个特定portfolio。

在另一份基本代码里,你看到的是这样:

不用挠头,也不需要知道 trader 对象的细节,可能在它内部确实存在着map嵌套map的结构,但这也是trader自己的事情,与你无关。

现在回过来再问问你更愿意采用哪一份基本代码呢?

很久以前,我们仅有几种基本的数据结构:位(bit)、字节(byte)和字符(character)——其实它也是字节,但是我们把它们当作字母和标点看待。数值有一点麻烦,因为在二进制体系中,不能很好地表示常用的十进制数,所以我们采用了几种不同精度的浮点数类型。接下来是数组和字符串(字符串实际上就是数组)。另外,我们拥有的堆栈、队列、哈希表、链接表、跳转表(skip list)以及其他一些好用的数据结构在现实世界里根本就不存在。“计算机科学”的努力方向就是将现实世界映射到受限的数据结构中去。那些真正的大师们甚至还能记起当初他们是如何做到这一点的。

后来我们有了用户自定义类型!是的,这不算是新闻,但是它确实在某种程度上改变了游戏规则。如果你的领域里包含了一些类似 trader、portfolio 这样的概念,你就可以用它们的类型名称来建模,比这更重要的是,你还可以用领域术语对它们之间的关系进行建模。

如果不使用领域术语来编码,你就是在创建一个默认的(也可以理解为“秘密的”)理解方式:这里的int用来表示一个trader;那边的int标识一个portfolio。(最好不要把它们混在一起!)如果你用算法代码来表示一个业务概念(例如“不允许某些trader查看某些portfolio——如果这么做,就是非法的”),也就是说存在着一个键(key)键对应的 map 表——你不用做审核,也不用接纳别人给予的帮助。

下一个接手的程序员可能不知道其中的奥秘,所以,为什么不把它表示得明确一些呢?用一个键查找另外一个键,这样的存在性检查粗看起来也不可怕,但是,怎么才能让别人一眼就能看出这里实现了防止利益冲突这一业务规则?

在代码里明确领域概念,意味着别的程序员能更容易地领会代码的意图,不需要按自己对该领域的理解尝试着改写算法;也意味着当领域模型发生变化时——它一定会变化的,当你对该领域的理解深入时,能在一个好的起点上改进代码,再假以良好的封装,规则或将只存在于某一个地方,这样修改起来就不会牵扯到其他对此有依赖关系的代码,从而显得更为明智。

几个月后,前来接手代码的程序员将会感谢你。那个程序员很可能就是你自己。代码就是设计

Code Is Design

瑞恩·布勒西(Ryan Brush)

想象着你明早醒来,看到建筑工业在21世纪取得了突破性的进展,我们拥有了数以百万计的机器人,它们廉价又高效,能够凭空制造出原材料,消耗的能量接近于零,并有自我修复的能力。更强大的是只要提供一份精确无误的建筑蓝图,这些机器人就能在没有人类干预的情况下把它建造出来,所有这一切的成本几乎可以忽略不计。

可以想象这对建筑业会有多大的冲击,但是,建筑业的上游又会发生什么?如果建造成本可以忽略,那么架构师和设计师的行为又会有怎样的改变?今天,在建筑物投资兴建之前,物理模型和计算机模型都会预先被构建起来并严格测试,如果建造是免费的,我们是不是还需要这么操心?如果一座建筑物倒塌了,那也不是大问题——只要找出错误之处,然后让神奇的机器人重新再建造一座就是了。这里还有更深远的含义:假如开始时用的是陈旧的模型、未完成的设计,只要一遍遍地重复建造,其结果也会接近最终目标。对于一个马虎的观察者来说,可能难以分辩出未完成设计与已完成产品的不同之处。

我们对时间表的预测能力将逐渐减弱。建造成本要比设计成本更容易计算——我们知道安装一根钢梁的大概成本,以及需要安装多少根钢梁。随着可预测的任务大幅减少,难以预计的设计时间开始占据主导地位。结果就是生产速度变得更快,而原本可靠的进度表变得不再可靠了。

当然,经济竞争力的压力还是在起作用。建造成本问题消除之后,能快速完成设计的公司将在市场上获得优势,这项能力成为了工程公司的核心竞争力。不可避免的是,有些不精通设计的人看到一个未经验证的设计版本时,只看到了早日投放市场的好处,于是就会说“这个看上去够好了。”

一些处于生死边缘的项目将会被倾注更多的努力,但是,在多数情况下,消费者也学会了忍受未完成的设计。公司先卖出有缺陷的大楼与车辆,然后不断地派出神奇机器人给它们“打补丁”。所有这些都指向一个惊人的违反直觉的结论:我们的唯一假定是大幅削减建造成本,导致的结果却是质量恶化。

这不该让我们感到惊讶,因为上面讲到的故事正在软件业里上演。如果我们承认代码就是设计——它更像是创造性过程,而非机械过程,软件危机就如上所述。现在,我们还面临着设计危机:出于质量的要求,我们能创造出新的设计,但是,对如何验证它们却无能为力。来自于使用未完成设计的压力空前巨大。

幸运的是,这个模型也给我们提供了如何做得更好的线索。物理仿真等同于自动化测试,软件设计只有通过了一系列严格的测试验证之后才能算完成。为了使这些测试更加高效,我们找到了控制大型系统中巨量状态空间的方法。我们的希望在于改良的程序设计语言及设计的最佳实践。最后,还有一个无法回避的现实:伟大的设计是由伟大的设计者作出的,他们穷尽一生精通了该门技艺。代码也是如此。关于代码布局的麻烦事

Code Layout Matters

史蒂夫·弗里曼(Steve Freeman)

在若干年前,我从事着一份Cobol系统的维护工作。在那里,项目成员未经允许不得更改代码的缩进,除非找到修改代码的合理理由,因为曾有一次某个人在一特定列的起始行之前插入了一行代码,结果造成一些功能不能正常使用。这种情况甚至在编排格式误导入的时候也会发生,有时就是这样,因此,我们必须仔细阅读代码,因为我们不能完全相信它。这条指导方针对程序员而言就是一笔财富。

有一项研究表明我们把太多的编程时间用在了浏览和阅读代码上——用在找到要修改地方的时间,超过了实际写代码的时间,所以,我们想要对此进行优化。以下是三条优化措施:

易于检索

人们都擅长可视化模式匹配(这是我们祖先在大草原上识别出狮子时留存下来的行为残迹),这样,我就可以通过标准化让那些跟领域不直接相关的东西——多数商业语言带来的所有“附属复杂性(accidental complexity)”淡出背景。如果一段代码看似一样,那么行为也应当一样,那么,我的知觉系统就会帮我挑出其中的差异。这就是为什么我也会去观察代码约定,这个约定就是类的各部分该如何在一个编译单元里排列:常量、字段、公共方法、私有方法。

清晰的布局

我们都已经学会了花时间寻找合适的命名,因此,我们的代码已经尽可能清晰地表达了它做的事情,而不是仅仅罗列出步骤——是不是?代码的布局也是表达的一部分。第一步要做的是让整个团队使用同一个自动格式化器,随后,在我写代码时就可以对它作手工调整。如果团队成员间没有分歧意见,团队内部就能很快地普及“手工完成”的风格。格式化器并不能理解我的意图(我知道的,因为我曾写过一个),但是,换行和分组可以反映出代码的意图,而不仅仅靠语[1]法(Kevin McGuire开发的代码自动格式化器帮我挣脱了这方面的束缚。)。

紧凑格式

屏幕上显示的东西越多,我能看到的也越多,再不会因为拖动滚动条或切换文件而打断思路,这也意味在我在头脑里可以保存更少的状态。大段的注释和大片的空白对于八字名称(eight-character name)和行式打印机而言是有意义的,但是,我现在用的是 IDE,它有语法着色和交叉链接的功能。在这里,像素是我的制约因素,因此,我希望每一样东西都能帮助我加深对代码的理解,希望布局能帮助我理解代码,除此之外,别无它意。

一个非程序员朋友曾评论说代码看上去像诗一样。当看到那些真正优秀的代码时,我也有这样的感觉——文本里的一切都是有意图的,它帮助我理解整个想法。不幸的是,写代码不像写诗,它没有罗曼蒂克的想象。[1]译注:Kevin McGuire是Eclipse资深开发人员。代码审查

Code Reviews

马蒂亚斯·卡尔森(Mattias Karlsson)

你应该做一下代码审查。为什么?因为它能提高代码质量,降低差错率,但是,这并不是你能想到的全部原因。

许多程序员不太喜欢做代码审查,因为他们曾在这方面有过糟糕的经历。我见过很多组织要求所有代码在部署到产品环境之前,必须通过一个正式的审查。通常,审查是由架构师或首席开发人员来做的,实际应用中可以描述为:架构师审查了所有的一切。这一点写在公司的软件开发过程指导手册里,所以,程序员们都必须遵守。

一些组织很需要这样一个硬性的、正式的过程,但是多数组织做不到。在大多数组织中,这种做法是反生产力(counterproductive)的。被审查者会感到自己正被陪审团评判,而审查者不仅需要有时间阅读代码,还要有时间了解系统的所有细节,最终他们成了这个过程的瓶颈,弱化了审查的最终效果。

代码审查不仅仅是简单的更正代码错误,其目的应该是共享知识、建立统一的编码指导标准。与其他程序员共享代码能够使代码成为集体共有,任选一个项目团队成员在开发的间隙里通读代码,不仅是为了查找错误,更多的是在审查的时候学习代码,理解代码。

代码审查的时候态度要温和,确保评语是有建设性的,不是刻薄的。审查会议需要有不同角色的分工,避免团队成员的资历影响到代码审查,比如说一个人主要审查文档,另一个人审查异常处理,第三个人审查功能性。这样的做法可以让团队成员均分审查工作的负担。

每个星期都要有一个正式的代码审查日,用两个小时左右的时间开一次审查会议。会议采用简单的循环制模式,让审查者轮流执掌每一次审查会议,也别忘了让团队成员在每次审查会议上变换一下角

试读结束[说明:试读内容隐藏了图片]

下载完整电子书


相关推荐

最新文章


© 2020 txtepub下载