我们,采集的只是石头,却必须始终展望着未来的大教堂

务实的哲学

  • 人生是你自己的,是你在拥有、经营和创造。
  • 提供选择,别找借口。如果磁盘挂起,你所有的源码都在里面–而你没有备份,这就是你的错,跟老板说“我的源码被猫吃了”解决不了问题
  • 不要搁置“破窗”(糟糕的设计、错误的决定、低劣的代码)不去修理。每发现一个赶紧修一个。一定要告诉自己“不要打破窗户”
  • 找出合理的请求,然后不断完善,一旦有成果产出,展示给人们看,让他们大吃一惊。
  • 够好即可得软件就是最好的。让用户参与权衡,如果你早点给用户一点东西玩,他们的反馈常常能引领你做出更好的最终方案。
  • 目标:
    • 每年学习一门新语言,不同的语言以不同 的方式解决相同的问题,拓宽思维,避免陷入陈规
    • 每月读一本技术书,扩展领域,学习一些和项目不相关的东西
    • 还要读非技术书
    • 上课,本地大学或网上有趣的课程
    • 与时俱进
  • 批判性思维,批判性地分析你读到的和听到的东西
    • 问“五个为什么”
    • 谁从中受益
    • 有什么背景
    • 什么时候在哪里可以工作起来
    • 为什么这是个问题
  • 交流,缺乏有效的交流,好点子就成了一个孤儿。
  • 传递信息的过程才算是交流,为了把信息送达,你需要了解受众的需求、兴趣和能力。
  • 明白自己想说什么,越是有效的交流,影响力越大。
  • 想法很重要,但是听众还希望有个好看的包装。

务实的方法

优秀设计的精髓

  • 优秀的设计比糟糕的设计更容易变更,要信奉ETC原则(Easier To Change)
    • 为什么解耦很好?因为通过隔离关注点,可让每一部分都容易变更
    • 为什么单一职责原则有用?因为一个需求变化仅体现为某个单一模块上的一个对于变化
    • 为什么命名很重要?因为好的命名可以使代码更容易阅读,而你需要通过阅读来变更代码
  • ETC是一种价值观念,不是一条规则
    • ETC是个向导,它能帮助你在不同的路线中选出一条。

DRY–邪恶的重复

  • 程序员一直处于维护模式下,从未间断。我们的理解每天都在变化。
  • 想要可靠地开发软件,或让开发项目更容易理解和维护,唯一的方法是遵循DRY原则:在一个系统中,每一处知识都必须单一、明确、权威地表达。
  • DRY–不要重复自己。
    • DRY不限于编码,DRY针对的是你对知识和意图的复制,它强调的是,在两个地方表达的东西其实是相同的,只是表达方式有可能完全不同。
    • 代码中的重复
    • 文档中的重复
    • 数据结构中重复
    • 表征的重复
    • 开发人员间的重复

正交性

  • 正交性:在计算科学中,象征着独立性和解耦性。有助于构建易于设计、构造、测试和扩展的系统。

    • 但凡编写正交的系统,就能获得两个主要收益:提高生产力和降低风险。
    • 我们希望设计的组件自成一体:独立自主,有单一的清晰定义的意图。当组件彼此隔离时,你知道可以变更其中一个组件,而不必担心影响到其他组件。
  • 正交性设计

    • 系统应该由一组相互协作的模块构成,每个模块实现的功能应独立于其他模块
    • 不要依赖那些你无法控制的东西(电话号码,邮政编码,身份证号等外部标志符)
    • 在引入第三方工具包和程序库时,请注意保持系统的正交性
  • 编码时保持正交性,

    • 保持代码解耦
    • 避免全局数据
    • 避免相似的函数
    • 养成不断质疑代码的习惯,只要有机会就重新组织,改善其结构和正交性。
  • 基于正交性设计和实现的系统更容易测试

    • 建议将测试作为常规构建过程的一部分自动执行
    • 修Bug也是评估整个系统的正交性的好时机
  • 正交性也适用于文档,涉及的两个独立维度是内容和呈现。

  • 采用正交性原则并与DRY原则紧密结合,可以让系统变得更灵活、更容易理解,并且更容易调试、测试和维护。

可逆性

  • 实现一件事情的方法往往不止一种,提供第三方产品的供应商通常也不止一个
  • 错误在于认为任何决定都是板上钉钉的,而没有为可能出现的意外做好准备
  • 不设最终决定,很多人会尽力保持代码的灵活性,但其实还要考虑在体系结构、部署和供应商集成方面保持灵活性
  • 放弃追逐时尚,要让你的代码具备“摇滚”精神:顺境时摇摆滚动,逆境时直面困难。

