相比第一版,书中大约三分之一的主题是全新的,而其余的大部分都被部分或全部重写了。作者再版的目的是,让内容变得更清晰、更贴切,并在某种程度上不受时间的影响。所以可以直接阅读第二版了。
务实的哲学
务实的程序员的特质是什么?是他们面临问题时,在解决方案中透出的态度、风格及理念。他们总是越过问题的表面,试着将问题放在更宽泛的上下文中综合考虑,从大局着想。他们的另一个成功点是他们为所做的一切负责。
务实的编程源于务实思考的哲学。
人生是你的
软件开发在任何职业列表中,绝对是你自己最能掌控的职业之一。
我们的技能供不应求,我们的知识不限于地域,我们可以远程工作。
我们收入颇丰。我们真的能做我们想做的任何事情。
你有权选择
你的工作环境很糟糕?你的工作很无聊?尝试纠正它。不过不要一直尝试下去,你还可以改变组织,或是让自己换一个组织。
如果你的技术过时了,安排时间学习一些看起来有趣的新东西,这是一种自我投资,只有为此加班才是合理的。
想要远程工作?要求过了吗?如果他们说不行,就去找个说行的人。
我的源码被猫吃了
一切行为都要对自己负责,也意味着自己要承担责任。
提供选择,别找借口
给出选择,而不是找借口。不要说搞不定;解释一下要做些什么才能挽回这个局面。
软件的熵
不要放任破窗
破窗理论:如果一间房子的一扇窗户破了,运转良好的干净系统会迅速恶化,紧接着,一扇又一扇窗户都会因缺乏维护而破掉,从而使得整个房子变得破败不堪。
不要搁置“破窗”(糟糕的设计、错误的决定、低劣的代码)不去修理。每发现一个就赶紧修一个。如果没有足够的时间完全修好,那么就把它钉起来。也许你可以注释掉那些糟糕的代码,显示一行“尚未实现”的信息,或用假数据先替代一下。采取行动,预防进一步的损害发生,表明一切尽在你的掌握中。
石头做的汤和煮熟的青蛙
做推动变革的催化剂
学习士兵。
牢记全景
够好即可的软件
将质量要求视为需求问题
知道何时止步。
知识组合
对知识组合做定期投资
- 每年学习一门新语言
- 每月读一本技术书
- 还要读非技术书
- 上课
- 加入本地的用户组和交流群
- 尝试不同的环境
- 与时俱进
批判性地分析你读到和听到的东西
问自己几个值得思考的问题:
- 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 脚本或程序将一次又一次地以相同的顺序执行相同的指令。
一切都要依赖自动化。
取悦用户
作为开发者,我们的目标是取悦用户。
用户真正要的不是代码,他们只是遇到某个业务问题,需要在目标和预算范围内解决。他们的信念是,通过与你的团队合作,能够做到这一点。
取悦用户,而不是只交付代码
如果你想取悦客户,就和他们建立起某种关系,这样即可积极地帮助他们解决问题。
傲慢与偏见
在作品上签名
保持匿名会滋生粗心、错误、懒惰和糟糕的代码,特别是在大型项目中 ------ 很容易把自己看成只是大齿轮上的一个小齿,在无休止的工作汇报中制造蹩脚的借口,而不是写出好的代码。
跋
先勿伤害
请问自己:我自己愿意成为这个软件的用户吗?我希望分享自己的详细信息吗?我希望自己的行踪被交给零售店吗?我愿意乘坐这辆自动驾驶汽车吗?做这件事我能心安吗?
有一些创造性的想法,开始打道德行为界限的擦边球,如果你参与了这类项目,就和出资者一样负有责任。
不要助纣为虐
你正在为自己和子孙后代建设未来——这是你的职责所在,去创造一个让所有人心向往之的宜居未来。当你做的事情违背了这个理想时,要敢于承认,并有勇气说“不!”对可以拥有的未来充满憧憬,才有动力去创造它。即使是空中楼阁,也要每天为它添砖加瓦。
我们都有精彩的人生。