《程序员修炼之道》读书笔记

偶然在组里书柜里看到了这本书,听说是新人程序员的圣经,正巧自己最近也在找这种培养思维和习惯的书看,所以就打算趁闲暇看下,希望对自己有所帮助。

PS:看了几页,很多都是寓意的小故事,没法精确地摘抄某几个句子来表达思想,所以可以的话还是买本书看一看吧。

第一章 注重实效的哲学

在前言里,书里提到了两个提示,就放在这里记录一下:

  • 提示1:关心你的技艺。除非你在乎能否漂亮地开发出软件,否则其他事情都是没有意义的。
  • 提示2:思考!你的工作。在你做某件事情的时候思考你在做什么。

我的源码让猫给吃了

  • 提示3:提供各种选择,不要找蹩脚的借口。
  • 负责,责任是你主动担负的东西。如果你确实同意要为某个结果负责,你就应切实负起责任。
  • 在你问问题或者说做不到之前,先停下来,在头脑里预演一遍对话,想想其他人可能会说什么,把理由说给猫听,感觉理由是否合理,否则不要麻烦别人。

软件的熵

  • 熵是指某个系统中无序的总量。软件中的无序增长时,程序员们称之为“软件腐烂”。
  • 提示4:不要容忍破窗户。即不要留着低劣的设计、错误决策、糟糕的代码而不去修理。发现一个就修一个。
  • 置之不理会更快加速腐烂的进程。
  • 简单来说,就是大家都不会愿意做第一个弄脏东西的人,但如果东西已经有地方脏了,那就无所谓了,所有人都不会再在乎。

石头汤与煮青蛙

  • 提示5:做变化的催化剂。在有些情况下,你可能确切地知道要什么,但需要去处理整个事件的时候就会遇到拖延和冷漠,事情会变复杂,每个人都会护卫自己的资源。这时就是你拿出石头的时候,因为人们会觉得参与正在发生的成功更容易,让他们看见未来,你就能让他们狙击在你周围。
  • 提示6:记住大图景。不要像温水煮青蛙一样,要留心大图景,要持续不断地观察周围发生的事情,而不只是你自己在做的事情。

足够好的软件

  • 让你的用户参与权衡。
  • 提示7:我们所制作的系统的范围和质量应该作为系统需求的一部分规定下来。今天的了不起的软件常常比明天的完美软件更可取。
  • 不要因为过度修饰和过于求精而毁损完好的程序。

你的知识资产

  • 提示8:定期为你的知识资产投资。要点有定期投资、多元化、管理风险、低买高卖以及重新评估和平衡。
  • 可以设定一些目标,比如一年学一门新语言、一季度看一本技术书……
  • 提示9:批判地分析你读到的和听到的。要抓住学习机会,要批判地思考。

交流

  • 知道你想要说什么,规划想要说的东西。
  • 了解你的听众。
  • 选择时机。
  • 选择风格。
  • 让文档美观,并在制作过程中让听众参与。
  • 做倾听者。把会议变成对话。
  • 回复他人。
  • 提示10:你说什么和你怎么说同样重要。

第2章 注重实效的途径

重复的危害

  • 提示11:DRY-Don’t Repeat Yourself。系统中的每一项知识都必须具有单一、无歧义、权威的表示。
  • 无意的重复里提到,即使重复了也要把重复限制在局部,不要暴露给外界。
  • 提示12:让复用变得容易。

正交性

  • 在计算机技术里,正交是指某种不依赖性或解耦性,如果两个或更多事物中的一个发生变化,不会影响其他事物,这些事物就是正交的。比如在设计良好的系统中,数据库代码和用户界面就是正交的。
  • 提示13:消除无关事物之间的影响。
  • 编写正交的系统,可以提高生产率并降低风险。
  • 团队划分方面的一点建议就是将基础设施与应用分离,每个主要的基础设施组件(比如数据库、通信接口、中间件等)有自己的子团队。

可撤销性

  • 提示14:不存在最终决策。要考虑到决策的可撤销性。

曳光弹

  • 提示15:用曳光弹找到目标。
  • 总有改动需要完成,总有功能需要增加。
  • 曳光代码有很多优点:用户能够及早看到能工作的东西;开发者构建了一个他们能在其中工作的结构;你有了一个集成平台;你有了可用于演示的东西;你将更能够感觉到工作进展。
  • 感觉就是先搭个架子,再步步逼近。
  • 曳光和原型编程还有区别,原型编程是生成用过就扔的代码,而曳光代码虽然简约,却是完整的(当然我并没有理解……)。可以把原型制作视为在第一发曳光弹发射之前进行的侦查和情报搜集工作。

