目录

《这就是软件工程师》读书笔记

摘要
《这就是软件工程师》读书笔记。

1 行业地图

软件工程师是一群严谨的人,倾向于持续改善、追求极致…还有更极致的一些软件工程师,恨不得觉得100分都是用来突破的,在他们眼里,满分的位置是不断上调的。

这一点提醒了我,一定要追求极致,不能满足于能跑就行,如果时间允许的话。


一定要去做有价值的事情,就是去做那些更难一些的、别人不会或不想干的事情,来让自己不断精进。——陈皓

一线或者次一线城市,软件工程师更有市场,全球如此。


几乎所有人都会混淆行动与进展,混淆忙碌与多产。——《代码大全》

郄小虎说35岁是能力的坎不是年龄的坎,这一点有待商榷,他说工程师有四个阶段也比较模糊,他说新工种会不断出现,这一点我比较赞同。

陈皓说35岁要成为贡献者级别才不会被淘汰,这是仅次于创始人的级别,在我看来就是高管级别。

陈智峰说未来很多传统公司也有软件开发的需求,这一点没错,会不会自己做就是另一回事了。


2 新手上路

2.1 入行前

选择平台:去面向未来、技术驱动的公司


大部分刚毕业的新人在选择去哪家公司时,会考量很多因素:薪资待遇、奖金、假期••••••还有,是去成熟大厂,还是创业公司?


在我看来,这些都不是新人选择去什么平台的决定性因素。选择平台时,新人应该判断两件事:这家公司是否面向未来,是否受技术驱动。


第一,这家公司做的事情,能不能适应未来的发展。因为这一行的发展速度太快了,说是速生速死都不为过。就算一家公司给你开出了极高的薪资,但它过不了多久就被淘汰了,你加人这样的平台又有什么意义呢?


我当年从普林斯顿大学毕业时,摆在面前的选择有两个。一个是去传统的研究所做研究员,比如微软、英特尔、IBM、贝尔实验室,它们都有全世界最牛的研究所。当时面试我的都是业界非常有声望的人物,当年的大学课本有很多都是他们编写的,比如 UNIX 系统的贡献者之一布菜恩 •柯林汉 ( Brian Kernighan) 等。


另一个是我师兄辍学加入的一家名不见经传的小创业公司。这家公司里全都足年轻人,正在试图把全世界的网站都扒下来,把每个人想看到的信息送到大家面前。


我当时做了一个比较:前者稳定、光鲜,我还能跟那么多业界传奇共同工作,但是,他们显得没什么激情、暮气沉沉;后者虽然默默无闻,可每一个年轻人的眼里都透着光。而且,他们要做的是一个从来没有人实现过的、有着巨大需求、开创新未来的事业。


这样一想,我毫不犹豫地选择了后者。后面的故事很多人可能都知道,这家公司几年之后成长为全球最大的搜索引擎公司,从名不见经传变成了与徽软等比肩的超级航母——谷歌。


**计算机与互联网的发展都太快,如果要选择,一定优先选走在未来航道上的那些快速发展的公司。因为高速成长的公司正需要人才,它需要解决的向题也是新的,你会跟着公司一起去解决这些问题,你的能力会越来越强。**Faccbook首磨运营官桑德伯格 (Shergl Sandberg)曾说,当你遇到火箭般上升的公司,不要管舱位,先坐上去。


第二,你要去的这家公司是不是一家技术驱动、以技术文化为主导的公司。为什么这么说?因为职业生涯的初期,软件工程师最需要的是先充电,把自己的基础打好,培养起扎实的编程能力和良好的职业素养。你只有在最开始充好电,才能在这一行走远。如果一家公司不崇尚技术,对软件工程 师的成长完全不重视,你进去干几年,像钉子一样只会干很窄的一个领域的事情,沉淀不下什么东西,后期的发展就会非常受限。


当年谷歌吸引我的,除了它的业务,更重要的是,它是一家技术驱动的公司,软件工程师在公司里是最重要的人有最大的话语权。公司相信软件工程师,并通过创造自由宽松的环境、高效协同的文化以及相应的培养机制助力软件工程师成长(谷歌源源不断地培养出了大量优秀的技术人才)。如果你也遇到对技术和软件工程师非常重视的公司,就要想办法先进去。

郄小虎选公司的眼光有点东西。此文我全文摘录,值得细读,尤其是我加粗的部分。


陈皓说找到自己合适的路线,最不济可以勤奋。这一点其实很重要,但他没细说,可能他比较牛。这其实是关于自身竞争力的问题,如何在市场中保持自己的竞争力,也和35岁问题息息相关。在现在的饱和市场中找到自己的一席之地,是一个需要好好思考的问题,现在的我没有答案。


2.2 编码

软件开发过程,书里比较笼统,之后会写文细说,毕竟前东家这一块基本是做到了全国最差之一,值得反思一下。


陈智峰编码要遵循规范,一点没错,之后会写文详细分析阿里和谷歌的Java规范。


陈皓说好的代码无止境,理论上是对的,但实际上我认为其实是有的,像JDK部分源码应该没有需要优化的空间了,除非有什么新技术思路出现。

他把代码分成三个等级:可读——可扩展——可重用。这很不容易,要对系统和业务有全面的认知。他还提到解耦的几种方式:设计模式、函数式编程、DSL、状态机、插件、依赖倒置和反转控制,之后可以研究一下。


第二是写出干净的接口。什么是干净的接口?假设你写了一段代码,别人联调的时候不需要问你就知道接口该怎么用;一旦遇到性能问题,或者要在下个版本添加功能,别人很自然地就知道该在哪里加这个功能——这样的接口就是干净的接口。


我入行最初两年,桑杰 •格玛瓦特(Sanjay Chemawat)是我的团队领导,他在接口设计上有一套习惯,我觉得很受用。具体来说,他在评审代码时,会要求我们给接口加注释,在注释里给出一些基本的使用例子,告诉别人这个接口怎么用,比如怎么用这个接口写出一个小程序。而在一些高并发多线程的程序里,他会要求我们注释清楚,某个接口是否允许高并发,它的线程控制是怎么样的,内存管理是什么样的,等等。这对读代码以及使用代码的人就非常友好


