第一版前言
务实程序员的特征:
-
早期的采纳者/快速的适配者
你对技术和技巧有一种直觉,喜欢尝试。当接触到新东西时,你可以很快地掌握它们,并把它们与其他的知识结合起来。你的信心来自经验。
-
好奇
你倾向于问问题。你热衷于收集各种细微的事实,坚信它们会影响自己多年后的决策。
-
批判性的思考者
你在没有得到证实前很少接受既定的现实。
-
现实主义
你试图理解所面临的每个问题的本质。这种现实主义让你对事情有多困难。需要用多长时间有一个很好的感知。
-
多面手
你努力熟悉各种技术和环境,并努力跟上新的进展。虽然目前的工作可能要求你在某个专门领域成为行家,但你总是能够进入新的领域,迎接新的挑战。
每一天都要努力打磨你的技能,并往技能库里添加新的工具。
第1章 务实的哲学
你的事业是你自己的,你的人生是你的——都是你自己所拥有的。
1.1 人生是你的
人生是你自己的,是你在拥有、经营和创造。
提示:你有权选择。
1.2 我的源码被猫吃了
一个务实的程序员能完全掌握自己的职业生涯,从不害怕承认无知和错误。
我们固然会为我们的能力而骄傲,但面对缺点时也必须诚实——承认我们犯了错误,而且是因为我们的无知而犯下的。
团队信任
你的团队需要能信赖和依赖你——你也应该同样地放心依赖他们每个人。在一个以信任为基础的健康环境中,你可以安全地说出你的想法,表达你的思想。
承担责任
你必须分析超出你控制范围的风险情况。如果责任的伦理内涵过于含糊,或是面对无法实现的情况,抑或风险过大,你都有权不承担责任。你必须根据自己的价值观和判断做决定。
当你决定对一个结果承担责任时,要明白这意味着你将承接相关的义务。当你犯了错误,或是做出了错误的判断时,诚实的承认它,并尝试给出选择。
不要把问题归咎于别人或其他什么事情上,也不要寻找借口。不要把所有问题都归咎于供应商、编程语言、管理或是同事。这些因素都可能是问题的一部分。它们的确会对解决方案造成影响,但不是给你的借口。
提示:提供选择,别找借口。
给出选择,而不是找借口。不要说搞不定;解释一下要做些什么才能挽回这个局面。
别害怕请教别人,别害怕承认自己需要帮助。
当意识到自己在说“我不知道时,一定要接着说“——但是我会去搞清楚”。
1.3 软件的熵
有很多因素会导致软件腐烂,最重要的一个似乎是项目工作中的心理性状态,或者说文化。无视一个明显损坏的东西,会强化这样一种观念:看来没有什么是能修好的,也没人在乎,一切都命中注定了。所有的负面情绪会在团队成员间蔓延,变成恶性循环。
提示:不要放任破窗。
不要搁置“破窗”(糟糕的设计、错误的决定、低劣的代码)不去修理。每发现一个就赶紧修一个。如果没有足够的时间完全修好,那么可以把它钉起来。也许你可以注释掉那些糟糕的代码,显示一行“尚未实现”的信息,或用假数据先替代一下。采取行动,预防进一步的损害发生,表明一切尽在你的掌握中。
1.4 先勿伤害
不要只是因为一些东西非常危急,就去造成附带损害。
一扇破窗——一段设计糟糕的代码,一个让团队在整个项目周期内都必须要遵守的糟糕管理决定——就是一切衰退的开始。
一定要告诉自己,“不要打破窗户”。
1.5 石头做的汤和煮熟的青蛙
找出你合理的请求,然后不断完善。一旦有成果产出,展示给人们看,让他们大吃一惊。人们都觉得,加入一个推进中的成功项目更容易一些。
提示:做推动变革的催化剂。
提示:牢记全景。
永远留意着大局,持续不断地审视你身边发生的事情,而不要只专注于你个人在做的事情。
1.6 够好即可的软件
训练自己写出够好即可的软件——对用户、未来的维护者来说够好即可,只要好的程度能让你自己内心平静就可以。
所有系统必须达到用户需求才算完成,需要达到基本的性能、隐私和安全标准。
让用户参与权衡
无视来自用户方面的需求,一味地像程序中堆砌功能,一次又一次打磨代码,这是很不专业的表现。
提示:将质量要求视为需求问题。
知道何时止步
不要让过度的修饰和精炼侵蚀掉一个完好的程序。继续前行,让代码在它该有的位置驻留一段时间。
1.7 知识组合
知识和经验是你最重要的专业资产。不过它们是一种时效资产。
学习新事物的能力是你最重要的战略资产。
构建知识组合
-
定期投资
-
多样化
计算机技术变化迅猛,熟悉的技能越多,越能适应变化。
-
风险管理
不要把所有的技术鸡蛋都放在一个篮子里。
-
低买高卖
在一项新兴技术变得流行之前就开始学习。
-
重新评估调整
提示:对知识组合做定期投资。
目标
-
每年学习一门新语言
不同的语言以不同的方式解决相同的问题。多学习几种不同的解决方法,能帮助自己拓宽思维,避免陷入陈规。
-
每月读一本技术书
深入理解需要读长篇的书。在你掌握了当前正在使用的所有技术后,扩展你的领域,学习一些和你的项目不相关的东西。
-
还要读非技术书
掌握软技能。
-
上课
在本地大学或是网上找一些有趣的课程。
-
加入本地的用户组和交流群
不要只是去当听众,要主动参与。独来独往对你的职业生涯是致命的;了解一下公司之外的人们都在做什么。
-
尝试不同的环境
尝试不同的操作系统或编辑器环境。
-
与时俱进
关心一下和你当前项目不同的技术,阅读相关的新闻和技术贴。
持续投资非常重要。一旦你进入了对某个新语言或新技术的舒适期,向前走,再学一个。
学习的过程将会扩展你的思维,为你打开全新可能性的大门,让你领悟新的做事方式。想法的交叉传授很重要;试着把你领悟到的东西应用到你当前的项目中。即使项目没有用到某些技术,你也可以借鉴一些想法。
学习的机会
当你对一个问题毫无思路时,承认你无法作答,把找答案作为一项个人挑战。问问周围的人,或是上网搜索,不要让问题沉寂下去。
批判性思维
提示:批判性地分析你读到和听到的东西。
批判性思维,问几个值得思考的问题:
-
问“五个为什么”
每当有了答案后,还要追问“为什么”。
-
谁从中收益
-
有什么背景
每件事都发生在它自己的背景下,这也是为何“能解决所有问题”的方案通常不存在。
-
什么时候在哪里可以工作起来
是在什么情况下?太晚了吗?太早了吗?不要停留在一阶思维下(接下来会发生什么),要进行二阶思考:当它结束后还会发生什么?
-
为什么这是个问题
是否存在一个基础模型?这个基础模型是怎么工作的?
1.8 交流
作为开发人员,我们必须在多个层次上进行交流。
提示:英语就是另一门编程语言。
了解听众
传达信息的过程才算交流。为了把信息送达,你需要了解受众的需求、兴趣和能力。
收集反馈,不要只是等待问题的出现,把它问出来。
明白自己想说什么
计划好你想要说什么,写一个大纲,然后问自己:“这是否用正确的方式向我的听众传达了我想表达的东西?”
在参加会议或给大客户打电话之前,记下你想要沟通的想法,并准备多个让对方理解的策略。
选择时机
说的东西不仅要内容合适,说的时间也要合适。问自己一个简单问题:“现在是讨论…的好时机吗?”
挑选风格
根据听众调整你的表达方式。
让它看起来不错
糟糕的外观能毁掉一个好的内容,你需要一个好看的包装。
让听众参与
只要有可能,让读者参与到文档的初稿中来。听取他们的反馈,汲取他们的智慧,通过这个过程,才能编写出更好的文档。
做倾听者
把会议变成一场对话,你将更有效地表达你的观点。
回应别人
记得回复邮件和信息,就算简单地说一句“我稍后答复你”都好。随时知会别人,能让人更容易原谅你偶然的疏忽,让人觉得你并没有忘记他们。
提示:说什么和怎么说同样重要。
除非你与世隔绝,否则必须学会交流。越是有效的交流,影响力就越大。
文档
提示:把文档嵌进去,而不要拴在表面。
建议给模块和导出函数都加上注释,这能在其他开发者使用的时候,给他们很大的助力,但不是所有都必须加上。
注释源码是一个绝佳的机会,可以用来记录那些在其他地方无法记录的项目细节。
第2章 务实的方法
2.1 优秀设计的精髓
提示:优秀的设计比糟糕的设计更容易变更。
信奉ETC(Easier To Change,更容易变更)原则。ETC是一种价值观念,不是一条规则。
有意识的问自己:“我刚刚做的事情是让整个系统更容易改变还是更难改变?”当保存文件时问一遍,当写测试时问一遍,当修复Bug时问一遍。
2.2 DRY——邪恶的重复
维护从来不是离散的活动,而是整个开发过程中的常态。
DRY原则:在一个系统中,每一处知识都必须单一、明确、权威地表达。
提示:DRY——不要重复自己。
并非所有的代码重复都是知识的重复。
一个模块提供的所有服务都应该通过统一的约定来提供,该约定不应表露出其内部实现是基于储存还是基于运算的。
开发人员之间的重复
鼓励开发人员之间积极频繁的交流。指派团队中的一个人做项目知识管理员,他的工作就是促进知识的传播。
提示:让复用变得更容易。
你要努力的方向,应该是孕育出一个更容易找到和复用已有事物的环境,而不是自己重新编写。
2.3 正交性
在计算机科学中,这个术语象征着独立性或解耦性。对于两个或多个事物,其中一个的改变不影响其他任何一个,则这些事物是正交的。
正交的好处
非正交系统天生就复杂,难以变更和控制。
提示:消除不相关事物之间的影响。
设计的组件应该自成一体:独立自主,有单一的清晰定义的意图。
正交的收益:提高生产力及降低风险。
- 将变更限制在局部后,开发时间和测试时间都会减少。
- 促进重用的可能。与其他新组件重用。
- 一个模块生病不太可能影响到系统的其他部分。
设计
当规划好组件后,问问自己:**如一个特别功能背后的需求发生显著改变,有多少模块会受影响?**对于一个正交系统,答案应该是“一个”。
不要依赖那些你无法控制的东西。
工具包和程序库
当拿出一个工具包时,问问自己,它是否会将一些不应该有的变化强加给你的代码。
编码
- 保持代码解耦
- 避免全局数据
- 避免相似的函数
养成不断质疑代码的习惯,只要有机会就重新组织、改善其结构和正交性,这个过程称为重构。
测试
基于正交性设计和实现的系统更容易测试。
2.4 可逆性
实现一件事情的方法往往不止一种。
寻找重要的需求,那些定义了系统的需求。寻找你有疑问的地方,那些你认为有重大风险的地方。然后对开发进行优先级排序,首先从这些地方开始编码。
提示:使曳光弹找到目标。
使用曳光代码的优势:
- 用户可以更早地获得能工作的东西
- 开发者构造了一个可以在其中工作的框架
- 你有一个集成平台
- 你有可以演示的东西
- 你对进度有更好的感觉
曳光弹并不总能击中目标
当你不能100%确定要做什么的时候,就用这个技术。如果一开始的一系列尝试出错了,请不要诧异,去弄清楚如何改变你已经做好的东西,想办法让它靠近目标,这种精益的开发方式,让你能够以更快的速度、更小的成本,收集到针对应用程序的反馈,并生成一个新的更准确的版本。
原型与曳光代码的区别是原型生成的是一次性代码,而曳光代码是最终系统框架的组成部分。
2.5 原型与便签
原型制作比完整的产品制作要便宜得多。不必在制作真正的东西上投入,就可以找出有风险或不确定的因素。
需要做原型的东西
原型适合于任何有风险的东西,任何之前没有尝试过或对最终系统来说很关键的东西,任何未经证实、实验性或可疑的东西。
可以为下列事物做原型:
- 架构
- 已存在的系统中的新功能
- 数据结构或外部数据的内容
- 第三方工具或组件
- 性能问题
- 用户界面设计
原型设计是为了学习经验。它的价值不在于产生的代码,而在于吸取的教训。
提示:用原型学习。
怎样使用原型
在原型上可以忽略的细节:
- 正确性
- 完整性
- 健壮性
- 格式
白板也是一个画原型的好工具。
2.6 领域语言
提示:靠近问题域编程。
语言分为内部领域语言和外部领域语言。
内部语言和外部语言之间的权衡
内部领域语言可以利用其宿主语言的特性:创建出来的领域语言更为强大。
内部领域语言的缺点是会受到宿主语言的语法和语义的限制。外部语言没有这样的限制。外部语言只需编写一个解析器就可以了。
建议:花费的努力不要比节省下来的还多。如果一个项目很复杂,可以尝试编写一个翻译程序来生成代码。
2.7 估算
提示:通过估算来避免意外。
多精确采购
当有人让你估算的时候,你要问自己的第一个问题是,答案会用在什么场合下,对方需要高的精度,还是只是一个大约的估计?
估算从何而来
问问已经做过的人,在全身投入建模之前,先问问周围人有没有经过过类似的情况,看看他们是如何解决问题的。
-
理解在问什么
你还需要掌握问题域的范围,范围通常是问题的隐含前提,你需要养成习惯,在开始猜测之前就加以考虑。
-
对系统进行建模
建立粗略的思维模型框架。
-
把模型分解成组件
-
确定每个参数的值
-
计算答案
-
记录你的估算能力
当估算错误时,应该复盘为什么会偏离,避免下次继续犯同一错误。
估算项目进度
两种不确定性的技巧
粉刷导弹
列出一个乐观的、一个最有可能和一个悲观的估算,计算最佳和最差时间。
吃掉大象
重复下列步骤做增量开发:
- 检查需求
- 分析风险
- 设计、实现、集成
- 和用户一起验证
提示:根据代码不断迭代进度表。
被要求一个估算时说什么
应该说:“我等一下答复你。”
放慢节奏,花点时间完成本部分描述的步骤。
第3章 基础工具
定期给工具箱添加工具。要一直寻找更好的做事方法。如果感觉手头的工具搞不定遇到的问题,先记录下来,再去试试其他的工具,只要它足够强大,就可能对你有帮助。
纯文本的威力
提示:将知识用纯文本保存。
文本的威力
所谓纯文本,不是说文本是无结构的:HTML、JSON、YAML等这些都是纯文本。纯文本的好处:
- 为防备老化而加保险
- 利用杠杆效应让已有工具发挥最大优势
- 易于测试
Shell游戏
如果使用图形界面去完成所有工作,就会错失环境的全部能力。图形化工具的好处在于所见即所得;弱势之处是所见即全部。
提示:发挥Shell命令的威力。
加强编辑能力
提示:游刃有余地使用编辑器。
只学习那些让你过得更舒服的命令。
编辑时要自省,如果发现自己在做重复的事,可以尝试看有没有更好的方法。当找到一个有用的方法之后要尽快将它内化成肌肉记忆(不断重复)。
深入研究一下编辑器的扩展语言,搞明白怎样用它来将一些重复工作自动化。
版本控制
提示:永远使用版本控制。
确保所有内容都在版本控制之下。
调试
没有人能写出完美的软件。
调试心理学
接受一个事实:调试只是在解决问题并为此攻关。
提示:去解决问题,而不是责备。
Bug是你的错还是别人的错并不重要。无论是谁的错,问题仍然要你来面对。
调试心态
在开始调试之前,正确的心态非常重要。
提示:不要恐慌。
不要在“但那不可能发生”的思路上浪费哪怕一个神经元,因为很明显它会发生,而且已经发生了。
永远要去发掘问题的根本原因,而不仅仅停留在问题的表面现象。
从哪里开始
- 拜访报告Bug的用户,这样才能收集到比最初提供的更多的数据。
- 认为测试对应用程序的测试而言还不够。必须粗暴地测试所有边界条件,并且复原实际的最终用户使用模式。
提示:修代码前先让代码在测试中失败。
提示:读一下那些该死的出错信息。
身处陌生之地的程序员
输入值的敏感度:获取数据集的副本,并用数据测试本地运行的应用程序副本,确保其在本地也能崩溃。然后将数据用二分法分解,知道能准确地分离出导致崩溃的输入值。
使用跟踪信息来获取程序或数据结构的变化。
二分法
橡皮鸭:向其他人解决该问题,你一步步解释代码用来做什么,这一简单的做法通常能让问题跳出屏幕来暴露自己。
提示:“select”没出问题(以为是第三方库出问题时可读读文档,或许就能解决)。
让人吃惊的元素
运行多年的代码,同样有可能会出Bug。
提示:不要假设,要证明。
文本处理
提示:学习一门文本处理语言。
Python和Ruby是不错的选择。
工程日记
试着拥有一本工程日记。
第4章 务实的偏执
提示:你无法写出完美的软件。
务实的程序员不相信自己的代码,并且会为自己的错误建立防御机制。
契约式设计
契约规定了你的权利和责任,同时也规定了对方的权利和责任。如果任何一方不遵守契约,还会面对一个针对后果的协议。
DBC
文档化及对主张进行检验是契约式设计(DBC)的核心。
提示:通过契约进行设计。
通过使用断言或DBC机制来验证前置条件、后置条件和不变式,可以尽早崩溃并报告有关问题更准确的信息。
语义不变式,是事物意义的核心,而不受策略的影响(策略用于更新动态的业务规则)。
设计软件的时候,也要设计设计它的契约。
死掉的程序不会说谎
务实的程序员会告诉自己,如果出现错误,就意味着已经发生了非常糟糕的事情。
捕获再释放只适合用在鱼身上
提示:尽早崩溃。
崩溃,不要制造垃圾
“防御式编程是在浪费时间,让它崩溃!”
一个死掉的程序,通常比一个瘫痪的程序,造成的损害要小得多。
断言式编程
提示:使用断言去预防不可能的事情。
如何保持资源的平衡
分配资源,使用它,然后释放它。
提示:有始有终。
分配资源的函数或对象,对释放资源应负有责任。
提示:在局部行动。
嵌套的分配
对于一次需要不止一个资源的例程,可以对这个资源分配的基本模式做一些扩展:
- 释放资源的顺序与分配资源的顺序相反。
- 在代码的不同位置,如果都会分配同一组资源,就始终以相同的顺序分配它们。这将减少死锁的可能性。
检查平衡
为每种类型的资源做一个封装起,使用这些封装器去跟踪所有的分配和释放操作,确保资源的用量并没有较上个执行循环不断递增。
不要冲出前灯范围
提示:小步前进——由始至终。
总是采取经过深思熟虑的小步骤,同时检查反馈,并在推进前不断调整。把反馈的频率当作速度限制,永远不要进行”太大“的步骤或任务。
怎样的任务才算太大?任何大到需要“占卜”的任务:
- 估计未来几个月之后的完成日期
- 为将来的维护或可扩展性预设方案
- 猜测用户将来的需求
- 猜测将来有什么技术可用
别超过你能看见的范围。越是必须预测未来会怎样,就越有可能犯错。与其浪费精力为不确定的未来做设计,还不如将代码设计成可替换的。
提示:避免占卜。
第5章 宁弯不折
解耦
耦合是修改之敌,因为它将事情连接在一起,要改就得一起改。
提示:解耦代码让改变更容易。
任何时候,只要两端代码共享点什么东西,都可能发生耦合。
提示:只管命令不要询问(TDA)。
德墨忒尔法则
定义在C类中函数只应该调用:
- C类其他实例的方法
- 它的参数
- 它所创建出来的对象的方法,包括在栈上和堆上的对象
提示:不要链式调用方法。
“一个点”规则有一个很大的例外:如果你链式调用的东西真的不太可能改变,那么这个规则就不适用。
邪恶的全局化
全局可访问的数据是应用程序组件之间耦合的潜在来源。
提示:避免全局数据。
提示:如果全局唯一非常重要,那么将它包装到API中。
在现实世界中抛球杂耍
事件
事件表达出信息的可用性。编写响应式事件的应用程序的四个策略:
- 有限状态机
- 观察者模式
- 发布/订阅
- 响应式编程与流
有限状态机(FSM)
状态机基本上就是怎样处理事件的一个规范。它由一组状态组合,其中一个是当前状态。
FSM的妙处在于,我们可以将其纯粹地表示为数据。把状态保存在外部存储器中,并使用这些状态来驱动状态机,这是处理工作流需求的好方法。
观察者模式
观察者根据其兴趣被注册到观察对象上,这通常由传递一个待调用的函数引用来实现。当事件发生时,观察对象遍历它的观察者列表,并调用每个传递给它的函数。事件作为调用参数提供给函数。
观察者模式有一个问题:因为每个观察者都必须与观察对象注册在一起,所以它引入了耦合。此外,由于在典型的实现中,回调是由观察对象以同步的方式内联处理的,因此可能会导致性能瓶颈。
发布/订阅
发布/订阅(pubsub)推广了观察者模式,同时解决了耦合和性能问题。
与观察者模式不同,发布者和订阅者之间的通信是在代码之外处理的,并且可能是异步的。
缺点:很难查看在一个重度使用pubsub模式的系统中发生了什么,无法在查看发布者的同时立即看到有哪些订阅者涉及特定的消息。
响应式编程、流与事件
熟悉的电子表格比如Excel,就是响应式编程。如果一个单元格引用了另外一个单元格,当另外一个单元格的值改变时,该值也会随之变化。
流让我们把事件当作数据集合来对待,流可以是异步的。
事件流通常以事件触发的形式增殖。事件流是异步集合。
围绕事件编写的代码都比对应的线性代码更容易响应,解耦效果也更好。
变换式编程
所有程序其实都是对数据的一种变换——将输入转换成输出。当我们在构思设计时,很少考虑创建变换过程,我们操心的是类和模块、数据结构和算法、语言和框架。
我们需要重新把程序视为从输入到输出的一个变换。当这样做时,结构将变得更清晰,错误处理更加一致,耦合降低了很多。
# 获取目录树中最长的五个文件
find . -type f | xargs wc -l | sort -n | tail -6 | head -5
这像一条工业装配线:一端输入原始数据,另一端输出成品(信息)。
提示:编程讲的是代码,而程序谈的是数据。
寻找变换
从需求开始并确定它的输入和输出。将整个程序表示为函数,找到从输入到输出的步骤,这是一个自顶向下的方法。
|>
管道操作符的作用是将左侧的值作为右侧函数的参数,插入在第一个参数上。目前JavaScript不支持管道运算符。
提示:不要囤积状态,传递下去。
将代码看作一系列变化,可以为编程打开思路。一旦养成这个习惯,将发现代码变得更简洁,函数变得更短,而设计也变得更平顺。
继承税
通过继承共享代码的问题
继承就是耦合。不仅子类耦合到父类,以及父类的父类等,而且使用子类的代码也耦合到所有祖先类。
通过继承构建类型的问题
多重继承的耦合性更强
提示:不要付继承税。
更好的替代方案
- 接口与协议
- 委托
- mixin与特征
接口与协议
由一个类来实现一个或多个行为集。implements
实现接口interface,这个类似于ts中的class实现interface。
提示:尽量用接口来表达多态。
委托
提示:用委托提供服务:“有一个”胜过“是一个”。
mixin、特征、类别、协议扩展…
创建一组函数,给这个函数起一个名字,然后它去扩展一个类或对象。
特性:将现有事物和新事物的功能合并在一起。
提示:利用mixin共享功能。
挑战
下次发现自己在进行子类化的时候,花点时间检查一下备选方案。能通过接口、委托和mixin来实现想要的东西吗?这样做能减少耦合吗?
配置
如果代码依赖某些值,而这些值在应用程序发布后还有可能改变,那么就把这些值放在程序外部。通过这种方式来参数化应用程序,让代码适应其运行的环境。
提示:使用外部配置参数化应用程序。
可能会置入配置数据的内容包括:
- 外部服务(数据库、第三方API等)的证书
- 日志级别与日志位置
- 应用程序使用的端口、IP地址、机器名及集群名
- 特定环境的校验参数
- 外部设置参数,例如税率
- 特定场合的格式化细节
- 许可证密钥
静态配置
YAML和JSON是目前流行的纯文本格式的配置文件。
配置服务化
配置数据应保持在应用程序外部,但不直接放在文件中,也不放在数据库里;而是储存在一个服务API之后。这样做的好处:
- 在身份认证和访问权限控制将多个应用程序的可见内容阻隔开的情况下,让多个应用程序可以共享配置信息
- 配置的变更可以在任何地方进行
- 配置数据可以通过专有UI维护
- 配置数据变得动态
第6章 并发
并发性指的是两个或更多个代码段在执行过程中表现得像是在同时运行一样。并行性是指它们的确是在同一时刻一起运行。
时域耦合:代码给几个事情强加了一个顺序进来,而这个顺序对解决手头问题而言并非必须。
打破时域耦合
绝多大数人的思考方式是倾向于将事情线性化,先做这个,再做那个。但这样的思考方式会导向时域耦合。
搜寻并发性
分析哪些事情可以同时进行,哪些必须按照严格的顺序发生。
提示:通过分析工作流来提高并发性。
活动图由一组用于圆角框表示的活动构成。可以在活动图中标识出能够并行和不能并行的活动,以此来最大化并行性。
并行的机会
并发性是一种软件机制,而并行性则和硬件相关。
将一大块工作分解成独立的小块,并行处理每一块,然后合并结果。
挑战
在早上为开始工作做准备的时候,你会同时执行多少项任务?可以用UML活动图来表示这些任务吗?能找到通过增加并发性来更快做好准备的方法吗?
共享状态是不正确的状态
提示:共享状态是不正确的状态。
非原子更新
原子性的设计:
信号量是一个在同一时间只能让一个人持有的东西。术语有locl/unlock、claim/release等。
非事务性更新
当代码的两个或多个实例可以同时访问某些资源时,就会出现潜在的问题。
提示:随机故障通常是并发问题。
在共享资源环境中实现并发非常难,尝试的过程将充满挑战。
角色与进程
对于实现并发性,可以使用角色与进程,而且无须同步访问共享内存。
-
角色是一个独立的虚拟处理单元,具有自己的本地(且私有的)状态。每个角色都有一个信箱。当消息出现在信箱中且角色处于空闲状态时,角色被激活并开始处理消息。处理完该条消息后,它将继续处理信箱中的其他消息,如果信箱是空的,则返回休眠状态。
在处理消息的进程中,一个角色可以创建其他角色,可以向其他认识的角色发送消息,也可以创建一个新的状态,用来在处理下一条消息时作为当前状态。
-
进程通常代表一种更通用的虚拟处理机,它一般由操作系统实现,可以让并发处理更容易。进程也能被约束为以角色的形式运转。
角色只会是并发的
有些事情,在角色的定义中是不会出现的:
- 没有一件事是可控的。
- 系统中唯一的状态,保存在消息和每个角色的本地状态中。消息除了接收方可以读取,没有其他途径检查;本地状态在角色之外无法被访问。
- 所有的消息都是单向的
- 角色会将每一条消息处理完,一次只会处理一条。
提示:用角色实现并发性时不必共享状态。
没有显示的并发
在角色模型中,不需要为处理并发性编写任何特定代码,因为没有共享状态。角色会基于收到的消息自己处理这些事情。
挑战
你当前是否还在写用互斥量来保护共享数据的代码。为什么不试试使用相同的代码基于角色模型来做一个原型呢?
黑板
黑板方法的关键特点有:
- 没有哪个侦探需要知道其他侦探的存在
- 这些侦探可能受过不同的训练,可能会不同的教育水平和专业知识。
- 在案件的处理过程中,不同的侦探可能加入又退出,也可能轮班工作。
- 黑板的内容没有限制,可能是图片、句子、物证等等。
这是一种放任自由的并发形式。
提示:使用黑板来协调工作流。
第7章 当你编码时
编码不是机械工作。务实的程序员会对所有代码进行批判性思考,包括自己的代码。我们不断看到程序和设计的改进空间。
测试的主要收益来自于你思考和编写测试期间,而不是运行测试那一刻。
7.1 听从蜥蜴脑
无论直觉是怎么来的,都有一个共同点:无法用语言表达。直觉让你感觉,而不是思考。
如果能感到一种挥之不去的疑虑,或在面对一项任务时感觉有些不情愿,那可能是那些经验视图和你说些什么——要注意听。让直觉来提高你的绩效。
如何同蜥蜴脑交谈
提示:倾听你内心的蜥蜴。
停止正在做的事情,给自己一点时间和空间,让大脑自我组织。远离键盘,停止对代码的思考,做一些暂时不需要动脑筋的事情——散步、吃午饭、和别人聊天,或是先睡一觉。让想法自己从大脑的各个层面渗透出来:对此不用很刻意,最终这些想法可能会上升到有意识的水平。
如果这不起作用,试着把问题外化。告诉朋友、橡皮鸭等等。但如果试过这些方法之后,还是无法脱困,就该告诉大脑:打算要做的事情,并没有那么重要,可以用做原型的方法来干这些事。
游戏时间
- 可以使用便签或其他方式,告诉自己正在做原型
- 提醒自己,原型注定要出问题,提醒自己,原型即使没有出问题也会被扔掉。这样做没有坏处。
- 在空编辑器窗口中,写一条注释,用一句话描述你想学点什么或做点什么。
- 开始编码。
不仅仅是你的代码
当在阅读他人的代码时,慢慢啃,在看似重要的地方做笔记,这是一件苦差事,但很有用。
不仅仅是代码
学会在编码时听从直觉是一项需要培养的重要技能。它不仅适用于代码,还有比如需求、设计等等,在问题发生之前用直觉找到并主动暴露它。
挑战
有没有什么事情你知道应该去做,但却因为觉得它可怕或困难而推迟?试试本部分中的技巧。把时间限制在一个小时或是两个小时之内,并向自己保证:当铃声响起时,会删除做过的事情。你从中学到了什么?
7.2 巧合式编程
记住那个士兵的故事,下结论时要保持警惕,以免出错。我们应该避免通过巧合编程,因为靠运气和意外来获得成功是行不通的,编程应该深思熟虑。
怎样算是靠巧合编程
- 实现造成的偶然事件
- 和结果解决是不够的
- 虚幻的模式
- 环境造成的偶然事件
- 隐式的假设
提示:不要依赖巧合编程。
如何深思熟虑地编程
- 时刻注意你在做什么。
- 你能向一个更初级的程序员详细解释一下代码吗?如果做不到,也许正在依赖某个巧合。
- 不要在黑暗中编码。构建一个没有完全掌握的应用程序,或者使用一个并不理解的技术,就很可能会被巧合咬伤。如果不确定它为什么能用,就不会知道它为什么出错。
- 要按计划推进。
- 只依赖可靠的东西。不要依赖假设。如果你不知道某件事是否可靠,那你就要做好最坏的打算。
- 将假设文档化。契约式设计,可以帮助你在心中澄清设想,也可以帮助你与他人沟通。
- 不要只测试代码,还是测试假设。不要猜,去实际试一下。
- 为你的精力投放排一个优先级。要把时间花在重要的方面。如果根本原理或基础设施都会出问题,花哨的外表则更是不堪一击。
- 不要成为历史的奴隶。不要让现有的代码去支配未来的代码。如果不再合适,所有代码都可以替换。
下次碰到有什么事情看起来可行,但你不知道为什么,确保它不是一个巧合。
7.3 算法速度
务实的程序员几乎每天都在做:评估算法所用的时间、处理器、内存等资源。
大O符号
O ( n 2 ) O(n^2) O(n2)表示在最糟糕的情况下,时间会随n的平方变化, O ( ) O() O()这个符号表示正在测量的对象(时间、内存等)的值设置了一个上限。
O ( ) O() O()符号不仅适用于时间,也可以用它来表示算法使用的任何其他资源。
- O ( 1 ) O(1) O(1) 常量(访问数组中的元素,简单的代码段)
- O ( l g n ) O(lg n) O(lgn) 对数(二分查找)。对数的底无关紧要,他等价于 O ( l o g n ) O(log n) O(logn)
- O ( n ) O(n) O(n) 线性(顺序查找)
- O ( n l g n ) O(n lg n) O(nlgn)比线性槽糕一点,不过还能接受(快速排序及堆排序的平均时间)
- O ( n 2 ) O(n^2) O(n2) 平方(选择排序及插入排序)
- O ( n 3 ) O(n^3) O(n3) 立方(两个n×n矩阵乘法)
- O ( C n ) O(C^n) O(Cn) 指数
常识判断
用常识判断出需要基本算法的级别。
- 简单循环——算法大概在 O ( n ) O(n) O(n)。
- 嵌套循环——时间复杂度是 O ( n 2 ) O(n^2) O(n2)。
- 二分法—— O ( l g n ) O(lg n) O(lgn)。
- 分治法——如果算法会将输入分成两半来分开处理,最后再把结果合并起来,那么就是 O ( n l g n ) O(n lg n) O(nlgn)
- 组合问题
实践中的算法速度
在职业生涯中,不太可能花很多时间来编写排序程序。库里面有现成的实现,可以拿过来直接使用。
当我们写循环时,会明白这是一个 O ( n ) O(n) O(n)的算法,嵌套循环就是 O ( n 2 ) O(n^2) O(n2)的算法,这是应该问问自己这些值能有多大,如果这些是有界的,那么就能知道代码运行大概需要多长时间,如果这些数字依赖于外部因素,那么可能需要停下来,考虑一下较大值对运行时间或内存消耗的影响。
提示:评估算法的级别。
如果不确定代码会运行多久,或是不知道代码需要多少内存,那就跑跑看。把不同的量和对应的开销制成一张图,图上曲线的形状很容易理解。
提示:对估算做测试。
对于小输入集, O ( n 2 ) O(n^2) O(n2)算法不一定比 O ( n l o g n ) O(nlogn) O(nlogn)算法差。注意不要过早地优化。在投入宝贵的时间尝试改进算法之前,确保算法确实是瓶颈,总是最为可取。
7.4 重构
代码需要演化,它不是一个静态的东西。
软件开发更像是园艺而非建筑,需要时刻检测花园的健康状况,并根据需要做出调整。
对代码进行重写、修订、结构调整的这一系列工作被称为重组。重构的定义:重组现有代码实体、改变其内部结构而不改变其外部行为的规范式技术。这一定义的关键部分是:
- 这项活动是有规范的,不应随意为之。
- 外部行为不变;现在不是添加功能的时候。
重构并不是一种特殊的、隆重的、偶尔进行的活动,重构是一项日复一日的工作,需要采取低风险的小步骤进行,它更像是耙松和除草这类活动。这是一种有针对性的、精确的方法,有助于保持代码易于更改,而不是对代码库进行自由的、大规模的重写。
为了保证外部行为没有改变,你需要良好的自动化单元测试来验证代码的行为。
何时该重构
当你学到一些东西,当你比去年、昨天甚至十分钟前更了解某事时,进行重构。不要犹豫,去改掉它。
无论问题是多是少,都有可能促使我们对代码进行重构:
-
重复
你发现了一处违背DRY原则的地方。
-
非正交设计
发现做一些事情可以让其更为正交。
-
过时的知识
事情变化了,需求偏移了,你对问题的了解更多了——代码也需要成长
-
使用
当系统在真实的环境中被真实的人使用时,你会意识到,与以前的认识相比,一些特性现在看来更为重要,反而“必须拥有”的特性可能并不重要。
-
性能
你需要将功能从系统的一个区域移动到另一个区域以提高性能。
-
通过了测试
重构应该是一个小规模的活动,需要良好的测试支持。
复杂的现实世界
如果现在不进行重构,那么以后就需要投入更多的时间来解决问题——因为需要处理更多的依赖关系。
提示:尽早重构,经常重构。
重构,和大多数事情一样,在问题很小的时候做起来更容易,要把它当做编码日常活动。
怎样重构
重构的核心是重新设计。重构是一项需要慢慢地、有意地、仔细地进行的活动。
- 不要试图让重构和添加功能同时进行。
- 在开始重构之前,确保有良好的测试。尽可能多地运行测试。如果变更破坏了任何东西,都将很快得知。
- 采取简单而慎重的步骤:将字段从一个类移动到另一个类,拆分方法,重命名变量。重构通常涉及对许多局部进行的修改,这些局部修改最终会导致更大范围的修改。如果保持小步骤,并在每个步骤之后进行测试,就能避免冗长的调试。
7.5 为编码测试
提示:测试与找Bug无关。
测试获得的主要好处发生在你考虑测试及编写测试的时候,而不是在运行测试的时候。
测试驱动编码
为方法写一个测试的考虑过程,使我们得以从外部看待这个方法,这让我们看起来是代码的客户,而不是代码的作者。
提示:测试是代码的第一个用户。
测试所提供的反馈至关重要,可以指导编码过程。
与其他代码紧密耦合的函数或方法很难进行测试,因为你必须在运行方法之前设置好所有的环境。所以,让你的东西可测试也减少了它的耦合。
如果你在开始编写代码之前,就考虑过测试边界条件及其工作方式,那么就很可能会发现简化函数的逻辑模式。如果你考虑过需要测试的错误条件,那么将会相应地去构造这个函数。
测试驱动开发
TDD的基本循环是:
- 决定要添加一小部分功能。
- 编写一个测试。等相应功能实现后,该测试会通过。
- 运行所有测试。验证一下,是否只有刚刚编写的那个测试失败了。
- 尽量少写代码,只需要保证测试通过即可。验证一下,测试现在是否可以干净地运行。
- 重构代码:看看是否有办法改进刚刚编写的代码(测试或函数)。确保完成时测试仍然通过。
这个循环周期应该非常短——只有几分钟的时间,这样就可以不断地编写测试,然后让它们工作。
只要遵循TDD工作流程,就能保证代码始终都有测试。这意味着你会一直处于考虑测试的状态。
务必实践一下TDD。但真这样做时,不要忘记是不是停下来看看大局。编写大量的代码实际上并不能让你离解决方案更近。
TDD:你需要知道该去何方
提示:既非自上而下,也不自下而上,基于端对端构建。
构建软件的唯一方法是增量式的,构建端到端功能的小块,一边工作一边了解问题。
不是由测试驱动,而是——从对传统上如何解决这类问题的基本理解开始,然后专注于改进算法。
测试对开发的驱动绝对能有帮助。但是,就像每次驱动汽车一样,除非心里有一个目的地,否则就可能会兜圈子。
单元测试
单元测试即对每个模块单独进行测试,以验证其行为。
针对契约测试
将单元测试看作是在针对契约测试,编写测试用例来确保指定的单元遵守了契约。
提示:为测试做设计。
开一扇测试窗口
在生产环境测试,可以使用跟踪消息的日志文件,或者通过热键组合或魔术URL来弹出一个诊断窗口。
提示:要对软件做测试,否则只能留给用户去做。
7.6 基于特性测试
提示:使用基于特性的测试来校验假设。
基于特性的测试是对单元测试的补充。基于特性的测试让你从不变式和契约的角度来考虑代码;你会思考什么不能改变,什么必须是真实的。这种额外的洞察力会对代码产生神奇的影响,可以消除边界情况,并突显使数据处于不一致状态的函数。
7.7 出门在外注意安全
当你觉得代码已经完成时,分析代码中那些可能出错的路径,并将其添加到测试套件中。你要考虑传入错误的参数、泄漏的资源或资源不存在等此类事情。
安全性的基本原则
- 将攻击面的面积最小化
- 最小特权原则
- 安全的默认值
- 敏感数据要加密
- 维护安全更新
将攻击面的面积最小化
-
代码复杂性滋生攻击载体
代码的复杂性会让攻击面更大,留下更多产生意料之外的副作用的机会。
-
输入数据是一种攻击载体
永远不要信任来自外部实体的数据。
-
未经身份认证的服务成为攻击载体
-
经过身份认证的服务成为攻击载体
淘汰不使用的、旧的或过时的用户和服务。
-
输出数据成为攻击载体
不要泄漏信息,并确保所通报的数据适合该用户的权限。对潜在的危险信息,要做截断或混淆。
-
调试信息成为攻击载体
提示:保持代码简洁,让攻击面最小。
最小特权原则
在最短的时间内使用最少的特权。不要自动获取类似root这样的最高级别权限,如果真的需要,获取后只做最少量的工作,然后迅速放弃权限,这样可以降低风险。
安全的默认值
在应用程序或网站用户的设置里,默认值应该是最安全的。
敏感数据要加密
不要将个人身份信息、财务数据、密码或其他凭据以纯文本的形式保存在数据库或其他外部文件中。如果数据被泄漏,加密提供了额外的安全级别。
不要把保密内容、API密钥、SSH密钥、加密密码或其他凭据,和源码一起提交到版本控制中。
维护安全更新
提示:尽早打上安全补丁。
我们应该鼓励长的随机的密码,因为它有更高程度的熵。
常识与密码学
当涉及加密时,永远不要自己做,不要自己做加密。
只应依赖可靠的东西:经过良好审查、彻底检查、维护良好、经常更新、最好是开源的库和框架。
或者可以采取务实的方法,启动第三方提供的鉴权服务,让其他人去操心。
7.8 事物命名
事物应该根据它们在代码中扮演的角色来命名。只要你有所创造,就需要停下来思考“我这一创造的动机是什么?”
当我们试图理解某事时,单词有一定的优先级。
尊重文化
尊重当前身处的语言文化。
一致性
每个团队每个项目都有自己的词汇表,使用项目术语表,列出对团队有特殊意义的术语。
更名更难
提示:好好取名;需要时更名。
挑战
- 当发现函数或方法的名称过于宽泛时,请尝试对其进行重命名,以表达真正的功能。
第8章 项目启动之前
8.1 需求之坑
提示:无人确切知道自己想要什么。
提示:程序员帮助人们理解他们想要什么。
需求是一个过程
提示:需求是从反馈循环中学到的。
务实的程序员将所有项目视为采集需求的练习。
代入客户的立场
有一个简单的方法可以让你深入客户的头脑,但这个方法并不常用:成为客户。
提示:和用户一起工作以便从用户角度思考。
需求与策略
提示:策略即元数据。
需求与现实
成功的工具会让用的人觉得称手,成功的需要也会将其考虑在内。
需求的文档化
最好的需求文档,或是也是唯一的需求文档,就是可以工作的代码。
需求文档是帮助指导实现过程的路标。需求文档不是为客户准备的。
客户之所以请程序员来,是因为程序员会对所有的细节和细微之处感兴趣,尽管客户的动机只是解决一个高阶的、有些模糊的问题。需求文档是为开发人员编写的,其中包含的信息和细微之处有时难以理解,并且常常让客户感到乏味。
**需求文档是为计划准备的。**通过保持需求的简短陈述,鼓励开发人员去澄清问题。
过渡规范化
生成需求文档的另一大危险是过于具体。好的需求是抽象的,最简单最能准确反应业务需求的语句是最好的。
需求不是架构;需求无关设计,也非用户界面;需求就是需要的东西。
最后一根稻草
许多项目失败,都可以归咎于不断扩大涉及范围——也称之为功能膨胀、特性泛滥或需求蠕变。
可以通过与客户的反馈来防止需求蔓延开来,反馈是双向的。
维护一张术语表
创建并维护一张术语表,在上面记录项目中所有特定术语和词汇的定义,项目的所有参与者都应该使用同一张术语表来确保一致性。
提示:使用项目术语表。
8.2 处理无法解决的难题
确定真正的约束条件,在这个约束条件下找到解开的方法。
自由度
认识到你所受到的约束和你所拥有的自由度,因为认识到这些就会找到答案。
提示:不要跳出框框思考——找到框框。
当面对一个棘手的问题时,把你面前所有可能的解决途径都列举出来,不要忽略任何东西,无论听起来多么无用或愚蠢,现在遍历列出的清单并逐条解释为什么不能选择这条路。你确定吗?你能证明吗?
确定最具限制的约束,然后让其余的约束适配它。
跳出自身的局限
当这个问题无法解决时,去做一些比如遛遛狗等能将注意力分散的事情。
注意力分散的人在解决复杂问题时比有意识的人做得更好。
还有另外一个方法,找个人或橡皮鸭去说明这个问题,并让他们问你一些问题,比如:
- 为什么你在解决这个问题?
- 解决这个问题有什么收益?
- 你遇到的问题是否与边界情况有关?你能消除这个边界情况吗?
- 有没有一个更简单的相关问题,是你能解决的?
幸运眷顾有准备的人
在日常工作中,将什么行得通什么行不通反馈给大脑,是供养大脑的最好方法。
挑战
- 仔细看看你今天卷入的难题。你能解决这个棘手的问题吗?一定要这样做吗?一定要做吗?
- 当你投入到当前的项目中,是否得到了一组约束条件?它们到现在都仍然适用吗?对它们的解释仍然有效吗?
8.3 携手共建
用户是你团队的一部分。
提问、讨论、做笔记,还要在真正编码的同一时刻提问和讨论。
结对编程
在结对编程中,一个开发人员操作键盘,另一个不操作。他们可以一起解决问题,还可以根据需要切换打字任务。
结对编程的好处:
- 不同的人有不同的解决问题的技巧和方法,对任何特定的问题有不同的关注点
- 充当打字员的开发者专注于语法和编码风格的底层实现,另一个人可以自由地在更高层次的范畴考虑问题。
- 同伴带来的压力有助于克服脆弱的瞬间,避免你做一些比如乱起变量名的事情,最终导致软件质量的提高。
群体编程
群体编程在解决困难问题的效果很好。群体可以很容易地将通常不被认为是开发团队一部分的人囊括进来,比如用户、项目赞助者等等。
该做什么
启动结对编程的小技巧:
- 打造代码,而非打造自我。这与谁最聪明无关;我们都有许多闪光的瞬间,也有糟糕的时刻。
- 从小规模做起。只需要4-5人的群体,或者开始时只组成几对,做一些短期活动。
- 批评要针对代码,而不针对个人。
- 倾听他人的观点并试着理解。观点不同不是错误。
- 频繁进行回顾,为下一次做好准备。
从一个简单的练习开始,而不是知道跳到最难的产品代码。
提示:不要一个人埋头钻进代码中。
8.4 敏捷的本质
敏捷是一个形容词,指你的风格,而不是指你这个人。
提示:敏捷不是一个名词;敏捷有关你如何做事。
敏捷宣言价值观:
- 个人和互动高于流程和工具。
- 工作的软件高于详尽的文档。
- 客户合作高于合同谈判。
- 响应变化高于遵循计划。
永远不可能有一个叫敏捷的工艺流程
这些价值观不会告诉你该做什么,当你决定要做点什么的时候,它们会告诉你要去追寻什么。
应该做什么
敏捷工作的秘诀:
- 弄清楚你在哪里。
- 朝想去的方向迈出有意义的最小一步。
- 评估在哪里终结,把弄坏的东西修好。
重复这些步骤,直到完成。
不持续对流程做实验的团队,不是敏捷团队。
用来驱动设计
为了使整个工作敏捷起来,需要实践优秀的设计(软件架构),因为优秀的设计使事情容易改变。
第9章 务实的项目
9.1 务实的团队
程序员有点像猫:聪明、意志坚强、固执已见、独立,并且经常引起网络崇拜。
务实的团队很小,充其量也就10-12人左右。成员很少进出。每个人都很了解彼此,相互信任,互相依赖。
提示:维持小而稳定的团队。
禁止破窗
如果团队不鼓励开发者在这些修复工作上花费时间,那么问题会进一步恶化。团队作为一个整体,不应该容忍破碎的窗户——那些没人去修的小问题。
团队必须对产品的质量负责。质量只能来自团队每个成员的独立贡献。质量是内在的,无法额外保证。
煮熟的青蛙
对于项目中的变化,鼓励每个人积极监控环境的变化。保持清醒,对项目范围扩大、时间缩短、额外特性、新的环境——任何在最初的理解中没有的东西,都要留心。对新的需要要保持度量。
团队不必对变化导致的失控心抗拒——只需要知道变化正在发生就可以。
为知识组合安排日程
团队的工作不应仅致力于开发新功能,还可能包括:
-
旧系统的维护
-
流程的反思与精炼
花时间观察周围,找出什么是有效的,什么是无效的,然后做出改变。
-
实验新技术
做一个原型来慎重考察候选技术。
-
学习和提升技能
提示:排上日程以待其成。
团队整体的对外交流
在外人看来,最糟糕的项目团队就是那些看起来闷闷不乐、沉默寡言的团队。他们的会议组织混乱,没有人愿意发言。电子邮件和项目文档一团糟:每一个都使用着不同的术语,没有哪两样东西看起来是相同的。
优秀的项目团队有独特的个性。人们期待与他们会面,因为知道他们准备得很充分,会让看到他们表现的每个人都心情愉悦。他们生成的文档是清晰的、准确和一致的。团队用同一个声音说话,甚至可能还不乏幽默感。
不要重复自己
在团队中良好的沟通是避免DRY问题的关键,良好的沟通指的是无摩擦力,即提问题、分享进展、问题、见解和学到的东西,以及时刻关注队友正在做什么——都很容易。
团队的曳光弹
精益人士觉得设立不同的角色和头衔(比如前后端分离)这种方法是一种浪费,所有这些不同的角色和活动,实际上代表了对同一问题的不同看法,人为地将其割裂开来可能会造成大量的麻烦。
提示:组织全功能的团队。
自动化
自动化是每个项目团队的基本组成部分。确保团队拥有构建工具的技能,以便可以构建和部署工具,用其来将项目开发和生产部署自动化。
团队是由个人组成,赋予每个成员能力,让他们以自己的方式发光发热,要提供完善的架构来支持他们,并确保项目交付的价值。
挑战
- 找找软件开发领域之外的成功团队,是什么让他们成功的?
- 下次开始一个项目前,试着说服周围的人给项目设计一个品牌。给你的组织一些时间来适应这个想法,然后进行一个快速调查,看看品牌在团队内部和外部产生的影响有何不同。
9.2 椰子派不上用场
环境很重要
目前的趋势是采用成功公司的政策和流程,比如Spotify、Netflix。Stripe等等。每家公司对软件开发和管理都有自己独特的见解。但是考虑一下环境:你是否处于相同的市场,具有相同的约束和机会、相似的专业知识和组织规模、相似的管理和相似的文化?用户基础和需求是否相近?
特定的神器,以及浮于表面的结构、策略、流程和方法是不够的。
提示:做能起作用的事,别赶时髦。
同一尺码无法适应所有人
在开发软件时,没有哪一个计划是可以照搬的。获取每种特定方法中最好的部分,并对其进行调整以供使用。没有适合所有情况的方法,而且当前的方法还远远不够完整,因此你不只需要关注某一个流行的方法。
真正的目的
提示:在用户需要时交付。
9.3 务实的入门套件
构建和发布过程、测试、项目文书工作,还有项目上的任何其他重复任务,都必须是自动的,并且在任何能力足够的机器上都是可重复的。
务实的入门套件:
- 版本控制
- 回归测试
- 完全自动化
版本控制驱动
提示:使用版本控制来驱动构建、测试和发布。
无情的持续测试
务实的程序员会主动去找出Bug,将来就不必忍受由别人找到我们的Bug所带来的耻辱。
单元测试抓捕小鱼(小Bug),集成测试抓捕食人鲨(大Bug)。
提示:尽早测试,经常测试,自动测试。
提示:直到所有的测试都已运行,编码才算完成。
构建可能包括几种主要的软件测试类型:单元测试、集成测试、确认和验证、以及性能测试。
单元测试
单元测试是所有其他测试形式的基础。
集成测试
集成测试是对单元测试的扩展,是测试整个子系统如何履行契约。
确认和验证
测试用户获取的东西是否是他们想要的。
性能测试
测试是否满足性能要求——包括预期的用户数、连接数或每秒事务数。
提示:使用破坏者检测你的测试。
测试要彻底
尝试在测试期间监视代码覆盖率的工具,并跟踪哪些代码执行或没有执行。不要期望100%的覆盖率。
提示:测试状态覆盖率,而非代码覆盖率。
绷紧渔网
提示:每个Bug只找一次。
一旦一个Bug被测试工程师发下,它应该就是被测试工程师发现的最后一次。要立即修改自动化测试,以便这个特定的Bug在往后每次都能被检查到。
完全自动化
提示:不要使用手动程序。
一切都要依赖于自动化。除非构建完全自动化,否则无法在匿名云服务器上构建项目。
9.4 取悦用户
用户真正要的不是代码,他们只是遇到某个业务问题,需要在目标和预算范围内解决。他们的信念是,通过与你的团队合作,能够做到这一点。
如何挖掘用户的期望,可以问一个简单的问题:这个项目在完成一个月(或是一年,不管多久)之后,你根据什么来判断自己已经取得成功?
实现期望:
- 确保团队中的每个人都清楚这些期望。
- 在做决定的时候,想想哪条路更接近这些期望。
- 根据期望严格分析用户需求。如果能证明有方法会使项目更接近目标,那么就不要害怕,大胆提出改变需求的建议。
- 随着项目的进展,继续考虑这些期望。
提示:取悦用户,而不要只是交付代码。
务实程序员的本质是“解决问题的人”,而不是“软件工程师”。
9.5 傲慢与偏见
务实的程序员不会逃避责任,相反,我们乐于接受挑战,并让自己的专长广为人知。
提示:在作品上签名。
不应该百般猜忌地捍卫自己的代码不被他人干涉;同理,你应该尊重别人的代码。恪守怒道,以及在开发者之间建立相互尊重的基础。
保持匿名会滋生粗心、错误、懒惰和糟糕的代码。
虽然代码的所有权必须有所归属,但不必被个人拥有。
人们应该在一段代码上看到你的名字,并对它是可靠的、编写良好的、经过测试的。文档化的充满期许。这是一件非常专业的工作,出自专业人士之手。一个务实的程序员。
跋
道德罗盘
对于我们交付的每一段代码,我们有义务问自己两个问题:
- 我已经保护好用户了吗?
- 我自己会用它吗?
提示:先勿伤害。
如果你参与了打道德行为的擦边球的项目,就和出资者一样负有责任。
提示:不要助纣为虐。
想象你的未来
去创造一个让所有人心向往之的宜居未来。对可以拥有的未来充满憧憬,才有动力去创造它。
提示:你要为自己的人生做主。精心营造,与人分享,为之喝彩。好好享受吧!