《程序员修炼之道:通向务实的最高境界(第2版)》出版于2020年4月。
虽然很多地方的翻译不通顺、很奇怪,但是不妨碍整体阅读,事实上也可以跳读。
看中文技术类书籍,有时可以发发呆走走神,因为目录、排版几乎都耳熟能详,接下来的主题、内容可猜出个大概。
但这本书的排版,非常另类。翻译没有改变排版,那本文的摘抄尽可能保持不变吧:一级标题是章,二级标题是节,三级标题是提示。
感悟:增加自己的一点解读。
第一版前言
务实的程序员应该有的特征:
- 早期的采纳者/快速的适配者:对技术和技巧有一种直觉,喜欢尝试。当接触到新东西时,可以很快地掌握它们,并把它们与其他知识结合起来。这种信心来自经验。
- 好奇:倾向于问问题。热衷于收集各种细微的事实,坚信它们会影响自己多年后的决策。
- 批判性的思考者:在没有得到证实前很少接受既定的现实。
- 现实主义:你试图理解所面临的每个问题的本质。这种现实主义让你对事情有多困难、需要用多长时间有一个很好的感知。一个过程应该很难,或是需要点时间才能完成,对这些的深刻理解,给了你坚持下去的毅力。
- 多面手:熟悉各种技术和环境,经过努力能跟上最新的进展。工作可能要求在某个专门领域成为行家。也能够进入新的领域,迎接新的挑战。
1 关注你的技艺
2 思考!思考你的工作
1.务实的哲学
1 人生是你的
人生是你自己的,是你在拥有、经营和创造。
但是,总有一些原因导致开发者拒绝改变。
3 你有权选择
你可以去改变组织,或是让自己换一个组织。
感悟:换组织,那不就是跳槽么。在中国当前乃至往后三十年甚至更久的软件编程环境下,这是几乎不可能的死胡同。跳槽太频繁?不招HR们待见。
2 我的源码被猫吃了
尽管有彻底的测试,有优秀的文档,有完备的自动化,结果还是出了问题——交付被推迟,未曾预料的技术问题出现。
团队信任:团队信任对于创造力和协作至关重要。在一个以信任为基础的健康环境中,你可以安全地说出你的想法,表达你的思想。学会依赖你的团队成员,他们也会依赖你。
承担责任:责任意味着你对某事积极认同。你保证事情能搞定,并为之做出承诺,但你不必直接掌控事情的每个方面。除了个人尽力做好,你必须分析超出你控制范围的风险情况。如果责任的伦理内涵过于含糊,或是面对无法实现的情况,抑或风险过大,你都有权不承担责任。你必须根据自己的价值观和判断做出决定。
4 提供选择,别找借口
给出选择,而不是找借口。不要说搞不定;解释一下要做些什么才能挽回这个局面。
3 软件的熵
熵是一个物理学术语,定义一个系统的无序总量。
绝望是会传染的,就像狭窄空间中的流感病毒。所有的负面情绪会在团队成员间蔓延,变成恶性循环。
5 不要放任破窗
不要搁置破窗
(糟糕的设计、错误的决定、低劣的代码)不去修理。每发现一个就赶紧修一个。如果没有足够的时间完全修好,那么就把它钉起来。
软件开发中应该遵循的方法:不要只是因为一些东西非常危急,就去造成附带损害。破窗一扇都嫌太多。
感悟:不要因为你面对着一坨屎一样的项目代码,也往里面拉屎。
一定要告诉自己:不要打破窗户。
4 石头做的汤和煮熟的青蛙
但当你为做整件事去征求意见的时候,见到的往往是推脱和茫然的眼神。
人们都觉得,加入一个推进中的成功项目更容易一些。因为只要一窥未来,大家就能团结在一起。
6 做推动变革的催化剂
大多数软件灾难都始于微不足道的小事,项目的拖延也是一天天累积而成的。系统一个特性接一个特性地偏离规范,一个接一个补丁加到代码上,最终原始代码无影无踪。往往就是一件件小事的累积破坏团队和士气。
7 牢记全景
不要学寓言里的青蛙,永远留意着大局,持续不断地审视你身边发生的事情,而不要只专注于你个人在做的事情。
5 够好即可的软件
《够好即可的软件就是最好的》:你能训练自己写出够好即可的软件——对用户、未来的维护者来说够好即可,只要好的程度能让你自己内心平静就可以。
够好即可
这个词并不意味着草率或糟糕的代码。所有系统必须达到用户的需求才算完成,需要达到基本的性能、隐私和安全标准。
一些建议:
- 让用户参与权衡:如果你早点给用户一点东西玩,他们的反馈常常能引领你做出更好的最终方案;
- 知道何时止步:不要让过度的修饰和精炼侵蚀掉一个完好的程序。继续前行,让代码在它该有的位置驻留一段时间。它或许并不完美,不要紧的——它就算永不完美也没关系。
8 将质量要求视为需求问题
6 知识组合
时效资产:资产的价值随时间推移而减少。
学习新事物的能力是你最重要的战略资产。
知识组合:将程序员所了解的一切有关计算过程的事实、工作的应用领域,以及所有经验,视为他们拥有的知识组合。
构建知识组合:
- 定期投资:每天固定时间摄入知识。
- 多样化:知道的东西越多,价值就越大。熟悉的技能越多,越能适应变化。
- 风险管理:不能把全部精力用于某一个细分领域。
- 低买高卖:在一项新兴技术流行前就开始学习!!感悟:在Go语言刚流行时,深入学习并成功转行,好过在Java世界里卷死。
- 重新评估调整:定期复盘
9 对知识组合做定期投资
一些建议:
- 每年学习一门新语言
- 每月读一本技术书
- 还要读非技术书
- 上课
- 加入本地的用户组和交流群
- 尝试不同的环境
- 与时俱进
- 学习的机会
- 批判性思维
10 批判性地分析你读到和听到的东西
比如:
- 问五个为什么
- 谁从中受益
- 有什么背景
- 什么时候在哪里可以工作起来
- 为什么这是个问题
7 交流!
11 英语就是另一门编程语言
了解听众:传达信息的过程才算是交流。为了把信息送达,你需要了解受众的需求、兴趣和能力。
明白自己想说什么
12 说什么和怎么说同样重要
13 把文档嵌进去,而不要栓在表面
2.务实的方法
8 优秀设计的精髓
14 优秀的设计比糟糕的设计更容易变更
能适应使用者的就是好的设计。对代码而言,就是要顺应变化。因此要信奉ETC原则(Easier To Change,更容易变更)。
9 DRY——邪恶的重复
DRY的原则:在一个系统中,每一处知识都必须单一、明确、权威地表达。
15 DRY——不要重复自己
16 让复用变得更容易
10 正交性
对于两个或多个事物,其中一个的改变不影响其他任何一个,则这些事物是正交的。
非正交系统天生就复杂,难以变更和控制。当系统的组件相互之间高度依赖时,就没有局部修理这回事。
17 消除不相关事物之间的影响
但凡编写正交的系统,就能获得两个主要的收益:
- 提高生产力:正交的方法同时促进重用。如果组件有一个职责定义清晰的规范,就能以原作者预想不到的方式与新组件组合使用。系统耦合越松散,重新配置系统和再加工就越容易。
- 降低风险:有4种风险。
正交性原则
TODO
11 可逆性
没有什么是永恒不变的——如果你严重依赖某些事实,几乎可以肯定将来其会发生变化。
18 不设最终决定
很多人都会尽力保持代码的灵活性,但其实还要考虑在体系结构、部署和供应商集成方面保持灵活性。
19 放弃追逐时尚
要让你的代码具备摇滚
精神:顺境时摇摆滚动,逆境时直面困难。
12 曳光弹
在一个复杂多变的世界里考虑如何命中目标,是很有趣的。面对一个复杂多变的需求时,如何快速交付高质量产品。
寻找重要的需求,那些定义了系统的需求。寻找你有疑问的地方,那些你认为有重大风险的地方。然后对开发进行优先级排序,首先从这些地方开始编码。
20 使用曳光弹找到目标
TODO
13 原型与便签
原型设计是为了学习经验。它的价值不在于产生的代码,而在于吸取的教训。
21 用原型学习
原型利于在开发的早期就识别出潜在的问题点,并给予纠正——此时修正错误不仅廉价还容易。
14 领域语言
概念:
早期绑定:Early Binding,变量在编译期就绑定到特定类型。
晚期绑定:Late Binding,变量在运行期绑定到特定类型。
mixin:
宏机制:
Haskell的思想:
22 靠近问题域编程
领域语言:
- 内部领域语言,可以利用其宿主语言的特性:创建出来的领域语言更为强大,而且这种威力是毫无代价的。缺点是会受到宿主语言的语法和语义的限制。
- 外部领域语言:没有语法限制。只要为这种语言编写一个解析器即可。编写解析器可能意味着要向你的程序添加新的库和工具:bison或ANTLR之类的解析器生成器,或是诸如PEG之类的解析框架。
15 估算
23 通过估算来避免意外
24 根据代码不断迭代进度表
3.基础工具
定期给工具箱添加工具。让需求来驱使你不断选购新的工具。
16 纯文本的威力
纯文本是将知识持久地存储下来的最佳格式。
大多数二进制格式的问题是,理解数据所需的上下文与数据本身是分离的。这是在人为地将数据与其含义剥离。数据可能因此被加密起来;缺少解析它们的应用逻辑,数据变得毫无意义。然而用纯文本,就有了一种自解释的数据流,而它和创建它的应用程序是相互独立的。
25 将知识用纯文本保存
网络上的各种基本协议也是纯文本,如HTTP、SMTP、IMAP。
文件比较工具:diff、fc。
17 Shell游戏
图形工具的好处在于WYSIWYG,what you see is what you get,所见即所得;弱势之处是WYSIAYG,what you see is all you get,所见即全部。
26 发挥Shell命令的威力
设置你的专属Shell:
- 设置颜色主题
- 配置提示信息
- 别名和Shell函数
- 命令补全
18 加强编辑能力
27 游刃有余地使用编辑器
以一整年为跨度,即使编辑效率只提高4%,只要每周花在编辑上的时间有20小时,每年就能凭空多出一周时间。
19 版本控制
共享目录绝非版本控制。
感悟:个人感觉微软的SharePoint还是挺不错的。
28 永远使用版本控制
Branch,分支的好处:
- 提供隔离
- 团队项目的工作流核心通常围绕着分支来开展
20 调试
现代计算机系统仍有局限性,能干你让它干的事情,却不一定是能干你想让它干的事情。
29 解决问题,而不是责备
Bug是你的错还是别人的错并不重要。无论是谁的错,问题仍然要你来面对。
30 不要恐慌
31 修代码前先让代码在测试中失败
32 读一下那些该死的出错信息
33 select没出问题
34 不要假设,要证明
21 文本处理
35 学习一门文本处理语言
22 工程日记
日记本有三大好处:
- 比记忆更可靠
- 提供一个地方,用来保存与当前任务无关的想法(不会被遗忘)
- 像一种橡皮鸭
4.务实的偏执
36 你无法写出完美的软件
23 契约式设计
37 通过契约进行设计
24 死掉的程序不会说谎
不要写出捕获某种异常,打印日志,然后又抛出异常这种代码!!!
38 尽早崩溃
尽快检测问题的好处是可以更早崩溃,而崩溃通常是你能做的最好的事情。
Erlang和Elixir语言信奉这种哲学:防御式编程是在浪费时间,让它崩溃!
基本原理是一样的:一旦代码发现本来不可能发生的事情已发生,程序就不再可靠。从这一时刻开始,它所做的任何事情都是可疑的,所以要尽快终止它。
25 断言式编程
39 使用断言去预防不可能的事情
26 如何保持资源的平衡
40 有始有终
41 在局部行动
27 不要冲出前灯范围
前灯有一定的照射范围,被称为投射距离。软件开发中,不可能预测到需求怎么变更、项目如何进展。
42 小步前进——由始至终
不过要适可而止:别超过你能看见的范围。越是必须预测未来会怎样,就越有可能犯错。与其浪费精力为不确定的未来做设计,还不如将代码设计成可替换的。当你想要丢弃你的代码,或将其换成更合适的时,要让这一切无比容易。使代码可替换,还有助于提高内聚性、解耦和DRY,从而实现更好的总体设计。
43 避免占卜
明天看起来会和今天差不多,不会更好。
5.宁弯不折
需要尽一切努力编写尽可能宽松、灵活的代码:解耦合、响应式应用程序、变换式编程(函数管道)、少用继承、配置。
28 解耦
耦合有传递性。感悟:牵一发而动全身。
44 解耦代码让改变更容易
任何时候,只要两段代码共享点什么东西,都可能发生耦合。
耦合的症状(表象):
- 不相关的模块或库之间古怪的依赖关系;
- 对一个模块进行的
简单
修改,会传播到系统中不相关的模块里,或是破坏系统中的其他部分; - 开发人员害怕修改代码,因为他们不确定会造成什么影响;
- 会议要求每个人都必须参加,因为没有人能确定谁会受到变化的影响。
45 只管命令不要询问
TDA,tell-don’t-ask,只管命令不要询问。
不应该根据对象的内部状态做出决策,然后更新该对象。
得墨忒耳法则,LoD,Law of Demeter,迪米特法则,最少知识原则。
注:
每个单元对于其他的单元只能拥有有限的知识:只是与当前单元紧密联系的单元; 每个单元只能和它的朋友交谈:不能和陌生单元交谈; 只和自己直接的朋友交谈。
46 不要链式调用方法
47 避免全局数据
48 如果全局唯一非常重要,那么将它包装到API中
29 在现实世界中抛球杂耍
编写响应式应用程序的四个策略:
- 有限状态机:FSM,
- 观察者模式
- 发布/订阅
- 响应式编程与流
TODO
30 变换式编程
49 编程讲的是代码,而程序谈的是数据
50 不要囤积状态,传递下去
31 继承税
51 不要付继承税
52 尽量用接口来表达多态
53 用委托提供服务:“有一个”胜过“是一个”
54 利用 mixin 共享功能
32 配置
配置和代码隔离。
55 使用外部配置参数化应用程序
可能会置入配置数据的内容包括:
- 外部服务(数据库、第三方API等)的证书
- 日志级别与日志位置
- 应用程序使用的端口、IP地址、机器名及集群名
- 特定环境的校验参数
- 外部设置参数,例如税率
- 特定场合的格式化细节
- 许可证密钥
静态配置:YAML和JSON等配置文件形式;也可以放在数据库。
配置服务化:以服务的形式提供配置动态更新等能力,好处:
- 多个应用程序可共享配置信息
- 配置的变更可在任何地方进行
- 配置数据可通过专有UI维护
- 配置数据动态化
不要写出渡渡鸟般的代码:没有外部化配置,代码适应性和灵活性很差。
不要做得太过:配置数量不能过多,不会变化的数据可不用配置化。
6.并发
并发性指的是两个或更多个代码段在执行过程中表现得像是在同时运行一样。并行性是指它们的确是在同一时刻一起运行。
如果代码给几件事情强加一个顺序进来,而这个顺序对解决手头问题而言并非必需,就会发生时域耦合。
33 打破时域耦合
时域耦合:Temporal Coupling,也叫时间耦合,和时间有关的耦合。
活动图:工作流建模及分析。
56 通过分析工作流来提高并发性
并发性是一种软件机制,而并行性则和硬件相关。
将一大块工作分解成独立的小块,并行处理每一块,然后合并结果。
Elixir语言编译器启动时,会将正在构建的项目分解成不同模块,然后并行地编译每个模块。有时一个模块会依赖别的模块,在这种情况下,编译将暂停,直到被依赖模块的构建结果可用为止。当顶层模块编译完成时,就意味着所有依赖项都已编译完毕。最终结果是获得一个能利用所有(处理器)核心的快速编译器。
34 共享状态是不正确的状态
57 共享状态是不正确的状态
TODO
58 随机故障通常是并发问题
有些语言本身就内置并发支持:Rust强化数据所有权的概念;一次只能有一个变量或参数包含对任何特定可变数据段的引用。
函数式语言倾向于使所有数据不可变,从而简化并发性。注意:只是倾向性。
35 角色与进程
TODO
59 用角色实现并发性时不必共享状态
36 黑板
之前在准备软考高级-系统架构师时,经常看到黑板架构风格。
基于计算机的黑板系统,最初用于人工智能,解决一些大型和复杂的问题:语音识别、知识推理系统等。
大卫·盖勒特的Linda是最早的黑板系统之一,将事实存储为带类型的元组。应用程序可以将新的元组写入Linda,也可以采用模式匹配这种形式来取出已有的元组。
后来出现分布式的类黑板系统,如JavaSpaces及T Spaces。
数据到达的顺序无关紧要:当发布一个事实时,可以触发适当的规则。反馈也很容易处理:任何一组规则的输出都可以发布到黑板上,从而触发更多适用的规则。
60 使用黑板来协调工作流
消息系统(如,Kafka、NATS)可以像黑板一样工作。
7.当你编码时
37 听从蜥蜴脑
61 倾听你内心的蜥蜴
38 巧合式编程
软件程序能运行,大概率是一种巧合,说不定过几天就会出Bug。
对于调用的例程,其只应依赖文档上的行为。如果你做不到,不管出于什么原因,那就把你的假设记录下来。
人类天生就善于发现模式和归咎原因,即使那些东西只是巧合。感悟:和幸存者偏差感觉比较类似。
不要假设,要证明。
找到恰好能用的答案和找到正确的答案不是一回事。
62 不要依赖巧合编程
从生成需求到测试,巧合会在所有的层次上产生误导。不以既定事实为基础的假设,是所有项目的祸根。
不要只测试代码,还要测试假设。
把精力分配安排一个优先级,把时间花在重要的方面。
39 算法速度
常见的几个大O符号:
- O ( 1 ) O(1) O(1):常量,访问数组中的元素,简单代码段
- O ( l g n ) O(lg n) O(lgn):对数,如二分查找。对数的底无关紧要,等价于 O ( l o g n ) O(logn) O(logn)
- O ( n ) O(n) O(n):线性,如顺序查找
- O ( n l g n ) O(nlgn) O(nlgn):比线性差一点,还能接受,如快速排序及堆排序的平均时间
- O ( n 2 ) O(n2) O(n2):平方,如选择排序及插入排序
- O ( n 3 ) O(n3) O(n3):立方,两个 n × n n×n n×n矩阵乘法
- O ( C n ) O(Cn) O(Cn):指数,旅行推销员问题,集合划分
63 评估算法的级别
当 n n n较小时,一个简单的 O ( n 2 ) O(n2) O(n2)循环比一个复杂的 O ( n l g n ) O(nlgn) O(nlgn)算法表现得要好得多,在 O ( n l g n ) O(nlgn) O(nlgn)的内层循环非常昂贵时尤为如此。
64 对估算做测试
最快的算法并不总是最适合当前工作的。不要过早地优化,在投入宝贵的时间尝试改进算法前,确保算法确实是瓶颈,总是最为可取。
40 重构
重组现有代码实体、改变其内部结构而不改变其外部行为的规范式技术。
何时该重构:随时随地!
什么样的代码需要重构:
- 重复:违背DRY;
- 非正交设计:互相干扰;
- 过时的知识:需求偏移、变更等;
- 使用:生产实践验证后,不太重要的;
- 性能:需要提高性能;
- 通过测试:通过测试后的代码能不能更优雅、更简洁、更易于维护等;
- 复杂的现实世界:此时不重构,忍受一时痛苦,等来的将是永久的痛苦;
65 尽早重构,经常重构
简单技巧:
- 重构和添加功能不能同时进行;
- 重构前,确保有良好的测试。尽可能多地运行测试,如果变更破坏任何东西,都将很快得知;
- 采取简短而慎重的步骤:字段移到另一个类,拆分方法,变量重命名。局部修改,量变产生质变。
自动化重构:使用IDE快捷键。
41 为编码测试
66 测试与找Bug无关
测试的价值:考虑测试及编写测试时,而不是在运行测试时。
测试驱动编码。
67 测试是代码的第一个用户
测试所提供的反馈至关重要,可以指导编码过程。
TDD,测试驱动开发,测试先行开发。TDD的基本循环:
- 决定要添加一小部分功能
- 编写一个测试。等相应功能实现后,该测试会通过
- 运行所有测试。验证一下,是否只有刚刚编写的那个测试失败
- 尽量少写代码,只需保证测试通过即可。验证一下,测试现在是否可以干净地运行
- 重构代码:看看是否有办法改进刚刚编写的代码(测试或函数)。确保完成时测试仍然通过。
这个循环周期应该非常短,只有几分钟时间,这样就可以不断地编写测试,然后让它们工作。
警惕成为TDD的奴隶:
- 花费过多的时间来确保总是有100%的测试覆盖率
- 编写冗余的测试
- 设计时倾向于从底层开始,然后逐步上升
两种设计学派:
- 自上而下:应该从试图解决的整个问题开始,把它分解成几块。然后逐步拆分成更小的块,以此类推,直到最后得到小到可以用代码表示的块为止;
- 自下而上:从底层开始生成一层代码,为这些代码提供一些更接近于目标问题的抽象。然后添加一层具有更高层次的抽象。这个过程会持续下去,直到所要解决问题的抽象出现,这里也就是最后一层。
这两个学派实际上都没成功,因为都忽略软件开发中最重要的一个方面:不知道开始时在做什么。自上而下学派认为可以提前表达整个需求,然而他们做不到。自下而上学派假设他们能构建出一系列的抽象,这串抽象最终会将他们带到一个单一的顶层解决方案,但是当不知道方向时,如何决定每一层的功能呢?
68 既非自上而下,也不自下而上,基于端对端构建
69 为测试做设计
70 要对软件做测试,否则只能留给用户去做
42 基于特性测试
71 使用基于特性的测试来校验假设
43 出门在外注意安全
安全性的基本原则:
- 将攻击面的面积最小化
- 最小特权原则
- 安全的默认值
- 敏感数据要加密
- 维护安全更新
系统攻击面的面积:攻击者可以在其中输入数据、提取数据或调用服务执行的所有访问点的总和。
几种常见的攻击载体:代码复杂性、输入数据、未经身份认证的服务、经过身份认证的服务、输出数据、调试信息。
72 保持代码简洁,让攻击面最小
最小特权原则:在最短的时间内使用最少的特权。
安全的默认值:默认值应该是最安全的。
敏感数据要加密:敏感数据不要上传到Git,通过配置文件或环境变量来管理。
维护安全更新:打补丁。
73 尽早打上安全补丁
密码的反模式:好的安全性常常与常识或惯例背道而驰。
NIST的建议:
- 不要将密码长度限制在64个字符以内。推荐256为最佳长度。
- 不要截断用户选择的密码。
- 不要限制特殊字符,如
&%$#/
。NIST表示接受所有可打印的ASCII字符、空格和Unicode。 - 不要向未经身份认证的用户提供密码提示,或提示输入特定类型的信息,如:你的第一只宠物叫什么名字?
- 不要禁用浏览器中的粘贴功能。破坏浏览器和密码管理器的功能,并不能使系统更安全。实际上,它会促使用户创建更简单、更短、更容易破解的密码。NIST和英国的国家网络安全中心都特别要求校验方允许粘贴功能。
- 不要强加其他组合规则。
- 不要蛮横地要求用户在一段时间后更改密码,只在有正当理由的情况下,如系统遭到破坏。
常识与密码学:永远不要自以为是,自己来加密。应该依赖可靠的东西:经过良好审查、彻底检查、维护良好、经常更新、最好是开源的库和框架。
44 事物命名
Stroop Effect,斯特鲁普效应:字义对字体颜色的干扰效应。
在命名时,要不断地寻找方法来阐明你的意思,而这种行为本身将使你在编写代码时更好地理解代码。
在计算机科学中只有两件难事:缓存失效和命名。
爱默生:愚蠢的使用一致性是无知的妖怪。放到编程命名来说,开发者A觉得很完美的命令,在B来看则表示别的甚至完全相反的意思。由此引入结对编程。
结对编程:通过交流达到(对某个概念、事物的理解等)一致。
项目术语表,团队达成一致。
74 好好取名;需要时更名
如果命名不合理,则立马更名。IDEA里使用快捷键Ctrl + F6(更改签名 )或Shift + F6(重命名文件、变量)。不能立马更名?则说明存在更大的问题:违背ETC原则。
8.项目启动前
项目启动前:了解需求。
45 需求之坑
需求很少停留在表面。
需求被埋在层层的假设、误解和政治之下。
需求通常根本不存在。即所谓的伪需求。
75 无人确切知道自己想要什么
需求神话:程序员的用武之地,即工作是帮助人们了解他们想要什么。
76 程序员帮助人们理解他们想要什么
最初对需求的声明,往往并非绝对化的要求。
需求是一个过程。
77 需求是从反馈循环中学到的
每次迭代都以客户的直接反馈结束。这样可使我们走上正轨,并确保如果走错了方向,损失的时间是最少的。
代入客户的立场:以客户的角度来编码。
78 和用户一起工作以便从用户角度思考
需求与策略:
一个设计及实现良好的系统,应该是这样的一个系统:当策略改变时(往往会变的),只需要更新该系统的元数据。
以这种方式采集需求,自然会导向一个通过良好分解来支持元数据的系统。
79 策略即元数据
需求与现实
80 使用项目术语表
46 处理无法解决的难题
81 不要跳出框框思考——找到框框
47 携手共建
82 不要一个人埋头钻进代码中
48 敏捷的本质
83 敏捷不是一个名词;敏捷有关你如何做事
敏捷宣言价值观:
- 个体和互动高于流程和工具
- 工作的软件高于详尽的文档
- 客户合作高于合同谈判
- 响应变化高于遵循计划
因为无论是物理世界中的敏捷,还是软件开发中的敏捷,谈的都是对变化的响应,对遇到的未知事情做出的响应。
9.务实的项目
49 务实的团队
务实的团队很小,充其量也就10-12人左右。成员很少进出。每个人都很了解彼此,相互信任,互相依赖。
84 维持小而稳定的团队
应用到整个团队的务实的技巧:
- 禁止破窗:质量只能来自团队每个成员的独立贡献。质量是内在的,无法额外保证;
- 煮熟的青蛙:鼓励每个人积极监控环境的变化;
- 为知识组合安排日程:
只要有空闲时间就去做
,意味着这件事永远不会做; - 旧系统的维护:无法避免就好好做;
- 流程的反思与精炼:太多的团队忙于排水,而没有时间修补漏洞;
- 实验新技术:做一个原型来慎重考察候选技术;
- 学习和提升技能:很多技能在团队范围内传播时更有效。
85 排上日程以待其成
86 组织全功能的团队
50 椰子派不上用场
87 做能起作用的事,别赶时髦
能够即期交付并不意味着你必须每天每分钟都交付。只有当这样做在业务上有意义时,才有必要在用户需要时即期交付。
88 在用户需要时交付
过度投资于任何一种特定的方法,会让你对其他方法视而不见。当你习惯于一种方法时,很快就看不到其他的出路了。你已经僵化,变得不再能快速适应。
51 务实的入门套件
三个关键且相互关联的主题:
- 版本控制
- 回归测试
- 完全自动化
89 使用版本控制来驱动构建、测试和发布
构建、测试和部署通过提交或推送给版本控制来触发。
90 尽早测试,经常测试,自动测试
好项目的测试代码可能会比产品代码更多。
91 直到所有的测试都已运行,编码才算完成
构建可能包括几种主要的软件测试类型:单元测试、集成测试、确认和验证,以及性能测试。
92 使用破坏者检测你的测试
如果你对测试非常认真,那么可以从源码树中分出一个单独的分支,有目的地引入Bug,并验证测试是否能够捕获到。
Chaos Monkey:Netflix推出。
测试要彻底,代码覆盖率的分析工具。
93 测试状态覆盖率,而非代码覆盖率
使用基于特性的测试技术,根据被测代码的契约和不变式生成测试数据。
94 每个Bug只找一次
出现一次的Bug,大概率还是会再次发生。因此有必要引入自动化测试。
要实现完全自动化,而不是手动或半自动化,不管是使用rsync和ssh这样简单的shell脚本,还是Ansible、Puppet、Chef或Salt这样功能全面的解决方案。
95 不要使用手动程序
一旦引入手动步骤,就打破一扇非常大的窗户:破窗理论。