那作为写代码的人,我们就要考虑得尽量全面一些,最好是不仅考虑到当下可能会遇到哪些问题,还能预想到明年会有什么需求,并考志到当这个需求来的时候,可以在哪个地方改,给后来的人一个大致的方向,为将来的可扩展性预留空间。


总之,整洁代码一定是方便使用者的。如果使用者想到的问题你预先都想到了,并且在注释或文档里全部讲清楚了,这样的代码不会差。

陈智峰说整洁代码不是写出来的,而是读出来的,很有道理。其实写代码有些地方像写作文,要多读,多模仿好的代码,自己埋头写很难进步。


这是真正的软件工程师与“代码操作员”最大的不一样:**真正的软件工程师是用抽象的模型解决更多的问题,而代码操作员从来都是用 case-by-case(逐个解决)的方式,通过使蛮力来完成工作。**DRY(Don’t Repeat Yourself)原则可以强化你的抽象能力。

陈皓说四个编程原则:避免重复,单一指责,高内聚、低耦合,开闭原则。陈智峰说不需要拘泥于原则,不一定要遵守,解决问题是最重要的。


2.3 测试

测试这一块,之后会也值得写文细说,毕竟前东家这一块基本也是做到了全省最差之一,值得反思一下。


陈皓说了几种测试:单元测试、功能测试(整合测试)、集成测试(结合外部环境测试)、非功能测试(包括但不限于性能测试、安全测试、稳定性测试、健壮性测试、破坏性测试、可用性测试、灵活性测试等)、回归测试(以前的BUG再测一遍)。


测试接口定义的程序语意,而不是当前实现的具体行为。


这一点涉及接口和实现的分腐,简单来说,接口注重的是“what”,是抽象,而实现注重的“how”,是细节。接口定义的程序语意是说,这个模块会做什么事情,而实现的具体行为是说,具体怎么实现这件事情。


举个例子,假设我到网上买东西,我希望挑好一件商品后,付钱,最后货送到我家。我买商品,商品送到我家,我只关心这个过程(这个过程相当于 “接口定义的程序语意”),而不关心商家到底是用邮政快递送到我家,还是用顺丰快递送到我家(具体运送的方式相当于 “当前实现的具体行为”).。


也就是说,在程序世界里,接口只定义从输人到输出这个过程,但这个过程其实可以有很多种实现方式,软件工程师写测试用例的时候要测输人和输出的过程,尽量不要检测中间步骤的效果。因为输人输出是稳定的,但中间步骤可以很灵活,我们要重点测试稳定不变的部分。

陈智峰说测试要自己做,确实,出现低级BUG比较丢脸;多想边界条件,这个很重要,可以提升健壮性;测试接口定义的程序语义而不是具体行为,这一点很有意思,整段摘录了;对重要模块做到编写时完成基本性能测试,没毛病;对交付后的BUG进行记录,通过回归测试自动化测试。


2.4 改Bug

小黄鸭调试法:如果你已经知道了某段代码大概率有问题,就可以拿一个小黄鸭或者小熊放在桌上,把它当听众,然后把这段代码对着它一行一行地解释,甚至为什么这个地方用数组都要讲得很清楚。这个过程可以帮 你理顺代码逻辑,相当于让你用一种自言自语的方式,自发地发现问题。

陈智峰提了几种改Bug的方式:模拟场景、二分法、调试工具、极限测试、小黄鸭调试法(最后这个比较有意思)。


  1. 看整体:试着不从主线出发,而是检查 Bug 是否会费响其他支线。通过回顾所有的审查和测试等工作,看整个系统的合并及最终运行情况。最佳的方式是补充所有功能点的 测试案例,甚至是单元测试。
  2. 改细节:一步一步地重构。比如之前是因为复杂的嵌套导致某个Bug 反复出现,这个阶段你就不是只改Bug 本来出现的地方,还可以优化结构,让代码更易于理解,更安全。
  3. review 之前的 review:在提交之前,找别人帮你 review一下整个修复过程,看看方案是否完善,有没有更好的建议。

最后,当所有问题解决之后,一定要梳理下从最初找 Bug到最后改 Bug 的整个过程。《程序员修炼之道:通向务实的最高境界》这本书也指出,“如果修复这个 Bug 花了很长时间,向问自己为什么”。你需要思考的是,怎么做才能吸取经验教训,将来在类似的问题上不再裁跟头,以及采用的方法、使用的工具是否还有可以改进的地方。

他还提了关于重构的几个方法,复盘修复Bug的过程还是比较重要的。


2.5 成长论

陈智峰说要做任务分解,很有道理,主要在于两点:将一个大的任务分解,每个子任务要解决的问题就变少了,而且提交后能及时发现问题,不至于大返工;任务分解有利于理清解决问题的思路。


陈智峰和郄小虎都说要多读优秀代码,理由和我之前提到的相同。


陈皓说要多读文档和书,因为代码只能告诉你细节,但是没有why,而文档和书会告诉你why,也就是代码为什么这么写,非常有道理,醍醐灌顶!他还说要学习牛人的方法,而不是抄他们的答案,是这么回事。


郄小虎说和优秀的人一起工作提升会非常快,这是废话,问题是一起工作的人不一定就优秀,优秀了也不一定能帮到你,不过现在可以在网上找到很多优秀的人。


陈智峰说大办学习非常有效,可以互为磨刀石(互相review),互为回音壁(反馈梳理思路)。有肯定最好,不过我没有。


3 进阶通道

3.1 设计程序

陈皓说要避免X-Y问题,其实就是明确需求,很多人自己也搞不清楚自己的需求到底是什么,就要问清楚。

他还说要明确模糊问题,及明确问题的边界条件,关注不可预期案例(也就是不符合设计期望的特殊情况)。