原型与便笺

  • 建立原型的原因是为了分析和揭示风险,并以大大降低的带来来为修正提供机会,就像是汽车制造商造的某种模型,概念图,只是为了展示某一个方面,但却不是真正能用的(这么一想感觉原型和曳光弹确实有区别好像……)。
  • 提示16: 为了学习而制作原型。原型的设计目的地是会大一些问题,可以忽略不重要的细节,比如制作GUI原型的时候就不要在意取到的数据正确与否。原型的价值不在于所产生的代码,而在于学习的经验教训。
  • 构建原型时,可以忽略以下细节:正确性、完整性、健壮性、风格。

领域语言

  • 提示17:靠近问题领域编程。
  • 可以通过两种不同的方式使用你实现的语言:数据语言和命令语言。
  • 没看太明白,感觉好像说的是写一串公用的话,然后利用这段话解析成所需代码。

估算

  • 提示18:估算,以避免发生意外。
  • 建议这样估算时间,十五天内用天,八周内用周,30周内用月,超出30周的时候请努力思考一下再估算。要选择能反映你想要传达的精度的单位。
  • 理解提问内容,建立系统的模型,把模型分解成组件,给每个参数指定值,计算答案,追踪你的估算能力
  • 提示19:通过代码对进度表进行迭代。

第3章 基本工具

纯文本的威力

  • 提示20:用纯文本保存知识。
  • 纯文本有两个主要缺点:1、与压缩的二进制格式相比,存储纯文本所需空间更多;2、要解释及处理纯文本文件,计算上的代价可能更昂贵。
  • 好处:保证不过时,杠杆作用,更易于测试。

shell游戏

  • 提示21:利用命令shell的力量。

强力编辑

  • 提示22:用好一种编辑器。
  • 编辑器特性:可配置、可扩展、可编程
  • 用好一款编辑器可以提高生产率。

源码控制

  • 提示23:总是使用源码控制。

调试

  • 要接受事实:调试就是解决问题,要据此发起进攻。
  • 提示24:要修正问题,而不是发出指责。
  • 提示25:不要恐慌。这是调试的第一准则。
  • 如果你目睹bug或见到bug报告时的第一反应是“那不可能”,那你就错了,不要把脑细胞浪费在以“那不可能”起头的思路上,因为已经发生了。
  • 要总是设法找出问题的根源,而不只是问题的特定表现。
  • 从何处开始,在解决问题时,可能需要搜集所有的相关数据,比如可能需要观察报告bug用户的操作,以获取足够程度的细节。
  • 测试策略,一旦你认为你知道了在发生什么,就到了找出程序认为在发生什么的时候了。
  • 开始修正bug的最佳途径是让其可再现。使数据可视化。
  • 调试器通常会聚焦于程序现在的状态,但有时需要更多的东西——可能需要观察程序或数据结构随时间变化的状态。
  • 橡皮鸭
  • bug有可能存在于第三方库——但不应该是你的第一想法。提示26:”select”没有问题。
  • 提示27:不要假定,要证明。
  • 当遇到令人吃惊的bug时,除了只是修正它之外,还需要确定先前为什么没有找出这个故障。考虑是否需要改进单元测试。
  • 如果bug是一些坏数据的结果,那是否能进行更好的参数检查来隔离它们。
  • 考虑造成这个bug的条件是否存在于系统中的其他地方。

文本操纵

  • 提示28:学习一种文本操纵语言,比如Perl(这两天迁移代码的时候刚遇到过)。

代码生成器

  • 提示29:编写能编写代码的代码。
  • 分为被动代码生成器以及主动代码生成器。
  • 被动代码生成器减少敲键次数,本质上是参数化模板,结果一旦生成,就变成项目中充分的源文件。
  • 通过主动代码生成器,可以取某项知识的一种表现形式,将其转化为你的应用需要的所有形式。

第4章 注重实效的偏执

  • 提示30:你不可能写出完美的软件。

按合约设计

  • 什么是正确的程序?不多不少,做它声明要做的事情的程序。用文档记载这样的声明,并进行校验,是按合约设计(DBC)的核心。
  • 提示31:通过合约进行设计。
  • 在设计时简单地列举输入域的范围是什么、边界条件是什么、例程允诺交付什么——或者更重要的,它不允诺交付什么——是向着编写更好的软件的一次飞跃。
  • 如果要对参数进行任何显式的检查,就必须由调用者来完成。

