这是一本比较老的书,第一版应该是出在上个世纪90年代。但二十多年对于软件领域来说已经过了好几代,第一版书中所引用的工具和技术,放在今天,充其量不过略显古雅有趣,而更多的会给人带来困扰。于是这本书的作者后续又整理了第二版(大约是在2020年出版)。书中大约三分之一的主题是全新的,里面引用的技术和代码基本都修改了。
个人的感受,看这本书并不是为了学习前沿的技术,而是去学习实践和方法中的闪光点,思想的价值往往不太容易随着时间的流逝而贬值。另外,这本书是许多短小主题的合集。每一个主题都针对特定的话题而独立成章。阅读的时候不难发现大量的交叉引用,有点像字典,可以根据某个关键词查找。然后,可以以任意次序随意阅读这些主题。
书名有个关键词叫务实,书中是这么说务实主义的**:不应该拘泥于任何特定的技术,而应该拥有足够广泛的背景和经验基础,以便在特定的情况下选择合适的解决方案**。你的背景来自对计算机科学基本原理的理解,而你的经验来自广泛的实际项目。理论结合实践会让你变得强大。
第一章 务实的哲学
这一章几乎没怎么说技术或者方法论,反而通过很多个小故事,说一些生活上的哲理。
- 一个务实的程序员具备什么样的特质?
务实的程序员在面临问题时,在解决方案中透出的态度、风格及理念是不同的。他们总是越过问题的表面,试着将问题放在更宽泛的上下文中综合考虑,从大局着想。毕竟,若不去了解来龙去脉,结合实际如何谈起?又怎能做出明智的妥协和合理的决策?
- 人生的价值是什么?
书中甚至引用了李小龙的一句名言:我活着不是为了满足你的期望,正如你也不是因为我的期望而活着。
现代社会中,很多的人害怕变化,就缩在那里,期盼着事情会自己变好。他们眼睁睁地看着自己的技能过时,看着自己做着重复无意义的工作,看着自己的生活陷入泥泞之中。可是人生是你自己的,是你在拥有、经营和创造,自己不去争取更好的生活,抱怨别人又能改变什么呢?
- 软件的熵
热力学法则决定了宇宙中的熵会趋向最大化。而在一个项目中,即使有最合理的计划和最佳的人员,项目还是可能在生命周期中不断无序化,这是很自然的现象
- 不要放任破窗
对应了上面的软件的熵,既然软件会不断无序,那么就不要搁置“破窗”(糟糕的设计、错误的决定、低劣的代码)每发现一个就赶紧修一个。
- 筹备更为困难
项目开始时,你准备去征求意见,见到的往往是推脱和茫然的眼神,这很正常,大部分人不太愿意为不确定的未来付出很多成本,但是,一旦有成果产出,展示给人们看,想法就会很快转变,人们都觉得,加入一个推进中的成功项目更容易一些。
- 够好即可
这个词并不意味着草率或糟糕的代码。达到用户的需求仍然是基本的述求。但我们不需要额外增加功能,也不需要提供高出很多的性能,这不仅仅是节约项目时间,更是留给用户一个机会,让他们能亲自参与评判,用户的反馈常常能引领开发者做出更好的产品。
第二章 务实的方法
这一章说了很多的方法论,提示开发者要在开发中记住这些基础原则
- DRY(不要重复自己)
DRY的意思绝不仅仅是“不要复制粘贴源码”,**DRY针对的是你对知识和意图的复制。**它强调的是,不要重复表达一样的东西,比如文档,数据,API等等,这些重复和冗余处理起来可比代码处理麻烦多了
- 正交性
“正交性”是从几何学中借用来的术语。若两条直线相交后构成直角,它们就是正交的。在计算科学中,这个术语象征着独立性或解耦性。对于两个或多个事物,其中一个的改变不影响其他任何一个,则这些事物是正交的。正交的系统,有两个非常主要的优点:提高生产力及降低风险。在开发中我们常常会听到这些词,例如模块化、组件、分层等等,这些都是正交性的体现。
- 曳光弹
战场上,士兵们使用曳光弹来调整他们的瞄准:这是一种务实的方法,可以在真实条件下提供实时反馈,不断调整。开发代码也需要我们“命中目标”,程序员在开发中寻找重要的需求,寻找有疑问的地方,有重大风险的地方,从这些地方开始设计编码。当然不可能每次都击中目标(用户的述求),需要不断地调整瞄准,直到击中目标。这正是曳光弹的意义所在
- 原型
原型绝不仅仅是为了编码服务的,那就太局限了。设计一个原型可以是为了学习经验,可以是吸取教训,也可以是为了探索问题。不要简单把原型一模一样的开发成产品,同时也要提醒需求方或者项目管理,演示用的原型是有欺骗性的,潜在的问题不可能全部暴露,要有变化的准备。
- 估算
项目要开发多久?需要消耗多少资源人力?很多问题没有明确的答案,然而,只要你愿意估算,这些问题都是可以回答的。管理人员往往会想要一个简单可靠的数字,但这很难,进度是由团队、团队的生产力和环境综合决定的。一个好的建议是,不断迭代进度表,如果误差超过了50%,试着找到进度异常的原因。
第三章 基础工具
这一章介绍了一些辅助项目开发的工具,这一部分的内容就需要仁者见仁智者见智了。因为工具一定会随着公司,开发者,技术的发展变化而不断改变。
- 文本:这部分可以衍生到数据的存储,讨论了纯文本、二进制格式使用的场景。
- shell:程序员要学会使用终端,使用shell
- 编辑器:找到适合自己的编辑器,熟练使用它,会节约很多时间
- 版本控制:确保所有内容都在版本控制之下——文档、电话号码列表、供应商备忘录、脚本、整理的日志文件——所有的一切。(话虽如此,但感觉还是需要视情况量力而行)
- 调试:说了挺多查bug的方法,有不少借鉴意义(说实话,现在大部分人解决bug应该都蛮依赖百度,Google)
第四章 务实的偏执
偏执在生活中常常是一个贬义词,但是在开发中,有时候偏执不失为一个好性格。
- 防御式编程和契约式设计
应该没人能写出完美的代码,务实的程序员会为自己的错误建立防御机制。在麻烦发生之前就做好准备,不要把自己置于无法自拔的境地。(有一些观点是认为防御式编程就是浪费时间,这部分书中也有提到)
开始项目前,对要接受的东西要求严格一点,并且尽可能少地对回报做出承诺。如果你订的契约是可以接受任何东西,并且承诺要回报整个世界,那么你就完蛋了!
- 尽早崩溃
一个死掉的程序,通常比一个出了问题的程序,造成的损害要小得多。(避免将损坏的数据写入一些重要的库,或执行些奇怪的动作占用大量资源等等),这个也是防御式编程引起争论的一个重要的点,二者如何平衡需要看实际的需求和场景了。
- 断言式编程
无论何时,如果自己认为,这是不可能发生的时,添加代码来检查这一点。最简单的方法是使用断言。(最近一直写python,但自己不太习惯用断言,得注意养成一些习惯,毕竟断言对于调试,定位bug都很有用)
- 保持资源的平衡
一说到资源,程序员脑子里一定会啪啪闪过很多词汇:死锁、事务、网络连接、内存、文件、线程……如果有这个敏感度,自然是极好的。所有涉及到资源的东西,都要时刻警惕!
- 不要冲出前灯范围
车灯照亮的范围是有限的,同样,软件开发中,我们无法看到遥远的未来,离照射轴越远,就越黑暗。所以建议多采取经过深思熟虑的小步骤,同时检查反馈,并在推进前不断调整,如果闷头向前冲,很可能直奔悬崖。
第五章 宁弯不折
我刚开始看成了宁折不弯,实际上恰恰相反。时代的发展太快了,我们的代码很快便会过时,或是因市场变化而被抛在后面。“弯”就是要在面对不确定的世界时可以保持灵活性和适应性。
- 解耦
很多经验丰富的开发人员反而害怕修改代码,因为他们不确定会造成什么影响!任何时候,只要两段代码共享点什么东西,都可能发生耦合。书中介绍了得墨忒耳法则(LoD),不过这是80年代末提出的,现在未必能套用,大致意思就是按照一定的规范写函数,保持函数简洁和解耦
- 避免全局变量
这是书中的观点,修改全局变量的实现,可能会潜在地影响到系统中的所有代码。如果全局数据包括了外部资源,就有落入全局化陷阱的风险。(这个观点我没有特别理解)
- 交互
这里介绍了状态机,观察者模式,发布订阅模式,响应式编程等等,不做赘述了。有意思的是,书中说:React和Vue是当前的热门技术,但这在本书付梓之前,可能会过时。(然而目前仍然是最主流的前端技术)
- 变换式编程
书中引入了一个观点:所有程序其实都是对数据的一种变换——将输入转换成输出。变换式模型不再把数据看作是遍布整个系统的小数据池,而要把数据看作是一条浩浩荡荡的河流。数据不再和特定的函数组一起绑定在类定义中,而是需要不断传递的。
- 继承税
书中对面向对象编程也有一定的批判,理由是会有数据封装在对象中,这些对象会来回折腾,改变彼此的状态,引入了很多耦合。继承也并不理想,类之间的细微差别会逐层添加,最终可怕地爬满墙壁。
书中写了三种更好的替代方案: 接口与协议、委托、mixin和特征
- 配置
建议配置数据保持在应用程序外部,但不直接放在文件中,也不放在数据库里;而是储存在一个服务API之后,大致有这么几个优点:1、多个应用程序可以共享配置信息,通过身份认证和访问权限控制隔离。 2、 配置的变更可以在任何地方进行。 3、配置数据可以通过专有UI维护。 4、配置数据变得动态。
第六章 并发
并发和并行是计算机科学中重要的两个概念,当人们刚开始设计架构或编写程序时,倾向于将事情线性化,这符合绝大多数人的思考方式,但不是计算机的方式。合理的并发将极大减少程序运行时间。
- 找到并发的机会
找到那些耗时活动中,无须把时间花在代码上的活动,例如查询数据库、访问外部服务、等待用户输入——所有这些事情时常会让我们的程序卡住,直到操作完成。这些等待的间隙,都有机会去做一些更有意义事情,不要让CPU变得空闲。
- 共享状态是不正确的状态
并发过程中一定要注意共享的资源,避免出现死锁。
- 黑板
“黑板”是书中的比喻,侦探们在调查一起谋杀案时,使用黑板来协调工作和解决问题。每个侦探都会在黑板上写线索,侦探可能加入又退出,但黑板上的线索可以被所有侦探使用。这其实就是个消息中间件,生产消息,存储消息,消费消息。开发中也可以使用这样的“黑板”进行协调
第七章 当你编码时
“一旦项目到了编码阶段,就几乎只剩一些机械工作:只是把设计翻译成可运行的代码段而已。” 这种态度是软件项目失败的最重要的原因。这导致许多系统最终变得丑陋、结构糟糕、不可维护。
如今AI盛行,但就目前而言AI还是取代不了程序员的工作,究其本质,是因为程序员的编码工作不是简单的翻译,而是需要根据场景不断地思考,会倾听需求,会灵光一闪……
- 倾听你内心的蜥蜴
“蜥蜴”也是一种比喻,可以理解为直觉。意思就是有经验的程序员,会敏锐的察觉到代码中奇怪的地方,即使没有执行。
另外,当开发陷入僵局时,不妨远离键盘,停止对代码的思考,适当的休息后,就能抓到灵光一闪的契机。
- 避免巧合式编程
很多程序员都可能遇到过,输入几个参数,代码正常工作。所做的有限“测试”似乎能表明工作正常,但那只是一个巧合。一定不要在黑暗中编码,构建一个没有完全掌握的应用程序,或者使用一个并不理解的技术,就很可能会被巧合咬伤。
如果希望花费更少的时间来编写代码,就要在开发周期中尽可能早地捕获并修复错误,这样可以一开始就少犯错
- 算法速度
就是学习计算时间复杂度,但实际开发中,大部分都是拧螺丝,对算法要求不高,但在有条件的情况下,能提升下最好。
- 重构
重构的契机很多,比如代码耦合严重,维护困难,技术过时了,性能不好等等。
在重构中需要注意几点:1、不要试图让重构和添加功能同时进行。2、在开始重构之前,确保有良好的测试。这样,如果变更破坏了任何东西,都将很快得知。3.保持小步骤,并在每个步骤之后进行测试,就能避免冗长的调试。
- 测试
测试获得的主要好处发生在你考虑测试及编写测试的时候,而不是在运行测试的时候。测试所提供的反馈至关重要,可以反过来指导编码过程。
第八章 项目启动之前
《敏捷宣言》说道:“个体与互动高于流程与工具“,关键的问题在项目启动之前解决,这才能完成一个成功的项目。
- 需求之坑
客户很可能都不知道自己想要的到底是什么,这不离谱,反而正是我们程序员的用武之地。我们的工作是帮助人们了解他们想要什么。事实上,这很可能是我们最有价值的属性。
千万不要对需求照单全收,和客户一起去探索,去沟通。客户最开始的想法可能都是战术或者战略性的,我们则是发现其中的细节,并找寻边缘情况。
- 思维的框框
总会遇到一个困扰你很久的问题,这个问题甚至可能会很简单。生活中常常会说什么“换个角度思考”,“跳出框框再看一眼”,但程序开发不是这样的,有些框是真正存在的(限定了约束条件)。这就很难了。首先要检视这些框框是否真的必要,是否可以换条路走,然后再去思索与突破。当你觉得很累的时候,也不妨放空自己,分散下注意力,或许后面能灵光一闪
- 敏捷的本质
敏捷开发真正核心的一个价值观是:**响应变化高于遵循计划。**无论是生活还是软件开发,没有什么东西是真正一成不变的。我们能做到的就是不断地调整自己,拥抱变化,往目标迈进一小步。
第九章 务实的项目
最为核心的就是能够稳定可靠地交付软件!
- 务实的团队
务实的团队很小,充其量也就10-12人左右,过多的人员会导致交流成本直线上升。项目的质量来自团队每个成员的贡献,只要有一个人摆烂,这个团队的质量都会被限制。鼓励团队设置“质量官”,但质量的检查应当对事不对人。
一定要安排合理的计划和日程,只要有空闲时间就去做,往往意味着这件事永远不会去做。
优秀的团队会积极沟通和交流,能够对外发声,展现团队的魅力(这也能一定程度提高团队凝聚力)
- 务实的入门套件
分别是:版本控制、测试、自动化。前两项毋庸置疑,最后一项自动化,未必所有项目都能做到。比如仍然存在手动部署的情况,一旦引入了手动的步骤,那么就可能存在一扇很大的破窗了
- 取悦用户
交付代码不是最终目的,和客户建立起某种关系,积极地帮助客户解决问题,使客户满意才是“软件工程师”的内核(做解决问题的人)