郄小虎说设计程序像写文章的谋篇布局,不完全对,因为最初的需求会经常变化,甚至全部推翻(前东家)。他说要围绕核心主张,有时候核心主张并不存在。只有最初的需求是确定的,合理的,才能像他说的一样在最初就规划好。但是很难,毕竟现在变化很快,所以可扩展性、可维护性就尤为重要。


对于软件抽象来说,最常见的方法有两个:过程抽象和数据抽象。


所滑过程抽象,就是把要解决的向题分解为一个个小的子问题,然后用一个个独立的代码模块(如函数、类、AP1等)来完成,事把这些代码模块组织起来构建成一个复杂的系统。在组织的过程中,消除相似的模块,消除模块间的依赖。尽可能让每个校块都能蛋用到其他业务场景下。这样就光成了抽象。


而所谓数据抽象,就是将复杂数据的使用和它的构造分离开来,数据结构用于定义数据的构造,数据接口用于定义数据的使用。通过隐藏数据对象的内部特征,定义数据的外部使用,大大降低系统的复杂度。


举个例子,对于电商网站上的产品属性,软件工程师可以怎么抽象呢?你可能会说,可以抽象成品类、尺寸等信息,但这种方法只适用于相对静态不变的产品。实际上很多产品经常发生变化,比如以前的手机并没有内存,突然之间智能手机出来了,顺带着多加了一些属性;再比如以前的沙发是 沙发,床是床,突然之间出了沙发床,它既是沙发也是床,有了床和沙发的共同属性。


要想在产品信息不断变化的情况下,对产品进行稳定的管理,就器婴做一-个更高维度的抽象。具休抽象成什么样子呢?比方说我可以抽象出一个“属性池”,全世界所有的商品属性都在里面,然后我随便圈一圈,圈到的一组属性就代表某种商品,这样一来,这件商品就变成了一组属性的集合;同样的道理,某个品类也就是一组属性的集合。


这就是数据上的抽象,非常灵活,也非常容易理解,而且可以适应未来那些还不知道会变成什么样的商品。我们也可以看到,这种抽象用到了数学的语言和定义——商品就是一组属性的集合。一旦数据被抽象到了这个地步,就可以非常容易地定义出各种数学规则一—当一个属性集合是另一个 属性集合的子集时,那么它们就是父子类目。于是我们的程序和数据存储也会变得非常容易。如果你是新人,看到这里也许依然会觉得上面的内容有些抽象,没关系,很多东西必须有足够多的经历才会有感觉,多实践、多动手,相信你会越来越能理解。


如果你想对抽象有更多了解,推荐你看一本书:《计算机程序的构造和解释》。它对你做程序设计会有非常大的帮助。

数据抽象这一块非常高级,值得思考。


鲁鹏俊说原型设计,不要从头开始做,而是先做最难的部分,非常有道理。先做最难的,可以提早发现问题,节约开发时间,如果完成了最难的部分,剩下的其实只是体力活。


然而很多软件工程师总是过度关注实现细节,所以他们往往局部代码写得不错,整体设计却相对较差。这点需要在进阶阶段多多注意


那怎么才能做出好的接口设计呢?我的建议是前后多想几步。举个例子。如果你的项目在第一步,你就要考虑以后可能会做第二步或者第三步,当前的接口设计能不能满足后续的开发要求;而当你做第二步或第三步的时候,假设有个方案A,你一方面要考虑,方案A能不能跟已有接口匹配,会不会对已有接口产生不好的整响,,另一方面还要考虑,方案A会不会在第四步、第五步的时候导致什么向题。无论是往前还是往后,你想得越远,接口的稳定性就越好


在每个选择的节点,你可能会面临两种场景:

  1. 当前方案過到了问题,这个问题没办法绕过去——那就要退回到原本的原型设计,确认其他解快方案是否可行;

  2. 现在还不能确认当前方案对其他步骤的影响,但是你根据整体的设计判断,将来不管是第四步还是第五步,大概率都有应对的解决方案那就执行你的方案。


总的来说,当你做原型设计时,一定要关注接口设计;当你做接口设计时,要有更多的考量,想一想每个阶段可能会调用哪些接口、每个接口需要哪些字段、怎样定义数据,等等。只有把这些问题想清楚,才能避纪不确定因素对项目整体的影响。

陈智峰说接口定义要多想,一点没错,前提是不赶工的话,想的角度其实就是扩展性和稳定性。


架构设计这个词给人的感觉似乎很高深,很多人不知道从哪里入手,其实只要掌握一个思想,你的思路就能清晰起来,那就是“分而治之”。


架构设计是什么?简单来说就是把需求进行抽象和分解,作为设计架构的人,首先你要知道怎样把同类型的内容抽象出来,还得知道实现目标要分成哪些步骤,以及怎么从大的步骤里切出小模块(设计模式)。


举个简单的例子。假设我们的需求是从海量视频里选出20个短视频来显示,那么你就得好好思考一下:首先,视频那么多,具体选出哪20个短视频?这里有一个很重要的内容叫作“扩召回”,它有很多方法来选择,可能根据用户的兴趣(method1),或者根据用户点击频次(method2),或者 用协同过滤的方法(method3)………抽象就是把每一个方法实现到一个类里面去,再把所有这些类抽象成一个接口,比如GetMore。


选出视频后,你需要一个推荐模块,负责给出20个视频的ID。


收到每一个ID之后,对应的视频内容从哪里来?这时候你需要一个存储模块,存放所有的视频数据。


拿到视频内容后,怎么显示给用户?这时候你需要一个显示模块,直接面向所有用户。


这样一来,推荐、存储、显示三个大的模块就分出来了。再往下你还可以细分出很多子模块,比如把显示模块分成播放、快进、点费等子模块,从大到小一层层分下去,这就是分而治之。


划分模块时需要遵循 “高内聚,低耦合” 原则一—每个模块高度内聚,模块和模块之间要解耦。内聚是说相似的东西都要放在一块,解耦是说两个很不一样的东西要尽量分开。