曳光弹

  • 曳光弹式开发,即在真实条件下针对移动目标进行即时反馈的必要性

  • 寻找重要的需求,那些定义了系统的需求。寻找你有疑问的地方,那些你认为有重大风险的地方。然后对开发进行优先级排序,首先从这些地方开始编码

  • 最初的曳光弹就是,创建一个简单的工程,加一行“Hello World!,并确保其编译和运行。然后我们再去找整个应用程序中不确定的部分,添加上让它们跑起来的骨架

  • 曳光代码不是一次性的:编写它是为了持续使用

  • 曳光弹式开发和项目不会结束这种理念是一致的:总有东西需要改,总有新功能需要加,这是一个逐步递增的方法

  • 使用曳光代码的优势:

    • 用户可以更早地获得能工作的东西
    • 开发者构造了一个可以在其中工作的框架
    • 你有了一个集成平台
    • 你有可以演示的东西
    • 你对进度有更好的感觉
  • 曳光弹并不总能击中目标,去弄清楚如何改变你已经做好的东西,想办法让它靠近目标。能够以更快的速度、更小的成本,收集到针对应用程序的反馈,并生成一个新的更准确的版本

原型与便签

  • 用原型来研究什么类型的东西?任何有风险的东西,任何之前没有尝试过或对最终系统来说很关键的东西,任何未经证实、实验性或可疑的东西以及任何让你不舒服的东西
  • 当制作一个原型时,那些细节可以忽略?正确性,完整性,健壮性,格式
  • 不要把原型用于产品

领域语言

语言之界限,即是一个人世界之界限。

  • 我们总是试图使用应用领域的词汇表来编写代码,在一些案例中,务实的程序员能跨越到下一个层级,直接用该领域的语言编程,直接使用该领域的词汇、语法和语义。
  • 领域语言的特征
    • RSpec和Phoenix路由是用它们的宿主语言(Ruby和Elixir)编写的;Cucumber测试和Ansible的配置是用它们自己的专门语言编写的。我们将RSpec和Phoenix路由视为内部领域语言的范例,Cucumber和Ansible采用的是外部语言
  • 内部语言和外部语言之间的权衡
    • 内部领域语言可以利用其宿主语言的特性,创建出来的领域语言更为强大,缺点是会受到宿主语言的语法和语义的限制
    • 外部语言没有这样的限制,只需编写一个解析器

估算

  • 通过学习估算,可以发展为对事物的数量级产生直觉,可以判别事情的可行性
    • 所有的估算都是基于对问题的建模,一旦得到了模型,就可以将其分解为组件,每个组件都有一些参数,这些参数会影响组件对整个模型的贡献
    • 一旦参数被分离,就可以为每个参数分配一个值
  • 估算项目进度
    • 计划评审技术(Program Evaluation Review Techningue,PERT),每个PERT任务都有一个乐观的、一个最有可能的和一个悲观的估算。任务被排列进一个相互依赖的网络中,然后使用一些简单的统计方法来确定整个项目的最佳和最差时间
    • 确定一个项目的时间表的唯一方法,通常是来自于在这个项目上获得的经验
    • 根据代码不断迭代的进度表,进度是由团队、团队的生产力和环境综合决定的,把提炼进度表作为每次迭代的一部分。

基础工具

纯文本的威力

  • 我们把需求以知识的形式收集起来,然后在设计、实现、测试和文档中表达这些知识。
  • 纯文本是将知识持久地存储下来的最佳格式,纯文本赋予了我们操作知识的能力,既可以用手工的方式,也可以用编程的方式操作
  • 文本的威力
    • 为防设备老化而加保险
    • 利用杠杆效应让已有工具发挥最大优势
    • 易于测试

Shell游戏

  • 在Shell中,你可以调用所有能用的的工具,或通过管道用各种方式把工具组合起来
  • 图形工具的好处在于WYSIWYG–所见即所得;弱势之处是WYSIAYG–所见即全部

加强编辑能力

  • 操作编辑器游刃有余,最主要的收益来自于变得“顺畅”,不必再把心思花在编辑技巧上面。思考变流畅,编程就会受益
  • 逐步游刃有余:只学习那些让你过的更舒服的命令
    • 编辑时要自省,每次发现自己又在重复做某件事情时,要习惯性地想到“或许有更好的方法”
    • 一旦发掘出一个新的有用的特性,需要尽快把它内化为一种肌肉记忆

