相比第一版,书中大约三分之一的主题是全新的,而其余的大部分都被部分或全部重写了。作者再版的目的是,让内容变得更清晰、更贴切,并在某种程度上不受时间的影响。所以可以直接阅读第二版了。
务实的哲学 #
务实的程序员的特质是什么?是他们面临问题时,在解决方案中透出的态度、风格及理念。他们总是越过问题的表面,试着将问题放在更宽泛的上下文中综合考虑,从大局着想。他们的另一个成功点是他们为所做的一切负责。
务实的编程源于务实思考的哲学。
人生是你的 #
软件开发在任何职业列表中,绝对是你自己最能掌控的职业之一。
我们的技能供不应求,我们的知识不限于地域,我们可以远程工作。
我们收入颇丰。我们真的能做我们想做的任何事情。
你有权选择
你的工作环境很糟糕?你的工作很无聊?尝试纠正它。不过不要一直尝试下去,你还可以改变组织,或是让自己换一个组织。
如果你的技术过时了,安排时间学习一些看起来有趣的新东西,这是一种自我投资,只有为此加班才是合理的。
想要远程工作?要求过了吗?如果他们说不行,就去找个说行的人。
我的源码被猫吃了 #
一切行为都要对自己负责,也意味着自己要承担责任。
提供选择,别找借口
给出选择,而不是找借口。不要说搞不定;解释一下要做些什么才能挽回这个局面。
软件的熵 #
不要放任破窗
破窗理论:如果一间房子的一扇窗户破了,运转良好的干净系统会迅速恶化,紧接着,一扇又一扇窗户都会因缺乏维护而破掉,从而使得整个房子变得破败不堪。
不要搁置“破窗”(糟糕的设计、错误的决定、低劣的代码)不去修理。每发现一个就赶紧修一个。如果没有足够的时间完全修好,那么就把它钉起来。也许你可以注释掉那些糟糕的代码,显示一行“尚未实现”的信息,或用假数据先替代一下。采取行动,预防进一步的损害发生,表明一切尽在你的掌握中。
石头做的汤和煮熟的青蛙 #
做推动变革的催化剂
学习士兵。
牢记全景
够好即可的软件 #
将质量要求视为需求问题
知道何时止步。
知识组合 #
对知识组合做定期投资
- 每年学习一门新语言
- 每月读一本技术书
- 还要读非技术书
- 上课
- 加入本地的用户组和交流群
- 尝试不同的环境
- 与时俱进
批判性地分析你读到和听到的东西
问自己几个值得思考的问题:
- ask 5 why
- 谁从中受益
- 有什么背景
- 什么时候在哪里可以工作起来
- 为什么这是个问题
交流 #
英语就是另一门编程语言
说什么和怎么说同样重要
把文档嵌进去,而不要拴在表面
注释源码。
务实的方法 #
优秀设计的精髓 #
优秀的设计比糟糕的设计更容易变更
关注易于变更的能力。
DRY – 邪恶的重复 #
在一个系统中,每一处知识都必须单一、明确、权威地表达。
Don’t repeat yourself
重复的现象有很多:
- 代码中的重复
- 文档中的重复
- 表征的重复
- 内部 API 的重复
- 外部 API 的重复
- 数据源引起的重复
- 开发人员间的重复
让复用变得更容易
正交性 #
“正交性”是从几何学中借用来的术语。若两条直线相交后构成直角,它们就是正交的。在计算科学中,这个术语象征着独立性或解耦性。
排除不相关事物之间的影响
有几种技术可以用来保持正交性:
- 保持代码解藕
- 避免全局数据
- 避免相似的函数
可逆性 #
不设最终决定
放弃追逐时尚
曳光弹 #
你肯定看过那些人们用机关枪射击的电影、电视节目和电子游戏?在这些场景中,会经常看到子弹在空中留下明亮的轨迹线。这些线条来自曳光弹。
和普通弹药间隔着一起被压入弹夹。当曳光弹发射时,上面的磷就会被点燃,在枪和击中物之间留下一道烟火轨迹。如果曳光弹击中了目标,那么之后的常规子弹也会击中。士兵们使用这些曳光弹来调整他们的瞄准:这是一种务实的方法,可以在真实条件下提供实时反馈。
曳光弹式开发和项目不会结束这种理念是一致的:总有东西需要改,总有新功能需要加。这是一个逐步递增的方法。为了在编码中获得相同的效果,我们会找一些东西,能让我们快速、直观、可重复地从需求中得到最终系统的某个方面。
使用曳光弹找到目标
使用曳光弹代码有很多优势:
- 用户可以更早地获得能工作的东西
- 开发者构造了一个可以在其中工作的框架
- 你有了一个集成平台
- 你有可以演示的东西
- 你对进度有更好的感觉
原型与便签 #
使用原型学习
当制作一个原型时,哪些细节可以忽略:
- 正确性:你可以在适当的地方使用替代数据。
- 完整性:原型只需要满足有限的功能,可能只有一个预先选好的输入数据片段及单个菜单选项。
- 健壮性:错误检查可以不完整,甚至完全没有都行。如果你偏离了预定的航线,原型机很可能烧毁在绚丽的烟火中——那又如何!
- 格式:原型代码可能并不需要太多注释和文档(尽管围绕从原型中获取的经验,可能会产生大量文档,但是相对而言,原型系统本身的文档要少得多)。
领域语言 #
靠近问题域编程
估算 #
通过估算来避免意外
根据代码不断迭代进度表
这可能不受管理人员的欢迎,他们通常在项目开始之前就想要一个简单可靠的数字。你必须帮助他们去理解,进度是由团队、团队的生产力和环境综合决定的。明确了这一点,把提炼进度表作为每次迭代的一部分,你就可以估算出能力范围内最精确的进度安排。
基础工具 #
纯文本的威力 #
将知识用纯文本保存
Shell 游戏 #
发挥 shell 命令的威力
加强编辑能力 #
游刃有余地使用编辑器
版本控制 #
共享目录绝非版本控制!
永远使用版本控制
调试 #
去解决问题,而不是责备
最容易欺骗的人就是自己。
Don’t Panic
遇事不要慌。。
修代码前先让代码在测试中失败
读一下那些该死的出错信息
“select” 没出问题
不要假设,要证明
文本处理 #
学习一门文本处理语言
工程日记 #
写日记
务实的偏执 #
你无法写出完美的软件 #
契约式设计 #
通过契约进行设计
契约驱动测试。
死掉的程序不会说谎 #
尽早崩溃
断言式编程 #
使用断言去预防不可能的事情
如何保持资源的平衡 #
有始有终
在局部行动
不要冲出前灯范围 #
我们无法看到遥远的未来,离照射轴越远,就越黑暗。
小步前进 —— 由始至终
避免占卜
很多时候,明天看起来会和今天差不多,但不要指望一定会这样。
宁弯不折 #
如何编写只弯曲却不会折断的代码。
解藕 #
解藕代码让改变更容易
只管命令不要询问
这个原则说的是,不应该根据对象的内部状态做出决策,然后更新该对象。
不要链式调用方法
当你访问某样东西时,尽量不要超过一个“.”。
“一个点”规则有一个很大的例外:如果你链式调用的东西真的不太可能改变,那么这个规则就不适用。
避免全局数据
如果全局唯一非常重要,那么把它包装到 API 中
在现实世界抛球杂耍 #
有限状态机 #
观察者模式 #
发布/订阅 #
响应式编程与流 #
变换式编程 #
所有程序其实都是对数据的一种变换:将输入转换成输出。然而,当我们在构思设计时,很少考虑创建变换过程。相反,我们操心的是类和模块、数据结构和算法、语言和框架。
编程讲的是代码,而程序谈的是数据
不要囤积状态,传递下去
继承税 #
你想要一根香蕉,但得到的却是一只拿着香蕉的大猩猩,甚至还有整个丛林。– 乔·阿姆斯特朗
有点“智商税”的意思。
继承就是耦合。
不要付继承税
更好的替代方案:
- 接口与协议
- 委托
- mixin 与特征
尽量用接口来表达多态
用委托提供服务:“有一个”胜过“是一个”
利用 mixin 共享功能
Django-Rest-Framework 是比较典型的例子。
配置 #
使用外部配置参数化应用程序
并发 #
打破时域耦合 #
时间对我们来说有两个重要的方面:并发性(在同一时刻发生的多件事情)以及次序(事情在时间轴上的相对位置)。
通过分析工作流来提高并发性
共享状态是不正确的状态 #
你坐在最喜欢的餐厅。吃完主菜,问男服务员还有没有苹果派。他回头一看——陈列柜里还有一个,就告诉你“还有”。点到了苹果派,你心满意足地长出了一口气。与此同时,在餐厅的另一边,还有一位顾客也问了女服务员同样的问题。她也看了看,确认有一个,让顾客点了单。
总有一个顾客会失望的。
随机故障通常是并发问题
角色与进程 #
用角色实现并发性时不必共享状态
黑板 #
使用黑板来协调工作流
当你编码时 #
听从蜥蜴脑 #
蜥蜴脑即本能。
倾听你内心的蜥蜴
首先,停止正在做的事情。给自己一点时间和空间,让大脑自我组织。远离键盘,停止对代码的思考,做一些暂时不需要动脑筋的事情——散步、吃午饭、和别人聊天,或是先睡一觉。让想法自己从大脑的各个层面渗透出来:对此不用很刻意。最终这些想法可能会上升到有意识的水平,这样你就能抓住一个“啊哈!”的时刻。
如果这不起作用,就试着把问题外化。把正在写的代码涂画到纸上,或者向你的同事(最好不是程序员)解释一下是怎么回事,向橡皮鸭解释一下也行。
但也许在试过这些方法后,还是无法脱困。那么就该行动了。我们需要告诉大脑:打算要做的事情,并没有那么重要。可以用做原型的方式来干这件事。
巧合式编程 #
不要依赖巧合编程
首先判别什么是巧合,并对此形成识别模式,其次决策依据里不能有巧合和假设。
算法速度 #
评估算法的级别
对估算做测试
重构 #
何时该重构:
- 重复,发现了一处违背 DRY 原则的地方。
- 非正交设计
- 过时的知识
- 使用:当系统在真实的环境中被真实的人使用时,你会意识到,与以前的认识相比,一些特性现在看来更为重要,反而“必须拥有”的特性可能并不重要。
- 性能
- 通过了测试
尽早重构,经常重构
对编码测试 #
测试与找 Bug 无关
测试是代码的第一个用户
既非自上而下,也不自下而上,基于端对端构建
为测试做设计
要对软件做测试,否则只能留给用户去做
基于特性测试 #
使用基于特性的测试来校验假设
出门在外注意安全 #
务实的程序员有相当多的偏执。我们知道自己有缺陷和限制,外部攻击者会抓住每个我们留下的漏洞去破坏系统。特定的开发和部署环境,将有其自己的围绕安全性的需求,但是你应该始终牢记一些基本原则。
将攻击面的面积最小化
系统攻击面的面积指,攻击者可以在其中输入数据、提取数据或调用服务执行的所有访问点的总和。
保持代码简洁,让攻击面最小,代码复杂性滋生攻击载体
输入数据是一种攻击载体
未经身份认证的服务成为攻击载体
经过身份认证的服务成为攻击载体
输出数据成为攻击载体
调试信息成为攻击载体
最小特权原则
在最短的时间内使用最少的特权。换句话说,不要自动获取类似root 或 Administrator 这样的最高级别权限。如果真需要这么高级别的权限,那就去申请,获得权限后只做最少量的工作,然后迅速放弃权限,这样可以降低风险。
安全的默认值
例如,密码输入的默认设置可能是隐藏输入,用星号替换每个字符。
敏感数据要加密
不要将个人身份信息、财务数据、密码或其他凭据,以纯文本的形式保存在数据库或其他外部文件中。如果数据被泄露,加密提供了额外的安全级别。
维护安全更新,尽早打上安全补丁
了解一些密码学常识。
事物命名 #
名字有着深刻的含义。
在计算机科学中只有两件难事:缓存失效和命名。
好好取名;需要时更名
项目启动之前 #
需求之坑 #
无人确切知道自己想要什么
程序员帮助人们理解他们想要什么
需求是从反馈循环中学到的
和用户一起工作以便从用户角度思考
策略即原数据
使用项目术语表
如果用户和开发者使用不同的名称来称呼相同的事物,或者更糟糕的是,使用相同的名称来指代不同的事物,那么项目就很难取得成功。
处理无法解决的难题 #
不要跳出框框思考:找到框框
携手共建 #
康威定律:设计系统的架构受制于产生这些设计的组织的沟通结构。
结对编程
群体编程
不要一个人埋头钻进代码中
敏捷的本质 #
敏捷不是一个名词,敏捷有关于你如何做事。
我们一直在实践中探寻更好的软件开发方法,身体力行的同时也帮助他人。由此我们建立了如下价值观:
个体和互动 高于 流程和工具
工作的软件 高于 详尽的文档
客户合作 高于 合同谈判
响应变化 高于 遵循计划
也就是说,尽管右项有其价值,我们更重视左项的价值。
务实的项目 #
务实的团队 #
维持小而稳定的团队
团队的工作不应仅致力于开发新功能,还可能包括:
- 旧系统的维护
- 流程的反思与精炼
- 实验新技术
- 学习和提升技能
派上日程以待其成
组织全功能的团队
椰子派不上用场 #
人们容易受到诱惑,掉入货物崇拜的陷阱:通过投资去造出神奇的外观,希望吸引来潜在有效的魔法。但与最初发生在美拉尼西亚的货物崇拜一样,用椰子壳制成的赝品是替代不了真机场的。
做能起作用的事,别赶时髦
在用户需要时交付
务实的入门套件 #
使用版本控制来驱动构建、测试和发布
尽早测试,经常测试,自动测试
直到所有的测试都已经运行,编码才算完成
使用破坏者检测你的测试
测试状态覆盖率,而非代码覆盖率
每个 Bug 只找一次
一个 Bug 一旦被人类测试员发现,这就应该是它被该人类测试员发现的最后一次。要立即修改自动化测试,以便这个特定的 Bug,从此往后每次都被检查到 —— 不能有任何例外,无论它多么琐碎,也无论开发者有多少抱怨,或是不停唠叨“哦,永远不会再发生了”。
不要使用手动程序
人不像电脑那样具有可重复性。我们也不应对此抱有期望。shell 脚本或程序将一次又一次地以相同的顺序执行相同的指令。
一切都要依赖自动化。
取悦用户 #
作为开发者,我们的目标是取悦用户。
用户真正要的不是代码,他们只是遇到某个业务问题,需要在目标和预算范围内解决。他们的信念是,通过与你的团队合作,能够做到这一点。
取悦用户,而不是只交付代码
如果你想取悦客户,就和他们建立起某种关系,这样即可积极地帮助他们解决问题。
傲慢与偏见 #
在作品上签名
保持匿名会滋生粗心、错误、懒惰和糟糕的代码,特别是在大型项目中 —— 很容易把自己看成只是大齿轮上的一个小齿,在无休止的工作汇报中制造蹩脚的借口,而不是写出好的代码。
跋 #
先勿伤害
请问自己:我自己愿意成为这个软件的用户吗?我希望分享自己的详细信息吗?我希望自己的行踪被交给零售店吗?我愿意乘坐这辆自动驾驶汽车吗?做这件事我能心安吗?
有一些创造性的想法,开始打道德行为界限的擦边球,如果你参与了这类项目,就和出资者一样负有责任。
不要助纣为虐
你正在为自己和子孙后代建设未来——这是你的职责所在,去创造一个让所有人心向往之的宜居未来。当你做的事情违背了这个理想时,要敢于承认,并有勇气说“不!”对可以拥有的未来充满憧憬,才有动力去创造它。即使是空中楼阁,也要每天为它添砖加瓦。
我们都有精彩的人生。