做架构设计之所以要分而治之,并采用“高内聚,低耦合”原则,主要有两方面的好处,第一,有利于捋清思路,让设计变得清晰;第二,有利于团队分工,让每个人做自己擅长的事情,各自负责不同的模块,不用相互扯皮。 当你做好层层分解后,会很清楚每个部分是做什么的,别人看了也不会晕头转向。


做架构分析很考验一个软件工程师的功力。现实中有些软件工程师连流程图都不会画,就是因为根本没想明白自己想做什么,


上面只是举了一个简单的例子,如果你对架构设计感兴趣,可以去读一本很经典的书《设计模式:可复用面向对象软件的基础》。

鲁鹏俊这个文章说的很好了,全文摘录在此。我想架构设计的原理应该都是相通的,前东家的小项目也是这么个思路。


陈智峰说架构设计要考虑异常情况和极限情况,和模块设计、接口设计的思路都是相通的,稳定性和健壮性。


技术团队经常会接到一些以 前没做过的需求,不太确定实现细节,这时候就需要做技术调研,看看同样或类似的需求在业内有没有被实现过,分析不同方案的优缺点,得出结论,做出决策。


如果你去网上查“如何做好技术调研”,会得到很多实用的建议,比如要充分理解需求、尽可能搜集资料、合理安排时间,等等。这些建议都很好,也比较容易理解,这里不做 赘述。我自己做技术调研总结了两个心得,分享给你。


第一,调研做得好不好,和阅读代码的能力高度相关。同样是做调研,有的人做得又快又好,有的人很长时间都理不出头绪,其中的差距在于阅读代码的能力。代码读得越快,意味着你的搜素能力越强,越能快速定位自己想要的东西。一般我们做调研都是带着问题去的,面对别人写好的代码方 案,如果你读代码的能力强,读几段就能知道大概是什么意思,以及哪个地方跟你的问题相关,然后直按跳到相关地方,不用一行一行地去找,这就大大提升了调研效率。


第二,分折优缺点,结合场景才有效。做技术调研会强及分析不同方案的优缺点,这时候需要注意,分析优缺点不能泛泛而谈,而是要结合实际场景。为什么要强调场景?因为优缺点只有在场景下才成立,如果缺少场景,那么只有特点,没有优缺点。比如一辆车很贵,配置也不高,那么“贵 就是它的缺点吗?不一定,得看场景。如果是土豪为了炫富,那么贵就是优点;如果是普通人以实用为目的,那么贵就是缺点了。回到技术调研上来,我们的实际场景就是公司的现状,这一点很容易被忽略。


这里顺便提一下,网读代码的能力是需要慢慢培养的,我一开始也不擅长读代码,后来研究生毕业的时候,我做了一个中文分词相关的毕业设计,当时用了中科院的一个开源系统。为了弄清楚中文分词是怎么做的,我从头到尾把它的每个代码读了一遍,并且边读边记——这个类是干什么的,那个功能有什么用,一一记下来。我先是一块一块地读,最后把笔记拼到-个大图里。然后一下子就有了整体感,理解了中文分词原来是这么做的。读代码是一个从小到大的过程,需要逐步积累,可一旦你有意识地培养这个能力,以后能越读越快。

鲁鹏俊的这个文章也很好,全文摘录在此,结合场景的部分很有道理。读代码和读英文类似,多读才能快。


3.2 项目管理

第一种是瀑布式开发模式。这是一种传统的软件开发模式,简单来说就是一层一层地开发。先分析需求,产生需求文档;再做概要设计,技术选型等;接着做详细设计,事无巨细地梳理流程和细节;最后编码、测试、上线。但由于用瀑布式开发要考虑得非常全面,相当于一群人要在一块巨石上雕出一个巨大无比的雕像,所以一个项目的能得五六个月,甚至一年才做完,它的缺点就是慢。


第二种是敏捷开发模式。它的意思是我在做雕像前,先由一个高手把框架开发出;来,然后把后续的任务拆解成一个个小模块,拆得越碎越好,接着让每个团队(甚至每个人)负责其中一块,大家根据协议并行开发,最后拼在一起。这样的话一个项目可能只需要一两周就能上线。敏捷开发的特点是,我不需要一直做到很完整的程度再上线,而是做一点发一点,小步快跑,快速迭代。它的本质是化繁为简。


但值得一提的是,敏捷开发虽然高效,搞不好也会变得一团糟。有的公司滥用敏捷,用这个方式为自己的不思考找借口,没想清楚就直接干。他们总是会说,“这个问题我们在下个版本选代时说”,“先上线再说”⋯⋯他们以为这种 “后面再说〞的方式就是所谓的“迭代〞,同时不断地回避关键问题,最终问题绕不过去,只能推倒重来。


第三种是班车模式。意思是我的发布每周一次(比如每周二),如果你能赶得上就跟着一起发,如果赶不上就等下一班。这种模式看上去像是瀑布和敏捷的一种折衷方式。为了 控制变更的需求,开发不要太快,也不要太慢,找一定的节奏来。


第四种是分布式微服务开发模式。也就是把代码库、数据库全部分开,每个服务都由一个全功能的小团队(前端、后端、开发、测试、运维、产品)来负责,这样就可以把一个大部门拆分成多个小分队,让代码更容易维护和上线。这种开发模式的好处是,所有团队之间没有工程上的依赖,大家耦合在一个标准统一的开发模式和框架上,能非常方便和高效地协作。这就是所谓的 DevOps 开发模式。

不同开发模式的介绍,基本全文摘录了。其实还漏了一种,就是前东家的死妈式开发模式,一个脑图直接干,后期大返工。


鲁鹏俊解释了一下火车头开发模式,其实就是保持一个频率,一边提需求,一边实现,一边发布。这个需求来不及就先关掉,下周再发,不会影响整体的进度。


他还说了用A / B Test去验证新增需求的好坏,类似于小规模预发布。