版本控制

进步,远非寓于改变之中,而是依赖于保持。那些不能铭记过去的人,注定要重蹈覆辙。

  • 版本控制系统跟踪你在源码和文档中所做的每个更改,使用正确配置的源码控制系统,总是可以将软件回退到以前的某个版本
  • 版本控制系统对跟踪Bug、审核、性能以及质量这些目的,意义重大
  • 永远使用版本控制,确保所有内容都在版本控制之下,文档,电话号码列表,供应商备忘录,Makefile文件,构建和发布过程,整理日志文件和小Shell脚本
  • 分支出去,提供了隔离,团队项目的工作流核心通常围绕着分支来开展
  • 把版本控制视为项目中枢

调试

  • 调试只是在解决问题并为此攻关
  • 去解决问题,而不是责备。Bug是你的错还是别人的错并不重要。无论是谁的错,问题仍然要你来面对
  • 调试心态:不要恐慌
  • 你在看到Bug或Bug报告时的第一反应是“这不可能”,那你就大错特错了。不要在“但那不可能发生”的思路上浪费哪怕一个神经元,因为很明显它会发生,而且已经发生了
  • 永远要去发掘问题的根本原因,而不仅仅停留在问题的表面现象
  • 在开始查Bug之前,请确保正在处理的代码可以干净构建–没有警告
  • 开始动手修Bug时,最好是使其重现,修代码前先让代码在测试中失败
  • 读一下那些该死的出错信息
    • 错误的结果
    • 输入值的敏感度
    • 二分法
    • 输出日志或跟踪信息
    • 找个橡皮鸭:向其他人解释该问题,可以用来找到问题的原因
    • 排除法
  • 出错时受到惊吓的程度,与对正在运行的代码的信任程度成正比

文本处理

  • 运用文本处理语言,可以快速地做出工具,或是为想法建立原型
  • 工程日志

4 务实的偏执

在一个系统不完善、时间安排荒谬、工具可笑、需求不可能实现的世界里,让我们安全行事吧。就像伍迪·艾伦说的:“当所有人都真的在给你找麻烦的时候,偏执就是一个好主意。”

契约式设计

  • 契约规定了你的权利和责任,同时也规定了对方的权利和责任。另外如果任何一方不遵守契约,还会面对一个针对后果的协议

  • 伯特兰·迈耶在《面向对象软件构造》发明了契约式设计的概念。这是一种简单但功能强大的技术,侧重于文档化(并约定)软件模块的权利和责任,以确保程序的正确性。什么是正确的程序?不多也不少,正好完成它主张要做事情的程序。

    • 文档化及对主张进行检验是契约式设计(BDC)的核心
  • 软件系统中每一个函数和方法都力争有所作为。

    • 前置条件,传递良好的数据是调用者的责任
    • 后置条件,例程完成时世界的状态,不允许无限循环
    • 类的不变式,从调用者的角度看,类会确保该条件始终为真

    如果调用者满足了例程的所有前置条件,则例程应保证在完成时所有后置条件和不变式为真。

  • 通过契约进行设计,在编写代码前,简单地列出输入域的范围,边界条件是什么,例程承若要交付什么–或更重要的是,没有承若要交付什么–这些对编写更好的软件来说,是巨大的飞跃

  • 可以用断言部分模拟契约式设计,断言是一种对逻辑条件的运行时检查

  • 通过使用断言或DBC机制来验证前置条件,后置条件和不变式,可以尽早崩溃并报告有关问题更准确的信息;在问题发生的地方尽早崩溃,能让找到问题和诊断问题更加容易

  • 使用语义不变式来表达不可违背的需求,它必须是事物意义的核心,而不受策略的影响(策略用于更动态的业务规则)

  • 在自主代理的视野下,代理有拒绝自己不想接受的请求的自由,它们可以自由地对契约重现协商。

死掉的程序不会说谎

  • 尽快检测问题的好处之一是,可以更早崩溃,而崩溃通常是你能做的最好的事情
  • 一个死掉的程序,通常比一个瘫痪的程序,造成的损害要小得多

断言式编程

