《程序员修炼之道 - 从小工到专家》读书笔记

iOS成长之路

共 14812字,需浏览 30分钟

 ·

2021-07-10 23:42

本篇文章是对《程序员修炼之道 - 从小工到专家》一书的总结和解读。该书作者是 Andrew Hunt 和 David Thomas。他们都是敏捷宣言的17个创始者之一。Andrew还是敏捷联盟(Agile Alliance)的创始人。David 则是著名的 DRY(Don't Repease Yourself) 一词的发明者。这本书也广泛出现在各类计算机推荐书单之中,其受欢迎程度不言自明。

该书目前有两个版本,我阅读的是第一版:

第二版是这样的:

两版内容稍有不同。

刚毕业那会读过一遍,但有很多地方没看明白,感觉云里雾里的。今年在制定每天的阅读分享计划时一下就想到了它,结合一定的工作经验再次阅读这本书,然后对每个章节都进行思考和总结,感觉才算是挖掘出了它的价值。这本书写作时间虽然比较久了(初版1999年),里面引用的一些语言,一些技术框架都已经很少使用甚至过时了,但本书强调的如何做一名注重实效的程序员,及众多建议却不会过时。

以下内容是对各个章节的总结,篇幅有些长,12000+ 字,大家可以按顺序或者按自己感兴趣的章节进行阅读。

第一节:我的源码让猫给吃了。

1、开发过程中出现未曾预料的技术问题,交付晚了等情况,没关系,这些是无法避免的。发生了,我们就要尽可能想方设法地职业的去处理它们。程序员这个职业需要诚实和坦率,要敢于承认自己的错误。

2、要对担负的东西负责,如果某些东西真的超出了你的控制范围可以不处理,需要尽早提出这个不可控的点。自己职责所在的事情就需要为其结果负责。当结果不达标,比如磁盘垮了,但你却没有备份代码,那这就是你的错。不要为出错的情况找借口,想老板说"我的源码让猫给吃了”,对问题没有任何帮助,而要向他们提供可行的解决方案,做什么能够最大的挽回局面。

第二节:软件的熵

1、熵是一个热力学概念,指的是在某个系统中的“无序”的总量,热力学定律指出宇宙中的熵总是倾向于最大化。软件工程里中也存在这么一个定律,工程越庞大,代码的“无序”状态越严重。

2、破窗理论指出,当一个东西本身就破旧时,不但没人爱惜,还会朝他仍石头,导致更多破窗。软件开发中也一样,如果我们项目留有很多“破窗户”(低劣的设计、错误的决策、糟糕的代码),之后接手的人也会倾向于是它变得更糟糕。如果代码很漂亮,你自己以及之后接手的人,都可能会格外注意,不把它弄脏的。所以我们应该尽早处理工程中遗留的问题。

第三节:石头汤和煮青蛙

1、三个士兵返乡,路上饿了,路过一个村子,想跟村民借点吃的,但村民粮食贫乏不愿意出借。士兵们没有气馁,他们煮开了一锅水,往里面放了几块石头。村民好奇为他们在干嘛,士兵解释,这叫石头汤,如果能放点胡萝卜的话会更好喝。村民跑回家拿来了胡萝卜,士兵说如果放些土豆会更美味,又有人跑回家带来了土豆。后面又有人加了别的东西,最后士兵和大家一起吃了一顿饱饭。

2、有时候你确切的知道自己需要什么以及怎么做,但请求许可这件事往往会遭遇拖延和漠然,每个人都会护卫他们自己的资源,这让事情变得复杂,这叫“启动杂役”(start-up fatigue)。这时候我们不应该等着所有事情都准备好,而应该先拿出“石头”煮起来,就是想让事情启动起来。只要是有益的事情,你把做出的一部分结果拿给别人看,然后告诉他们如果加的别的什么会更好,大家一般都会帮忙的。

第四节:足够好的软件

1、使质量成为需求问题。很多时候对于质量的评估都是开发人员在进行,我们对质量要求低,交付时会出现很多问题,我们对质量要求高,会很大程度延误工期。所以指定需求时,把质量这一块考虑进去,在商定的时间内,由产品或者客户决定他们可以接受的质量是什么样的。

2、没有完美的软件,应该知道何时止步。今天了不起的软件常常比明天的完美软件更可取。及早让客户使用,他们的反馈常常会把你引向更好的解决方案。

第五节:你的知识资产

1、本杰明·富兰克林说过:知识上的投资总能得到最好的回报。这没问题,但遗憾的是知识是有时效的资产,特别是计算机领域。我们可以把我们了解的技术实现、工作经验视为知识资产,并使用管理金融资产的形式管理这些知识。

2、经营知识资产可以从以下方面进行:

  • 定期投资:定期投入时间学习,即使很小的投资也是很重要的。
  • 多元化:作为底线我们需要对当前所从事的技术熟练掌握。但不要就此止步,技术的发展变化很快,掌握的知识越多,就越能更好的进行调整,赶上变化。
  • 管理风险:不要把所有的“技术鸡蛋”放到一个篮子里。
  • 低买高卖:新技术流行之前就掌握它往往比之后跟风再学得到更大的回报。

这些知道方针里最重要也是最简单的就是:定期为你的知识资产投资。

3、具体方案介绍

  • 每年至少学习一种新语言。
  • 每季度阅读一本技术书籍,习惯之后可以一个月就阅读一本。
  • 也要阅读非技术书籍,记住计算机是由人使用的。
  • 在本地大学或者网上系统地学一门课程。
  • 体验不同的环境,如果你只在 Windows 上工作,可以试下 Unix。如果你只使用某一种 IDE 那可以试试其他 IDE。

第六节:交流

1、知道你想要说什么

当我们面临会议,重要通话,或者只是撰写技术文档,问下自己你要表达的中心想法是什么,围绕这一点进行展开。

2、了解你的听众

比如你要做一场分享,你可以按照 WISDOM 的形式思考这几个问题:

  • 你想让他们学到什么
  • 他们对你讲的什么内容感兴趣
  • 他们有多富有经验
  • 他们需要多少细节
  • 你想要谁拥有这些信息
  • 你如何促使他们听你说话

3、选择风格

传达一个消息,可以是正式的邮件,黑板上的绘图,口头描述,及时消息,选一个适合你的目的的方式。

4、让文档美观

技术文档不光要注意内容也要注意形式,使用 LaTeX 或者 Markdown 进行排版。

5、让听众参与

引导他们提问,以问答的形式推进分享进程。

6、回复他人

你说什么和你怎么说同样重要。尽量不要忽视别人的询问,即使回复他们稍后再联系都会更好一些。

第七节:重复的危害

1、可靠的开发软件,并让我们的开发更易于理解和维护的唯一途径,是遵循我们称之为 DRY 的原则:系统中的每一项都必须具有单一、无歧义、权威的表示。

DRY 是 Dont’t Repeat Yourself 的缩写。

2、重复的产生通常有以下种类:

强加的重复。开发者觉得他们无可选择,其实是有一些方法让我们避免重复的。

无意的重复。开发者没有意识到他们在重复信息。这个需要通过提高代码意识或者 CR 进行减少。

无耐性的重复。开发者偷懒,因为重复可以让事情更容易。有时往往会遇速则不达,在这类重复面前我们应该更慎重。

开发者之间的重复。同一个团队或者不同团队的几个人重复了同样的信息。需要一个统筹的人引导大家交流,提供一个中央区域,管理维护公共代码。

第八节:正交性

1、正交性是一个从几何学中借鉴而来的术语,如果两条直线相交成直角,他们就是正交的。这在向量中的解释是沿着一条直线移动,你投影到另一条直线上的位置不变。

在计算机中,该术语用于表示某种不相依赖性或解耦性。

2、正交的好处是它提高生产效率,各个组件不相互依赖,使得改变得以局部化,促进复用,对于正交组件进行组合也可以提高生产效率,同时它还降低了代码的风险。

3、延伸开来,项目团队的配合也应该遵循正交性。如果成员之间任务重叠较多容易让大家疑惑问题和责任的归属如何划分,这会造成配合的效率低下。

代码设计的时候也应该尽可能考虑正交性,这需要结合一些特定的设计模式以达成目的。

第九节:可撤销性

如果某个想法是你唯一的想法,再没有什么比这更危险的事情了。在设计软件时,我们需要为可能出现的某种错误做准备,比如数据库的更换,开发平台的更换。这需要我们设计之初就考虑到构建一个相对灵活的架构。

第十节:曳(ye)光弹

1、在黑暗中使用机枪射击有两种方式。

方式一:你需要知道目标准确的位置,然后考虑当时的温度、湿度、气压、风力等一系列因素,计算完位置之后进行射击。

方式二:使用曳光弹,发射时,曳光弹中的磷点燃,会照亮它经过的地方和最终位置,我们用曳光弹确认位置之后,就不需要那些繁杂的计算,直接使用机枪进行射击。

2、在黑暗中发光的代码。通常一个项目的开发是非常复杂的,如果只是一个模块一个模块的开发,我们可能直到最后才能确认项目运行情况。更好的做法是,我们要让系统尽早的跑起来,然后根据需要给它完善细节。这样会有以下好处:

  • 用户能够及早看到能工作的东西。
  • 开发者构建了一个能在其中工作的结构。
  • 你有了可用于演示的东西。
  • 你能够感觉到工作进展。

第11节:原型与便笺

1、原型是你可以在忽略细节的情况下,考虑项目走流程,主要使用场景,他们是否正确,是否可行。通常也可以用用于演示

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

3、制作原型甚至不需要编码,你可以用便笺,白板上制作原型。制作原型时你需要尝试回答以下问题:

  • 主要组件的责任是否得到了良好定义?是否恰当?
  • 主要组件间的协作是否得到了良好的定义?
  • 耦合是否得以最小化?
  • 你能否克服确认重复的潜在来源?
  • 接口定义和各项约束是否可接受?

第12节 领域语言

1、计算机语言会影响你思考问题的方式,以及你看待交流的方式。

2、领域语言通常是为了简化流程,用于配置或者控制应用程序。

3、DSL 可以理解为一个小型语言,它可以是扩展自已有语言。

4、在设计一种 DSL 时,考虑可读性还是简单性时,主要权衡的应该是可扩展性和可维护性,因为通常大多数应用都会超出预期的使用期限。

第13节 估算

1、通过学习估算,并将此技能发展到事物的数量级有直觉的程度,你就能展现出一种魔法般的能力,确定他们的可行性。

2、多准确才足够准确?130 个工作日和大概 6 个月,是不同的,显然,前者表示的精度更高。我们在做估算的时候也需要选好描述估算时间的单位值。

3、估算结果怎么来呢。

首先需要确认你是否理解了需求所涉及的各个方面,这个是前置条件。

然后你需要建立系统模型,在这个系统中,把模型分拆成各个组件,然后给每个参数设置定一个值,最后根据模型计算一个时间。

4、模型应该是一个动态的,它像一个人工智能模型,你需要持续不断的训练它,才能使它真正准确起来。每次的估算都需要记录,反思估算效果,找出影响因素,加入新的影响项或者调整对应参数。

5、被要求进行估算时间时,我们可以这样回答:我等会儿回答你。然后花点时间仔细检查我们在这一节描述的步骤,你总能得到更好的结果。

第14节 纯文本的威力

本节是第三章:基本工具,首节内容,章节介绍里有一句话:

许多新程序员都会犯下错误,采用单一的强力工具,比如特定的集成开发环境(IDE),而且再也不离开其舒适的界面。这实在是一个错误。我们要乐于超越IDE所施加的各种限制。要做到这一点,唯一的途径是保持基本工具集的“锋利”与就绪。

1、纯本文由可打印字符组成,人可以直接阅读和理解其形式。

这里强调可打印含义是字符时经过编码的可阅读字符,而不是二进制。这在现在看来几乎是不用争辩的,谁还会用二进制存储信息,但当时计算机算力和存储都有限,纯文本会占据更多空间,解码会耗费算力。但源于技术的发展,这些都是可以忽略不计了。

2、纯文本的优点之一:保证不过时。这一点需要我们扩展纯文本能够自描述。自描述的含义是它自己能告诉我们它的含义。

123-45-6789

<SSNO>123-45-6789</SSNO>

上面的例子中下面一条就是自描述的,我们能通过 SSNO 推断出这里存的就是社会保障号,另外根据 <SSNO> 这一标记我们可以很轻松的将对应内容提取出来。

3、另外两个优点是杠杆作用和更易于测试。这里说的是我们可以利用各种工具 diff、fc、git,或一些语言例如 Python 等对纯文本进行各种调整和查看工作。

第15节 Shell 游戏

1、对于操纵文本的文件的程序员,命令 Shell 就是工作台。我们可以利用 Shell 启动各种应用、搜索文件、查询系统状态,甚至还可以构建复杂的宏命令,完成各种常见活动。

2、对于习惯 GUI 的开发者来说一直使用 Shell 有些极端。GUI 的好处是所见即所得,但他的缺点却是,所见即全部所得。GUI 环境通常受限于它们的设计者想要提供的能力。

3、比如我们想要做一件事:在一个代码仓库里,查找上周没有修改过的,使用了 awt 库的 java 文件。

如果使用Shell,可以执行:

find . -name ‘*.java’ -mtime +7 -print | xargs grep 'java.awt'

如果使用 GUI,你可以设想一下,这个过程会很麻烦,也很容易出错。

4、Shell 可能比较晦涩,但是掌握之后它能很大程度提高你的效率。Shell 可以做各种组合搭配,然后构建一个命令序列,让常做的事情自动化。

第16节 强力编辑器

1、我们认为你最好是精通一种编辑器,并将其用于所有编辑任务:代码、文档、备忘录、系统管理等等。

进行编辑活动时,你不必停下来思考怎样完成文本操作,编辑器将成为你双手的延伸,键会在滑过文本和思想时歌唱起来。

这就是我们的目标。

2、好的编辑器应该具有这些特性:可配置、可扩展、可编程、语法突显、自动缩进、类IDE特性。

3、编辑器对生产效率是有影响的。试想当我们需要一个字符一个字符或者一行一行移动时,按一次键,就以词,行,块的单位移动,显然效率更高。

4、然后做什么。选一种强大的编辑器,好好学习它。不断学习,减少你敲击的次数。设法扩展它,让它能胜任更多任务。

推荐两款编辑器:vim、Emacs

第17节 源码控制

1、原谅我们犯错的按钮是 UNDO 键,通常他们还支持多级 UNDO 和 REDO。而源码控制系统就相当于一个巨大的 UNDO 键,一个项目级的时间机器。源码控制系统(SCCS)能够追踪你在源码和文档中做的每一项改动。

2、应该总是使用源码控制,即使团队只有你一人,即使项目很小。

3、可以尝试的源码控制系统有 CSV、RCS、ClearCase 等。(那时 Git 还没流行起来)

第18节:调试

1、调试心理学。调试的目的是解决问题,不要因为别人提出 bug 而发起进攻。

2、当你目睹 bug 发生或者看到 bug 报告时,第一反应不要是“那不可能”。很明显已经发生了,把时间用在思考它为什么产生上面。

3、使数据可视化。例如循环引用问题,如果可视化的话可以很轻易地进行排查。

4、跟踪代码。发生 crash 我们能够查看系统的调用堆栈,但这些数据不一定够。对于非 crash 类错误,因为没有抛出,我们甚至不知道发生了什么。所以添加所谓的跟踪日志很有必要,这类日志最好采用统一规范,便于后期我们可以自动解析他们。

5、橡皮鸭,也叫小黄鸭调试法。遇到无法定位的问题时,对着小黄鸭(屏幕)解释自己的实现逻辑,很可能在说的过程中你自己就发现了问题所在。

6、不要第一时间怀疑 OS,IDE,三方库的问题,他们出问题的概率比你代码出问题概率小得多。我们应该首先确认和排查自己的问题。

7、对 bug 原因进行复盘。修复了一个 bug,不要就让它结束了,想一下,为什么它会出现了,如何避免。定位过程如果耗时较长,也需要复盘下为何花费了那么长时间,以及后续如何优化。

第19节 文本操纵

1、学习一种文本操纵语言。文本操作语言对于编程的意义,就像是刳刨机对于木工活的意义。

2、文本操作的案例。

  • 我们的测试数据有好几万条,散落在不同文件,如果需要进行合并并转换为特定格式,手动处理是无法想象的。但如果使用 Perl 几个小时就可以完成。
  • 数据库 schema 维护。可以写一组 Perl 脚本读取数据库 schema 定义的纯文本文件,根据它生成,用于创建数据库的 SQL 语句。schema 的 XML 版本等
  • 生成 web 文档。可以编写 Perl 程序,分析数据库 schema,C 或 C++ 源文件,及其他资源,生成 HTML 文档。

文中很多案例使用 Perl,这些工作也可以使用 Python 代替或者 Shell 里的 awk,sed 代替。

第20节 代码生成器

1、作为程序员,有时会需要我们在不同地方重复相同信息。如果出现这种情况,你就可以考虑构建代码生成器了。代码生成器就是编写能编写代码的程序。

2、有两类代码生成器:被动代码生成器和主动代码生成器。

3、被动代码生成器是独立执行的。它可以用来生成模板,版权声明,每个新文件的标准注释等等。

4、主动代码生成器会在每次需要其结果时被使用。比如根据数据库 schema 创建代码。

5、代码生成器不一定要生成代码,它可以用来输出任何格式的内容,比如 HTML、XML、纯文本等。

比如 iOS 里的三方库 R.Swift[1] 就是一个根据资源名自动生成对应结构体的主动代码生成器。

第21节 按合约设计

1、注重实效的程序员会不信任自己,所以他们针对自己的错误行为进行防卫性编码。

2、按合约设计(Design By Contract,简写DBC)是 Bertrand Meyer 为 Eiffel 语言发展的概念。它的核心是用文档记载模块的权利与责任,并进行校验。它的目的是对函数做一些前置检查和后置保证,结合编译器的支持,我们能够尽早的发现代码问题。

3、DBC 有三个概念。

前条件:为了调用例程必须为真的条件。

后条件:例程保证会做的事情,其完成时的状态。

类不变项:其确保从调用者的视角来看,该条件总是为真。

4、Java 中的 iContract 框架是专为 DBC 设计的,它通过注释里的 @pre、@post、@invariant 声明这三个概念。它会读取注释并生成包含断言逻辑的源文件。Eiffel 则是通过 require、ensure、is 三个值表示对应概念。但是支持 DBC 的语言真的很少。

第22节:死程序不说谎

1、对待程序我们通常会有“它不会发生”的心理状态,这会导致我们忽视一些问题。对于注重实效的程序员来说,如果我们忽略了一个错误,将是非常糟糕的事情。

2、我们一些异常情况,我们应该及早崩溃,用于强调问题的存在。

3、引起崩溃的时候不要造成破坏,比如申请的资源还没有释放等情况。

4、死程序带来的额危害通常比有隐患的程序要小得多。

第23节 断言式编程

1、如果它不可能发生,用断言确保它不会发生。

assert(string != NULL)

断言里写的为真的条件,当不为真时触发断言,程序退出。

2、断言检查的是决不应该发生的事情,而不是错误处理。

3、断言应该一直开着,不要在线上环境关掉它。

断言对应的是一种强提示,它迫使我们必须遵守。像是单元测试,我们通常都使用断言的形式进行检查。

第24节 何时使用异常

1、异常很少应作为程序的正常流程的一部分使用,异常应该保留给意外情况。如果移除了所有的异常处理器,代码就无法运行,那说明异常正在被用于非异常情况中。

2、是否应该使用异常取决于实际情况。比如打开文件,文件不存在,是否应该发生异常?如果文件应该在那里,那么异常就有正当理由。如果不确定文件是否在那里,返回错误就可以了。

第25节 怎样配平资源

1、对于资源(内存、事务、现成、文件、定时器等)的管理要有始有终,你分配了对应的资源,就需要考虑对应的解除逻辑。要有始有终。

2、嵌套的资源分配,应该使用与分配次序相反的顺序进行解除。

3、异常的配平需要避免违反 DRY 原则。例如文件打开的异常情况,会导致 try..catch 有两条路径,那如何避免在正常流程和 catch 流程都处理 error 情况呢?C++ 可以依赖对象自动析构的特性,Java 可以依赖 finally子句。

4、当无法配平资源时,需要设定一个规则,决定谁为某个聚集数据结构中的数据负责,以及如何负责。这里有点类似引用计数方案,无引用时释放。

5、自动化检查资源配平状态,可以依赖一些三方工具。

第26节 解耦与得墨忒(tei)耳法则

1、把你的代码组织成最小单位(模块),并限制他们之间的交互。如果随后必须替换某个模块,其他模块仍能够继续工作。

2、应使耦合减至最少。对象间直接的横贯关系,有可能很快带来依赖关系的组合爆炸。比如对某个模块的“简单”改动会传遍系统中的一些无关模块。

3、函数的得墨忒耳法则,它规定了某个对象的任何方式都应该只调用属于以下情形的方法:

  • 它自身
  • 传入该方法的任何参数
  • 它创建的任何对象
  • 任何直接持有的组件对象

4、得墨忒耳法则的好处是它使得代码的适用性更好,更健壮,但这也有一定的代价。如果某些解耦的操作很复杂,或者解耦带来某些时间和空间的重大开销,这时就需要根据实际情况考虑,可以暂时舍弃该法则。

第27节 元程序设计

1、元数据是关于数据的数据,即对应用进行描述的数据。典型情况,元数据在运行时,而不是编译时被访问和使用。

2、我们想要让我们的系统变得高度可配置,像是屏幕颜色,提示文本等,这些应该作为配置项而不是作为代码集成到项目中。

3、以声明方式思考(规定要做什么,而不是怎么做),并创建高度灵活的可适应的程序。结合元数据就是,将抽象放进代码,细节放进元数据。

4、Enterprise Java Beans 是一个用于简化分布式、基于事务的环境中的编程框架。它处理了不同机器、在不同数据库供应商之间,不同线程及复杂平衡的事务。它的使用只需我们编写一个 bean,并将其放到 bean container 中。

5、更好的协作式配置是让应用自身适应其环境,进行动态配置。

第28节 时间耦合

1、时间耦合就是关于时间的各种事项。

2、软件设计中,时间的角色通常有两方面对我们来说很重要:并发(事情同一时间发生)、次序(事情在时间中的相对位置)。我们期望的是要容许并发,并考虑解除任何时间次序上的依赖。

3、可以选择使用 UML 活动图进行工作流分析,以改善其并发性。

4、在设计架构时,用服务进行设计而不是组件。饥饿的消费者模型是在多个消费者进程间进行快速而粗糙的负载平衡的一种有效途径。

5、编写线性代码,我们很容易做出一些假定,把我们引向不整洁的编程。但并发会迫使你更仔细的对事情进行思考。

6、尽可能使用线程安全的类,开发时也应尽可能设计线程安全的类。

第29节 它只是视图

1、我们都知道应该将程序分而治之,划分成不同模块。这里模块(或类)的定义是,具有单一的定义良好的责任。那如何在不同模块之间进行通信,处理事件呢?有以下两种方式。

2、发布/订阅模式,又叫 Observer(观察者)模式。它的工作模式是,由订阅者 Subscriber 向发布者 Publisher 进行注册,注册之后,Publisher 的事件会通知到 Subscriber。未注册和解除注册将不会收到之后的事件通知。

3、Model-View-Controller 是一种将模型与表示模型的 GUI 分离的架构模型,它能有效降低数据和视图之间的相互影响。

第30节 黑板

1、设想侦探破案的过程,他借助于一块黑板,把不同线索写出来;其他侦探也可以写下自己的推断和已掌握的案情细节。所有这一切串联起来将共同帮助案件侦破,但不同的线索之间是可以独立进行的。

2、这里的黑板可以抽象为一种处理事件的模型。不同于原始的工作流需要考虑各种状况,不同组合,先后顺序等,黑板系统只管写入,读取,查询,通知等基础功能,任意符合条件的事件都可以进入这个系统。

3、黑板模型也是一种解耦形式。

第31节 靠巧合编程

从本节开始进入书目的第6章,本章主要讲在编码时应该注意的各类事项。传统智慧认为,项目一旦进入编码阶段,工作主要就是机械的把设计转换成可执行语句。我们认为,这种态度是许多程序丑陋、结构糟糕、不可维护的最大一个原因。编码不是机械工作,要想让程序长久无误的运行,每一分钟都需要做出决策,且需要对这些决策进行仔细的思考和判断。

1、靠巧合编程即代码正好是可运行的,至于为什么能够正常运行,却不清楚。这是我们应该极力避免的。

2、在打算重构某个看起来有问题的代码时,我们会面临这样的疑惑,是否有必要冒着把能工作的东西弄糟的风险呢?这时我们可以考虑一下几个理由:

  • 它也许不是真的能工作,只是看起来能工作。

  • 你依靠的边界条件也许只是一个巧合。

  • 多余和没必要的调用会让你的代码变慢并增加新 bug 的风险。

3、如何深思熟虑的编程,有以下建议:

  • 总是意识到你在做什么。
  • 按照计划(设计)行事。
  • 依靠可靠的事物而非假设。
  • 不要只是测试你的代码,还要测试你的假定。
  • 不要让已经做完的事情限制你的下一步,做好重构的准备。

第32节 算法效率

1、注重实效的程序员几乎每天都要使用估算,估算的资源包括:时间、处理器、内存等等。

2、估算算法即是我们熟知的时间复杂度,用O()表示,它有以下几种常见类型。

  • O(1),常量时间,不随数据的多少变化
  • O(n),线性时间,简单的循环
  • O(m*n),嵌套循环
  • O(log(n)),二分法,平衡二叉树的查询
  • O(nlog(n)),分而治之,快排
  • O(2^n),指数级,斐波那契数列

3、不同的时间复杂度在达到一定数量级的时候将相差很多,所以某些情况我们要想方设法优化算法的效率。我们主要需要关注的是是复杂度的阶。在确认了算法之后,还需要对其进行测试。

4、最好的并非总是最好的,是否使用最优算法,还需要根据我们遇到的实际情况。有时数据量很小的情况,算法的效率是可以忽略不计的。

第 33 节 重构

1、重写、重做和重新架构代码合起来,称为重构。

2、当代码出现以下特征,就应该考虑重构了:

  • 出现重复内容,违反DRY原则。
  • 非正交的设计。
  • 知识过时了,或者你对某部分的了解更深一步。
  • 对性能造成了影响。

3、重构的原则:早重构、常重构。重构面临的敌人通常都是时间,但这个借口并不成立,因为之后由此引发的时间额外消耗很可能更多。

4、如何重构。

  • 不要试图在重构的同时增加功能。
  • 重构之前,确保拥有良好的测试。
  • 采取短小,深思熟虑的步骤,不要一次改动太多内容。

第34节 易于测试的代码

1、软件 IC 是人们在讨论可复用性和基于组件的开发时喜欢使用的比喻。意思是集成电路芯片可以很容易的进行组合,我们希望软件开发也能达到这个效果。芯片的设计有完善的测试,同样的软件开发也可以做同样的事情。

2、针对合约进行测试及为测试而设计,即 TDD 测试驱动开发。

3、编写单元测试,对比较大的项目,将每个测试都放进一个子目录。

4、使用测试装备。构建一套完善的测试体系,它能够记录测试状态,分析输出结果是否符合预期,以及选择和运行测试。

5、推进测试文化,尽可能完善地测试你的软件,否则你的用户就得替你测试。

第35节 邪恶的向导

1、这里的向导指的是那些用于帮助我们构建程序自动生成的代码,通常他们还被称为脚手架。为什么称向导(wizard)是邪恶的呢,这是因为通过工具生成的代码,很容易被我们忽略,在这种情况下你编写的过程更倾向于靠巧合编程。

2、这里不是抵制向导代码,而是在强调,不要使用你不理解的向导代码。如果使用,一定要清楚它的机制。

3、开发每天都在使用不完全理解的事物,比如集成电路的工作原理,处理器的中断结构、用于调度的算法、各种系统库的工作机制等。需要注意的是,这些属于底层依赖,他们也是向导,但不是应用本身的一部分,我们可以对这部分有所了解,但他们不属于邪恶的向导。

第36节 需求之坑

从本节开始进入了第七章节:在项目开始之前。本章节讨论了在项目开始之前的一些建议。

1、完美,不是在没有什么需要增加,而是在没有什么需要去掉时达到的。这句话的一种解读时,不要搜集需求,需求太多,容易让我们抓不住重点,更应该深挖需求,围绕核心功能不断打磨。

2、挖掘需求,需要我们与用户一同工作,像用户一样思考。

3、制定需求文档。看待用例的一种方式是强调其目标驱动的本质,它强调的是要重视需要做成什么以及需要什么条件。需求文档最好配一些UML用例图。

4、需求的制定不能太具体,要保持一定的抽象。需求不是架构,不是设计,需求只是需要。这个有点类似于开发中的面向接口而不是面向具体实现编程。

5、维护词汇表。“客户”和“顾客”,可能表达不同的含义,但如果混用会让人迷惑,我们可以维护一个词汇表,专门用户描述他们的具体含义。

6、把需求文档发布到内网,参与人员都可以随时查看和提出意见。

第37节 解开不可能解开的谜题

1、戈尔迪斯结号称是没人能解开的结,后来亚历山大大帝来了,用剑劈开了这个结。

2、面对看似不可能解决的问题,一定要转换思路,不要受任何先人之见影响。不要在盒子外面思考,要找到盒子。

3、有时你会发现,自己在处理的问题比你以为的要难得多,总会感觉一定有更容易的方法。这时你可以退回一步,问问自己:

  • 有更容易的方法吗
  • 你是在解决真正的问题,还是被外围的技术问题转移了注意力
  • 这件事情为什么是一个问题
  • 是什么使它如此难以解决
  • 它必须以这种方式完成吗

很多时候,对需求的重新诠释能让整个问题全部消失— 就像戈尔迪斯结。

第 38 节:等你准备好

1、倾听反复出现的疑虑。当你遇到一个反复让你疑虑的问题,需要注意它,给自己时间去理解,之后它可能就会变成某种更坚实的东西。

2、对于某些东西,我们可能不愿意轻易做出承诺,总希望再等等,更多意见的提出。但这很可能是一种拖延,怎么区分是有效的等待还是拖延的接口呢?我们应该快速地构建原型,并进行推延,可能很快我们就找到了更好的解决方案。

第 39 节:规范陷阱

1、编写规范是一项重要的职责,但问题是很多人可能会陷在这里,不断地增加规范项。我们可以做这样一个尝试,写一份简单的描述,告诉别人怎样系鞋带。

这可能是一份并不能帮助他人的描述,因为对有些事情“做”胜于“描述”。因为无意识的行为更快,考虑规范反而会拖慢进度。

2、对待开发文档也一样,不要编写过于详细的规范。因为很可能开发者在思考某个问题时会想到两种不同方案,经过简单对比,选择一个更优的那个。但面对严格的规范文档,一步步思考,这更可能束缚开发者的发挥。

第 40 节:圆圈与箭头

1、设计文档里的圆圈和箭头用来解释他们指代的作用,但这还有可能是推翻我们原先设定的证据。感觉这个是承接上一节的内容,不要被以前的假设和设计所限制,留有一定的弹性空间。

2、我们相信,盲目地采用任何技术,而不把他们放进你的开发实践和能力的语境中,这样的处理日后可能会让你后悔。

3、不要迷信工具以及各种方法学,注重实效的程序员会批判地看待他们,并从中提取精华,融合成每个月都在变得更好的一套工作习惯。

第41节 注重实效的团队

1、书籍的前几章讲了几条如何成为注重实效的开发者的建议,当然他们也对团队有所帮助,如果个体都是注重实效的,那他对整体起的作用更大。

2、不要留破窗户:作为整体的团队更不应该容忍代码质量的问题,不规范的不在乎质量的团队,很有可能把那些注重实效的开发者带偏。

3、煮青蛙:整体中的个人更难觉察到作为团队所存在的问题,可以指定一个“检测员”,让他去检查团队整体进度,依赖项的准备情况,各个环节的配合等内容。

4、交流:杰出的项目团队往往有着截然不同的个性,更能与其他团队进行配合。有一个简单的帮助团队凝聚力的方法:创立团队品牌,以品牌代指整个团队。

5、不要重复你自己(DRY):由于个人理解程度的不同或者新成员的加入,团队总会面临重复的内容,适当的指派一名管理员,让他专门维护这些资料,所有对此有疑问的人都不必自我寻找,只要去找管理员就行了。

6、正交性:对于较大的团队,更应该通过功能进行组织划分,而不是工作职务。比如开发多个项目,会有多名开发,多名测试,多名设计,他们之间更应该按照具体项目进行划分,一个项目的开发,测试和设计为一个小团体。

7、自动化:确保一致性和准确的一种很好的方式是使团队所做的每件事情都能自动化,如果还没有做到那就尝试去做。

8、知道如何停止绘画:团队是由个体组成的,给每个成员足够的空间,并支持他们,而不是一直给他们具化各种需求。

第42节 无处不在的自动化

1、文明通过增加我们不加思索就能完成的重要操作的数目而取得进步。— 阿尔弗雷德·诺思·怀特海

2、我们应该在尽可能多的场景下使用自动化,因为人工流程及不能保证一致性也无法保证重复性。

3、在一些特定场景下我们可以选择适当的工具进行自动化处理:

  • 执行周期任务时可以使用 Cron
  • 项目编译时虽然可以使用 IDE,但是 Makefile 更适合自动化场景
  • 使用生成代码解决不得不做的重复性代码问题
  • 构建自动化,其实就是目前比较常用各类 CI/CD 工具
  • 自动化管理,这是一种更自由的自动化场景,比如要回复 Email,要发布网站,审批流程等,这些可以使用 Shell 或者 Perl、Python 等高级脚本语言。

第43节 无情的测试

1、注重实效的程序员会受到找到自己 bug 的驱使,以免以后经受由别人找到我们 bug 带来的羞耻。

2、早测试,常测试,自动化测试。要通过全部测试,编码才算完成。

3、测试主要围绕三个方面进行:测试什么、怎样测试、何时测试。

4、测试什么。测试类型有以下这些:

  • 单元测试:单元测试是函数级,有时也算做模块级的测试,要保证他们都正常通过。
  • 集成测试:组成项目的子系统能工作,且它们之间能很好的协同。
  • 验证和校验:这个类似AB测,是产品层面的测试。
  • 异常测试,资源耗尽,错误情况,如何恢复等。
  • 性能测试,压力测试,负载测试:这些情况通常是服务器相关的测试流程。
  • 可用性测试:也是产品项的测试,需要分析和调研,需求是否是有用的。

5、怎样测试。主要介绍有哪些测试方法:

  • 回归测试:回归测试通常是测试的后面阶段,新旧功能一起测试,新功能不应该影响旧功能。

  • 测试数据:对于一些特殊场景,测试数据能起到很大帮助,比如需要大量数据,强调边界条件的数据,能展现特定字段的数据等。

  • GUI系统:UI通常不好测试,所以应该做好解耦,将逻辑和视图拆分开进行测试。

  • 对测试进行测试:故意制造一些bug,观察你的测试系统能否发现。

  • 彻底测试:这里需要强调的是测试覆盖率,这其中需要强调的是测试状态的覆盖,而不是代码覆盖。要对不同状态都覆盖到。

6、何时进行测试。尽早测试,而且测试应该是自动完成的,我们在提交代码时就应该保证测试已经全部通过。

第44节 全都是写

1、代码要跟文档紧密结合,我们要认真对待注释及文档,他们不是可有可无的东西。

2、我们喜欢看到简单的模块级头注释,关于重要数据和类型声明的注释,以及给每个类和每个方法所加的简要头注释,用于描述函数的用法和任何不明了的事情。

3、应当使用特定的格式进行注释,通常对应语言或者 IDE 有推荐的注释格式。

4、可执行文档,即使按照特定格式进行注释,然后利用工具提取注释内容并生成文档。例如 JavaDoc

5、有时文档撰写者和开发并不是同一人,但他们应当接受同样的原则,特别是 DRY,正交性,以及自动化原则等。

iOS也有一个文档生成工具:jazzy[2],支持 OC 和 Swift,它可以根据标准的注释生成文档。

第45节 极大的期望

1、某个项目团队奇迹般的完成了一个非常复杂的项目,但却遭到用户抵制,原因是该引用没有帮助系统。所以考虑现实,项目的成功是由它在多大程度上满足了用户的期望来衡量的。

2、要与客户之间多交流期望,了解他们的需求,而不是一味沉溺在技术的世界里。

3、适当制造惊喜,会有些通用性的技巧能让项目获得更好的体验。比如:

  • 气球式帮助
  • 快捷键
  • 日志文件分析器
  • 自动化安装

第46节 傲慢与偏见

1、注重实效的程序员不会逃避责任,相反,我们乐于接受挑战,乐于使我们的业务知识广为人知。

2、过去时代的手艺人为能在他们的作品上签名而自豪,你也应该如此。Sign Your Work.

3、Kent Beck 在极限编程(XP)里的建议是采用公共的代码所有权,其还要求了结对编程,以防匿名的危险。所有权的好处是能为优秀的开发带来自豪感,并且当人们在一段代码上看到你的名字时,会将其认为质量的保证。

参考资料

[1]

R.Swift: https://github.com/mac-cain13/R.swift

[2]

jazzy: https://github.com/realm/jazzy


浏览 62
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报