等到所有开发工作完成,还不能立即上线。上线前有两件事必须做完,一件是监控打磨,另一件是压力测试。


首先是监控打磨。在我看来,如果一个架构师不怎么建监控,那这个架构师肯定不怎么样,这是绝对的。因为监控会直接反映系统问题,帮你快速定位 Bug,是一个非常有效的工具。我曾经用建监控的方式发现了某个系统的上千个Bug。 具体怎么做呢?


先建一个测试环境,然后把整个系统分成若干部分,接下来在每个部分里建立指标。以音视频数据的传播为例,数据从A 点传到B点,大致分了个过程:(1)从主播端传到接人服务器的过程;(2)从接人服务器传到服务器另一端,也就是核心网的过程;(3)从核心网传到观众端的过程。假设你最开始就以这三个过程的延时时间为指标,那么当你跑数据的时候,监控会把延时记录下来。如果程序跑着跑着,你突然发现有一个peak(峰值)——正常延时 500 毫秒,程序突然产生了一个1分钟的延时,你就要搞清楚这是为什么。


为了找到答染。你需要打点,比如一个程序里有X、Y两点,从X点到Y点用了1分钟,但你不知道这1分钟是在哪个地方用的。那么你可以在这个程序里打10个点,弄清楚每两个点之间用了多长时间,然后就能定位哪个地方可能有问题。


监控实际上是一个闭环。针对你的设计会有一个监控程序,有很多的测试用例,也就是我们常说的test case,进入到测试环境时,你就不断地在上面加各种各样的test case。如图3-4所示:

/images/This_is_Software_Engineer/IMG_1803.jpg
图3-4

设计在测试环境跑起来之后,你的监控上会有各种各样的指标,发现问题时就会自动报 Bug,报出的 Bug 就进入 Bug系统。工程师可以修改 Bug,然后把修完的代码再次提交到测试环境里去,让它在里面不停地迭代,这么一来很快就能把 Bug 修好,你的监控也会很快地建立起来了。


有了监控之后还需要做压力测试,比如现在你的系统流量是100qps,如果流量变成 1000 qps,这个系统还能不能扛得住?做压力测试一般可以上 10倍的压力。如果经受住压力测试,说明这个系统没什么问题,才能正式发布。

鲁鹏俊说了一下监控与压测,全文摘录在此。


3.3 团队合作

在几乎所有互联网公司,业务和技术经常处于撕扯状态,业务觉得你的技术都是为我做的,结果做出 来哪儿都不好用;技术觉得业务啥都不懂,提的需求简直匪夷所思。那作为软件工程师,你该怎么处理这一困境呢?是用更高的声量在会议上吵赢吗?不对。你要知道怎么去 “规训”业务。


首先,你要告诉业务,不要把技术仅仅当作需求解决方。业务如果只是把技术当作需求解决方,得到的就是被动的技术团队。而如果真正把技术当作解决问题的参与方,技术的主观能动性就能被调动起来。


其次,你要告诉业务,不要直接将需求丢给技术,而是要告诉技术真正想解决的核心问题是什么


为什么要特別强调这一点呢?因为有时候业务提的需求不是真正的需求,他只是给了技术团队自己认为的解决方案,要求技术 去实现。但是业务真正想解决的核心问题,可能并不是用这个方案能解决的,或者这个方案不是最好的。如果业务能把自己想解决的问题告诉技木,那么技术就会一起来想办法,还可能会想到更好的方法。


我在谷歌广告团队时,负费提升从搜索到广告转化的成功率。成功率会通过一个叫 RPM 的指标反馈出来。公司的业务团队发现中国的 RPM 值一直偏低,就跟技术团队沟通。但是他们没有直接说要提升 RPM 值,而是说“你们把广告字体变大一些,广告背景色调得更加醒目一些”。这时我们就问了,这么做想解决的核心问题是什么?沟通之后,才知道是要提升广告的 RPM 值。


于是我们开始针对这个需求作调研。调研发现中国的用户有一个习惯,一打开页面,上面的部分根本不看,直接看下面。但是谷歇的广告展示是在顶部的,所以导致中国的RPM 值就比较低。后来,技术团队将展示的位置调到最下面,很快就把 RPM 值提起来了。


你看,业务提需求时,有时候真正想解決的核心问题并没有给到技术团队。如果我们直接按照业务提的,把广告宇体改大,把广告背最色调充,可能反而会让用户体验更不好,导致 RPM 更低。但如果和业务沟通了核心问题,技术就会一起来想办法,我们根据关键问题选出最优的解决方式,其实只是做一个简单的小调整,就把这个问题很好地解决了。对技术来说,这是一个更加高效的解决方案;对业务来说,也解决了真正的问题——这样双方都能比较愉快地把事情推进下去。


最后,你要告诉业务,今天我们面临的所有问题都不是单单纯的技术问题,大家一起努力,才能从根本上解决问题。业务提需求前应该先去思考相关问题的产生来源,下次会不会再出现,多给出一些可供技术人员参考的信息,明确哪些需要技术解决,哪些需要业务解决总之,技术只有知道如何和业务沟通,才能把需求实现好。

郄小虎关于与业务沟通的文章,要会调教业务,很值得读一读,尤其是第二点,不要把需求直接给到技术,全文摘录在此。

与技术团队和业务团队之间的沟通障碍相比,技术团队内部的沟通,大家肯定认为会非常顺畅吧,毕竟技术人员的思维方式、话语体系都是一样的。但其实,并没有那么和谐美好。


在技术团队里,我们通常把离业务近的团队叫作前台团队,把离业务远的团队叫作中后台团队。技术团队的沟通之所以出现问题,用一句话概括就是,离业务近的同学觉得离业务远的同学做的东西都没用,离业务远的同学觉得离业务近的同学做的东西太短视。


这其实是一个长期目标和短期目标平衡的问题。