自责中往往有种奢侈。我们自责时,总觉得别人无权在责备我们。

  • 使用断言去预防不可能的事情,不要使用断言来代替真正的错误处理
  • 断言与副作用,如果对条件做评估本身有副作用,就可能发生这样的事情。属于海森堡Bug–调试本身改变了被调试的系统的行为
  • 保持断言常开

如何保持资源的平衡

  • 有始有终,分配资源的函数或对象,对释放资源应负有责任
  • 嵌套的分配
    • 释放资源的顺序与分配资源的顺序相反
    • 在代码的不同位置,如果都会分配同一组资源,就始终以相同的顺序分配它们,减少死锁的可能性。
  • 保持平衡与异常,如果抛出异常,保证之前分配给异常的所有内容依次释放,变量的作用域或try catch代码块中的finally句子
  • 检测平衡,写一些代码去实际检查资源有没有被恰当地释放

不要冲出前灯范围

  • 小步前进–由始至终。总是采取经过深思熟虑的小步骤,同时检查反馈,并在推进前不断调整。把反馈的频率当作速度限制,永远不要进行“太大”的步骤或任务

宁弯不折

解耦

当我们试着单独挑出一个事物的时候,总会发现它与宇宙中其他一切都有关联。

  • 解耦代码让改变更容易
  • 只管命令不要询问,这个原则是说,不应该根据对象的内部状态做出决策,然后更新该对象
  • 不要链式调用方法,当你访问某样东西时,尽量不要超过一个“.”
  • 邪恶的全局化,全局可访问的数据是应用程序组件之间耦合的潜在来源
    • 全局数据包括单件,外部资源
    • 如果全局唯一非常重要,那么将它包装到API中
  • 继承增加了耦合
  • 让代码害羞一点:让它只处理直接知道的事情,这将有助于保持应用程序解耦,使其更易于变更

在现实世界中抛球杂耍

  • 事件表达出信息的可用性,它可能来自外部世界:用户点击了按钮,也可能来自于内部:搜索完成了。
  • 四个策略:有限状态机,观察者模式,发布/订阅,响应式编程与流
  • 有限状态机(FSM)
    • 状态机基本上就是怎样处理事件的一份规范,它由一组状态组成,其中一个是当前状态,对于每个状态,我们列出对该状态有意义的事件
    • 添加动作,可以在某些转换上添加触发动作来增强它
  • 观察者模式
    • 在观察者模式中,我们有一个事件源,被称为被观察对象,而客户列表,也即观察者,会对其中的事件感兴趣
    • 观察者模式有一个问题:每个观察者必须与观察对象注册在一起,所以引入了耦合。此外,回调是由观察对象以同步的方式内联处理的,会导致性能瓶颈
  • 发布/订阅
    • 发布/订阅推广了观察者模式,同时解决了耦合和性能问题
    • 与观察者模式不同,发布者和订阅者之间的通信是在代码之外处理的,并且可能是异步的
  • 响应式编程、流与事件
    • 流让我们把事件当作数据集合来对待

变换式编程

  • 所有程序其实都是对数据的一种变换——将输入转换成输出,然后当我们在构思设计时,很少考虑创建变换过程。相反,我们操心的是类和模块、数据结构和算法、语言和框架
  • 编程讲的是代码,而程序谈的是数据
  • 寻找变换的最简单方法是:从需求开始并确定它的输入和输出,这是一个自顶向下的方法
  • 不要囤积状态,传递下去。不要把数据看作是遍布整个系统的小数据池,而要把数据看作是一条浩浩荡荡的河流。数据成为与功能对等的恭喜:管道是一系列的代码—>数据—>代码—>数据……数据不再和特定的函数组一起绑定在类定义中。
  • 错误处理怎么做?可以在变换的内部或外部做错误检查

继承税

  • 通过继承共享代码的问题
    • 继承就是耦合,不仅子类耦合到父类,以及父类的父类等,而且使用子类的代码也耦合到所有祖先类
    • 通过继承构建类型的问题,为了表示类之间的细微差别而逐层增加,增加复杂性,使程序更加脆弱
  • 不要付继承税,更好的替代方案:接口与协议,委托,mixin与特征
    • 尽量用接口来表达多态,接口与协议给了我们一种不使用继承的多态性
    • 用委托提供服务:“有一个”胜过“是一个”
    • mixin基本思想很简单:希望能够为类和对象扩展新的功能,但不用继承。讲现有事物和新事物的功能合并在一起

配置

  • 使用外部配置参数化应用程序
    • 静态配置
    • 配置服务化,存储到API中