死程序不说谎

  • 防卫性编程。
  • 提示32:早崩溃。要崩溃,不要破坏。有很多时候,让你的程序崩溃是你的最佳选择。
  • 基本的原则:但给你的代码发现,某件被认为是不可能的事情已经发生时,你的程序就不再有存活能力。

断言式编程

  • 提示33:如果它不可能发生,用断言确保它不会发生。不过绝不要把必须执行的代码放在assert中。不要用断言代替真正的错误处理,断言检查的是绝不应该发生的事情。
  • 海森堡虫子:调试改变了被调试系统的行为。

何时使用异常

  • 异常很少应该作为程序的正常流程的一部分使用,异常应该保留给意外事件。
  • 提示34:将异常用于异常的问题。

怎样配平资源

  • 提示35:要有始有终。分配资源、使用它,然后解除分配。分配资源的例程也应该负责释放它。
  • 在嵌套的分配情况下:有两点建议,一是以与资源分配的次序相反的次序解除资源的分配。二是在代码的不同地方分配同一组资源时,总是以相同的顺序分配它们。这会降低死锁的可能性。

第5章 弯曲,或折断

解耦与德墨忒尔法则

  • 提示36:使模块之间的耦合减至最少。
  • 响应集的定义:类的各个方法直接调用的函数的数目。
  • 得墨忒尔法则:某个对象的任何方法都应该只调用属于以下情形的方法:它自身、传入该方法的任何参数、该方法内new创建的任何对象以及任何直接持有的组件对象。
  • 事实上,如果对得墨忒尔法则进行反转,使模块紧密耦合,有时就可以获得重大的性能改进。这就需要看情况来权衡。

元程序设计

  • 动态配置,提示37:要配置,不要集成。
  • 为一般情况编写程序,把具体情况放在别处——在编译的代码库之外。提示38:把抽象放进代码,细节放进元数据。
  • 没有元数据,你的代码就不可能获得它应有的适应性与灵活性。

时间耦合

  • 在设计时间要素时,有两个方面对我们很重要:并发和次序。
  • 我们需要容许并发,并考虑解除任何时间或次序上的依赖。
  • 提示39:分析工作流,以改善并发性。
  • 提示40:用服务进行设计。
  • 提示41:总是为并发进行设计。

它只是视图

  • 模块或类的一个好定义就是,它具有单一的、定义良好的责任。
  • 发布/订阅模式,Model-View-Controller模式:既让模型与表示模型的GUI分离,也让模型与管理视图的控件分离。提示42:使视图与模型分离。
  • 模型:表示目标对象的抽象数据模型。模型对任何视图或控制器都没有直接的了解
  • 视图:解释模型的方式,它订阅模型中的变化和来自控制器的逻辑事件。
  • 控制器:控制视图、并向模型提供新数据的途径,它既向模型、也向视图发布时间。

黑板

  • 黑板系统让我们能够完全解除对象之间的耦合,并提供一个论坛,知识消费者和生产者可以在那里匿名、异步地交换数据。
  • 提示43:用黑板协调工作流。

第6章:当你编码时

靠巧合编程

  • 避免靠巧合编程——依靠运气和偶然的成功——而要深思熟虑地编程。
  • 对于你的例程的调用,要只依靠记入了文档的行为。如果处于任何原因你无法做到这一点,那就充分地把你的各种假定记入文档。
  • 提示44:不要靠巧合编程。并非以明确的事实为基础的假定是所有项目的祸害。
  • 怎样深思熟虑地编程:1、总是意识到你在做什么。2、不要盲目地编程。3、按照计划行事。4、依靠可靠的事物。5、为你的假定建立文档。6、不要只是测试你的代码,还要测试你的假定。7、为你的工作划分优先级。8、不要做历史的奴隶,不要让已有的代码支配将来的代码。

算法速率

  • 提示45:估算你的算法的阶。
  • 不确定代码运行时间的时候可以试着运行它,并记录不同的输入和运行时间,把结果绘制成图标。
  • 提示46:测试你的估算。