中后台团队一般都希望把系统尽可能做大做深。而前台团以的目标主要是怎么尽快给业务交付功能。目标不一致,两个团队的诉求不一样,就很容易发生矛盾。这时候,就需要大家互相理解,理解对方的诉求是什么、真正的痛点在哪里:从后向前,中后台要有意识,主动去理解前合团以的需求;从前往后,前台团队也不能仅仅因为业务的压力,就用短期目标来要求中后台团队做不合理的事情。否则中后合团队的动作会变形,最后造成的影响就是不停地打补丁,代码的可维护性、可扩展性越来越差,没有技术的沉淀,人员也不会有成长,最终交付业务功能的速度越来越慢。


我经常说,技术是第一生产力,有放大器的作用,能够把结果规模化、高效化地放大。这就像数字里0和1的关系一样,1后面的0越多,这个数字越大,但前提是前面要有个1才可以。具体到技术团队,前台团队是那个 1,中后台团队就是后面的0。因此,中后台团队要围绕着 1去建设,去创造价值,去解决前台的需求和痛点,甚至在前台团队还没有看到之前就预见出一些可能发生的问题,从而给前台团队提供有价值的服务,这样双方都能达到一个比较好的平衡。

郄小虎关于技术团队之间的沟通,全文摘录。


3.4 学习进阶

陈皓说要学习基础技术,很对,他说需要4-5年,确实。他说要系统的学,形成知识树,也很对,我目前的学习就是只有点没有线,但是也要先有点,才能有线。想变成面可能还需要场景和业务的实际运用。他说要探索知识缘由,也就是了解知识的来龙去脉和前世今生,其实就是为什么的问题,为什么要这么做,不那么做,也是非常重要的,但是会比较花时间。然后他说要掌握方法套路而不是答案,尤其是高级方法,没错。他最后说主动学习可以提高学习效果,不赘述了。


4 高手修养

4.1 分岔路的选择

陈皓对公司的分类还是比较准确的,比我分的好。前东家就是典型的小作坊公司。


4.2 业务上的精进

关于如何拥有前瞻能力,陈皓老师给出了几点建议:


第一,你一定要有知识的广度,需要去读论文,读业内各大公司的资料;还要去各个公司做广泛的交流,保证有足够多的不同的信息进入你的视野。


第二,多做跨行业的交流,跳出自己的圈子,跟其他行业的人交流,特别是投资人、创业者等见多识广的人群。

郄小虎说高级阶段需要前瞻能力,要知道为什么系统今天是这个样子,以及未来他会朝着什么样的方向区演进。

要想做好预测,建议你多总结行业内的历史,时间不用太长,关注过去 10年、20年发生的事就可以。只有你知道历史的轨迹,才更容易知道未来在哪里。

他还说要有取舍能力,就是最终选择哪种方案。明确目标是方案决策的唯一标准。同时,还要学会预测,因为选择了方案之后的结果是不确定的。


在技术难题的问题上,很生人说得過到一个解次一个就行。但在我看来,技术难题有时候要自己去找,这对自己能力的提升有很大帮助。


我现在做的是偏研究型的工作,经常解决一些技术上的难题,比如分布式运行系统的设计,Zanzibar 系统中的一致性协议设计等。这些项目大多不是别人分配给我的,而是我自 己主动找的。


**找项目的时候我会考虑两点:第一,整个行业或公司发展的方向是什么,找对大方向;第二,圈定那些跟我目前的工作相关,而我又不太懂,需要继续学习的领域。**如果这个领域中有很多厉害的人,他们都对这个方向感兴趣,像图像识别、语音识别或者机器翻译等,并且早期的一些研究结果 让我感觉这是一个新领域,会解决很多过去解决不了的问题,那么这就是值得花时间去研究的领域。


之后我会约相关领域的牛人聊聊,了解他们在工作中还有哪些问题没被解决。只要这个领域存在没解决的问题,就一定有技术难题。很多难题开始的时候都是很复杂的,你要抓住复杂问题当中的核心问题,把一些次要问题放在一边然后集中精力攻克核心问题。


技术难题之所以难,是因为情况复杂,没有通用的解决办法,唯一通用的是保持一个好心态:你要有战胜困难的信心,也要有接受失败的准备。如果你没有接受失败的淮备,就不会去尝试与众不同的方案,没有与众不同的方案,很多技术难题是没法解决的。在攻克技术难题时,想方设法尝试 不同的方案是最重要的。

陈智峰这一段话讲得太好了,就是要这样。简单的事情,别人老早就做完了,只有做难的事情,别人不敢想的事情,seemingly impossible的事情,才有可能有所突破。就像马斯克要做电动车,一开始人们都嘲笑他,但他做出来之后,市场就改变了。他如果做燃油车,肯定不会有现在这样的成就。其实seemingly impossible是可以做出来的,只是人们不相信,做电动车远没有那么困难,只是一开始没人相信电动车可以做的比燃油车更好,可以取代燃油车。


郄小虎说尝试不同的解决方案。我想说,一般情况下,其实只有知识广度够了,才能有更好的解决方案。我工作中的经历,和他文中的例子都证明了这一点,不知道后缀树这个数据结构,那么相关的方案自然想不到。我的当务之急是提升知识广度!


任何项目在实施之前都要做技木选型,就是你要选择一种技术作为项目的实现方式。我在过去20 年做过很多次技术选型,一次次实践下来,脑海里逐渐形成了一套自己的思维方式。


一般来说,我选技术会考虑两大方面的因素,一个是宏观、主观的,一个是微观、客观的。


先来看宏观的两个要素。


  1. 看这项技术解决的是不是大问题。任何新技术出来都是为了解决之前的某个问题,你要先看这个技术要解决的是不是一个大问题。所谓的大问题就是格局更大的问题,比如 C++ 解决的是 C 语言的一些问题,而 Java 则是以颠覆者的角度解释了 C/C++ 的所有问题。但是面对 WebSphere 这样的企业级技术来说,Java 在语言层面上解决的问题明显就不够大了。如果一个技术解决的是一个大问题,那么就很值得投入时间。

  1. 看这项技术解决问题的方式是否让人有想象空间。所谓想象空间就是说,这个技术是否会让你有一种可以干很多事情的感觉。比如智能手机这个技术的想象空间就很大,AI技术的想像空间也很不锴;再比如,Java 出现的时候,一门语言可以运行在所有的设备上,这种想象空间实在是令人神往。但是我们也要明白,想像空间超大的东西,炒作空间也很多。

是不是大问题,有没有想象空间,这两个点要靠主观判断,需要你花很长时间在这个行业里解决难题、踩坑、犯错才会有感觉,具体因人而异。


很多技术解决的是大问题,也很有想象空间,最终还是废掉了,为了避免这种情况,我们还要有徽观层面上比较客观的评估因素。


  1. 看有没有大公司撑腰。如果这项技术由谷歌、亚马逊等大公司主导,或者背后有大公司不断投钱,那么它就更有可能成功。如今大获成功的 Linux,一开始就是被 IBM 撑着做起来的。

  1. 看有没有很好的技术社区。也就是说这项技术要有人捧,就像Docker、Java 这样的技术,它们的技术社区都是相当夸张的。

  1. 看有没有杀手级应用。如界一项技术解决的是大问题,并且有想系空间,那它一定有杀手级应用。所谓杀手级过用,意味着这项技术有颠覆性,并且已经颠覆掉一些东西了。比如 Java 的杀手级应用 WebSphere、Spring、SOA 等。

  1. 看有没有经历十年以上时间。十年是一个成熟技术产品的成长周期。就跟人的生育一样,十个月就十个月,十个月生下孩子才健康,这个时间是跨越不了的。

这么多年来,我基本是按以上六个因素进行决策的。比如 2014 年,我在阿里建议使用一个新技术 Docker,最后不了了之。四年以后的 2018年,阿里全面走上 Docker 这条技术路线。而正是因为我很早就了解到了 Docker、Kubernetes(简称K8s),让我的技术观发生了非常大的变化,使我今天的技术影响力更进了一步。


还有2016 年,我创业的时候,需要为团队做技术选型,当时我在两个选项K8s和 Mesos 之间徘徊,最终选了 K8S。那一年至少 60%~70% 的创业公司选 Mesos, K8s 基本上没有人选,但我要求我们团队必须用。到今天,全世界都在用K8s,Mesos 基本被淘汰掉了。


关于技术选型,我在上面总结的六个维度也许不够全面,一定会有特例,但经过我的实践检验,基本上是比较靠谱的。

陈皓关于技术选型的文章,讲得太好了,全文摘录。不仅可用于技术选型,也可用于技术学习。同时从侧面印证了他之前关于前瞻提升的方法的有效性(关于如何拥有前瞻能力,陈皓老师给出了几点建议)。


很多人之所以觉得代码评审没用,主要有两个理由。第一,时间不够,工期压得太紧,连敲代码的时间都不够,哪有时间做评审?第二,需求总变,代码的生命周期太短了写再好的代码也没意义,反正过两天就会废弃。


这两个观点我可以理解,但非常不认同。这就好像说锻炼身体太累了,又没时间,环境污染这么重,况且人早晚都要死,所以活那么健康没意义。。。。。我从2002 年开始就浸泡在严格的代码评审里,做不做代码评审,直接关系到代码的工程水平。而所谓“时间不够”,“需求总变”,不是拒绝评审的理由:如果业务逼得紧,让技术人员疲于奔命,那应该是需求管理和项目管理的问题。需求总变,我们更应该做代码评审,因为需求变得越快,对代码质量的要求越高,就算是一次性代码,也应该评审一下它会不会影响那些长期在用的代码。

陈皓关于代码评审说的很好,但是问题的核心在于,很多公司不在乎质量,有若干原因。比如公司的要求低,能跑就行,出问题再优化,不行返工,反正压榨工人是最简单的;或者公司是作坊型的,就像陈皓之前说的小商品公司,那么做的项目,不是自己的产品,是给客户用的,没有代码审核的必要,后续优化还可以另收费;等等等等。只有公司的产品属于自己,是靠自己的产品质量吃饭的,才有代码评审的动力。同时才能把握开发节奏,不会一味追求不停做项目,而没时间评审。


在谷歌,所有的代码必领经过评南才能提交,很多人刚开始做代码评审,不知道愛律哪些方面,面对代码无从下其安只要掌握了重点,你就能又快又好地展开评审。谷歌曾经开源过一份评审指南(Google’s Code Review Guidelines),提到了代码评审应该关注的几个方面,其中包括:

  1. 设计:代码是否经过精心设计并适合系统?
  2. 功能:代码是否符合开发者意图?代码对用户是否友好?
  3. 复杂性:代码是否可以更简洁?未来其他开发人员接手时,是否易于理解与易用?
  4. 测试:代码是否经过正确且设计良好的自动化测试?
  5. 命名:开发人员是否为变量、类、方法等选择了明确的名称?
  6. 注释:注释是否清晰有效?
  7. 风格:代码是否遵循了谷歌的代码风格?
  8. 文档:开发人员是否同步更新了相关文档?

我自己做代码评审,会在这份指南的基础上特别关注三个地方。首先是代码的接口设计,软件工程师改代码或者新写软件的时候,要花很多时间琢磨接口,如果接口设计不太合适,或者有更好的建议,我会提出来。其次是算法复杂度是不是最低的,如果有更简洁的方案,我也会提出来。最后是测试,我会看代码使用的测试是否全面,如果我想到了目前没测试到的一些情况,就会提出建议。


通常情况下,体量比较少、改动比较少、上下紧凑的代码会更容易通过评审。那些10行20行、改动精准的代码评审起来很快;而上下紧凑的代码则保证了评审人不需要跳来跳去,只需要打开一个文件,从上到下看完就可以,如果代码写得很清楚,很容易通过评审。

陈智峰自己特别关注的三个点,也可以作为自己代码优化的方向。