重构

  • 代码需要演化:它不是静态的事物。
  • 无论代码具有下面的哪些特征,都应该考虑重构代码:重复、非正交的设计、过时的知识、性能。
  • 提示47:早重构,常重构。
  • 怎样进行利大于弊的重构:1、不要在重构的同时增加功能;2、在开始重构之前,确保拥有良好的测试,并尽可能经常地运行它们。3、采取短小、深思熟虑的步骤。

易于测试的代码

  • 我们需要从一开始就把可测试性构建进软件中,并且在把各个部分连接在一起之前对每个部分进行彻底的测试。
  • 提示48:为测试而设计。
  • 提示49:测试你的软件,否则你的用户就得测试。

邪恶的向导

  • 提示50:不要使用你不理解的向导代码。
  • 感觉说的就是不要使用你不理解的自动生成的代码。

第7章:在项目开始之前

需求之坑

  • 提示51:不要搜集需求——挖掘它们。
  • 找出用户为何要做特定事情的原因、而不只是他们目前做这件事情的方式。
  • 提示52:与用户一同工作,以像用户一样思考。
  • 需求不是架构,不是设计,不是用户界面,而是需要。
  • 提示53:抽象比细节活得更长久。
  • 管理需求增长的关键是向项目出资人指出每项新特性对项目进度的影响。
  • 要创建并维护项目词汇表。提示54:使用项目词汇表。

解开不可能解开的谜题

  • 提示55:不要在盒子外面思考——要找到盒子。在面对棘手的问题的时候,列出所有在你面前的可能途径。不要排除任何东西,不管它听起来有多无用或愚蠢。
  • 所需要的只是真正的约束、令人误解的约束、还有区分它们的智慧。

等你准备好

  • 提示56:倾听反复出现的疑虑——等你准备好再开始。

规范陷阱

  • 编写规范是一项重要职责。
  • 提示57:对有些事情“做”胜于“描述”。
  • 没有给编码者留下任何解释余地的设计剥夺了他们发挥技巧和艺术才能的权利。
  • 越是把规范当做安乐毯,进入编码截断就会越困难。

圆圈与箭头

  • 提示58:不要做形式方法的努力。
  • 应该使用形式方法,但要记住它只是工具箱里的又一种工具。
  • 提示59:昂贵的工具不一定能制作出更好的设计。

第8章:注重实效的项目

注重实效的团队

  • 认为项目的各种活动——分析、设计、编码——会孤立地发生,这是一个错误。它们不会孤立发生。它们是看待同一问题的不同方式,人为地分隔它们会带来许多麻烦。
  • 提示60:围绕功能、而不是工作职务进行组织。

无处不在的自动化

  • 提示61:不要使用手工流程。

无情的测试

  • 提示62:早测试,常测试,自动测试。
  • 提示63:要到通过全部测试,编码才算完成。
  • 我们需要查看项目范围测试的三个主要方面:测试什么、怎样测试以及何时测试。
  • 单元测试是对某个模块进行演练的代码。是所有其他形式的测试的基础。
  • 集成测试说明组成项目的主要子系统能工作,并且能很好地协同。
  • 验证和校验。
  • 资源耗尽、错误及恢复。
  • 性能测试,压力测试。
  • 可用性测试。
  • 怎样测试包括:回归测试、测试数据、演练GUI系统、对测试进行测试、彻底测试。
  • 回归测试把当前测试的输出与先前值进行对比。
  • 提示64:通过“蓄意破坏”测试你的测试。
  • 提示65:测试状态覆盖,而不是代码覆盖。
  • 提示66:一个bug只抓一次。一旦测试人员找到了某个bug,就应该是最后一次发现这个bug。增加新的测试,从此每次都检查这个特定的bug。

全都是写

  • 注重实效的程序员会把文档当做整个开发过程的完整组成部分加以接受。
  • 提示67:把英语当做又一种编程语言。
  • 提示68:把文档建在里面,不要拴在外面。
  • 一般而言,注释应该讨论为何要做模式、它的目的和目标。
  • 不应该出现在注释中的一些内容:文件中的代码导出的函数的列表、修行历史、该文件使用的其他文件的列表、文件名。
  • 在源文件里应该出现的最重要的信息之一是作者的姓名。
  • 文档和代码是同一底层模型的不同视图,但视图是唯一应该不同的东西。

极大的期望

  • 在现实中,项目的成功是由它在多大程度上满足了用户的期望来衡量的。
  • 提示69:温和地超出用户的期望。

傲慢与偏见

  • 提示70:在你的作品上签名。