陈皓说评审不是为了找Bug,而是进一步提升代码质量,确实如此,他还说不要程序做完了再做评审,应该写一点做一点,之前陈智峰也提到类似的观点


4.3 带团队的心法

其实在国外一些公司,工程师的技术和管理是分开的。比如谷歌前些年,每个团队里有一个角色叫Tech Lead,负责技术:解决项目上的复杂技术问题,帮助团队里的成员在技术上成长,并在日常写代码过程中指导其他人怎么做。另外有一个manager 是不写代码的,只负责行政、组织等工作,比如团队建设等。


在谷歌,manager 可以没有,但 Tech Lead 是必须要有的。我在谷歌的时候,平时工作汇报的对象就是 Tech Lead。现在谷歌里 面 Tech Lead 和manager 两者基本合一了。一般来说,manager 都是由技术很牛的工程师转过来的——技术很牛的工程师,去做了 manager。

郄小虎说工程师不愿意被manage,更愿意被lead。其实是人不愿意被manage,因为那是反人性的,除非军队一些特殊行业除外。


刚成为管理者时,你要按捺住,要克服和适应,要做好思想的转变——之前你想的都是怎么让自己变得更好,现在你要做的是怎么样让其他人更好,怎么样让团队变得更好。你要给团队指导方向,告诉大家哪些该做,哪些不该做,哪些要坚持,并让下面的同学能够理解这样做的原因,用软件 工程师能够理解的方式说服他们。

他还说管理者要多让其他人做而不是自己做。


作为这一行的管理者,你不仅仅要告诉下属具体做什么,还要说明几个层面的问题:第一,为什么要做这件事,不做另一件事;第二,做这件事有什么好的方法。对于那些入门级的软件工程师,你可能要手把手指导,并且告诉他,这件事情可能有五种做法,为什么我的这个做法比较好。在这个 过程中,你需要通过讲道理,让别人心服口服。而那些高阶的人有自己的主意,比如 A 做项目时,想去借用一下其他相关产品的内容做个补充,他会想着把相关的内容全部借用过来。这虽然表面上能解决问题,但可能不是最好的做法,因为用户在使用产品时,在中心模块看到其他产品的内容,体验会很不好。而你要能说清楚或证明为什么他想做的这件事不太好,不应该做,然后给出更好的方向和方法。

他还说相较于下指令,要讲道理,这其实是优秀管理通用的。


从我个人而言,我招聘的时候不太会看简历上的经验,而是更侧重去评判候选者的一些元能力,看他工作过程中沉淀出了哪些基本素质,哪些可以持续拥有的能力。比如系统设计、代码的结构化,通过分析找到关键问题,这都是元能力的体现。我会在面试过程中问一个我最近遇到的问题,可 能是一个特别基础的问题,像酒店的房卡是怎样设计的,地图的定位系统是怎样设计的等,看他怎样回答这个问题。有的人可能直接就开始给你讲设计方案,但有的人会先去定义真正的问题,让问题更加清晰,甚至先考應限制条件,先分析,再拆解,后设计。这个过程就可以体现候选者的元能力。

他还提出元能力很重要,其实就是分析问题和解决问题的能力


在软件工程师领域,道理也一样。管理者在推进当前业务需求的同时,也要布局未来更有效的解决方式,平衡好短期目标与长期目标。


具体怎么做呢?首先,管理者需要判断,哪些事情需要长期投人,做了之后能产生规模效应。


比如,谷歌花了相当大的精力投人到代码发布的流水线上,它之所以做这件事,就是希望从每个软件工程师写代码到代码最终上线,整个流程的时间变得最短,质量最高。你可能觉得,没有这个我也可以写代码,但是有了以后效率完全不一样,是农业社会到工业社会的转变。软件工程就是这 样,要在研发体系和工具上有长期的投人,可能短期内你觉得不划算,但做完之后会带来革命性的提升。


其次,管理者要坚定地投入到长期目标上。之所以强调坚定,是因为眼前的事太多,一不小心,长期的项目就半途而废了。这个坑太大了。我们看到很多技术团队忙于应付眼前的事情,今天 10个人实现了 10个需求,明天来了100个需求,完成不了,就开始加人,不停地堆人,最后系统变成了一个个代码的堆叠,直到没办法正常运转,或者运转的效率非常低。


因此,技术管理者要在完成短期目标的同时,时时记住长期目标的推进。

他还提到管理者要关注长期目标,这一点很容易被忽视。因此他提到要判断紧急和重要,在业务看来所有的需求都是紧急的,其实重要的事情更要持续推进。他最后提到团队合作要一加一大于二,要找到利益共同点。


5 行业大神

Latency Numbers Every Programmer Should Know——by Jeff Dean

每个计算机工程师都应该知道的数字列表——杰夫·迪恩

崔叉叉注

这个数字列表,网上有多个版本,书里的应该是流传比较广的一个错误版本,此处是正确的。

参考:

中文            英文                 时间(纳秒)       时间(微秒)  时间(毫秒) 
一级缓存引用 L1 cache reference 0.5 ns
分支错误预测 Branch mispredict 5 ns
二级缓存引用 L2 cache reference 7 ns
互斥锁 / 解锁 Mutex lock/unlock 25 ns
主存引用 Main memory referenc 100 ns
用Zippy压缩1K字节 Compress 1K bytes with Zippy 3000 ns 3 us
通过1Gbps网络发送1K字节 Send 1K bytes over 1 Gbps network 10,000 ns 10 us
从内存连续读取1MB Read 1 MB sequentially from memory 250,000 ns 250 us
同一数据中心内的往返 Round trip within same datacenter 500,000 ns 500 us
磁盘寻址 Disk seek 10,000,000 ns 10, 000 us 10 ms
从磁盘连续读取1MB Read 1 MB sequentially from disk 20,000,000 ns 20, 000 us 20 ms
在CA向荷兰发包再返回 Send packet CA –> Netherlands –> CA 150,000,000 ns 150,000 us 150 ms

6 行业清单