85-研发效率破局之道

开篇词 | 为什么你要关注研发效能？

你好，我是葛俊，曾在 Facebook 内部工具组工作，与两个同事共同负责软件研发工具套件 Phabricator 的开发和开源，是Phabricator的主要作者之一。

最近这十年，国内互联网产业的发展速度不亚于硅谷，在商业模式创新方面甚至已经完成超越，但是我们在研发效能方面始终比较落后。今年年初爆发的 996 大讨论，让国内的加班问题，吸引了国内外开发者的关注。我们很难予以否认，在互联网行业繁荣发展的背景下，国内很多公司采用了“拼工时”的做法，却忽略了最最应该关注的研发效能。

现在，我想请你回忆下，你是否也曾为下面这些问题感到困扰呢？

研发团队看起来人也不少，大家也很辛苦，加班也不少了，但是产品发布还是常常延期，上线后产品问题频发。

用户需求从需求分析、产品设计、开发、测试最终流到部署，但最终发布的产品与用户需求偏差却很大。

产品发布上线时出现大量提交、合并，导致最后时刻出现很多问题，团队成员集体熬夜加班，却将大把的时间花在了等待环境、等待验证上。

开发提测质量不好，大量压力聚集到测试这一步，导致代码返工率很高。引入单元测试、代码审查，效果却都不明显。

开发人员疲于应付业务，没有精力或者兴趣去精进技术，对 Git、命令行等强大工具的使用仅限于皮毛，士气低迷、工作效率低下。

这其实就是团队的研发效率，也就是研发效能出现了问题。那么，研发效能到底是什么呢？

一提到研发效能，很多人的第一反应可能都是开发的速率，也就是研发团队能否快速发布产品。但在我看来，速率只是效能的三大支柱之一。

除了快，产品开发更重要的是方向正确，因为不能给用户和公司真正提供价值的产品，做了也是白做。另外，高效能还需要有可持续性，否则短期的高产出可能会严重伤害长期的产出。比如，连续熬夜加班带来的身体问题，会导致后续工作效率低下，得不偿失。

因此，研发效能的完整定义应该是：团队能够持续地为用户产生有效价值的效率，包括有效性（Effectiveness）、效率（Efficiency）和可持续性（Sustainability）三个方面。简单来说，就是能否长期、高效地开发出有价值的产品。

可喜的是，国内越来越多的公司开始在研发流程、工具、文化等方面下功夫，很多百人研发人员规模的公司开始组建了专门的效能团队，来提高整个公司的研发效能。

这是一个很好的现象和趋势。但，很多公司在推进研发效能的时候，常常不知道从何下手，或者是花了精力、加大了投入却看不到效果，产出抵不上投入。比如，我在一些公司做内训和顾问工作的时候，经常会遇到下面这样的案例：

想通过指标度量的方式来衡量团队的效能，要求每个团队达到一定的测试覆盖率。研发团队在产品完成后进行突击，来编写单元测试，最终达到了要求，但产品质量却没有提高。

引入业界先进工程实践，学习 Google 使用大仓，但因为基础设施不成熟，对大量二进制文件支持很差，结果是算法团队因为有很多的二进制模型文件，每次 Git Clone 都需要半小时，怨声载道。

希望建设工程师文化来提高产出和活跃气氛，跟公司老大以及 HR 商量好几条价值观在公司宣传推广，还组织了几次团建活动，但是收效甚微。大家真正工作起来还是老样子。

这些问题的根源都在于，软件开发的灵活性决定了研发效能提升的困难性：可以关注的点太多，可以使用的方法也很多，但如果只是简单照搬业界研发实践的话，效果往往不好，有时甚至会造成负面效果。

而与国内公司形成鲜明对比的是，硅谷的互联网公司在推进研发效能方面做得要好得多。在 2000 年互联网泡沫之后，美国的互联网产业从疯狂增长进入到了“精耕细作”的阶段，需要通过比拼效能在竞争中取得优势，并在此过程中积累了很多经验。

在这其中，Facebook 的研发效能非常高，更是硅谷公司中的一个典范。比如，早在 2012 年 Facebook 月活达到 10 亿的时候，后端服务及前端网站的部署，采用的是每周一次全量代码部署、每天一次增量代码部署，以及每天不定次数的热修复部署，但部署人员就只有三个，达到平均每个部署人员支撑 3.3 亿用户的惊人效率。

又比如，社交网络出现 Bug 的时候，调测起来非常麻烦。因为要复现 Bug 场景中错综复杂的社交网络数据，困难并且耗时。但在 Facebook，它采用开发环境跟生产环境共享一套数据的方法。这就使得开发人员可以非常方便地在自己的机器上复现这个 Bug，进行调测。当然，这样的数据共享机制背后有着强大的技术和管理支撑来规避风险。

2010 到 2013 年之间，我在 Facebook 基础平台团队的内部工具组，作为核心成员，研发并开源了研发工具套件 Phabricator。2013 到 2015 年，我又作为效能工具的使用者，参与了 Facebook 对外产品的研发。也正因为这几年的工作，我对 Facebook 如何提高研发效能有了越来越清晰的理解，认识到研发效能的提高，需要整个公司在研发流程、工程方法、个人效能和文化管理等方面进行精心设计。

离开 Facebook 之后，我在硅谷的 Stand Technologies 公司、国内的创业公司以及华为担任过技术总负责人、CTO、技术专家和团队主管等角色，带领百人技术团队进行研发。

比如，2017 年到 2018 年，我在华为开发工具部主导下一代集成开发工具环境，为软件开发工程师提供全栈的端云一体工具平台，服务于 2 万多开发者，致力于提高公司整体的研发效能。同时，我也尝试将研发效能的工程实践引入华为。比如，我在团队进行了几次黑客松（Hackathon），每次活动，平均 10 个开发者就产生一个项目，每 10 个项目中就有 1.5 个成功落地。

工作 15 年来，我在研发效能团队工作过，也在产品团队中推动过研发效能，这其中包括硅谷和国内的公司，也包括大型企业和创业公司。对怎样在一个公司或者团队引入效能实践，有比较丰富的经验。

所以，当极客时间团队邀请我写一个与研发效能相关的专栏时，我毫不犹豫地就答应了，希望能够借此把之前的经验、教训做一次系统地梳理，帮助到同样对效能有期待同时又有困惑的同行者，另外对自己也是一次温故知新的机会。

在这个专栏中，我会从 4 个方面，分 5 个模块与你讲清楚如何做到研发的高效能。

研发效能综述。我会讲解研发效能的定义、模型，并着重介绍研发效能度量的正确使用方法。借此，希望你能够梳理出研发效能的主脉络，构建出一副清晰的知识图谱。

研发流程模块。我将与你分享敏捷、持续集成、分支管理、持续交付、DevOps、团队协同等话题，希望能帮助你深入理解研发过程中的关键流程，以及流程优化的基本原则，从而能够针对自己的实际情况，找到最合适的工程实践，让软件开发的整个流程更加顺畅、高效。

工程方法模块。我将与你讨论研发流程中各关键步骤高效工程方法，包括开发、测试、运维。并与你分析软件开发的趋势和热点，比如智能化开发、在移动设备上开发、云原生等等。通过这个模块，我希望加深你对这些具体工程方法的了解，并学会如何正确地使用这些方法。

个人效能模块。我将与你分享如何提高个人效能，具体涉及深度工作、Git、命令行、VIM、工具环境集成等内容，旨在帮助你提高技术的专精程度和持续成长。每个开发人员都应该提高自己的效能，只有这样才能持续学习持续提高，避免被业务拖着跑。

管理和文化模块。我将重点与你系统分析硅谷管理和文化，尤其是 Facebook 的工程师文化，并根据我在国内公司的具体落地经验，给出推荐的文化引入和建设方法。

研发效能和软件开发一样，都具有很大的灵活性，提高研发效能也不是照搬照套就能做好的。所以在写作专栏的过程中，我会着重讲解 Why，带你深入了解效能实践背后的原理，然后才给出 How，也就是具体的实践。因为只有深刻理解原理，才能灵活运用。

同时，我会与你分享尽量多的案例，带你一起了解国内外一些公司的优秀做法，分析它们成功的经验，当然，我也会分享失败的案例，以及背后的原因。不过更重要的是，我希望你能够跟着我一起分析，通过对比思考，找到真正适合团队和自身的实践。而这，是我写作这个专栏的真正初衷。

这是研发效能专栏的第一篇文章，如果可以的话，欢迎你在留言区做个自我介绍，和我聊聊你或者你的团队在研发效能方面的实践以及遇到的问题，增进我们彼此的了解。而且，我也希望你看过这个专栏后，能够再回头来看看最初留下的内容，相信届时你已经对研发效能有了新的理解和思考。

01 | 效能模型：如何系统地理解研发效能？

你好，我是葛俊。今天，我来和你聊聊什么是研发效能，以及研发效能的模型，这些内容是理解整个专栏的基础。

今年的 3 月 26 日，一位昵称为 996icu 的用户，在 GitHub 上创建了 996.ICU 的项目，自此 996 这个话题被推上了风口浪尖。目前，这个项目已经拿到了 24 万多颗星。朋友们也常常问我：硅谷的公司有没有 996？

其实，在硅谷，很少有公司要求 996。不过，在初创公司，因为业务紧张、同事间的竞争，加班也很常见。但是，硅谷和国内的公司有一个很大的区别，就是硅谷的公司一般是任务驱动，只要完成任务就行，不管你花了多少时间。而国内很多实行 996 的公司不仅仅是要求完成任务，更强调工作时长。但其实，专注时长的这种操作在软件开发行业是不合理的，因为长期加班不能保证持续的高效产出。

从我以及身边许多开发者的经验来看，每天能够高效地产出代码五六个小时，已经相当不错了。短期突击加班会有效果，但如果长期加班，通常效率、质量会下降，产生了 Bug 就要花费更多的精力去修复。如果这些 Bug 发布到了用户手上，损失就会更大，得不偿失。

长期加班还会出现无效加班的结果。比如，有个朋友在一家国内一流的互联网公司工作，据他反馈，公司实行 996，很多人加班其实是磨洋工，低效加班非常明显。可想而知，其他推行 996 工作制的公司，大概率也会存在这种问题。

那么，长期加班效果不好，面对激烈竞争，我们到底应该怎么办呢？在我看来，这个问题还是要从软件开发本身的特点来解决。

为什么要关注研发效能？

软件开发是一个创造性很高的过程，开发者之间的效率相差很大。就比如，10x 程序员的生产效率可以达到普通开发者的 10 倍。其实，不仅是个人，团队间的效率相差也很大。所以，相比工作时长而言，公司更应该关注的是研发效能。

接下来，我们再回顾一下我在开篇词中给研发效能的定义：

研发效能，是团队能够持续为用户产生有效价值的效率，包括有效性（Effectiveness）、效率（Efficiency）和可持续性（Sustainability）三方面。简单来说，就是开发者是否能够长期既快又准地产生用户价值。

硅谷的很多知名公司，比如 Google、Facebook、Netflix 等，在研发效能上做得很好，是研发效能的标杆。这，也是它们业务成功的重要因素。

以我在开篇词中提到的 Facebook 的部署上线流程为例。Facebook 在 2012 年达到 10 亿月活的时候，部署人员只有三个，达到平均每个人支撑 3.3 亿用户的惊人效率。举个形象的例子，如果全中国每一个人都使用 Facebook，那最多只要 5 个部署人员就够了。

Facebook 做到这一点的基础，就是不断提高研发效能。还是以上面的部署流程为例，原来的部署已经非常高效，但是在 2017 年，Facebook 又引入了持续部署，做了进一步的优化，实现了更高效率的部署上线。试想一下，如果 Facebook 选择堆人、堆时间的话，那需要增加多少人、多少加班才能做到呢？

所以在我看来，如果研发效能提不上去，单靠加人、加班根本不可能解决问题。

注重研发效能的另一个巨大好处，是开发者能够聚焦产出价值，更容易精进自己的技术。于是，团队容易建立起好的氛围，进而又能促进生产效率的提高，形成良性循环，支撑持续的高效开发。

所以，国内公司近一两年越来越注重提高研发效能，许多公司甚至专门成立了工程效率部门。但是，在真正开展研发效能提升工作时，它们却常常因为头绪太多无从下手，或者对方法了解不够，导致画虎不成反类犬的效果。这，又和软件研发的高度创造性和灵活性紧密相关。

自软件行业诞生起，开发者们就发挥聪明才智，不断创造新的方法、流程和工具来适应和提高生产效率。互联网产业爆发以来，这一趋势更是明显：从最初的瀑布研发流程到敏捷到精益，从持续集成到持续发布到持续部署，从实体机到虚拟机到 Docker，从本地机器到数据中心再到云上部署，从单体应用到微服务再到无服务应用。新的工具、方法，可谓层出不穷。

面对如此多的选择，如果能处理好，则开发体验好，产品发布速度快，研发过程处于一个持续的良性发展情况。但处理不好，就会事倍功半，出现扯皮、重复劳动、产品质量不好、不可维护的情况。

微服务的不合理引入就是一个典型的例子。自从亚马逊（Amazon）成功大规模地应用后，微服务逐渐形成风潮，很多公司在不清楚适用场景的情况下盲目采用，结果是踩了很多坑。

比如，我见过一个初创公司，在业务还没开展起来的时候，一上来就采用微服务，因为没有要求一致的技术栈，也没有限制服务的大小，于是开发人员怎么方便怎么做，只考虑局部优化而忽视了全局优化。半年下来，20 人的开发团队搞出了 30 多个服务和 5 种开发语言。服务之间的调用依赖和部署上线越来越复杂，难以维护，每次上线问题不断，经常搞通宵才能让服务稳定下来。同时，知识的共享非常有限，有好几个服务只有一个人了解的情况，一旦这个人不在的时候这个服务出现问题，解决起来就基本上成了“不可能的任务”。这样的错误使用微服务的公司非常普遍。

那，到底怎样才能有效地提高研发效能呢？硅谷的业界标杆公司又是怎么做到高效能的呢？

只有深入研发活动的本质，才能提高效能

我的建议是，提高研发效能，需要深入了解研发活动的本质，从纷乱的表象和层出不穷的方法中，看到隐藏的模型，找到根本原则，然后从这些原则出发，具体问题具体分析，找到合适的方法。这样做的原因是，软件研发很灵活，在实践的时候总会见到不一样的状况。越是灵活的东西，就越需要理解其本质，这样才能做到随机应变。

在 Facebook 的时候，我们做事时都遵循一些基本原则。比如，有一个原则是“不要阻塞开发人员”，贯穿在公司的很多研发和管理实践中。接下来，我给你举两个具体的应用场景，来帮助你理解这个原则。

第一个应用场景是，本地构建脚本的运行速度要足够快。开发人员在自己的开发机器上写完代码之后，都要运行这个脚本进行构建，把新做的改动在自己的开发机器沙盒环境上运行起来，以方便做一些基本检查。

这个操作非常频繁，所以如果它的运行时间太长，就会阻塞开发。因此，确保这个脚本的快速运行就是内部工具团队的一个超高优先级的任务。我们对每次脚本的运行进行埋点跟踪，当运行时长超过 1.5 分钟后，我们就会停下手中的工作，想尽一切办法给这个本地构建加速。

第二个应用场景是，商用软件的采购。对一定数额下的软件购买，开发人员可以自行决定，先斩后奏。而且那个数额还蛮高的，覆盖一般的软件完全没问题。

我个人就经历过两次在晚上加班时，要购买一个商业软件的情况。如果等主管审批，就需要到第二天。但，因为公司信任工程师能够在这样的情况下做出利于公司的决定，所以我可以直接购买并使用。这样一来，除了能提高这几个小时的开发效率外，更重要的是，我觉得自己拥有信任和权力，工作积极性更加高涨。

这两个应用场景差别很大，却都是基于“不要阻塞开发人员”这个原则。Facebook 之所以会有这个原则，正是因为它认识到了，开发流程的顺畅是生产优质软件的关键因素，只有这样才能最大程度地释放开发者的创造性和积极性。相比之下，很多公司更注重强管理下的流程和制度，而忽略了开发的顺畅，结果是开发人员工作起来磕磕绊绊，又谈何高效率呢。

看到这里你会说，我已经很清楚地知道，要想提高研发效能，就必须理解软件开发的本质。那么，软件开发的本质到底什么呢？

接下来，我就和你探讨一下软件开发的本质特点，然后基于这些特点，搭建出一个研发效能的模型，希望你可以灵活运用，找到提高研发效能的主要着力点。这个思路，将是贯穿整个专栏的主线索。

研发效能的模型是什么？

在我看来，软件开发本质上就是一条超级灵活的流水线。这个流水线从产品需求出发，经过开发、测试、发布、运维等环节，每一个环节的产出流动到下一个环节进行处理，最后交付给用户。

图 1 软件开发的流水线

另外，这条流水线的每个环节都还可以细分。比如，本地开发环节可以细分为下面几个部分：

图 2 本地开发流水线

这种流水线工作方式，在传统的制造业中很普遍，也已经有了很多经验和成功实践。最典型的就是汽车生产中使用的丰田生产体系（Toyota Production System，TPS）。所以，我们可以参考传统制造行业的经验来提高效能。

事实上，瀑布模式就类似于传统流水线的处理方法：它强调每个环节之间界限分明，尽量清晰地定义每一个环节的输入和输出，保证每一个环节产出的质量。但，和传统制造业相比，软件开发又具有超强的灵活性，体现在以下四个方面。

最终产品目标的灵活性。传统流水线的目标确定，而互联网产品的最终形态通常是在不断地迭代中逐步明确，相当于是一个移动的标靶。尤其是最近几年，这一灵活性愈发明显。比如，在精益开发实践中，常常使用 MVP（最小可行性产品，Minimal Viable Product）来不断验证产品假设，经过不断调整最终形成产品。

节点之间关系的灵活性，比如流水线上的多个节点可以互相融合。DevOps 就是在模糊节点之间的边界，甚至有一些实践会直接去掉某些环节，或者融入到其他环节当中。

每个节点的灵活性。每一个生产环节都会不断涌现出新的生产方式 / 方法。比如测试，最近十多年就产生了测试驱动开发、Dogfood（狗粮测试）、测试前移等方法；最近又出现的测试右移，开始强调在生产环境中进行测试。

每个节点上的开发人员的灵活性。跟传统制造业不同，流水线上的每一个工作人员，也就是开发者，都有很强的灵活性，主要表现在对一个相同的功能，可以选择很多不同的方式、不同的工具来实现。
比如，之前我在 Facebook 做后端开发的时候，同样一个代码仓，有的同事使用命令行的编辑环境 VIM/Emacs，有的使用图形界面 IDE，有的使用 WebIDE；在实现一个工具的时候，大家可以自己选择使用 Python 还是 Ruby 还是 PHP。这其中的每一个选择，都很可能影响效能。

基于这些特点，我们可以从以下四个方面去提高研发效能。

图 3 研发效能模型

优化流流程，主要针对特点 1 和 2，也就是最终产品目标的灵活性和节点间关系的灵活性，进行优化。具体来说，针对最终产品目标的灵活性，主要是提高流程的灵活性，让它能聚焦最终产生的用户价值，以终为始地指导工作，击中移动的标靶。而针对节点之间关系的灵活性，则主要聚焦流水线的顺畅，以保证用户价值的流动受到的阻力最小。

团队工程实实践，主是针对特点 3，也就是每个节点的灵活性进行优化，聚焦每一个生产环节的工程实践进行提高。

个人工程实实践，主是针对特点 4，也就是每个节点上开发人员的灵活性，来提高个人研发效能。争取让每个开发人员都能适当地关注业务、以终为始，同时从方法和工具上提高开发效率，实现 1+1>2 的效果。

文化和管管理。任何流程、实践的引入和推广，都必须有合理的管理方法来支撑。同时，文化是一个团队工作的基本价值观和潜规则。只有建立好文化，才能让团队持续学习，从而应对新的挑战。所以，要提高效能，我们还需要文化和管理这个引擎。

优化流程、团队工程实践、个人工程实践以及文化和管理，就是我们提高研发效能需要关注的四个方面，也就是我们所说的研发效能模型。

在接下来的文章中，我会以这个模型为基础，从以上这四个方向与你介绍硅谷公司，尤其是我最熟悉的 Facebook 的成功实践，并着重向你讲述这些实践背后的原理。因为只有理解了这些原理和原则，我们才有可能在这个超级灵活和高速发展的软件开发行业里见招拆招，立于不败之地。

总结

在今天这篇文章中，我和你再次强调了研发效能是团队能够持续为用户产生有效价值的效率，包括有效性、效率和可持续性三方面。

而关于如何提高效能，我的建议是深入了解研发活动的本质，从纷乱的表象和层出不穷的方法中，看到隐藏的模型，找到根本原则，然后从这些原则出发，具体问题具体分析，找到合适的方法。

最后，我借鉴传统制造业流水线的形式，并结合软件开发的特定灵活性，总结出了研发效能的模型，主要包括优化流程、团队工程实践、个人工程实践、管理与文化。专栏的后续内容，我将分为这四大模块，帮助你提高团队和个人的研发效能。

其实，研发效能对于硅谷的公司和个人来说，已经是常识，而我在美国工作的 10 多年，也已经习惯了这种工作方式。回国之后，我深入了解了国内的开发情况，对效能关注不到位的现状颇为吃惊。因为这不符合软件开发这个行业的基本特性，也限制了国内软件行业的发展。

同时，我也对国内开发人员的生存环境，感到些许遗憾。本来软件开发是一个可以充分发挥创造性的、有趣的行业，技术的发展有无尽的空间。但是，开发人员却常常被业务拖着跑，技术发展和创造性都被限制住了。另外，因为拼时长的做法，也伤害了开发者的身体健康和家庭关系。

正是因为这些惊讶和遗憾，我将这十几年对研发效能的理解与实践，做了一次系统梳理，于是就有了今天提到的研发效能模型。我希望这种系统化的模型方法，能够为国内软件团队的效能提升提供一些参考和引导，也希望能够提升开发者个人的技能，以节省出时间来体会研发的快乐，提高生活的幸福感。

思考题

你有见过 996 对团队和产品造成伤害的具体事例吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

02 | 效能度量：效果不好甚至有副作用，怎么回事？

你好，我是葛俊。今天，我来和你聊聊研发效能的度量。

在和技术管理者，尤其是高层管理者聊起研发效能的时候，常常提起效能的度量这个话题。管理学大师彼得 · 德鲁克（Peter Drucker）曾经说过，“一个事务，你如果无法度量它，就无法管理它”（If you can’t measure it, you can’t manage it）。要想提高研发效能，自然要首先解决效能的度量的问题。

在软件研发过程中，数据驱动的手段被大量采用，比如说通过使用漏斗指标和 A/B 测试，用数据来指导产品的方向。按理说，软件开发行业是数据驱动的高手，用数据驱动来理解研发效能应该早被研究透了啊。

但事实上，效能的度量却是一个出了名的难题，至今没有哪个公司敢号称已经找到了效能度量的完美答案。不仅如此，绝大部分软件公司在使用研发效能度量这个工具时，不但没有起到正向作用，还伤害了产出和团队氛围。

所以，在今天这篇文章中，我就与你一起看看研发效能度量到底是什么、度量使用中的常见错误，以及困难的原因。

研发效能度量的定义和作用

首先，我来介绍一下效能度量的定义和作用。

研发效能的度量代表一组可量化的数据或参数，用来跟踪和评估开发过程的“健康”状况。 换句话说，研发效能的度量，从应用程序开发的生命周期中获取数据，并使用这些数据来衡量软件开发人员的工作效率。我们希望通过这样的度量，能够根据客观的数据而不是个人的主观意见去决策，从而实现以下几点：

跟踪团队的表现、提高团队的绩效。通过确定研发效率指标，公司可以明确团队和成员的工作预期，从而使得开发人员能够目标性更清晰地投入研发。同时，这些生产指标可以作为“晴雨表”，帮助团队定位影响工作效率的不良因素，从而帮助团队消除这些因素，最终达到团队更快乐、绩效更高的目的。

提高项目计划的精确度。团队负责人可以通过度量来估算一个需求端到端的成本，包括收集成本、设计系统成本、开发测试成本，以及运维成本等，来了解每项活动在项目总成本中的占比，从而更好地确定这些活动的优先级。同时，我们可以了解哪些步骤有较大风险和不确定性，从而提高预测项目进展的精准度。

了解流程是否高效，寻找需要改进的关键领域。我们可以衡量进行每项研发活动所需的时间，并估算其对质量和生产效率的影响，然后比较成本和收益，最终确定哪些步骤是高效的，以及哪些步骤是需要改善的。我们还可以对不同的实践进行 A/B 测试，以此来选择更好的方法。

提高团队绩效、提高计划精确度，以及寻找关键的待改进领域，这三个因素的结合有助于简化工作流程，发现瓶颈，帮助团队持续改善现有产品的生命周期，从而更高效地生产出质量更好的产品。

效能度量的错误案例

因为上述作用，绝大多数公司都非常重视研发效能的度量，并且基本上所有公司都或多或少地采用了度量。但遗憾的是，能够将其用好的公司少之又少，而且我见到的公司中还存在大量误用的情况。

接下来，我与你分享一些真实案例，我们一起来看看它们错在哪里，以及可以如何调整。

案例 1：全公司范围内推行一套效能度量指标

某大型公司为了提高效能，专门成立了一个不小的团队，通过详细调研，制定了一组研发效能度量指标，并引入和开发了相应的工具。客观来讲，这些度量方式的质量很高。

准备就绪后，这个公司找了几个团队做试点，把这些效能度量数据作为参考提供给他们使用。三四个月下来，试点效果不错，这几个团队的产出速度有了一定的提高。于是，公司高层决定在全公司范围内推行这套方案。

但，随着大范围的推广，一些团队发现，它们的研发流程跟试点团队差别较大，需要花费相当多的时间去收集度量数据，受益却不明显，甚至是得不偿失。

更极端的情况是，有一个团队的开发环境和这套效能度量工具的环境差别实在太大，所以不得不专门为这个工具部署了一个环境。然后每个周六，他们就要到这个环境里，为度量系统提供数据，还常常被迫提供一些“假数据”，以满足度量系统的需求。

因此，这些团队觉得这套方案并不适合自己，公司内逐步出现了些许反对声音。但是，公司推进这套流程的态度非常强硬，为了顺利推进，还把这套流程与团队绩效绑定到了一起。一旦某个团队的指标未能达到要求，就直接实行绩效扣分。由此，效能度量的负面作用逐渐凸显。大量团队为了好的绩效，而被迫去玩数字游戏。

全公司范围推行这套方案的一年多，可以说是怨声载道。明明是一套高质量的度量系统，却阻碍了团队业务的发展，并严重伤害了员工的积极性。直至最后这个实际情况被反馈到一位高管那里，这套度量系统才被完全取缔。

案例 2：一个中型公司推行质量方面的指标

一个 500 人左右的公司，公司的组织架构按照职能进行水平划分，并没有进行矩阵式管理。为了提高公司的产品质量，QA 团队提出了一组软件质量方面的度量指标，包括 Bug 严重性定义，每一个发布里不同级别的 Bug 的未解决率，以及上线的一些质量流程。QA 团队得到了公司高层的支持，强制推行这些指标。

这些指标针对的主要是开发活动，但开发团队却认为，这些度量用处不大。他们认为，公司研发过程中的真正问题在于产品定义变化过快，常常是在冲刺开始后还不断更改需求。这就导致开发和测试团队即使拼命加班还常常错过发布日期，产品的 Bug 也不少。所以，开发团队提出，真正应该度量的是产品需求的稳定性。但是，因为公司高层认为需求变化是为了满足用户需求，所以没有批准开发团队的这个要求。

结果是，开发团队觉得公司效能度量只是用来束缚他们的，根本不能帮助他们工作，于是工作积极性下降，离职率上升。同时，产品质量也并没有因为这些度量指标的推行而提高。直到我写下这篇文章的时候，这个公司还是这个情况。

案例 3：某创业公司聚焦度量开发、测试、上线准确度等指标

一个由 10 多个人组成的，拿到了 Pre-A 投资的创业公司，正处在研发产品寻找市场吻合度的阶段。掌权的 CEO 和产品副总坚信数据驱动，于是对研发流程定义了严格的度量和指标，比如 App 上线周期准时率、Bug 的关闭速度、性能参数，等等。

整个团队很专业，也很敬业。他们花了很多精力去严格完成这些度量指标，做到了绝大部分情况下准时、高质量地上线产品。但是，CEO 和产品副总并不是产品专家，只关注了开发过程中的数据，却没有收集其他步骤的数据去快速试错和寻找产品方向。

结果就是，虽然产品的每一个发布都很准时，质量也非常高，但因为公司在寻找市场需求吻合度方面动作迟缓，导致用户增长缓慢。因此，一年半以后，资金耗尽，投资人失去信心，公司倒闭。

这 3 个案例只是不同规模公司的几个典型场景，类似的失败案例数不胜数。那么，上面这些案例的问题出在什么地方？效能度量为什么这么难？

效能度量被大量误用，问题究竟出在哪儿？

研发效能难以度量的最根本原因在于，软件开发工作是一项创造性很强的知识性工作，非常复杂且伴随有大量不确定因素。

比如，软件产品的需求变化很快，需求文档的更新常常滞后于工程实现，甚至有的敏捷方法论提倡完全抛弃需求文档。

又比如，软件产品的实现方式有很大的不确定性。一个相同的功能，可以采用多种语言、框架、平台，使用各种不同的研发流程生产出来。在这种情况下，我们很难通过度量来衡量这些不同研发方法和中间过程的优劣。

面对这样的一个复杂系统，我们不可能覆盖其全部参数。而如果这时，研发人员的利益和这个度量结果相关，那么他就很可能会通过“做数字”来欺骗度量系统。

关于这个主题，美国著名学者罗伯特·奥斯汀（Robert Austin）写过一本书，叫作《衡量和管理组织绩效》(Measuring and Managing Performance in Organizations) 。他在这本书中给出的结论是，如果你不能度量一个事物的所有方面，那就不要去度量它。否则，你将得到“做数字”的欺骗行为。

我在文稿里放了一组有名的 Dilbert 漫画。这组漫画，讲的是一个公司宣布使用 Bug 修复数量做度量，每修复一个 Bug 奖励 10 美元，消息一出，开发人员欢呼雀跃。一个程序员当场表示，当天下午就要给自己写出一辆汽车，因为他很容易就可以写出很多简单的 Bug，然后马上去修复它们。

通过这个例子，我想要和你说明的重点是：度量与绩效挂钩，结果是指标上去了，却没给软件产品带来任何好处。

备注：图片来自https://dilbert.com/strip/1995-11-13

而我刚刚和你举的大型公司全公司范围内推行一套度量指标的案例，正是犯了度量与绩效挂钩的错误，这种情况下，很容易出现“做数字”的不良行为。这也是使用度量时最常见的错误，我们一定要留意。

研发效能难以度量的第二个原因，和上面提到的根本原因相关，但有其特殊性。很多公司有竖井（silo）存在，所以常常会把注意力放到某一两个竖井上，进行局部优化。但是，局部优化并不代表全局优化，甚至会让全局恶化。

上面那个中型公司推行质量方面指标的案例，就是在进行局部优化，不但没能提高产品质量，反而导致员工积极性受损，同时影响了团队之间的关系。

这样的问题，在按职能划分团队的公司很容易出现。因为在这样的组织划分下，更容易出现竖井，自然就更容易按照竖井来考虑小团队的表现。如果你的公司基于职能划分，一定要多加留意。

研发效能难以度量的第三个原因在于，度量指标一般用来度量软件产品的生产过程和产品质量，但是公司真正需要关注的是产品能否解决用户问题，也就是说能否产生用户价值。技术产品输出和用户价值输出之间的沟壑难以打通。

上面案例中那家创业公司聚焦度量开发测试指标，就是犯了这样的错误。产品质量再好，没有用户价值也是白费工夫。

当然了，研发效能难以度量还有一些其他原因，比如：

度量数据的收集难易程度不同，人们倾向拿容易收集的数据去关联效率，但事实上难以收集的数据对度量可能才真正有用。比如说代码行数容易衡量，但是用处很小。相比之下，产品用户价值的度量要困难得多，但用处却大得多。

软件开发和实践有一个滞后效应。比如，在团队中引入代码审查，在刚开始实行的时候，总体效率会出现短时间的下降，一两个月后才会逐步显现正面效果。那么，你现在要怎么度量它才好呢？

总结

在今天的分享中，我给出了效能度量的定义及作用，列举了三个典型的失败案例，并通过这几个例子和你详细分析了度量困难的几个主要原因。

研发效能的度量一直以来都是个难题，很多业界大佬也都发表过“研发效率不可度量”的观点。比如，马丁 · 福勒（Martin Fowler）在一篇叫作“无法衡量生产效率” (CannotMeasureProductivity) 的文章中指出：这是我认为我们必须承认无知的地方。

周思博（Joel Spolsky）在一篇名为“飙高音”（做到最好，Hitting the High Notes）的文章中写到，衡量程序员的工作效率相当困难，原因如下：

几乎任何你能想到的指标（比如，调测过的代码行数、功能点、命令行参数的个数）都很容易被“做数字”；

我们极少要求两个程序员做完全相同的事情，所以很难获取大型项目的有价值度量数据作为参考。

那是不是说在研发软件时，我们就不能使用度量效能的方法来指导工作了呢？如果是的话，这对于软件团队管理者而言，将会是一个难以接受的事实。

这个问题的答案，我们就留到下一篇文章再揭晓吧，敬请期待。

思考题

你在工作中有没有经历过研发效能度量的失败案例？如果有的话，你觉得失败的原因是什么？关于度量谜题怎么解决，你有没有什么建议？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

03 | 效能度量：如何选对指标与方法，真正提升效能？

你好，我是葛俊。今天，我来和你聊聊如何正确使用效能度量。

在上一篇文章，我给你介绍了效能度量的定义、作用，以及几个使用误区。我们先简单回顾一下：

软件系统异常复杂，度量指标无法覆盖其所有参数，从而容易被“数字游戏”欺骗。

竖井指标的提高不等于全局指标的提高，局部优化不等于全局优化。

研发效能度量指标一般用来衡量软件产品的生产过程和产品质量，但公司真正需要关注的是能否产生用户价值。这两者之间存在着难以跨越的鸿沟。

正是因为这种种看似难以解决的问题，业界甚至有人认为研发效能的度量是一个无解的问题。但我并不这样认为。如果使用得当，效能度量可以给公司的研发带来非常大的好处。

举一个真实的例子。国内一个大概 20 人的研发团队，研发流程混乱，产品发布经常推迟，但是大家都不清楚问题出在哪儿。于是，团队负责人决定引入数据驱动开发：项目经理正式跟踪研发过程中每部分的耗时，并在功能发布后复盘。复盘时大家发现，整个研发过程耗时分布如下：

开发耗时 1 周；

联调耗时 1 周；

测试、发布耗时 1 周。

大家一致认为联调耗时一周，是最需要优化的地方。于是对联调部分进行深入讨论，发现根本原因在于前后端沟通不顺畅：常常出现后端改动 API，但前端不知情的情况，这是耗时最主要的原因。

为解决这个问题，团队最终引入了一个 Mock Service，并规定：在每个功能开发之前，前后端要规定好 API 格式，并由后端产生一个 Mock Service，让前端从一开始就有明确的 API 可以调用。后端如果要改变 API 格式，必须通知整个团队，并立即更新这个 Mock Service。这就最大程度地避免了沟通不畅造成的时间浪费。

两个月以后，这个团队的平均开发周期有了明显改善，分布如下：

定义和生成 Mock Service 耗时 1 天；

开发耗时 1 周；

联调耗时 1 天；

测试、发布耗时 3 天。

可以看到，虽然定义和生成 Mock Service 需要额外的一天，但是联调周期缩短了 4 天，测试、发布周期也缩短了 2 天。所以，整个开发周期从 3 周降到 2 周，效果显著。

在这个成功使用度量的例子中，该团队主要做对了以下三点：

从全局的角度考虑度量指标，度量产品生产周期中各个阶段的数据，而不是直接查看某个竖井；

使用度量来寻找问题而不是用来做绩效考评；

使用度量来检验改进措施的效果。

从我的经验看，成功使用度量的关键在于：首先要对度量的分类有一个比较系统的了解，然后根据效能度量的特点，以及自己团队的目标来选取度量指标和方法。

效能度量的指标分类

在我看来，要真正发挥度量的作用，找到合适的度量指标，必须先对指标进行分类。我推荐从团队和个人这两个维度对度量指标进行分类，其中团队维度中又分为速度、准确度和质量 3 类，所以一共是 4 类。

速度：天下武功，唯快不破，速度指标主要用来衡量团队研发产品的速率。比如，前置时间，从任务产生到交付的时长。

准确度：关注产品是否跟计划吻合，跟用户需求吻合，能否提供较大的用户价值。一个例子是功能的采纳率，也就是有百分之多少的用户使用了功能 x。

质量：如果质量有问题，产品的商业价值会被大打折扣。质量包括产品的性能、功能、可靠性、安全等方面。

个人效能：个人开发过程中的效率指标，比如开发环境生成速度、本地构建速度等。

根据经验，我总结了一张导图，展示了这 4 个方面的度量指标，供你参考。可以看到，我一共给出了 40 多个指标，从名字上可以看出它们大概要度量什么。

在后面的度量指标推荐时，我也会与你讨论其中几个指标。如果你不清楚哪条指标的具体含义，可以自行查阅，或者留言给我。另外，你现在只需要对这些指标有个整体概念，用到时再回过头深入研究，选出适合自己的就可以了。

图 1 研发效能度量指标分类

接下来，我就直接和你推荐度量效能的一个原则和四个使用方法。

效能度量的原则

效能度量不与绩效挂钩，是正确使用效能度量最重要的一点，再怎么强调也不为过。所以，我向你推荐的效能度量的原则就是：效能度量不要与绩效挂钩，而应该作为参考和工具，帮助团队提高效能。

因为无法覆盖 100% 的度量指标，把度量与绩效挂钩就一定会产生“做数字”的现象。这时，使用效能度量非但起不到正面效果，还会对公司和团队造成伤害。管理者常常倾向于使用度量与绩效挂钩这种方法，是因为它能具体到数字方便管理。这种错误，我们一定要注意避免。

如果你只能记得这篇文章的一句话，那我希望这句话是“效能度量不要与绩效挂钩”。

Facebook 就刻意避免把效能度量跟绩效挂钩。比如，代码审核相关的效能度量页面，并没有包括审核的提交个数、代码审核的时长、代码审核通过率这些常见指标。

如果有团队提出要获取这些数据，那么我们工具团队只会提供一些脚本，由团队主管自己去获取所需数据，而且获取的方式并不那么方便。同时，团队成员也不能在 Phabricator 网站上直接看到这些数据。

这是一个比较典型的主动避免度量，从而避免它与绩效挂钩的例子。

不能把效能度量与绩效挂钩，那怎样才能使用度量提高效率呢？答案是：提供度量作参考和工具，帮助团队提高效能。

度量一旦与绩效脱离关系，就可以作为重要参考和工具，帮助团队持续进步。比如，下面这些度量指标:

缺陷密度，可以让团队了解产品质量的走向。

新旧 Bug 占比，一定程度上可以反映技术债的严重程度。

即使是代码行数这样臭名昭著的度量指标，如果只是用作参考，都可以帮助团队和成员提高。

在 Facebook，有超过 4 套的数据展示面板工具。这些面板工具使用起来非常灵活，开发人员可以定制面板，展示对自己有价值的效率度量，比如上线前高优先级 Bug 数、未完成 Bug 数、燃尽图等。我们每个团队都是主动使用这些面板，来帮助团队达成业务目标。

下面，我就来向你推荐 4 个度量使用方法。

效能度量的推荐方法

第一，目标驱动，度量对的事

提供用户价值是公司存在的根本，因此与之相关的指标是最最重要的。这一点非常好理解。从这个角度来看，以下几个相关度量指标比较有效：

净推荐值 (Net Promoter Score，NPS)，是通过调研了解用户满意度，实用性很强。如果你不了解的话，可以看一下这篇文章对 NPS 的介绍。

系统 /App 宕机时间 (System/App Downtime) 和严重线上事故数 (Incidents)，衡量的是系统的可用性，通常与用户价值直接挂钩。

热修复上线时间 (Hotfix Time)，指的是一个热修复从编码完成到部署到生产的时长，关系到解决重大缺陷的效率，与用户价值强相关。

我的建议是，你可以再去看看准确度的其他衡量指标，根据团队情况，找到能够最直接衡量产出有效性的指标，而不是一定要采用上面这 3 个指标。

第二，先从全局上找瓶颈，再深入细节

前面提到过，在度量效能时，很多团队往往是一上来就不加辨别地扎到某几个竖井里去寻找问题。这样的局部优化往往对全局优化无效，还会影响团队之间的关系，带来负面效果。正确的做法应该是，先检查全局，找到关键瓶颈之后，再进入细节分析和解决的环节。

具体实现起来，方法也很简单，就是收集产品周期中每一个阶段所占用的时间，包括计划的时间和最后实际花费的时间，然后寻找问题最大的地方。

具体的耗时收集方法，大致有两种：

人工收集。比如，文章开头提到的项目经理手工收集研发过程中每环节的耗时。

通过工具收集。比如，Trello 的任务显示看板，或者 Jira 的看板，都可以清楚地看到每个环节有多少任务以及流动情况，从而直观地识别瓶颈。

收集到了每环节的耗时之后，下一步就是去发现瓶颈。除了直观的观察之外，我推荐一个工具：累积流程图（Cumulative Flow Diagram)。具体方法是：横轴是日期，纵轴是每天统计的各节点任务数量，绘制出来就形成了累积流程图。比如，我们统计待办（Backlog）、开发（Dev）、测试（Test）、生产（Production）这几个节点，累积流程图如下所示：

图 2 累积流程图示意图

从水平方向看，待办和生产这两条线的距离就是交付时间（Cycle Time），也就是从开始开发到交付的时长。垂直方向的距离代表 WIP，也就是系统中的任务数。通过这张图，我们就可以一目了然地看到任务在流程中的流动情况，并直观地发现问题。比如，WIP 数值变大大、交付时间变长通常都代表研发效能下降。

可以看到，累积流程图对于查找全局瓶颈非常有用。实际上，我们还可以通过它预估发布日期，查看开发是否被阻塞等。这里有一篇文章，讲述了如何通过累积流程图，找到问题并进行调整，供你参考。

第三，通过主观的方式来评价、提高效能

没有客观的方法去衡量开发人员的生产效率，并不意味着你无法衡量它。 一个办法是，你可以尽量公平地去主观地测量它。事实上，平时工作中我们也确实是这么做的。

比如，在一个团队里面，大家通常都能对谁是技术大牛达成共识。这是我们的大脑依据平日收集的点滴事实做出的判断，也是因为当前还没有 AI 系统能够自动做出这样的分析，才显得非常主观。

所以，我推荐收集人工反馈的办法，来帮助我们做出尽量公平的主观评价。

针对研发环境、流程、工具的效能进行评价，可以使用公司成员对研发效能满意度的净推荐值。虽然现在还没有很强的理论依据可以证明，这个指标可以大幅提高研发效率，但从我看到的许多真实案例来说，事实确实如此：满意度让员工工作更积极，而工作积极又能提高满意度，是一个良性循环。

我在 Facebook 内部工具团队工作时，我们每个季度都通过调查问卷收集开发人员的建议，另外，我们还有 IRC 聊天室类似的工具供讨论和吐槽。这些反馈，都会成为工具团队调整工作内容和优先级的依据。

至于调查问卷的内容，具体来说可以关注以下这几个方面：冲刺的准备充分度、团队沟通有效性、冲刺过程效率、交付价值如何、交付信心如何、对产品方向及路线的兴奋度等。

针对个人研发效能作评价，可以采用类似 360 度绩效考评的方式来收集同事之间的评价。评价的标准基于在用户价值输出上做出的贡献，包括自身产生的价值，以及帮助团队成员产生的用户价值。如果一个员工可以很好地产出用户价值，那他的研发效率通常不会差。其实，Facebook 就是使用这种方式来评价员工效能的，虽然主观但很公正。

我整理了一张表格，列了一些可以用来收集反馈的问题，涵盖开发效率、质量、团队贡献等方面。

图 3 可以用来收集反馈的问题

第四，关注个人维度的指标提高效能

个人效能相关的度量，直接反映开发人员的开发效率和满意度，对团队产出影响很大。所以，作为管理者 / 内部效能团队，应该关注开发人员的高频活动，并自动化和优化这些步骤，让开发人员能专注开发。

一般来说，“个人调测环境构建速度”是一个比较重要的指标。它描述的是开发人员在本地做好一个改动，到能够进行本地调测的时长。开发人员每次修改自行验证都要经历这个步骤，对它进行优化非常有用。

我以前在 Facebook 的时候，后端代码及网站的绝大部分修改都可以在一分钟之内在本地开发机器上使用线上数据进行验证，非常爽快，效率极高。

但是，我曾经在其他公司见到过这样一种情况：一个修改需要在本地编码，上传到服务器编译，再通过工具下载到另外一个机器上验证。这个过程至少需要一个小时，在这种情况下，即使是在验证时发现一个简单错误，修改后简单验证也需要再花费一个小时。

不难想象这种情况下开发者的沮丧心情。如果能解决个人效能维度上的痛点，必然对提高产出和士气有重大作用。

小结

好了，这就是我今天要和你分享的效能度量的指标和方法了。接下来，我与你总结下今天的核心知识点。

首先，我将度量指标分为了准确度、速度、质量和个人效能 4 个方面，并列举了 40 多个具体指标。然后，我根据软件研发以及效能度量的特点，给出了 1 个原则和 4 种建议的度量方法。原则是不要与绩效挂钩；度量方法包括：目标驱动，度量对的事；先从全局上找瓶颈，再深入细节；通过主观的方式来评价、提高效能；关注个人维度的指标提高效能。

我把这 4 种方法，以及基本思路总结成了一张表格，方便你理解与记忆。

图 4 4 种推荐的度量方法

研发效能的度量很灵活，也很容易踩坑。所以，我希望上面的这些原则和方法能够作为你实施度量的参考，从而达到以下几个目的：

跟踪团队的表现、提高团队的绩效；

提高项目计划的精确度；

了解流程是否高效，寻找需要改进的关键领域。

最后，我来分享一下我个人对效能度量的两大感受：

度量只是工具，不是目的。切记度量的真正的目标是提高效能，不要舍本逐末。比如说，如果度量花费的时间超过了收益，那就不要去做。

虽然我们推崇数字驱动，但在效能的度量上，不要迷信数字，适当使用主观反馈效果反而更好。

思考题

你能从下面这张累积流程图中，看出什么问题吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

04 | 流程优化：怎样才能让敏捷、精益真正为我所用？

你好，我是葛俊。今天我们来聊聊怎样从流程方面来提高研发效能。

从这一篇文章开始，我们就正式进入研发流程模块了。在第 1 篇文章中，我与你强调了软件开发的最大特点在于它是一条非常灵活的流水线，因此提高研发效能的第一步就是优化流程。

图 1 提高研发效能的第一步就是优化流程

因为优化流程会涉及方法论，所以我会先和你介绍高效实践方法论的关键要素。然后，我会按照目标、原则和实践 3 个层次，从抽象到具体，给你逐步讲解如何优化流程。

需要说明的是，在这一篇文章中，我不会与你大量讨论实践细节，而是把这些内容放在了后续的文章中。

如何实践方法论？

其实，在研发流程上，最不缺的就是方法论，从敏捷到精益再到看板，层出不穷。但，实施效果却不理想。尤其是敏捷，无论在硅谷还是在国内，绝大部分使用敏捷的团队实施得都不理想，导致这个概念的争议很大，Scrum 有时甚至成了贬义词。

相比之下，像 Facebook、Google 等高效能公司，并没有强调使用 Scrum、看板等工具，研发效能却很高。这是不是说敏捷、精益这些方法论本身就有问题呢？

事实上，虽然 Facebook、Google 这些公司没有明确提及敏捷、精益、看板这些方法论，或者没有严格地去使用 Scrum 等框架，但在开发流程中却实实在在地应用了这些方法论的精髓。所以说，方法论实施效果不好，关键在于没用好。

在学习方法论的时候，我推荐使用类似美国著名作家、企业顾问西蒙斯 · 涅克（Simon Sinek）总结的 Why-How-What 黄金圈法则。

参见下图：最中间的一个圆是 Why，也就是这个方法论的目标，是要解决一个什么问题；第二个圆是 How，也就是这个方法论的基本原则、指导思想；最外层的圆是 What，也就是这个方法论的具体实践。

图 2 Why-How-What 黄金圈法则

这 3 个圆圈，从内向外，是一个从抽象到具体，从通用到定制的过程。

在使用一个方法论的时候，一定要从内往外看。

中心的目标一般错不了。比如，敏捷的目标就是快速应对变化。

原则的通用性就差一些，你需要在理解的基础上挑选适合自己的。比如，敏捷中有一条原则是“面对面交谈是最好的沟通方式”，就不一定适合你的团队。

具体的实践，就更要小心，切忌生搬硬套。

所以，我们必须首先深入理解这个方法论的目标和基本原则，然后根据原则因地制宜地选择具体实践。在选择具体实践时，往往需要在已有实践上做些修改才能达到效果，否则事倍功半。

以最容易出现问题的 Scrum 为例。敏捷的目标是快速应对变化，而 Scrum 就是用来服务这个目标的。但是，很多团队在使用的时候，严格照搬 Scrum 的具体方法，而“严格照搬”本身就已经违背了敏捷的目标。

相比起来，Facebook 的众多团队，“严格”使用 Scrum 的很少，却一直在大力优化管理、开发等流程来快速应对变化，最快找到并满足用户的最新需求。具体来说，他们很早就引入了 A/B 测试、灰度发布、每周定时全量代码部署等实践。这些都是和敏捷方法论相吻合的，也是 Facebook 业务成功的关键技术支撑。

在引入实践的时候，我的建议是逐步优化已有的开发流程和框架，甚至只给出原则，让团队成员逐步摸索并最终找到合适的方法。比如，你可以把一个核心原则通知给团队：“流程中总是有一个核心瓶颈。找到它，并解决它”。通过这样一个基本原则的指导，让具体实践逐步形成，对现有工作的影响，以及受到的阻力都会比较小。

了解了怎样使用方法论，接下来我和你一起看一组提高研发流程的目标、原则和实践。其实，这些并不是一套全新的方法论，而是基于 Facebook、Google 等高效能公司的实践，并结合对精益、看板、敏捷等方法论的思考，我得出来的一个总结。

目标一：寻找用户价值

以终为始地来看，我们最终目的是产生用户价值，生存下去。为此，我们经常需要主动去寻找最好的产品形态。只有方向找准了，流程产生的结果才能有效，才能产生用户价值。所以，优化研发流程的第一步，就是提高寻找用户价值的效率。

在这一点上，精益创业（Lean Startup）系统最有效。虽然名字里面有创业二字，也有较多商业模式设计和用户开发方面的内容，但它有两条原则值得作为开发流程的参考。

第一条原则是：衡量每一个时间段成果的标准，应该是价值假设方面的进展。也就是说，你的工作应该让你学习到如何更好地给用户提供价值，而不是开发了多少功能。

举一个极端的例子。比如，你的团队在一月份开发了 5 个功能，用户反馈都一般。这时，你看不出用户更喜欢什么东西，更讨厌什么东西。而在二月份，你们只开发了一个功能，预计很有用，但收到的用户反馈却是负面的，被迫连夜回退了这个功能。

那么，哪个月的成果更好呢？显然是二月份，因为它能明确告诉你，这个假设是错的，让你们在寻找产品符合市场吻合度（Product-Market-Fit）上前进了一大步。

第二条原则是：使用最小可行性产品（Minium Viable Product，MVP）来帮助学习。这里的关键点是，要以探索价值为出发点设计产品，最快地验证你的假设，功能要尽量少，能够使用就可以。具体的方法有数据驱动、A/B 测试、灰度发布等。

在 Facebook 工作的时候，我们开发一个功能时，都会提前计划要验证哪些假设，然后设计怎样收集数据才能够验证这些假设。这样一来，功能上线的时候就开始收集数据了，一旦发现功能不能提供足够的用户价值，就把这个功能下线，从而确保每个功能都是本着提供用户价值的目的去的。

同时，公司也会不断投资 A/B 测试等试验框架，让开发人员能够尽量容易地收集和处理数据。这种开发方式有一个名字，叫作度量驱动开发（Metrics Driven Development）。如果你想深入了解这种开发方式，可以参考 Uber 公司的这篇文章。

目标二：提高用户价值的流动效率

软件研发是一条流水线，里面流动的是一个一个给用户提供价值的功能。那怎么提高这条流水线的效率呢？也就是，如何提高用户价值的流动效率。

这里有 4 条比较直观的基本原则：

第一，让功能尽快地流动；

第二，让节点之间的联动更加顺畅；

第三，节点之间的融合；

第四，发现整个流程中的瓶颈，并解决它们。

接下来，我们分别看看这四条基本原则。

第一，让功能尽快地流动，说白了就是快速开发。问题发现得越晚，修复代价越高，这已经是常识。要做到快速开发，开发人员需要尽量把功能拆分，同时做好一个提交之后尽快提交。要做到这一点，关键原则是降低提交的交易成本（Transaction Cost）。

提交的交易成本指的是，每一个提交都需要做的额外工作。只有把这个工作量降下来，才能让功能拆分有价值，否则就会抵消掉拆分带来的好处。

具体工程实践包括提高本地构建速度，提供方便的自测环境、测试自动化、持续集成、定位问题提交自动化等。快速开发这个话题是开发高效的关键因素，我会在下一篇文章中与你详细讲述。

第二，让节点之间的联动更加顺畅，可以通过对关键流程的自动化、工具之间的网状互联，以及节点之间的融合来实现。在具体实践上，个人代码上线后，在和他人的代码集成时容易出现问题，这时就可以使用 CI/CD（持续集成 / 持续交付）流水线来自动化代码集成过程。

尤其是 CI，基本上对所有软件开发公司都很有价值。我们一定要尽量用上 CI。

另外，有些公司有从需求到开发到上线的一条龙流水线，也被叫作开发者桌面。国内的公司，尤其是大公司很喜欢这种方式。它的好处是，可以把很多底层的东西隐藏起来，容易上手。但它的缺点就是灵活性不够，毕竟难以用一条流水线做到高度的定制，来满足各种各样的高效开发。

开发者桌面的全景图如下所示。开发者桌面作为核心节点，连接其他所有工具，各个工具之间也有一些连接。

图 3 开发者桌面的全景示意图

在 Facebook 和 Google，并没有一个端到端的流水线，它们采取的方式是：让这些流程中使用的工具通过开放的 API 进行一个网状连接，从而开发者可以灵活定义小范围的流程，高效工作。在这个网状结构里面，关键节点会作为重要节点大量连接其他流程中的工具，公司对这些关键的、小范围的流程进行大量优化。

比如下面这张图片，一共有 11 个工具，互相之间连接很多，形成网状结构。其中工具 4 和工具 9 是关键节点，与很多工具有连接。

图 4 工具网状连接示意图

比如在 Facebook，代码审查工具 Phabricator 是一个关键节点，可以支持代码审查、代码静态检查、单元测试、集成测试、前后端联调等关键开发工作。这一组工作就是上面提到的一个关键的、小范围的流程。

最近几年由于 IDE 的发展，Nuclide 成为了一个新的重要节点。开发者可以在 Nuclide 里直接进行高效的代码开发，以及代码审查等一系列工作。类似地，Google 的 WebIDE 也是工具链的一个重要节点。

从我个人的经验看，这种网状结构提供的灵活性，对提高开发效率和提升开发体验，都有积极的促进作用。

第三，节点间的融合，也是为了保证节点间的联动顺畅。也就是模糊节点间的边界，让功能在节点之间的流动更顺畅。在这一方面，我见到比较有效的方式是：职能团队提供平台和工具，让全栈工程师能够自己处理端到端的工作。比如，测试团队提供测试平台和工具，运维团队提供运维平台和工具，这些平台和工具可以通过服务化自助使用。

比如，在 Facebook 没有专职的测试人员，只有测试工具团队，运维人员也很少，主要是提供大量的工具让开发人员能够自己进行测试、运维的基本工作，实现开发人员在“开发、测试、运维”这几个步骤上的全栈工作。因为开发人员对自己开发的功能最为熟悉，所以这种端到端的全栈工作方式，可以让开发和调测效率达到最高。在国内，我看到阿里云效也在推荐这种方式。

第四，发现整个流程中的瓶颈，并解决它们。约束理论创始人艾利 · 高德拉特（Eliyahu Moshe Goldratt）在 20 世纪 90 年代，提出了解决约束的聚焦五步法，至今仍然适用软件研发这个价值流体系。具体步骤如下：

第一步，找到系统中的瓶颈；

第二步，最大限度地利用（Exploit）瓶颈，尽量通过提高效能的办法解决瓶颈；

第三步，让企业的所有其他活动都让步于瓶颈改善工作；

第四步，打破（Elevate）瓶颈，如果第二、三步无效，就通过给瓶颈节点增加资源的方法来解决瓶颈；

第五步，重返（Repeat）第一步，找出新的瓶颈，持续改善。

具体的实践有可视化和复盘。

在可视化方面，粘上便利贴的白板，或是像 Trello 那样的电子看板就是很好的工具。

图 5 可视化实践示意图

这里我需要强调一下，我们一般把这种任务卡片化的系统叫做看板，但它并不是真正的“看板”，只是一个任务可视化的面板而已。

真正的“看板”是信号卡片，用来表示流水线系统可以增加新的工作项了。后面我会详细介绍。通过任务可视化，我们可以直观地看到哪一个环节的任务特别多，卡住了，也就是瓶颈在哪儿。另外，前面文章提过的累积流程图，也是发现问题的一个有效工具。

另外，统计图表和仪表板也是很不错的工具。在 Facebook，任务管理系统、部署系统、代码审查系统的 API 是开放的，各个团队可以方便地查询数据制作图表。在仪表板方面，2014 年的时候就有 4 个系统可使用。大部分的统计图表和仪表板，都是非工具团队自己开发出来并最终推广全公司使用，定制功能也很强。

在复盘方面，Facebook 做得也很好。他们有一个 SEV 复盘系统，用来记录公司发生的重要事故进行复盘。每个团队也会不定期地复盘，以定位瓶颈问题并提高。

举一个复盘方法的例子。我在国内某公司遇到一个问题，就是团队在协作上配合不积极，出了问题不是先去解决问题，而是先想方设法甩锅。为解决这个问题，我引入了线上事故回溯讨论会，每两周一次，对发生的事故进行讨论，重在根因分析和以后如何避免，并事前强调目的不是追责。因为，每个故障分析都能暴露出藏在深处的问题，对提高产品质量和团队间的信任效果都很好。

小结

优化流程，是提高研发效能的第一步。我们应该按照目标、原则和具体实践的顺序学习和使用各种方法论，选择和配置最适合自己团队的工程实践。

我基于 Facebook、Google 等高效能公司的实践，并结合对精益、看板、敏捷等方法论的思考，给你介绍了“寻找用户价值”和“提高用户价值的流动效率”两条目标，以及它们对应的 6 条原则和若干具体实践。

从我的经验来看，高效的研发工作流，对产品质量和研发速度至关重要，同时可以提升员工的幸福感，进而保证持续的高效产出，提高研发效能。

Facebook 在流程方面的实践，给我最大的一个感受就是，以实用主义的态度，从原则出发，灵活优化流程。在 Facebook 的几年时间里，我并没有听到很多新方法论的时髦术语，但是公司的很多实践却和这些方法论的原则是一致的，甚至是超前这些方法论的。

比如，在 DevOps 流行前的很多年，Facebook 就开始了打通开发和部署的工作，以及推行全栈工程师的工作方式。

从实际出发、以终为始的实用主义，是我从 Facebook 学到的高效研发的最重要原则，没有之一。

思考题

你理解的精益看板，跟文中提到的任务可视化白板一样吗？你知道它们之间的关系吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

05 | 代码入库前：Facebook如何让开发人员聚焦于开发？

你好，我是葛俊。今天，我将与你分享优化流程中，代码入库前的开发流程。

代码入库之前的开发活动，主要包括编码、调测调优、静态检查、自动化测试、代码审查等。这是开发者编写代码的步骤，自然是提高研发效能的关键环节。

图 1 本地开发流水线

提高开发者编写代码的效能，关键在于让开发者不受阻塞、不受不必要的干扰，从而全身心地聚焦在产品开发上。我把这种不受阻塞的开发状态叫作持续开发。

一个团队如果能够做到持续开发，那么它的有效产出自然会很好。而对于个人开发者而言，持续开发能够帮助我们把精力集中在技术本身，对技术和个人能力的提升都大有裨益，所以是一种很好的开发体验。

在我看来，持续开发的基本原则主要包括两条：

规范化、自动化核心步骤；

快速反馈，增量开发。

接下来，我们就一起看看这两条核心原则吧。

规范化、自动化核心步骤

要让开发者聚焦于开发，就必须把研发流程中可以自动化的步骤尽量自动化。因为一般不可能完成所有步骤的自动化，所以我推荐的方式是：分析关键路径上的活动，以及耗时较长的活动，然后投入精力优化这些步骤。

首先，我们需要明确具体的开发步骤有哪些。我将其归纳为以下三大步：

获取开发环境，包括获取开发机器、配置环境、获取代码等。

在本地开发机器上进行开发，包括本地的编码、调测、单元测试等。

代码入库前，把改动提交到检查中心（比如 Gerrit），再进行一轮系统检查，主要包括代码检查、单元测试、代码审查等，通过之后再入库。

图 2 代码入库前的 3 大开发步骤

针对这三大步骤，我们可以有以下 3 个工程实践：

提高开发环境的获取效率；

规范化、自动化本地检查；

建设并自动化代码入库前的检查流程。

接下来，我们分别看看这 3 个工程实践如何落地。

提高开发环境的获取效率

开发环境的设置，包括开发机器的获取、网络配置、基本工具以及代码的获取和配置。这些操作的频率不是特别高，但如果步骤复杂、耗时长，就会对新成员加入、成员切换团队或者项目，产生比较大的影响。所以，开发环境的获取效率，通常是值得优化的。

有一个可以采用的优化方式是，把整个开发环境的获取，进行服务化、自助化。也就是说，开发者可以自助地申请获取环境，不需要 IT 部门的人员介入，从而既节省了开发者的时间，又降低了 IT 部门的人力成本。

比如，我之前在 Facebook 工作的时候，采用虚拟机作为个人开发机。内部工具团队开发了一个基于共享机器池的开发环境服务系统，让开发者可以在网页上申请和释放机器。机器返还之后，开发环境服务系统会自动对它进行清理，配置之后再重新放回机器池中。这就使得开发者可以在 5 分钟之内拿到一套干净的环境。

而至于开发机器上的代码，这个服务系统可以克隆获取团队常用的代码仓，并定时拉取最新的代码。这就使得开发者拿到一台机器之后，只需要再额外拉取很少的代码就可以进行开发。

上面这种方法定制性很强，Facebook 并没有开源。如果你准备进行这方面尝试的话，在机器的生成和配置方面，我推荐两种方式。

第一种方式，借助基础设施即代码（Infrastructure As Code，IaC）系统。比如，HashiCorp 公司的Terraform 工具。它支持声明式的方式快速产生自定义配置的机器，并在上面运行脚本进行配置。TerraForm 使用插件机制支持许多底层平台，比如 AWS、阿里云或者本地系统。

这种方式的优点是使用方便、功能强大，但前期投入大。

第二种方式是，提供机器镜像和配置脚本。通过镜像让每一台新机器拥有最基本的设置，比如 CPU、操作系统、基本软件，然后通过脚本实现基本配置，比如网络设置、软件更新等。这种方式的优点就是，前期投入小。我在 Stand 时，就使用了这种方法，效果不错。不过它的缺点就是不够灵活。

规范化、自动化化本地检查

本地检查是指，开发者在开发机器上进行的验证，比如语法检查、规范检查、单元测试、沙盒搭建等。我推荐的方式是，根据团队实际情况，找到合适的工具和配置进行这些检查，并让团队成员统一使用。

在这个方面，Facebook 的方法是，把很多工具都放到一个网盘上，挂载到每台开发机器的 Linux 文件系统上，让开发者们不用安装就可以直接使用。

挂载共享网盘的方法非常方便，因为用户不用操心工具的升级。但如果你们的系统没有这样的网盘的话，也可以通过脚本让开发人员一键安装工具和完成配置，效果也不错。缺点就是软件更新比较麻烦，因为要通知用户手动更新或者设计自动更新机制。

至于检查中使用的工具，我们需要根据具体的语言和框架去选择。

建设并自动化代码入库前的检查流程

建设并自动化代码入库前的检查流程，是持续集成前的必要工作，也可以看作是持续集成的一部分。它对入库代码质量起到一个门禁作用，对提高质量用处很大。我认为，除了人数非常少的初创公司以外，其他开发团队都应该进行这个配置。

这个流程一般可以使用代码仓管理系统作为中心，直接使用或者通过钩子集成其他工具和系统来实现。比如，使用 GitLab 提供的 GitLab CI/CD 框架。基本方法是，在项目的根目录里创建一个.gitlab-ci.yml 文件，来描述你的检查环境设置和步骤。你可以点击这个链接查看具体的方法。

在 Facebook，这一步使用的是开源版 Phabricator 的一个内部 Fork。Phabricator 在工作流中使用单元测试和 Linter 的方法，你可以参考帮助文档。

以上内容就是持续开发的第一个原则，也就是规范化、自动化核心步骤。这个原则，可以帮助开发者尽量减少非开发工作的耗时，从而把更多的时间、精力投入到本职的开发工作中。接下来，我们再来看看持续开发的第二个原则，即提供快速反馈，促进增量开发，这样能及早暴露问题，从而保证将来的工作不会因为实现错误，或者方向调整而进行昂贵的修改。

提供快速反馈，促进增量开发

提供快速反馈，进行增量开发指的是，能够快速验证已经完成的开发工作，说白了就是边开发边验证。具体的工程实践主要包括以下 3 个：

灵活使用各种 Linter 和测试；

建设并优化沙盒环境；

使用实时检查工具。

接下来，我们分别看看这 3 个工程实践如何落地。

灵活使用各种 Linter 和测试

最常用的快速验证方法就是，提高运行静态检查和测试的方便性、灵活性。各种语言、框架都有自己的测试框架和 Linter，这里我就不再一一列举了。接下来，我会与你分享两种通用的有效使用 Linter 和测试的方法。

首先，用命令行的工具来封装各种检查。命令行工具特别适用于自动化，方便开发人员使用。比如，我们可以通过命令行脚本，来实现简单的工作流。

举一个具体的例子，我希望团队在开发中，在运行公司提供的统一检查之外，还可以运行一些适应团队自身特点的检查，每个开发人员也可以添加自己希望使用的检查。这样就可以通过一个 Shell 脚本，依次调用公司的、团队的、个人的检查来实现，很方便。

其次，以服务化的方式，把这些检查的能力提供出来。比如，Facebook 的基础平台团队提供了在云上运行单元测试的能力，并把这个能力通过服务的方式提供给开发者，以方便他们在自己的开发机器上调用。也就是说，开发者可以调用云上资源运行大量的测试而不占用本地资源，从而在运行测试的同时可以高效地进行开发工作。

建设并优化沙盒环境

沙盒也是一个高频使用的、提高质量的工具。开发者如果能够方便地在本地搭建沙盒进行验证，那么进行开发自测的频率和质量就会大大提高，进而提高产品质量。所以，我推荐你在沙盒环境的搭建上进行投入。

在沙盒环境搭建中，有两个常见的优化点：

本地构建。因为我们必须把改动构建成产品才能进行本地验证，而这个步骤通常耗时较长。我推荐的优化方法是，不要使用全量构建，尽量只进行最小范围的增量构建。

测试数据的产生。产生贴近生产环境的数据往往比较费劲，Facebook 的做法是，开发环境直接使用生产环境的数据，不过这个方法比较激进，使用的公司比较少。另一个常见方法是，进行生产数据的导出并脱敏，然后使用到沙盒环境中。

使用实时检验工具

快速提供检查反馈，做到极致就是开发者无需手动触发检查，工具就会自动探测到改动、自动运行检查。最常见的是，IDE 中的实时语法检查。我们可以花一些时间来配置 IDE。另外，有些工具可以自动监视文件系统的变化，文件有变化时自动重启服务。这对于开发者来说，非常便利。

举个例子。使用 Node.js 进行开发时，nodemon 就是不可或缺的工具，你只要在原来的命令前，加上 nodemon 就可以使用。比如，启动服务的语句是./bin/www，使用 nodemon 的形式就是 nodemon ./bin/www。这样运行服务之后，如果你的文件有修改，nodemon 就会自动重新运行。

你可以在下面这个动图中看到，在第一次保存时，有语法错误，nondemon 重新启动失败；第二次保存时修复了语法错误，nodemon 成功重启服务。通过 nodemon，我减少了两次手动重启服务的繁琐操作。

图 3 nodemon 使用示例

类似的工具，SpringBoot 有 Spring-Boot-Devtools，你可以点击这个链接查看详细描述。针对 Python，可以直接使用 nodemon，这里也有一个链接供你参考。

如果你使用的框架、语言没有直接可以使用的工具，帮助你进行实时重启服务的话，可以使用类似watchdog/watchmedo的工具来实现自动化。比如

watchmedo shell-command \

 --patterns="*.py" \

 --command='python "${watch_src_path}"' \

 .

会监控所有的 Python 文件改动并自动重启。

提供快速反馈，边开发边验证，虽然只是一个简单的原则，但可以带来很多好处。最直接的收益就是，能够大大提高开发者对当前代码的信心，从而促进代码尽早入仓、尽早集成。

可能你也注意到了，代码集成越晚发现问题就越晚。这正是产品上线的最后关头合并混乱，产品质量差、返工率高的一个重要原因。所以，我建议在你的工作流程中，要尽量提高实时验证的能力。如果你这么做了，很快就会看到效果。

小结

在今天这篇文章中，我和你分享了两条持续开发的基本原则，来帮助开发者在代码入库前聚焦于开发工作：一是，规范化、自动化代码入库前的核心步骤；二是，提供快速反馈，帮助开发者边开发边验证，以促进增量开发。

我将今天的内容，总结为了一幅图，帮助你复习。

图 4 代码入库前流程优化

这些原则和实践，是我根据自己的经验总结出来的。如果能直接适用于你的团队当然最好了，但我更加希望的是，你能从这些原则和实践的讨论中，理解它们背后的思路，从而找到合适的方法和实践，去有优化代码入库前这个环节中，最需要优化的地方，让开发者能够真正聚焦于开发。

另外，我给你一个落地持续开发的小贴士：持续开发很适合用自上而下和自下而上相结合的方式来推动。因为开发者最了解自己工作的痛点，所以也能比较准确地找到需要优化的地方。在 Facebook，很多工具和流程都是由开发者自发开发或者引入，后来逐步推广至团队和公司使用的。

所以我推荐，作为开发者，你可以自己抽一点时间提高自己的工作流程，自动化繁琐的工作；而作为管理者，你可以有意识地奖励这样的优化行为，并对适用于团队的部分进行推广。

思考题

最后，我来给你留下两个思考题吧。

在开发环境方面，你有没有尝试过在 Docker 里面进行开发？你觉得这种方式的好处是什么，弊端又是什么呢？

有些开发者喜欢写好一个比较大的功能单元，然后再一口气调测。你觉得这样做的好处和坏处，各是什么呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

06 | 代码入库到产品上线：Facebook如何使用CI/CD满足业务要求？

你好，我是葛俊。

在上一篇文章中，我和你分享了代码入库前的流程优化，即持续开发。今天，我会继续与你介绍流程优化中，代码入库和入库后的 3 种持续工程方法，即持续集成（Continuous Integration, CI)、持续交付（Continuous Delivery, CD）和持续部署（Continuous Deployment, CD)。

在接下来的分享中，首先我会与你介绍这 3 种方法的定义和作用，帮助你深入理解这 3 个“持续”背后的原理和原则；然后我会以 Facebook 为参考，给你介绍基于这些原则的具体工程实践。我希望，这些内容能够帮助你用好这三个“持续”方法，提高团队的研发效能。

首先，我先来介绍一下，持续集成、持续交付和持续部署是用来解决什么问题的，也就是它们的定义和作用。

3 个“持续”的定义和作用

不知道你是否还记得，在开篇词中，我提到过一个低效能的情况，即产品发布上线时出现大量提交、合并，导致最后时刻出现很多问题。这个情况很常见，引起了很多用户的共鸣。产生这个问题最主要原因是，代码合并太晚。这里，我再与你详细解释一下原因。

当多个人同时开发一款产品的时候，很可能会出现代码冲突。而解决冲突，需要花费较多的时间；同时很可能出现冲突解决失败，导致产品问题。

如果没有一个机制督促我们尽早把代码推到主仓进行集成的话，我们通常会尽量先在自己的分支上进行开发。结果往往是，在冲刺快要结束，或者功能即将发布时，才出现大量的代码合并。

而这时因为很长时间没有进行过代码集成了，进行集成的代码量通常比较大，不同开发者的代码区别很大，冲突也很严重，难以解决。具体的负面影响是：发布推迟，产品质量不高，每一次发布时的熬夜和紧张影响团队士气。

而持续集成的根本出发点，就是为了解决这个问题。也就是说，它能够帮助开发人员尽量早、尽量频繁地把自己的改动推送到共享的代码仓库分支上，进行代码集成，从而减少大量代码冲突造成的低效能问题。

所以，持续集成的定义就是：在团队协作中，一天内多次将所有开发人员的代码合并入同一条主干。

代码入库后，剩下工作是把代码编译打包成可以发布的形式，先发布到测试环境进行测试，再发布到类生产环境进行测试，最终部署到生产环境。

在这个过程中，有两个问题需要特别注意。

首先，我们需要这个流程尽量频繁。如果我们能够把产品和功能尽快地发布到市场上，就能够更快地服务客户，以更快的试错速度寻找到用户，提供真正对客户有价值的功能。
即使你的产品由于自身特性不会太频繁地部署给用户，但这种能够频繁生产出可以马上部署的产品的能力，也能让你在需要部署时，快速完成任务。

其次，在产品发布到不同环境的过程中，我们会发现一些在开发和持续集成中没有暴露的问题。如果在产品要正式发布时才发现这些问题，就会造成产品交付推迟，影响线上用户等情况，造成损失。针对这个情况，我们需要提前发现问题。

而解决这两个问题，正是持续交付和持续部署的出发点。

持续交付的目标是，对每一个进入主干分支的代码提交，构建打包成为可以发布的产品。它的定义是：一种软件工程方法，在短周期内完成软件产品，以保证软件保持在随时可以发布的状态。也就是说，对每一个提交，把集成后的代码部署到“类生产环境”中进行验证。如果代码没有问题，后续可以手动部署到生产环境中。

而持续部署，则更进一步。它把持续交付产生的产品立即自动部署给用户，定义就是：将每一个代码提交，都构建出产品直接部署给用户使用。

以上就是持续集成、持续交付与持续部署的作用和定义。在实现上，它们共同的本质是，让每一个变更都经过一条自动化的检验流水线，来检查每一个变更的质量，通过就进入下一个阶段。

这里的“下一个阶段”具体包括：代码并入主仓、产品进入测试环境、产品进入类生产环境、产品最终进入生产环境，如下图所示。

图 1 CI/CD 流水线示意图

你应该已经注意到了，整条流水线中，持续部署只是持续交付的最后一步，也就是自动化上线的那一步，前面的各种检查，都属于持续交付流水线。所以，我在后面的内容中再提到流水线时，CI/CD 指的就是“持续集成 / 持续交付”。

CI/CD 流水线，能够大大提高代码入库的速度和质量，是这几年硅谷互联网公司做到高效研发的必要条件。接下来，我就与你介绍 CI/CD 流水线的具体原则以及最佳实践，然后以 Facebook 的具体实践为例帮助你加深理解。

需要注意的是，在这篇文章中，我会重点与你分享 CI/CD 流水线的搭建原则。而关于具体的搭建方式，通常是持续集成工具 + 代码仓管理系统 + 检查工具 + 测试工具，比如 Jenkins+GitLab+SonarQube+Linter+UnitTest 的组合。你可以参考这个链接提供的方式去搭建。

CI/CD 流水线的具体原则以及最佳实践

根据上面提到的 3 个“持续”的本质，要做到高效，有 3 条基本原则：

流水线的测试要尽量完整；

流水线的运行速度一定要快；

流水线使用的环境，尽量和生产环境一致。

基本原则 1：流水线的测试要尽量完整

CI/CD 流水线的测试只有尽量完整，代码和产品的质量才能有保证。所以，最主要的工程实践，就是在流水线中运行大量高质量的测试和检查。

Facebook 就有大量的单元测试和集成测试用例、安全扫描，以及性能专项测试用例。如果某个验证在流水线中失败，开发人员会考虑是否要添加测试用例来防止再出现类似的问题。

另外，Facebook 持续在测试用例的开发上投入。在内部工具团队，有一个专门的测试工具团队来建设测试框架和测试流程，方便开发人员自己开发测试用例。比如，我在 Facebook 那几年，他们就一直在改进 JavaScript 的 Mock 框架，对开发人员写测试用例来说非常方便。

基本原则 2：流水线的运行速度一定要快

因为每一个变更都要通过 CI/CD 流水线的检验，所以流水线的速度关乎研发速度。而要提高这条流水线的速度，我们可以从以下两个方面考虑。

首先，从技术角度考虑。比如：

使用并行方式运行各种测试来提速；

投入硬件资源，使用水平扩展的方式来提速；

使用增量测试的方式进行精准验证。也就是说，只运行跟当前改动最相关的测试，以减少测试用例的运行数量。

其次，权衡流水线的运行速度、流水线资源和测试完整性的关系。不难理解，运行速度快、占用资源少、测试完整难以兼顾，因此我们必须做出权衡。这里我推荐几个方法：

如果通过增加硬件资源来提升运行速度需要的成本太高的话，可以对测试用例按优先级进行分类，每天运行流水线的时候，不用每次都运行所有测试用例，只选择其中几次进行全量测试。

提供支持，让开发人员在本地也能运行这些测试，从而使用本地资源尽早发现问题，这就避免了一些有问题的提交占用流水线的资源，进而提高整条流水线的运行速度。

运行测试的时候，按照一定的顺序运行测试用例。比如可以先运行速度快的用例，以及历史上容易发现问题的用例。这样可以尽早发现问题，避免耗费不必要的资源。

基本原则 3：流水线使用的环境，尽量和生产环境一致

这里的环境，包括机器环境、数据、软件包、网络环境等。环境不一致可能导致问题暴露在用户面前，损失严重；另外，在非生产环境上难以复现生产环境的问题，调试困难。

保证流水线环境与生产环境一致，具体方法包括：

软件包最好只构建一次，保证各种不同环境都用同一个包。如果不同的运行环境需要不同的参数，可以采用环境变量的方式把这些参数传递给软件包。

使用 Docker 镜像的方式，把发布的产品以及环境都打包进去，实现环境的一致性。在我看来，这正是 Docker 的最大好处。

尽量使用干净的环境。比如，测试时，使用刚从镜像产生的系统；又比如，使用蓝绿部署，每次产生新的部署时，直接丢弃旧的环境。

以上就是 CI/CD 流水线的 3 个基本原则和最佳实践。通过提高验证的完整性、速度，以及保证环境的一致性，我们可以降低成本，提高产品质量和验证产品价值假设的速度。

接下来，为了帮助你理解并正确运用这些原则和最佳实践，我们一起来看看 Facebook 是怎么做的。

具体案例：Facebook 是如何实施 CI/CD 来提高效能的？

Facebook 一直就非常注重 CI/CD，早在 2009 年就建设了顺畅的 CI/CD 流水线，而且一直在持续改进。

在 CI 方面，加强建设持续开发，让开发人员能在开发环境上进行大量的验证。本地的所有验证，与 CI 流水线上的验证方式保持一致，这就大大提高了开发人员在本地发现问题的能力，从而大量避免了有问题的代码提交到 CI 流水线，浪费资源。

在代码入库的步骤，采用 Phabricator 作为 CI 的驱动，并作为质量检查中枢，尽量提高入库前代码审查的流畅性。在这个过程中，Facebook 做到了以下几点：

测试的完整性。代码提交到 Phabricator 进行代码审查的同时，进行各种静态检查、单元测试、集成测试、安全测试，以及性能测试等。

工具的集成。Phabricator 提供的插件机制，可以跟其他系统和工具集成，以支持运行各种检查。

沙盒环境。代码在提交到 Phabricator 进行审查时，Phabricator 会自动产生一个沙盒环境。沙盒环境有两个好处：一是，可以让开发者之间进行联调；二是，可以让开发者并行地进行其他开发工作，因为在进行代码审查时，开发者的开发机器并没有被占用。

高效的代码审查。比如，代码审查不通过时，代码作者可以方便地在原来的提交之上进行修改，并在下一轮审查时只进行增量代码的审查。这就大大降低了每次代码审查的交易成本，从而保证了 CI 的顺畅性。

代码入库之后，进入持续交付步骤。Facebook 使用大仓，同一个仓中每天有几千个代码提交，所以持续交付的挑战很大。他们有一个专门的发布工具团队，自研了一套发布工具来实现自动化流水线，通过以下两点比较好地实现了流水线资源和测试完整性的平衡。

不针对每一个提交进行 CD 验证，而是按照一定时间间隔进行验证。因为提交太多，如果每个提交都进行构建打包，资源消耗实在太大，所以 Facebook 采用了按照一定时间间隔，比如，每 10 分钟进行一次构建打包。这就大大降低了资源的消耗，不过这里有个问题，在验证步骤发现 Bug 时，因为验证的是最近 10 分钟的所有提交，所以不能精准定位造成问题的提交。
针对这个问题，Facebook 使用单主干开发分支方式，并强制在代码合并时，只能使用 git rebase 不能产生合并提交，所以提交历史是线性的，从而可以使用 git bisect 命令来自动化定位问题。这部分内容我会在下一篇文章中详细介绍。

对验证进行分级。也就是说，有几条不同的 CD 流水线，按照不同的时间间隔运行构建和检验。根据运行时间间隔的不同，它们运行的检验数量以及检查出来的 Bug 优先级也不同。间隔时间越长，运行的检验越全面，检查出来的 Bug 优先级越高。

这里需要说明的是，2017 年以前，Facebook 并没有把每一个在主干分支上成功通过流水线验证的软件包作为发布候选，而是在每周五的固定时间，从主干分支上拉出一个发布分支，稳定 3 天后上线。也就是说，这并不是严格意义上的持续交付。这是因为当时的自动化检验还不能确保产品达到上线要求。其实，这对很多公司来说都很常见，都需要一些额外的测试和检验来确保上线产品的质量。

最后，是持续部署的操作。在 2017 年以前，Facebook 并没有持续部署，而是采用的每周全量代码部署的方式。但到 2017 年，因为代码提交实在太多，每次周部署代码，处理的提交量会超过 10000，需要很长时间才能稳定发布分支，所以 Facebook 转向了持续部署。

具体的方法是，极致地进行自动化测试验证。关于实施细节，你可以参考 Facebook 的第一个发布工程师 Chuck Rossi 对持续部署流程的描述。

值得一提的是，跟持续交付一样，Facebook 的持续部署也不是纯粹的持续部署。因为代码提交太多，他们并没有每个提交都单独部署，而是采用类似持续交付的方法，把一段时间之内的提交一起部署。这种不教条的方式，是我从 Facebook 学到的一个重要的做事方法。

小结

Facebook 在 CI/CD 上做到了极致，对每一个代码提交都高效地运行大量的测试、验证，并采用测试分层、定时运行等方式尽量降低资源消耗。正因为如此，他们能够让几千名开发人员共同使用一个大代码仓，并使用主干开发，产生高质量的产品，从而实现了超大研发团队协同下的高效能。

在前面几篇文章中，我们多次提到“持续”。这个词，近些年在软件研发中比较流行，比如我今天与你分享的持续集成、持续交付、持续部署，加上持续开发，一共有 4 个了。

实际上，在 CI/CD 流水线中，做为流水线的一部分，测试一直在运行并最快地给开发者提供反馈。这正是另一个“持续”，也就是“持续测试”的定义。

“持续”如此重要的原因是，软件开发是一个流程，只有让这个流程持续运转才能高效。这里我把这 5 个持续都列举出来，方便你复习、参考。

图 2 5 个“持续”方法定义与关键点对比

思考题

在几千名开发人员共同使用一个大代码仓的工作方式下，做好 CI 有很大的挑战性。你觉得挑战在哪里，容易出现什么样的问题，又应该怎么解决呢？

今天我提到了持续开发在 CI 中的作用，请你结合上一篇文章，思考一下持续开发和 CI/CD 是怎样互相促进的。

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

07 | 分支管理：Facebook的策略，适合我的团队吗？

你好，我是葛俊。今天，我来跟你聊聊研发过程中的 Git 代码分支管理和发布策略。

在前面两篇文章中，我们讨论了持续开发、持续集成和持续部署的整个上线流程。这条流水线针对的是分支，因此代码的分支管理便是基础。能否找到适合自己团队的分支管理策略，就是决定代码质量，以及发布顺畅的一个重要因素。

Facebook 有几千名开发人员同时工作在一个大代码仓，每天会有一两千个代码提交入仓 ，但仍能顺利地进行开发，并发布高质量的产品。平心而论，Facebook 的工程水平的确很高，与他们的分支管理息息相关。

所以在今天这篇文章中，我会先与你详细介绍 Facebook 的分支管理策略，以及背后的原因；然后，与你介绍其他的常见分支管理策略；最后，向你推荐如何选择适合自己的分支策略。

Facebook 的分支管理和发布策略

Facebook 的分支管理策略，是一种基于主干的开发方式，也叫作 Trunk-based。在这种方式中，用于开发的长期分支只有一个，而用于发布的分支可以有多个。

首先，我们先看看这个长期存在的开发分支。

开发分支

这个长期存在的开发分支，一般被叫作 trunk 或者 master。为方便讨论，我们统一称它为 master。也就是说，所有的开发人员基于 master 分支进行开发，提交也直接 push 到这个分支上。

在主干开发方式下，根据是否允许存在短期的功能分支（Feature Branch)，又分为两个子类别：主干开发有功能分支和主干开发无功能分支。Facebook 做得比较纯粹，在主代码仓中，基本上禁止功能分支。

另外，在代码合并回 master 的时候，又有 rebase 和 merge 两种选择。Facebook 选择的是 rebase（关于这样选择的原因，我会在后面与你详细介绍）。所以，Facebook 的整个开发模式非常简单，步骤大概如下。

第一步，获取最新代码。

git checkout master

git fetch

git rebase origin/master

第二步，本地开发，然后执行

git add

git commit

产生本地提交。

第三步，推送到主代码仓的 master 分支。

git fetch

git rebase origin/master

git push

在 rebase 的时候，如果有冲突就先解决冲突，然后使用

git add

git commit

更新自己的提交，最后重复步骤 3，也就是重新尝试推送代码到主代码仓。

看到这里，你可能对这种简单的分支方式有以下两个问题。

问题 1：如果功能比较大，一个代码提交不合适，怎么办？

解决办法：这种情况下，第二步本地开发的时候可以产生多个提交，最后在第三步一次性推送到主仓的 master 分支。

问题 2：如果需要多人协同一个较大的功能，怎么办？

解决办法：这种情况下，Facebook 采用的是使用代码原子性、功能开关、API 版本等方法，让开发人员把功能拆小尽快合并到 master 分支。

比如，一个后端开发者和一个前端开发者合作一个功能，他们的互动涉及 10 个 API 接口，其中两个是在已有接口上做改动，另外 8 个是新增接口。

这两名开发者的合作方式是：

第一，后端开发者把这 10 个接口的编码工作，以至少 10 个单独的提交来完成。强调“至少”，是因为有些接口的编码工作可能比较大，需要不止一个提交来完成。

第二，对已有 API 的改动，如果只涉及增加 API 参数，情况就比较简单，只需要在现有 API 上进行。但如果牵涉到删除或者修改 API 参数，就要给这个 API 添加一个新版本，避免被旧版本阻塞入库。

第三，在实现功能的过程中，如果某个功能暂时还不能暴露给用户，就用功能开关把它关闭。

这就保证了，在不使用功能分支的情况下，这两个开发者可以直接在 master 分支上合作，并能够不被阻塞地尽快提交代码。当然了，这种合作方式，可以扩展到更多的开发者。

以上就是开发分支的情况。接下来，我再与你讲述发布分支和策略。

发布分支

基于主干开发模式中，在需要发布的时候会从 master 拉出一条发布分支，进行测试、稳定。在发布分支发现问题后，先在 master 上修复，然后 cherry-pick 到发布分支上。分支上线之后，如果需要长期存在，比如产品线性质的产品，就保留。如果不需要长期存在，比如 SaaS 产品，就直接删除。Facebook 采用的方式是后者。

具体来说，部署包括 3 种：有每周一次的全量代码部署、每天两次的日部署，以及每天不定次数的热修复部署。日部署和热修复部署类似，我们下面详细介绍周部署和热修复部署。

每次周部署代码的时候，流程如下所示。

第一步，从 master 上拉出一个发布分支。

git checkout -b release-date-* origin/master

第二步，在发布分支进行各种验证。

第三步，如果验证发现问题，开发者提交代码到 master，然后用 cherry-pick 命令把修复合并到发布分支上：

git cherry-pick <fix-sha1> # fix-sha1 是修复提交的 commit ID

接着继续回到第二步验证。

验证通过就发布当前分支。这个发布分支就成为当前生产线上运行版本对应的分支，我们称之为当前生产分支，同时将上一次发布时使用的生产分支存档或者删除。

在进行热修复部署时，从当前生产分支中拉出一个热修复分支，进行验证和修复。具体步骤为：

第一步，拉出一个热修复分支。

git checkout -b hotfix-date-* release-date-*

第二步，开发人员提交热修复到 master，然后 cherry-pick 修复提交到热修复分支上。

git cherry-pick <fix-sha1>

第三步，进行各种验证。

第四步，验证中发现问题，回到第二步重新修复验证。验证通过就发布当前热修复分支，同时将这个热修复分支设置为当前的生产分支，后面如果有新的热修复，就从这个分支拉取。

这里有一张图片，描述了每周五拉取周部署分支，以及从周部署分支上拉取分支进行热修复部署的流程。

图 1 Facebook 的代码分支管理和部署流程

以上就是 Facebook 的代码分支管理和部署流程。

需要注意的是，这里描述的部署流程是 Facebook 转到持续部署之前采用的。但考虑到非常多的公司还没有达到持续部署的成熟度，所以这种持续交付的方式，对我们更有参考价值。

Facebook 分支管理策略的背后原因

Facebook 采用主干分支模式，最大的好处是可以把持续集成、持续交付做到极致，从而尽量提高 master 分支的代码质量。

解释这一好处之前，我想请你先看看下面这 3 个措施有什么共同效果：

几千名开发者同时工作在同一条主干；

不使用功能分支，直接在 master 上开发；

必须要使用 rebase 才能入库，不能使用 merge。

其实，它们的共同效果就是：必须尽早将代码合入 master 分支，否则就需要花费相当长的时间去解决合并冲突。所以每个开发人员，都会尽量把代码进行原子性拆分，写好一部分就赶快合并入库。

我曾经有过一个有趣的经历。一天下午，我和旁边的同事在改动同一个 API 接口，实现两个不同的功能。我们关系很好，也都清楚对方在做什么，于是一边开玩笑一边像在比赛一样，看谁先写好代码完成自测入主库。结果是我赢了，他后来花了十分钟很小心地去解决冲突。

Facebook 使用主干分支模式的好处，主要可以总结为以下两点：

能够促进开发人员把代码频繁入主仓进行集成检验。而这，正是持续集成的精髓。与之相对应的是，很多 20 名开发者的小团队，采用的也是共主干开发方式，但使用了功能分支，让大家在功能分支上进行开发，之后再 merge 回主仓。结果是，大家常常拖到产品上线前才把功能分支合并回主干，导致最后关头出现大量问题。

能够确保线性的代码提交历史，给流程自动化提供最大方便。不要小看“线性”，它对自动化定位问题意义非凡，使得我们可以从当前有问题的提交回溯，找到历史上第一个有问题的提交。更棒的是，我们还可以使用折半查找（也叫作二分查找）的办法，用 O(LogN) 的时间找到那个有问题的提交。

比如，在一个代码仓中，有 C000 ~ C120 的线性提交历史。我们知道一个测试在提交 C100 处都是通过的，但是在 C120 出了问题。我们可以依次 checkout C101、C102，直到 C120，每次 checkout 之后运行测试，总能找到第一个让测试失败的提交。

或者更进一步，我们可以先尝试 C100 和 C120 中间的提交 C110。如果测试在那里通过了，证明问题提交在 C111 和 C120 之间，继续检查 C115；否则就证明问题提交在 C101 和 C110 之间，继续检查 C105。这就大大减少了检查次数。而这，正是软件算法中经典的折半查找。

事实上，Git 本身就提供了一个命令 git bisect 支持折半查找。比如，在刚才的例子中，如果运行测试的命令行是 runtest.sh。那么，我们可以使用下面的命令来自动化这个定位流程：

> git checkout master # 使用最新提交的代码

> git bisect start

> git bisect bad HEAD # 告知 git bisect，当前 commit 是有问题的提交

> git bisect good C100 # 告知 git bisect，C100 是没有问题的提交

> git bisect run bash runtest.sh # 开始运行自动化折半查找

...

Cxxx is the first bad commit  # 查找到第一个问题提交

...

bisect run success

> git bisect reset # 结束 git bisect。回到初始的 HEAD

很方便吧。而如果历史不是线性的，也就是说如果提交使用了 merge，那么我们就不能方便地定位出第一个问题提交了，更别说是折半查找了。

这种快速定位问题的能力，可以给 CI/CD 带来巨大好处。在持续交付过程中，我们常常没有足够的资源对每一个提交都进行检查。比如前面提过，Facebook 的持续交付流水线就是每隔一段时间，对代码仓最后一个提交运行流水线的检查。如果发现问题，就可以通过上面这种方法自动化地找到问题提交，并自动产生 Bug 工单，分配给提交者。

其他主要分支方式

除了主干开发的分支管理策略，还有 3 种常用方式：

Git-flow工作流；

Fork-merge 工作流；

灵活的功能分支组合成发布分支。

我在文中给出了链接供你参考。接下来，我们具体看看这几种方式。

Git-flow 工作流

Git-flow 工作流有两个长期分支：一个是 master，包含可以部署到生产环境的代码；另一个是 develop，是一个用来集成代码的分支，开发新功能、新发布，都从 develop 里拉分支。此外，它还有 3 种短期分支，分别是新功能分支、发布分支、热修复分支，根据需要创建，当完成了自己的任务后就会被删除。

Git-flow 工作流的特点是规定很清晰，对各种开发任务都有明确的规定和步骤。比如：

开发新功能时，从 develop 分支拉出一个前缀为 feature- 的新功能分支，在本地开发，并推送到远端中心仓库，完成之后合并入 develop 分支，并删除该功能分支。

发布新版本时，从 develop 分支拉出一个前缀为 release- 的发布分支，部署到测试、类生产等环境进行验证。发现问题后直接在发布分支上修复，测试通过之后，把 release 分支合并到 master 和 develop 分支。在 master 分支上打 tag，并删除该发布分支。

这种工作流，在前几年非常流行。它的好处是流程清晰，但缺点是：

流程复杂，学习成本高。

容易出错，容易出现忘记合并到某个分支的情况。不过可以使用脚本自动化来解决。

不方便进行持续集成。

有太多的代码分支合并，解决冲突成本比较高。

Fork-merge

Fork-merge 是在 GitHub、GitLab 流行之后产生的，具体做法是：每个开发人员在代码仓服务器上有一个“个人”代码仓。这个“个人”代码仓实际上就是主代码仓的一个 clone。开发者对主代码仓贡献代码的步骤如下：

开发者产生一个主代码仓的 fork，成为自己的个人代码仓；

开发者在本地 clone 个人代码仓；

开发者在本地开发，并把代码推送到自己的个人代码仓；

开发者通过 web 界面，向主代码仓作者提出 Pull request；

主代码仓的管理者在自己的开发机器上，取得开发者的提交，验证通过之后再推送到主代码仓。

看起来步骤繁琐，但实际上和主干开发方式很相似，也有一个长期的开发分支，就是主仓的 master 分支。不同之处在于，它提供了一种对主分支更严格、更方便的权限管理方式，即只有主仓管理者有权限推送代码。同时，主仓不需要有功能分支，功能分支可以存在 fork 仓中。所以，主仓干净便于管理。

这种方式对开源项目比较方便，但缺点是步骤繁琐，不太适用于公司内部。

灵活的功能分支组合成发布分支

除了上述方式之外，还有一种非常灵活，但对工具自动化要求很高的分支方式，即基于功能分支灵活产生发布分支的方式。这种方式的典型代表是阿里云效的“分支模式”。

具体方法是大量使用工具对分支的管理进行自动化，开发人员在 web 界面上自助产生针对功能的分支。编码完成后，通过 web 界面对分支组合、验证，并上线，上线之后分支再自动合入主库。

这种方式的好处是：

方便基于功能进行开发。也就是说，开发者可以针对每个功能产生一个分支进行开发。

灵活，也就是能够方便地对功能进行组合，发布到对应环境上测试。出了问题，可以方便地添加或者删除功能。

但这种方式的问题是，对工具的依赖比较高，没有一个系统的工具来自动化的话，基本做不起来。另外，这种方式会大量封装底层的实现，使开发人员不知道底层发生的问题，一旦出现问题就不太容易解决。

哪一种分支管理策略更适合我的团队呢？

要找到适合自己团队的分支管理策略，我们先来对比下上面提到的几种方式的优缺点吧。

图 2 几种常用的代码分支管理策略对比

另外，要找到合适的代码分支管理策略，你还可以参考以下 3 个问题，根据答案帮助你进行选择。

问题 1：如果提供功能分支让成员共享，在哪里建立这个分支？

如果团队不大，可以允许在主仓创建功能分支，不过注意定时删除不用的分支，避免影响 Git 的性能。如果团队比较大，可以考虑使用 Fork-merge 方式，在上面提到的“个人代码仓”里创建功能分支，从而避免污染主仓。

问题 2：要不要使用 Merge Commit?

代码在合并到主干的时候，可以选择 rebase 或者 merge。使用 rebase 的好处是上边提到的方便定位问题。而使用 merge 的好处是，可以清晰地在分支里看到一个功能的所有提交，不像在 rebase 中，一个功能的提交往往是分散的。

问题 3：团队成熟度如何?

单分支开发集成早，质量比较好，但对团队成员和流程自动化要求高。所以，如果你的团队比较小，或者比较成熟的话，可以考虑使用单分支，否则可以选择多分支开发模式，但要想办法把集成提前，同时逐步向单主干分支过渡。

总结来说，尽量减少长期分支的数量，代码尽早合并回主仓，方便使用 CI/CD 等方法保证主仓代码提交的质量，是选择分支策略的基本出发点。

小结

首先，我分享了 Facebook 使用的单主干开发分支，以及通过临时发布分支进行部署的分支管理策略和部署方式。然后，我与你介绍了几种常见的分支管理策略，并给出了推荐的选择方法。

在 Facebook 工作时，我们一直使用这种主干分支开发方式。它强迫我们把代码进行原子化，尽量确保每一个提交都尽快合入 master，并保证代码质量。一开始我不是很习惯，但习惯后我发现它的确很棒。

首先，因为你和你的合作开发者都需要尽快把代码拆小、入仓，这就帮助我们提高了功能模块化的能力。其次，因为 master 里面的提交一般都比较健康，并且是比较新的代码，所以很少会被不稳定的因素阻塞。最后，线性提交历史对开发者的日常工作也很有帮助。我们在开发的时候，常常会碰到一个本来工作得好好的 API，在拉取到最新代码之后出现了问题。这时，我就可以使用这种方法找到第一个造成问题的提交，从而方便定位和解决问题。

一个流程设计、实施得好，对产品来说可以提高质量，对团队来说可以提高效能，对个人来说可以帮助成长。这就是一举三得。

思考题

产品线性质的产品开发，以及 SaaS 产品开发，在选择分支管理策略时有不同的考量。你觉得哪种分支管理方式更适合二者呢？

你知道 trunk-based 里面“trunk”的意思吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

08 | DevOps、SRE的共性：应用全栈思路打通开发和运维

你好，我是葛俊。今天，我来跟你聊一聊 DevOps 和 SRE。

DevOps 和 SRE，尤其是 DevOps，是最近几年软件研发方法的重要趋势。因为它们都跟打通开发和运维流程相关，所以常常被混淆。比如，SRE 等同于 Google 的 DevOps、SRE 是升级版的 DevOps，就是两个常见的误区。

事实上，DevOps 和 SRE 虽然关系紧密，但差别还是蛮大的。所以今天，我首先会通过 DevOps 和 SRE 的对比，引出它们背后的 Why、How 和 What（也就是它们的目标、原则和具体实践）。然后，我会结合自己在一个创业公司的经验，向你推荐如何在团队落地 DevOps。

DevOps 和 SRE 的定义和异同

因为 DevOps 和 SRE 都是比较新的概念，而且在不断地发展变化，所以学术界和工业界对它们的定义并未达成一致。

接下来，我参考已有定义，并加入自己的理解，对 DevOps 和 SRE 的大致定义如下：

DevOps，Development 和 Operations 的组合词，是一种重视“软件开发人员（Dev）”和“IT 运维技术人员（Ops）”之间沟通合作的文化、活动或惯例。 通过自动化“软件交付”和“架构变更”的流程，使得软件的构建、测试、发布更加快捷、频繁和可靠。

SRE，全称是 Site Reliability Engineer ，网站可靠性工程师，是一个职位，是软件工程师和系统管理员的结合，主要目标是创建可扩展且高度可靠的软件系统。

为达到这个目标，SRE 需要掌握如下相关知识：算法、数据结构、编程、网络编程、分布式系统、可扩展架构、故障排除等。SRE 使用工具和系统支撑其完成工作，比如自动化发布系统、监控系统、日志系统、服务器资源分配和编排工具等，而这些工具往往需要他们自己开发和维护。

所以总结来说，DevOps 是打通开发和运维的文化和惯例，而 SRE 是 DevOps 的具体实践之一。说到相同点，它们都是为了打通 Dev 和 Ops，提高研发效能；说到区别，DevOps 是文化，而 SRE 是职位。如果要类比的话，DevOps 与 SRE 的关系，就像敏捷跟 Scrum 的关系。

理解了 DevOps 和 SRE 的定义和异同后，我们再来看看它们的目标、原则和具体实践吧。

DevOps 和 SRE 的 Why、How、What

不知道你有没有考虑过，传统定义中，开发人员和运维人员的利益其实是冲突的。

开发人员的职责是开发功能。功能上线越多，对团队和公司的贡献就越大。所以，开发人员倾向于多开发功能，并快速上线。而运维人员的主要职责是，上线功能，以及保证系统的稳定性，更关注系统的稳定性。新功能上线越多，工作量就越大，服务越容易不稳定，所以运维人员不愿意功能多上线、快速上线。

另外，职能竖井越严重，这些问题就越严重。因为在这种情况下，开发人员写完代码就扔给运维人员撒手不管了，运维人员也很少能从开发人员手里拿到有效信息。此时，线上问题的修复对运维人员来说挑战很大。于是，他们会更加谨慎。

这就是 DevOps 和 SRE 最初要解决的问题：开发和运维这两个角色的目标不一致，导致研发和上线流程的不顺畅，最终严重影响软件上线的效率。比如，运维团队倾向设置多而严格的上线检查门禁，限制上线频率。而开发人员则很可能把一些功能“伪装”成 Bug 修复，来绕过针对版本发布的严格检查。

那怎么解决这个问题呢？接下来，我向你推荐以下几个原则和具体方法。

原则 1：协调运维和开发人员的目标、利益

目标、利益不一致，是导致开发团队和运维团队矛盾的首要问题，所以想办法让它们的目标、利益变得一致，是整个 DevOps 中最重要的一条。因为只有协调好了目标和利益，它们才有动力去解决问题。实现这个原则的一个最主要的方法，我称之为“全栈开发”。

什么意思呢？全栈开发，就是每一个工程人员的工作涵盖了不止一个领域。虽然他专攻某一个领域，但对负责的领域都有责任。说白了，就是对产品结果负责，而不是只对某一个具体环节负责。

具体的实施方法，主要包括两大方面。

第一，增加一个新的运维角色，用开发的方式去做运维。不同于传统运维人员主要关注网站和服务的稳定和快速，这种新型运维角色负责帮助开发人员推动业务快速开发、快速上线，具体工作包括：优化流程，提供自助工具。

在 Facebook，这个角色由一个专门的发布工程师团队承担。而在 Google，这个角色就是 SRE。具体来讲，SRE 的工作职责是负责日常运维、紧急响应、工具研发、建设平台化服务体系、容量规划和容量管理、Oncall 等。他们会被指派到具体的产品团队中，深入到开发第一线，拿出至少 50% 的时间进行编程工作，针对性地自动化和优化 CI/CD 中的流程、工具等。这里请注意，他们的开发工作不是业务开发，而是工具开发和自动化等。

与 Facebook 的发布工程师团队相比，SRE 的最大特点是更多地参与到具体产品和项目中。为了方便讨论，我把 SRE 和发布工程师团队这种新的运维角色，统称为“类 SRE”。类 SRE 顺利推进的关键是，找到高质量的开发、运维多面手，也就是有很强的开发能力，同时有系统维护、网络问题排查等运维技能的工程师。

需要注意的是，除了类 SRE，传统的运维角色（比如网络工程师、系统工程师）仍然存在，他们也使用类 SRE 提供的 DevOps 工具链提高工作效率，所以人员数量比以前少了很多。在遇到线上问题时，开发、类 SRE 和传统运维需要配合解决。

第二，修改开发人员的职责描述为，快速开发和上线稳定的高质量产品，让他们也参与到一部分运维工作中去。

开发人员最主要的工作仍然是开发，但会使用类 SRE 团队提供的工具、流程来进行发布相关的工作，包括代码部署、线上问题定位和处理等部分工作。这样，他们会在代码开发时就注意提高服务的稳定性和代码质量。

比如在 Facebook 进行日部署的时候，开发人员写好了修复的代码提交，还需要去找到提交所依赖的、还没有上线的其他提交，跟这些提交的作者进行确认，没有问题后才可以一起上线。

上线时，这个开发人员对这一组提交负责，把它们全部提交到日部署流程工具上，并负责功能验证。而负责部署的发布工程师，则会使用工具自动化地把这些提交部署到日部署环境中，并进行系统验证。可以看到，开发人员较多地参与了整个部署过程，这正是持续开发的体现。

另一个比较直观地体现开发人员参与运维工作的实践是，让开发人员 Oncall，也就是身上别个 BP 机，线上出现问题要马上响应，即使半夜三更也要爬起来，让功能的开发人员承担起解决一线问题的职责。

通过引入类 SRE 角色和修改开发者职责描述这两种方法，开发人员和部分运维人员的职责，都从原来的单一职能扩展到了产品的高效开发上线，从根本目标和利益上实现了这两个角色的对齐。

所以说，“全栈开发”方式非常重要，效果也非常好。这，也正是我把“全栈”用在文章标题中的原因。

原则 2：推动高效沟通

之所以强调推动高效沟通，就是为了解决开发和运维因为存在“部门墙”而导致的信息不流通的问题。

具体的实施方法包括：

设置聊天室（比如 IRC），用于沟通部署进展和问题。

引入 ChatOps，也就是通过自动化的方式，提高部署相关的沟通。比如，可以让聊天机器人在 ChatOps 聊天室中自动发送发布流程的进展，也可以向聊天机器人询问服务部署进展等。这些自动化可以节省很多部署人员的时间。

把任务、代码提交、发布的关联关系，在工具中呈现出来，并提供比如任务看板、系统监控看板等可视化工具，显示开发、运维重点信息。这样可以大大提高运维人员定位问题的效率。

原则 3：优化从开发到部署的整个上线流程

优化上线流程，主要目的是解决频繁上线工作量大、产品质量不稳定的问题。

这个原则的主要方法是优化代码入库和部署上线流程，并最大化地利用工具来自动化流程。最近 10 年这个原则发展得非常快，逐渐涵盖了快速、高质量上线的各方面工作。这些方面的效率也被称作交付效能，具体实践包括：基于主干开发、持续集成、持续交付、持续部署、实现系统松耦合等。

前 4 条实践，我们已经在前面的文章中讲解过了；而关于系统松耦合，我会在“工程方法”模块中与你详细讨论。

理解了 DevOps 和 SRE 的目标、方法和具体实践后，我们再来看看具体怎么落地吧。

落地步骤推荐

在落地 DevOps 时，我经常看到有些团队一上来就去寻找工具，比如使用 Jenkins 来搭建流程。但正如上面所说，DevOps 的本质是解决开发和运维之间的冲突，所以落地时首先要从人出发，然后是流程，最后才是工具。

所以，我推荐的具体落地步骤是：

对团队目标达成共识，并重新定义职责；

设计 CI、CD、快速反馈，以及团队沟通等流程；

引入工具，实现自动化。

接下来，我就用我在一家创业公司的实践来帮助你理解这些落地步骤。

落地实践案例

这个案例，是我在一个创业公司落地 DevOps 的实例。当时公司刚成立不久，我的角色是技术总负责人，操作的自由度很大。为了提高产品研发的交付效能，也就是团队快速发布高质量软件的能力，我采用了以下步骤和方法。

第一，在团队内部统一认识

研发团队内部统一认识，主要是让团队成员明确我们的目标一致。同时在做绩效考评时，对大家的要求都是产品快速、高质量上线。

我还把“统一认识”这一点，扩展到研发以外的部分，比如市场、设计团队。在推广时，我始终注意以公司利益为出发点，而不是按照职能划分来追责，获得了 CEO 和其他团队管理者的支持。最终，推广得比较顺利。

第二，增加“发布工程师”角色

这个角色的职责，相当于 Facebook 发布工程师团队的职责。因为我在部署、运维方面的经验相对丰富，便担起了这个工作，投入相当一部分精力去设计、优化产品的开发和上线流程，包括持续集成、持续交付、手动部署流程，以及问题监控流程和 Oncall 机制。

其他开发人员则使用这个流程来部署、上线、监控、解决问题，提高交付效能。在这个过程中，我作为发布工程师，主要起到了“使能”的作用，即日常的部署上线由开发人员自己负责，而我只参与解决一部分运维相关的难题。

第三，设计问题沟通流程

在解决了“人”的问题之后，我在流程方面，设计了部署沟通流程，以及部署系统常见问题及解决办法收集流程。

部署沟通流程是：建立了专门的聊天室沟通持续集成、持续交付，以及手动部署的进展情况，确保发布上线流程相关信息的顺畅流通。

部署系统常见问题及解决办法收集流程是：在解决完线上问题之后，记录下问题的细节和解决步骤。很简单，就是因为很多问题会重复出现。每个人都可以更改、搜索这些记录，对后续解决问题帮助非常大。所以，我们每次遇到问题，都会先搜索这个知识库，找到答案和线索的概率能达到百分之七八十。

第四，用工具来实现流程自动化

完成了有关人和流程的工作之后，工具的实现就不那么难了。DevOps 相关工具主要包括两大类：CI/CD 工具链和沟通工具。

我已经在第 5、第 6和第 7篇文章中，与你详细讲述了 CI/CD 流水线的内容；而沟通工具的内容，将会是下一篇文章的主题。这里，我给出当时我们使用的工具链，也就是一个小型创业公司落地 DevOps 具体工具链的示例，供你参考。

图 1 CI/CD 工具链和沟通工具

通过这一系列针对人、流程、工具的措施，整个研发团队实现了全栈开发，目标一致、流程顺畅，能够聚焦于开发，交付效能非常高，实现了后端服务每周上线三次，热修复上线时间 5 分钟，MTTR（Mean Time To Recovery）大概 15 分钟，线上也很少出问题，系统的可用性达到 4 个 9（即 99.99%）。

同时，对于开发者来说，全栈开发机制也优化了开发体验，提升了技术成长速度。

当然，这些实践能够顺利推广，与这是一个小的初创公司有关。一方面，公司存亡直接关乎个人利益；另一方面，一切都在建设期，引入具体实践的阻力非常小。如果是在大的团队，或者是职能已经成型的团队，实施的困难会相对较大。这时，我建议你从以下 3 个方面入手：

在团队普及全栈开发的理念；

在你管理范围内推行全栈开发，做出效果；

通过实际运行效果获得公司管理层的支持，让他们了解全栈开发模式为提升研发效能带来的好处，从而逐步改变职责定义和流程。

小结

今天，我给你介绍了 DevOps 这个很火的话题，以及 SRE 和它的关系。文章中，我与你介绍了 DevOps 的目标、原则和具体实践。这三条原则分别是协调开发和运维的目标、推动高效沟通和”优化从开发到部署的整个上线流程。最后，我通过自己的实战经验，介绍了用“人、流程、工具”的步骤来落地 DevOps。

在我看来，打通开发和运维的“部门墙”，最关键、最根本的就是解决人的问题，也就是第一条原则，”协调开发和运维的目标“，要解决的问题。而全栈开发就是一个非常棒的解决方法。

简单来说，全栈开发就是让工程师不再只是对某一个单一职能负责，而是对最终产品负责。全栈开发是一个很好的抓手，逐步提高全栈开发的程度，大家的目标自然就会对齐，从而主动去提高，那其他方面的提高就容易得多了。

事实上，测试工作也是全栈开发的一部分。在 Facebook，没有专门的功能测试团队，而是有一个测试工具团队提供测试平台、流程和工具，供开发者使用。以此，让开发者更进一步地对整个产品负责。这种开发人员全栈，其他职能提供支持的工作方式，可以总结为如下所示的图片。

图 2 全栈开发方式示意图

最后，我再与你分享一点我的体会。

云技术尤其是 Docker 和 Kubernetes 的流行，使得越来越多的底层运维工作被自动化，导致对传统的系统工程师、运维工程师的需求越来越少。所以，懂得开发的运维人员，会越来越重要；同时，更关注部署、测试，甚至产品的全栈工程师，也会越来越受欢迎。

思考题

有些人认为“全栈工程师”要求太高，一个人不能掌握那么多领域的知识，不现实。你怎么看呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

09 | 信息流通：让团队高效协同，让产品准确击中目标

你好，我是葛俊。今天，我来和你聊聊团队的信息流通问题。

研发过程中的信息流通，指的是各种跟研发相关的信息在工具、团队成员之间的流动。这些信息大到公司战略，小到 Bug ID，是一个团队达成共识、高效工作的重要因素。比如，你是否也曾遇到下面这些研发过程中的不顺畅问题呢？

最终产品背离用户需求。我曾提到，一个常见的低效能问题是，研发团队生产出来的产品与最初的产品设计差别很大，甚至需要完全返工。

前后端沟通不顺畅。后端修改 API 阻塞了前端的工作，或者后端实现了新的 API 前端不知道，继续使用旧的 API 造成浪费。

信息孤岛。在公司内部找信息反而比在互联网上找信息还要难，需要到处找人问，还不一定能问到。

信息难以溯源。团队成员不知道怎么寻找问题的源头，也不知道软件包发布了哪些功能。

工作干扰大。开发工作常被实时聊天工具、电话等打断，刚整理好的思路又得重新梳理。

实际上，这些问题都是信息流通不顺畅造成的。那么今天，我们就来看看如何做到信息流通，从而让开发人员更顺畅地生产出高价值的产品。总结来说，就是要从以下三个方面入手：

首先，从“人”入手，建设共享文化，鼓励共享行为，使信息共享与团队成员利益一致，从而让大家愿意共享。

然后，在流程和工具方面，针对与研发相关的信息，设计并实现对应代码、文档的共享，以及信息在流水线上的自动化流动。

最后，在沟通技巧上下功夫，掌握高效沟通的原则，根据场景选择合适的沟通方式和工具。

接下来，我们分别看看如何实施吧。

团队成员愿意共享是有效沟通的前提

人是首要的，不解决人的意愿问题，流程、工具再好，也用不好甚至用不起来。所以，让团队成员愿意共享，是有效沟通的前提。那，怎么才能调动起团队成员共享消息的意愿呢？

首先，要让团队成员了解信息的重要性。软件开发本来就需要多人协作，沟通的重要性不言而喻。但，有相当一部分开发者，尤其是初级开发者，因为工作经验欠缺等原因不愿意去和别人沟通，甚至意识不到信息流通的重要性。

所以，作为开发人员，我们要主动去克服不愿意沟通的倾向，比如在对需求有疑问时提醒自己，如果现在花一小时的时间去沟通，很可能会节省自己后面三天的时间；而作为团队管理者，我们更要在团队内强调沟通的重要性。

但，了解的信息沟通的重要性，只是实现顺畅沟通的基础。更重要的是，我们要在团队内部建设机制，来鼓励共享的行为，从而形成共享的文化。

简单来说，就是让信息共享和每个成员的利益紧密联系起来。这样做，可以帮助我们解决最终产品背离用户需求，以及前后端沟通不畅导致返工等低效能问题。这一类问题的共同点在于，负责整个产品或者 API 的成员没有紧密合作。在按职能划分的团队，尤其容易出现。因为职能部门的成员，在职能线上向上汇报，最关注的往往是职能部分的完成情况，而不是整个产品的进展。

一个比较有效的解决办法是，按照产品或者功能划分团队，让团队成员直接对产品或者功能负责，Facebook 和 Spotify 等很多高效能公司使用的就是这种方法。

一个团队负责一个产品或者功能，构成上一般不超过 15 个人，包括产品经理、UI 设计、数据科学家、前后端开发者。团队的工作重心就是把产品和功能做好，这也就意味着只有把产品和功能做好了，每个人的绩效才会好。

在这种情况下，沟通就和自己的利益紧密相关，所以大家会通过主动沟通去推动产品和功能的开发。比如，产品设计得慢了，开发人员就会去催促询问，看有没有自己能够帮得上忙的地方。

但是，改变公司的组织架构往往阻力大、推进缓慢，我们还是先立足现状，去解决按照职能划分团队的沟通问题。一个比较有效的办法是，使用虚拟团队。我以前在微软的 Office 团队第一次见到这种方式，后来又把它用在了其他公司，效果非常好。

具体的办法是，给每个功能设计一个虚拟团队，包括产品、设计，以及相关的开发人员。之所以说是虚拟团队，是因为它并不存在于公司的正式组织架构里。我们明确这个团队的职责，就是要做好某个功能。当时我采用的办法也很简单，就是给每个虚拟团队建立一个专门的聊天群，来沟通这个功能的相关问题。而我每周会收集每个虚拟团队的工作进展情况，以此来推动产品维度的进展。

说到这儿，你可能会有一个疑问，我对非开发部门没有掌控权怎么办？事实上，我在推动这个虚拟团队的时候，只负责管理前后端开发人员，对产品等团队没有管理权。但因为这个虚拟团队，和其他团队不存在利益冲突，而且流程很轻，所以并没有任何反对声音。而在虚拟团队中，我负责管理的前后端开发人员会主动发力，促进整个虚拟团队的顺畅沟通，效果非常好。

有了主动沟通的意愿，我们就可以开展下一步工作了，即设计流程和使用工具，推动研发信息的高效沟通。

设计流程和使用工具，推动研发信息高效沟通

这一步的关键在于确认研发流程中的重要信息，然后针对性地设计合适的流程，并选用恰当的工具。

在我看来，对提高研发效能起到关键作用的信息，主要分为 4 种。

第 1 种信息是，战略目标相关的信息

这一类信息的处理原则是尽量公开。只有当团队成员清楚公司以及团队目标时，才能更容易地把自己的目标与之对齐。或许，这就是 OKR 最近几年特别流行的原因吧。如果你想深入了解 OKR，并在团队中落地，可以参考我的朋友黄勇开设的《黄勇的 OKR 实战笔记》课程。

Facebook 每年都会召开一个员工大会，详细列举公司的战略目标；每周还会有一个 Q&A 会议，每个员工都可以参加，马克 · 扎克伯格（Mark Zuckerburg）会亲自出席，并回答员工提出的各种问题。这些举措，都促进了员工对公司战略和目标的深入理解。

第 2 种信息是，代码相关的信息

这一类信息的处理原则也是尽量公开。代码是最直接的参考，是最实时的文档。Facebook 基本所有的代码仓，都对全部开发人员公开。更进一步地，我们不但可以阅读其他团队的代码，还可以主动去修改、提高他们的代码，只要修改得当，发出的 PR 就会被接受。

在国内，由于 IP 保护不力等客观原因，绝大部分公司不愿意把代码对所有员工公开，这也可以理解。不过在这种情况下，我还是建议在不泄露核心机密、不影响核心业务的前提下，尽量扩大代码公开的范围以及受众人群。

选择共享代码的工具和方式，基本原则是方便查找，并在进行开发工作的主要入口（比如 IDE、命令行工具、Web 浏览器）提供接口。

以 Facebook 为例，他们使用 Phabricator 的 Diffusion 子功能，方便团队成员在网页上浏览和查找代码仓的历史；在代码搜索方面，他们自研了一个内部工具，对主要代码仓的代码进行几乎实时的索引，开发人员通过网页浏览器、命令行，以及 IDE 的插件使用代码搜索功能。

高效的代码浏览和搜索对研发顺畅很重要，推荐你加大在这方面的投入。

第 3 种信息是，研发过程中用到的各种文档

这些文档，包括产品设计文档、开发设计文档、测试文档、部署流程文档等。确保这一类信息的高效流通，比较有效的原则是，通过统一的工具，方便大家添加、修改、查询这些文档。

在 Facebook，每个产品团队可以选择自己的文档管理方式。总的来说，绝大部分团队主要使用公司统一的 Wiki 来进行松散的文档管理，既方便添加、修改，也方便搜索。而对于那些比较正式的文档，有些团队会使用类似 Quip 的共享文档系统进行管理。

关于文档管理，有两点值得强调：

第一，文档的管理流程，由每个小的功能团队决定。就比如，我刚才提到的 10 个人左右的小团队，因为他们的共同目标是尽快开发好产品，所以会主动寻找一种适合自己团队的文档管理方式和工具。

第二，绝大部分 Wiki 和 Quip 管理的文档都是全公司公开的，所以团队之间也可以很方便地找到其他团队的相关文档，避免信息孤岛的问题。

第 4 种信息是，各种标识信息

整个研发流程中，在各种工具之间流动着多种标识信息，包括任务工单、代码提交号、版本号、代码审查 ID、测试用例 ID、Bug ID 等。在我看来，管理这一类信息的有效方法是，各种工具通过提供 API，做到服务化，形成工具之间的网状连接，以方便开发人员在工具上快速拿到需要的各种信息。

接下来，我们看一个热修复的具体案例。开发人员写好热修复的代码提交后，需要去发布工具网站填写这个提交的 Commit ID，申请进行热修复。热修复发布工具根据 Commit ID，找到对应的代码审查 ID 以及 Bug ID，自动显示到这个网页上。同时，这个工具还会自动找到这个热修复提交所依赖的、还没有上生产的其他提交，并显示出它们的信息。

有了这些信息，开发人员就可以方便地检查、确认自己的这个热修复可以提交，然后点击确认，正式申请热修复。最后，热修复发布工具自动 cherry-pick 这些提交，把它们都部署到一个热修复环境上进行验证。

整个流程中涉及的服务和工具包括：代码仓服务、代码审查服务、Bug 管理系统、测试验证服务、发布上线服务等。这些工具具有信息高度互通以及自动化的特点，使得开发人员和运维人员能够以最快的速度拿到各种信息，避免繁琐而且容易出错的手工操作，从而尽快部署热修复。

这也就解决了信息溯源难的问题。

通过对研发流程中重要信息的管理，以及对应的高效沟通方式，我们就实现了研发信息流通的顺畅性，从而实现了团队的高效协作。最后，我们再来看看在具体的工作中，团队成员之间有哪些沟通方式和技巧。

沟通方式技巧

高效沟通是个很大的话题，涉及方式、技巧等内容。落到研发团队沟通的具体场景中，我认为高效沟通的首要原则就是，根据沟通需要达成的任务的实时性、方便追溯性，以及对别人的干扰程度，选择合适的沟通工具。

面对面、电话、实时聊天工具、邮件、具体任务工具中讨论等不同的沟通方式，在实时性、方便追溯性、对他人干扰程度等方面各不相同。我将其总结为了一幅图，供你参考。

图 1 各种沟通方式的特点

可以看到，不同的沟通方式之间差别很大，我们应该根据具体场景去选择不同的沟通工具。但在现实工作中，有一个不太好的趋势，就是大家都倾向于追求沟通的实时性，大多使用即时聊天工具和电话。这个情况在国内尤其严重，很多公司完全使用即时聊天工具，比如微信、钉钉等，基本上放弃了邮件等其他方式。

使用这种方法，最主要的好处实时性好，因为每个人都希望自己的问题能够马上得到反馈。但，短暂的好处却带来了长远的利益损失，主要表现在以下两个方面。

问题难以追踪。在聊天群里讨论问题时，问题只要一多马上就乱了，很难找到之前的相关内容。

对他人干扰巨大。一个极端情况是，我见过一个公司，有数据显示开发人员平均每 8 分钟就会被打断一次。可想而知，这种情况下的开发效率自然很低。

解决这个问题的办法很简单，就是针对不同的情况，要求大家使用合适的工具去沟通，并逐渐形成习惯。

具体来说，首先，对某个任务进行讨论时，最好是直接使用任务工具中的讨论功能，同时通过 @的方式来告知相关人员。而一般的工具都可以配置出现 @时，自动发邮件通知对方。所以，如果不是紧急任务，都可以采用这种方式。

其次，在询问问题的时候，可以分以下 3 种情况，选择沟通方式和工具：

如果问题不紧急，尽量使用邮件，避免使用即时聊天工具和电话。为了确保大家能够及时回复邮件，可以在团队内制定一个规定，要求检查邮件的频率，比如说每天至少检查一次或者两次。

如果问题紧急，则直接使用即时聊天工具、电话。

如果要讨论的问题比较复杂，或者是非常紧急，则直接选择电话，或者面对面沟通。

在我看来，选择合适的沟通方式及工具，不仅可以大大减少员工间的互相干扰，还可以提高问题的可追溯性，对团队以及个人的研发效能提升帮助非常大。

小结

在今天这篇文章中，我从人、流程、沟通方式和工具这 3 大方面，和你推荐了沟通顺畅，进而提高效能的方法。我们再一起回忆下核心知识点吧。

实现高效沟通首先要解决的，就是团队成员的意愿问题，让他们愿意沟通。我们可以将沟通与团队成员的利益挂钩，在团队内部建设机制，来鼓励共享的行为，从而形成共享的文化。

然后，针对研发流程中流动的各种信息，我们要做好分类，针对性地设计合适的流程，并选用恰当的工具，最大程度地共享给团队成员。比如，公司的代码、战略目标、文档、流程等信息，尽量公开；再比如，使用统一的工具，方便团队成员添加、查询有效信息。

最后，在平时的沟通中，要权衡实时性、可追溯性以及对其他成员的干扰程度。根据场景，选择合适的沟通方式和工具，提升团队和个人的研发效能。

开发者大多在沟通上有所欠缺，有时甚至认识不到沟通的重要性。事实上，信息的缺失，对研发效能影响非常大。试想一下，如果没有搜索引擎的帮助，你写代码的效率会下降多少呢。所以，在信息沟通上多花些时间、精力，对团队和个人效能的提升都大有裨益。

另外，在硅谷的互联网公司，大家都很在意被打断的情况。如果你在没必要的情况下，使用聊天工具或者电话跟同事沟通的话，他会非常生气。

在 Facebook 时，有些同事会直接告诉其他人，如果我戴上耳机，就表示不希望被干扰，除非紧急情况，否则不要找我当面沟通问题。虽说这种做法有些极端，但我非常希望国内公司也能够尝试类似的工作方式，因为它能让大家安心开发，在高效产出的同时，更多地享受心流的快乐。

思考题

在工作中，你见到的信息沟通的最大问题是什么，在今天的文章中能找到合适的解决方法吗？如果没有找到，你还有什么建议的解决方法吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

10 | 答疑篇：反对996并不是反对奋斗

你好，我是葛俊。

这篇答疑文章，已经是我们“研发效率破局之道”专栏的第 10 篇文章了。很多同学在这些文章下面留下了精彩的留言，阐述了自己对研发效能的认识、遇到的问题以及解决方案。

比如，@囧囧冰淇淋，基本每节课都会整理详细的学习笔记，并结合自己的经验进行思考、提炼和总结；又比如，@Geek_93f953、@Geek_1988，@Robert 小七、@Johnson、@李双、@许童童、@寒光等同学，提出了高质量的问题。还有很多其他同学留下了非常精彩的留言，这里我就不一一提及了。

这些留言活跃了专栏气氛，帮助其他同学进一步思考，也激励着我要把专栏写得更好。所以，在这里我首先要对你表示感谢，感谢你对我的信任，也感谢你的积极参与。

这 9 篇文章涉及的问题，我基本都在评论区直接回复过了。在今天这篇文章中，我会挑选 4 个大家普遍关注的问题再详细展开一下，也算是研发效能综述和研发流程这两个模块的一次总结与复习，打好基础以应对接下来的工程方法、个人效能、管理和文化模块的内容。

现在，我们就正式开始今天的 4 个问题吧。

反对 996 并不是反对奋斗

在专栏第 1 篇文章“效能模型：如何系统地理解研发效能”中，我谈到了 996 的话题。从留言来看，关于我对 996 的态度，有些同学还存在些误解。所以，我们再来讨论下这个问题。

第 1 个误解是，硅谷的互联网公司加班不太多，工作生活间的平衡做得很好

事实是，硅谷的互联网公司，加班也比较常见。这一点，在创业初期的公司尤其明显。

比如，我在 2010 年加入 Facebook 的时候，Facebook 已经比较成熟了，有接近 800 名开发人员。但由于业务的高速发展和同事间的竞争，我们的加班都很严重。我每个周末去办公室加班的时候，都能看到大概百分之三四十的同事在加班。

所以，工作和生活的平衡，完全要靠自己来调节。而我看到的是，很多开发人员实际上调节的都不是特别好，基本上只有工作没有生活。

另外，这样的加班是自愿的，没有加班工资。只有在一些特殊时期，比如和竞争对手拼速度的时候，公司会要求大家 Lock down（类似于国内的封闭开发），才会有加班工资。

第 2 个误解是，反对 996 是在反对奋斗

正如上面所说，硅谷的互联网公司也有很多人在加班，我个人也是大量的主动、自愿加班。因为，我热爱软件开发这个行业，愿意花费大量的时间、精力为之奋斗。

所以，我反对 996，并不是反对奋斗，而是反对用工作时长，尤其是强制上下班时间，来衡量工作效率。

在第 2 篇文章“效能度量：效果不好甚至有副作用，怎么回事？”中，我提到研发效能度量困难的一个原因就是，度量数据的收集难易程度不同，人们倾向拿容易收集的数据去关联效率。因此，管理者使用时长这种很直观、很容易度量的指标去衡量研发效能，结果就是事倍功半。

相比之下，硅谷的很多高效能公司，都是任务驱动型的，也就是说只要你完成任务了，工作时长无所谓。当然了，因为任务量大以及同事间的竞争，很多人会主动加班。但需要注意的是，这些公司并不会强制要求工作时长。更进一步地，它们会提供非常灵活的工作时间安排，方便大家提高工作效率。

比如，Facebook 默认每周三是没有会议的工作日，也就是尽量不安排会议，大家可以选择在家工作。另外，Facebook 的上下班时间很灵活，这对于需要接送孩子的员工来说，就很方便了。

总而言之，反对 996，是反对不科学地使用工作时长来提高研发效能。

如何优化移动端开发的流程？

有同学在第 7 篇文章“分支管理：Facebook 的策略，适合我的团队吗？”后留言反馈，研发流程模块的这几篇文章针对的都是后端开发，想了解下移动端开发的内容。在这里，我要和你澄清一下，我前边描述的各种概念和原则，比如持续开发、持续集成、持续交付，对前端（包括 Web 前端、移动前端等）和后端来说都是一致的。

以 Facebook iOS 应用开发为例。他们采用的也是单主干的开发分支模式，也要求代码提交的原子性，以及 master 分支上线性的代码提交历史。在持续集成方面，他们也是使用 Phabricator 作为流程和质量控制中心，进行各种各样的代码入库前检查。在持续交付方面，他们也是采用了和后端类似的方式，每隔一定时间进行一次全量的构建和验证。

当然，前、后端的开发也有些区别，比如：

iOS 的 App Store 的发布周期是两周一次，所以他们采用了两周一次全量部署的方式，取消了日部署和热修复部署。不过，后来 Facebook 采用在原生 App 中实时加载 JavaScript 的方式，在一定程度上绕过了 App Store 的发布周期限制，于是之后也引入了热修复部署流程。

后端代码在持续交付过程中，每次构建的结果直接部署到一个网站，大家通过特定网址去访问即可进行验证。而移动端开发的情况要复杂一些，Facebook 的方式是提供 App 安装服务，让大家可以在自己的手机上安装不同版本的 App，包括 master 分支版本、周部署测试版本以及线上版本等，并提供自动更新的功能。通过这些自动化，使得移动开发的流程更顺畅。

在测试移动端 App 的时候，需要测试大量的手机硬件和操作系统版本的组合。针对这一需求，Facebook 进行了大量的自动化，能够让测试在各种不同的环境中自动运行。同时，Facebook 还研发了一个服务化的手机池，让开发人员自助式地把自己的 App 部署到某一个特定的硬件和操作系统上，并使用远程控制进行检验。

这里，我再和你扩展一下前、后端配合的内容。前、后端团队有各自的部署日程（即版本火车），由功能开发团队决定后端和前端分别搭乘哪一辆版本火车上线。一般采用的方式是，后端先上线，同时使用功能开关让这个 API 对用户不可见；然后前端上线，最后打开功能开关完成整个功能。

总的来说，研发流程这个模块中提到的各种原则，在前端和后端都同样适用。在理解这些原则之后，你可以针对具体的情况，去设计适合的流程和方法。

关于环境获取的具体建议

有同学留言反馈，环境问题是他们研发过程中的最大痛点。具体来说，联调环境、测试环境的获取，常常需要排队。这里，我再提供些具体的解决方法吧。

从我的经验来看，使用云的架构，尤其是在 Docker 和 Kubernetes 的支持下，把这些环境做成自助化服务，是个比较好的解决办法。

比如，虽然 Kubernetes 没有提供“环境”这一概念，但我们可以在它上面添加一层封装，通过 Infrastructure as Code（IaC）的方式，来自动化环境的获取和释放。这是一个比较通用的办法。具体来说，实现环境服务化的思路是：

首先，把环境模板化，并把模板作为代码进行存储。这个模板系统需要支持环境中的资源设置，以及服务间的依赖。同时需要提供设定变量的能力，来支持用户在环境生成时指定参数，处理诸如数据库、MQ 等服务在环境上的差异。

然后，结合集群资源权限做发布管道编排，这样就可以给不同的流水线的运行结果，也就是软件包，自动按照模板生成环境。

最后，可以选择把生成环境的权限开放给开发者，让他们可以通过模板生成联调环境使用。

事实上，这样的环境生成、管理系统作用很大。比如，可以通过发布管道编排来实现开发、运维、测试整体变更追踪。而且，随着业务增长，可以扩展到任何应用程序都能按需部署到任何规模的任何环境中。还有，如果 QA 可以将测试数据和测试用例也服务化，编排到管道中，就可以实现安全高效的一站式发布。

在下一篇文章中，我会与你更系统地讨论如何给团队配置、提供高效的研发环境。希望这样的内容安排，可以最大程度地帮助你解决环境问题。

哪几个效能度量指标比较实用？

在第 3 篇文章“效能度量：如何选对指标与方法，真正提升效能？”中，我对常用的度量指标给出了分类方法，以及选用的基本原则。有同学反馈，希望我能给出一些更具体的实施和使用建议。

所以，在今天这篇文章中，我会基于不同的改进目标，分别从提供用户价值、流程高效和质量这 3 个角度，再给出几个具体建议。

从提供用户价值的角度来看，可以选择以下几个指标。

净推荐值（NPS）；

系统 /App 宕机时间和严重线上事故数；

热修复上线时间；

核心服务 SLA 可用性指标，也就是我们常说的服务能达到几个 9。这个指标尤其适用于需要提供服务给外部客户的组织，涉及一些服务的性能和可用性的指标契约，直接反映是否达成了对客户的承诺。

从流程高效的角度来看，可以选择以下几个指标。

WIP（在制品）数量，是看板理论的核心思想之一。它指的是，已经开始但没有完成的工作，详见第 3 篇文章对累积流程图的描述。一个非常有效的提高研发流程顺畅度的办法是，限制 WIP。也就是说，每个环节不能同时有超过一定数量的任务。如果你想了解具体细节的话，可以参考《看板方法：科技企业渐进变革成功之道》这本书。

发布频率。如果系统发布没有交易成本，发布频率越高越好，因为它可以更快地提供用户的反馈。发布的交易成本指的是，每次部署需要的流程工作，比如拉分支、代码合并、运行测试用例等。考虑到发布的交易成本，对很多互联网产品来说，1~2 周通常是比较合适的发布频率。

构建时长，指的是个人构建以及 CI/CD 构建时长等指标。它们对持续开发和 CI/CD 顺利执行影响很大。

环境获取时长，指的是开发机器环境、沙盒环境、测试环境的获取需要多长时间。这几个指标，对持续开发影响很大。

从质量的角度来看，可以选择以下几个指标。

工单返工率：反映的是开发团队的代码质量和自测程度，以及 QA 的压力和能力。

持续交付通过率：执行构建 -> 部署 -> 测试 -> 发布全流程的成功率，反映的是开发自测质量，以及自动化验收的稳定性。

高优先级安全漏洞产生率，安全漏洞修补速度：这两个安全相关的指标反映的是，团队在安全方面的风险和处理能力。

小结

好了，以上就是今天的主要内容了。如果有哪些你希望深入了解的话题还未涉及到，希望你可以留言给我。

最后，我想再和你强调一下，第 4 篇文章“流程优化：怎样才能让敏捷、精益真正为我所用？”中，提到的 Why-How-What 黄金圈法则和“实用主义”原则。

我觉得，这是提高研发效率的关键所在。因为软件研发是一个非常灵活、非常有创造性的活动，所以我们一定要抓住根本，了解我们到底需要达到什么目的，有哪些基本原则，然后才是学习一些可供我们参考的最佳实践。这样，我们才能灵活运用这些原则、最佳实践，真正提升团队的研发效能。

所以，在整个专栏的写作中，我也会着重系统化地讲解研发效能的基本原则。让我备受鼓舞的是，很多同学在留言中表示会支持这个思路。这里，我衷心希望你可以通过实用主义的方式，去寻找合适自己的最佳实践。

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

11 | 研发环境：Facebook怎样让开发人员不再操心环境？

你好，我是葛俊。今天，我来和你聊聊研发环境的问题，也就是如何才能让开发人员少操心环境，甚至不操心环境。从这篇文章开始，我们就一起进入工程方法模块了。

在第 5 篇文章关于“持续开发”的讨论中，我与你介绍了获取开发环境是开发工作的核心步骤之一，对提高研发效能来说是非常值得优化的环节。当时，我与你提到了开发环境服务系统以及沙盒环境，解决了开发环境的准备工作。

而这里的“开发环境”只是研发环境的一部分，特指开发机器环境，包括开发机器的获取、网络配置、基本工具以及代码的获取和配置。今天，我们就来看看整体的研发环境的配置问题，从全局上切实解决开发人员因为操心环境而导致的效能低下。

在此之前，你可以先回忆下是否对以下问题似曾相识呢？

开发人员使用的电脑配置太低，运行太慢，编译一次要 10 分钟；

测试环境不够，上线时熬夜排队等环境；

工具零散，不成系统，很多步骤需要手动操作，开发思路常常因为流程不连贯而被打断；

团队成员的环境设置参差不齐，有个别开发人员环境配置得比较好，效率比较高，但没有固化下来，其他团队成员也用不上。

这些问题，实际上都可以归结为研发环境不够高效。就像低劣的空气质量和食物质量会影响我们的身体健康一样，不理想的研发环境会严重降低研发效能。

具体来说，按照开发流程，软件研发需要以下几种环境：

开发机器；

IDE；

开发过程中使用的各种工具、数据和配置；

本地环境、联调环境；

测试环境、类生产环境。

接下来，我会按照这个顺序，分别与你介绍这 5 套环境如何配置。

因为 Facebook 在环境配置上做得非常好，比如说，他们可以保证开发人员在 5 分钟之内拿到一套干净的开发机器环境。所以，我会与你着重分享 Facebook 的实践，然后根据这些实践，总结出一套提供高效研发环境的原则。同时，我也会与你介绍一些我在其他公司的落地实践，帮助你掌握与环境配置相关的内容。

如何配置高效的研发环境？

开发机器

Facebook 从不吝啬在开发机器上的投资。每个开发人员除了有一台笔记本外，还在远程数据中心有一台开发机器。数据中心的机器用于后端以及网站的开发，为方便描述，我称之为后端开发机。而笔记本有两个用处：一是用来做移动端 App 的开发；二是作为终端，连接到后端开发机做后端和网站开发。

两台机器的配置都非常强劲。后端开发机一开始是实体机器，后来为了便于管理和提高资源利用率，逐步转为了虚拟机，但不变的是配置依然强大。在 2015 年的时候，绝大部分机器配置都能达到 16 核 CPU 、144G 内存。笔记本有两种选择，一种是苹果 MacBook Pro，另一种是联想 ThinkPad，都是当时市场上顶配或者顶配下一级的配置。

开发机器的获取和释放，则是通过共享机器池以服务化自助化的方式完成的。更多细节，你可以再复习下第 5 篇文章的相关内容。

IDE

IDE，指的是集成开发环境。Facebook 持续在 IDE 上投入，以完善其功能、一致性及一体化体验。接下来，我会以后端和网站开发为例来说明。

因为代码存放并且运行在后端开发机上，所以在 2012 年以前，绝大部分开发人员都是使用 SSH 远程登录到后端开发机，在那里使用 VIM/Emacs 这类能在命令行里运行的编辑器进行开发工作。也有人尝试在笔记本上运行 GUI 的 IDE，远程挂载后端开发机上的文件系统，但总是有卡顿，效果不好。

在这种情况下，公司首先采用的办法是，尽量提高命令行编辑器的体验，把 VIM/Emacs 配置成类似 IDE 的工具，将最常见的功能，比如代码搜索、代码跳转、Debugger 等集成进去，效果还算可以。

不过，与 GUI 形式的 IDE 相比，这种命令行的工作方式还是有一些局限性，比如，和其他工具服务集成不方便，需要记忆大量快捷键，在显示形式上不够丰富导致用户体验不够好，等等。所以，Facebook 持续投入研究 GUI 形式的 IDE。

第一个成型的 GUI IDE 是一个 Web IDE，也就是在数据中心运行一些 Web IDE 服务。这些 IDE 服务连接到开发者的后端开发机上获取代码，同时提供网页界面，供开发人员用浏览器登录，进行开发工作。这种专门的 IDE 服务解决了远程文件夹挂载的卡顿问题，同时跟其他服务集成起来也很方便。

比如，它可以与 Phabricator 的代码审查功能集成在一起，在代码中用 inline 的方式显示其他人对你的代码的 comments，也就是说，在两行代码之间显示一个额外区域，用于展示 comments。类似的，它还可以方便地与代码搜索服务、代码跳转服务、Debugger 集成，甚至还可以跟 CI/CD 工具链的发布工具集成，所以非常方便。

到 2014 年的时候，Facebook 的大部分开发人员都逐渐转移到这个 Web IDE 上去了。如果你想深入了解 Web IDE，可以参考下Apache Che项目。

这个 Web IDE 已经很方便了，但基于网页的 IDE 在易用性和安全性还是有一些局限，所以 Facebook 继续加大在 GUI 形式的 IDE 方面的投入，最后使用 Electron 框架实现了一个原生的 IDE，也就是Nuclide。

Nuclide 的工作原理和 Web IDE 基本一致，都是在数据中心运行 IDE 服务，IDE 的前端则运行在本地笔记本上，通过与 IDE 服务联通实现代码编辑等功能。只不过 Nuclide 的前端，是运行在开发者笔记本上的一个原生应用，而 Web IDE 的前端是运行在笔记本上的网页浏览器而已。Nuclide 的功能比 Web IDE 更强大，易用性也更好，同时因为没有浏览器的依赖，安全性也更好一些。

本地环境、联调环境

关于 Facebook 的本地环境，我已经在第 5 篇文章中介绍过了，主要就是加快本地构建，使用生产环境的数据，从而使得本地环境更加快捷、方便。

而至于联调环境，我曾在第 6 篇文章中简单提过。在代码提交到 Phabricator 上进行审查的时候，Phabricator 会调用一个沙盒系统创造出一个沙盒环境，运行正在被审查的代码。这个系统也是用机器池实现的，同时也是一个自助式服务，也就是说开发人员可以不通过 Phabricator，直接调用 API 来生成沙盒环境。

本地环境和联调环境是开发中最高频使用的环境，对持续开发很重要。接下来，我再与你分享两个我在其他公司的实施案例吧。

第一个例子是，我在 Stand 公司搭建本地环境。Stand 的业务规模小，因此并没有使用微服务，主要的服务只有单体的网站后端服务、数据库服务 MySQL、缓存服务 Redis，以及一些数据监控服务，相对来说比较简单。

我们的做法是，把这些依赖服务尽量在本地开发机器（也就是笔记本）上都运行一个实例。实在不能在本地运行的服务，要么在本地环境运行时不调用它，要么就在调用它的时候传递额外的参数，表明这个调用来自开发环境，而被调用的服务则针对这样的调用进行特殊处理，从而达到不污染线上环境的效果。

这是一个很常见的办法，简单有效。不过，它的缺点是本地环境数据跟线上环境有区别。

如果你的系统采用的是微服务，则可以采用以下 3 种常见办法。

第一种方法是，直接在自己的机器上把所有的依赖服务都跑起来。这种方法的优点是方便，但要求开发机器配置要求高。

第二种方法是，团队维护一个环境，让大家的开发环境接入。也就是，开发者自己的机器上只运行自己开发的服务，调用其他服务时就使用这个共享环境中运行的服务实例。这种方式对开发机器要求不高，但需要团队维护一个环境，并且一般来说大家需要经常更新这个环境中的服务，整个环境容易不稳定。

第三种方法是，使用服务虚拟化工具，来模拟依赖的服务，比如Mountbank、WireMock。你可以参考这篇文章来了解 WireMock 的使用方法。

第二个例子是，我为一个云产品团队提供联调环境。这个云产品结构非常复杂，有十多个服务，至少需要 3 台服务器；不但有软件，还有数据、组网等复杂的设置，部署很困难；更严重的问题是，这个环境一旦损坏就很难修复，需要从头再来，所以开发人员自己配置基本不可能，运维人员也是忙于维护，应接不暇。

针对这个问题，我们也是使用了机器池的办法。不过，这个机器池里面的单元不再是单个机器，而是由 3 台机器组成的服务。这个服务自助化提供给开发者使用，使用之后自动回收销毁。

同时，确保机器池中有两套空闲环境。不够就补充，多了就删除，以保证获取环境时可以马上得到，同时又不会因为有太多空闲环境而造成资源浪费。

另外，每次有了新的稳定版本，运维人员都会更新脚本，并重新安装和配置系统，保证开发人员能够在稳定版本上进行联调。

这样一来，就解决了团队开发人员的环境问题，将获取环境的时间由 2~4 个小时缩短到了几分钟。

开发过程中使用的各种工具、数据和配置

除了 IDE，开发过程中还会用到其他工具，比如代码搜索、发布部署，以及日志查看工具等。这些工具的组合，可能与你平时理解的开发环境不大一样，但事实上它也是一种广义的开发环境，对开发效率影响很大。

这部分环境的优化，主要是使用工具之间的网状互联来提高效率。关于工具的网状互联，我在第 4 篇文章中有介绍。这里，我想强调 Facebook 在的另一个重要实践：重视开发体验，将开发流程中常用步骤的自动化做到极致。

我用一个具体的例子来说明。我们都知道，Git 的 Commit Message（代码提交描述）是提供信息的重要渠道。但它有一个局限，就是只能存储文本，而图片在描述问题时常常比文字有效得多，也就是“A picture is worth a thousand words”，翻译为中文就是我们常说的“一图胜千言”。

为解决这个问题，Facebook 采用了以下方式：

提供了一个图片存储服务，并为上传的图片提供永久 URL；

开发了一个端测的截屏工具，截屏之后自动上传到图片存储服务，而且，在上传成功之后自动把图片 URL 保存到本地笔记本的系统剪贴板中；

提供了一个内部使用的 URL 缩短工具，避免 URL 太长占用太多文字空间。

这三个工具集成起来，一个具体的使用场景是这样的：我在写 Commit Message 的时候，如果要截屏描述修改效果，就使用一个快捷键（比如 Cmd+Alt+4）激活截屏工具，随后拖动鼠标截屏，然后使用 Cmd+v 就可以直接把图片的 URL 粘贴到 Commit Message 里面了。

这个工作流非常顺畅、高效，不仅被大量用于 Commit Message 的书写中，也被经常被用在聊天工具中。后来，我到了其他公司，都会先配置这样一套工具流程。

测试环境、类生产环境

在测试环境、类生产环境的管理上，Facebook 使用一个叫作TupperWare的内部系统，以 IaC 的方式进行管理。不过，Facebook 并没有开源这个系统。如果你所在公司使用的是 Docker，那可以使用 Kubernetes 实现类似的功能。

如果你们没有使用 Docker，可以试试 HashiCorp 公司的Terraform，或者使用Ansible、Chef之类的配置管理工具，来产生一套干净的环境供团队成员使用，之后再销毁，既方便又不浪费资源。

这里，我再与你分享一个我在Stand 公司使用 AWS 管理压测环境的例子。当时我们没有使用 Docker，于是，我们选择了 AWS 的 OpsWorks 框架。它是 AWS 基于 Chef-Solo 开发的一个应用程序管理解决方案，同时支持基础设施的建模和管理。

OpsWorks 这个框架的用法也是声明式的，只不过这个声明不是纯代码，而是在 AWS 的网页上配置的，使用效果和 TupperWare 差不多，就是首先定义一个压测环境需要几台机器，需要运行什么操作系统、需要什么负载均衡器、需要什么数据库等。

然后，通过 OpsWorks 上暴露的钩子，使用代码来管理应用的生命周期，从而实现系统和应用的初始化。通过这种方式，我们可以很方便地使用 OpsWorks 产生一个云主机集群用于压测，结束之后马上删除，方便而且划算。

其实，使用 Ansible 或者 Chef 也可以实现类似功能，但需要自己开发些东西，这里我就不详细讨论了。

提供高效研发环境的原则

通过以上实践可以看出，配置高效的研发环境主要包括以下几条原则：

舍得投入资源，用资源换取开发人员时间。Facebook 之所以从不吝啬在开发机器硬件上的投入，是因为人力成本更高。

对环境的获取进行服务化、自助化。这一点可以在开发机器、联调环境的获取上，得到很好的体现。同时常常使用 IaC、配置管理系统（比如 Chef）和机器池的方法来实现，同时利用弹性伸缩来节约资源。

注重环境的一体化、一致性，也就是要把团队的最佳实践固化下来。比如 Facebook 一个常见的操作是，配置文件统一处理。以 VIM 为例，将 VIM 的配置文件存放到网络共享文件夹中，开发人员只要在自己的.bashrc 文件中加上一行

source /home/devtools/vimconfig

就可以搞定。

小结

今天，我首先按照开发流程，也就是开发机器、IDE、本地环境和联调环境、开发过程中使用的各种工具及配置，以及测试环境和类生产环境的顺序，与你讲述了高效研发环境的具体实践。然后，基于这些实践，总结了 3 个基本原则：一是，用资源换时间；二是，服务化、自助化环境的获取；三是，实现环境的一体化、一致性。

我觉得，这些原则和实践的背后有一个重要思路，就是 Facebook 重视环境，并持续优化环境。这一点在 IDE 的演化上尤为明显，从命令行到 Web IDE 再到 Nuclide，一直在进步。另外，在去年年底，Facebook 停止了对 Nuclide 的开源项目的维护，这也意味着后面他们可能还会有对 IDE 的一轮新的优化。

以上种种做法，使得我在 Facebook 做开发的时候，对研发环境的感觉就是不用操心，需要使用的时候直接到网站上申请就可以使用；配置也方便，团队的配置都已经在那里了；同时，环境中的各种工具、流程都很顺畅，让我能够静下心来做开发、写算法，做我最能提供价值的事情。

思考题

我在“开发过程中使用的各种工具、数据和配置”这一章节中提到的截屏工具流程，你觉得价值大吗？值得引入你所在的公司吗？如果值得的话，可以怎么来实现？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

12 | 代码审查：哪种方式更适合我的团队？

你好，我是葛俊。今天，我们来聊聊都有哪些代码审查方式，以及哪种方式更适合你的团队。

国外互联网行业的很多效能标杆公司都非常重视代码审查（Code Review），比如 Facebook、Google 等就要求每一个提交都必须通过审查。

现在，国内的很多公司也在有意无意地引入代码审查：有的团队直接使用代码仓库管理工具提供的审查功能，比如 GitHub、GitLab 提供的 PR 审查；有的团队则使用专门的审查工具，比如 Phabricator、Gerrit；还有些团队采用面对面检查；甚至有少数公司，尝试使用结对编程的方式进行代码审查。

虽然国内公司在代码审查上做了不少尝试，也有一些公司做得比较好，比如我了解到七牛云就做得不错，但大多数国内公司还是对代码审查理解得不够深入，对审查方法的认识也不够全面，只能简单地去追随一些最佳实践。结果就是，有些团队的代码审查推行不下去，半途而废；有的则流于形式，花了时间却看不到效果。

那么，怎样才能做好代码审查呢？

我认为，做好代码审查的一个前提条件就是，要找到适合自己团队的方法。要做到这一点，你就要对代码审查有一个深入的了解，弄清楚常用方式及适用场景，然后才能做出正确选择。

所以，在今天这篇文章里，我首先会和你分享常用的代码审查方式及其适用场景，然后和你分享几个具体案例。

什么是代码审查，有什么作用？

首先，我们来看看代码审查的定义。代码审查是指代码作者以外的其他人对代码进行检查，以寻找代码的缺陷和可以提高的地方。

这里需要注意的是，按照定义，人工检查才是代码审查。机器进行的检查一般叫作代码检查或者代码自动检查。常见的办法是人工审查和机器检查同时进行，我会在后面和你详细讨论这部分内容。

代码审查的作用很多，主要表现在 5 个方面。

作用 1，尽早发现 Bug 和设计中存在的问题。我们都知道，问题发现得越晚，修复的代价越大。代码审查把问题的发现尽量提前，自然会提高效能。

作用 2，提高个人工程能力。不言而喻，别人对你的代码提建议，自然能提高你的工程能力。事实上，仅仅因为知道自己的代码会被同事审查，你就会注意提高代码质量。

作用 3，团队知识共享。一段代码入库之后，就从个人的代码变成了团队的代码。代码审查可以帮助其他开发者了解这些代码的设计思想、实现方式等。另外，代码审查中的讨论记录还可以作为参考文档，帮助他人理解代码、查找问题。

作用 4，针对某个特定方面提高质量。一些比较专业的领域，比如安全、性能、UI 等，可以邀请专家进行专项审查。另外，一些核心代码或者高风险代码，也可以通过团队集体审查的方式来保证质量。

作用 5，统一编码风格。这，也是代码审查的一个常见功能，但最好能通过工具来实现自动化检查。

以上就是代码审查的 5 个主要作用。接下来，我会与你详细介绍具体的审查方法。在这部分内容中，我会针对每一种方法详细讨论其优缺点及适用场景，并根据我的经验给出建议。这是本篇文章的中心内容，希望你可以多花些精力好好理解消化。

根据团队特点选择代码审查方法

代码审查有多种方法，按照不同的维度可以有不同的分类。

按审查方式分类

按照审查方式，代码审查可以分为工具辅助的线下异步审查和面对面审查两类。

工具辅助的线下异步审查，就是代码作者通过工具将代码发送给审查者，审查者再通过工具把反馈传递给作者。比如，GitHub、GitLab、Gerrit、Phabricator 等工具的审查都是这种方式。这是当前最常见的审查方式，应用灵活的话，可以在任何一个团队取得良好的效果。

使用这种方式的优点，主要表现在两个方面：

一是，审查者可以控制审查时间，减少对自己工作的干扰。比如，审查者可以用一两个小时的时间，来集中处理多个代码审查要求。

二是，在工具中的讨论会留下文档，方便以后参考。

相应地，使用这种方式的缺点是实时性不好。但，这个问题很容易解决。比如，你可以在工具上发出审查要求，然后跑到审查者办公桌前进行面对面讨论。

面对面审查，就是审查者和代码作者在一块儿阅读代码，进行实时讨论。这种方式的好处是快，可以高效地审查不方便用文字讨论的代码。比如，架构问题的讨论就很适合用这种方式。面对面审查的一个极端例子是结对编程，也就是两个人同时写代码，可以说是把实时性发挥到了极致。

但，面对面审查的主要问题，就是对审查者的干扰大，如果大量使用的话会影响审查者的心流。一个降低干扰的方法是，提前约时间。

10 年之前我在微软工作的时候，我们就常常会使用面对面审查的方法。但除了特殊情况以外，大家都需要提前预约才能去找审查者进行面对面审查。总的来说，这种方式还是可以接受的。

按审查人数分类

按审查人数，代码审查可以分为一对一审查和团队审查两类。

一对一审查，就是审查者单独进行审查。除了面对面的一对一审查，基于工具的异步审查是最常见的一对一审查。

这种方式的好处是，安排起来容易，对审查者的干扰小。不过，代码作者一般不会只把代码发给一个人审查，而是会同时发给几个人，避免出现因为单个审查者最近没空而耽误审查速度的情况。

需要注意的是，这种方式，可能会出现几个审查者同时审查一段代码的现象，从而造成重复劳动。常见的解决办法有两个：

一种是，代码作者明确邀请审查者检查代码的不同部分或者不同方面；

另一种是，审查者在开始审查时通知其他审查者，确保不会重复审查。

团队审查，就是团队成员聚在一起讨论一些代码。这种方式适合专项讨论，比如团队成员集体讨论关键代码。具体方式常常是面对面在会议室进行，当然有的团队也会使用电话会议。

团队审查的好处是，大家一起讨论可以检查出更多问题，但缺点也比较明显，因为要开会，所以很容易出现会议效率不高的通病。解决这个问题，至少做到以下四点：

一是，会前做好准备。也就是，组织者提前发出审查要求和要点，要求每个参会者会前阅读。

二是，增加一个协调人员，以确保会议讨论能聚焦和按部就班地进行。

三是，要有会后跟进，最好能够把文字记录存档到可以搜索到的地方，供以后参考。

四是，尽量减少参会人数。

当然了，这四点适用于所有会议。

按审查范围分类

按照审查范围，代码审查可以分为增量审查和全量审查两类。

代码增量审查，是只针对改动部分进行审查。在一个团队形成代码审查的机制以后，一般会只审查新增代码。

这种方式的好处是，能把有限的时间花在最容易出问题的地方。不过需要注意的是，在审查新的代码改动时，一定要一同考虑相关代码，看看是否会因为新代码的引入造成问题。另外，也需要注意是否会有旧的代码，可以被重构掉。

目前来看，这种方法没有什么明显的缺点。

代码全量审查，是对现有代码的全量，比如说整个文件、某几个函数进行审查。

这种方式常见的适用场景有两个：

一是，专项检查；

二是，在刚开始引入代码审查时，对遗产代码进行一次审查。

这种方式的优点是关注整体质量。但，缺点是工作量大，不能常常进行。

按审查时机分类

按照审查时机，代码审查可以分为代码入库前门禁检查、设计时检查、代码入库后检查三类。

代码入库前门禁检查，是把代码审查作为门禁的一部分，要求代码在入库前必须通过人工审查。这也是最常见的审查方式。比如，GitHub 等工具里面进行的 PR 审查就属于这一类；在 Gerrit 工具里，代码入库前必须通过打分才能入库，也是典型的门禁场景。

这种审查方式的优势很明显，如果没有流于形式的话，可以在代码入库前的最后一步拦截问题，从而避免入库后昂贵的缺陷修复成本。

但这种方法可能导致一个问题，即太过死板从而降低代码入库效率。比如，如果一个公司严格要求入库前门禁检查，即使是修改一个错别字也必须要完整检查才行，这在进行紧急热修复的时候可能就不合适。

解决这个问题的办法就是，引入灵活的机制。比如，Facebook 通过 Phabricator 审查，有一个办法可以让代码作者绕过审核者直接将代码入库，但是需要通知审核者这样操作的理由。一个常见的合理理由，就是“这个修复很简单，我的把握很大。但又很紧急，需要马上通过热修复上线。另外，现在是凌晨一点半，不想把你叫起来帮我审查。”

代码入库前的设计时检查，就是在设计阶段进行代码审查。这种方式主要是讨论代码的架构设计是否有问题。因为代码还没有成型，修改成本非常小，代码作者的抵触情绪也很小。相反，如果代码写好后再去审查架构设计，一旦发现问题就会改动较大，甚至推倒重来，非常浪费时间。我见到的大多数情况是，为了保证项目进度而不得不放弃修改。

事实上，有调查显示，代码审查更容易发现架构问题而不是 Bug。所以我的建议是，尽量使用代码设计时审查。具体的方法是，可以使用与门禁代码检查相同的工具，只不过这个时候发出去的 PR 目的是为了讨论，而不是为了入库。讨论结束后删除这个 PR 即可。

需要强调的是，设计时代码审查的用处巨大，但在实践中却常常因为大家对它不够了解而被忽略。

代码入库后检查，就是检查已经入库的代码。有些工具专门支持事后审查，比如 Phabricator 的审计功能（Audit）。

这种方式的好处是，既不阻塞代码入库，又可以对提交的代码进行审查，只要入库代码没有马上上线，风险就很小。实际上，这就是我们开发审计功能的意图。如果你所使用的工具里没有这个功能，可以在代码历史浏览工具里，用讨论的方式进行。虽然不够方便，但也够用了。

入库后检查的另一个作用是，提高遗产代码的质量。

以上就是代码审查的基本方式、特点及适用场景。掌握了这些，你就可以根据自己团队的特点，选择最合适的方式。最后，我再和你分享三个成功案例，希望给你更多启发。

代码审查方式选择的三个成功案例

下面的这三个案例，团队规模、引入代码审查的时机，以及适用的代码审查工具、方式都有所区别，你可以边看案例，边思考适合自己团队的代码审查方法。

案例一：5 个开发者组成的初创团队的代码审查实践

虽然这个团队只有 5 名开发者，但灵活地做到了高性价比的代码审查。总结他们的经验来看，主要体现在以下几个方面。

从审查方式来看，他们采用的是基于 GitHub 的线下异步审查。这样做的考量以及好处是：

他们的代码仓库是 GitHub，使用 GitHub 的 PR 功能直接进行审查。虽然 GitHub 不如专业的代码审查工具方便，但够用了。

使用 GitHub 做基本的代码审查所需的配置很少，所以引入工具的工作量也非常小。

有的情况使用面对面审查，方法是在代码 PR 发出去之后基于 GitHub 来进行面对面讨论，同时会把几个讨论放到一起，而不是无节制地使用面对面审查，以提高工作效率。

从审查范围来看，从开发第一个 MVP 开始，他们就引入了代码审查，所以后续一直采用代码增量的一对一审查。只是在 App 上线之后，针对安全，对登录模块做了一次全量代码入库后检查。

从审查时机来看，他们并没有强制使用代码入库前门禁检查。但是，他们仍然把代码审查作为一个高优先级的任务来做，要求没有特殊原因都要做代码审查。

除此之外，他们做了力所能及的机器检查，比如通过 GitHub 的钩子运行各种 Linter 以及单元测试和一些集成测试，与人工检查互为补充。

结果就是，这个初创公司从一开始就形成了灵活使用代码审查的文化，提高代码质量的同时，并没有减缓代码入库的速度。

案例二：30 人团队的代码审查实践

这个团队的代码管理工具是 GitLab，没有使用代码审查。因为业务发展太快，代码质量问题越来越严重，所以他们决定引入代码审查。具体来说，做法如下。

他们放弃了 GitLab，直接用 Gerrit 进行代码仓库管理和代码审查。这是因为 GitLab 的代码审查功能不如 Gerrit 强大。而 Gerrit 同时也具备代码仓管理功能，所以就没必要同时维护两个系统了。

从审查方式和审查人数来看，他们采用的是工具辅助的线下一对一审查，对于团队审查非常慎重，只是偶尔使用。

从审查范围来看，他们只是在开始阶段，集中团队核心成员对现有遗产代码进行了几次多对一、面对面的代码全量审查。

从审查时机来看，他们将代码审查作为门禁的一部分，严格执行，以保证业务快速发展时期，上线代码的基本质量。

在机器检查方面，他们使用 Gerrit、Jenkins、SonarQube 三个工具互相集成，自动化了较多的机器检查。

针对之前出现过多次架构问题，但因为发现较晚而来不及修复的情况，团队逐渐引入设计时审查这一实践，尽早发现问题并修复，效果非常不错。

这里，我需要再强调下这个团队引入代码审查的步骤。

他们通过规定严格的提交说明（Commit Message）规范以及 PR 描述规范，来逐步提高代码提交的原子性。也就是说，每个提交只做一件事，同时这个提交又不会太小。这种方法很有效，我会在下一篇文章中和你做进一步介绍。

案例三：百人以上团队的代码审查实践

这个团队原本使用 GitLab 作为 Git 服务器，不过没有做代码审查。在引入代码审查的过程中，为了提高代码审查的效率和体验，他们没有使用 GitLab 做代码审查，而是引入了 Phabricatgor。具体做法是：

使用 Phabricator 的镜像方式进行代码审查。也就是说，代码仓库仍然是 GitLab，Phabricator 上只有一个用来做代码审查的克隆，这样既实现了代码审查，又把对原有 Git 流程的影响降到了最小。

因为团队较大，又分散在多个地区，所以大量使用了线下的异步审查流程。为了保证开发人员在等待一个提交审查的同时，还可以做其他开发工作，他们使用了 Git 的提交链和多分支的方法。关于 Git 的这个使用技巧，我会在后面的文章里与你详细讨论。

基于 Phabricator 进行代码设计时审查，解决因为代码仓库规模大，导致添加新功能时设计复杂也容易有所疏漏的问题。开发人员使用伪代码来表明自己的设计计划，并发出代码审查需求，然后跟审查者进行面对面讨论或者视频会议讨论。

使用代码审查对新人进行培训，通过严格审查新人提交的代码，来传达团队的代码规则、质量基准等。

另外，值得一提的是，在使用 Phabricator 进行代码审查之前，这个团队最常用的是少量的团队集中审查，并且有审查权的只是团队的几个核心成员。这种方式导致了两个问题：一是审查效率低下；二是这几个核心成员本身都是技术骨干，但因为需要花费大量时间做审查，导致无法贡献足够多的新代码。

采用新的代码审查方式之后，降低了团队开会进行代码审查的频率，并且逐步放开了审查权限，解决了代码审查成为瓶颈的问题。

以上就是三个成功案例，相信根据你们公司的实际情况具体分析，你一定能找到合适的方法。

小结

代码审查对团队产出质量、个人技术成长有很多好处。代码审查方式多种多样，根据不同维度可以分为工具辅助线下审查、面对面审查、多对一审查、一对一审查、代码增量审查、代码全量审查、设计时审查、入库前检查、入库后检查等。

我将这些审查方法的优缺点整理为了一张表格，你可以以此为参考，根据团队实际情况去挑选合适的方式。

总体来说，绝大部分团队都适合引入工具进行异步的一对一审查，在互联网上也有比较多的关于这种审查方式的最佳实践推荐。比如，最近 Google 发表了他们的代码审查指南（Google’s Code Review Documentation），你可以参考。

另外，多使用一些设计时审查尽早进行讨论一般也都效果不错。

知道了应该使用哪个方式，下一步就该具体的引入了。在下一篇文章，我会和你介绍成功引入代码审查的几个具体实践。

思考题

你们团队使用了代码审查吗？具体使用了哪几种审查方式呢？

设计时检查除了可以避免后期对代码的大规模调整外，对顺利引入代码审查还有一些其他作用。你能想到还有哪些作用吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

13 | 代码审查：学习Facebook真正发挥代码审查的提效作用

你好，我是葛俊。今天我们来聊聊代码审查的落地。

在上一篇文章中，我和你详细讨论了代码审查的作用，也给出了选择适合自己团队审查方式的建议。但是，仅仅知道要做什么还不够，更重要的是落地。我见到很多国内公司也在尝试使用代码审查，但是效果很不好，往往流于形式，最常听到的一个负面反馈就是“代码审查浪费时间”。

代码审查的成功推行的确不是一件容易的事。今天，我们就一起尝试来解决这个问题。我会从三个方面给出一些建议：

第一，在团队内引入代码审查的步骤和方法；

第二，成功推进代码审查的关键操作；

第三，持续做好代码审查的重要原则。

今天的文章较长，我们现在就进入第一个部分，

引入代码审查的步骤和方法

从我的经验来看，要成功引入代码审查，首先要在团队内达成一些重要的共识，然后选择试点团队实行，最后选择合适的工具和流程。

1. 代码审查应该计入工作量

代码审查需要时间，这听起来好像是废话，但很多团队在引入代码审查时，都没有为它预留时间。结果是大家没有时间做审查，效果自然也就不好。而效果不好又导致代码审查得不到管理者重视，开发人员更不可能将代码审查放到自己的工作计划中。于是，形成恶性循环，代码审查要么被逐渐废弃，要么流于形式。

之前在 Facebook 的时候，我们预估工作量的时候就会考虑代码审查的时间。比如，我平均每天会预留 1~2 个小时用于代码审查，大概占写代码总时间的 1/5。同时，代码审查的情况会作为绩效考评的一个重要指标。

另外，平时我们也会给审查者关于审查质量的实时反馈。比如，我刚加入 Facebook 的时候，对代码审查不够重视，做得不够好。我的主管就两次给我反馈，让我提高审查质量。其中一次是让我多给同事做审查，另外一次是让我多给一些结构上的建议，不用太重视语法细节。

经过这两次反馈，我意识到了代码审查是我工作中实实在在的一部分，需要得到足够的重视。也正因为此，我才真切地感受到了代码审查的价值。

总之，管理者要明确代码审查是开发工作的重要组成部分，并记入工作量和绩效考评。这，是成功引入代码审查的前提，再怎么强调都不为过。

形成共识以后，下一步就是选择试点团队推行代码审查。

2. 选择试点团队

引入代码审查的一种方法是，在整个团队从上往下全面推行。在团队规模比较小的时候，比如少于 20 个开发者，这个方法很好用，能够快速推动。但如果团队规模较大，成员对好的代码审查方法又不是很熟悉的话，往往会造成混乱。

比如，大家会倾向于按照自己的想法进行审查，这就可能会出现审查者只检查格式，或者把个人喜好强加到别人身上的错误做法。

如果在一开始的时候，就出现较多的负面效果的话，会让大家丧失推行代码审查的信心。

所以，我推荐先不要大范围实施，而是应该先选择试点团队，之后再推广。这样做的好处是，一方面，试点团队成员有限，容易推行新的做法；另一方面，有了试点团队的成功案例后，再全面推行就有了可借鉴的经验，会更顺畅。

关于试点团队的选择，最好是找成员经验丰富、对技术有追求，同时所做业务又不是最紧急的小团队。这样的团队成员不仅会有兴趣，而且也有精力去学习好的审查实践，容易出成果。

有了试点团队，我们接下来就需要选择代码审查工具及配置流程了。

3. 选择代码审查工具，并把机器审查和人工审查结合起来

我在前一篇文章中提到过，几乎所有团队都适合使用工具进行异步的一对一审查。

关于工具，如果你的团队本来已经在使用 GitLab、GitHub、Gerrit、Phabricator 管理代码的话，那么很容易上手代码审查。因为，GitHub、GitLab 有基于 PR 的审查。而 Gerrit 和 Phabricator 本身就主打代码审查的功能。

如果你所在团队没有使用这些工具的话，就只能投入资源引入一个新工具，可以是上面提到的几个工具，也可以是其他审查工具，比如Review Board。

至于代码审查工具的设置，你可以在网上搜索到详细的配置指南。这里我就不一一列举。

我在这里着重讲讲配置流程中非常重要的一步，就是配置机器审查和人工审查配合的工作流。代码审查是代码入库前质量保证的重要一环，所以通常是和 CI 工具配合使用。最好能够让机器自动化地完成关于代码风格、静态检查、单元测试等工作，这样可以让审查者把最宝贵的时间投入到逻辑、设计等难以自动化的问题上。这里，我和你分享一种最常见的工作流。

第一，将代码提交到本地 Git 仓库或者用于审查的远端 Git 服务器的分支上；

第二，把 commit 提交给代码审查工具；

第三，代码审查工具开始进行机器审查和人工审查；

第四，如果审查通不过就打回重做，开发者修改后重新提交审查，直到审查通过后代码入库。

至于具体的工具集，我这里给出两个例子：

第一个例子是 Facebook 采用的方式。他们使用 Phabricator 作为代码审查工具。同时直接使用原生的 Git Server 作为代码仓服务器。用户将改动提交到 Phabricator，然后进行机器和人工审查。检查通过后，Phabircator 负责将代码推送到 Git Server 上，或者用户手动将本地改动 Push 到 Git Server 上。
这个工作流使用的是原生 Git Server 管理主仓，如果你要使用 GitLab 或者 GitHub 也可以。关于机器审查的配置，Phabricator 提供大量的钩子和插件机制，非常方便。具体细节你可以参考Phabricator 官方文档。

第二个例子是使用 GitLab、Jenkins 和 SonarQube 进行配置。具体使用 GitLab 管理代码，代码入库后通过钩子触发 Jenkins，Jenkins 从 GitLab 获取代码，运行构建，并通过 Sonar 分析结果。这里有一篇不错的文章供你参考。

好了，以上就是今天文章的第一部分，引入代码审查的三个步骤：一是，就代码审查的工作量达成共识；二是，选择试点团队；三是，确定审查工具和流程。接下来，我们来看本文的第二大部分：成功推进代码审查的关键操作。

成功推进代码审查的关键操作

代码审查需要注意的点有很多，网上也有很多文章列举了很多最佳实践。从我在 Facebook 的经验来看，其中有两个实践最是直接、明确，而且有效：一是注意审查提交的原子性，二是审查中关注提交说明（Commit Message）。

操作一：提高提交的原子性

代码提交的原子性，是指一个提交包含一个不可分割的特性、修复或者优化，同时这个提交要尽可能小。

原子性提交的优点是，结构清晰，容易定位问题。一般来说，代码提交的原子性做得越好，代码质量越好。同时，原子性提交因为小而聚焦，也是做好代码审查的基础。

我曾经和一个 10x 开发者合作一个大功能。他将其拆为了 15 个原子性提交，每一个提交的代码量都控制在 500 行以下。这 15 个提交都是我审查的，在 3 天的协作过程中，这个同事不断发出审核 PR，我不断反馈修改意见，然后他不断对旧的提交进行修改，同时又发出新的 PR 请求。

大多数时候，都是三四个提交在被同时审查，而这三四个提交是实现同一个功能。整个审查过程非常流畅，效率很高。试想一下，如果不是代码提交的原子性做得很好，绝对难以做到这一点。

这段经历给我的触动非常大，让我深刻意识到了原子性提交对代码审查的重要性。

所以在 Facebook，代码提交的原子性是代码审查非常重要的指标，如果提交的原子性不好，常常会被直接打回。我后来在 Stand 公司就采用了这个办法，让大家在审查中首先看原子性。如果一个提交做了几件事，不看细节直接打回。因为团队小，很快就形成习惯，效果很棒。我推荐你在你们团队也尝试类似办法。如果能接受一开始的阵痛并执行下来的话，很快就会看到效果。

另外，这里还有一个实现原子性提交的技巧：在对一个大功能进行原子性拆分的时候，功能开关是一个很好的工具。这里有一个马丁 · 福勒（Martin Fowler）的关于功能开关的链接，可供你参考。

操作二：提高提交说明的质量

提交说明是提高代码审查的利器，好的格式应该包含以下几个方面：

标题，简明扼要地描述这个提交。这部分最好在 70 个字符之内，以确保在单行显示的时候能够显示完整。比如，在命令行常用的 git log --oneline 输出结果要能显示完全。

详细描述，包括提交的目的、选择这个方法的原因，以及实现细节的总结性描述。这三个方面的内容最能帮助审查者阅读代码。

测试情况，描述的是你对这个提交做了什么样的测试验证，具体包括正常情况的输出、错误情况的输出，以及性能、安全等方面的专项测试结果。这部分内容，可以增加审查者对提交代码的了解程度以及信心。

与其他工具和系统相关的信息，比如相关任务 ID、相关的冲刺（sprint，也可翻译为“迭代”）链接。这些信息对工具的网状互联提供基础信息，非常重要。

这里还有一个 Git 的技巧是，你可以使用 Git 的提交说明模板（Commit Message Template），来帮助团队使用统一的格式。比如，可以像下面这个例子一样，使用 git config --global commit.template 命令来设置模板。

# 配置文件：提交说明模板文件 ~/.git_commit_msg.txt

> cat .git_commit_msg.txt

Summary:

Test:

Task ID:

# 设置上述文件为提交说明模板。

> git config --global commit.template ~/.git_commit_msg.txt

# 使用实例：之后 git commit 命令自动使用上述模板

> git add app.js

> git commit

Summary:

Test:

Task ID:

# Please enter the commit message for your changes. Lines starting

# with '#' will be ignored, and an empty message aborts the commit.

#

# On branch master

# Your branch is up to date with 'origin/master'.

#

# Changes to be committed:

#       modified

通过制定严格的提交说明格式来规范其质量，可以方便审查者查理解被审查代码的意图、实现思路，并通过测试情况，加快对代码的理解，提高对代码质量的信心，从而大大提高审查者的效率。同时，严格的提交说明格式及好的说明质量也可以督促开发者提高代码质量。所以说，它是一个简单、直观且有效的代码审查落地实践。

下面，我就给出一个之前我在一个公司逐步提高提交说明要求的一个具体例子，供你参考。

当时的情况是，大家的提交说明非常简短，很多只包含一句话。比如，功能提交常常写个“实现 A、B 功能”；Bug 修复提交则更简单，通常就三个字母“Fix”。针对这个情况，我采取了以下三个步骤：

第一步，规定提交说明一定要包括标题、描述和测试情况三部分，但暂时还不具体要求必须写多少字。比如，测试部分可以简单写一句“没有做测试”，但一定要写。如果格式不符合要求，审查者就直接打回。这个格式要求工作量很小，比较容易做到，两个星期后整个团队就习惯了。虽然只是在提交说明里增加了简单描述，也已经为审查者和后续工作中进行问题排查提供一些必要信息，所以大家也比较认可这个操作。

第二步，要求提交说明必须详细写明测试情况。如果没有做测试一定要写出具体理由，否则会被直接打回。这样做，不但为审查者提供了方便，还促进了开发人员的自测。整个团队在一个多月后，也养成了详细描述测试情况的习惯。

第三步，逐步要求提交的原子性。我要求每一个提交要在详细描述部分描述具体实现了哪些功能。如果一个提交同时实现了多个功能，那就必须解释为什么不能拆开提交；如果解释不合理的话，提交直接被打回。

这一步实施起来比较困难，原因包括大家对功能拆分不习惯或者不熟悉、对 Git 操作不熟悉。针对这些问题，我通过内部培训来提高团队的 Git 能力。同时，我先在一个小团队内集中精力实践提交的原子性，通过我的直接参与来快速提高这个小团队的能力。然后，再让这个团队帮助其他团队提高。

大概三个月以后，整个团队在提交原子性方面提高了很多，代码审查也就真正有效地做起来了。

可以看到，在这个过程中，提交说明起到了抓手的作用，有效地帮助团队推进开发自测，以及提高代码原子性。

好了，以上内容就是成功推进代码审查的两个关键操作，即提高提交的原子性，以及使用提交说明做抓手。最后，我们再来看看成功推行代码审查在文化方面的两个原则。

成功推行代码审查的两个关键原则

代码审查原则一：互相尊重

代码审查是两个开发者之间的技术交流，双方都要谨记互相尊重的原则。

从代码作者的角度来看，审查者要花费时间去阅读他并不熟悉的代码，来帮助你提高，应该尽量为审查者提供方便。

比如，提高提交说明的质量，就是对审查者最基本的尊重。还有，如果你的代码都没有进行自测就提交审查，你觉得审查者心里会怎么想呢？又比如，如果你提交的一个审查有一万行代码，让审查者怎么看呢？所以，代码作者一定要提审查者着想，帮助审查者能够比较轻松地完成审查。

这里还有一些细节性的问题：

注意描述文字的格式。比如，使用 Markdown 格式书写，在 GitHub、GitLab 等工具上就会比较美观。这些格式方面的问题，可能有的开发者会觉得麻烦而不屑于去做。但实际上，这样的细节，会让审查者更愿意也更容易去阅读你的提交说明，提高代码审查的效率。这也是对审查者的尊重。

在描述测试情况的时候，尽量提供真实的输出，如果是 UI 改动的话，最好能够提供截屏。提交说明只支持文字，但你可以把图片上传到其他地方，然后提供链接。这样审查者可以更直观的看到修改效果，对审查效率的提高有非常大的帮助。

如果需要审查者特别注意某一方面，要明确指出。如果有些代码过于复杂，可以主动找审查者当面讨论。

从审查者的角度来看，在提出建议的时候，一定要考虑代码作者的感受。最重要的一点是，不要用一些主观标准来评判别人的代码。

在 Facebook 的时候，我团队里有一个同事对一些技术的细节特别坚持己见。本来两个实现方式的效果差不多，设计也各有优劣，但他要求作者一定要按照他的思路来实现。同时，他的语言能力特别强，常常在讨论里面长篇大论地写他的理由，让代码作者非常头痛，降低了大家的研发效能。最后还是大家都在绩效考评时给他提意见，他才改了一些。

尊重代码作者的做法，还有：

在打回提交的时候，一定要礼貌地描述原因。

审查要尽量及时，如果不能及时审查要告知作者。

这些都只是互相尊重的一些具体实践。在做代码审查时，我建议你随时记得要互相尊重，多为对方考虑。只有这样，代码审查才能顺畅。

代码审查原则二：基于讨论

代码审查常常出现问题的一个地方是，在审查过程中因为意见不同而产生争执甚至争吵，所以一定记住代码审查的目的是讨论，而不是评判，作为管理者一定要在团队中强调这个原则。

讨论的心态，有助于放下不必要的自尊心，从而顺利地进行技术交流，提高审查效率。另外，讨论的心态也能促进大家提早发出审查，从而尽早发现结构设计方面的问题。

在 Facebook 时，我们常常会发出一些目的只是讨论的代码审查，讨论之后会抛弃这个提交，然后重新发出新的代码，效果非常不错。

另外，我还有一些关于讨论的建议：

审查者切记不要说教，说教容易让人反感，不是讨论的好方法。

审查者提意见即可，不一定要提供解决方法。我曾经见过一个团队要求提出问题必须给出对应的答案，结果是大家都不愿提问题了。

想办法增加讨论的有趣性。在 Facebook 做代码审查的时候，我们常使用图片进行讨论，用有趣的方式表达自己的意见。这样做有两个好处，一是容易被对方接受，二是开发工作比较枯燥，我们应该主动找点乐趣，你说对不对？

比如说，如果觉得代码提交太大，审查者就可能会贴一张有很多星球的图片。星球按由小到大小到的顺序排成一行，最左边的是地球，右边是木星，再右边是太阳。天体越来越大，最右边一个超级大，但是标签上写的不是星球的名字，而是“你的代码提交”。大概是这样一幅图：

以上就是两个文化相关的原则。互相尊重和基于讨论。只有这样，才能实现代码作者和审查者的双赢：代码作者的代码质量得到提高，代码审查者能顺畅审查代码。

小结

几乎所有的开发团队都适合使用工具进行代码审查。无论团队大小如何，都可以通过合适的代码审查进行高性价比的讨论。

在今天的这篇文章中，我针对代码审查的引入、推进，以及文化原则三个方面，给出了一些建议。

在引入阶段，我有三个建议：

团队统一思想，代码审查是有效工作的一部分，应该计算到工作量里面；

选择合适的试点团队；

让机器审查和人工审查结合，使得人工审查更聚焦。

在推进实施的阶段，我推荐提高提交的原子性，以及重视使用提交说明两个关键操作。

最后，我建议通过互相尊重和基于讨论这两个原则，从文化的角度固化团队的代码审查实践。

这三个方面的建议，是我基于 Facebook 的经验总结得出的，是 Facebook 高效代码审查的重要原则和方法，可以帮助你在团队中推动高效代码审查。

同时，这三个方面的措施也分别对应了在一个团队引入、推进、深化代码审查的步骤。希望对你在团队中引入代码审查的具体过程有一定的借鉴作用。

思考题

你见过或者经历过推行代码审查的成功或者失败案例吗？你觉得成功或失败的原因是什么呢？

上面提到 GitHub、GitLab、Gerrit 都是用 Git 分支来存储被审核的代码。只有 Phabricator 使用数据库存储。你知道为什么吗？（提示：Phabricator 里没有“Git”这三个字母。）

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

14 | 质量与速度的均衡：让“唯快不破”快得更持久

你好，我是葛俊。今天，我来和你聊聊团队可持续性的快速开发，怎样才能让“唯快不破”快得更持久。

最近几年，一提到开发，很多人想到的都是“天下武功，唯快不破”。也就是说，开发过程越快越好，越快越有竞争力。这的确是软件开发，尤其是互联网行业软件开发的不二法则。也正如我在前面文章中多次提到的，快速开发可以快速得到用户反馈，更快地验证用户价值假设。无疑，这是高效开发的重要原则。

因此，我们在实际工作中，往往会为了快而选择各种“捷径”。比如：

要开发已有功能的一个相似功能，因为时间很紧就先 copy & paste，保证功能按时上线。

需要在一个函数里增加功能，这个函数已经有 800 行了，加上新功能后会有 1000 行。重构这个函数是来不及了，先把功能加上去再说。

说是“捷径”，是因为这些都不是最优解，有点儿投机取巧。它们的确能让我们在短期内保证快速交付，满足业务发展需求。但如果没有任何补救措施的话，时间长了我们就再也快不起来了。

比如，“Copy & Paste”方式的编程，会导致后续添加功能时，需要在很多地方做类似修改，工作量大且容易出错。再比如，无视函数变大的操作，会导致后续的修改、调试异常困难。

这些问题都会成为开发工作中的技术债，也就是在开发产品或者功能的过程中，没有使用最佳的实现方法而引入的技术问题。无疑，这些技术问题会为将来的产品维护和开发带来额外开销。只有正确地处理技术债，才能让我们的研发持续地快下去。

接下来，我们就来看看技术债的成因、影响，以及对应的处理方法。

技术债的成因

从成因来看，技术债的引入包括主动和被动两种。

主动引入，即开发人员知道某一个实现会产生技术债，但仍采用这样的实现。最常见的情况是，由于业务压力，在时间和资源受限的情况下不得不牺牲质量。

被动引入，即不是开发人员主动引入的技术债。常见的情况有两种：一是，产品不断演化，技术不断发展，原来的设计、实现落伍了；二是，开发团队的能力和水平有限，没有采用好的开发方法、实践。

所以说，技术债是无法避免的，我们要做的就是明确它的影响、处理好它。

技术债有哪些影响？

提到技术债，我们想到的往往是它的坏处，比如难以维护、难以增加新功能等，但实际上它也有巨大好处。

关于技术债的好处，我们可以对应着金融领域的经济债务来理解。我们都知道，经济债务最明显的好处在于，可以帮助我们完成很多本来不可能完成的任务，比如贷款买房。相应的，技术债可以在短期内帮我们快速完成业务开发，满足用户需求，就类似房贷的作用。

但跟经济债务一样，技术债也需要偿还，也会产生利息，而且是利滚利。也就是说，每一步累积的技术债都会叠加起来，为开发增加越来越大的难度。长期来看，如果一直借债不还，开发新功能会越来越慢，产品维护越来越难，甚至是无法维护必须推到重来，就像还不上房贷房子被银行收回一样。

那么，技术债务应该如何处理、如何偿还呢？

处理技术债的基本原则是什么？

在我看来，处理技术债的基本原则有以下两个方面。

第一方面，要利用技术债的好处，必要时要大胆“举债前行”。也就是说，在机会出现时，使用最快的方式完成业务服务用户，抢占市场先机，“不要在意那些细节”。

一个具体的例子是，RethinkDB 在与 MongoDB 的竞争中失利。在技术上，RethinDB 比 MongoDB 更追求完美，但比 MongoDB 发布稳定版本晚了三年，错过了 NoSQL 的黄金时机，最终在 2017 年 1 月份宣布破产。在这个过程中，他们没有充分利用技术债抢占市场，应该是竞争失败的一个重要原因。文章中我放了两个链接供你阅读参考，你可以了解一下 RethinkDB 公司的人以及外部用户对他们的失败进行的反思文章 1、文章 2。

第二个方面，要控制技术债，在适当的时候偿还适当部分的技术债。

在我看来，国内大部分公司的业务驱动做得比较好，大都能够比较充分地利用技术债的好处，但在技术债的管控方面，通常做得不太够，具体来说就是常常有大量技术债堆积，给业务长期发展带来巨大阻碍。

所以在下面的内容中，我会与你详细讲述应该怎样控制技术债。

如何控制技术债？

从我的经验看，控制技术债主要有以下 4 步：

让公司管理层意识到偿还技术债的重要性，从而愿意投入资源；

采用低成本的方式去预防；

识别技术债并找到可能的解决方案；

持续重构，解决高优先级技术债。

接下来，我们分别看看这 4 步具体如何实施吧。

1. 让公司管理层意识到偿还技术债的重要性，从而愿意投入资源

通常来说，开发人员能直观感受到技术债的坏处，大都愿意去偿还技术债，所以技术债累积的主要原因是，管理层不理解，或者说是没有认识到技术债累积给业务发展带来的巨大坏处。

这也就意味着，解决技术债的第一步就是，让管理层意识到偿还技术债的重要性，从而愿意投入资源去解决。在我看来，让管理层理解技术债比较直观、有效的方式，就是上面提到的与经济债务的类比。

另外一个办法是，将偿还技术债与业务发展联系起来。如果能够说明某一项技术债已经阻碍了公司重要业务的发展，说服管理层投入资源解决技术债就会比较容易。

2. 采用低成本的方式预防

所谓具体问题具体分析，我们在预防技术债时，也需要根据技术债的成因采取不同的措施。

对主动引入的技术债，要尽量让管理层和产品团队了解技术上的捷径将会带来的长期危害，从而在引入技术债时客观地权衡其带来的短期收益和长期损害，避免引入不必要的技术债。

在被动引入的技术债中，由于产品演化导致设计落伍的问题不是很好预防。而由开发团队的能力问题引入的技术债，我们可以使用加强计划和代码审查等方法实现低成本的预防。

其中，加强计划，可以帮助开发人员更合理地安排时间，从而有相对充裕的时间去学习并选择更优秀的功能实现方案。而代码审查的作用就更好理解了，它可以帮助我们在早期发现一些不必要引入的技术债，以更低的成本去解决它。

关于技术债的预防，我还有一个小贴士，就是在接口部分多下功夫。因为接口涉及实现方和多个调用方，所以接口部分累积的技术债，影响范围通常比较大。而与之相对应的模块内部实现，技术债的影响范围就比较小。所以，在涉及主动引入的技术债时，我们需要区别对待接口部分和实现部分。

3. 识别技术债并找到可能的解决方案

对不能预防的技术债，我们需要高效地把它们识别出来，并了解常见的解决办法。其中，对于主动引入的技术债，可以在引入的时候就添加任务到 Backlog。而对于被动引入的技术债，则需要周期性的审视，这需要技术管理者主动地收集、整理技术债问题。

总结来说，技术债可以分为两大类：复杂度相关和重用性相关。我们可以关注这两个方面来识别技术债。

第一是，复杂度相关。

史蒂夫 · 迈克康奈尔（Steve McConnell）在其经典著作代码大全（Code Complete）中，提出的一个核心观点是：如何处理复杂度是软件开发最核心的问题。我非常认同这个观点，因为人类大脑容量有限，大概只能同时记住 7 项内容，而软件包含的元素非常复杂远超过 7 项。所以，要实现可维护的软件，我们必须想尽办法去降低其复杂度。

具体来说，我们在开发时，要时刻注意会增加代码复杂度的“坏味道”，比如：

组件间依赖混乱，职责不清晰；

组件、文件、函数太大，包含的内容太多；

使用不必要的、复杂的设计范式；

函数、接口参数太多等。

解决复杂度问题的基本原则是，把一个系统拆解为多个子系统，用抽象和分层的方法，让我们同时只面对有限的信息，并且能够有条理地深入到每一个子系统中查看细节。具体的解决方法有：

对系统进行二进制组件或者代码层面的解耦；

使用简单化的设计编码原则，避免不成熟的优化；

对常见的代码“坏味道”做出一些规范，比如限制代码行的长度、禁止循环依赖、限制圈复杂度（Cyclomatic complexity）；

对复杂的设计添加注释。

第二是，重用性相关。

软件开发的另一个重要原则是 DRY，即 Don’t repeat yourself。代码重复是一个很常见的技术债，在软件抽象的各个层次（比如应用、架构、组件、代码）都会出现。避免重复的具体方法有：

应用层面，复用业务单元，典型案例就是业务中台；

架构层面，复用基础设施后台；

组件层面，避免出现责任重叠的组件、数据存储等；

代码层面，避免出现重复函数、代码块。

接下来最后一步，就是要持续性地重构，去解决高优先级的技术债任务。

4. 持续重构，解决高优先级的技术债

作为技术管理者，除了业务目标外，还要制定团队的技术目标，来解决最重要、最紧急的技术债任务。

技术债任务的具体处理方法有两种：一种是，把技术债的任务和业务相关的任务放到一起，在每一个迭代中持续完成；另一种方法是，采用突击的方式，在某个特定的时间段集中解决技术债问题。

比如，我在 Facebook 和微软的时候，我们团队就都使用过 Bug Bash 的工作方式，也就是在每几个迭代以后，专门花几天时间来解决前面遗留下来的 Bug，而不开发新功能。这样做的好处有两个：

第一，集中精力修复 Bug 可以减少上下文切换，能够更聚焦在提高产品质量上，因为提高质量和写新功能的思路是有区别的。

第二，能够让团队成员短暂地从紧张的业务气氛中脱离出来，从而精力充沛地投入到下一个业务开发迭代中去。

小结

在今天这篇文章中，我与你介绍了要想让开发工作能够持续地快下去，正确的做法是在恰当的时间“举债前行”，而在平时的开发工作中要持续定位技术债任务，并解决高优先级的部分。

为了帮助你理解技术债与公司业务发展的关系，我再和你分享一个案例。A、B、C 三个公司对待技术债的态度分别是：

A 公司：只关注业务，不偿还技术债；

B 公司：持续关注技术债，但对业务时机不敏感；

C 公司：持续关注业务和技术债。对业务机会很敏感，敢放手一搏大量借贷，也知道什么时候必须偿还技术债。

A 公司在开始的时候，业务产出会比较多，但由于技术债带来的影响，效率会逐渐降低。

B 公司在开始的时候，业务产出比较少，但由于对技术债的控制，所以能够保持一个比较稳定的产出，在某一时间点超过 A 公司。

C 公司在有市场机会的时候，大胆应用技术债，同时抽出一小部分时间精力做一些技术债预防工作。这样一来，在一开始的时候，C 的业务产出输出介于 A 和 B 之间，但和 A 的差距不大。

随后，在抢占到一定的市场份额之后，C 公司开始投入精力去处理技术债，于是逐步超过 A。另外，虽然 C 公司此时的生产效率低于 B 公司，但因为市场份额的优势，所以总业绩仍然超过 B。并在高优先级技术债任务处理好之后，生产效率也得到了提升，将 B 公司也甩在了身后。

这个例子很有代表性，你可以用它来说服管理层在偿还技术债上做投入。

思考题

经济债务可以申请破产保护，你觉得技术债可以有这样的福利吗？为什么呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

15 | 开源：从Phabricator的开源历程看开源利弊

你好，我是葛俊。今天，我来和你聊聊开源这个话题。

从克莉丝汀· 彼得森（Christine Peterson）1998 年提出“开源”这个名词，到今天已经 21 年了。可以说，在这些年里开源改变了软件开发世界。如今，开源覆盖了 IDE、移动端开发、前后端开发、运维、服务治理、AI 等众多领域的项目。比如，GitHub 上 2018 年最流行的前十个项目，包括 VSCode、React Native、Angular、Ansible、Kubernetes、TensorFlow 等，对这些领域都有覆盖。

从使用者的角度看，开源软件的价值不言自明。可以说，99% 的科技公司都在使用开源软件。

从贡献者的角度看，前十个项目中有 8 个项目的背后都有公司做支撑。毫无疑问，开源对公司来说也有吸引力的。但是，很多公司并没有开源，尤其是国内做开源的公司更是比较少，原因是什么呢？其实，就是因为开源有很多坑。

我在 Facebook 时参与了 Phabricator 开源的全过程，见证了其为公司带来的好处，比如因为模块化带来的代码质量提升、从开源社区获得的资源支持，也见证了开源的一些弊端，比如因为和开源社区目标不一致而带来的运维成本增加，以及最终导致的项目 Fork。

Phabricator 的整个开源过程

一般来说，开源一个项目的流程包括以下九步：

公司 / 员工对某项目有开源的意愿；

权衡利弊决定是否开源，以及后续的维护计划；

法律和信息安全方面的审核；

选择 License；

选择 Contributor License Agreement；

选择版本控制代码服务商（比如，GitHub、GitLab、BitBucket 等）；

代码模块化，与公司代码分离；

正式开源，发布信息；

项目维护和持续开发。

接下来，我就以 Phabricator 的开源过程为例，帮助你理解公司进行开源的利弊，以及使用它来提高研发效能的一些原则和实践。

Facebook 决定对 Phabricator 进行开源

Phabricator 源自 Facebook 内部对代码审查的需求，后逐渐发展为软件开发的一个 Web 工具套件，包括代码审查、代码仓托管、缺陷跟踪、项目管理、团队协作等应用程序。

开源之前，Phabricator 主要由开发工具团队维护并增加新功能，其他开发人员也会向其贡献代码。它发展得非常快，为 Facebook 的开发和质量保障提供了很大帮助。但，Phabricator 有个问题就是，经常会出现严重的性能问题。具体来说就是，Phabricator 的速度会随着时间推移而逐步下降，每隔一年左右，就会达到让开发人员无法忍受的地步。

导致这个性能问题的主要原因有两个：

第一， Phabricator 和 Facebook.com 在同一个代码仓，共享 Facebook.com 的底层代码库，但开发人员对 Phabricator 的响应速度要求比用户对 Facebook.com 要高。

第二，非开发工具团队的开发人员在增加功能的时候，因为不了解全貌，所以在贡献代码的同时，往往会降低 Phabricator 的速度。一个功能降低的速度看不出来，但累积起来，总体速度的下降就很明显了。

所以每隔一年左右，我们就需要对 Phabricator 做一次重构，来提高响应速度。

2010 年年中的时候，这个性能问题再度爆发了，开发工具团队决定认真思考有没有更好的解决办法，从根本上解决这个问题。经过仔细分析，我们得出的解决方案就是和 Facebook.com 解耦。

正好这个时候，开源社区对 Phabricator 的代码审核功能非常感兴趣。我们认为开源 Phabricator 或许是一个可行的办法，同时调研结果显示开源 Phabricator 有以下好处：

Phabricator 是一个内部工具，不是面向用户的产品，开源非但不会影响公司的核心竞争力，还可以提高影响力。

开源自然而然地就会把它从主代码仓剥离出来，实现与 Facebook.com 的解耦，实现提速。

开源意味着代码从此要公开出去，更多的人可以看到。这就给 Phabricator 的开发人员带来压力，让他们更关注产品质量。这样一来，Phabricator 的性能就会更有保障。

可以利用开源社区的开发资源。

当然，开源 Phabricator 也有缺点：

开源之后，Phabricator 势必要支持更加通用的开发场景，这可能就会影响对 Facebook 特有场景的支持。

开源之后，代码不会像在内部那样容易管控，灵活性会降低。

经过分析，我们认为可以使用插件的形式，从技术上解决对 Facebook 特有开发场景的支持问题。也就是说，在 Facebook 内部创建一个单独的代码仓作为插件，集成到开源的 Phabricator 之中。

而对 Phabricator 的代码管控问题，我们可以把它放到 Facebook 组织之下，从而保留比较强的管控力。

所以综合分析，内部工具团队以及上一级的基础平台团队，决定这一次的重构目标是开源 Phabricator。

开源准备工作

在确认了开源 Phabricator 之后，我们还要完成一些非开发的准备工作。

第一，法律和信息安全方面的审核。

这一步主要是确认此项目的开源，会不会使公司面临法律和信息安全方面的风险，由公司的律师团队和安全专家操作。一般来说，法律风险重点关注是否会泄露自己公司以及第三方公司知识产权；信息安全方面关注是否会暴露公司的安全漏洞。

第二，选择授权协议。

授权协议包括开源软件授权协议（Open-source License）和开源贡献协议两种。

其中，开源软件授权协议，指的是使用者享有的权利和受到的限制，比如 GPL、MIT、Apache 等协议。Phabricator 选择的是 Apache 2.0。这里，有两个工具可以帮助你做出选择，分别是“怎样选择开源协议？”和“开源指南”。

开源贡献协议，指的是对软件贡献者权力的限定，目的是赋予开发者对开源项目贡献代码的权力，并赋予项目管理者按照软件授权协议去发布软件。它包括 CLA（Contributor License Agreement）和 DCO（Developer Certificate of Origin）两种。Phabricator 选择的是 CLA。关于这个协议的选择，你可以参考“CLA 和 DCO 的区别”这篇文章。

因为具体选择哪个协议与法律有关，所以我只给出了参考链接，如果你的公司需要开源项目，推荐你去咨询律师。

第三，选择版本控制代码服务商。当时我们选择的是开源方面最流行的 GitHub。

开源具体步骤

完成了准备工作之后，剩下的就是正式的开发工作了。这部分工作主要包括以下三步。

第一步，把 Phabricator 代码和 Facebook 代码解耦。

我们做了一次比较彻底的重构，把分散在各处的代码，集中到 5 个代码仓里，分别是底层的 API 库 Libphutil、网站应用集 Phabricator、客户端 Arcanist、文档系统 Diviner 以及 Facebook 内部功能插件模块，完成了 Phabricator 的模块化。

第二步，进一步优化性能。

针对代码的性能，尤其是底层的 API 库，我们进行了很多优化。因为开源以后只需要支持通用的开发场景，所以我们不必考虑原来在 Facebook 代码仓的复杂调用，更容易去针对性地提高性能。

第三步，支持功能定制。

功能定制是开源的主要难点。除了解耦，我们还需要保证在解耦之后，仍然能够灵活地添加 Facebook 开发人员需要的定制需求。主要有以下三种方法：

Phabricator 提供对象的字段（Field）、类、库 3 个级别的扩展，我们主要采用库级别的扩展实现对 Facebook 的定制功能。

Phabricator 提供接口，供 Facebook 内部工具调用。

Facebook 内部工具代码提供接口，供 Phabricator 调用。

这样一来，我们就实现了 Phabricator 和 Facebook 其他内部工具的无缝集成。

除此之外，为了把 Phabricator 的部署从 Facebook 内部工具拆分出来，我们还需要完成以下工作：

数据库的迁移，即把 Phabricator 的相关数据从 Facebook 的数据库中迁移出来。

Phabricator 的部署。开源前 Phabricator 属于内部工具网站的一部分，所以不需要单独部署，但开源后我们需要给它重新设计和实现一套部署系统。

完成这些开发工作后，Phabricator 不仅从 Facebook 中剥离了出来，还显著提高了代码质量，比如模块化更好、注释更清晰、性能更好等。这些正是开源为 Facebook 带来的重要好处。同时，因为参与开源项目可以回馈社区并提升个人影响力，所以公司内部的 Phabricator 开发人员也都热情高涨。

但开源也意味着，我们需要投入额外的精力去实现 Phabricator 与其他内部工具的无缝集成，才不会影响 Facebook 开发者的使用体验。这，也是开源要付出的代价。

开源初期发展

完成开发工作后，Facebook 正式对外宣布了 Phabricator 的开源，同时正式切换到新部署的 Phabricator 集群。整个切换过程比较顺利，只是在一开始的时候，Phabricator 和其他工具间的联动出现了一些 Bug，修复之后就稳定下来了。

于是，Phabricator 也就开始进入开源的代码仓和内部的插件代码仓同时开发的阶段。针对 Facebook 的内部需求，我们尽量把它通用化，放到开源的代码仓中实现；实在需要定制的，才会放到 Facebook 的插件代码仓中。

这时，我的一位同事从 Facebook 离职，去了开源社区全职为 Phabricator 工作。他还创立了一家公司，致力于 Phabricator 的商用。于是，我们在开源社区也有了更强大的资源支持。

从 2011 年年初开源到 2013 年年底我离开 Phabricator 项目，Facebook 和开源社区对 Phabricator 的发展目标是一致的，所以一直在合力增加 Facebook 需要的功能，合作得非常好。总的来说，我们的确充分利用了开源社区开发者对 Phabricator 的贡献。同时，业界的很多著名公司开始使用 Phabricator，包括 Uber、Pinterest、Airbnb 等，提升了 Facebook 的声望。

Fork

2014 年开始，开源社区支持的公司越来越多，而它们的使用场景和 Facebook 不太一样，也就是说 Facebook 要想继续使用 Phabricator 的最新版本，就必须花费较大成本进行版本更新及维护。

而因为 Facebook 在 Phabricator 的使用上累积了非常多的数据，所以每一次数据库的 Shema 变动，都会带来非常麻烦的数据迁移工作，常常需要 DBA 的帮助才能实现不中断服务的版本更新。

考虑到这些新增功能对 Facebook 用处不大，而维护的成本又很高，所以 2014 年下半年 Facebook 决定停止使用外部开源的 Pabricator，重新在公司内部自己维护一套 Fork 的 Phabricator 代码。这样一来，开源版的 Phabricator 引入新功能的时候，Facebook 只在需要的情况下，才会参考外部的实现在内部引入。

其实，公司和开源社区目标不一致的现象比较普遍。在我看来，这可以算是开源项目的第一大坑。Facebook 对 Phabricator 采取的措施是内部 Fork，让开源社区继续自由发展，既然不能从开源社区得到资源，就把代码挪回公司内部获取完全的管控和自由度。这是处理目标不一致问题的第一种方法，你也可以借鉴。

第二个办法是，对代码仓强管控，但结果往往是开源社区 Fork 一个新项目重起炉灶，和第一种方法的结果其实差不多。

除了 Fork 之外，还有第三种办法，就是采用不同的分支来支持不同的目标。这样的好处是，公司依然可以获得开源社区的资源支持，坏处是分支管理、版本管理繁琐，也缺乏 Fork 的灵活性。

以上，就是 Facebook 开源 Phabricator 到最终 Fork 的全过程。我在这其中讲述了 Facebook 处理具体开源问题的一些方法，你也可以借鉴到自己的项目中。

开源对公司的利弊

这里，为了帮助你加深理解，我把开源对一个公司的利弊做了总结整理，如下表所示。

总的来说，我认为开源在以下两种情况下最为有利：

第 1 种情况是大公司。不难发现，上面列举的各种好处对大公司比较明显，提高公司声誉就是典型例子。所以，2018 年 GitHub 前十名开源项目中，除了 NPM 和 Ansible 外，其他的 8 个项目都是由 Microsoft、Facebook、Google 三个大公司支撑。

第 2 种情况，需要通过开源获取影响力从而扩展业务的公司。比如，Docker 公司在开源 Docker 项目之前，名气并没有多大，但是把 Docker 项目开源之后，一下子就变成了明星企业，在改变了 Pass 发展格局的同时，也改变了自己的命运。

另外，从适合开源的项目的角度来看，平台、基础设施、工具等（比如，Phabricator 以及 2018 年 GitHub 前十名开源项目）适合开源，而业务层的项目因为通用性不强，不适合开源。

小结

开源正在改变软件开发的格局，选择开源自己的项目对公司来说也是有利有弊。所以，今天我以 Phabricator 的开源过程为例，和你分享了开源一个项目涉及哪些步骤，在这其中获得的好处以及需要付出的代价。

开源对公司的好处，主要表现在提高代码质量、得到开源社区的免费帮助、提高开发者的积极性、提高公司声誉、回报社区等。而缺点和挑战，主要包括定制困难、内外协调，以及版本维护等。如果你的公司或者团队在考虑是否开源，可以将这些利弊作为参考。

在我看来，Phabricator 算是开源的一个成功案例。因为在整个过程中，我们充分利用了开源带来的好处，而且在开源过程中投入的开发资源，即使不开源也是需要的。

一定程度上讲，开源 Phabricator 的过程，也体现了 Facebook 的实用主义。在需要开源的时候，放手去开源；在发现维护的性价比不好时，就果断 Fork。虽然从个人情感的角度说，我不愿意看到 Phabricator 在 Facebook 内部最后 Fork 了，但理智地看这的确是一个很好的决定。

思考题

跟硅谷相比，国内公司参与开源的非常少。你觉得主要原因是什么，将来的趋势又会是什么样的呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

16 | 高效上云：如何用云计算来提高效能？

你好，我是葛俊。今天，我们来聊一聊，如何使用云计算来提高研发效能。

自从 AWS 的出现，云的崛起已经成为软件开发不可阻挡的趋势。它逐渐像水和电一样，成为软件开发的一项基础设施。不容置疑的是，云极大地提升了软件研发的效能。

以我之前在 Stand 公司开发社交 App 的工作为例，项目刚开始时只有 3 个研发人员（包括两个后端和一个前端开发者）。我们使用了 AWS 的云服务，三个月就上线了第 1 个手机版本，而且是可以弹性伸缩稳定支撑百万月活的版本。在云出现之前，这是难以想象的。

所以说，怎样高效地使用云，包括公有云、私有云和混合云，对每一个团队来说都是一个绕不过去的话题。

云计算的话题很大，但我们今天重点要说的是，落实到研发效能上我们应该怎么做。我将其归纳为以下两点：

第一，深入理解在云上进行研发以及运行服务的特点，充分利用它的优势，比如服务化、自助化和弹性伸缩；

第二，了解并妥善处理它带来的挑战，比如分布式系统带来的安全和控制方面的问题。

接下来，我们分别看看这两个方面吧。

理解并利用云计算的优势

我们先简单看看云计算的定义：云计算把许多计算资源整合起来，使用软件实现自动化管理，通过网络快速提供给用户使用。其中，计算资源，包括服务器、存储、数据库、网络、软件、分析服务等。也就是说，云计算通过自动化和自助化，使得计算能力成为一种商品，在互联网上流通，就像水、电、煤气一样，可以方便地取用，且价格较为低廉。

介绍完云计算的定义，我们再来看看它的特点。云计算的特点包括，大规模、分布式、虚拟化、按需服务、高可用、可扩展等。其对研发效能的提升，我觉得主要可以归结为服务化、自助化和弹性。下面，我们分别来看看这三个方面吧。

服务化

在服务化方面，云服务按照抽象程度分为 3 类，包括：基础设施即服务 (IaaS)、平台即服务 (PaaS) 和软件即服务 (SaaS)。它们也被称为云计算栈，最下层是 IaaS，最上层是 SaaS，越上层的服务越抽象，也就是把下层细节封装的越多，由云平台处理的越多。当然，如果不使用云的话，这些所有的服务和资源都需要我们自己管理。

如下图所示，分别列举了这三种服务方式中，哪些服务和资源由云服务商提供，哪些由我们自己处理。

备注：图片引自https://www.bmc.com/blogs/saas-vs-paas-vs-iaas-whats-the-difference-and-how-to-choose/

可以看出，细节抽象得越多，云服务商负责的部分就越多，我们就越能够聚焦自己的业务，从而提高研发效能。所以，在业务开发中，我们应该尽量使用抽象的服务，也就是说尽量使用 SasS，不行就使用 PaaS，还不行再使用 IaaS。

再以 Stand 的 App 开发为例。因为那款社交 App 与金融相关，同时客户增长速度还不确定，所以我们对数据库的要求包括高可用和可扩展两个方面。如果我们自己来维护数据库的话，需要招一个专门的 DBA，同时还需要一定的时间去搭建。但，如果使用 Pass 服务提供的 RDS（关系型数据库）的话，高可用和可扩展都可以由 AWS 承担，我们可以全身心地投入到业务开发中，快速上线产品。

当然，正是因为 RDS 这个 PaaS 服务给用户提供了非常大的价值，所以它的收费也很高。在我们使用的所有 AWS 服务中，数据库花费大概占到了 70%。我推荐你在权衡使用 IaaS、PaaS 或者是 SaaS 产品的时候，综合考虑其提供的价值以及价格。一个常见的模式是，初创公司在业务刚起步时，使用 SaaS 或者 PaaS 快速开发业务；业务成长到一定规模之后，再逐步转到 IaaS 以及私有云降低成本。

自助化

云服务的第 2 个特点，也是它的重要优势，是自助化。也就是说，用户在使用云上的资源和服务时，尽量减少人工参与，主要采用自动化的方式，比如通过工具或者 API 调用来完成工作。这样一来，既降低了成本，也提高了使用的灵活性和方便性。比如，使用云计算我们可以用一个 API call 去获得一个环境，再用另一个 API call 就可以部署一个版本。这相比传统的、用手工填表的方式，效率不知道要高出几个数量级。

总结来讲，自助化在软件开发中的价值主要表现在开发环境的获取和 CI/CD 流程两个方面。

开发环境的获取，包括开发机器环境、联调环境的获取，你可以再回顾下第 11 篇文章中的相关内容。这是自助化最典型的价值。

方便实现 CI/CD 流程是自助化提供的第二大优势。比如，在持续发布方法中有一个很重要的评判标准，就是看你的团队能够在多长时间内把一行代码的改动部署到生产环境上去。现在做得比较好的互联网公司，大都能够做到分钟级。这一点之所以重要，是因为只有能够快速部署，才能快速修复错误，从而才敢于快速试错，实现快速创新，在竞争中获得优势。

这其中的工作很多，包括拉分支、合并代码、构建、测试验证、部署、监控，以及可能的回滚。要实现分钟级的部署，我们必须要把服务化和自助化用到极致。而云服务提供的各种自助化，就大大降低了 CI/CD 流程自助化的实现难度。

弹性和共享

云服务的一个基本理念是，把大量的资源集成起来，然后共享给多个客户使用。正是因为多个客户共享资源，所以可以实现单个客户的按需使用。也就是说，客户 A 不使用资源的时候，可以释放出来供其他用户使用，A 也不必对这部分资源付费。同时，A 在需要大量资源的时候，资源池里通常可以有空闲的资源供它使用。这也就是我们所说的弹性。云服务的弹性，是可以用来提高研发效能的第三大特性。

接下来，我与你分享下具体如何利用云服务的弹性。比如，业务量的大小不确定，对很多公司的技术和基础设施都是一个巨大的挑战。如果我们使用自己的机房，不可能非常灵活地添加、删除资源，所以需要投入大量精力进行容量规划，以防止后期业务超过系统承载量而导致宕机。但这样的容量规划，在业务还不够大的时候必然会造成资源浪费。而使用云计算，我们就可以很好地解决这个问题。

如果使用的是公有云，那么你使用的服务本身就可以自动扩容，实现弹性伸缩，所以一定要在可以使用弹性伸缩的地方充分利用它。 比如，我在 Stand 公司的时候，我们的网站后端服务器、消息队列中间件、数据库、压测环境等，都使用了弹性伸缩，而且是尽量使用自动弹性伸缩的功能。

比如，后端服务器可以根据 CPU 的负载量来调节服务节点数量，达到一定阈值之后，就自动产生新的服务器并添加到集群中；当负载量降低到某一个阈值时，又会自动释放一些机器。从而，既实现了在业务量突增的时候，能够保证业务的正常运行，又能最大限度地降低成本。

如果使用的是私有云，你的服务和其他公司就不能共享。所以，从共享的角度看，不如使用公有云的好处大。不过在这种情况下，公司内部的各种不同服务、不同团队之间的资源共享，也可以给你带来收益。

比如，你可以让开发环境、测试环境、类生产环境、生产环境、内部工具系统环境都使用同一套 Kubernetes 集群，在其中使用不同的 namespace 进行隔离。这样就可以实现这几个环境的资源共享。又比如，你可以在公司内部署一套 OpenStack 私有云环境，让公司的各个产品线都在这个私有云上工作，从而实现团队之间、产品之间的资源共享，享受弹性和共享的好处。

处理云带来的分布式计算的挑战

云计算在给我们带来巨大方便的同时，也因为它的新特性以及开发模式带来了相当大的挑战。在我看来，云计算带来的最大挑战在于，为了使用云的弹性伸缩能力，我们的软件架构必须是分布式的，支持水平扩展。

而这种分布式的架构和传统的单体架构区别很大，如果处理不好，会给我们带来很大麻烦，最典型的例子当属微服务。微服务架构非常适合云计算，能够充分利用它的弹性伸缩能力。但如果使用不当、管理不好的话，就会出现调用混乱、依赖不清晰、难以维护的问题。

分布式计算有很多挑战，从我的经验看来，做好以下两点特别重要：

自治和集中管理相结合。要做好分布式计算，我们首先要让解耦的产品团队能够独立进行产品的设计、开发、测试和上线，这样才能真正利用解耦带来的灵活性。但同时，我们必须要有一定集中式的管理，这样才能把控全局。
这其中最重要的是信息可视化，比如系统整体的质量看板，能让大家一眼看到整个系统中各部分的运行状况；又比如，针对调用链复杂难以调测的情况，建设微服务调用链追踪系统，收集每一个客户端请求从发出到被响应经历了哪些组件、哪些微服务、请求总时长、每个组件所花时长等信息，以帮助我们定位性能瓶颈，进行性能调优。

错误处理。分布式系统，因为有很多组件同时运行，很多组件都有可能会出错。所以，我们必须要对这些错误进行处理，保证局部错误不会对全局带来非常大的影响。具体来说，可以使用的办法有：

信息可视化。通过数据可视化、监控、预警，迅速发现错误以便及时处理。

错误隔离。比如，微服务的一大好处就是，把错误限制在一个小的服务中。

提高系统容错性。只进行系统拆分还不能满足系统对容错性的要求，我们还需要确保一个服务的问题不会影响其他服务，形成所谓的“雪崩效应”。

自动修复能力。也就是说，出现问题能够自动修复，可以大大提高系统的可用性。一个最简单的办法就是，重启服务。

小结

在今天这篇文章中，我首先与你介绍了云计算对提升研发效能的作用，第一是服务化，第二是自助化，第三是弹性伸缩。

在产品设计和日常工作中，我们应该注意考虑如何利用云计算的特性，比如快速获取环境、自助化部署、在更高的抽象级别使用资源服务，来提高生产效率。

然后，我与你介绍了云计算带来的挑战以及一些应对方案。正是因为云计算带来了分布式计算，所以它的安全和控制是最大的挑战。具体的解决方法有，自治和集中管理相结合、信息可视化、错误隔离、提高系统容错性、自动修复等。

在我看来，云计算的确可以提高开发体验以及产品上线的效能。比如，在 Facebook 的时候，我们使用自研的容器及管理系统 Tupperware，极大地方便了各个环境的获取以及系统的使用效率；又比如，我们的测试、构建、分支管理都做到了很好的服务化、自助化，为产品的快速发布和高质量奠定了基础。

同时，我们也在处理分布式计算方面，投入了很多精力。举一个典型的例子，为了提高系统各个服务的容错能力，Facebook 会在某一时刻对某一个数据中心的所有服务器断电进行测试，并事前会通知每一个服务的所有者，确保自己的服务在这样的极端情况下仍能保证业务健康运行。

而我在创业公司的时候，更是充分利用了公有云提供的各种服务来快速上线产品，实现了传统基础设施环境下不可想象的高效能。虽然云计算现在还没有像水、电、煤气那样普遍，但我相信那一天不会太遥远。

思考题

在你当前的工作中，你觉得最能够使用云计算来提高研发效能的地方是什么？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

17 | 测试左移：测试如何应对新的开发模式？

你好，我是葛俊。今天，我们来聊聊测试这个话题。

为什么需要测试左移，测试右移？

测试可以保证产品质量，重要性不言而喻。但，要做好测试也比较困难，需要克服很多挑战。尤其是，持续交付、敏捷开发等开发模式为传统软件测试方式带来了更大的时间压力。

我们先来看看下面这种熟悉的测试方式都有什么问题：测试人员接到项目后参与需求评审，然后根据需求文档写用例、准备脚本，等开发提测之后正式开始测试、提 Bug、回归，测试通过后就结束了，项目交给运维上线，之后投入下一个项目继续重复这样的流程。

这样的流程看似没什么错，但有两大问题：

测试人员非常被动。当需求质量、开发质量较差的时候，测试人员只能被动接受。

但同时，测试又被认为是质量的责任人。如果因为需求质量、开发提测质量差而导致上线延期，大家通常会首先怪罪测试团队。

这些问题，在新的开发模式下愈发严重。因为这些新的开发模式有一个共同点，就是要缩短产品的交付周期，对自动化的要求越来越高，能够专门留给传统竖井流程中测试环节的时间越来越短，自然更难保证质量。

在极端的情况下，比如在持续部署的模式下，所有测试都是自动化的，已经完全没有留给测试人员专门进行手工测试的时间了。与此同时，测试的能力和质量又是这些开发模式成功的关键。否则，即使可以频繁地构建产品，质量不过关价值也为零。在我看来，《持续交付：发布可靠软件的系统方法》这本关于持续交付经典图书，更像是一本介绍测试的书，也是因为这个原因吧。

所以，在快速开发模式的挑战下，测试左移、测试右移就应运而生了。这些测试模式，能让测试人员拥有更多主动权，以及更多的时间进行测试。那，到底什么是测试左移和测试右移呢？

什么是测试左移和测试右移？

测试左移和右移，就是把测试的范围从传统测试的节点中释放出来，向左和右扩展。

向左扩展，就是让测试介入代码提测之前的部分。比如，扩展到开发阶段，在架构设计时就考虑产品的可测试性，并尽量进行开发自测等。另外，测试可以更进一步扩展到需求评审阶段，让测试人员不只是了解需求，更要评估需求的质量，比如分析需求的合理性以及完整性等。

类似的，测试右移，是让测试介入代码提测之后的部分。比如，测试人员在产品上线过程中，利用线上的真实环境测试。另外产品上线之后，测试人员仍然介入，通过线上监控和预警，及时发现问题并跟进解决，将影响范围降到最低。这样一来，测试人员不但有更多的时间进行测试，还能发现在非生产环境中难以发现的问题。

从测试右移的概念中可以看出，它与我们下一篇文章“蓝绿红黑灰度发布：这些五颜六色的发布到底怎么用？”中的各种部署模式强相关，所以我会在下一篇文章中与你介绍。而在今天这篇文章中，我会与你详细介绍测试左移的原则和实践。

测试左移的原则和实践

在我看来，要做好测试左移，有 3 个基本原则：

调整团队对测试的态度；

把测试添加到开发和产品需求步骤中；

频繁测试，快速测试。

接下来，我们具体看看这 3 个原则吧。

测试左移原则一：调整团队对测试的态度

调整团队对测试的态度，打破竖井的工作方式，是测试左移的前提。一个有效的办法是，按照功能的维度管理团队，让整个功能团队对产品负责。也就是说，如果产品质量出现问题，不只是测试团队“背锅”，而是会影响整个功能团队的绩效。同时，让质量问题的直接责任人承担更多的责任，来进一步增强团队成员的责任心。这种利益绑定的办法，虽然简单但非常有效，只不过出现质量问题时要记得进行根因分析，以避免再次出现类似问题。

另外，还要改变团队成员对测试工作的认知。传统的工作方式中，我们通常认为发现 Bug 最重要，但其实为了提高产品质量，更重要的是预防 Bug。所以说，在测试左移的过程中，我们应该更聚焦在预防 Bug 上。

测试左移原则二：把测试添加到开发和产品需求步骤中

测试左移的第一步，是把测试工作融入到开发步骤中。常用的办法是，让测试人员参与到开发阶段的方案设计中，了解开发的实现方式。因为很多开发人员可能只熟悉他负责的那一部分，而测试人员往往对全局更加了解，所以测试人员要评估改动范围以及是否有遗漏的模块和系统。

另外一个比较彻底，也很有效的方法是，我在第 8 篇文章中提到的全栈开发。当时，我们讨论全栈开发的场景主要是，通过运维团队提供工具和支持，让开发人员尽量参与到运维工作中去。对于测试来说，也是同样的道理。我们可以让测试团队转型，进行工具开发，并更多地去支持专项测试，比如性能测试、安全测试等，通过“使能”的办法，让开发人员完成功能测试，包括单元测试、集成测试等。

说到让开发人员完成部分测试工作，常常会听到很多质疑声。反对者认为，测试人员的心理模型跟开发人员不一样，他们更倾向于去找问题。而开发人员面对自己开发的产品，潜意识里就不愿意去找问题，比如，他们不愿意专门尝试各种边界输入进行测试，而把自己开发的功能搞崩溃。所以，开发人员和测试人员更适合分开。

这种观念，在 10 年前瀑布开发模式盛行时就深入人心。我曾经也非常认同，但在 Facebook 工作了 5 年后改变了看法。如果你能够把开发人员的责任界定得很清楚，谁开发的产品谁要保证质量，那么开发人员自然而然地就会去尝试做好测试，比如进行边界测试。而且，开发人员最了解自己写的代码，所以他能够最高效地对自己的代码进行测试。

当然，做全栈开发的同时我们仍会保留一部分功能测试人员，毕竟从竖井模式转变到全栈模式是一个循序渐进的长期过程。不过 Facebook 做到了极致，完全没有了功能测试人员，也就是我们所说的“去 QA”。

测试左移到了开发阶段之后，再往左移一步就到了产品设计阶段，在这里，测试人员除了解需求外，更重要的是评估需求的质量。

我推荐使用 BDD（Behavior Driven Development，行为驱动开发）的方法进行开发，促进团队在需求评审时更多地考虑测试。BDD 是通过特定的框架，用自然语言或类自然语言，按照编写用户故事或者用例的方式，从功能使用者的视角描述并编写测试用例，从而让业务人员、开发人员和测试人员着眼于代码要实现的业务行为，并以此为依据通过测试用例进行验证。

这里有一篇关于 BDD 的文章，推荐你阅读。

测试左移原则三：频繁测试，快速测试

测试左移的第三个重要原则是，频繁测试、快速测试。在测试左移之前，我们需要等待提测，比较被动，不能频繁测试。但测试左移到开发阶段之后，我们就有了很大的自由度去频繁运行测试，从而更好地发挥测试的作用，尽早发现更多的问题。

这里最重要的方法就是，我在讲持续开发时提到的几个关于验证的操作，具体包括：

规范化、自动化化本地检查；

建设并自动化代码入库前的检查流程；

提供快速反馈，促进增量开发。

你可以再复习下第 5 篇文章中的相关内容。

另外，为了能够顺利、频繁地运行测试，我们还要提升测试运行的速度。给测试提速的常见办法包括：

并行运行。比如把测试用例放到多台机器上运行，用资源换时间。

提高构建速度。比如使用精准构建，因为通常构建之后才能运行测试。

精准测试，也就是只运行跟改动最相关的测试。可以建立需求与代码的关系，以及需求与测试用例的关系，从而在代码改动时重点关注与之最相关的测试用例。

分层测试，即不同情况运行不同测试用例集合。

减少不必要的用例。比如，识别不稳定的用例，对其删除或者优化。

在这里，我再与推荐两个精准构建和精准测试的工具。一个是，Facebook 使用并开源的Buck系统，可以用来提高构建速度；另一个是，Google 开源的Bazel，支持精准构建和精准测试。

小结

在敏捷、持续交付等开发模式愈发流行的今天，产品的研发节奏越来越快，传统的开发提测之后进行测试，然后交给运维上线的测试模式受到很大的挑战。由此促成了测试左移和右移等新的测试方法，也就是测试向左扩展到产品设计、开发流程中，向右扩展到发布、生产流程中，从而在整个研发流程中持续关注测试，解决新模式给测试带来的挑战。

而测试左移，本质上是尽早发现问题、预防问题。基本原则包括：从人的角度出发，让产品、开发、运维人员统一认识，重视测试；从流程上，让测试融入产品设计和开发步骤中；快速测试、频繁测试。

其实，软件开发行业早就达成了共识，问题发现得越晚，修复代价越大。《代码大全》这本书，从软件工程实践的角度说明了修复⼀个 Bug 的成本在产品需求分析阶段、设计阶段、开发阶段、测试阶段有着天壤之别。比如，在集成阶段修复一个 Bug 的成本是编码阶段的 40 倍。除了成本悬殊之外，在修复难度、引入新问题的可能性、沟通成本、团队状态等方面也有很大的影响。

在我开来，Facebook 正是成功进行了持续测试，让测试融入到了整个研发流程中，从而没有 QA 团队也能保证产品的高质量。

思考题

你认为测试左移和测试右移，会不会减少测试人员的工作机会呢？如果你是测试人员，又应该怎么面对这个新的测试模式带来的挑战呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

18 | 蓝绿红黑灰度发布：这些五颜六色的发布到底怎么用？

你好，我是葛俊。今天，我来和你聊聊最近流行的一些部署、发布方法，以及测试右移。

最近几年，我见到了很多跟颜色相关的部署、发布方法，比如蓝绿部署、红黑部署、灰度发布等。今天，我会首先与你分享它们的基本定义和要解决的根本问题；然后，与你一起深入看一看高效应用这些方法的基本原则，以及一些具体的实践。

各种部署方式的定义

我们先来看看蓝绿部署（Blue-green Deployment）、红黑部署（Red-black Deployment）和灰度发布（Gray Release ，或 Dark Launch）的定义和流程吧。

蓝绿部署

蓝绿部署，是采用两个分开的集群对软件版本进行升级的一种方式。它的部署模型中包括一个蓝色集群 A 和一个绿色集群 B，在没有新版本上线的情况下，两个集群上运行的版本是一致的，同时对外提供服务。

系统升级时，蓝绿部署的流程是：

首先，从负载均衡器列表中删除集群 A，让集群 B 单独提供服务。

然后，在集群 A 上部署新版本。

接下来，集群 A 升级完毕后，把负载均衡列表全部指向 A，并删除集群 B，由 A 单独提供服务。

在集群 B 上部署完新版本后，再把它添加回负载均衡列表中。

这样，我们就完成了两个集群上所有机器的版本升级。

红黑部署

与蓝绿部署类似，红黑部署也是通过两个集群完成软件版本的升级。

当前提供服务的所有机器都运行在红色集群 A 中，当需要发布新版本的时候，具体流程是这样的：

先在云上申请一个黑色集群 B，在 B 上部署新版本的服务；

等到 B 升级完成后，我们一次性地把负载均衡全部指向 B；

把 A 集群从负载均衡列表中删除，并释放集群 A 中所有机器。

这样就完成了一个版本的升级。

可以看到，与蓝绿部署相比，红黑部署只不过是充分利用了云计算的弹性伸缩优势，从而获得了两个收益：一是，简化了流程；二是，避免了在升级的过程中，由于只有一半的服务器提供服务，而可能导致的系统过载问题。

至于这两种部署方式名字中的“蓝绿”“红黑”，只是为了方便讨论，给不同的集群取的名字而已，通过不同颜色表明它们会在系统升级时运行不同的版本。

灰度发布

灰度发布，也被叫作金丝雀发布。与蓝绿部署、红黑部署不同的是，灰度发布属于增量发布方法。也就是说，服务升级的过程中，新旧版本会同时为用户提供服务。

灰度发布的具体流程是这样的：在集群的一小部分机器上部署新版本，给一部分用户使用，以测试新版本的功能和性能；确认没有问题之后，再对整个集群进行升级。简单地说，灰度发布就是把部署好的服务分批次、逐步暴露给越来越多的用户，直到最终完全上线。

之所以叫作灰度发布，是因为它介于黑与白之间，并不是版本之间的直接切换，而是一个平滑过渡的过程。

之所以又被叫作金丝雀发布，是因为金丝雀对瓦斯极其敏感，17 世纪时英国矿井工人会携带金丝雀下井，以便及时发现危险。这就与灰色发布过程中，先发布给一部分用户来测试相似，因而得名。

好了，以上就是几种有颜色的部署发布方式了。如果你还有哪些地方理解得不够透彻，可以去网络上搜索相关文章，或者直接给我留言吧。接下来，我将继续按照黄金圈法则，来帮助你深入了解这些部署、发布方式的 Why、How 和 What。

蓝绿、红黑部署和灰度发布的 Why

究其根本，这些部署、发布方法，是为了解决频繁发布的生产模式带来的两个问题：

减少发布过程中新旧服务切换造成的服务停止时间。蓝绿部署和红黑部署都能实现无宕机时间部署（0 downtime deployment）。

控制新版本发布因为质量问题带来的风险。灰度发布就是一个例子。

蓝绿红黑灰度发布的 How

实现这两个目标的基本原则，是把服务上线过程拆分为部署、发布和发布后 3 个阶段，并充分利用这 3 个阶段的特点来提高服务上线的效率、质量和安全性。

这 3 个阶段的详细定义和特点分别是：

部署（deploy），指的是我们把一个代码包拷贝到服务器上运行，但并不把它暴露给用户，也就是并不给用户提供服务。这个阶段比较耗时，但因为还没有面向用户，所以风险很小。

发布（release），是把部署好的服务暴露给用户的过程，也就是开始真正上线服务用户了。这个过程可以通过负载均衡的切换很快实现，但风险很大，一旦出现问题损失就会比较大。

发布后（post-release），指的是服务完全上线以后的阶段。因为产品已经完全上线，我们的主要工作不再是预防，而是变成了监控和降低损失。

以红黑部署为例，从开始在新生成的集群 B 上部署新的版本，到线上的流量通过负载均衡指向 B 之前，是处于部署阶段；而负载均衡从 A 指向 B 的过程，就是发布阶段；等到负载均衡完全指向 B 之后，就进入了发布后阶段。

部署、发布、上线这几个名词，其实区分不太明显，我们平时在讨论服务部署上线时，也经常会混用。在这里，我之所以要和你明确区分这几个阶段，是因为我们可以针对每个阶段的特点来实现两个目标：

提高上线产品的效率，也就是减少发布过程中新旧服务切换造成的服务停止时间。

提高上线产品的安全性，也就是控制新版本引入的质量问题。

蓝绿、红黑部署和灰度发布 What

关于提高上线产品的效率，实践主要有两个：一是利用负载均衡切换线上流量，二是使用功能开关切换线上流量。这两种方法都比较简单。

而提高上线产品的安全性，相对来说就比较复杂了，但又很重要。因为在敏捷、持续交付等开发模式愈发流行的今天，产品的研发节奏越来越快，我们必须在上线过程中，在生产环境上进行更多的测试，以保证产品质量。

讲到这里，你可能一下就想到了，这正是我们在上一篇文章中提到的测试右移要做的工作。接下来，我就与你分别介绍如何在部署、发布、发布后这 3 个阶段提高上线产品的安全性，也就是测试右移的实践。

部署阶段的实践

在部署阶段，因为服务还没有真正面对用户，所以比较安全。在这一步，我们可以尽量运行比较多的检验。但一定要注意的是，我们在运行检验的时候，不能产生副作用，也就是不能影响到正在给用户提供服务的系统。具体来说，我们可以运行集成测试、流量镜像（shadowing，也叫作 Dark Traffic Testing or Mirroring）、压测和配置方面的测试这 4 种检验。

第一种检验是，集成测试。

集成测试，指的是对模块之间的接口，以及模块组成的子系统进行的测试，介于单元测试和系统测试之间。

传统的集成测试是在测试环境或类生产环境上进行的。这种方式的问题在于，测试运行的环境和生产环境差别较大，不容易发现生产环境可能会出现的问题。一个最典型的原因是，在这些非生产环境上，只有测试用例在运行，没有在处理任何真实的用户请求，所以在生产环境中运行集成测试，才可能发现在非生产环境上难以发现的问题。

在具体进行集成测试的时候，如果所做的操作没有产生数据，也就是不会产生副作用，会比较安全。如果产生了数据，我们一般有两种处理方法：

第 1 种方法是，对测试产生的数据添加一个“测试”标签。同时代码的逻辑，对有测试标签的数据都进行特殊处理，比如说完全忽略。

第 2 种方法是，对测试用例产生的请求，就直接不写数据。具体实现方法是，在业务里直接添加这个特殊处理的逻辑。如果你使用的是服务网格（Service Mesh），则可以使用服务的代理（比如 Sidecar Proxy）来进行处理。

第二种检验是，流量镜像。

流量镜像，指的是对线上流量的全部或者一部分进行复制，并把复制的流量定向到还没有面向用户的服务实例上，从而达到使用线上流量进行测试的效果。

关于引流的实现，通常是使用代理，比如 Envoy Proxy 和 Istio 配合使用。如果你想深入了解引流的实现方式，可以参考“使用 Envoy 做镜像引流”这篇文章。

需要注意的是，使用引流进行测试时，不能给生产环境带来副作用。具体办法与集成测试的处理方法类似，我们也可以给引流产生的数据打标签，在流量复制的时候，对复制的请求统一添加一个特殊字段（比如 shadow），从而让接收到请求的服务可以对其进行特殊处理。

使用流量镜像，除了普通的检测之外，还有一个比较有用的实践就是，对测试流量与实时服务流量的运行结果进行对比，来检查新服务的运行是否符合预期。Twitter 在 2015 年开源了一款这样的代理工具Diffy ，它可以在镜像流量的同时调用线上服务和新服务，并对结果进行对比。

第三种检验是，压测。

压测，也是在部署阶段比较有价值的一种测试方法。比如，我们可以把新服务部署到一个比较小的集群上，然后把线上环境的流量全部复制并指向这个新集群，以相对客观地了解最新服务的抗压能力。

第四种检验是，配置方面的测试。

系统配置方面的变更，一旦出现问题，往往会给业务带来重大损失。部署阶段，因为不直接面向用户，所以是测试配置变更的好时机。

如果你想深入了解这部分内容的细节，可以参考Facebook 关于可靠性的见解这篇文章。

发布阶段的实践

在发布阶段，我们可以使用金丝雀发布和监控两种方法，来及早发现错误，并减少错误带来的损失。

第一个方法是，金丝雀发布。

金丝雀发布，是发布阶段最基本、最常见的实践。这里，我两个小贴士：

让金丝雀服务先面向内部用户，也就是 Dogfooding，来降低出现问题时造成的损失。

最近几年出现的一些部署工具和平台，比如 Spinnaker，已经对金丝雀发布有了比较好的支持。你可以考虑直接使用，降低引入成本。

第二个方法是，监控。

监控，是安全发布必不可少的关键环节，其重要性不言自明。在发布过程中，我们应该注意监测用户请求失败率、用户请求处理时长和异常出现数量这几个信息，以保证快速发现问题并及时回滚。

发布后的实践

产品成功发布之后，我们的主要工作就是监控和补救，具体实践包括三个：监控、A/B 测试和混沌工程（Chaos Engineering）。

第一个实践是，监控。

服务上线后，我们需要提供有效的监控，来了解服务的质量。关于监控的内容，我推荐参考可观察性（Observability）的三大支柱，即日志、度量和分布式追踪。如果你想深入了解这部分内容，推荐你看一下这篇文章。

第二个实践是，A/B 测试。

系统上线之后发现问题，有一个快速的补救办法是，继续使用旧的服务代码。对于这一点，我们可以通过 A/B 测试的方法来实现。

也就是说，添加风险比较大的新功能时，使用 A/B 测试让新旧功能并存，通过配置或者功能开关决定使用哪一个版本服务用户。如果发现新功能实现有重大问题，可以马上更改配置（而不需要重新部署服务），就能重新启用旧版本。

第三个实践是，混沌工程。

混沌工程，指的是主动地在生产环境中引入错误，来测试系统的可靠性的工程方法。最早为人熟知的混沌工程，是网飞（Netflix）公司的 Chaos Monkey。这种方法可以引入的错误主要包括：

杀死系统中的节点，比如关闭服务器；

引入网络阻塞的情况；

切断某些网路链接。

不过，一般是在公司达到了很好的稳定性之后，对稳定性有更上一层楼的需求时，或者是对稳定性要求特别高的公司，混沌工程的价值才比较大。

小结

我首先与你介绍了一些常用的部署、发布方式，包括蓝绿部署、红黑部署和灰度发布。这些方法的目的，都是为了解决频繁发布的生产模式带来的挑战。而解决这些挑战最基本的原则是，把服务上线的过程拆分为部署、发布和发布后 3 个阶段，并分别进行优化。

在部署阶段，我们要充分利用服务还没有暴露给用户的特点，尽量进行集成测试、压测、配置测试等检测；在发布的阶段，我们主要是采取灰度发布的方式并配合使用监控，在出现问题时，马上进行回滚；而在发布后阶段，则有监控、A/B 测试以及混沌工程等实践。

在我看来，快速发布模式没有给测试留下足够的时间，我们的确需要在部署上线的过程中，在提高产品质量上多下功夫。Spinnaker 这种原生支持灰度发布的工具的出现和流行，也正表明了这一趋势。在这种在生产环境上进行测试的方式，最关键的是要做好风险控制。

另外，这种模式给测试团队带来了非常大的挑战。我觉得，在不久的将来，传统测试方式会越来越不流行。测试团队需要尽快转型，来适应这种新的开发模式。

思考题

你觉得金丝雀发布可以用在移动端应用或者桌面应用上吗？如果可以的话，大概要怎么实现呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

19 | 不再掉队，研发流程、工程方法趋势解读和展望

你好，我是葛俊。今天，我们就来聊一聊研发流程和工程方法的一些趋势吧。

软件行业从诞生之日起，就一直是充满了发展和变化，各种工程方法、研发模式不断涌现，而且涌现的速度越来越快。对于开发团队和个人来说，这既是挑战也是机会：说是挑战，是因为我们需要持续学习才能跟得上它的发展；说是机会，是因为如果能够快速学习并应用这些实践，我们就可以在竞争中取得优势。

这些挑战和机会，并不强依赖于资源、背景，其实是为我们提供了一个相对公平的竞争环境。这，也是软件行业这些年来涌现了许多白手起家的成功公司和个人的重要原因。

在今天这篇文章中，我会针对当前比较流行的研发流程、工程方法的趋势，尤其是与国内研发比较相关的部分，做一些解读和展望，和你说说我的理解、预测，希望作为你以及你的团队，在技术选型以及工程方法选择上的一些参考。

接下来，我将会从协作方式、云计算平台、应用开发和 AI 这 4 个方面与你展开讨论。

协作方式的相关趋势

在我看来，协作方式的相关趋势，主要表现在以下两个方面：

首先，团队远程办公、灵活工时办公，会越来越普遍；

其次，聊天工具和其他工具的集成，会越来越普遍。

接下来，我与你说说我为什么会有这样的预测吧。

团队远程办公、灵活工时办公，会越来越普遍

远程办公之前用得不是特别多，主要是因为沟通效率比较低，也不利于掌控团队氛围。但，视频会议以及团队协作工具的快速发展，使得这些问题不再那么明显了。

这时，远程办公的巨大好处，也就是可以克服人才的地域局限性，就凸显出来了。比如，很多人在考虑要不要应聘工作时，都会考虑通勤距离。所以，一旦能够打破这个地域限制，招聘的空间就会开阔很多。

远程办公的另一个好处是，可以大量减少通勤时间，进而提高研发产出。近些年来，交通拥挤、通勤时间过长的情况，越来越严重。美国有科学研究表明，如果每天上班单程超过 30 分钟，就会对人的情绪产生比较大的负面影响。而国内的一线城市，从我接触到的样本看，上班单程低于 30 分钟的情况大概只占 20%。

所以最近这些年，越来越多的公司开始或多或少地采用远程办公的方式，以此来缓解通勤时间过长给员工造成的压力。有些公司做得比较彻底，让绝大部分开发人员绝大部分时间都是远程办公，我知道的公司包括 GitHub 和 Atlassian。而更多的公司，则采用的是每周允许员工部分时间在家办公，比如 Facebook 的开发人员每周三可以在家办公。

至于灵活工时，本质是用任务来驱动。这一点比较符合软件开发的特点，在第 1 篇文章中我已经与你讨论过了，这里不再过多展开了。在硅谷，基本上所有的软件公司，都是这样操作的。在国内，这种情况也越来越普遍。

关于远程办公方式，我还有两个小贴士：

一是，要尽量使用视频会议，而不是电话会议。有研究表明，电话会议不如面对面沟通的效率高，主要是因为缺少了由面部表情和肢体语言传递的信息。

二是，做好信息的数字化。比如，建设好任务系统、文档系统等，从而让研发人员在工作中能尽量从这些系统中获取信息，而不用过于依赖面对面或者实时聊天系统，依然能够高效工作。

聊天工具和其他工具的集成会越来越普遍

聊天工具和研发流程中其他工具的紧密集成，最近几年在国外很流行，最典型的就是 Slack。它可以和任务工具、代码审查工具、部署工具、监控系统进行集成，从而实现我前面第 4 篇文章中提到的工具网状互联，提高研发效能。

所以，我觉得下面这种工作方式会越来越普遍：聊天工具里有各种各样的聊天室和聊天机器人，团队成员在不同的聊天室讨论不同的话题，并通过聊天机器人和其他工具进行集成和交互，来获取信息以及执行一些操作。比如，询问聊天机器人当前线上的服务分别是哪些版本、相关需求有哪些、Commit 有哪些、具体的开发和测试人员是谁等。又比如，通过运维机器人添加两台机器到集群中去。

聊天是人类最自然的沟通方式之一，所以在聊天室和机器人的帮助下，执行这些操作会非常高效，提高研发效能也就不在话下了。

云计算平台的相关趋势

正如我在第 16 篇文章中提到的，云计算正在改变我们开发软件的方式。利用好云计算平台的趋势，是提高团队研发效能必须要做的事儿。在我看来，云计算最大的趋势应该是 Docker 和 Kubernetes 带来的各种可能性。

Kubernetes 自诞生以来，背靠着 Google 公司的强大技术支撑和经验积累，发展得异常迅猛，现在，它已经成为了容器编排的事实标准。在我看来，其中最大的作用是，我们可以用它来建设 PaaS。

使用 PaaS，我们可以快速部署和管理应用程序，把容量预配置、负载均衡、弹性扩容和应用程序运行状况监控的部署细节交给平台来管理，让研发团队聚焦于业务，对高效业务开发极其有用。但是，PaaS 平台容易出现灵活性不足的情况。

比如，我之前在 Stand 公司开发后端服务时，非常希望能够使用 AWS 提供的 PaaS 服务（比如，Elastic Beanstalk 服务），来减轻团队在运维方面的工作压力，但试用之后，发现其无法支持一些定制化的需求，比如平台的技术栈的灵活性不够，而且更新的时候透明度不够。所以我们只能忍痛放弃，最终选择使用更下一层的 IaaS 服务，通过自己管理虚拟机来部署和管理服务。

而解决上述灵活性不足问题的一个方法是，灵活生成新的 PaaS 平台。但，PaaS 平台的建设必须依托于下层的 IaaS 才能实现，所以技术要求很高，工作量和资源要求也很大，只有专门做 PaaS 的公司和云厂商才有能力提供 PaaS。

Kubernetes 出现后，提供了强大的容器管理和编排功能，事实上是实现了一种基于容器的基础设施的抽象，也就是实现了 IaaS 的一个子类。所以通过它，我们终于可以方便地建设定制化的 PaaS 了，一个具体的例子是 FaaS（Function as a Service）。Kubernetes 的出现，极大地降低了建设 FaaS 的工作量，所以很快出现了基于它的实现，比如OpenFaaS、Fission。

正是基于 Kubernetes 提供的构建 PaaS 的能力，我预期，将来越来越的产品会构建在基于 Kubernetes 和 Docker 的 PaaS 之上。

今天，很多公司对 Kubernetes 还是直接使用，也就是通过一个对 Kubernetes 比较了解的运维团队，来支持公司的服务运行在 Kubernetes 集群上。但 Kubernetes 的学习成本比较高、学习曲线比较陡峭，整个系统的运行并不是那么顺畅。所以，我觉得将来的趋势将会是这样的：

如果你所在的团队比较小，可能会选择第三方通过 Kubernetes 提供的 PaaS 平台；

如果你所在的团队比较大，可能会基于 Kubernetes 建设适合自己的 PaaS 平台。

另外，我觉得很可能会出现这样的情况：整个公司运行一套 Kubernetes 作为 IaaS，上面运行多个不同的 PaaS 平台，支持各种服务的运行。如下图所示。

备注：CaaS（Containers as a Service），是允许用户通过基于容器的虚拟化来管理和部署容器、应用程序、集群，属于 IaaS 平台的范畴。

应用开发的相关趋势

随着云计算的普及，分布式计算会越来越流行，最典型的例子莫过于微服务的盛行。设计正确的架构，来支持产品的开发和部署，是云时代高效研发的重要因素。

在我看来，应用开发的相关趋势，主要表现在云原生开发方式和服务网格两个方面。

云原生的开发方式

应用程序运行在云端，需要基于云的架构设计，这就意味着我们需要一套全新的理念去承载这种开发模式。这套理念，就是云原生开发。

由 Heroku 创始人 Adam Wiggins 提出并开源、由众多经验丰富的开发者共同完善的12 原则（12-factor），是云原生开发理念的理想实践标准。

服务网格

复杂的服务拓扑结构，是云原生应用程序的一个重要难点。而服务网格正是用来处理服务间通信的专用基础设施，它提供了应用间的流量、安全性管理，以及可观察性，比较好地解决了这一问题。

在服务网格架构中，流量管理从 Kubernetes 中解耦，每一个服务对网络拓扑并不知情，通信都是通过代理来进行的。所以，我们就可以通过代理来方便地完成很多工作。

比如，在部署阶段进行的集成测试，我们就可以借助它来实现。假设 A 和 B 是系统中的两个服务，并且 A 会调用 B。我们希望在部署 A 的一个新版本时，在生产环境进行 A 和 B 的集成测试：

通过 A 的 egress 代理，让它对下游服务发出的请求都自动加上一个 x-service-test-b 的 header；

而在 B 的 ingress 代理接收到有 x-service-test-b 的请求时，自动通知 B 这是一个测试请求。测试完毕之后，修改代理去除这个 header 即可。

目前，关于服务网格有两款比较流行的开源软件，分别是Linkerd 和 Istio ，都可以直接在 Kubernetes 中集成，也日渐成熟。

AI 方面的相关趋势

这几年，AI 绝对是最火的话题之一。我们讨论研发流程和工程方法，自然也绕不过 AI。不过，AI 在软件研发上的应用还处于起步阶段。

在我看来，AIOps、CD4ML（Continuous Delivery for Machine Learning，机器学习的持续交付）、语音输入是比较适合在软件研发中落地的。

接下来，我们分别看看这 3 个方面。

AIOps

做好 AI 的前提就是，有大量的数据积累。而运维相关的工作，就有大量的线上日志、监控、指标等数据，所以 AIOps 是比较容易落地的一个方向。具体来说，我觉得AI 可以从以下两个方面来提高运维效率：

第一个方面是，检测并诊断异常。也就是说，通过对历史故障数据进行分析学习，找到规律，从而在新故障出现或者快要出现时，能够实时探测到，并给出一些可能的诊断。

第二个方面是，智能地对资源进行弹性的扩容、缩容及流量切换。目前，我们通常采用 CPU 利用率、内存利用率等少量维度的指标，去判断是否要扩容或缩容，判断逻辑比较简单。而利用 AI，我们可以收集更多维度的数据，综合分析后自动进行扩容或缩容，更合理地利用资源。另外，更进一步地，我们还可以利用 AI 做更智能的流量切换，进一步提高资源利用率。

CD4ML

在机器学习中有很多需要人工参与的步骤，我们可以通过提高这些步骤的自动化程度来提高其效率。CD4ML 就是将持续交付实践应用于开发机器学习模型上，以便于随时把模型应用在生产环境中。

语音输入协助软件开发

现在，语音输入效果已经比较好，应该算是 AI 最成熟的领域。从我个人来说，我就经常使用。比如，我会用 Amazon 的 Echo 玩游戏、用小米音箱控制家里的空调、通过 Siri 使用滴滴打车等。

实际上，我在写这些专栏文章的时候，就经常使用语音输入形成第一版文字，然后再手工编辑完善，能够节省不少时间。

在我看来，语音输入将来同样可以应用到软件开发工作中。比如，用语音来输入程序；再比如，通过移动设备上的语音输入完成一部分研发相关工作，包括申请机器、运行流水线，查看系统状态等。

小结

今天，我从协作方式、云计算平台、应用开发和 AI 这 4 个方面，与你分析了如何在软件开发工作中运用这些趋势，去提高研发效能。同时，我也对这些趋势做了大胆预测。当然了，欢迎你在留言中与我分享你的其他看法。

为了方便你学习、理解，我把这些趋势的重点内容整理到了一张表格中，供你参考。

除此之外，软件开发行业还有非常多的、创新性的方法和实践，我无法一一展开了。比如，移动端开发中，GraphQL可以高效、灵活地从后端获取数据，以及使用 JavaScript 之外的语言进行 Web 开发的框架，比如Vapor。

我一直很庆幸，能够在软件研发这个行业工作。因为它有足够的想象力，给了开发者持续发展的空间。上面提到的这些趋势和方向，都让我非常兴奋。希望能带给你一些帮助，至少引发你的一些思考。当然了，对这些趋势的选择、解读和预测，都只是我个人的观点，欢迎你通过留言与我分享你的看法。

思考题

使用 AI 来提高研发效能，是一个很有趣的话题。除了我今天提到的这些，你觉得还有哪些可能的方式吗？将你的预测与想法，分享给我吧。

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

20 | 答疑篇：如何平衡短期收益和长期收益？

你好，我是葛俊。今天，我来针对“工程方法”模块的留言问题，做一次集中解答与扩展吧。

针对“工程方法”模块的文章，很多同学留下了精彩的留言，阐述了自己对研发效能的认识、遇到的问题以及解决方案。

虽说我已经一一回复过你们的留言了，但有些问题在我看来非常有价值，值得再扩展一下。所以，我今天挑选了 3 个典型问题进行详细回答，并对每一个问题延展出一个话题和你讨论，希望能帮助你加深对这些问题的理解，切实帮助你提高你或者你团队的研发效能。

问题一：因为赶进度就慢慢不做代码审查了

@john_zhang 同学在第 13 篇文章中，提出了这样一个问题：

我们推行过一段时间的代码审查，因为团队只有三五个人，所以采用的是团队审查，每天半个小时左右，可惜后来赶开发进度，慢慢就没做了。现在开发同事总是以赶进度为由，不太认同代码审查，怎么破？

这条留言下有好几位同学都点赞了，看来这是一个常见的情况。所以，我再和你分享下针对这个问题的具体解决办法吧。

团队统一思想，代码审查是有效工作的一部分，应该计算到工作量里面。

减少团队审查，更多地使用工具进行一对一的代码审查。因为团队审查很难做到效率高，所以应该只是针对一些重点的提交才采用这种方式。

培训团队，统一认识，让大家看到代码审查的长期收益，比如考虑代码的可维护性以及后续添加新功能的速度，而不只是盯住当前的开发进度这个短期收益。甚至，我们可以把代码审查这个操作，作为团队的制度要求大家执行。

说到这里，又涉及了长期收益和短期收益的冲突要怎么权衡，这也是我们普遍关注的一个问题。接下来，我再和你扩展下这个问题吧。

比如，不止一个同学提到“公司连给研发人员配置好一点的电脑的意识都没有，何谈研发效能？” 的确，使用配置低的电脑能够节省当前的开支，但是会损害长期的开发效率。这里的短期利益便是当前开支，而长期利益就是公司的长期产出。

又比如，我在第 14 篇文章中与你讨论的技术债问题，借债是考虑短期利益，而还债考虑的就是长期利益。

从这些例子可以看到，短期利益和长期利益往往会存在冲突，我们必须做好取舍，才能最大程度地提高研发效能。在我看来，短期利益和长期利益的权衡，可以重点考虑以下两个方面。

第一，寻找两者最佳的平衡点。比如，在一条线段上，最左边的端点是只关注短期利益，最右边则是只关注长期利益，两个极端情况都不好。但是，我见到的大部分情况是，团队做的选择会特别靠近最左边。虽然说有些选择是出于业务压力情非得已，但有相当一部分情况，是因为短视造成的。由于太追逐眼前利益，而造成长期的生产力下降、研发人员积极性下降、产出降低的例子比比皆是。

为了避免后劲不足的情况，我们必须往中间移动，找到最佳平衡点。

第二，随着时间推移，这个最佳平衡点也会有变动。比如，在第 14 篇文章末尾，我给你举的 A、B、C 三个公司对待技术债的案例。C 公司在不同的时间分别选择了最合适的平衡点，很好地平衡了借债与还债，从而获得了竞争优势。这里，我建议你结合长期利益与短期利益的权衡，再返回第 14 篇文章，回顾下这个案例。

问题二：截屏上传工具链的实现

在第 11 篇文章中，我与你介绍了一个截屏工作链，具体步骤包括截屏、上传图片、获取 URL、缩短 URL、保存到系统剪贴板。这样一来，我们使用快捷键触发截屏流程之后，就可以直接用 Cmd+V 把图片的短 URL 粘贴到 Commit Message 或者聊天工具中了。

有许多同学对这个工具很感兴趣，询问我如何实现，以及是否有对应的开源产品。不过遗憾的是，我没有找到对应的开源产品和方法。所以，我再和你说说，我之前在 Stand 公司时是如何实现的吧：

图像存储用的是 Google Drive；

截屏上传工具是Share Bucket。它支持上传到指定的 Google Drive，同时还支持存储 URL 到剪贴板。还有一个类似的工具是MonoSnap。

至于 URL 缩短服务，我们当时并没有使用。如果你考虑使用的话，可以看一看开源的YOURLS。

这里，感谢 @日拱一卒同学，和我们分享了他们公司的实现方法：

我们的解决方案和你说的差不多，涉及到不同的工具，处理方式不太一样。

如果工具本身支持图片存储，例如 ZenHub 或者 JIRA，我们用工具本身来存储图片。

如果工具本身不支持图片存储，就用公司提供的网盘来存储图片，在工具中引用相关的链接。

在我看来，第二种方法会更好一点。因为有一个统一存储图片文件的地方，不同的工具都可以指向同一张图片。

其实，在 Facebook，还有另外一个类似的工具链，只不过它针对的不是截屏，而是拷贝文本。

我们在工作中常常会针对一段文本进行讨论，但如果这段文本特别长的话，把它拷贝到讨论工具中，就会占用很大的空间。所以，我们就用了一个工具链来解决这个问题。使用步骤也类似：

拷贝要讨论的文本。

用快捷键触发工具链。这些工具会自动把文本上传到一个服务器，然后把 URL 缩短，并把缩短后的 URL 保存到系统剪贴板里。

使用快捷键 Cmd+v 把短 URL 粘贴到讨论工具（比如 Commit Message）中，其他人点击这个 URL 就可以到文本服务器上查看具体的文本内容。

这个工具链涉及了文本存储服务，PasteBin是目前最流行的工具，不过它并没有开源，而且只提供 SaaS 服务。而我们日常工作中拷贝的文本，通常是不能放到公网上的。所以，PasteBin 很可能不适合你。这里，我推荐一个类似的开源工具HasteBin，你可以安装到公司内网使用。

关于这个截屏工具流，我想扩展的一个话题是，研发效能的提高带来的量变会产生质变，也就是说研发效能的提高，可能看起来只是做事情快了一些，产出高了一些，甚至有人会认为这并没有什么太大的作用。但实际上，效能提高累积到一定阈值之后，就可以带来质变。

以截屏工具链为例。在没有类似工具链的情况下，虽说可以手动实现上面的工作，但因为操作步骤太繁琐，所以我们在 Commit Message 里很少使用截屏。而这个工具链将这些繁琐的操作简单化了，由此带来的效率提升让绝大多数开发者都乐于在 Commit Message 中使用截屏去讨论问题。也就是说，这个工作流带来的效能提升累积到了一定的阈值之后，引发了质变。

提升研发效能的量变带来的质变，另一个表现是，效能的提升增强了公司整体的竞争力。当效能提高到一定程度的时候，就可以在公司竞争中起到重要作用甚至是决定性作用。

在我看来，这并不是夸大之词，尤其是在软件开发行业逐步成熟，需要比拼研发效能的今天。

问题三：Facebook 的 SEV 系统是做什么用的？

@Geek_f0179a 同学在第 4 篇文章中问到：Facebook 有一个 SEV 复盘系统，这个系统的主要功能是什么，会记录哪些关键信息呢？

SEV 系统是一个事故响应及根因分析系统，SEV 是严重性“Severity”的前三个字母。这个系统会根据事故的严重性为其分配一个数字，大概是 1~4：SEV1 最严重，SEV4 最轻。每次发生事故，除了记录严重性之外，还要按时间顺序记录事故发生、发现、定位、解决、确认解决过程的每一步的时长和具体操作细节。

每周公司都会定时举行 SEV 讨论会，基本只讨论严重的问题，比如 SEV1 和 SEV2 的事故。会议由高层管理者主持，事故责任人轮流进入会议室对事故进行描述和讨论。

这个会议的主要目的是，对事故进行回溯，分析根因以及如何避免再次出现类似问题。也就是说，SEV 系统的目的，重在总结提高，不要多次重复同一个错误，如果重复犯错要惩罚。

SEV 系统的效果很好，在避免问题重复出现的同时，我们也比较敢于试错，至少从我的经验看是这样的。

针对这个事故复盘系统，我想扩展的话题是：出现问题之后，到底应不应该追责，应该怎样追责？

在我看来，考虑这个问题应该从最终目的出发。复盘的目的是最大程度地减少以后再出现同一个问题的概率。那我们应该怎样处理呢？

如果每个错误都要惩罚，就会有以下几个问题：

因为害怕惩罚，尽量去掩盖问题而不是暴露问题、分析问题，或者尽量撇清责任甩锅。

惧怕闯祸，团队成员开始信奉“多做不如少做”。

因为担心指出别人的问题，会让他受罚，结果就是不愿意得罪人，导致问题被隐藏。

而另一个极端，如果犯错没有任何惩罚的话，又会造成以下问题：

同样的错误一犯再犯。

没有奖惩造成不公平。这就会让原本认真工作的员工失去认真做事的动力，降低对代码质量、产品质量的关注，提高质量的工程措施（比如，单元测试、开发自测等）更是难以推广。

所以，在我看来，针对错误更有效的处理方法应该是，以保持团队的主动性、责任感和执行力为原则，具体措施包括：

追究责任，但不是惩罚。知其然并知其所以然，搞清楚前因后果，避免重复犯同一个错误。

重复犯错一定要惩罚。

反复问为什么，找到根本原因。

小结

好了，以上就是今天的主要内容了。如果有哪些你希望深入了解还未涉及的话题，那就直接给我留言吧。

接下来，我们就会开启新的模块了，进入个人效能的讨论了，希望这个模块能够帮助你提升个人效能，持续成长，成为 10x 开发者，提升个人竞争力。

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

21 | 高效工作：Facebook的10x程序员效率心法

你好，我是葛俊。从今天这篇文章开始，我们就正式进入个人效能模块了。今天，我要和你分享的主题是，程序员如何高效地进行开发工作。

最近比较流行的一个说法是 10x 程序员，也就是 10 倍程序员，意思是一个好的程序员，工作效率可以达到普通程序员的 10 倍。要做到这一点并不容易，我们需要在编程技术、工作方式、工具使用等方面全面提高。

今天这篇文章，我将聚焦于如何提高自己的编程技术，给出在实践中被证明有效的 3 条原则，包括抽象和分而治之、快速迭代，以及 DRY（Don’t Repeat Yourself），并针对每条原则给出几个高效实践。而关于工作方式、工具使用等方面的内容，我会在后面几篇文章中与你详细讨论。

第一条原则：抽象和分而治之

虽然我们面对的世界非常复杂，但大脑只能同时处理有限的信息，那怎么平衡这个有限和复杂之间的矛盾呢？

最好的办法是，把一个系统拆分为几个有限的子系统，每个子系统涵盖某一方面的内容，并将其复杂性隐藏起来，只对外暴露关键信息。

这样，我们在研究这个系统的时候，就无需考虑其子系统的细节，从而对整个系统进行有效的思考。如果我们需要深入了解某一个子系统，再打开这个子系统来看即可。

以此类推，如果这个子系统还是很复杂，我们可以再对其进行拆分。这样一来，在任何时候，我们思考时面对的元素都是有限的，复杂度也下降到了大脑的能力范围之内，从而完成对一个复杂系统的理解和处理。

这个拆分处理的过程，就是我们常说的分而治之；而用子系统来隐藏一个领域的内部细节，就是抽象。抽象和分而治之，是我们理解世界的基础。

比如，我们在了解一张简单的桌子时，首先想到的是它由 1 个桌面和 4 条桌腿组成。那么，桌面和桌腿就是子系统：桌面就是一个抽象，代表实现摆放物品功能的一个平面；桌腿也是一个抽象，代表支撑桌面的结构。

如果我们需要进一步了解桌面或者桌腿这两个子系统，可以再进一步去看它们的细节，比如两者都有形状、重量、材料、颜色等。但如果一上来就考虑这些细节的话，我们对桌子的理解就会陷入无尽的细节当中，无法快速形成对整个桌子的认知。

软件开发也是这个道理，我们必须做好抽象和分而治之，才能做出好的程序。

所以，拿到一个任务之后，我们要做的首先就是进行模块的定义，也就是抽象，然后对其分而治之。

为方便理解，我再和你分享一个在 Facebook 时，几个前后端开发者同时开发一个功能的案例吧。

这个功能由一个前端开发者和两个后端开发者完成，整个研发过程至少涉及 3 个抽象和分而治之的操作：

第一步，前后端模块进行自然的拆分。这时，前后端开发者一定会一块儿认真讨论，明确前后端代码运行时的流程，后端需要提供的 API，以及交付这些 API 的时间。

第二步，两个后端开发者对后端工作进行拆分，确定各自的工作任务和边界。

第三步，每个开发者对自己负责的部分再进行抽象和拆分。

在这个过程中，一定要明确模块之间的依赖关系，尽快确定接口规格和可调用性。比如，在前后端的拆分中，常常会采用这几个步骤处理 API：

前后端开发者一起讨论，明确需要的 API。

后端人员会先实现 API 的 Mock，返回符合格式规范的数据。在这个过程中，后端开发者会尽快发出代码审查的要求给另一个后端和前端开发者，以确保格式正确。

Mock 实现之后尽快推到主仓的 master 上 (也就是 origin/master)，并尽快将其部署到内部测试环境，让前端开发者可以使用内部测试环境进行开发和调试。

这些 API 还不能面对用户，通常会先使用功能开关让它只对公司开发人员可见。这样的话，即使 API 的代码在 origin/master 上部署到了生产环境，也不会对用户产生影响。

通过这样的操作，前后端的任务拆分就顺利完成了。

提高抽象和分而治之效率的一个技巧是，在设计代码架构时注意寻找合适的设计模式。

设计模式指的是，设计过程中可以反复使用的、可以解决特定问题的设计方法，最经典的莫过于《设计模式：可复用面向对象软件的基础》中列举的 23 个设计模式，以及针对企业软件架构的《企业应用架构模式》。同时，我们还要注意公司内部具体的常用模式。这些模式都是经实践检验有效的，且传播较广容易理解，都可以作为你进行模块拆分的参照。

具体实现功能的过程中，也会处处体现分而治之的思想。最主要的一个表现是，每个开发者都会把自己的代码尽量做到原子性。代码的原子性指的是，一个提交包含一个不可分割的特性、修复或者优化。

在实际工作中，功能往往比较大。如果只用一个提交完成一个功能，那这个提交往往会比较大，所以我们需要把这个功能再拆分为子功能。

比如，某个后端 API 的实现，我们很可能会把它拆分成数据模型和 API 业务两部分，但如果这样的提交还是太大的话，可以进一步将其拆小，把 API 业务再分为重构和添加新业务两部分。

总之，我们的目的是让每个提交都做成能够独立完成一些任务，但是又不太大。一般来说，一个提交通常不超过 800 行代码。

第二条原则：快速迭代

通过前面的文章，我们已经明确了快速迭代对提高研发效能的重要意义。接下来，我们就看看在具体的编程中，快速迭代的一些实践吧。

第一，不要追求完美，不要过度计划，而是要尽快实现功能，通过不断迭代来完善。优秀的架构往往不是设计出来的，而是在实现过程中逐步发展、完善起来的。

Facebook 有一条常见的海报标语，叫作“Done is better than perfect”，意思就是完成比完美要重要。要实现快速迭代，我们在设计和实现功能时都要注意简单化。

有些开发者过于追求技术，投入了大量时间去设计精美、复杂的系统。这样做没有问题，但一定要有一个度，切忌杀鸡用牛刀。因为复杂的系统虽然精美，但往往不容易理解，维护成本也比较高，修改起来更是不容易。

所以，我们在 Facebook 进行开发的时候，尽量使用简单实用的设计，然后快速进行版本迭代。

第二，在设计的实现中，尽量让自己的代码能够尽快运行起来，从而尽快地验证结果。我们常常会先实现一个可以运行起来的脚手架，然后再持续地往里面添加内容。

在工作中，因为往往是在一个比较大的系统里工作，不能很容易地运行新代码。这时，我们可以编写脚本或者单元测试用例来触发新写的代码。通常情况下，我们更倾向于使用后者，因为这些测试用例，在功能开发完成上线之后，还可以继续用于保证代码质量。

在我看来，在开发过程中，能触发新写的代码帮助我开发，是单元测试的一个重要功能。

第三，为了能够快速进行验证，一个重要实践是设置好本地的代码检验，包括静态扫描、相关单元测试的方便运行，以及 IDE 能够进行的实时检查等。

第四，代码写好之后，尽快提交到主代码仓并保证不会阻塞其他开发人员。

实际上，这是代码提交原子性的另外一个重要特点，即代码提交的原子性，可以保证主代码仓在理论上能够随时基于 master 分支上的任何提交，构建出可以运行的、直接面对用户的产品。在这种方式下，每个开发者在任何时候都可以基于 origin/master 进行开发，从而确保 Facebook 几千人共主干开发时分而治之能够顺利进行。

关于实现代码提交的原子性，我还有一个小技巧，就是如果当前编写的代码提交实在不方便马上推送到 origin/master 分支上，我们也可以频繁地 fetch origin/master 的代码到本地，并在本地对 orgin/master 进行 rebase 来解决冲突。这样就可以确保，我们开发的代码是基于最新的主仓代码，从而降低代码完成之后 push 时冲突的可能性。

第三条原则：DRY

DRY，也就是不要重复你自己，是很多开发模式的基础，也是我们非常熟悉的一条开发原则了。比如，我们把一段经常使用的代码封装到一个函数里，在使用它的地方直接调用这个函数，就是一个最基本的 DRY。

代码逻辑的重复，不仅仅是工作量的浪费，还会大大降低代码的质量和可维护性。所以，我们在开发时，需要留意重复的代码逻辑，并进行适当的处理。

具体来说，首先是寻找重复的逻辑和代码。在动手实现功能之前，我们会花一些时间在内部代码仓和知识库中进行查找，寻找是否有类似的功能实现，以及一些底层可以复用的库，过程中也可以直接联系类似功能的实现者进行讨论和寻求帮助。另外，有一些 IDE，比如 Intellij IDEA，可以在编码的过程中自动探测项目中可能的代码重复。

找到重复的逻辑和代码之后，主要的处理方式是，把共同的部分抽象出来，封装到一个模块、类或者函数等结构中去。

如果在开发新功能时发现有需要重构的地方，一个常见的有效办法是，先用几个提交完成重构，然后再基于重构用几个提交实现新功能。

在编程工作中，除了代码的重复外，比较常见的还有流程的重复。比如测试中，我们常常需要重复地产生一些测试数据，运行完测试之后再把这些数据删除。

这些重复的流程也需要 DRY，最主要的办法是自动化。以重复的测试数据产生、删除流程为例，一般的做法是，编写脚本进行自动化，当然有些时候也需要写一些复杂的可执行程序来生成数据。

流程重复还有一个特点是，它常常和团队相关，也就是说很多成员可能都会重复某些操作，这样的操作更值得自动化。比如，团队的很多成员常常都需要产生测试数据，这时我推荐你主动对其进行自动化、通用化，并提交到代码仓的工具文件夹中供团队使用。

小结

今天，我针对如何使自己成长为 10x 程序员，首先给出了在编程技术方面的 3 个原则，分别是抽象和分而治之、快速迭代，以及 DRY。然后，针对每一条原则，我给出了 Facebook 高效开发者的一些常用实践。

其实，我们还可以从这 3 条原则中延伸出其他很多有效的实践。

比如，好的代码注释。对子系统设计进行合理的注解，可以方便其他开发者在不同的抽象层面对软件结构有更直观的了解。而且如果系统拆分得当的话，需要注释的地方就会比较少。又比如，代码的设计时审查，就是帮助我们及早进行架构讨论，从而实现快速迭代。

为方便你理解并运用到自己的开发工作中，我将这些实践总结到了一张表格中，如下所示。

另外，关于编程技术的高效实践也是不断演化和发展的。以设计模式为例，最近几年又出现了针对 Kubernetes 开发场景的模式，你可以参考《Kubernetes Patterns》这本书；针对云原生（Cloud Native）开发，也有了业界比较认可的12-factor 原则等。将来必定还会有其他新的设计模式产生。比如，伴随着 AI 的逐渐成熟，针对 AI 的设计模式必定会出现。

所以，作为一名软件开发者，我们必须要持续学习。我之前在一家创业公司时，有一个刚大学毕业两年的同事，他有一个非常好的习惯，就是每天早上比其他同事早半个小时到办公室，专门来学习和提高自己。正是因为他的持续学习，使得他虽然工作时间不长，但在整个团队里一直处于技术领先的位置。你也可以借鉴这个方法，或者采用其他适合自己的方法来持续地提升自己。

思考题

我今天提到的关于分而治之的实践，哪一条对你触动最大呢？同时，也和我分享一下你在工作实践中的感受吧。

你还知道哪些编程技术方面的高效原则和实践吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

22 | 深度工作：聚焦最有价值的事儿

你好，我是葛俊。今天，我来和你聊聊深度工作这个话题。

我在上一篇文章中提到，要想成为一个 10x 程序员，我们需要在编程技术、工作方式、工具使用等方面全面提高自己。今天，我们就从工作方式的角度来看看如何提高个人效能吧。

毋庸置疑，身边充斥的各种信息，无时无刻不在吸引着我们的注意力，导致我们的可用时间越来越少，需要处理的信息、事情却越来越多。这中间的关系一旦处理不好，很容易就会出现问题。比如：

工作从早忙到晚，但一直被业务拖着跑，绩效一般，个人也不得到成长。

碎片时间很努力地学习相关技术，似乎学了不少，但不成系统，学完也就完了，没什么效果。

工作太忙，没有时间锻炼、放松，效率越来越低，可能自己还察觉不到。

工作总是被打断，无法静下心来工作和学习。

所以今天，我就和你聊一个看似简单的问题：在任务多、干扰多的现状下，如何最高效地利用时间，去做最重要的事儿，同时有更多的时间来放松和享受生活。

在我看来，这个问题的答案就是深度工作，聚焦最有价值的事儿。深度工作这个概念，是由卡尔 · 纽波特（Cal Newport）提出的，指的是在无干扰的状态下，才能专注地进行的专业活动。这样的活动能够使个人的认知能力发挥到极限，从而让我们创造出最大价值，成为一个不可替代的人。

就我个人而言，我也一直在想办法去聚焦最有价值的事儿，也读了不少 GTD（Geting Things Done，尽管去做）的书，尝试了不少方法，积累了不少经验，也踩了不少坑。总结来看，实现深度工作的办法说起来很简单，可以概括为以下三步：

以终为始，寻找并聚焦最重要的任务；

追根究底，寻找最高效的解决方案；

安排时间和精力，高效执行解决方案。

但，这些办法真正实行起来，就比较有挑战了，所以接下来我会从这 3 个方面与你分享些实用的方法和技巧。

1. 以终为始，寻找并聚焦最重要的任务

我们都清楚，事情做得快非常重要，但做真正有效的事情更重要。这也就意味着，我们首先要找到目前最最重要的任务。

在找最重要的任务时，我建议你按照以下三步来操作。

自己定义任务；

聚焦目标，以终为始；

无情的筛选。

下面我们分别看看这三步具体如何实施吧。

第一步，自己定义任务

GTD 的创始人大卫 · 艾伦（David Allen）提出，日常任务可以分为 3 种：

预先计划的任务（Pre-defined Work），比如迭代之初就计划好了的功能开发任务；

临时产生的任务（Work As It Appears），比如 Bug、邮件、临时会议等；

自己定义的任务（Defining Work），即根据当前状况，自己决定需要做的任务。

在我看来，我们要把更多的时间和精力放到自己定义的任务上。你可能会觉得，前两种任务已经够多了，也非常重要。但其实，我们容易忽略的第 3 种任务，可以帮我们减少前两种任务中不必要的浪费。

因为，预先计划好的任务的优先级常常会随着情况的改变而改变，而临时产生的任务很可能当时觉得很紧急，但实际上是可以推迟或者甚至不做的。我们根据当前情况决定要做的任务，就可以避免把时间花在低优先级的任务上。

我的建议是，在每天的工作间隙，花一些时间考虑自己去定义任务，问自己这几个问题：

预先定义的任务是否还需要做？

有没有什么更重要的任务要替代现在手上的任务？

临时产生的干扰任务怎么处理才好？需要我来处理吗？

……

第二步，聚焦目标，以终为始

聚焦目标，以终为始，其实就是在自己定义任务。因为时间有限，为目标服务的任务才最重要。

作为高效开发者，常见的目标包括业务成功、帮助团队，以及个人成长这 3 个。如果能找到三者重合的任务就最好不过了。

我在 Facebook 工作的时候，有个朋友发现日常工作中自己和团队常常要用到面板，而现有的面板系统对他们的场景不够友好。于是，他自己花时间实现了一个基于 Python 的 DSL 的面板系统。他的工作中就会大量使用 Python，所以这个项目对他的个人技术成长很有好处。

工具做好第一版之后，团队成员非常喜欢这个工具，并很快就扩展到其他的很多团队，比如我也用它给我们团队做了一个面板。这个工具非常好用，半年后在公司立项，并由这个朋友主导，逐渐发展为公司最受欢迎的面板系统。

这个面板工具任务，就是一个帮助业务成功、帮助团队和个人成长三者相结合的一个案例。

日常工作中更常见的情况是，三者不能兼得。这时，我们首先应该关注业务成功，因为它是我们工作的最基本目标，是基础。在此之上，我推荐先考虑帮助团队成长。因为帮助团队的同时，往往会给自己带来一些直接或间接的成长机会。

第三步，无情的筛选，少即是多

生命有限，而工作无限，所以我们必须要无情地排优先级。

很多人都有一个倾向，就是贪多，认为越多越好，我曾经也这样。在自己的书单里添加了几百本书，书签页中添加了几百篇要读的技术文章，Todo List 里添加的任务也越来越多，还计划学习这个语言、那个框架。但实际做起来，却因为时间有限，不但只能浅尝而止，还让自己很疲倦。痛定思痛，我下决心去做减法。

在我看来，“数字 3 原则”很有效，也就是强制把要做的事、要达到的目标，都限制在 3 个以内。

我曾参加过一个增进对自己了解的工作坊，期间有一个练习，帮助我们明确自己最关注的道德品质。

首先，我们要从一个有 50 项品质的列表上，勾选出 25 项自认为最重要的；然后，在这 25 项品质中，再选出 10 项最重要的；最后，从这 10 项里再筛选出 3 个。

第一轮筛选很轻松，第 2 轮筛选时就有一点儿困难了，到了第 3 轮 10 选 3 时真的非常痛苦。它强迫我仔细思考，到底哪 3 个品质对我才是最重要的。作出决定之后，你就会发现对自己的了解更深了。

这对我帮助非常大，所以我在后续的工作计划中，也强迫自己使用数字 3 原则，无论是三年规划、半年规划、本周规划，还是当天工作，都做一个无情的筛选，找出最最重要的 3 件事儿。我推荐你也尝试一下。

经过这三步，我们已经明确了最重要的任务，接下来我们再看看如何寻找高效的解决方案吧。

2. 追根究底，寻找最高效的解决方案

回想一下你和产品经理打交道的过程，是不是下面这样呢？产品经理决定如何在业务上满足用户需求，而开发人员就是，从产品经理手中接过任务，马上开始技术上的设计和实现。

这种工作方法我们很熟悉，但其实有很大的局限性。因为开发人员对技术实现最熟悉，如果我们能结合业务进行思考，追根究底，常常能对业务的解决方案做出一些改进，甚至是重新设计出更好的方案。而这样的改进，对个人研发效能的提高帮助非常大。

我在做 Phabricator 项目时，曾收到一个任务，要解决用户进行 inline 讨论时，模态弹窗（modal）挡住弹窗下代码的问题。你可以先思考下，自己收到这样的任务会如何做，然后再对比看看我的经历，收获会更大。

这个任务的描述很简单，就是去改进 Phabricator 使用的 JavaScript 库，让它支持用鼠标拖动模态弹窗。但是，当我了解这个 JavaScript 库后，发现工作量非常大，需要三四天才能完成。正当我考虑如何下手时，一个经验丰富的同事提醒我先别着急去做这样的大改动，可以想想看还有没有其他方法，能够更简单地解决这个问题。

我觉得这个建议非常有道理，于是仔细思考了用户需求，并与产品经理进行沟通和讨论。结论是，要解决代码被遮挡的问题，并不一定非要拖动模态弹窗，还有一个比较简单的办法，就是放弃使用模态弹窗做讨论功能，而是直接在被讨论的代码和下一行代码之间插入一个文本框供用户进行讨论。

这样一来，不但解决了代码被遮挡的问题，还因为无需改动底层库，工作量直接降为了一天，同时给用户提供了预期之外的方便。因为没有模态弹窗，用户写注释时还可以上下滚动代码窗口，来查看更大范围内的代码，体验更好了。

此后，我在接到任何任务时，都会先考虑它到底要解决什么问题，有没有更好的解决方案，花些时间去深入思考，往往都能帮我节省后面的很多时间。

所以我认为，开发人员也要对业务有一定的了解。面对任务的时候，多问几个 Why，并与产品经理和团队成员充分沟通，了解它到底要解决什么问题，只有这样，我们才能以解决问题为出发点，找到最高效的解决方案。

接下来，我们再看看如何管理时间和精力，去具体实现这些方案吧。

3. 安排时间和精力，高效执行解决方案

信息爆炸、时间碎片化，给我们的时间管理和精力管理带来了很大挑战。关于这个话题，也有很多理论和实践了，我也尝试过很多方法。在这里，我给你推荐 3 种我认为最有效的方法，包括用番茄工作法来记录深度工作时间、用拥抱无聊来控制对手机的依赖，以及用反向行事日历（Reverse Calendar）来确保休息和高效。

接下来，我们分别看看这 3 种方法吧。

用番茄工作法来记录深度工作时间

简单地说，番茄工作法就是把时间划分为固定时长的工作时间和休息时间。一个番茄时间包含两部分，25 分钟的工作学习和 5 分钟的休息时间。

番茄工作法的精髓是，在每一个工作时间段，避免干扰、全神贯注。因为只有精力高度集中，减少上下文切换，才能进入深度工作状态，进而最大程度地发挥我们的心智能力，提高个人效能。

根据我的精力以及工作性质，我对番茄时钟做了一点调整，把每一个工作时段调整成为 40 分钟。在这个时间段内，我尽量避免实时聊天工具和电话的干扰，集中精力去做当前最重要的事情。条件允许的话，我会把手机调成静音，并关闭电脑上的提醒功能，甚至干脆离开办公区去走走，边散步边进行专注的思考。

用拥抱无聊来控制手机依赖

手机依赖症越来越普遍，我也深受其害，有段时间甚至去卫生间都一定要带着手机。

后来，我发现了一个“拥抱无聊”的方法。具体来说就是，在一些非常碎片化的时间，不要因为无聊就马上抄起手机，去找一些有意思的东西放松或者学习，而是尝试适应无聊。

其实，无聊也是一种不错的状态，试着去享受它，不要让自己的大脑一直处于活跃状态。这样可以让我们得到休息，而且更重要的是，能够比较好地避免自己打扰自己的倾向。所谓“自己打扰自己”，就是会不自觉地去想、去做一些与手上工作不相干的事儿，自己干扰自己。

不知道你有没有这样的感受，当习惯一有空闲就使用手机之后，在工作中稍有一点空闲，或者是遇到比较难以处理的问题时，就会不自觉地想停下来看一看手机。我认为，这就是因为我们习惯了用手机来缓解紧张和压力而形成的条件反射，就是对手机的一种依赖。

用反向行事日历来确保休息和高效工作

反向行事日历主要用来处理因为工作太多导致的忽略休息、影响健康和效率低下的问题。

我们在安排日程时，通常会把工作任务放到日历中，而不会把运动、休息、吃饭等非工作任务放入其中。反向日历正好相反，首先就是把这些非工作任务作为最为最高优先级放到日历中，然后再在剩余的时间中安排工作任务。

在我看来，这个方法有两个非常明显的好处：

第一，可以强制我们去休息和锻炼；

第二，可以让我们更有紧迫感，提高工作效率。当我们在日历上把运动、休息和吃饭的时间标注出来之后，就会清楚看到剩下可以用来工作的时间很有限，必须要提高工作效率才能完成任务。

小结

我在开篇词中指出，团队研发效能的定义包括有效性（准确）、效率（快速）和可持续性 3 个方面。实际上，个人研发效能也包括这 3 个方面。其中，有效性就是要做对的事儿，效率就是要高效地执行，而可持续性就是要安排好时间和精力，有张有弛才能持续发展。

针对这 3 点，我今天就与你分享了聚焦最重要的事儿的 3 个步骤：第一，以终为始，寻找并聚焦最重要的任务；第二，追根究底，寻找最高效的解决方案；第三，安排时间和精力，高效执行解决方案。

可以看到，这三步就是从个人效能的 3 个方面入手的，即：聚焦最重要的任务，是帮助我们提高有效性；寻找高效的解决方案并高效执行，是帮助我们快速完成开发任务；而合理安排时间和精力，就是让我们持续地进行高效开发。

知易行难，同样适用于这些原则。所以，我非常希望你能实践并坚持这些原则和方法，当然了你也可以去寻找更适合自己的方法，让自己这块好钢能用到刀刃上。

思考题

你有其他克服手机依赖症的好方法吗？

你觉得碎片时间，更适合碎片化学习 / 娱乐，还是拥抱无聊呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

23 | 效率工具：选对用对才能事半功倍

你好，我是葛俊。今天，我来和你聊一聊效率工具。

在前两篇文章中，我分别从编程技术和工作方式两个方面，与你介绍了如何提高个人研发效能，成长为 10x 程序员。今天，我再与你分享些工具使用方面的技巧和方法，来帮助你全方位地提高自己的工作效率。

谈到研发效能，一定离不开工具。工具使用得当，可以大幅提升研发效率，甚至可以因此引发业务发展、开发行为的质变。比如，我在第 11 篇文章中与你分享的截屏工具链带来的效率提升，就使得其被广泛用在了 Commit Message 中，大幅提高了整个过程的效率。

我一直对各种工具很感兴趣，从业这些年来，在这上面花了不少时间，也踩了一些坑。比如，我曾经在 Emacs 上面花费了大量的时间，去自动化和优化很多细枝末节的操作。结果是，花在优化和自动化上的时间，比节省下的时间还要多，得不偿失。

所以，我总结得出，对于工具一定要选对和用对。

选对工具指的是，我们要针对不同的任务，找到合适的工具来提高效率。而用对工具指的是，我们要分配适量的时间和精力来选择工具，要时刻注意投入产出比。

关于用对工具，我推荐一个比较好的方式是，留意工作中经常重复和繁琐的工作步骤，隔一段时间就做些工具方面的调研，看看有没有更合适的工具或者使用方法来优化这些流程。对于一些非常繁琐、执行频率又非常高的操作，如果没有现成的工具，甚至可以考虑自己开发一些工具和脚本进行优化。频次越高的操作，越值得优化。

接下来，我就按照功能和你分享一些有助于提高工作效率的工具。

因为我最近的工作电脑是 macOS 系统，所以我今天推荐的适用于苹果操作系统和生态的工具会偏多一些。但，这些优化的方向和思路是一致的，在 Windows 操作系统上你也能找到类似的工具。另外，安卓系统更加灵活，有比苹果生态更多的工具。

第一个任务类别是，操作系统上的通用操作

操作系统上的通用操作，主要有窗口切换、程序启动、窗口管理、剪贴板管理等。这些操作普遍且频繁，比较值得优化。

关于窗口切换，各操作系统都有一些自带的支持。比如，macOS 系统自带的方式是 cmd+tab，Windows 系统自带的是 Alt+Tab 或者 Win+Tab。关于程序启动，macOS 系统有 Spotlight，Windows 系统可以使用 Win 键或者 Win+R 启动任务。

但这两个操作实在是频繁，且操作系统自带的功能只是基础功能。所以。我推荐你进行进一步优化。

在 macOS 系统上，Alfred、Quicksilver 这两个工具就很不错。Windows 系统的话，我推荐Wox。

这些工具的用法也很简单，使用一个快捷键启动，然后输入过滤条件，查找需要运行或者切换的程序，回车即可。使用 Quicksilver 启动 / 切换程序的方法，如下所示。

关于窗口管理，有一个常用操作，是把一个窗口自动缩放，并摆放到屏幕的某一个位置（比如屏幕的左上角）。

在 macOS 系统中，我一般使用BetterTouchTool这个工具实现，也推荐你使用。通过它，我可以非常方便地实现窗口缩放和拖动。有两个操作，我觉得特别方便：

一是，按住 alt 键，无论光标在窗口的任何位置，都可以通过挪动鼠标拖动窗口；

二是，按住 cmd+alt 键，无论光标在窗口的任何位置，都可以通过移动鼠标来改变窗口大小。

BetterTouchTool 的配置和使用，如下面两幅图所示。

最后是，剪贴板管理。目前，操作系统自带的剪贴板只能保存一条记录，但如果我们使用剪贴板历史管理工具的话，可以非常方便地保存和使用多条历史拷贝内容。

在 macOS 系统上，我用的工具是 Quicksilver。Windows 上类似的工具有很多，比如Ditto。

第二个任务类别是输入

关于输入，我主要和你推荐 4 类工具，分别是语音输入、文字快速输入、重新定义按键和具体的键盘。

我们先看看第一类工具，语音输入。我在电脑上常用的是讯飞语音输入，手机上常用的是讯飞输入法。在电脑上使用语音输入有一个技巧是，把手机作为麦克风。这样输入的效果非常好，尤其是旁边有人说话的时候，手机麦克风的过滤功能就非常棒了。

在手机上，我经常使用讯飞输入法的语音便签功能，保存录音的同时还可以直接把语音转为文字，对于记录灵感来说特别方便。

第 2 类工具是文字快速输入。Mac 上最流行的是TextExpander，它可以方便地生成预先定义的字符串、当天日期，也可以很方便地指定文字生成之后的光标所在位置等。

比如，经过下面的配置之后，只要输入缩写“;mj”就可以生成日记模板，并把光标放到第一个任务处。

## %Y-%m-%d:

今天关键任务

%|

-

成功之处，不足之处

-

在 Windows 系统上，开源免费的AutoHotkey非常强大。它不但可以进行文本扩展，还可以运行程序。我来和你举一个运行程序的例子吧。比如，你可以使用 Win+V 直接切换到 VIM。如果 VIM 先前没启动的话，则会直接其启动。

;;; switch to vim

#v::

if WinExist( "ahk_class Vim" )

{

  WinActivate

}

Return

第三类工具是，重新定义按键。在我看来，对程序员最实用的一个功能是，把大写锁定键（caps lock）转换成 ctrl 键。因为 caps lock 的位置很方便使用频率却很低，而编程中常会用到 ctrl 键，所以这个重新定义对程序员非常有用。

至于重新定义按键的工具，在 Mac 上我使用的是Karabiner-Elements，在 Windows 上使用 AutoHotkey 就可以。

另外，我对 caps lock 键的定义不是简单地重新定义为 ctrl，而是如果和其他键共同使用的话就是 ctrl 键，单独使用的话就是 esc 键。因为，esc 键的使用频率也很高，尤其是在 VIM 中。

在 Karabiner-Elements 中的具体设置方法是：

"rules": [

    {

        "manipulators": [

            {

                "description": "Change ctrl to esc if pressed alone.",

                "from": {

                    "key_code": "caps_lock",

                    "modifiers": {

                        "optional": [

                            "any"

                        ]

                    }

                },

                "to": [

                    {

                        "key_code": "left_control"

                    }

                ],

                "to_if_alone": [

                    {

                        "key_code": "escape"

                    }

                ],

                "type": "basic"

            }

        ]

    }

]

第四类工具是，具体的键盘。开发者要经常输入，选择一款顺手的键盘就很重要了。键盘的选用，我看重的是手感和键盘布局。手感的话，机械键盘的确好一些，你可以根据自己的体验选择一款合适的。而布局的话，左右手按键分离较远的一般来说会好一些。

根据这些原则，我平时使用Kinesis Pro2 键盘。因为，它的中间是凹进去的，所以手指与每一行按键的距离都差不多，输入时很轻松。另外，特殊键由大拇指控制，这个特点我也非常喜欢。

第三个任务类别是，知识管理

对学习和知识管理，我主要与你分享 6 类工具。

第一类工具是云盘，用来确保存储的内容不会因为本地电脑的意外情况而丢失。目前，市面上的各种云盘都可以满足这个需求。这里我有一个小建议，使用云盘时最好能够使用本地的自动同步工具，自动地将本地文件夹里的内容同步到云盘。

第 2 类工具是笔记。我挑选笔记工具的原则，包括支持云同步，支持电脑和手机端同时访问。印象笔记、Mac 自带的 Notes、石墨文档等都不错。

第 3 类工具是写文档的工具。相较于笔记工具来说，写文档的工具还有一个挑选原则是支持 Markdown。我现在是组合使用 iCloud、1Writer 和 Typora：使用 iCloud 进行同步，在手机使用 1Writer，在电脑上使用 Typora。

第 4 类工具是思维导图。对思维导图，比较重要的特性包括跨平台、方便使用快捷键、方便导入\导出其他格式。我以前一直用的是FreeMind，它基于 Java 实现，在 Windows、macOS 和 Linux 系统上都可以使用，快捷键配置也很强大，但它的一大缺陷是在手机上不能使用。所以，最近两年我转移到了XMind Zen上面。它可以在电脑和手机上同时使用并自动同步，显示也比较美观。

第 5 类工具是截屏、录屏。我对截屏、录屏工具的选用原则是标注功能要好，另外要可以把录屏保存成 GIF 格式。我现在最常用的是对这两方面都支持不错的MonoSnap。另外，录屏的时候，我们常常希望把当前的键盘输入显示到屏幕上，我推荐使用KeyCastr；编辑 GIF 文件的话，我使用的是GIF Brewery 3。

这个专栏中，你见到的录屏和截屏图片，我就是使用这三个工具完成的。另外，还有一个对命令行终端录制比较方便的工具asciinema。它可以把你在终端里面的操作和输出用 JSON 形式保存下来，也可以上传到 asciinema 的网站上，还可以方便地在你自己的网站上引用显示。但这个工具的缺点是，无法显示键盘的按键操作。

第 6 类是处理 PDF 文件的工具。这样的工具我主要关注基本的标注功能，以及在平板电脑上可以使用电子笔操作。在电脑上，我一般使用Foxit Reader和 macOS 自带的 Preview；在手机上，我主要使用的是PDF Expert。

在我看来，一个比较高效的方式是，把 PDF 文件放到笔记软件中，然后在笔记中直接对其进行操作。印象笔记和苹果自带的 Notes 都支持这个功能。

第四个任务类别是，浏览网页

访问互联网是非常高频的操作，所以我把它单独列成一类。对开发人员，我推荐使用 Chrome 和 Firefox，因为两者都有很强的扩展功能，对开发活动比较友好。

因为最近几年我主要在使用 Chrome，所以接下来，我与你介绍一些我常用的 Chrome 插件和技巧。

第一个插件是Octotree。它会在你访问 GitHub 查看项目时，在窗口左侧以树形结构清晰明了地显示代码仓结构。

第二个插件是Pocket for Chrome。它需要与Pocket配合使用，实际上就是一个在线的网页书签保存服务，但它的推荐功能很优秀，并且对手机端的支持也很好。

第三个插件是smartUp Gestures。通过它，你可以按住鼠标右键不放，在屏幕上画一些形状，也就是手势（Gesture），来进行一些操作。

我最常使用的功能有：切换到左边（或右边）一个标签页、向下（或向上）翻页至网页末尾（或网页开头）、返回网页访问历史的上一页（或下一页），以及关闭当前标签页。

在文稿里我有一个录屏供你参考。

第 5 个任务类别是，开发

和开发工作相关的工具比较多，包括编辑器（比如 VS Code、VIM）、IDE（比如 Visual Studio 和 JetBrains 系列）、代码仓管理工具（比如 Git 和 HG）、API 测试工具（比如 Postman），以及命令行工具等。

关于开发工具的使用，我会在后面的文章中与你详细介绍其中的使用技巧，帮你提高编码效率。

小结

无论“磨刀不误砍柴工”“工欲善其事，必先利其器”，还是乔布斯曾说过的“You cannot mandate productivity, you must provide the tools to let people become their best.（你不能强制要求大家提高生产力，你必须提供工具，让大家发挥他们的最大能力）” ，强调的都是工具的重要性。

所以，作为开发人员，我们一定要选对工具、用对工具，才能提高效能。在今天这篇文章中，我从操作系统上的通用操作、输入、知识管理、网页浏览和开发 5 个方面给你推荐了一些工具及使用方法。当然了，关于开发工具这个重头戏我还会在后面的文章中与你详细分享。

其实，使用哪款工具，是一个仁者见仁，智者见智的问题。所以，通过这篇文章，我更希望达到的目的是，能帮助你对日常工作中最常见的操作进行思考，寻找值得优化和可以使用工具优化的地方，从而提高个人的研发效能。

但，我还要强调的是，工具只是辅助，编程工作更重要的还是思考。所以，我建议你不要花费过多的时间在工具研究上，要时刻留意投入产出比。

思考题

除了今天提到的操作系统上的通用操作、输入、知识管理、网页浏览和开发 5 个方面外，你觉得还有哪些方面的操作比较频繁吗，有什么值得优化的地方吗，又有什么值得推荐的工具吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

24 | VIM：如何高性价比地学习VIM的实用技巧？

你好，我是葛俊。今天，我来和你聊聊 VIM 的使用技巧。

在“特别放送 | 每个开发人员都应该学一些 VIM”这篇文章中，我和你详细介绍了 VIM 提高研发效能背后的原因。我推荐每个开发者都应该学一些 VIM 的原因，主要有两个：

独特的命令模式，可以大量减少按键次数，使得编辑更高效；

支持跨平台，同时可以在很多其他 IDE、编辑器中作为插件使用，真正做到一次学习，处处使用。

VIM 确实可以帮助我们提高效率，但面对这样一个学习曲线长而且陡的编辑器，我们很容易因为上手太难而放弃。所以，如何性价比高地学习 VIM 的使用技巧非常重要。

我推荐你按照以下三步，来高效地学习如何使用 VIM：

学习 VIM 的命令模式和命令组合方式；

学习 VIM 最常用的命令；

在自己的工作环境中使用 VIM，比如与命令行环境的集成使用。

接下来，我们分别看看这三步吧。

VIM 的模式机制

VIM 的基本模式是命令模式，在命令模式中，敲击主体键的效果不是直接插入字符，而是执行命令实现对文本的修改。

使用 VIM 的最佳工作流

在我看来，在命令模式下工作，效率高、按键少，所以我推荐你尽量让 VIM 处于命令模式，使用各种命令进行工作。进入编辑模式完成编辑工作之后也立即返回命令模式。

事实上，我们从命令模式进入编辑模式修改文件，之后再返回命令模式的全过程，就是一个编辑命令。它跟其他的命令，比如使用 dd 删除一行，并没有本质区别。接下来，我们一起看个具体的例子吧。

比如，我要在一行文字后面加上一个大括号，后面再写一行代码。开始编辑时，光标处于这一行的开头。

  config =

^

修改的目标是这样：

  config = {

    timeout: 1000ms,

  }

在 VIM 中，我的操作是，首先敲击大写字母 A，将光标移到这一行的末尾，并进入编辑模式：

 config =

          ^

然后，输入“{ timeout: 1000ms, 回车}”，在文件中插入内容。最后，敲击 Esc 键回到命令模式。编辑完成。

  config = {

    timeout: 1000ms,

  }

   ^

实际上，整个过程就是执行了一条“在本行末尾插入文字”的命令。整条命令的输入是“A{ timeout: 1000ms, 回车}Esc”。虽然比较长，但仍然是一条文本编辑命令。

所以实际上，我们在 VIM 中的工作，正是在命令模式里执行一条条的命令完成的。理解了这一点，你就可以有意识地学习、设计命令来高效地完成工作了。

为了让命令更加高效，VIM 还提供了强大的命令组合功能，使得命令的功能效果呈指数级增长。

命令的组合方式

在 VIM 中，有相当一部分命令可以扩展为 3 部分：

开头的部分是一个数字，代表重复次数；

中间的部分是命令；

最后的部分，代表命令的对象。

比如，命令 3de 中，3 表示执行 3 次，d 是删除命令，e 表示从当前位置到单词的末尾。整条命令的意思就是，从当前位置向后，删除 3 个单词。类似的，命令 3ce 表示从当前位置向后，删除三个单词，然后进入编辑模式。

可以看到，命令组合的前两个部分比较简单，但第三个部分也就是命令对象，技巧就比较多了。所以接下来，我就与你详细介绍下到底有哪些命令对象可以使用。

其实，对命令对象并没有一个完整的分类。但我根据经验，将其总结为光标移动命令和文本对象两种。

第一种是光标移动命令。比如，命令是移动光标到本行末尾，那么d命令是移动光标到本行末尾，那么d 就表示删除到本行末尾；再比如，4}表示向下移动 4 个由空行隔开的段落，那么 d4}就是删除这 4 个段落。

这一类命令功能很强大，我们也很熟悉了，但它有一个缺陷，就是选择的出发点始终是当前光标所在位置。而我们在处理文本的过程中，尤其是在编写程序的时候，光标所在位置常常是处于需要修改内容的中间，而不是开头。比如，我们常常在写一个注释字符串的时候，需要修改整个字符串：

    comment: 'this is standalone mode',

                          ^

如果使用上面的光标移动作为命令对象的话，我们需要执行 bbbct’命令，也就是向左移动三个单词之后，再向右删除到第一个单引号的地方，操作起来很麻烦。

针对这种情况，VIM 提供了第二种命令对象：文本对象。具体来说就是，用字符代表一定的文字单位。比如，i"代表两个双引号之间的字符串，aw 表示当前的单词。使用这种文本对象很方便，比如上面的编辑例子，我们只需要命令 ci"就可以实现，比 bbbct’命令方便了很多。

具体来说，文本对象命令由两部分组成：

第一部分只能是字符 i 或者 a，表示是否包含对象边界。比如，i"表示不包括两边的引号，而 a"就包括引号；

第二部分选择比较多，表示各种不同的文本对象。比如

如果你要查看完整的文本对象，可以使用:help text-objects。

这一组文本对象选择功能很强大，而且是 VIM 自带功能，不需要安装任何插件。神奇的是，不知道为什么，很多使用 VIM 很久的人都不知道这个功能。我个人把它叫作 vip 功能，如果你以前没有用过，可以在文件中输入命令 vip 看看效果。相信不会让你失望。

可见，VIM 的命令操作及其组合功能非常强大，要高效使用 VIM，我们就必须使用命令模式以及命令组合。我看到有些开发者使用 VIM 时，一上来就使用 i 命令进入编辑模式，然后在编辑模式中工作。但是，编辑模式的功能很有限，完全发挥不出 VIM 提供的高效文本编辑功能。

接下来，我们进入第二步，也就是学习 VIM 最常用的命令。

命令模式中的基础命令

VIM 有大量的命令可供我们使用，这里我主要与你介绍针对单个文件编辑的命令，因为这是编辑工作最基础的部分，也是各种跨平台场景，尤其在其他 IDE 中通过插件使用 VIM 的方式的共性部分。

至于更多的命令和细节，你可以参考我的“命令模式中的基础命令”这篇博客，或者自行搜索查询。

接下来，我会按照以下编辑文件的逻辑顺序，来与你介绍关键知识点和技巧：

打开文件，以及进行设置操作；

移动光标；

编辑文本；

查找、替换。

第一组常用命令是，打开文件以及对文件的设置

这些命令包括打开文件、退出、保存、设置等。关于设置，如果在远端的服务器上，我常常运行一条命令进行基本设置：

:set ic hls is hid nu

其中，ic 表示搜索时忽略大小写，hls 表示高亮显示搜索结果，is 表示增量搜索，也就是在搜索输入的过程中，在按回车键之前就实时显示当前的匹配结果，hid 表示让 VIM 支持多文件操作，nu 表示显示行号。

如果要关闭其中某一个选项的话，在前面添加一个 no 即可。比如，关闭显示行号，使用

:set nonu

第二组常用命令是，移动光标

VIM 提供了非常细粒度的光标移动命令，包括水平移动、上下移动、文字段落的移动。这些命令之间的差别很细微。比如，w 和 e 都是向右边移动一个单词，只不过 w 是把光标放到下一个单词开头，而 e 是把光标放到这一个单词结尾。

虽然差别很小，但正是这样的细粒度，才使得 VIM 能够让我们使用最少的按键次数去完成编辑任务。

第三组常用命令是，编辑命令

编辑工作中我们应该大量使用命令组合来提高效率。关于组合命令的威力，我建议你看下“命令模式中的基础命令”那篇博客，我与你详细解释了一个 ct"的例子。

这里，我重点与你分享一个设计命令的技巧。VIM 的取消命令 u、重复命令.，都是针对上一个完整的编辑命令而言。所以，我们可以设计一个完整的编辑命令，从而可以使用重复命令. 来提高效率。比如，你要把一段文本中的几个时间都修改成 20ms，这段文本原来是

    timeout: 1000000ms,

    waiting: 300000ms,

    starting: 40000ms,

希望改变成

    timeout: 20ms,

    waiting: 20ms,

    starting: 20ms,

在 VIM 中有多种实现方法。一种还不错的方法是，使用修改命令 c 和移动命令 l 的组合，在每一行用一个命令搞定。具体操作是，将光标挪到 1000000ms 里数字 1 的地方，用 c7l20 命令对第一行进行修改，然后在第二行、第三行的相同位置分别使用 c6l20 和 c5l20 进行修改。

这个命令可以完成工作，但每条命令都不一样，不能重复。实际上，还有一种更好的方法。

你可以设计一条命令，在每一行重复使用。在这个例子里，这个命令是 ctm20，意思是从当前位置删除到下一个字母 m 的位置，进入编辑模式，插入 20，然后返回命令模式。

使用这条命令对第一行进行修改之后，你可以把光标挪到第二、三行使用重复命令. 来完成编辑任务，如下所示。

当然了，你还可以使用正则表达式进行查找替换，但是远比上面这个重复命令要复杂。这个设计命令并重复使用的方法非常好用，建议你上手实践下。

关于重复命令，VIM 另外还有一个录制命令 q 和重复命令 @，功能更强大，但也比较重。如果你想深入了解的话，可以参考这篇文章。

第四组常用命令是，查找、替换

这组命令主要有 /、* 和 s 等，很常用，网络上也有很多描述，这里我就不再详细介绍了。比如，我在“命令模式中的基础命令”这篇博客中详细介绍了 s 命令，可供你参考。

除了上述 4 种常见命令外，VIM 中还有一种很强大的可视模式（Visual Mode），可以提高编辑体验。接下来，我们再一起看看。

可视模式

可视模式一共有 3 种，包括基于字符的可视模式、基于行的可视模式和基于列的可视模式，详见“命令模式中的基础命令”这篇博客中的介绍。

接下来，我以基于列的可视模式为例，带你看看可视模式是如何提高编辑体验的吧。

我们先来看看第一个例子。有一段文本，每一行都用 var 声明变量（比如 var gameMode），现在我希望改成使用 const 来声明。我们来看看具体的实现方式。

第一步，把光标挪到第一行开头的 v 字符处，使用组合键 Ctrl-v 进入列可视模式，然后使用 7j 下移动七行，再用 e 向右移动一个单词，从而选中一个包含所有 var 的矩形区域。

第二步是开始编辑。输入 c，删除所有的 var，并进入编辑模式。

第三步是输入 const，并输入 Esc 完成编辑操作，回到命令模式，整个修改完成。

我们再来看看第二个例子。我希望在上面这段文字的每一行末尾都添加一个分号。具体的操作步骤如下所示：

首先，用 $ 命令把光标挪到最后一行的末尾，然后 Ctrl-v 进入列模式，再用 7j 命令将光标挪到第一行，这时，每一行的末尾都被包含到了一个方块里。不过因为每一行的长度不同，所以方块没有显示完全。

然后，输入 A 命令，光标挪到每一行的末尾，并进入编辑模式。

最后，输入要插入的分号，输入 Esc 完成编辑。这样，每一行的末尾都添加了一个分号。

最后，关于可视模式我还有三个比较有用的小贴士：

第一，在退出了可视模式之后，可以使用 gv 命令重新进入可视模式并选择上一次的选择。这个命令出乎意料得有用，因为我们常常需要对上一次的选择进行进一步的操作。

第二，进入可视模式之后，可以直接使用 v、V、Ctrl-v 在三种模式之间切换，而不需要退出可视模式再重新进入。比如，你一开始使用 v 进入了字符可视模式，这时发现你需要删除几行，就可以直接输入 V 进入行的可视模式。

第三，修改选取的区域范围。当你进入可视模式之后，光标默认处于所选区域的结尾处，你可以挪动光标调节选择区域的结尾部分。但，如果你想调节的是所选区域的开头部分，则可以使用命令 o 将光标跳到选择区域的开始部分，再次输入命令 o 光标又会跳到选择区域的末尾。一个更好玩的情况是，在列模式中，你可以使用小写 o 在对角线上跳转，大写 O 在水平方向跳转，从而可以灵活调整所选矩形的四个角。

以上就是命令模式中的常用基础命令了。理解了这些命令，再加上命令组合，你就可以比较高效地使用 VIM 进行文本编辑了。但，要更高效地使用 VIM，我们还有一个问题没有解决：VIM 虽然强大，但也只是整个工作环境中的一个工具，怎样才能在整个工作中高效地使用 VIM 技能呢？

所以，接下来我将带你探索如何寻找合适的 VIM 使用场景。

寻找合适的 VIM 使用场景

寻找合适的适用场景，常常包括两种情况，一是 VIM 不是主力 IDE 的情况，二是 VIM 与其他工具的集成。接下来，我们先看第一种情况。

我的工作环境中，VIM 不是主力 IDE 怎么办？

这种情况下，我会建议你先看一下你的主力 IDE 是否支持 VI 模式。目前，绝大部分主流 IDE 都支持，而且大都做得不错。如果你的主力 IDE 中能使用 VIM 命令的话，那从 VIM 特有的命令模式就能让你收益颇丰。

从我的经验来看，我只有在 Facebook 那几年使用 VIM 作为主力 IDE，其他时间使用的编辑器主要是 Intellij 的 IDE 系列和 VS Code。在这些 IDE 里，我一直在使用 VI 模式，效果也都很好。

除了在主力 IDE 中使用 VI 模式外，你还可以选择把 VIM 只作为一个单纯的文本编辑器，需要强大的编辑功能时，临时用一下就可以。比如，我在写微信小程序的时候，一开始使用的是原生的微信开发工具，编辑功能不是太强。所以在遇到一些重量级的编辑工作时，我就打开 VIM 快速搞定，然后再回到微信原生开发工具 IDE 里继续工作。

VIM 能在 Linux 命令行环境中与其他工具结合的很好

Unix/Linux 系统的一个设计理念是，每个工具都只做一个功能，并且把这个功能做到极致，然后由操作系统把这些功能集成起来。VI 当初就是作为 Unix 系统里的编辑器而存在的，到了 40 年后的今天，虽然 VIM 已经可以被配置成为一个强大的 IDE，但很大程度上依然是 Unix/Linux 系统的基础编辑器，仍然可以很方便地和 Unix/Linux 的其他工具集成。

为了帮助你理解，我再和你分享 3 个适用场景。

第一个场景：使用 VIM 作为其他工具的编辑器。

在大部分 Linux 系统里，默认的编辑器就是 VIM。如果不是的话，可以使用如下命令来设置：

// 全局使用 VIM

export EDITOR='vim'

第二个适用场景：使用管道（Pipe）。

管道是 Linux 环境中最常用的工具之间的集成方式。VIM 可以接收管道传过来的内容。比如，我们要查看 GitHub 上用户 xxx 的代码仓的情况，可以使用下面这条命令

curl -s https://api.github.com/users/xxx/repos | vim -

curl 命令访问 GitHub 的 API，把输出通过管道传给 VIM，方便我直接在 VIM 中查看用户 xxx 的代码仓的细节。

这里需要注意的是，在 VIM 命令后面有一个 -，表示 VIM 将使用管道作为输入。

第三个适用场景：在 VIM 中调用系统工具

除了系统调用 VIM 和使用管道进行集成之外，我们还可以在 VIM 中反过来调用系统的其他工具。这里，我与你介绍一个最实用的命令：在可视模式中使用! 命令调用外部程序。

比如，我想给一个文件里的中间几行内容前加上从 1000 开始的数字序号。

在 Linux 系统里，我们可以使用 nl 这个命令行工具完成，具体的命令是 nl -v 1000。所以，我们可以把这一部分文本单独保存为文件，使用 nl 处理后，再把处理结果拷贝回 VIM 中。

虽然可以达到目的，但过程繁琐。幸运的是，我们可以在 VIM 里面直接使用 nl，并把处理结果插入 VIM 中，具体操作如下。

首先，使用命令 V 进入可视模式，并选择这一部分文字。

然后，输入命令!，VIM 的最后一行会显示’<,’>! ，表示由外部程序操作选中的部分。这时，我们输入 nl -v 1000 并回车。

VIM 就会把选择的文本传给 nl，nl 在每一行前面添加序号，再把处理结果传给 VIM，从而完成了我们想要的编辑结果，也就是在所选的几行文字前面添加从 1000 到 1006 的序号。

同时，在 VIM 的底部，会显示 7 lines filtered，意思是 VIM 里面的 7 行文本被使用外部工具处理过。是不是很方便？

我每次使用这样的功能时都觉得很爽，因为这可以让我聚焦在工作上，而不会把时间花在繁琐的操作上。

以上就是 3 个最常用的 VIM 和其他工具集成的例子。这种工具的集成工作方式，可以大大提高单个工具所能带来的效率提升，我还会在后面的文章中与你详细讨论。

总结

在今天这篇文章中，我与你介绍了高效学习 VIM 的三个步骤，即：第一，学习 VIM 的命令模式和命令组合方式；第二，学习文本编辑过程中各个环节最常用的命令；第三，在自己的工作环境中找到适用 VIM 的场景。

事实上，我今天与你讲述的 VIM 命令和使用技巧，只是最常见的跨平台使用中的共性部分，对效能提升带来的效果也最直接、最明显。

但，这些远没有覆盖 VIM 的强大功能。比如，我没有与你讨论多文件、多 Tab、多窗口编辑的场景，以及插件的话题。如果你需要的话，可以把 VIM 配置成一个类似 IDE 的开发环境，进入沉浸式的 VIM 使用体验。关于插件方面的内容，我推荐 3 个插件：

pathogen。它是一个插件管理软件，很好地解决了 VIM 自带插件删除不理想的问题。关于 pathogen 的使用，你可以参考下这篇文章。

nerdtree，在 VIM 中添加文件夹管理的功能。

fugitive，在 VIM 中添加查看、编辑 Git 内容的功能。它的功能简直是强大到变态。

其中，pathogen 和 fugitive 的作者都是Tim Pope，一个 VIM 牛人。如果你想了解更多的插件的话，可以看看他的其他 VIM 插件。

不容置疑的是，VIM 的学习曲线非常长，即使我已经使用了 15 年，仍然会时不时地学习到一些新东西。只要你愿意，就可以一直学习一直提高。

思考题

你想要分享的最炫酷的 VIM 技巧是什么呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

25 | 玩转Git：五种提高代码提交原子性的基本操作

你好，我是葛俊。今天，我们来聊一聊 Git 吧。

毫无疑问，Git 是当前最流行的代码仓管理系统，可以说是开发者的必备技能。它非常强大，使用得当的话可以大幅助力个人效能的提升。一个最直接的应用就是，可以帮助我们提升代码提交的原子性。如果一个团队的成员都能熟练使用 Git 的话，可以大大提高团队代码的模块化、可读性、可维护性，从而提高团队的研发效能。但可惜的是，现实情况中，由于 Git 比较复杂，用得好的人并不多。

所以接下来，我会通过两篇文章，与你详细讲述如何使用 Git 助力实现代码原子性。今天这篇文章，我先与你分享 Git 支持原子性的 5 种基础操作；下一篇文章，则会给你介绍 Facebook 开发人员是怎样具体应用这些基础操作去实现代码原子性的。

通过这两篇文章，我希望你能够：

了解在分布式代码仓管理系统中，如何通过对代码提交的灵活处理，实现提交的原子性；

帮你学习到 Git 的实用技巧，提高开发效率。

我在第 21 篇文章中提到，代码提交的原子性指的是，一个提交包含一个不可分割的特性、修复或者优化。如果用一个提交完成一个功能，这个提交还是会比较大的话，我们需要把这个功能再拆分为子功能。

为什么要强调代码提交的原子性呢？这是因为它有以下 3 大好处：

可以让代码结构更清晰、更容易理解；

出了问题之后方便定位，并可以针对性地对问题提交进行“回滚”；

在功能开关的协助下，可以让开发者尽快把代码推送到 origin/master 上进行合并。这正是持续集成的基础。

而 Git 之所以能够方便我们实现原子性提交，主要有两方面的原因：

Git 提供方便、灵活的提交、分支处理功能，使得我们可以灵活地产生提交、修改提交、拆分提交，甚至改变提交的先后顺序。

Git 是一个分布式代码仓管理系统，每个开发人员在本地都有一个代码仓，从而可以放心在本地代码仓中使用上述功能，不用操心会影响到远程共享代码仓。

下面，我就来与你分享 Git 支持原子性的 5 种基础操作，具体包括：

用工作区改动的一部分产生提交；

对当前提交进行拆分；

修改当前提交；

交换多个提交的先后顺序；

修改非当前提交。

需要注意的是，在接下来的两篇文章里，我只会与你详细介绍针对原子性相关的操作，而关于 Git 的一些基础概念和使用方法，推荐你参考“图解 Git”这篇文章。

基本操作一：把工作区里代码改动的一部分转变为提交

如果是把整个文件添加到提交中，操作很简单：先用 git add < 文件名 > 把需要的文件添加到 Git 暂存区，然后使用 git commit 命令提交即可。这个操作比较常见，我们应该都比较熟悉。

但在工作中，一个文件里的改动常常会包含多个提交的内容。比如，开发一个功能时，我们常常会顺手修复一些格式规范方面的东西；再比如，一个功能比较大的时候，改动常常会涉及几个提交内容。那么，在这些情况下，为了实现代码提交的原子性，我们就需要只把文件里的一部分改动添加到提交中，剩下的部分暂时不产生提交。针对这个需求，Git 提供了 git add -p 命令。

比如，我在 index.js 文件里有两部分改动，一部分是添加一个叫作 timestamp 的 endpoint，另一部分是使用变量来定义一个魔术数字端口：

## 显示工作区中的改动

> git diff

diff --git a/index.js b/index.js

index 63b6300..986fcd8 100644

--- a/index.js

+++ b/index.js

@@ -1,8 +1,14 @@

+var port = 3000  ## <-- 魔术数字变量化

 var express = require('express')

 var app = express()

## vvv 添加 endpoint

+app.get('/timestamp', function (req, res) {

+  res.send('' + Date.now())

+})

+

 app.get('/', function (req, res) {

   res.send('hello world')

 })

-app.listen(3000)

+// Start the server

+app.listen(port) ## <-- 端口魔术数字变量化

这时，运行 git add -p index.js 命令，Git 会把文件改动分块儿显示，并提供操作选项，比如我可以通过 y 和 n 指令来选择是否把当前改动添加到 Git 的提交暂存区中，也可以通过 s 指令把改动块儿再进行进一步拆分。通过这些指令，我就可以选择性地只把跟端口更改相关的改动添加到 Git 的暂存区中。

> git add -p index.js

diff --git a/index.js b/index.js

index 63b6300..986fcd8 100644

--- a/index.js

+++ b/index.js

@@ -1,8 +1,14 @@

+var port = 3000

 var express = require('express')

 var app = express()

+app.get('/timestamp', function (req, res) {

+  res.send('' + Date.now())

+})

+

 app.get('/', function (req, res) {

   res.send('hello world')

 })

-app.listen(3000)

+// Start the server

+app.listen(port)

Stage this hunk [y,n,q,a,d,s,e,?]? s

Split into 3 hunks.

@@ -1,3 +1,4 @@

+var port = 3000

 var express = require('express')

 var app = express()

Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? y

@@ -1,7 +2,11 @@

 var express = require('express')

 var app = express()

+app.get('/timestamp', function (req, res) {

+  res.send('' + Date.now())

+})

+

 app.get('/', function (req, res) {

   res.send('hello world')

 })

Stage this hunk [y,n,q,a,d,K,j,J,g,/,e,?]? n

@@ -4,5 +9,6 @@

 app.get('/', function (req, res) {

   res.send('hello world')

 })

-app.listen(3000)

+// Start the server

+app.listen(port)

Stage this hunk [y,n,q,a,d,K,g,/,e,?]? y

当整个文件的所有改动块儿都处理完成之后，通过 git diff --cached 命令可以看到，我的确只是把需要的那一部分改动，也就是端口相关的改动，添加到了暂存区:

> git diff --cached

diff --git a/index.js b/index.js

index 63b6300..7b82693 100644

--- a/index.js

+++ b/index.js

@@ -1,3 +1,4 @@

+var port = 3000

 var express = require('express')

 var app = express()

@@ -5,4 +6,5 @@ app.get('/', function (req, res) {

   res.send('hello world')

 })

-app.listen(3000)

+// Start the server

+app.listen(port)

通过 git diff 命令，我们可以看到，endpoint 相关的改动仍留在工作区：

> git diff

diff --git a/index.js b/index.js

index 7b82693..986fcd8 100644

--- a/index.js

+++ b/index.js

@@ -2,6 +2,10 @@ var port = 3000

 var express = require('express')

 var app = express()

+app.get('/timestamp', function (req, res) {

+  res.send('' + Date.now())

+})

+

 app.get('/', function (req, res) {

   res.send('hello world')

 })

最后，再通过 git commit 命令，我就可以产生一个只包含端口相关改动的提交，实现了将本地代码改动的一部分转变为提交的目的。

如果你想深入了解 git add -p 的内容，可以参考这篇文章。

通过 git add -p，我们可以把工作区中的代码拆分成多个提交。但是，如果需要拆分的代码已经被放到了一个提交中，怎么办？如果这个提交已经推送到了远程代码仓共享分支，那就没有办法了。但如果这个提交还只是在本地，我们就可以对它进行拆分。

基本操作二：对当前提交进行拆分

所谓当前提交，指的是当前分支的 HEAD 指向的提交。

我继续以上面的代码示例向你解释应该如何操作。假如，我已经把关于 endpoint 的改动和端口的改动产生到了同一个提交里，具体怎么拆分呢？

这时，我可以先“取消”已有的提交，也就是把提交的代码重新放回到工作区中，然后再使用 git add -p 的方法重新产生提交。这里的取消是带引号的，因为在 Git 里所有的提交都是永久存在的，所谓取消，只不过是把当前分支指到了需要取消的提交的前面而已。

首先，我可以用 git log 查看历史，并使用 git show 确认提交包含了 endpoint 改动和端口改动：

## 查看提交历史

> git log --graph --oneline --all

* 7db082a (HEAD -> master) Change magic port AND add a endpoint

* 352cc92 Add gitignore file for node_modules

* e2dacbc (origin/master) Added the simple web server endpoint

...

## 查看提交

> git show

commit 7db082ab0f105ea185c89a0ba691857b55566469 (HEAD -> master)

...

diff --git a/index.js b/index.js

index 63b6300..986fcd8 100644

--- a/index.js

+++ b/index.js

@@ -1,8 +1,14 @@

+var port = 3000

 var express = require('express')

 var app = express()

+app.get('/timestamp', function (req, res) {

+  res.send('' + Date.now())

+})

+

 app.get('/', function (req, res) {

   res.send('hello world')

 })

-app.listen(3000)

+// Start the server

+app.listen(port)

然后，用 git branch temp 命令产生一个临时分支 temp，指向当前 HEAD。temp 分支的作用是，预防代码丢失。如果后续工作出现问题的话，我可以使用 git reset --hard temp 把代码仓、暂存区和工作区都恢复到这个位置，从而不会丢失代码。

> git branch temp

> git log --graph --oneline --all

* 7db082a (HEAD -> master, temp) Change magic port AND add a endpoint

* 352cc92 Add gitignore file for node_modules

* e2dacbc (origin/master) Added the simple web server endpoint

...

接下来，运行 git reset HEAD^ 命令，把当前分支指向目标提交 HEAD^，也就是当前提交的父提交。同时，在没有接–hard 或者–soft 参数时，git reset 会把目标提交的内容同时复制到暂存区，但不会复制到工作区。所以，工作区的内容仍然是当前提交的内容，仍然有 endpoint 相关改动和端口相关改动。也就是说，这个命令的效果，就是让我回到了对这两个改动进行提交之前的状态：

> git reset HEAD^

Unstaged changes after reset:

M index.js

> git status

On branch master

Your branch is ahead of 'origin/master' by 1 commit.

  (use "git push" to publish your local commits)

Changes not staged for commit:

  (use "git add <file>..." to update what will be committed)

  (use "git checkout -- <file>..." to discard changes in working directory)

 modified:   index.js

no changes added to commit (use "git add" and/or "git commit -a")

15:06:58 (master) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo

## 改动在工作区

> git diff

diff --git a/index.js b/index.js

index 63b6300..986fcd8 100644

--- a/index.js

+++ b/index.js

@@ -1,8 +1,14 @@

+var port = 3000

 var express = require('express')

 var app = express()

+app.get('/timestamp', function (req, res) {

+  res.send('' + Date.now())

+})

+

 app.get('/', function (req, res) {

   res.send('hello world')

 })

-app.listen(3000)

+// Start the server

+app.listen(port)

## 输出为空

> git diff --cached

最后，我就可以使用上面介绍过的 git add -p 的方法把工作区中的改动拆分成两个提交了。

基本操作三：修改当前提交

如果只需要修改 Commit Message 的话，直接使用 git commit --amend 命令，Git 就会打开你的默认编辑器让你修改，修改完成之后保存退出即可。

如果要修改的是文件内容，可以使用 git add、git rm 等命令把改动添加到暂存区，再运行 git commit --amend，最后输入 Commit Message 保存退出即可。

基本操作四：交换多个提交的先后顺序

有些时候，我们需要把多个提交交换顺序。比如，master 分支上有两个提交 A 和 B，B 在 A 之上，两个提交都还没有推送到 origin/master 上。

这时，我先完成了提交 B，想把它先单独推送到 origin/master 上去，就需要交换 A 和 B 的位置，使得 A 在 B 之上。我可以使用 git rebase --interactive（选项–interactive 可以简写为 -i）来实现这个功能。

首先，还是使用 git branch temp 产生一个临时分支，确保代码不会丢失。然后，使用 git log --oneline --graph 来确认当前提交历史：

> git log --oneline --graph

* 7b6ea30 (HEAD -> master, temp) Add a new endpoint to return timestamp

* b517154 Change magic port number to variable

* 352cc92 (origin/master) Add gitignore file for node_modules

* e2dacbc Added the simple web server endpoint

* 2f65a89 Init commit created by installing express module

接下来，运行

> git rebase -i origin/master

Git 会打开我的默认编辑器，让我选择 rebase 的具体操作：

pick b517154 Change magic port number to variable

pick 7b6ea30 Add a new endpoint to return timestamp

# Rebase 352cc92..7b6ea30 onto 352cc92 (2 commands)

#

# Commands:

# p, pick <commit> = use commit

# r, reword <commit> = use commit, but edit the commit message

# e, edit <commit> = use commit, but stop for amending

# s, squash <commit> = use commit, but meld into previous commit

# f, fixup <commit> = like "squash", but discard this commit's log message

# x, exec <command> = run command (the rest of the line) using shell

# b, break = stop here (continue rebase later with 'git rebase --continue')

# d, drop <commit> = remove commit

# l, label <label> = label current HEAD with a name

# t, reset <label> = reset HEAD to a label

# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]

# .       create a merge commit using the original merge commit's

# .       message (or the oneline, if no original merge commit was

# .       specified). Use -c <commit> to reword the commit message.

#

# These lines can be re-ordered; they are executed from top to bottom.

#

# If you remove a line here THAT COMMIT WILL BE LOST.

#

# However, if you remove everything, the rebase will be aborted.

#

# Note that empty commits are commented out

rebase 命令一般翻译为变基，意思是改变分支的参考基准。具体到 git rebase -i origin/master 命令，就是把从 origin/master 之后到当前 HEAD 的所有提交，也就是 A 和 B，重新有选择地放到 origin/master 上面。你可以选择放或者不放某一个提交，也可以选择放置顺序，还可以选择将多个提交合并成一个，等等。另外，这里说的放一个提交，指的就是在 HEAD 之上应用一个提交的意思。

Git rebase -i 打开编辑器时，里面默认的操作列表是把原有提交全部原封不动地放到新的参考基准上去，具体到这个例子，是用两个 pick 命令把 A 和 B 先后重新放到 origin/master 之上，如果我直接保存退出的话，结果跟 rebase 之前没有任何改变。

这里，因为我需要的操作是交换 A 和 B 的顺序，所以交换两个 pick 指令行，保存退出即可。Git rebase 就会先后把 B 和 A 放到 origin/master 上。

pick 7b6ea30 Add a new endpoint to return timestamp

pick b517154 Change magic port number to variable

# Rebase 352cc92..7b6ea30 onto 352cc92 (2 commands)

# ...

至此，我就完成了交换两个提交的先后顺序。接下来，我可以用 git log 命令，来确认 A 和 B 的确是交换了顺序。

## 以下是 git rebase -i origin/master 的输出结果

Successfully rebased and updated refs/heads/master.

## 查看提交历史

> git log --oneline --graph --all

* 65c41e6 (HEAD -> master) Change magic port number to variable

* 40e2824 Add a new endpoint to return timestamp

| * 7b6ea30 (temp) Add a new endpoint to return timestamp

| * b517154 Change magic port number to variable

|/

* 352cc92 (origin/master) Add gitignore file for node_modules

* e2dacbc Added the simple web server endpoint

* 2f65a89 Init commit created by installing express module

值得注意的是，A 和 B 的 commit SHA1 改变了，因为它们实际上是新产生出来的 A 和 B 的拷贝，原来的两个提交仍然存在（图中的阴影部分），我们还可以用分支 temp 找到它们，但不再需要它们了。如果 temp 分支被删除，A 和 B 也会自动被 Git 的垃圾收集过程 gc 清除掉。

其实，git rebase -i 的功能非常强大，除了交换提交的顺序外，还可以删除提交、和并多个提交。如果你想深入了解这部分内容的话，可以参考“Git 工具 - 重写历史”这篇文章。

基本操作五：修改非头部提交

在上面的基本操作二、三、四中，我与你介绍的都是对当前分支头部的一个提交或者多个提交进行操作。但在工作中，为了方便实现原子性，我们常常需要修改历史提交，也就是修改非头部提交。对历史提交操作，最方便的方式依然是使用强大的 git rebase -i。

接下来，我继续用上面修改 A 和 B 两个提交的顺序的例子来做说明。在还没有交换提交 A 和 B 的顺序时，也就是 B 在 A 之上的时候，我发现我需要修改提交 A。

首先，我运行 git rebase -i origin/master；然后，在弹出的编辑窗口中把原来的“pick b517154”的一行改为“edit b517154”。其中，b517154 是提交 A 的 SHA1。

edit b517154 Change magic port number to variable

pick 7b6ea30 Add a new endpoint to return timestamp

# Rebase 352cc92..7b6ea30 onto 352cc92 (2 commands)

# ...

而“edit b517154”，是告知 Git rebase 命令，在应用了 b517154 之后，暂停后续的 rebase 操作，直到我手动运行 git rebase --continue 通知它继续运行。这样，当我在编辑器中保存修改并退出之后，git rebase 就会暂停。

> git rebase -i origin/master

Stopped at b517154...  Change magic port number to variable

You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

22:29:35 (master|REBASE-i) ~/jksj-repo/git-atomic-demo >

这时，我可以运行 git log --oneline --graph --all，确认当前 HEAD 已经指向了我想要修改的提交 A。

> git log --oneline --graph --all

* 7b6ea30 (master) Add a new endpoint to return timestamp

* b517154 (HEAD) Change magic port number to variable

* 352cc92 (origin/master) Add gitignore file for node_modules

* e2dacbc Added the simple web server endpoint

* 2f65a89 Init commit created by installing express module

接下来，我就可以使用基本操作二中提到的方法对当前提交（也就是 A）进行修改了。具体来说，就是修改文件，之后用 git add < 文件名 >，然后再运行 git commit --amend。

## 检查当前 HEAD 内容

> git show

commit b51715452023fcf12432817c8a872e9e9b9118eb (HEAD)

Author: Jason Ge <gejun_1978@yahoo.com>

Date:   Mon Oct 14 12:50:36 2019

    Change magic port number to variable

    Summary:

    It's not good to have a magic number. This commit changes it to a

    varaible.

    Test:

    Run node index.js and verified the root endpoint still works.

diff --git a/index.js b/index.js

index 63b6300..7b82693 100644

--- a/index.js

+++ b/index.js

@@ -1,3 +1,4 @@

+var port = 3000

 var express = require('express')

 var app = express()

@@ -5,4 +6,5 @@ app.get('/', function (req, res) {

   res.send('hello world')

 })

-app.listen(3000)

+// Start the server

+app.listen(port)

## 用 VIM 对文件进行修改，在注释部分添加 "at a predefined port"

> vim index.js

## 查看工作区中的修改

> git diff

diff --git a/index.js b/index.js

index 7b82693..eb53f5f 100644

--- a/index.js

+++ b/index.js

@@ -6,5 +6,5 @@ app.get('/', function (req, res) {

   res.send('hello world')

 })

-// Start the server

+// Start the server at a predefined port

 app.listen(port)

22:40:10 (master|REBASE-i) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo

> git add index.js

## 对修改添加到提交 A 中去

> git commit --amend

[detached HEAD f544b12] Change magic port number to variable

 Date: Mon Oct 14 12:50:36 2019 +0800

 1 file changed, 3 insertions(+), 1 deletion(-)

22:41:18 (master|REBASE-i) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo

## 查看修改过后的 A。确认其包含了新修改的内容 "at a predefined port"

> git show

commit f544b1247a10e469372797c7dd08a32c0d59b032 (HEAD)

Author: Jason Ge <gejun_1978@yahoo.com>

Date:   Mon Oct 14 12:50:36 2019

    Change magic port number to variable

    Summary:

    It's not good to have a magic number. This commit changes it to a

    varaible.

    Test:

    Run node index.js and verified the root endpoint still works.

diff --git a/index.js b/index.js

index 63b6300..eb53f5f 100644

--- a/index.js

+++ b/index.js

@@ -1,3 +1,4 @@

+var port = 3000

 var express = require('express')

 var app = express()

@@ -5,4 +6,5 @@ app.get('/', function (req, res) {

   res.send('hello world')

 })

-app.listen(3000)

+// Start the server at a predefined port

+app.listen(port)

执行完成之后，我就可以运行 git rebase --continue，完成 git rebase -i 的后续操作，也就是在 A 之上再应用提交 B，并把 HEAD 重新指向了 B，从而完成了对历史提交 A 的修改。

## 继续运行 rebase 命令的其他步骤

> git rebase --continue

Successfully rebased and updated refs/heads/master.

## 查看提交历史

> git log --oneline --graph --all

* 27cba8c (HEAD -> master) Add a new endpoint to return timestamp

* f544b12 Change magic port number to variable

| * 7b6ea30 (temp) Add a new endpoint to return timestamp

| * b517154 Change magic port number to variable

|/

* 352cc92 (origin/master) Add gitignore file for node_modules

* e2dacbc Added the simple web server endpoint

* 2f65a89 Init commit created by installing express module

经过 rebase 命令，我重新产生了提交 A 和 B。同样的，A 和 B 是新生成的两个提交，原来的 A 和 B 仍然存在。

以上，就是修改历史提交内容的步骤。

如果我们需要对历史提交进行拆分的话，步骤也差不多：首先，使用 git rebase -i，在需要拆分的提交处使用 edit 指令；然后，在 git rebase -i 暂停的时候，使用基本操作 2 的方法对目标提交进行拆分；拆分完成之后，运行 git rebase --continue 即可。

小结

今天，我与你介绍了 Git 支持代码提交原子性的五种基本操作，包括用工作区改动的一部分产生提交、对当前提交进行拆分、修改当前提交、交换多个提交的先后顺序，以及对非头部提交进行修改。

掌握这些基本操作，可以让我们更灵活地对代码提交进行修改、拆分、合并和交换顺序，为使用 Git 实现代码原子性的工作流打好基础。

其实，这些基本操作非常强大和实用，除了可以用来提高提交的原子性外，还可以帮助我们日常开发。比如，我们可以把还未完成的功能尽快产生提交，确保代码不会丢失，等到后面再修改。又比如，可以产生一些用来帮助自己本地开发的提交，始终放在本地，不推送到远程代码仓。

在我看来，Git 学习曲线比较陡而且长，帮助手册也可以说是晦涩难懂，但一旦弄懂，它能让你超级灵活地对本地代码仓进行处理，帮助你发现代码仓管理系统的新天地。git rebase -i 命令，就是一个非常典型的例子。一开始，你会觉得它有些难以理解，但搞懂之后就超级有用，可以帮助你高效地解决非常多的问题。所以，在我看来，在 Git 上投入一些时间绝对值得！

为了方便你学习，我把这篇文章涉及的代码示例放到了GitHub上，推荐你 clone 下来多加练习。

思考题

对于交换多个提交的先后顺序，除了使用 rebase -i 命令外，你还知道什么其他办法吗？

文章中提到，如果一个提交已经推送到了远程代码仓共享分支，那就没有办法对它进行拆分了。这个说法其实有些过于绝对。你知道为什么绝大部分情况下不能拆分，而什么情况下还可以拆分呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

26 | Facebook怎样实现代码提交的原子性？

你好，我是葛俊。今天，我们继续来聊聊如何通过 Git 提高代码提交的原子性吧。

在上一篇文章中，我给你详细介绍了 Git 助力提高代码提交原子性的五条基础操作，今天我们再来看看 Facebook 的开发人员具体是如何使用这些操作来实现提交的原子性的。

为了帮助你更直观地理解、学习，在这篇文章里，我会与你详细描述工作场景，并列出具体命令。同时，我还把这些命令的输出也都放到了文章里，供你参考。所以，这篇文章会比较长、比较细。不过不要担心，这些内容都是日常工作中的自然流程，阅读起来也会比较顺畅。

在 Facebook，开发人员最常使用两种 Git 工作流：

使用一个分支，完成所有需求的开发；

使用多个分支，每个分支支持一个需求的开发。

两种工作流都利用 Git 的超强功能来提高代码原子性。这里的“需求”包括功能开发和缺陷修复，用大写字母 A、B、C 等表示；每个需求都可能包含有多个提交，每个提交用需求名 + 序号表示。比如，A 可能包含 A1、A2 两个提交，B 只包含 B1 这一个提交，而 C 包含 C1、C2、C3 三个提交。

需要强调的是，这两种工作流中的一个分支和多个分支，都是在开发者本地机器上的分支，不是远程代码仓中的功能分支。我在前面第 7 篇文章中提到过，Facebook 的主代码仓是不使用功能分支的。

另外，这两种 Git 工作流对代码提交原子性的助力作用，跟主代码仓是否使用单分支开发没有关系。也就是说，即使你所在团队的主仓没有使用 Facebook 那样的单分支开发模式，仍然可以使用这两种工作流来提高代码提交的原子性。

接下来，我们就先看看第一种工作流，也就是使用一个分支完成所有需求的开发。

工作流一：使用一个分支完成所有需求的开发

这种工作流程的最大特点是，使用一个分支上的提交链，大量使用 git rebase -i 来修改提交链上的提交。这里的提交链，指的是当前分支上，还没有推送到远端主仓共享分支的所有提交。

首先，我们需要设置一个本地分支来开发需求，通过这个分支和远端主仓的共享分支进行交互。本地分支通常直接使用 master 分支，而远端主仓的共享分支一般是 origin/master，也叫作上游分支（upstream）。

一般来说，在 git clone 的时候，master 是默认已经产生，并且是已经跟踪 origin/master 了的，你不需要做任何设置，可以查看.git/config 文件做确认：

> cat .git/config
...
[remote "origin"]
  url = git@github.com:jungejason/git-atomic-demo.git
  fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
  remote = origin
  merge = refs/heads/master

可以看到，branch "master"里有一个 remote = origin 选项，表明 master 分支在跟踪 origin 这个上游仓库；另外，config 文件里还有一个 remote "origin"选项，列举了 origin 这个上游仓库的地址。

当然，除了直接查看 config 文件外，Git 还提供了命令行工具。你可以使用 git branch -vv 查看某个分支是否在跟踪某个远程分支，然后再使用 git remote show去查看远程代码仓的细节。

## 查看远程分支细节
> git branch -vv
  master      5055c14 [origin/master: behind 1] Add documentation for getRandom endpoint

## 查看分支跟踪的远程代码仓细节
> git remote show origin
* remote origin
  Fetch URL: git@github.com:jungejason/git-atomic-demo.git
  Push  URL: git@github.com:jungejason/git-atomic-demo.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branches configured for 'git pull':
    master  merges with remote master
  Local ref configured for 'git push':
    master pushes to master (fast-forwardable)
11:07:36 (master2) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo

因为 config 文件简单直观，所以我常常直接到 config 文件里面查看和修改来完成这些操作。关于远程跟踪上游代码仓分支的更多细节，比如产生新分支、设置上游分支等，你可以参考Git: Upstream Tracking Understanding这篇文章。

设置好分支之后，我们来看看这个工作流中的具体步骤。

单分支工作流具体步骤

单分支工作流的步骤，大致包括以下 4 步：

一个原子性的功能完成后，使用第 25 篇文章中提到的改变提交顺序的方法，把它放到距离 origin/master 最近的地方。

把这个提交发到代码审查系统 Phabricator 上进行质量检查，包括代码审查和机器检查。在等待质量检查结果的同时，继续其他提交的开发。

如果没有通过质量检查，则需要对提交进行修改，修改之后返回第 2 步。

如果通过质量检查， 就把这个提交推送到主代码仓的共享分支上，然后继续其他分支的开发，回到第 1 步。

请注意第二步的目的是，确保入库代码的质量，你可以根据实际情况进行检查。比如，你可以通过提交 PR 触发机器检查的工作流，也可以运行单元测试自行检查。如果没有任何质量检查的话，至少也要进行简单手工验证，让进入到远程代码仓的代码有起码的质量保障。

接下来，我设计了一个案例，尽量模拟我在 Facebook 的真实开发场景，与你讲述这个工作流的操作步骤。大致场景是这样的：我本来在开发需求 A，这时来了更紧急的需求 B。于是，我开始开发 B，把 B 分成两个原子性提交 B1 和 B2，并在 B1 完成之后最先推送到远程代码仓共享分支。

这个案例中，提交的改动很简单，但里面涉及了很多开发技巧，可供你借鉴。

阶段 1：开始开发需求 A

某天，我接到开发需求 A 的任务，要求在项目中添加一个 README 文件，对项目进行描述。

我先添加一个简单的 README.md 文件，然后用 git commit -am ‘readme’ 快速生成一个提交 A1，确保代码不会丢失。

## 文件内容
> cat README.md
## This project is for demoing git

## 产生提交
> git commit -am 'readme'
[master 0825c0b] readme
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

## 查看提交历史
> git log --oneline --graph
* 0825c0b (HEAD -> master) readme
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp
...

## 查看提交细节
> git show
commit 0825c0b6cd98af11b171b52367209ad6e29e38d1 (HEAD -> master)
Author: Jason Ge <gejun_1978@yahoo.com>
Date:   Tue Oct 15 12:45:08 2019

    readme

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..789cfa9
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+## This project is for demoing git

这时，A1 是 master 上没有推送到 origin/master 的唯一提交，也就是说，是提交链上的唯一提交。

请注意，A1 的 Commit Message 很简单，就是“readme”这 6 个字符。在把 A1 发出去做代码质量检查之前，我需要添加 Commit Message 的细节。

图 1 提交链状态第 1 步

阶段 2：开始开发需求 B

这时，来了另外一个紧急需求 B，要求是添加一个 endpoint getRandom。开发时，我不切换分支，直接在 master 上继续开发。

首先，我写一个 getRandom 的实现，并进行简单验证。

## 用 VIM 修改
> vim index.js

## 查看工作区中的改动
> git diff
diff --git a/index.js b/index.js
index 986fcd8..06695f6 100644
--- a/index.js
+++ b/index.js
@@ -6,6 +6,10 @@ app.get('/timestamp', function (req, res) {
   res.send('' + Date.now())
 })

+app.get('/getRandom', function (req, res) {
+  res.send('' + Math.random())
+})
+
 app.get('/', function (req, res) {
   res.send('hello world')
 })

## 用命令行工具 httpie 验证结果
> http localhost:3000/getRandom
HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 19
Content-Type: text/html; charset=utf-8
Date: Tue, 15 Oct 2019 03:49:15 GMT
ETag: W/"13-U1KCE8QRuz+dioGnmVwMkEWypYI"
X-Powered-By: Express

0.25407324324864167

为确保代码不丢失，我用 git commit -am ‘random’ 命令生成了一个提交 B1：

## 产生提交
> git commit -am 'random'
[master 7752df4] random
 1 file changed, 4 insertions(+)

## 查看提交历史
> git log --oneline --graph
* 7752df4 (HEAD -> master) random
* 0825c0b readme
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp
...

## 查看提交细节
> git show
commit f59a4084e3a2c620bdec49960371f8cc93b86825 (HEAD -> master)
Author: Jason Ge <gejun_1978@yahoo.com>
Date:   Tue Oct 15 11:55:06 2019

    random

diff --git a/index.js b/index.js
index 986fcd8..06695f6 100644
--- a/index.js
+++ b/index.js
@@ -6,6 +6,10 @@ app.get('/timestamp', function (req, res) {
   res.send('' + Date.now())
 })

+app.get('/getRandom', function (req, res) {
+  res.send('' + Math.random())
+})
+
 app.get('/', function (req, res) {
   res.send('hello world')
 })

B1 的 Commit Message 也很简陋，因为当前的关键任务是先把功能运行起来。

现在，我的提交链上有 A1 和 B1 两个提交了。

图 2 提交链状态第 2 步

接下来，我需要进行需求 B 的进一步开发：在 README 文件中给这个新的 endpoint 添加说明。

> git diff
diff --git a/README.md b/README.md
index 789cfa9..7b2b6af 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,3 @@
 ## This project is for demoing git
+
+You can visit endpoint getRandom to get a random real number.

我认为这个改动是 B1 的一部分，所以我用 git commit --amend 把它添加到 B1 中。

## 添加改动到 B1
> git add README.md
> git commit --amend
[master 27c4d40] random
 Date: Tue Oct 15 11:55:06 2019 +0800
 2 files changed, 6 insertions(+)

## 查看提交历史
> git log --oneline --graph
* 27c4d40 (HEAD -> master) random
* 0825c0b readme
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp

现在，我的提交链上还是 A1 和 B1’两个提交。这里的 B1’是为了区别之前的 B1，B1 仍然存在代码仓中，不过是不再使用了而已。

图 3 提交链状态第 3 步

阶段 3：拆分需求 B 的代码，把 B1’提交检查系统

这时，我觉得 B1’的功能实现部分，也就是 index.js 的改动部分，可以推送到 origin/master 了。

不过，文档部分也就是 README.md 文件的改动，还不够好，而且功能实现和文档应该分成两个原子性提交。于是，我将 B1’拆分为 B1’’ 和 B2 两部分。

## 将 B1'拆分
> git reset HEAD^
Unstaged changes after reset:
M  README.md   ## 这个将是 B2 的内容
M  index.js    ## 这个将是 B1''的内容

> git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)
  modified:   README.md
  modified:   index.js
no changes added to commit (use "git add" and/or "git commit -a")

> git add index.js
> git commit   ## 这里我认真填写 B1''的 Commit Message

> git add README.md
> git commit   ## 这里我认真填写 B2 的 Commit Message

## 查看提交历史
* 68d813f (HEAD -> master) [DO NOT PUSH] Add documentation for getRandom endpoint
* 7d43442 Add getRandom endpoint
* 0825c0b readme
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp

现在，提交链上有 A1、B1’’、B2 三个提交。

请注意，在这里我把功能实现和文档分为两个原子性提交，只是为了帮助说明我需要把 B1’进行原子性拆分而已，在实际工作中，很可能功能实现和文档就应该放在一个提交当中。

图 4 提交链状态第 4 步

提交 B1’拆开之后，为了把 B1’’ 推送到 origin/master 上去，我需要要把 B1’’ 挪到 A1 的前面。首先，运行 git rebase -i origin/master。

> git rebase -i origin/master

## 下面是弹出的编辑器
pick 0825c0b readme                   ## 这个是 A1
pick 7d43442 Add getRandom endpoint   ## 这个是 B1''
pick 68d813f [DO NOT PUSH] Add documentation for getRandom endpoint

# Rebase 7b6ea30..68d813f onto 7b6ea30 (3 commands)
...

然后，我把针对 B1’’ 的那一行挪到第一行，保存退出。

pick 7d43442 Add getRandom endpoint  ## 这个是 B1''
pick 0825c0b readme                  ## 这个是 A1
pick 68d813f [DO NOT PUSH] Add documentation for getRandom endpoint

# Rebase 7b6ea30..68d813f onto 7b6ea30 (3 commands)
...

git rebase -i 命令会显示运行成功，使用 git log 命令可以看到我成功改变了提交的顺序。

> git rebase -i origin/master
Successfully rebased and updated refs/heads/master.

> git log --oneline --graph
* 86126f7 (HEAD -> master) [DO NOT PUSH] Add documentation for getRandom endpoint
* 7113c16 readme
* 4d37768 Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp

现在，提交链上有 B1’’’、A1’、B2’三个提交了。请注意，B2’也是一个新的提交。虽然我只是交换了 B1’’ 和 A 的顺序，但 git rebase 的操作是重新应用，产生出了三个新提交。

图 5 提交链状态第 5 步

现在，我可以把 B1’’’ 发送给质量检查系统了。

首先，产生一个临时分支 temp 指向 B2’，确保能回到原来的代码；然后，用 git reset --hard 命令把 master 和 HEAD 指向 B1’’’。

> git branch temp
> git reset --hard 4d37768
HEAD is now at 4d37768 Add getRandom endpoint

## 检查提交链
> git log --oneline --graph
* 4d37768 (HEAD -> master) Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp
...

这时，提交链中只有 B1’’’。当然，A1’和 B2’仍然存在，只是不在提交链里了而已。

图 6 提交链状态第 6 步

最后，运行命令把 B1’’’ 提交到 Phabricator 上，结束后使用 git reset --hard temp 命令重新把 HEAD 指向 B2’。

## 运行 arc 命令把 B'''提交到 Phabricator 上
> arc diff

## 重新把 HEAD 指向 B2'
> git reset --hard temp
HEAD is now at 86126f7 [DO NOT PUSH] Add documentation for getRandom endpoint

## 检查提交链
> git log --oneline --graph
* 86126f7 (HEAD -> master, temp, single-branch-step-5) [DO NOT PUSH] Add documentation for getRandom endpoint
* 7113c16 readme
* 4d37768 Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp

这时，提交链又恢复成为 B1’’’、A1’、B2’三个提交了。

图 7 提交链状态第 7 步

阶段 4：继续开发 B2，同时得到 B1 的反馈，修改 B1

把 B1’’’ 发送到质量检查中心之后，我回到 B2’ 继续工作，也就是在 README 文件中继续添加关于 getRandom 的文档。我正在开发的过程中，得到 B1’’’ 的反馈，要求我对其进行修改。于是，我首先保存当前对 B2’的修改，用 git commit --amend 把它添加到 B2’中。

## 查看工作区中的修改
> git diff
diff --git a/README.md b/README.md
index 8a60943..1f06f52 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
 ## This project is for demoing git

 You can visit endpoint getRandom to get a random real number.
+The end endpoint is `/getRandom`.

## 把工作区中的修改添加到 B2'中
> git add README.md
> git commit --amend
[master 7b4269c] [DO NOT PUSH] Add documentation for getRandom endpoint
 Date: Tue Oct 15 17:17:18 2019 +0800
 1 file changed, 3 insertions(+)
* 7b4269c (HEAD -> master) [DO NOT PUSH] Add documentation for getRandom endpoint
* 7113c16 readme
* 4d37768 Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp
...

这时，提交链成为 B1’’’、A1’、B2’’ 三个提交了。

图 8 提交链状态第 8 步

接下来，我使用第 25 篇文章中介绍的基础操作对 B1’’’ 进行修改。

首先，在 git rebase -i origin/master 的文本输入框中，将 pick B1’’’ 那一行修改为 edit B1’’’，然后保存退出，git rebase 暂停在 B1’’’ 处：

> git rebase -i origin/master

## 以下是弹出编辑器中的文本内容
edit 4d37768 Add getRandom endpoint   ## <-- 这一行开头原本是 pick
pick 7113c16 readme
pick 7b4269c [DO NOT PUSH] Add documentation for getRandom endpoint

## 以下是保存退出后 git rebase -i origin/master 的输出
Stopped at 4d37768...  Add getRandom endpoint
You can amend the commit now, with
  git commit --amend
Once you are satisfied with your changes, run
  git rebase --continue

## 查看提交历史
> git log --oneline --graph
* 4d37768 (HEAD) Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp

这时，提交链上只有 B1’’’ 一个提交。

图 9 提交链状态第 9 步

然后，我对 index.js 进行修改，并添加到 B1’’’ 中，成为 B1’’’’。完成之后，再次把 B1’’’’ 发送到代码质量检查系统。

## 根据同事反馈，修改 index.js
> vim index.js
> git add index.js

## 查看修改
> git diff --cached
diff --git a/index.js b/index.js
index 06695f6..cc92a42 100644
--- a/index.js
+++ b/index.js
@@ -7,7 +7,7 @@ app.get('/timestamp', function (req, res) {
 })

 app.get('/getRandom', function (req, res) {
-  res.send('' + Math.random())
+  res.send('The random number is:' + Math.random())
 })

 app.get('/', function (req, res) {

## 把改动添加到 B1'''中。
> git commit --amend
[detached HEAD 29c8249] Add getRandom endpoint
 Date: Tue Oct 15 17:16:12 2019 +0800
 1 file changed, 4 insertions(+)
19:17:28 (master|REBASE-i) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo
> git show
commit 29c82490256459539c4a1f79f04823044f382d2b (HEAD)
Author: Jason Ge <gejun_1978@yahoo.com>
Date:   Tue Oct 15 17:16:12 2019
    Add getRandom endpoint

    Summary:
    As title.

    Test:
    Verified it on localhost:3000/getRandom

diff --git a/index.js b/index.js
index 986fcd8..cc92a42 100644
--- a/index.js
+++ b/index.js
@@ -6,6 +6,10 @@ app.get('/timestamp', function (req, res) {
   res.send('' + Date.now())
 })

+app.get('/getRandom', function (req, res) {
+  res.send('The random number is:' + Math.random())
+})
+
 app.get('/', function (req, res) {
   res.send('hello world')
 })

## 查看提交链
> git log --oneline --graph
* 29c8249 (HEAD) Add getRandom endpoint
* 7b6ea30 (origin/master, git-add-p) Add a new endpoint to return timestamp

## 将 B1''''发送到代码审查系统
> arc diff

这时，提交链只有 B1’’’’ 一个提交。

图 10 提交链状态第 10 步

最后，运行 git rebase --continue 完成整个 git rebase -i 操作。

> git rebase --continue
Successfully rebased and updated refs/heads/master.

## 查看提交历史
> git log --oneline --graph
* bc0900d (HEAD -> master) [DO NOT PUSH] Add documentation for getRandom endpoint
* 1562cc7 readme
* 29c8249 Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp
...

这时，提交链包含 B1’’’’、A1’’、B2’’’ 三个提交。

图 11 提交链状态第 11 步

阶段 5：继续开发 A1，并发出代码审查

这时，我认为 A1’’ 比 B2’’’ 更为紧急重要，于是决定先完成 A1’’ 的工作并发送审查，同样也是使用 git rebase -i。

> git rebase -i HEAD^^  ## 两个 ^^ 表示从当前 HEAD 前面两个提交的地方 rebase

## git rebase 弹出编辑窗口
edit 1562cc7 readme  <-- 这一行开头原来是 pick。这个是 A1''
pick bc0900d [DO NOT PUSH] Add documentation for getRandom endpoint

## 保存退出后 git rebase -i HEAD^^ 的结果
Stopped at 1562cc7...  readme
You can amend the commit now, with
  git commit --amend
Once you are satisfied with your changes, run
  git rebase --continue

## 对 A1''修改
> vim README.md
> git diff
diff --git a/README.md b/README.md
index 789cfa9..09bcc7d 100644
--- a/README.md
+++ b/README.md
@@ -1 +1 @@
-## This project is for demoing git
+# This project is for demoing atomic commit in git

> git add README.md
> git commit --amend

## 下面是 git commit 弹出编辑器，在里面完善 A1''的 Commit Message
Add README.md file

Summary: we need a README file for the project.

Test: none.

# Please enter the Commit Message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Tue Oct 15 12:45:08 2019 +0800
#
# interactive rebase in progress; onto 29c8249
# Last command done (1 command done):
#    edit 1562cc7 readme
# Next command to do (1 remaining command):
#    pick bc0900d [DO NOT PUSH] Add documentation for getRandom endpoint
# You are currently splitting a commit while rebasing branch 'master' on '29c8249'.
#
# Changes to be committed:
#       new file:   README.md
#

## 保存退出后 git commit 输出结果
[detached HEAD 2c66fe9] Add README.md file
 Date: Tue Oct 15 12:45:08 2019 +0800
 1 file changed, 1 insertion(+)
 create mode 100644 README.md

## 继续执行 git rebase -i
> git rebase --continue
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
error: could not apply bc0900d... [DO NOT PUSH] Add documentation for getRandom endpoint
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply bc0900d... [DO NOT PUSH] Add documentation for getRandom endpoint

这个过程可能会出现冲突，比如在 A1’’’ 之上应用 B2’’’ 时可能会出现冲突。冲突出现时，你可以使用 git log 和 git status 命令查看细节。

## 查看当前提交链
> git log --oneline --graph
* 2c66fe9 (HEAD) Add README.md file
* 29c8249 Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp
...

## 查看冲突细节
> git status
interactive rebase in progress; onto 29c8249
Last commands done (2 commands done):
   edit 1562cc7 readme
   pick bc0900d [DO NOT PUSH] Add documentation for getRandom endpoint
No commands remaining.
You are currently rebasing branch 'master' on '29c8249'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git reset HEAD <file>..." to unstage)
  (use "git add <file>..." to mark resolution)

  both modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

## 用 git diff 和 git diff --cached 查看更多细节
> git diff
diff --cc README.md
index 09bcc7d,1f06f52..0000000
--- a/README.md
+++ b/README.md
@@@ -1,1 -1,4 +1,8 @@@
++<<<<<<< HEAD
 +# This project is for demoing atomic commit in git
++=======
+ ## This project is for demoing git
+
+ You can visit endpoint getRandom to get a random real number.
+ The end endpoint is `/getRandom`.
++>>>>>>> bc0900d... [DO NOT PUSH] Add documentation for getRandom endpoint

> git diff --cached
* Unmerged path README.md

解决冲突的具体步骤是：

手动修改冲突文件；

使用 git add 或者 git rm 把修改添加到暂存区；

运行 git rebase --continue。于是，git rebase 会把暂存区的内容生成提交，并继续 git-rebase 后续步骤。

> vim README.md

## 这个是初始内容
<<<<<<< HEAD
# This project is for demoing atomic commit in git
=======
## This project is for demoing git

You can visit endpoint getRandom to get a random real number.
The end endpoint is `/getRandom`.
>>>>>>> bc0900d... [DO NOT PUSH] Add documentation for getRandom endpoint

## 这个是修改后内容，并保存退出
# This project is for demoing atomic commit in git

You can visit endpoint getRandom to get a random real number.
The end endpoint is `/getRandom`.

## 添加 README.md 到暂存区，并使用 git status 查看状态
> git add README.md
19:51:16 (master|REBASE-i) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo

## 使用 git status 查看状态
> git status
interactive rebase in progress; onto 29c8249
Last commands done (2 commands done):
   edit 1562cc7 readme
   pick bc0900d [DO NOT PUSH] Add documentation for getRandom endpoint
No commands remaining.
You are currently rebasing branch 'master' on '29c8249'.
  (all conflicts fixed: run "git rebase --continue")

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

  modified:   README.md

## 冲突成功解决，继续 git rebase -i 后续步骤
> git rebase --continue

## git rebase 提示编辑 B2''''的 Commit Message
[DO NOT PUSH] Add documentation for getRandom endpoint

Summary:
AT.

Test:
None.

## 保存退出之后 git rebase --continue 的输出
[detached HEAD ae38d9e] [DO NOT PUSH] Add documentation for getRandom endpoint
 1 file changed, 3 insertions(+)
Successfully rebased and updated refs/heads/master.

## 检查提交链
* ae38d9e (HEAD -> master) [DO NOT PUSH] Add documentation for getRandom endpoint
* 2c66fe9 Add README.md file
* 29c8249 Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp

这时，提交链上有 B1’’’’、A1’’’、B2’’’’ 三个提交。

图 12 提交链状态第 12 步

阶段 6：B1 检查通过，推送到远程代码仓共享分支

这时，我从 Phabricator 得到通知，B1’’’’ 检查通过了，可以将其推送到 oringin/master 去了！

首先，使用 git fetch 和 git rebase origin/master 命令，确保本地有远端主代码仓的最新代码。

> git fetch
> git rebase origin/master
Current branch master is up to date.

然后，使用 git rebase -i，在 B1’’’’ 处暂停：

> git rebase -i origin/master

## 修改第一行开头：pick -> edit
edit 29c8249 Add getRandom endpoint
pick 2c66fe9 Add README.md file
pick ae38d9e [DO NOT PUSH] Add documentation for getRandom endpoint

## 保存退出结果
Stopped at 29c8249...  Add getRandom endpoint
You can amend the commit now, with
  git commit --amend
Once you are satisfied with your changes, run
  git rebase --continue

## 查看提交链
* 29c8249 (HEAD) Add getRandom endpoint
* 7b6ea30 (origin/master) Add a new endpoint to return timestamp
...

这时，origin/master 和 HEAD 之间只有 B1’’’’ 一个提交。

图 13 提交链状态第 13 步

我终于可以运行 git push origin HEAD:master，去推送 B1’’’’ 了。

注意，当前 HEAD 不在任何分支上，master 分支仍然指向 B2’’’’，所以 push 命令需要明确指向远端代码仓 origin 和远端分支 maser，以及本地要推送的分支 HEAD。推送完成之后，再运行 git rebase --continue 完成 rebase 操作，把 master 分支重新指向 B2’’’’。

## 直接推送。因为当前 HEAD 不在任何分支上，推送失败。
> git push
fatal: You are not currently on a branch.
To push the history leading to the current (detached HEAD)
state now, use
    git push origin HEAD:<name-of-remote-branch>

## 再次推送，指定远端代码仓 origin 和远端分支 maser，以及本地要推送的分支 HEAD。推送成功
> git push origin HEAD:master
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 392 bytes | 392.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:jungejason/git-atomic-demo.git
   7b6ea30..29c8249  HEAD -> master

> git rebase --continue
Successfully rebased and updated refs/heads/master.

## 查看提交链
> git log --oneline --graph
* ae38d9e (HEAD -> master) [DO NOT PUSH] Add documentation for getRandom endpoint
* 2c66fe9 Add README.md file
* 29c8249 (origin/master) Add getRandom endpoint
* 7b6ea30 Add a new endpoint to return timestamp

这时，origin/master 已经指向了 B1’’’’，提交链现在只剩下了 A1’’’ 和 B2’’’’。

图 14 提交链状态第 14 步

至此，我们完成了在一个分支上同时开发两个需求 A 和 B、把提交拆分为原子性提交，并尽早把完成的提交推送到远端代码仓共享分支的全过程！

这个过程看起来比较复杂，但实际上就是根据上面列举的“单分支工作流”的 4 个步骤执行而已。

接下来，我们再看看使用多个分支，每个分支支持一个需求的开发方式。

用本地多分支实现多个需求的提交的原子性

在这种开发工作流下，每个需求都拥有独享的分支。同样的，跟单分支实现提交原子性的方式一样，这些分支都是本地分支，并不是主代码仓上的功能分支。

需要注意的是，在下面的分析中，我只描述每个分支上只有一个提交的简单形式，而至于每个分支上使用多个提交的形式，操作流程与单分支提交链中的描述一样，就不再重复表述了。

多分支工作流具体步骤

分支工作流的具体步骤，大致包括以下 4 步：

切换到某一个分支对某需求进行开发，产生提交。

提交完成后，将其发送到代码审查系统 Phabricator 上进行质量检查。在等待质量检查结果的同时，切换到其他分支，继续其他需求的开发。

如果第 2 步的提交没有通过质量检查，则切换回这个提交所在分支，对提交进行修改，修改之后返回第 2 步。

如果第 2 步的提交通过了质量检查，则切换回这个提交所在分支，把这个提交推送到远端代码仓中，然回到第 1 步进行其他需求的开发。

接下来，我们看一个开发两个需求 C 和 D 的场景吧。

在这个场景中，我首先开发需求 C，并把它的提交 C1 发送到质量检查中心；然后开始开发需求 D，等到 C1 通过质量检查之后，我立即将其推送到远程共享代码仓中去。

阶段 1：开发需求 C

需求 C 是一个简单的重构，把 index.js 中所有的 var 都改成 const。

首先，使用 git checkout -b feature-c origin/master 产生本地分支 feature-c，并跟踪 origin/master。

> git checkout -b feature-c origin/master
Branch 'feature-c' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'feature-c'

然后，进行 C 的开发，产生提交 C1，并把提交发送到 Phabricator 进行检查。

## 修改代码，产生提交
> vim index.js
> git diff
diff --git a/index.js b/index.js
index cc92a42..e5908f0 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
-var port = 3000
-var express = require('express')
-var app = express()
+const port = 3000
+const express = require('express')
+const app = express()

 app.get('/timestamp', function (req, res) {
   res.send('' + Date.now())
20:54:10 (feature-c) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo

> git add .
20:54:16 (feature-c) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo

> git commit

## 填写详细 Commit Message
Refactor to use const instead of var

Summary: const provides more info about a variable. Use it when possible.

Test: ran `node index.js` and verifeid it by visiting localhost:3000.
Endpoints still work.

## 以下是 Commit Message 保存后退出，git commit 的输出结果
[feature-c 2122faa] Refactor to use const instead of var
 1 file changed, 3 insertions(+), 3 deletions(-)

## 使用 Phabricator 的客户端，arc，把当前提交发送给 Phabricator 进行检查
> arc diff

## 查看提交链
* 2122faa (HEAD -> feature-c, multi-branch-step-1) Refactor to use const instead of var
* 5055c14 (origin/master) Add documentation for getRandom endpoint
...

这时，origin/master 之上只有 feature-c 一个分支，上面有 C1 一个提交。

图 15 多分支提交状态第 1 步

阶段 2：开发需求 D

C1 发出去进行质量检查后，我开始开发需求 D。需求 D 是在 README.md 中，添加所有 endpoint 的文档。

首先，也是使用 git checkout -b feature-d origin/master 产生一个分支 feature-d 并跟踪 origin/master。

> git checkout -b feature-d origin/master
Branch 'feature-d' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'feature-d'
Your branch is up to date with 'origin/master'.

然后，开始开发 D，产生提交 D1，并把提交发送到 Phabricator 进行检查。

## 进行修改
> vim README.md

## 添加，产生修改，过程中有输入 Commit Message
> git add README.md
> git commit

## 查看修改
> git show
commit 97047a33071420dce3b95b89f6d516e5c5b59ec9 (HEAD -> feature-d, multi-branch-step-2)
Author: Jason Ge <gejun_1978@yahoo.com>
Date:   Tue Oct 15 21:12:54 2019

    Add spec for all endpoints

    Summary: We are missing the spec for the endpoints. Adding them.

    Test: none

diff --git a/README.md b/README.md
index 983cb1e..cbefdc3 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,8 @@
 # This project is for demoing atomic commit in git

-You can visit endpoint getRandom to get a random real number.
-The end endpoint is `/getRandom`.
+## endpoints
+
+* /getRandom: get a random real number.
+* /timestamp: get the current timestamp.
+* /: get a "hello world" message.

## 将提交发送到 Phabricator 进行审查
> arc diff

## 查看提交历史
> git log --oneline --graph feature-c feature-d
* 97047a3 (HEAD -> feature-d Add spec for all endpoints
| * 2122faa (feature-c) Refactor to use const instead of var
|/
* 5055c14 (origin/master) Add documentation for getRandom endpoint

这时，origin/master 之上有 feature-c 和 feature-d 两个分支，分别有 C1 和 D1 两个提交。

图 16 多分支提交状态第 2 步

阶段 3：推送提交 C1 到远端代码仓共享分支

这时，我收到 Phabricator 的通知，C1 通过了检查，可以推送了！首先，我使用 git checkout 把分支切换回分支 feature-c：

> git checkout feature-c
Switched to branch 'feature-c'
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

然后，运行 git fetch; git rebase origin/master，确保我的分支上有最新的远程共享分支代码：

> git fetch
> git rebase origin/master
Current branch feature-c is up to date.

接下来，运行 git push 推送 C1：

> git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 460 bytes | 460.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com:jungejason/git-atomic-demo.git
   5055c14..2122faa  feature-c -> master

## 查看提交状态
* 97047a3 (feature-d) Add spec for all endpoints
| * 2122faa (HEAD -> feature-c, origin/master, multi-branch-step-1) Refactor to use const instead of var
|/
* 5055c14 Add documentation for getRandom endpoint
...

这时，origin/master 指向 C1。分支 feature-d 从 origin/master 的父提交上分叉，上面只有 D1 一个提交。

图 17 多分支提交状态第 3 步

阶段 4：继续开发 D1

完成 C1 的推送后，我继续开发 D1。首先，用 git checkout 命令切换回分支 feature-d；然后，运行 git fetch 和 git rebase，确保当前代码 D1 是包含了远程代码仓最新的代码，以减少将来合并代码产生冲突的可能性。

> git checkout feature-d
Switched to branch 'feature-d'
Your branch and 'origin/master' have diverged,
and have 1 and 1 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)
21:38:22 (feature-d) jasonge@Juns-MacBook-Pro-2.local:~/jksj-repo/git-atomic-demo

> git fetch
> git rebase origin/master
First, rewinding head to replay your work on top of it...
Applying: Add spec for all endpoints

## 查看提交状态
> git log --oneline --graph feature-c feature-d
* a8f92f5 (HEAD -> feature-d) Add spec for all endpoints
* 2122faa (origin/master,) Refactor to use const instead of var
...

这时，当前分支为 feature-d，上面有唯一一个提交 D1’，而且 D1’已经变基到了 origin/master 上。

需要注意的是，因为使用的是 git rebase，没有使用 git merge 产生和并提交，所以提交历史是线性的。我在第 7 篇文章中提到过，线性的提交历史对 Facebook 的 CI 自动化意义重大。

图 18 多分支提交状态第 4 步

至此，我们完成了在两个分支上同时开发 C 和 D 两个需求，并尽早把完成了的提交推送到远端代码仓中的全过程。

虽然在这个例子中，我简化了这两个需求开发的情况，每个需求只有一个提交并且一次就通过了质量检查，但结合在一个分支上完成所有开发需求的流程，相信你也可以推导出每个需求有多个提交，以及质量检查没有通过时的处理方法了。如果这中间还有什么问题的话，那就直接留言给我吧。

接下来，我与你对比下这两种工作流。

两种工作流的对比

如果我们要对比这两工作流的话，那就是各有利弊。

单分支开发方式的好处是，不需要切换分支，可以顺手解决一些缺陷修复，但缺点是 rebase 操作多，产生冲突的可能性大。

而多分支方式的好处是，一个分支只对应一个需求，相对比较简单、清晰，rebase 操作较少，产生冲突的可能性小，但缺点是不如单分支开发方式灵活。

无论是采用哪一种工作流，都有几个需要注意的地方：

不要同时开发太多的需求，否则分支管理花销太大；

有了可以推送的提交就尽快推送到远端代码仓，从而减少在本地的管理成本，以及推送时产生冲突的可能性；

经常使用 git fetch 和 git rebase，确保自己的代码在本地持续与远程共享分支的代码在做集成，降低推送时冲突的可能性

最后，我想说的是，如果你对 Git 不是特别熟悉，我推荐你先尝试第二种工作流。这种情况 rebase 操作较少，相对容易上手一些。

小结

今天，我与你详细讲述了，在 Facebook 开发人员借助 Git 的强大功能，实现代码提交的原子性的两种工作流。

第一种工作流是，在一个单独的分支上进行多个需求的开发。总结来讲，具体的工作方法是：把每一个需求的提交都拆分为比较小的原子提交，并使用 git rebase -i 的功能，把可以进行质量检查的提交，放到提交链的最底部，也就是最接近 origin/master 的地方，然后发送到代码检查系统进行检查，之后继续在提交链的其他提交处工作。如果提交没有通过检查，就对它进行修改再提交检查；如果检查通过，就马上把它推送到远端代码仓的共享分支去。在等待代码检察时，继续在提交链的其他提交处工作。

第二种工作流是，使用多个分支来开发多个需求，每个分支对应一个需求。与单分支开发流程类似，我们尽快把当前可以进行代码检查的提交，放到离 origin/master 最近的地方；然后在代码审查时，继续开发其他提交。与单分支开发流程不同的是，切换工作任务时，需要切换分支。

这两种工作流，无论哪一种都能大大促进代码提交的原子性，从而同时提高个人及团队的研发效能。

我把今天的案例放到了 GitHub 上的git-atomic-demo代码仓里，并标注出了各个提交状态产生的分支。比如，single-branch-step-14 就是单分支流程中的第 14 个状态，multi-branch-step-4 就是多分支流程中的第 4 个状态。

思考题

在对提交链的非当前提交，比如 HEAD^，进行修改时，除了使用 git rebase -i，在它暂停的时候进行修改，你还其他办法吗？你觉得这种方法的利弊是什么？

Git 和文字编排系统 LaTex 有一个有趣的共同点，你知道是什么吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

27 | 命令行：不只是酷，更重要的是能提高个人效能

你好，我是葛俊。今天，我要与你分享的主题是，命令行下的高效工作技巧。

我先和你讲一个有意思的话题吧。命令行工具常常会给人一种黑客的感觉，好莱坞的电影里面常常出现命令行窗口的使用。不知道你听说过没有，很多好莱坞电影在拍摄时使用的其实是一个叫作nmap的工具。这个工具是做安全扫描的，只不过因为它的显示特别花哨，所以被很多电影采用。在 nmap 官方网站上，还专门列出来了这些电影的名单。

类似这种可以让自己看起来很忙的工具还有很多，比如Genact。下面是一个使用 Genact 的录屏，当然这里的命令并没有真正运行。这可能是整个专栏中，唯一一个让你看起来效率很高，实际上却是降低效率的工具，但说不定对你有用，你懂的。

讲完这个娱乐性的话题，我们进入正题吧。

为什么要使用命令行？

GUI 图形界面的出现是计算机技术的变革，极大方便了用户。但在这么多年后的今天，命令行工具为什么仍然有如此强大的生命力呢？

在我看来，对软件工程师来说，想要高效开发就必须掌握命令行，主要原因包括：

虽然鼠标的移动和点击比较直观，但要完成重复性的工作，使用键盘会快捷得多。这一点从超市的结算人员就可以看出来，使用键盘系统的收银员总是噼里啪啦地很快就可以完成结算，而使用鼠标点击的话明显慢很多。

作为开发人员，可以比较容易地使用命令行的脚本，对自己的工作进行自动化，以及和其他系统工具联动。但使用 GUI 的话，就会困难得多。

命令行通常可以暴露更完整的功能，让使用者对整个系统有更透彻的理解。Git 就是一个典型的例子，再好的 GUI Git 系统都只能封装一部分 Git 命令行功能，要想真正了解 Git，我们必须要使用命令行。

有一些情况是必须使用命令行的，比如 SSH 到远程服务器上工作的时候。

为了演示命令行的强大功能给我们带来的方便，下面是一个在本地查看文件并上传到服务器的流程的录屏。

通过这个案例，你可以看到命令行的以下几个功能：

在提示行会高亮显示时间、机器名、用户名、Git 代码仓的分支和状态，以及上一个命令的完成状态。

输入命令的时候，高亮显示错误并自动纠错。

使用交互的形式进行文件夹的跳转，并方便查找文件，还可以直接在命令行里显示图片。

使用交互的工具，把文件上传到远端的服务器，并快速连接到远端查看传输是否成功。

整个流程全部都是在命令行里完成的，速度非常快，用户体验也非常好。正因为如此，我看到的硅谷特别高效的开发人员，绝大多数都大量使用命令行。那，面对成百上千的命令行工具，我们怎样才能高效地学习和使用呢？

我将高效学习使用命令行的过程，归纳为两大步：

配置好环境；

针对自己最常使用命令行的场景，有的放矢地选择工具。

今天，我就与你详细讲述环境配置这个话题。而关于选择工具的话题，我会在下一篇文章中与你详细介绍。总结来讲，环境配置主要包括以下四步：

选择模拟终端；

选择 Shell；

具体的 Shell 配置；

远程 SSH 的处理。

这里需要注意的是，在命令行方面，macOS 和 Linux 系统比 Windows 系统要强大许多，所以我主要以 macOS 和 Linux 系统来介绍，而关于 Windows 的环境配置，我只会捎带提一下。不过，macOS 和 Linux 系统中的工具选择和配置思路，你可以借鉴到 Windows 系统中。

第一步，选择模拟终端

我将一个好的终端应该具有的特征，归纳为 4 个：

快，稳定；

支持多终端，比如可以方便地水平和纵向分屏，有 tab 等；

方便配置字体颜色等；

方便管理常用 SSH 的登录。

macOS 系统自带的终端不太好用，常见的替代工具有 iTerm2、Terminator、Hyper 和 Upterm。我平时使用的 iTerm2，是一个免费软件，功能强大，具备上面提到的 4 个特征。下面我就以 iTerm2 为例展开介绍。其他几个工具上也有类似功能，所以你不必担心。

在多终端的场景方面，iTerm2 支持多窗口、多标签页，同一窗口中可以进行多次水平和纵向分屏。这些操作以及窗口的跳转都有快捷键支持，你可以很方便地在网络上搜索到。

在管理常用 SSH 的登录方面，iTerm2 使用 Profile（用户画像）来控制。比如，下面是一个连接到远程服务器案例的录屏。

可以看到，在我的工作环境里常会用到 4 个 Profile，其中有两个是连接到远端服务器的，包括 Mosh Remote Server 1 和 SSH Remote Server 2。工作时，我使用 Cmd+O，然后选择 Server 1 这个 Profile，就可以打开一个新窗口，连接到这个远程服务器上。

每一个 Profile 都可以定义自己的字体、颜色、shell 命令等。比如，Server 1 是类生产服务器，我就把背景设置成了棕红色，提醒自己在这个机器上工作时一定要小心。所以在上面的录屏中你可以看到，连接到远端的 SSH 标签页，它的背景、标签页都是棕红色。另外，下面是如何对 Profile 颜色进行设置的截屏。

除了这些基础功能外，iTerm2 还有很多贴心的设计。比如：

在屏幕中显示运行历史（Cmd+Opt+B/F）。有些情况下，向上滚动终端并不能看到之前的历史，比如运行 VIM 或者 Tmux 的时候。这时，浏览显示历史就特别有用了。

高亮显示当前编辑位置，包括高亮显示当前行（Cmd+Opt+；）高亮显示光标所在位置（Cmd+/）。

与上一次运行命令相关的操作，包括显示上一次运行命令的地方（Cmd+Shift+up），选中上一个命令的输出（Cmd+Shift+A）。

其中第 2、3 项功能是由一组macOS 的集成工具提供的。这个工具集还包括显示图片的命令 imgls、imgcat，显示自动补全命令，显示时间、注释，以及在主窗口旁显示额外信息等。这些设计虽然很小，但非常实用。

关于 Windows 系统，2019 年 5 月微软推出了Windows Terminal，支持多 Tab，定制性很强，据说体验很不错。

选择好了终端，环境设置的第二步就是选择 Shell。

第二步，选择 Shell

在我看来，选择 Shell 主要有普遍性和易用性这两条原则。

Linux/Unix 系统下，Bash最普遍、用户群最广，但是易用性不是很好。常用来替代 Bash 的工具有Zsh和Fish，它们的易用性都很好。下面是两张图片，用于展示 Zsh 和 Fish 在易用性方面的一些功能。

Zsh：

Fish：

我个人觉得 Fish 比 Zsh 更方便。事实上，Fish 是 Friendly Interactive Shell 的简称。所以，交互是 Fish 的强项。可惜的是，Fish 不严格遵循 POSIX 的语法，与 Bash 的脚本不兼容，而 Zsh 则兼容，所以我目前主要使用的是 Zsh。

选好了模拟终端和 Shell 之后，便是配置环境的第三步，具体的 Shell 配置了。

第三步，具体的 Shell 配置

接下来，我以我自己使用的设置为例，向你介绍 Bash、Zsh、Fish 的具体配置吧。这里，主要包括命令行提示符的配置和其他配置两个方面。

之所以把命令行提示符单独提出来，是因为它一直展现在界面上，能提供很有用的价值，对命令行高效工作至关重要。下面是一张图片，展示了 Bash、Zsh 和 Fish 的命令行提示符。

这个窗口分为三部分，最上面是 Bash，中间是 Zsh，最下面是 Fish，都配置了文件路径、Git 信息和时间戳等信息。接下来，我带你一起看看这 3 个工具应该如何配置吧。

Bash 比较麻烦，配置文件包括定义颜色和命令行提示符的两部分：

## 文件 $HOME/.bash/term_colors，定义颜色
# Basic aliases for bash terminal colors
N="\[\033[0m\]"    # unsets color to term's fg color

# regular colors
K="\[\033[0;30m\]"    # black
R="\[\033[0;31m\]"    # red
G="\[\033[0;32m\]"    # green
Y="\[\033[0;33m\]"    # yellow
B="\[\033[0;34m\]"    # blue
M="\[\033[0;35m\]"    # magenta
C="\[\033[0;36m\]"    # cyan
W="\[\033[0;37m\]"    # white

# empahsized (bolded) colors
MK="\[\033[1;30m\]"
MR="\[\033[1;31m\]"
MG="\[\033[1;32m\]"
MY="\[\033[1;33m\]"
MB="\[\033[1;34m\]"
MM="\[\033[1;35m\]"
MC="\[\033[1;36m\]"
MW="\[\033[1;37m\]"

# background colors
BGK="\[\033[40m\]"
BGR="\[\033[41m\]"
BGG="\[\033[42m\]"
BGY="\[\033[43m\]"
BGB="\[\033[44m\]"
BGM="\[\033[45m\]"
BGC="\[\033[46m\]"
BGW="\[\033[47m\]"

## 文件 $HOME/.bashrc，设置提示符及其解释
###### PROMPT ######
# Set up the prompt colors
source $HOME/.bash/term_colors
PROMPT_COLOR=$G
if [ ${UID} -eq 0 ]; then
  PROMPT_COLOR=$R ### root is a red color prompt
fi

#t Some good thing about this prompt:
# (1) The time shows when each command was executed, when I get back to my terminal
# (2) Git information really important for git users
# (3) Prompt color is red if I'm root
# (4) The last part of the prompt can copy/paste directly into an SCP command
# (5) Color highlight out the current directory because it's important
# (6) The export PS1 is simple to understand!
# (7) If the prev command error codes, the prompt '>' turns red
export PS1="\e[42m\t\e[m$N $W"'$(__git_ps1 "(%s) ")'"$N$PROMPT_COLOR\u@\H$N:$C\w$N\n\["'$CURSOR_COLOR'"\]>$W "
export PROMPT_COMMAND='if [ $? -ne 0 ]; then CURSOR_COLOR=`echo -e "\033[0;31m"`; else CURSOR_COLOR=""; fi;'

命令行提示符之外的其他方面的配置，在 Bash 方面，我主要设置了一些命令行补全（completion）和别名设置（alias）：

## git alias
alias g=git
alias gro='git r origin/master'
alias grio='git r -i origin/master'
alias gric='git r --continue'
alias gria='git r --abort'

## ls aliases
alias ls='ls -G'
alias la='ls -la'
alias ll='ls -l'

## git completion，请参考 https://github.com/git/git/blob/master/contrib/completion/git-completion.bash
source ~/.git-completion.bash

Zsh 的配置就容易得多了，而且是模块化的。基本上就是安装一个配置的框架，然后选择插件和主题即可。具体来说，我的 Zsh 命令行提示符配置步骤包括以下三步。

第一，安装 oh-my-zsh。这是一个对 Zsh 进行配置的常用开源框架。

brew install zsh

第二，安装 powerline 字体，供下一步使用。

brew install powerlevel9k

第三，在~/.zshrc 中配置 ZSH_THEME，指定使用 powerlevel9k 这个主题。

ZSH_THEME="powerlevel9k/powerlevel9k"

命令行提示符以外的其他配置，主要是通过安装和使用 oh-my-zsh 插件的方式来完成。下面是我使用的各种插件，供你参考。

## 文件~/.zshrc.sh 中关 oh-my-zsh 的插件列表，具体插件细节请参考 https://github.com/robbyrussell/oh-my-zsh，以及使用 Web 搜索查询
plugins=(
  git
  z
  vi-mode
  zsh-syntax-highlighting
  zsh-autosuggestions
  osx
  colored-man-pages
  catimg
  web-search
  vscode
  docker
  docker-compose
  copydir
  copyfile
  npm
  yarn
  extract
  fzf-z
)

source $ZSH/oh-my-zsh.sh

至于 Fish 的配置，和 Zsh 差不多，也是安装一个配置的框架，然后选择插件和主题即可。在配置命令行提示符时，主要步骤包括以下两步。

第一，安装配置管理框架 oh-my-fish：

curl -L https://get.oh-my.fish | fish

第二，查看、安装、使用 oh-my-fish 的某个主题，主题会自动配置好命令行提示符：

omf theme
omf install <theme>
omf theme <theme>

## 我使用的是 bobthefish 主题
omf theme bobthefish

这里有一篇不错的关于使用 oh-my-fish 配置的文章，供你参考。

Fish 的其他方面的配置，也是使用 oh-my-fish 配置会比较方便。关于具体的配置方法，建议你参照官方文档。

关于环境的最后一个配置，是远程 SSH 的处理。

第四步，远程 SSH 的处理

SSH 到其他机器，是开发人员的常见操作，最大的痛点是，怎样保持多次连接的持久性。也就是说，连接断开以后，远端的 SSH 进程被杀死，之前的工作记录、状态丢失，导致下一次连接进去需要重新设置，交易花销太大。有两类工具可以很好地解决这个问题。

第一类工具是 Tmux 或者 Screen，这两个工具比较常见，用来管理一组窗口。

接下来，我以 Tmux 为例，与你描述其工作流程：首先 SSH 到远程服务器，然后用远程机器上的 Tmux Client 连接到已经运行的 Tmux Session 上。SSH 断开之后，Tmux Client 被杀死，但 Tmux Session 仍然保持运行，意味着命令的运行状态继续存在，下次 SSH 过去再使用 Tmux Client 连接即可。

如果你想深入了解 Tmux 的概念和搭建过程，可以参考这篇文章。

第二类是一个保持连接不中断的工具，移动 Shell（Mobile Shell）。这也是我目前唯一见到的一个。这个工具是 MIT 做出来的，知道的人不多，是针对移动设备的网络经常断开设计的。

它的具体原理是，每次初始登录使用 SSH，之后就不再使用 SSH 了，而是使用一个基于 UDP 的 SSP 协议，能够在网络断开重连的时候自动重新连接，所以从使用者的角度来看就像从来没有断开过一样。

接下来，我以阿里云 ECS 主机、运行 Ubuntu18.04 为例，与你分享 Mosh+Tumx 的具体安装和设置方法。

第一，服务器端安装并运行 Mosh Server。

junge@iZ4i3zrhuhpdbhZ:~$ sudo apt-get install mosh

第二，打开服务器上的 UDP 端口 60000~61000。

junge@iZ4i3zrhuhpdbhZ:~$ sudo ufw allow 60000:61000/udp

第三，在阿里云的 Web 界面上修改主机的安全组设置，允许 UDP 端口 60000~61000。

第四，在客户端（比如 Mac 上），安装 Mosh Client。

jasonge@Juns-MacBook-Pro-2@l$ brew isntall mosh

第五，客户端使用 Mosh，用与 SSH 一样的命令行连接到服务器。

jasonge@Juns-MacBook-Pro-2@l$ mosh junge@<server-ip-or-name>

下面这个录屏演示的是，我日常工作中使用 Mosh + Tmux 的流程。期间我会断开无线网，你可以看到 Mosh 自动连接上了，就好像来没有断过一样。

小结

今天，我与你分享的是使用命令行工具工作时，涉及的环境配置问题。

首先，我与你介绍了选择模拟终端、选择和配置 Shell 的重要准则，并结合案例给出了具体的工具和配置方法，其中涉及的工具包括 iTerm2、Bash、Zsh、Fish 等。然后，我结合远程 SSH 这一常见工作场景，给出了使用 Tmux 和 Mosh 的优化建议。

掌握了关于环境配置的这些内容以后，在下一篇文章中，我将与你介绍具体命令行工具的选择和使用。

其实，我推荐开发者多使用命令行工具，并不是因为它们看起来炫酷，而是它们确实可以帮助我们节省时间、提高个人的研发效能。而高效使用命令行工具的前提，是配置好环境。

以 Mosh 为例，我最近经常会使用 iPad SSH 到远端服务器做一些开发工作。在这种移动开发的场景下，iPad 的网络经常断开，每次重新连接开销太大，基本上没办法工作。于是，我最终发现了 Mosh，并针对开发场景进行了设置。现在，每次我重新打开 iPad 的终端时，远程连接自动恢复，好像网络从没有断开过一样。这样一来，我就可以在移动端高效地开发了。

而对研发团队来说，如果能够对命令行工作环境进行优化和统一，毫无疑问会节省个人选择和配置工具的时间，进而提升团队的研发效能。

思考题

你觉得 Tmux 和 Screen 的最大区别是什么？是否有什么场景，我们必须使用其中的一个吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

28 | 从工作场景出发，寻找炫酷且有效的命令行工具

你好，我是葛俊。今天，我继续和你分享命令行工具的使用。

在上一篇文章中，我与你介绍了命令行环境中的终端、Shell，以及远程连接的设置，解决了环境配置的问题。今天，我们再来看看具体的命令行工具的使用技巧。我会根据常见的工作场景来组织这些工具，因为优化工作流程、提高效率才是学习工具的真正目的。

从我的经验来看，开发人员最常见的、使用命令行的场景主要包括两个：

日常的操作，比如文件夹跳转、处理和搜索文件夹和文件内容、查看和管理系统信息；

开发中常见的工作，比如 Git 的使用、API 调试、查看日志和网络状况等。

我会重点与你分享这些场景中有哪些推荐工具以及使用技巧。而至于这些工具如何安装的内容，网络上已经有很多了，我就不再详细描述了。

日常操作中的工具和技巧

关于日常操作，Linux/Unix 系统已经自带了一些工具，另外还有些产生已久、为我们所熟悉的工具。不过，要更高效地完成工作，我们还有更强大的工具可以选择。

第一个场景：列举文件夹和文件，查看文件

列举文件的默认工具是 ls。除此之外，一个常用的工具是 tree，可以列出文件夹的树形结构：

另外，还有些比 tree 更方便的工具，比如 alder 和 exa。exa 尤其好用，优点包括：

默认就有漂亮的颜色显示，并且不同种类文件颜色不同；

可以像 ls 一样显示当前文件夹，也可以像 tree 一样显示树形结构。

另外，exa 还支持对文件状态显示的加强。

比如，添加–git 选项，exa 会显示文件的 git 状态，在文件名的左边，用两个字母来表示文件在工作区和 Git 暂存区的状态。其中，N 表示新文件，M 表示文件修改等。exa 的显示和 git status 命令输出的简单对比，如下图所示。

再比如，使用–extend 选项显示文件的额外信息。

再比如，使用 group-directories-first 选项，先显示文件夹再显示文件。

至于查看文件，Linux 默认的工具是 cat。相比起来，bat 是一个更好用的替代品，除高亮显示外，还可以显示 Git 的更改状态。

第二个场景：查找并打开文件，进行查看和编辑

一个常用的办法是，使用上面提到的工具来列出文件名，然后使用 grep 进行过滤查看，或者 VIM 进行查看和编辑。比如，使用命令

tree -I "node_modules" -f | grep -C3 index.md

可以得到当前文件夹中的所有 index.md 文件。

命令中，tree 的参数 -I，表示排除文件夹 node_modules；tree 的参数 -f，表示显示文件时包含文件路径，方便你拷贝文件的全名；grep 的参数 -C3，代表显示搜索结果 3 行的上下文。

我们也可以使用 VIM 代替 grep，进行更复杂的查找和编辑工作。我可以把 tree 的输出传给 VIM，然后在 VIM 中查找 index.md，使用 n 跳到下一个搜索结果，使用 VIM 命令 gF 直接打开文件，进行编辑后使用\bd 命令关闭这个 index.md 文件。然后，用同样的方式查找并编辑第二、三、四个 index.md，从而实现对当前文件夹下每一个 index.md 文件的查看和修改：

事实上，这正是一个很常见的命令行工作流：把某一个命令的输出传给 VIM，输出里包含有其他文件的完整路径，比如上面例子中 index.md 的路径，然后在 VIM 里使用 gF 命令查看并处理这些文件。我推荐你也尝试使用这种工作流。

另外，在上面的例子中，我使用 tree 命令来列举文件名。其实，很多时候我们使用 find 这种专门用来查找文件的命令会更加方便。不过，我今天要介绍的不是 find，而是它的一个替代品，即 fd。

我推荐 fd 的原因主要有 3 个：

语法比 find 简单；

fd 默认会忽略.gitignore 文件里指定的文件；

忽略隐藏文件。

后两点对开发者来说非常方便。比如，我在搜索时，并不关心 node_modules 里面的文件，也不关心.git 文件夹里的文件，fd 可以自动帮我过滤掉。

另外，fd 高亮显示，速度也很快。至于对查找到的文件进行编辑，跟上面提到的方法一样，用管道（Pipe）传给 VIM，然后使用 gF 命令即可。

fd index.md | vim -

另外，关于查找文件内容的工具 grep，我常用的一个替代品是 RipGrep（rg）。跟 fd 类似，它也很适合开发者，有如下 4 个特点：

默认忽略.gitignore 文件里指定的文件；

默认忽略隐藏文件；

默认递归搜索所有子目录；

可以指定文件类型。

比如，使用 rg tags 就可以方便地查找当前目录下所有包含 tags 的文件。它的查找速度非常快，显示也比 grep 要漂亮：

> rg tags
package-lock.json
2467:    "common-tags": {
2469:      "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
5306:        "common-tags": "^1.4.0",
5446:            "common-tags": "^1.4.0",

src/pages/2019-05-24-procrastination/index.md
6:tags: ['自我成长', '拖延症']

src/pages/2018-07-21-first-post/index.md
5:tags: ['this', 'that']

第三个场景：文件夹之间跳转

关于文件夹间的跳转，在 Bash 中有 cd 和 dirs 命令；在 Zsh 和 Fish 中，可以使用文件夹名字直接跳转；另外，Zsh 支持…、…和 - 等别名，用来分别跳转到父目录、父目录的父目录，以及目录历史中上一次记录，而不需要写 cd。

接下来，我与你介绍几个新的工具来支持更快的跳转。

实际上文件夹的跳转，有两种常见的情况：

一种是，快速跳转到文件夹跳转历史中的某条记录，即之前曾经去过的某个文件夹；

另一种是，快速找到当前文件夹中的某个子文件夹，并跳转过去。

对于第一种情况，常用的工具有两个，一个是 fasd，另一个是 z，它们差别不是特别大。我用的是 z，具体用法是：z 会按照访问频率列出最近访问过的文件夹，并使用字符串匹配的方式让你实现快速跳转。

比如，用 z dem来进行匹配和自动补全，找到我想去的 demo 文件夹，回车直接完成跳转。同时，我也可以用 z dem< 回车 > 直接跳转。

对于第二种情况，即快速定位某个子文件夹，我介绍一个超级酷的工具 fzf。本质上讲，fzf 是一个对输入进行交互的模糊查询工具。它的使用场景非常多，文件夹的跳转只是一个应用。所以，我还在再后面文章做更多的详细讨论。

安装好 fzf 之后，你就可以使用 Ctrl+T 进行文件夹的交互式查询，或者使用 Alt+C 进行文件夹跳转。

比如，我想跳转到 src/component 文件夹中，可以输入 Alt+C，fzf 就会列出当前文件夹下的所有文件夹。比如，我输入 com，没输入其他字符，fzf 会更新匹配到的文件夹，这时可以使用 Ctrl+P、Ctrl+N 进行上下选择，按下回车就可以进入选中的文件夹。

第四个场景：文件管理

系统自带的文件管理工具有 cp、mv、rsync 等。这里，我再介绍一些更方便的工具。

首先是一个用来重命名文件的小工具，叫作 vidir。顾名思义，vidir 就是 VI 来编辑目录的。具体使用方法很简单，vidir 命令后面接一个文件夹时，vidir 会打开 VIM，VIM 里面列举该文件夹中所包含的文件和子文件夹，然后使用 VIM 的命令来修改文件和文件夹的名字之后保存退出。这时，vidir 会自动帮助我们完成对文件和文件夹的重命名。

vidir 之所以使用 VIM 来修改文件，是因为 VIM 功能强大，修改非常方便。另外，vidir 也可以从管道接收文件夹和文件的列表。比如，我想把当前文件夹下所有前缀为 index 的文件，都在文件名前添加“new-”。这时，我可以使用命令 fd | vidir -。

这样，fd 命令会把当前文件夹下所有文件名传给 vidir。然后，vidir 打开 VIM，我们在 VIM 界面中修改文件名即可。如下所示的录屏图片中，包括了使用 VIM 的重复命令. 的技巧。

另外一组方便进行文件管理的工具是，命令行的文件管理器，即使用键盘命令在终端界面进行文件夹跳转、查看文件和移动文件等操作。这种命令行界面上的 UI 叫做 TUI（Terminal UI）。十多年前的 Borland 终端 IDE，就是这一类工具的翘楚，使用熟练之后效率会很高。

TUI 的文件管理器我用过 3 个：Midnight Commander (以下简称 mc)、Ranger 和 nnn。

mc 是两个窗口的文件管理器。如果你使用过 Windows Commander（Total Commander）的话，你就会对它的用法很熟悉。重要的命令有：使用 tab 进行两个窗口的切换、使用 F4 进行编辑、使用 F5 进行拷贝、使用 F9 进入菜单、使用 F10 退出。

我提供了一张录屏图片，简单演示了在一台远端服务器上使用 mc 进行多文件拷贝和编辑，并通过菜单修改显示主题的场景。

Ranger 和 nnn 是单窗口的文件管理器。Ranger 稍微有一点延迟，所以我一般使用 nnn。因为是单窗口，所以与我们平时在 GUI 中使用的文件管理器比较相似。

比如，在拷贝文件的时候，需要先进入文件所在文件夹，选择文件，然后进入目标文件夹，再使用拷贝命令把文件拷贝过去。我在录屏中演示了在 nnn 中进行文件夹的跳转、创建，文件的选择、拷贝，使用用系统工具打开当前文件，查看帮助等功能。

总的来说，这 3 个工具中我使用最多的是 nnn。跟 mc 相比，它最大的好处是快捷键设置跟 VIM 一致，不需要大量使用功能键 F1~F12。

开发中常见的工作

Git

命令行中的 Git 工具，除了原生的 Git 之外，常见的还有 tig、grv、lazygit 和 gitin。

我常用的是 tig。因为在 tig 中，我可以方便地进行查看改动、产生提交、查看历史（blame）等操作，功能非常强大。比如，在查看文件改动时，我们可以方便地使用命令 1 有选择性地把一个文件中改动的一部分添加到一个提交当中，实现第 26 篇文章中提到的 git add -p 的功能。

另外，我还可以通过 tig 快捷地查看一个文件的历史信息。

关于这两个功能的使用，你可以参考下面的录屏图片。

Web 访问

我常用的 Web 访问工具是 HTTPie，是 curl 命令的一个补充。HTTPie 的强项在于，专门针对 HTTP 协议，所以可以做到格式简单、易用性强两点。

而 curl 的优势，则包括功能强大、支持多种协议和基本所有服务器上都有预装。

关于这两个工具，我的建议是，curl 肯定要学，HTTPie 如果用得到也值得花时间学习。

对 JSON 进行处理

在命令行对 JSON 文本进行处理，最常见的工具是 jq。它能够对 JSON 进行查询和修改处理，功能很强大。

举一个查询的例子，我们有这样一个 person.json 文件列举某个人的详细信息：

$ cat person.json
{ "id": { "bioguide": "E000295", "thomas": "02283", "fec": [ "S4IA00129" ], "govtrack": 412667, "opensecrets": "N00035483", "lis": "S376" }, "name": { "first": "Joni", "last": "Ernst", "official_full": "Joni Ernst" }, "bio": { "gender": "F", "birthday": "1970-07-01" }, "terms": [ { "type": "sen", "start": "2015-01-06", "end": "2021-01-03", "state": "IA", "class": 2, "state_rank": "junior", "party": "Republican", "url": "http://www.ernst.senate.gov", "address": "825 B&C Hart Senate Office Building Washington DC 20510", "office": "825 B&c Hart Senate Office Building", "phone": "202-224-3254" } ] }

可以方便地使用 cat person.json | jq .”对 JSON 进行格式化输出，

$ cat people.json | jq .
{
  "id": {
    "bioguide": "E000295",
    "thomas": "02283",
    "fec": [
      "S4IA00129"
    ],
    "govtrack": 412667,
    "opensecrets": "N00035483",
    "lis": "S376"
  },
  "name": {
    "first": "Joni",
    "last": "Ernst",
    "official_full": "Joni Ernst"
  },
  "bio": {
    "gender": "F",
    "birthday": "1970-07-01"
  },
  "terms": [
    {
      "type": "sen",
      "start": "2015-01-06",
      "end": "2021-01-03",
      "state": "IA",
      "class": 2,
      "state_rank": "junior",
      "party": "Republican",
      "url": "http://www.ernst.senate.gov",
      "address": "825 B&C Hart Senate Office Building Washington DC 20510",
      "office": "825 B&c Hart Senate Office Building",
      "phone": "202-224-3254"
    }
  ]
}

以及使用 jq ".terms[0].office"命令查询他的第一个工作任期的办公室地址。

$ cat person.json | jq ".terms[0].office"
"825 B&c Hart Senate Office Building"

但，jq 存在的最大问题是，它有一套自己的查询处理语言。如果使用 jq 的频次没那么高的话，很难记住，每次都要去查帮助才可以。

针对这种情况，有人设计了另一种类似的工具，直接使用 JavaScript 作为查询处理语言，典型代表是 fx 和 jq.node。这，就大大方便了使用 JavaScript 的开发者，因为可以使用已经熟悉了的语法。

比如，对于上个案例的 JSON 文件，我可以方便地在 fx 工具中使用 JavaScript 的函数 filter() 进行过滤查询。

$ cat person-raw.json| fx 'json => json.terms.filter(x => x.type == "top")'
[
  {
    "type": "top",
    "office": "333 B&c Hart CIrcle  Building",
    "phone": "202-224-3254"
  }
]

查找、关闭进程

通常情况下，我们使用 kill 和 pkill，来查找和关闭进程。但，使用 fzf 之后，我们可以方便地进行交互式的查找目标进程。具体使用方法是，输入 kill，fzf 就会提供一个交互式的界面供你查找目标进程，然后回车确认即可。

在命令行上进行交互式的操作，非常爽，我推荐你一定要试试。

查看日志文件

关于查看日志文件的工具，我推荐 lnav。它比 tail -F 要方便、强大得多，有很多很棒的功能，包括：

支持很多日志格式，比如 syslog、sudo、uWSGI 等，并可以根据格式高亮显示；

支持多个日志同时显示，并用不同颜色区分；

支持正则表达式进行过滤等。

命令行本身的实用技巧

关于命令行的使用技巧，有两个非常值得一提：一个是!$，另一个是!!。

第一个!$，代表上一个命令行的最后一个参数。比如，如果我上一条命令使用

$ vim src/component/README.txt

下一步我想为它产生一个备份文件，就可以使用!$：

## 以下命令即   copy vim src/component/README.txt vim src/component/README.txt.bak
$ cp !$ !$.bak

第二个常用的是!!，表示上一个命令的完整命令。最常用的场景是，我先拷贝一个文件，发现没有权限，需要 sudo，下一步我就可以用 sudo !! 来用 sudo 再次运行拷贝命令。

## 因为权限不足，命令失败
$ cp newtool /usr/local/bin/

## 重新使用 sudo 运行上一条命令。即 sudo wtool /usr/local/bin/
$ sudo !!

小结

今天，我与你介绍了很多工具。使用工具提高研发效能，最关键的是找到真正常用的工作场景，然后去寻找对应的工具来提高效率。

需要注意的是，只有重复性高的工作，才最适合使用命令行工具；否则，用来适应工具的时间，可能比节省下的时间还要多。这，是命令行的一个基本特点。

最后，我把今天与你讨论的 Linux/Unix 系统自带工具和替代工具，整理为了一张表格，以方便你复习：

思考题

不知道你有没有注意到，在录屏中我多次用到了一个叫作 tldr 的工具。你知道它是什么作用吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

29 | 1+1>2，灵活的工具组合及环境让你的工作效率翻倍

你好，我是葛俊。今天，我们来聊一聊工具的组合和环境。

在前面几篇文章，我与你介绍了很多工具，有开发工具，也有跟开发不直接相关的工具。毫无疑问，这些工具都很好用。但，如何配置这些工具，才能真正发挥它们的价值，提高我们的研发效能呢？

我们都很清楚，工具的使用离不开具体的工作环境。如果我们在环境中使用的各个工具是割裂开的话，不仅会提高我们的学习成本、记忆成本，还会有工具间交互的衔接问题。所以，用好这些工具，我们还需要做两件事：

尽量把工具无缝集成，解决工具切换不顺畅的问题；

减少并优化常用的工作入口，从而提高工具一致性，降低使用多个工具时的心智负担。

只有这样，我们才能把工具配置成一套好的环境，真正聚焦在产生价值的工作上，发挥工具提升研发效能的作用，实现 1+1>2 的效果。

所以接下来，我会从工具集成和提高工具一致性两个方面，与你介绍如何把多个工具组合成为高效的工具环境。

工具的集成

工具的集成，最值得优化的情况包括两种：一是，使用管道（Pipe）对命令行工具进行集成；二是，对集成开发工具环境（IDE）进行配置，让 IDE 和周边工具集成。

使用管道（Pipe）对命令行工具进行集成

其实，我在前面的文章中已经使用过管道很多次了，只是使用场景比较简单而已。

比如，在第 24 篇文章中，我使用命令 curl -s <github_url> | vim -，来查看 GitHub 上某个用户的代码仓情况，通过管道把 curl 命令的输出传给 VIM，以方便我在 VIM 中查看较长的输出。

> curl -s https://api.github.com/users/jungejason/repos | vim -
Vim: Reading from stdin...

## 进入 VIM 窗口，显示所有 repo 的 array
[
  {
    "id": 213849635,
    "node_id": "MDEwOlJlcG9zaXRvcnkyMTM4NDk2MzU=",
    "name": "counter-redux-sample",
    "full_name": "jungejason/counter-redux-sample",
    "private": false,
    "owner": {
      "login": "jungejason",
...

这个案例使用了一次管道，比较简单。

而高效使用管道，首先是使用多个管道灵活地把多个工具连接起来。一个常见的场景是，grep 和 xargs 命令的组合。比如，ps aux | grep vim | grep -v grep | awk ‘{print $2}’ 可以找到我当前运行的 VIM 程序并将其关闭。

ps aux | grep vim | grep -v grep | awk '{print $2}' | xargs kill

整个管道链是这样工作的：

ps aux 打印所有运行程序，每行打印一个。

grep vim 显示包含 vim 的那些行。

grep -v grep 表示过滤掉包含 grep 的那一行。这是因为，上面一步 grep vim 命令中包含“vim”字样，所以它也会被 ps aux 显示出来，我们需要将其过滤掉。

awk ‘{print $2}’ 表示抽取出输出中的 PID，也就是进程 ID。

xargs kill 接收上一步传过来的 PID，组成命令 kill并执行，杀死 vim 进程。

上面管道每一步的执行输出，如下所示：

> ps aux
USER               PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
_windowserver      228  14.9  2.3 13044220 382952   ??  Ss    9Oct19 679:18.68 /System/Library/PrivateFrameworks/SkyLight.framewo
jasonge          49393  14.4  2.1  5586816 356860   ??  S    Sun12AM 583:37.42 /Applications/XMind.localized/XMind.app/Contents/F
jasonge          49371   9.7  0.4  4826572  64204   ??  S    Sat11PM 167:30.01 /Applications/XMind.localized/XMind.app/Contents/F
...

> ps aux | grep vim
jasonge          31383   0.0  0.0  4310340   4740 s001  S+    9:57AM   0:00.11 vim
jasonge          32304   0.0  0.0  4268056    704 s004  R+   10:17AM   0:00.00 grep vim

> ps aux | grep vim | grep -v grep
jasonge          31383   0.0  0.0  4310340   4740 s001  S+    9:57AM   0:00.11 vim

> ps aux | grep vim | grep -v grep | awk '{print $2}'
31383

> ps aux | grep vim | grep -v grep | awk '{print $2}' | xargs kill

这是使用多个管道的一个具体例子。在工作中，你可以根据实际情况，使用管道来封装常用的工具。

在命令行中高效使用管道的第二个重要方法，是使用模糊查询工具（Fuzzy Finder)。

常见的模糊查询工具，有 fzf、pick、selecta、ctrlp 和 fzy 等。其中，最有名的应该就是 fzf（你可以再回顾下第 28 篇文章中关于这个工具的介绍）。

在管道中使用模糊查询工具，可以大幅度提高使用体验。

在开发工作中，我们常常需要对文本进行搜索和过滤，一般会使用 grep 来实现。这个操作虽然简单，但频率非常高，很值得优化。比如，上面提到的杀死 vim 进程的操作。我们使用 ps aux 命令列举出所有进程之后，需要使用多个 grep 命令来进行搜索过滤，最终查找到我们需要的那一行。

在这个操作中，grep 过滤命令常常会比较复杂，为了达到正确的过滤效果，我们可能要调试多次才能找到正确的 grep 命令。另外，还可能会出现比较难以使用 grep 过滤的情况，比如有多个 vim 进程同时打开时，使用 grep 选出其中一个就不是很方便。

这时，使用模糊查询工具就很方便了。在我看来，模糊查询工具的本质，就是交互式的文本过滤工具。它接收文本，然后提供界面让用户输入查询条件，并在用户输入的同时实时过滤。当用户找到需要的结果回车确认后，输出结果文本，使用起来很自然。

以杀死 vim 进程的操作为例，我可以使用 ps aux | fzf 命令把进程列表发送给 fzf，fzf 首先会列举出所有进程供我搜索过滤。随着我的输入，fzf 进行实时过滤，去除不符合条件的那些行。在我输入了’vim 后，就只剩下几行结果了。这时，我可以使用上下键进行选择，回车之后 fzf 就会把这一行内容输出到 stdout 中。

可以看到，整个过程中我可以实时调整搜索条件，免去了使用 grep 需要多次调整参数的过程，使用起来非常便捷。

当然，除了把过滤结果输出到 stdout 外，也可以通过管道把输出传给其他工具，比如传给 awk 和 kill，来实现杀死 vim 进程的目的，具体的命令是 ps aux | fzf | awk ‘{print $2}’ | xargs kill。

可以看到，这个命令和前面使用 grep 的命令差不多，唯一区别是把之前命令中的两个 grep 替换成了 fzf。但正是这个区别，把中间的非交互文本过滤变成了交互式过滤，效果非常好。

实际上，你还可以更进一步，把这个使用 fzf 的命令保存为一个 shell 脚本，成为一个交互式的 kill 命令。如下所示：

#!/bin/bash

ps aux | eval "fzf" | awk '{print $2}' | xargs kill

从非交互文本过滤到交互文本过滤的简单转变，着实可以为我们带来意想不到的巨大方便。比如：

在切换工作路径时，可以用 find 命令列举出当前文件夹的所有子文件夹，然后进行交互式的过滤，最后切换路径。

在使用 apt 或者 brew 安装软件包时，可以用来过滤可用软件包，方便软件包的选择。

它甚至可以与一些网站服务对接，比如这个使用fzf 和 caniuse 服务集成的例子，就很酷。此外，fzf 官网上还有很多类似的脚本例子，推荐你去看看。

IDE 和周边工具集成

我们的目标是能在 IDE 中进行常见的软件研发活动，从而减少工具的切换。

具体来说，基本的 IDE 集成功能包括：

编码；

构建；

实时检查语法错误；

实时检查编码规格；

运行单元测试，并在测试输出中点击文件名进行跳转；

在本地运行服务，并可以设置断点进行单步调试；

连接远程服务进行单步调试。

这些功能比较常见，你可以根据自己使用的 IDE 在网上搜索相关资料，我就不再赘述了。接下来，我会重点与你分享些不是那么传统，但非常有效的集成功能。

首先，是 IDE 跟命令行工具的集成。

命令行里有很多的工具，所以 IDE 只要集成了命令行终端，就可以把它们一次性都集成了进来，所以效果非常好。

这里的集成有两个层次：

第一个层次是，终端窗口成了 IDE 应用的一个子窗口，这样使用比独立的终端窗口使用起来更方便。比如，可以使用快捷键方便地打开、关闭集成终端窗口。方便窗口管理。

第二个层次是，终端子窗口和 IDE 其他部分的一些交互。这个层次更深，也更重要，可以让我们更好地在 IDE 中使用命令行工具，是我们要留意的。我推荐你在你所用的 IDE 文档中，查看有哪些第二个层次的集成。

以 VS Code 为例，属于第二层次的集成有以下四种。

第一种，在 IDE 中拖动文件夹或者文件到集成终端窗口中，文件夹或文件的完整路径名会自动复制到终端里。

第二种，在集成终端中运行当前编辑窗口中打开的文件，也可以在编辑窗口中选中一段文本直接在终端运行。方法是运行命令 Terminal: Run Active File in Active Terminal，或者是 Terminal: Run Selected Text in Active Terminal。

第三种，集成终端中的命令输出，如果包含文件名，可以用 Cmd+ 鼠标点击，直接在编辑器中打开这个文件。这个操作我经常用，比如搜索文件时，因为我对 fd 和 rg 命令很熟悉，所以有时会直接使用 fd + rg 的组合，而不是用 VS Code 的原生文件查找功能了。

第四种，可以安装针对 VS Code 的命令行工具。比如，使用了oh-my-zsh 中的 vscode 插件后，我们可以在集成终端中使用 vscr 在当前 VS Code 中打开文件，用 vscd 进行文件比较等工作，实现了终端和 IDE 的更紧密集成。

其次，是 IDE 和代码仓的集成，包括 Gist、GitHub Pull、Git Graph。

在 IDE 中进行代码仓的操作集成，我个人是比较谨慎的态度。我会比较多地使用命令行的 Git，因为我认为目前还没有 GUI 工具能暴露 Git 的全面功能。在 IDE 中，我只选择最适合在图形界面中使用的一些场景。

比如，在 VS Code 中，我只使用了 3 个插件：

Gist，方便在编辑器中直接上传文件。

GitHub Pull Request，方便管理 GitHub 上的 PR 处理，可以直接在 VSCode 里查看和进行讨论，非常方便。

Git Graph，用于查看历史。它可以显示提交历史的图结构，点击提交可以直接查看文件，比命令行工具更快捷。

如果你想使用这几个插件，可以参考它们的官方文档。

以上就是工具集成方面的内容，接下来我们来看看如何提高工具一致性。

提高工具一致性

提高工具的一致性，主要方法是减少常用的工作入口，并对这些入口进行优化。

工具太多容易混乱，但如果能控制入口数量，也就是说进行工作时，只从几个有限的入口开始操作，同时对这些入口进行优化，就能提升工具使用体验的一致性，降低使用工具带来的负担。

要减少并优化常用的工作入口，那我们首先需要明确经常使用的、必要的工作入口。

在我看来， 命令行、IDE、桌面快捷启动工具（Launcher）和浏览器这 4 个工作入口非常必要，是值得重视并优化的。

1. 命令行

我比较喜欢命令行的一个重要原因是，它是一系列工具的共同入口。绝大部分命令行的命令都遵循一定的规律，又有统一的 man 命令查看手册，很容易找到使用方法。

使用命令行减少工作入口还有一个办法，是使用统一的客户端工具进行多种操作。比如，Git 命令带有很多子命令（比如，log、diff、show 等），从而一个 Git 命令行工具可以完成很多工作。

在 Facebook，很多工作都通过 Arcanist（Phabricator 的命令行工具）的各种子命令来完成。这样就实现了命令使用的一致性，降低了学习成本。

在日常工作中，你也可以尝试开发一个命令行工具，对常用工作进行封装。之前我在 Stand 的时候，就使用 Node.js 的commander模块，对日常研发工作进行了封装。

2. IDE

上面已经提过，IDE 是一个重要入口，我们应该把较多的开发工作集中到 IDE 中直接进行。

3. 桌面快捷启动工具

桌面快捷启动工具，也是一个非常重要的入口，我们可以通过它进行很多工作。以 Mac 上非常有名的 Alfred 来说，你可以用它来启动程序、切换程序、搜索文件、进行网页搜索、管理系统剪贴板历史、进行计算、运行系统命令（比如，重启机器、锁定机器、关闭特定程序、关闭所有程序等）等操作。

用好这个工具，可以大大提高工具使用的一致性。

4. 浏览器

现在，我们会大量使用网站应用，那浏览器是必不可少的入口。你可能会觉得，在浏览器中不就是使用 URL 吗，还能怎么提高呢？

这里，我就和你分享几种方法吧。

其实，我们比较熟悉的书签功能，就是对在浏览器中使用网页应用的一个优化。因为它可以记录常用的工具，你不用每次都输入。

此外，还有一种你可能不太熟悉但非常有效的办法，就是自己运行一个搜索引擎，并把它设为浏览器的默认搜索。这样，你就可以在你的搜索引擎中定义规则，并在浏览器的地址栏输入符合规则的操作。

比如，你可以在搜索引擎后端中实现对“t”的解析，用来打开任务系统中序号为 task-id 的任务。当你在浏览器地址栏中输入“t 123"的时候，搜索引擎自动解析出任务 ID 123，然后跳转到这个任务的 URL，比如 http://tasktool/123.html。当然这个任务工具可能是 Trello，也可能是 Jira。

在 Facebook 内部，就有一个这样的公用引擎，支持快捷跳转到公司几乎所有的网站中，同时还可以使用它直接进行各种搜索工作，比如内部网页搜索、wiki 搜索等，极大提高了公司各种网站应用使用时的一致性。Facebook 开源了这个搜索引擎框架，叫作Bunny1。

事实上，这种浏览器使用方式相当于把浏览器的地址栏变成了一个命令行入口。因为搜索引擎后端就是一个通用的后端服务，可以实现各种各样的强大功能，所以你可以自己做一个这样的搜索引擎，在浏览器地址栏里充分发挥想象力。

小结

只有把工具放到具体的工作环境中，才能发挥它们的真实价值。

今天，我从两个方面与你介绍了怎样把多个工具配置成为高效的工具环境。一是，如何对工具进行集成，减少工具切换时的不顺畅，提高个人的研发效能。二是，如何减少并优化工具的入口，从而提高工具使用体验的一致性，以降低工具使用成本，提高研发效能。

其实，工具环境的配置，是一个需要不断摸索，去寻找最佳平衡点的过程。比如，在 IDE 中进行集成，应该集成最常用的功能，而不应该尝试把所有的功能都使用插件集成到 IDE 中。否则，IDE 就会变得臃肿不堪，性能下降。

我推荐的办法是采用 80/20 法则。也就是，集中精力对工作中最常用的 20% 的操作，进行集成和优化。从我个人的体验来看，这样的平衡点是比较合适的。

总之，我们时刻要牢记，配置环境的目的是，配置出一个强大而灵活的环境，让一切工作流畅，从而提高生产效率，一定要注意投入产出比。

思考题

在浏览器作为工作入口的例子中，如果一个任务描述搜索到的任务不止一条时，我们可以怎么处理呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

30 | 答疑篇：关于价值导向和沟通

你好，我是葛俊。今天，我来针对“个人效能”模块的留言问题，和你做一次展开吧。

在“个人效能”模块的文章中，很多同学留下了精彩留言，阐述了自己对个人效能的认识、遇到的问题以及解决方案。看得出来，大家平时都对提高个人效能有着比较深入的思考。

其中，尤其让我印象深刻的有 @我来也 对各种命令行工具的讨论、@Johnson 对编辑器的讨论，以及 @Jxin 对价值和目标的思考，真的是非常精彩。

谢谢你们的留言！我很喜欢与你们交流的过程，所以会详细回答你们的每个问题。今天，我从留言的许多问题中，梳理出了价值导向和高效沟通这两个话题，再与你详细聊一聊。

工作中的价值导向

在第 21 篇文章“高效工作：Facebook 的 10x 程序员效率心法”中，@Jxin 同学与我展开了关于价值取向的一些讨论。从这个专栏的一开始，我就强调了目标的重要性，研发效能三要素中的第一条，就是准确性。我们只有先找对目标，后续的行动才会有意义。

所以，在工作中，我们需要考虑以什么目标作为最根本的出发点，是职业发展、薪酬、工作生活平衡、工作稳定性，还是实现价值？具体怎么选择，就要看你个人认可的价值到底是什么了。

比如，@Jxin 提出以实现价值为出发点，尽量把企业价值和个人价值的发展摆到一条线，能够做兼顾两者利益的任务。如果实在不能统一，就要多考虑些公司的发展，少考虑些个人短期利益的追求。对此，我很是赞同。

又比如，十多年前我在西雅图微软工作的时候，有一个朋友特别看重工作生活平衡和工作稳定性。刚毕业，她就在寻找一个有稳定工作机会的方向，后来选定了数据库方向。她认为这个方面的工作会长期稳定，于是在这个方向做到专精。事实证明，她的选择到现在看还是很不错。不用学习太多的新东西，仍然能够有稳定的工作。

虽然我个人更看重不断接触新领域，不断提高、发展自己，和她的价值取向不太一致，但我觉得她的做法也非常棒。她能明确自己想要追求的东西，于人于己都有好处，还能找到具体的实施路径，非常棒！

所以我建议，平时多考虑下自己最在意的到底是什么。这样工作起来才能有的放矢，把时间花在对自己最有价值的事情上。

有些时候，找到自己最在意的事儿并不容易，将其实现，则常常更有挑战性。所以，我再向你推荐一个提高实现目标效率和过滤不现实目标的方法：WOOP。WOOP 分别是 Wish（愿望）、Outcome（效果）、Obstacle（障碍）和 Planning（计划）四个单词的首字母。

使用 WOOP 的具体方法是，抽出你不会被干扰的 5 分钟时间，针对 WOOP 的 4 个组成部分进行冥想。

第一步是 Wish，也就是认真考虑我到底希望得到什么，希望实现什么？这里的“希望”，既可以是长期的希望，比如 5 年、10 年，也可以是短期的希望，比如一个月，或者就是今天。专注于一个愿望，并用 SMART 原则来具体描述它。SMART 是一个目标管理工具，我在下一篇文章中会详细展开，你可以先参考下“SMART 原则以及实际案例”这篇文章。

第二步是 Outcome，也就是愿望实现之后，能够达到哪些好结果。在大脑中形象地描绘并体验实现了这个愿望之后，我的生活能有哪些正向改变。有研究证明，这种形象化考虑正面结果的办法，可以大幅提高后续执行计划的积极性和效率。

第三步是 Obstacle，也就是实现这个愿望时可能有哪些障碍。在这一步，也要形象地描绘 2~3 个最主要障碍的具体细节。静下心来思考的时候，常常会发现平时的一些习惯，其实是实现这些愿望的重要障碍。这一步可以让我们在后续执行计划时，更敏锐地觉察到障碍的出现，从而更好地处理它们。

第四步是 Planning，也就是针对每一个具体可能出现的障碍，计划用什么办法去解决它。我们可以使用 if-then 的结构来处理，即 if 出现障碍 A，then 使用计划 B 来解决。同样地，我们要具象化这些计划，才能在后续工作时迅速反应，使用事先计划好的最佳方法去克服障碍。

在我看来，使用 WOOP 方法主要有两大好处：

让我们对自己的目标更清晰。通过 WOOP 中的具体思考，我们能对愿望有更清晰的认识，加深对自己的了解。在第四步计划的时候，我们可能会发现有的障碍，没办法克服。这时，我们就要果断放弃这个愿望。这样，WOOP 能帮助我们过滤不现实的愿望，避免浪费时间。

提高实现目标的效率。通过具体的这四个步骤，我们会对自己的愿望更有激情，对实施过程中可能出现的困难也更有预期，并且能够更有准备地去解决困难。

在我看来，WOOP 的效果非常好。在不被干扰的时候，花五分钟进行一次 WOOP 思考，对理清自己的目标、清除干扰非常有效，推荐你也试试。另外，在 Coursera 上有一个很棒的视频，是对 WOOP 方法创始人 Gabriele Oettingen 的访谈，也推荐你看一下。

沟通

在第 22 篇文章“深度工作：聚焦最有价值的事儿”中，@Landy 提出了一个关于沟通建议的问题。

沟通的确是高效工作的重要软技能，也是这些年来我一直在努力提高的方向。接下来，我就与你分享 3 个高效沟通的原则吧。

第一个原则是，沟通时要有同理心。我们沟通的目的，是在对方身上起作用，要从别人的角度考虑怎样去沟通。所以，我们需要了解以下三个方面的信息：

一是，对方的知识背景，从而使用他能理解的语言去沟通；

二是，对方想要知道什么，才能不绕弯路高效答复；

三是，对方的出发点，根据他的出发点将对话引向双赢的方向。

第二个原则是，外在表现很关键。中国的文化比较内敛，我们从小受到的教育也强调内在美，忽视外表，这就会引导我们形成忽视外在表现的习惯。因此，我在美国工作时吃了不少亏，做出的东西，却被会表现的人抢走了大部分功劳。这才逐渐认识到，这种“内敛”实际上是不对的，很多情况下外在表现更重要。

比如，你的内心对一个人非常好，但是你的脾气很暴躁，总对 TA 发脾气，做出对 TA 不好的举动。那，你对这个人到底是好还是不好呢？

我认为你的实际行为、外在表现，对这个人造成了真实伤害，你内心的好并没有实际作用。所以，你对 TA 是不好的。

外在表现很关键这个原则，在开发工作上表现为我们不但要重视实际的工作，也要重视别人对我们工作成果的感知。很多开发者从内心抵触 PPT，我觉得这就是高效工作的一个重要阻碍。我们确实需要花一些精力，去考虑如何把我们做出的东西更好地呈现出来。当然，我并不是说，要来虚的，具体做出东西才是王道，是前提。

第三个原则是，冰山原则，是美国软件界大佬 Joel Spolsky（周思博）提出来的。

这个原则对软件技术人员和非技术人员间的交流非常有用，指的是一个软件应用系统比较复杂，就像一座冰山。但是，它表现在外面的只是一小部分，就像冰山露出水面的部分一样。如果你沟通的对象对软件不熟悉的话，他就会认为，冰山上的可见部分就是全部工作，或者说是绝大部分的工作。

这样的结果就是，软件开发人员在做项目进展演示的时候，如果演示得很完整、漂亮，对方就会认为你的工作做得差不多了，即使你已经提前强调过这只是在界面上做的一个 Demo，也不会有效果。因为对方在潜意识里，就认为你的工作已经做得差不多了。

所以，我们在做演示的时候，要尽量把界面的完成程度和项目的进展程度对应起来。比如，不要把界面做得太漂亮，显示的文字可以用“XXX”，而不要用真实数据等。只有这样，才能让对方对项目的真实进展有比较客观的感知。Joel 有一篇博客专门讲这个话题，很有意思，推荐你阅读下。

小结

好了，以上就是今天的主要内容了，希望能对你梳理目标和提高沟通能力有所帮助。如果有你希望深入了解还未涉及的话题，那就直接给我留言吧。

接下来，我们就会开启新的模块了，进入管理和文化的讨论。在下一个模块，我会重点与你介绍硅谷的管理、文化，以及在国内的一些落地实践，希望帮助你了解如何利用管理推动高效研发的落地，以及如何使用文化来激发团队成员的内驱力和创造力，提高团队的研发效能。

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

特别放送 | 每个开发人员都应该学一些VIM

你好，我是葛俊。

在“研发流程”和“工程方法”模块中，我主要是从团队的角度和你分享如何提高研发效能，所以很多同学希望我能分享一些工具的使用，来提高自己的效能。所以今天，我准备了一篇关于 VIM 的文章。在这篇文章中，我会着重带你深入了解 VIM 的两个特点。因为正是基于这两个特点，VIM 可以很好地提高我们的工作效率。至于更多的、具体的 VIM 使用方法和技巧，我会在接下来的“个人效能”模块中，用另一篇文章专门详细与你介绍。

如果你已经是一个 VIM 的使用者了，那我希望文中关于 VIM 原理的讨论，可以帮助你更深入地理解它，进而可以更高效地使用它。而如果你还不是 VIM 的使用者，那我推荐你学习它的基本方法，并寻找适当的场景去使用它。

其实，向开发者们推荐编辑器，尤其是像 VIM 这样一个比较容易引起争议的编辑器，是一件有风险的事儿。但，基于我对 VIM 的了解和它能给开发者带来的巨大好处，我认为这个风险是值得的，相信你也能从中有所收获。

我们下来看看什么是 VIM。

什么是 VIM？

VIM 是一个老牌的编辑器，前身是 VI，第 1 个版本发行于 1978 年，距离今天已经有 41 年的历史了。

VIM 是 VI Improved，是提高版的 VI，相对来说比较新，但实际上它的第 1 个版本也早在 1991 年就发布，也已经有 28 年的历史了。

VIM 和我们日常使用的编辑器，比如 VS Code、Notepad++、Sublime Text 等，差别很大，而且上手比较难，新手在使用时常常会手足无措。一个常见的问题是，打开了 VIM 就不知道怎么退出。比如，有人就曾在Stack Overflow上提问怎么退出 VIM，6 年以来的阅读量已经接近 200 万。又比如，我还听到过一个玩笑，问怎样产生一个随机字符串呢，答案是让一个不会使用 VIM 的人打开 VIM 并尝试退出。

虽然如此，但在美国对开发人员进行的最喜欢的编辑器的调研中，VIM 往往能排进前 5 名。我个人的看法是，每一个开发者都应该学一些 VIM，原因有二：

VIM 基于命令行模式的特色，能让文本编辑工作更高效；

VIM 有极高的跨平台性，可以一次学习然后多处使用，尤其可以作为很多其他 IDE 的插件使用。

比如，当前我在进行微信小程序的项目开发，使用 VS Code 作为我主力 IDE。在 VS Code 中，我每天都在使用 VIM 插件，VIM 的命令操作大大提高了我的开发效率。作为开发者，编辑文本是最基本的工作，所以花些时间去了解最基本的 VIM 操作来提高效率和手指健康，是相当值得的。

在我看来，VIM 有两大特点：

具有独特的命令行模式；

跨平台非常棒，更能作为插件在很多其他 IDE 中使用。

而这两个特点，也正是我推荐你学习 VIM 的最主要原因。

特点一：VIM 独特的命令模式使得编辑文档非常高效

非 VI 系列的编辑器一般只有编辑模式这一种模式，也就是说，敲击任何主体键都会直接修改文件内容。比如，敲击键盘上的 e，文件里就添加了 e 这个字符。这里需要注意的是，键盘的主体键指的是，能显示在文件里的键，包括 a-z、数字、字符等。

而 VIM 有多种模式，其中最主要的是命令模式和编辑模式。命令模式是 VIM 的默认模式，我们用 VIM 打开一个文件的时候，默认进入的就是这个模式。在命令模式中，敲击主体键的效果不是直接插入字符，而是执行命令。比如：

敲击字母 e，表示将光标向右移动到当前单词最后一个字符；

敲击符号 *，表示在当前文件搜索光标所在的单词。

另外，在命令模式中输入“:< 命令 > 回车键”，可以执行一些命令行命令以及进行系统配置。比如：

输入:q! 表示，不保存文件并退出 VIM；

输入:set hlsearch 表示，打开搜索高亮。

至于我们在其他非 VIM 编辑器中熟悉的编辑模式，需要在命令模式中敲击某些命令才能进入。比如：

敲击 i 表示，在当前位置进入编辑模式；

敲击 O 表示，在本行上面添加一个空行并进入编辑模式。

进入编辑模式之后，使用体验就跟其他非 VIM 的编辑器效果差不多了，也就是说敲击主体键会直接插入字符。完成编辑工作之后，你需要再敲击 Esc 键返回命令模式。

请注意，在编辑模式时，我们无法退出 VIM。你只能在命令模式中使用 ZZ、ZQ、:qa! 等命令退出 VIM。如果你不会使用 VIM，然后不小心在命令行窗口中打开了它，没有菜单可以选择，的确很难找到办法退出，所以就有了各种不能退出 VIM 的笑话。

另外这里需要指出的是，VIM 实际上有多个模式，它的官方文档列举了一共 7 个基本模式和 7 个附加模式，而我在这篇文章中只做了命令模式和编辑模式两种模式的划分，是一个巨大的简化。事实上，命令模式中包含了常规模式（normal mode）、命令行模式（command-line mode）等，编辑模式则包括了插入模式（insert mode）、替换模式（replace mode）等。我之所以用命令模式和编辑模式的简单区分，一方面可以帮助你快速理解 VIM，另一方面也不会影响你对 VIM 的使用。

总结来说，拥有命令模式是 VIM 系列编辑器与非 VIM 系列编辑器的最大差别。那，VIM 为什么会有这种特性呢？

这是由 VIM 的历史决定的。VIM 的前身是 VI，VI 的前身是 Ex。Ex 是 Unix 诞生时代的编辑器。那个时候因为计算机技术以及计算机系统资源的局限性，编辑器只能使用命令来编辑文件。所以 VIM 就一直保留了命令模式。这个命令模式是初学者难以适应 VIM 的最主要原因，但同时也是 VIM 能高效编辑文档的关键所在。

为什么这样说呢？在一个非 VIM 的编辑器中，如果要做一个非编辑操作的时候，你需要敲击一个非主体键，或者组合键才能完成；而在 VIM 的命令模式中，你通常只需要敲击主体键。比如

我们在编辑文件的时候，有大量的非输入操作，比如挪动光标、查找、删除等，所以在非 VIM 的编辑器里，我们要大量使用非主体建和组合键。而在 VIM 中，我们可以大量使用主体键，从而大大减少使用键盘主要部分（也叫工作区）之外的特殊键，同时使用组合键的次数也大大减少了。

所以，综合来讲，虽然 VIM 中的模式切换会带来一些额外按键操作，但次数远远小于它节省的按键次数，总的按键数量明显减少。

接下来，我们通过一个具体的案例对比一下效果吧。我在输入一行代码注释时，希望输入的结果是

// This is making sure that userTotalScore is not null

但写到“not”的时候，我注意到我前面有一个拼写错误，把“making”写成了“mkaing”了

// This is mkaing sure that userTotalScore is not

                                                 ^

现在，我需要修改这个错误，修改之后再回到行尾，补充“ null”写完这句话。以 Mac 为例，不使用 VIM 和使用 VIM 的操作对比如下表：

统计下总次数，如下表所示：

可以看到，在这个场景中，使用 VIM 可以明显减少按键次数，包括组合键次数和特殊键次数。在真实的编辑场景中，我的经验是减少的按键次数会更多，对文本编辑效率的提高非常明显。

另外，组合键和非主体键这两种按键方式非常容易对手腕和手指造成伤害。其实，我之前是 Emacs 的重度使用者，但使用了四年之后，我的左手小拇指开始不舒服，这是因为在 Emacs 中我常常需要用这个手指按住 Ctrl 键来完成组合键操作。比如，使用 Ctrl+f 向右移动光标，使用 Ctrl-x Ctrl-S 保存文件。

为了手指健康，我试着从 Emacs 向 VIM 转移。一个月之后，手指不舒服的症状明显减轻了。于是，我逐渐停止了使用 Emacs，全面转向 VIM。此后，双手再长时间使用键盘工作，也不容易疲劳了。

特点二：VIM 是跨平台做得最好的编辑器，没有之一

因为 VIM 的悠久历史，同时一直在持续更新，所以在各大操作系统上都有适用的 VIM 版本，你可以到VIM 的官网上查看详情。所以，你掌握的 VIM 技能基本可以用在所有的操作系统上。

具体来说，在 Unix 系统上都有预装 VI。因为 VIM 的命令是向上兼容的，所以你熟悉的 VIM 的基本功能在 VI 上仍然可以使用。Linux 系统自带的基本都是 VIM，比如 Ubuntu18.04 自带的版本就是 VIM8.0。苹果操作系统因为是 Unix 的一个分支，所以预装有 VIM。

Windows 上没有预装 VIM。不过你可以很方便地安装 GVIM，或者直接运行一个免安装的可执行 GVIM 程序。

在移动端操作系统上，VIM 在 iOS 和 Android 端都有移植：

iOS 上面比较好用的叫 iVIM。我在 iPad 中进行一些重量级文本编辑的时候，就会使用 iVIM。具体的使用方法是，将需要编辑的文本拷贝到 VIM 里面，编辑好了之后再拷出来，使用体验也还不错。

Android 上的 VIM 移植得比较多，比如 DroidVim 就还不错。

VIM 跨平台特性的另外一个表现是，很多其他编辑器及 IDE 都有 VI 模式，支持最基本的 VIM 操作。

比如，IntelliJ 系列的 IDE 上有IdeaVim 插件、VS Code 里有VSCodeVim 插件，甚至 VIM 的老对手 Emacs 里也有好几个 VI 插件，最有名的是Viper Mode。

我最近半年使用最多的编辑器 VS Code，所以我以它为例，与你说明如何在其他编辑器中使用 VIM。

VSCodeVim 插件的安装很简单，使用默认的 VS Code 插件安装方法很容易就能搜索到，并一键安装；配置也简单，默认的配置使用体验就非常不错。我在使用 VSCode 一个月后，对 VS Code 比较熟悉了，开始试用 VSCodeVim 插件，之后就再也回不到原生模式了。因为，VIM 带来的效能提升，以及给手指带来的舒适感觉实在是太明显了。

VIM 的跨平台特性甚至超越了编辑器这个范畴，在一些不是编辑器的软件里面也有 VI 模式。比如，Chrome 浏览器和 FireFox 浏览器中都有 VI 插件，用户可以使用 VI 的快捷键方式来操作。

在我看来，在浏览器上使用 VI 模式的最大好处是，可以减少鼠标的操作。这一点对我的吸引力不是很大，不过我的另外两个朋友，一直在使用 Chrome 的 VI 模式插件Vimnium，反馈都是很好用。如果你非常偏好键盘而不是鼠标的话，推荐你也试试看。

在配置方面，VIM 的默认配置就基本够用。所以，我一般只在自己的主力开发机上，才会添加一组我的常用配置及插件来提高使用体验，其他不常用机器就保留默认配置。

总的来说，VIM 的跨平台做到了极致，因此我在很多地方都能用到积累的 VIM 经验。VIM 肌肉记忆不断强化，一直在帮助我提高工作效率。

小结

在这篇文章中，我着重与你讲述了 VIM 的命令模式与跨平台特性这两大特点。通过对这两个特点的深入探讨，阐述我认为每个开发人员都应该学一些 VIM 的理由。

VIM 编辑器的命令模式，是与其他非 VIM 编辑器的最大区别。也正是因为这个特性，使得其入门比较难，令很多新手望而生畏。但也正是因为命令模式，才使得 VIM 对于个人研发效能的提升帮助非常大。

而跨平台特性，使得我们一旦掌握了 VIM 技能，就基本可以用在所有的操作系统上，甚至是其他 IDE 中通过插件使用，从而最大程度地实现经验复用。

其实，除了命令模式和跨平台特性外，VIM 还有一些其他好处，比如速度快、免费、可扩展性强等。但是，我认为这两点从根本上把 VIM 和其他编辑器区别开来了，它们能让我们非常高效、健康的编辑文本。所以说，付出一些成本去学习 VIM 的基本使用是非常值得的。

有一种说法是，说人的双手在一生中能够按键盘的总次数是一定的，达到这个总次数之后，手指就不能很好地使用键盘工作了。不知道你信不信，反正我信了。

关于 VIM 的话题，我们今天就讨论到这里了。在“个人效能”模块，我还会与你详细分享如何高效地学习 VIM，并分享关于 VIM 的一些使用方法和技巧，帮你学会、用好 VIM 这个工具。

思考题

除了 Windows，你见过没有预安装 VI 的系统吗？那个系统上自带编辑器是什么呢？在这个系统上，你又是如何完成文本编辑工作的呢？

你见过 VIM 教徒和 Emacs 教徒的争吵吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

31 | 业务目标和技术目标两手抓：怎样打造高效团队？

你好，我是葛俊。今天，我来和你聊聊管理和文化。

今天这篇文章，是最后一个模块“管理和文化”的第一篇。在前 30 篇文章中，我已经从优化流程、团队工程实践、个人工程实践这三大方面与你介绍了很多原则、方法和具体实践。通过这些内容，相信你应该对软件研发活动的本质有了更深刻的理解，也对这条超级灵活的流水线如何提效有了新的认识。

但作为团队管理者，要提高团队的研发效能，掌握了这些原则、方法和实践后，还要通过管理和文化让它们真正在团队落地。管理是提高团队研发效能的基石，而文化是持久高效的保障。同时，管理又决定了文化，如下图所示。

所以，在接下来的几篇文章中，我会参考硅谷高效能公司的一些管理实践和原则，以及我在国内外公司的落地经验，与你介绍如何通过管理和文化来提高团队的研发效能。今天，我们就先从技术管理者的主要工作步骤出发吧。

在我看来，技术管理工作主要有如下 3 个步骤：

寻找目标；

目标管理；

计划并执行去实现目标。

其中，计划执行又包括人、流程和工具 3 个方面。

第一步：寻找目标

在第 1 篇文章中，我曾和你分享研发效能的三要素是有效、快速、可持续性，其中第一条就是有效，也就是准确性。所以，要想建设高效的研发团队，技术管理者的第一项重要工作就是，作为舵手，为团队寻找方向和目标。

首先，技术团队的根本目标就是业务目标。毋庸置疑，业务目标是团队存在的意义，完成它是一切的基础。

业务目标的设定有两个层次：

弄清楚公司和上级对团队的预期，达到这个预期，是团队的基础目标。

在基础目标之上，还要给团队设定一个进取目标。我们需要分析公司的发展方向，以及团队的实际情况，找到那些既符合公司利益，又通过跳一跳就能够得着的目标。

除了业务目标之外，还有另外一种技术管理者一定要关注的目标，即技术目标。如果只关注业务，我们的行动就容易短视。有这么一句话我非常赞同：技术常常在短期被高估，在长期被低估。作为技术管理者，我们需要看清楚并坚持技术在长期可以发挥的巨大作用，在技术上持续投资，制定并完成合理的技术目标。

在我看来，技术目标主要有两种：

一种是关于偿还技术债的，这是处理已经形成的问题。关于技术债的处理，你可以再回顾下第 14 篇文章中的相关内容。

另一种是前瞻性的技术目标。作为技术管理者，我们要有灵敏的技术嗅觉，对即将出现的技术挑战，做一些预防和准备。

关于前瞻性的目标，我再和你分享一个我在 Facebook 时的例子吧。

2013 年左右，我们从公司开发者的使用数据观察到，由于代码仓的迅速增大，Git 对它的支持有些吃力，比如一些操作的速度越来越慢。于是，我们抽出人力去做调研，克隆了一个 Facebook 的代码仓，并按照当时的增长速度去模拟大量的提交进行测试。

结果发现，再过半年，许多常用的 Git 操作速度都将下降到不可接受的程度。于是，我们团队专门成立项目组来解决这个代码仓将会出现的性能问题。最终，我们在问题还没发生前就把它解决掉了。这种具有前瞻性的技术目标，确保了公司的业务能够持续发展，给公司和团队带来的好处显而易见。

关于技术目标的设定，有两个常见问题：

一是，业务目标和技术目标的时间占比应该是多少？从我个人的经验看，80% 是一个合适的点。

二是，技术目标要不要立项？在我看来，这要视公司情况而定。如果你的主管对技术目标认可，那最好能够单独立项；否则，就把技术目标合并到业目标中。比如，要实现某一个业务，我们必须重构某一个组件。这里，重构组件就是一个技术目标，无论采用哪一种方式，我们都需要持续关注技术目标。

第二步：目标管理

目标管理的第一步就是制定计划。这里，我先给出一个有效设定目标的原则：SMART 原则。SMART 是 Specific（具体）、Measurable（可衡量）、Attainable（可达成）、Relevant（与主目标有相关性）和 Time-bound（有明确的截止期限）这 5 个英文单词首字母的缩写。

一个目标可能被定义为“今年下半年实现用户讨论功能”，但很明显这个定义不够清晰。而另外一种定义方法是“今年 12 月 31 号之前，实现用户讨论功能模块，在主页以及至少一个其他页面使用，并且用户使用率大于 10%”。

可以清楚看到，第二种定义方式更具体，团队成员有更明确的方向，能专门针对具体的模块使用场景和使用率努力。并且，具体的数字和截止期限，可以帮我们更容易跟踪项目进展。

很明显，第二种定义方式正是使用了 SMART 原则中的具体（S）、可衡量（M）和有明确截止日期（S）三个原则。另外，这个任务还要是可达成（A）的，也就是既要有挑战性又可以通过努力达成，这样团队工作起来才会更有劲头。最重要的是，与主目标的相关性（R）是前提，只有这样的任务才有价值。

总结来说，SMART 有更明确、具体的目标，利于员工更加明确、高效地工作，完成与公司目标更加对齐的业绩，也为管理者实施绩效考核提供了目标和标准。网络上有很多 SMART 原则的相关资料，比如SMART 原则以及实际案例这篇就还不错。

除了 SMART 原则，OKR 是一个很有用的目标管理工具。

我们经常会听到领导者（Leader）和管理者（Manager）这两个概念，但你有注意过它们的区别吗？两者经常混用，但实际上有一个本质区别：领导者告诉团队需要去哪里，而管理者告诉团队如何去到那里。

在我看来，每一个管理者都应该努力成为一个领导者，给团队目标，让团队成员自己找到达成目标的方法。而，OKR 正是帮助管理者做到这一点的工具。

其中，O 表示目标，是鼓舞人心的目标，可以使每个人保持一致和受到启发；KR 则表示关键结果，是达成目标需要注意的度量。在 Google 使用后，最近几年 OKR 很流行，效果的确很好。OKR 的内容比较多，如果你想要系统了解并在团队落地的话，推荐你阅读《黄勇的 OKR 实战笔记》这个专栏。

这里，我想强调一下，用好 OKR 的两个关键点。

第一，使用 OKR 最重要的目的是，让全公司对齐目标，所以在实施 OKR 时，我们要随时留意这个目的。可以说，这是执行 OKR 最关键的原则。基于这个原则，我们可以扩展出很多实际操作。比如公司级别定义一个 O，每个团队和个人根据实际情况制定自己的 O 和 KR；所有人的 OKR 都透明，全公司可见，同时定时做回顾、调整和复盘。

第二，OKR 不是一个 HR 工具，不是绩效管理方法。绩效管理方法一般包括目标、度量和考核（激励 / 惩罚），与之相比，OKR 只包括目标和度量，是一个目标导向工具。

OKR 之所以在目标对齐上有很大作用，是因为团队成员可以发挥主观能动性，自己制定与公司目标一致的 KR。而一旦 OKR 跟绩效挂钩，团队成员承担风险的意愿和内驱力就会大大减弱，倾向于制定更容易实现的 KR，从而失去了目标导向的意义。

所以，OKR 不要跟绩效挂钩。那么问题来了，使用 OKR 之后怎样进行绩效考评呢？实际上，这也很简单，只要评估团队成员具体完成的工作对公司的贡献度即可，甚至可以 OKR 和 KPI 并存。比如，公司高层可以使用 KPI，用数字衡量绩效，而全公司都使用 OKR 进行目标对齐。

第三步：任务执行

在具体的任务执行上，作为技术管理者，我们应该从人、流程和工具上来提高研发效能，高效达成业务目标和技术目标。

关于人：最关键的是调动起主观意愿

首先，把团队成员的利益统一起来，才会激发他们的主观能动性，自己想办法去达成目标。典型的例子是，为了消除职能竖井，采用全栈开发方式（你可以再回顾下第 8 篇文章中的相关内容）。

统一团队利益的主要方法是，采用康威定律来组织团队结构，使其与产品结构相吻合，让产品的成功成为团队一致的目标。

比如 Facebook，针对产品和功能，一般组织 10 人左右大小的团队，里面包括了前后端开发者、设计师、产品经理、数据科学家等。所有人的目标，都是如何把自己团队的功能、产品做好。小团队之间松耦合，有比较高的自主权，不同团队间主要通过目标的一致性来进行协调。这种方式不但使得大家的力往一处使，而且灵活机动、出产品很快。

把这种小分队的方法用到极致的是 Spotify 公司。他们在产品层面把各个功能模块隔绝开，某个功能出现问题，不影响其他功能正常使用。每个功能由 8 人左右的自主运营小组负责，称之为 Squad。Squad 的主管负责确认、沟通团队需要解决的问题，以及解决这些问题的意义，而团队成员的职责是自己决定如何解决这个问题，自主性非常强。

另外需要注意的是，在把组织架构和产品对应起来的时候，还有一个重要原则：DRY。对个人开发者说，DRY 是不要重复自己；而对公司或者团队来说，DRY 就是要建设针对基础设施、共享业务的平台，以避免重复造轮子。

以 Facebook 为例，Infrastructure 团队（基础平台团队）人数众多，话语权大，对公司的业务发展至关重要（我之前所在的内部工具团队，就是 Infrastructure 团队的一部分）。各项业务之所以能够快速开发、验证、上线、迭代，并实现高可用、高并发支持上亿日活用户，都跟 Infrastructure 团队的工作密不可分。

除了基础平台外，DRY 在业务方面最主要的表现是业务中台，也是最近非常火的话题。

调动人员的意愿，除了组织架构方面外，绩效管理和企业文化也很重要，我会在后面几篇文中与你详细介绍这些内容。

关于流程：选择合适的方法论、原则、实践

关于流程需要注意的是，寻找符合软件开发行业特性的方法，并根据团队情况不断优化。具体来说，我推荐使用先从全局的端到端流程入手，寻找系统瓶颈，然后再集中精力解决瓶颈，完成一轮优化。

这样可以从全局出发，避免以偏概全，能够最高效地使用团队的人力、物力。在优化过程中，我们要尽量采用数据驱动的方式，用数据来寻找问题，并通过数据的比较来检查改良措施的有效性。

针对流程的优化，你可以再回顾下第 4 篇文章中的相关内容；而关于效能度量，你可以再回顾下第2和第3篇文章中的相关内容。

另外，要提高团队的研发效能，作为团队技术负责人，需要保持技术判断力，包括技术选型的能力以及方法论的选择能力。你可以参考我在前三个模块中，给出的流程优化、团队工程实践及个人工程实践的一些高效方法论和实践。

关于工具：根据实践进行选择

完成了人、流程的工作之后，工具的选择就比较容易了：让团队根据方法论选择适用的工具即可。在前面的文章中，我对各个方法论的适用工具都做过详细介绍，你可以再回顾下相关内容。

这里，我再给出团队选用工具的两个原则吧。

第一个原则是，选择工具时要根据场景的复杂程度选择自建还是使用第三方服务 / 工具。对简单、单一场景，我推荐使用开源工具；而对复杂的系统和流程，可以考虑使用一些付费工具，比如 SaaS 产品，因为术业有专攻，这样比自建工具性价比更高。

通常情况下，针对小型创业公司，很多 SaaS 产品的价格不算太高，有些甚至免费。作为技术管理者，我们要考虑投入产出比，让开发人员把时间花在业务上可能才最合适。在硅谷，小公司使用付费软件服务的现象也很普遍，国内公司也慢慢有这个趋势了。

第二个原则是，工具虽然重要，但背后的方法论和原则更重要。比如 OKR，如果使用得当，使用一套简单的 wiki 系统就可以做起来；但如果概念不清、原则不对的话，即使引入专门的 OKR 工具效果也不会好。所以，作为技术管理者，我们一定要花时间了解工具背后的原则。

小结

今天，我通过寻找目标、目标管理，以及如何执行这三步与你介绍了一些管理方法。

首先，最重要的一点是，技术管理者需要同时关注业务目标和技术目标。只有这样，才能够让团队持续发展。如果今天这篇文章你只能记住一个观点的话，我希望是“业务目标和技术目标两手抓”。

在目标管理方面，OKR 是帮助团队对齐方向的不错工具。不过，我们使用 OKR 时一定注意，目的是对齐目标，与绩效挂钩的效果会大打折扣。

在具体的任务执行方面，从人、流程、工具三个方面入手，即想办法调动人的主观能动性，从流程全局入手把时间花在最需要优化的地方，以及根据具体方法论和场景复杂度选择工具。

最后，我觉得每一个技术人员，都应该花些时间去了解管理，原因包括两点：

对公司、团队的管理措施有更清晰的理解，可以帮助我们更高效的、有的放矢的工作；

管理是职业发展的一个方向，了解一些管理可以帮你尽早弄清楚这条路是否适合自己。

思考题

Facebook 提前预测并解决 Git 性能问题的例子中，我们最后使用 Mercurial 代替了 Git。你知道这其中的原因是什么吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

32 | 从Netflix公开的著名PPT谈硅谷公司文化

你好，我是葛俊。今天，我分享的主题是如何从公司文化的维度提高研发效能。

首先，什么是文化？定义很简单：

文化是决定一群人行为方式的共同认知、价值观和信念。

对于一个公司或者团队来说，文化就是决定其团队成员行动的基本价值观。文化决定行动，它的价值自然巨大。可以说，一个团队能否高效产出，文化起到关键作用。所以，硅谷的很多公司都特别重视公司文化。

比如，SpotIfy 对公司文化的态度就是，“如果远见是你想实现的目标，那么文化就是确保你能够实现这个目标的根本（If vision is where you are going, culture is what makes sure you can get there）”。再比如，工程师文化，正是 Facebook 这些年来的创造力引擎。

最近几年，国内公司也越来越重视文化建设了。比如，阿里巴巴的面试官有一个角色是“闻味官”，考察候选人是否和公司文化对味儿；再比如，有很多公司把核心价值观以红底黑字的形式做成条幅，挂在了墙上。但事实上，能够真正理解文化，并推行下去的公司却很少。

这里，我首先要和你厘清的是，文化更像是潜规则，写到横幅上的标语并不一定是公司文化；文化的建设，更是技术活和力气活的合体，绝不是喊几句口号就可以完成的。

比如，美国 2001 年爆发的安然丑闻案，就是因为许多高级职员参与了销毁、篡改、编造财会记录的行为，欺骗了股东，导致了这一起美国历史上最大的破产案，给股东造成了超过 600 亿美元的损失。而“诚信”正是安然公司价值观的第一条，被凿刻在大理石上，装潢精美地放置在公司一楼的大厅。所以说，“墙上的价值观”真正地践行起来，才能形成公司文化。

那么，了解公司文化，到底对我有什么帮助呢？

在我来看，如果你是管理者，或有志成为管理者，关于公司文化的讨论能够帮助你设计并实施适合自己团队的文化。而如果你现在还不是管理者，了解公司文化对你会有两方面的帮助：一是，可以帮助你推动团队进步，提高自己对团队的价值；二是，衡量团队文化是否适合自己，因为这是衡量是否要加入一个新团队或公司的重要考量因素。

接下来，我们看看到底如何推进好的文化在公司和团队落地。从硅谷的成功案例来看，推行文化主要包括三步：

定义出自己团队需要的文化；

从招聘、流程设计方案推动文化；

持续地用行动去推动，并总结提高。

接下来，我就以奈飞（Netflix）为例，与你详细探讨这三个步骤。之所以用奈飞做例子，是因为这个公司在推行公司文化方面做得非常彻底，并且他们对外公开的关于文化讨论的资料也比较多。

2004 年，奈飞发表了一篇关于公司文化的 PPT，叫作“自由和责任”，详细阐述了他们的公司文化及背后的思考。这个 PPT 非常不错，Facebook 的 COO 雪莉 · 桑德伯格（Sheryl Sandberg）曾经说，它是硅谷产出的文章里最重要的一篇，没有之一。

需要说明的是，这篇文章中我也参考了“自由和责任”这个 PPT 中的部分素材。通过对奈飞关于公司文化的经验介绍，我希望你了解怎样定义文化，以及更重要的是怎样落地文化来提高研发效能。

定义出自己需要的文化

从公司文化的维度提高研发效能，第一步就是要定义出你需要的文化。而文化比较抽象，所以常见的定义文化的方式就是定义核心价值观。因为价值观是团队成员做事儿的潜规则，是文化的具体体现。

而定义价值观，需要根据公司的发展，确定要追求的最重要方向。一般来说，公司的价值观定义包括四个方面的追求：产品快速上线、产品的有效性、客户满意度、员工满意度。但每个公司都要根据实际情况，来定义这些目标，不能简单地复制其他公司的成功经验。

比如，美国有相当一部分互联网公司推崇创新，它们的价值观是在任何地方、任何时候都要尽量保持创新。但如果是一家 ToB 或者与金融相关的公司，那么简单地引入这样的创新文化，肯定会让你的客户不高兴，因为这样的公司往往更重视安全和低风险。

从结果来看，奈飞的价值定义精准，对公司业务成功非常有效。所以接下来，我们就一起看看奈飞是怎样定义自己价值的吧，相信他们定义价值的思路对你也会有参考价值。

1997 年，奈飞成立并进入 DVD 租赁市场。当时，市场竞争已经非常激烈，除了几个很成功的线下连锁店（比如，BlockBuster、Hollywood Video、Movie Gallery）外，沃尔玛等巨头也开始加入。在这种情况下，奈飞仔细思考，制定了追求高效能 / 绩效和创新的核心价值观。因为只有这样才能杀出重围。

基于这两条基本价值，他们细化了判断力、创新、好奇心、沟通等十条核心价值观。你可以在奈飞官网看到每一条价值观的详细解读。

你可能会说，这些价值观都很常见，并没什么稀奇的啊。确实是这样，但奈飞最牛的地方是他们做到了。

以创新为例，奈飞在刚成立时，将小众电影作为了一个突破点。因为线下连锁店通常只关注当前票房较好，以及历史上比较流行的电影，于是小众电影很难找到就成为一些电影爱好者的痛点。这是奈飞得以生存下来的一个重要战略。所以说，创新这条价值观的确帮助他们在红海中杀出了一条血路。

定义出了自己需要的价值，更重要的是落地。那到底怎样去推动这些价值落地呢？

从招聘、流程方面设计方案推动文化

仅仅提出价值观是没用的，必须认真分析，找到合适的方法落实到招聘、流程、组织结构上。接下来，我们看看奈飞是怎样操作的吧。

他们关于创新的分析思路是这样的：一个小公司，在刚创立的时候，效能和创造性都很好，属于创新者。但随着公司规模逐渐变大，复杂度也随之变大，进而导致混乱，比如信息沟通不畅导致最终产品与用户需求差别很大。为控制混乱，公司就会引入流程和规则，但副作用可能是导致一流人才的流失，以及降低现有人才的生产效率和积极性。所以，很多公司壮大后，常常会败于新的创新者发起的挑战。

那怎么解决这个问题呢？

奈飞认为，在公司规模变大时，不应该引入规则，而是应该引入更多人才来控制混乱。只要招收到优秀的、负责任的人，并给与他们自由发挥的空间，他们就能够灵活高效地解决公司在成长过程中遇到的各种复杂问题。因此，只要引入人才的速度超过复杂度的增长速度，就可以让公司在成长的同时，持续保持高效能和创新能力。这就是他们的基本思路。

基于这个思路，在 HR 方面，他们采用高薪招聘、奖励人才，而对不合适的人容忍度则很低。基本思路就是：一个员工如果称职，就用高于市场平均水平的薪酬去招聘或留住 TA；一个员工如果不称职，就尽快解聘。

在具体操作上，他们使用了很多非常直接和非常规的方法。比如，取消每年定时的绩效考评，而是在平时实时考评。绩效考评结果只有合格与不合格两种，评判依据是员工能否胜任当前工作，并不太考虑过往表现：

对于绩效考评不合格的员工，没有其他大公司常用的 PIP 计划（绩效提高计划），而是直接用丰厚的遣散费解雇；

而对于绩效考评合格的人，也不是按照每年一定的百分比提升工资，而是根据市场价进行调节。

也就是说，在绩效考评合格的前提下，不管你的表现怎样，你的薪资都会根据市场平均水平来调高或者降低，但肯定会高于市场平均水平以保证你愿意留下来。

另外，他们也没有年终奖。是不是觉得很不一样呀？反正我第一次听说这些政策时，非常震惊。

奈飞认为，通过这些举措，能够保证任何时候，只有合格的人才能留下来，并且因为高薪，他们愿意留下来；另外他们能够接受这些特别客观的评定标准，所以是理性的“成人员工”。总结来说就是，这些措施能够确保员工有能力，并且负责。

有了这些有能力并且负责的人才后，在流程方面，奈飞就可以对员工进行松散、基于信任的管理了。

比如，奈飞是硅谷很早就使用假期无限制政策的公司之一，这也很好地体现了他们的思路。

在上市之前，奈飞有 10 天带薪假、10 天节日假期，以及几天病假的一个准正式系统。说它是准正式，是因为没有正式的跟踪系统，是让员工自己统计已经使用了的假期，并告知主管什么时候他们请假了即可。这个系统本来运行得挺好，但在上市时，审计程序要求有正式的系统统计员工放假时间。

这时，常规方法是引入一个这样的正式系统。不过，CEO 里德 · 哈斯廷斯（Reed Hastings）了解到，如果员工没有假期的话，那没有这样一个假期管理系统也能通过审计。所以，为了节省精力、资金，他们干脆决定不引入正式的假期统计系统，而是由员工自己决定什么时候放假，以及休多长的假。公司只是提供一些指引，比如财务人员应尽量避免在工作特别忙的时候请假；再比如，连续请假超过 30 天，需要向 HR 说明原因等。

因为，他信任员工能基于公司利益做选择。既然绝大部分员工都能自觉，何必让他们花额外的时间来处理流程问题呢？至于极少数不自觉的员工，只能说招聘时看走眼了，尽快解雇就是。

持续地用行动去推动，并总结提高

文化是一种潜规则，行动才能真正表现出文化。所以，推动文化建设的第一点，就是坚持去做。

团队的领导者，一定要以身作则。比如，奈飞采用了灵活的无限制假期的政策，那你有没有想到可能存在的一个问题，如果团队的其他成员都没有休假，只有我休假了，会不会影响我的绩效呢？所以，奈飞要求比较高层的领导，每年必须要休一定时长的假期，并且要向团队成员公布他的假期日程，来避免这个问题。

推动文化建设的第二点，是明确提出价值观。虽然说文化是潜规则，但推广文化还是需要把这些文化强调的价值明确地提出来，并尽可能多地和员工沟通，使他们对公司的价值观更加明确。所以，奈飞一直很强调他们的文化，甚至还将其总结成 PPT 对外公布。类似的，Facebook 也很强调文化，甚至有海报文化，到处都贴满标语。在后面几篇文章中，我也会和你介绍 Facebook 关于文化的很多实践。

推动文化建设的第三点，是注意文化的演进。为了适应并跟随社会的发展变化，公司文化也不应该一成不变。所以，我们在建设公司文化时，也要时常回过头来看看，公司现在的文化和价值观是否还适合当前情况。

关于奈飞文化的思考

最后，我想再和你分享一个奈飞的小案例，希望你借由他们在推广文化上的操作和得失，可以对公司文化建设有更深刻的理解。

首先，我想说的是奈飞在文化执行上非常坚决彻底。这一点在“自由和责任”这个 PPT 的作者帕蒂 · 麦科德（Patty McCord）身上得到了很好的体现。

帕蒂是制定这些政策的核心成员，在公司文化建设上功勋卓著，却在 2012 年因为跟不上公司发展被解雇了（因为奈飞的绩效考评不考虑过往贡献）。当时，这个事件还掀起了一段热烈讨论，很多人认为她是自掘坟墓。不过我倒是认为，这只能说明她当年制定的策略被成功执行了，真正起到了作用。

正因为奈飞采用了很多非常规的，甚至是极端的方式去推行文化，所以这些年来确实做到了持续的高效能和创造性，业绩也非常亮眼。

从 2012 年 Patty 被解雇到 2018 年，奈飞的股票涨了 25 倍，用户数达到 1.37 亿，覆盖 190 个国家。可以说，他们关于自由和责任的公司文化功不可没。

但极端政策给奈飞带来成功的同时，也有很多负面作用。因为公司对低绩效员工的容忍度非常低，解雇人员的频率比较高，所以很多人认为奈飞内部存在一种恐惧文化，有些人时刻担心自己会不会突然被解雇，时常处于害怕的状态。

这种状态让员工工作得并不开心，对创造性也会有很大影响。在公司满意度调查网站 glassdoor.com 上，奈飞的满意度只有 3.7 分，而 Facebook、Google 的满意度都在 4.5 分左右。2018 年 10 月，华尔街日报发表了关于奈飞文化的文章，针对其负面影响对奈飞的政策提出了一些质疑，在互联网圈造成了不小的轰动。

其实，我个人对奈飞的文化有两点不太认同。

第一，他们超级强调透明，认为一个成熟的人可以接受任何客观的评价。比如，奈飞会对一些被解雇的员工的解雇原因进行分析，甚至出现过让他们亲自参加的情况。虽然招聘的都是“成人员工”，能够客观看待因为绩效不合格被解雇这件事，但人终究还是有感性的一面，这种极端透明的措施确实会对一部分人造成影响。

第二，太强调当前表现，而不考虑过往贡献，会降低员工的归属感，进而降低创造性。相比起来，我更喜欢 Facebook 这样的文化。关于 Facebook 的文化，我会在后续文章中与你详细分析。

小结

今天，我和你分享了什么是企业文化，以及应该怎样设计、推动文化去提高公司的效能。

文化是团队成员做事的潜规则，所以好的文化是高效能的基础。而要通过建设好的文化来提高研发效能，我认为主要有以下三步：

根据公司目标，定义核心价值观；

围绕核心价值观，制定 HR、流程方面的方案；

用行动推动价值观实施，明确提出价值观来统一团队认识，注意文化的演进来保证公司文化与时俱进。

在介绍建设公司文化的三步时，我与你介绍了奈飞的很多实践，希望可以帮助你找到适合自己的方式、方法。

接下来的几篇文章，我会与你讨论我最熟悉的 Facebook 的企业文化，并分享我在其他公司引入文化的一些实践。敬请期待！

思考题

你知道还有哪个公司像奈飞这样推崇透明的文化，效果又怎样呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

33 | Facebook企业文化：工程师文化是创造力引擎

你好，我是葛俊。今天，我来和你聊聊 Facebook 的工程师文化吧。

在上一篇文章中，我以奈飞为例，与你介绍了公司文化建设的三部曲，即定义核心价值观、针对性地设计招聘和流程方案，以及持续推动并总结提高。

除了奈飞的“自由与责任”外，我们还比较熟悉、认可的是“Facebook 的工程师文化”。所以，接下来的 4 篇文章，我会与你详细介绍 Facebook 的工程师文化、相关的管理实践，以及我离开 Facebook 后在其他公司落地的实战经验。

那，我为什么要用这么多篇幅去分析 Facebook 的工程师文化，以及支撑这些文化的管理实践呢？原因很简单，因为我清楚地看到，工程师文化，可以极大地激发开发人员的内驱力和创造性。

毫无疑问，这是一个双赢的局面：既可以为公司产生更多更有价值的产品，也可以大幅提升开发者个人的成长速度和幸福感。但相比之下，国内的很多公司在这方面有所欠缺，团队成员大都处于简单执行的状态，内驱力和创造性远没有激发出来。

所以，通过接下来的 4 篇文章，我希望帮你深入理解工程师文化的 Why、How 和 What，为管理者设计、实现团队文化提供示范和借鉴，为个人参与文化建设和选择团队提供参考，进而提升团队的研发效能，以及个人的能力和幸福感！

我们先来看看工程师文化是什么吧。

Facebook 的工程师文化，到底是什么？

通过上一篇文章，我们知道“文化是决定一群人行为方式的共同认知、价值观和信念”。那，什么是工程师文化呢？

工程师文化本意是，工程师群体行为方式的共同认知、价值观和信念。对工程师来说，我们的行为包括开发软件、解决问题、与客户打交道、与团队合作等。本来工程师团队的做事方式可能千差万别，但后来“工程师文化”逐渐演化成了“用正确的工程方法、思路来完成工作的文化”。

那，Facebook 的工程师文化又是什么呢？一句话总结就是“黑客之道（The Hacker Way）”。

听到这里，你可能会说，黑客不是一个贬义词吗？事实上，黑客在程序员界本是一个褒义词，指的是动手能力极强的程序员，他们创造、实验、打破常规，可以让计算机做任何他想做的事儿，即使计算机本身不同意。

但现在，黑客却和入侵电脑系统为非作歹的罪犯画上了等号。这个误解极深，以至于业界大神保罗 · 格雷厄姆（Paul Graham）专门写了一篇文章“The Word ‘Hacker’”来澄清。

那，为什么 Facebook 要把“黑客之道”作为自己的企业文化呢？

Facebook 的企业使命在 2017 年 7 月有过一次演化，不过总的来说，就是提高传播和消费信息的方式，把世界更好地连接起来。这样的社交网络系统规模庞大，有着很多前所未有的分布式、高并发等挑战。同时，也因为第一次把社交映射到网路上，有无限的创新可能。所以建造这样的系统需要优秀人才不断创新，打破常规。

扎克伯格和 Facebook 的其他管理层认为，黑客做事的方式，很适合公司快速发展，不断进步、创新，能支撑公司实现使命。所以，Facebook 一直把黑客之道设置为最根本的工程师文化，并且在全公司推广。

2012 年 Facebook 上市时，扎克伯格发表公开信，给出了黑客的 4 个特质。按照我的理解，我再和你分享下这 4 个特质。

优化无止境：黑客认为优化无止境，产品无完美。在大部分人认为服务已经无法改善，或者是满足现状的时候，会直接上手进行进一步的优化。

持续进步：黑客不追求一蹴而就，一劳永逸。他们更喜欢迅速发布小规模更新，并从中吸取经验教训，通过长久努力不断进步。

代码为王：黑客更在意代码实现。代码胜于雄辩，只有真正实现了的东西才有价值。

能力为王：黑客有极度开放和精英为王的心态，不会因为谁的头衔高而默认其权威。相比之下，黑客更看重的是技术，以及把想法变为产品的能力，说白了就是谁行谁上。

针对这些特质和企业使命，Facebook 又总结了五条核心价值观，分别是：专注于影响力（Focus on Impact）、迅速行动（Move Fast）、勇往直前（Be Bold）、保持开放（Be Open）和创造社会价值（Build Social Value）。

接下来，我们就具体看看 Facebook 是怎样推行黑客文化的吧。

Facebook 工程师文化的具体实践

与奈飞推动企业文化建设一致，Facebook 也采取了一系列措施有效地推行其工程师文化落地，包括重视工程师文化的宣传，以及从流程、工具方面推动文化落地。接下来，我们就具体看看吧。

第一，非常重视工程师文化的宣传。

Facebook 一直有一个很大的“The Hacker Company”的标牌，几次搬家都被放到最显眼的地方。扎克伯格在上市的公开信中也详细讨论了公司的工程师文化。

此外，他们的海报文化也非常有名。进入园区，你会发现 Facebook 的一大特色，有很多写着各种各样标语的海报，比如“The Journey is 1% Done（我们的旅程只完成了 1%）”“Open the Door（敞开大门）”“Done is Better Than Perfect（完成比完美更重要）”“Focus on Impact（专注于影响力）”“Move Fast and Break Things（快速行动，打破陈规）”等。我最喜欢的是一句中文粤语的，“出来行，最紧要勇”，是勇往直前的意思。

很明显，这些标语与 Facebook 的工程师文化和核心价值观紧密相关。Facebook 的 COO 雪莉 · 桑德伯格（Sheryl Sandberg）在一次访谈中谈到这些海报时说，公司的文化正体现在这些海报中。

虽然这些标语看似在打鸡血，但在 Facebook 工作过后你就会理解，公司的确是在践行这些工程师文化，而且为公司发展和个人成长带来了巨大好处。所以，这些标语很多都是开发者自己打印了贴到墙上的。

第二，从流程、工具方面推动这些文化。

关于如何从流程、工具等维度推动工程师文化落地，我会边和你列举 Facebook 的具体实践，边与你分析这与公司文化落地的关系。

接下来，我们就具体看几个关于黑客特质以及公司价值观的实践吧。

在能力为王方面，扎克伯格没有独立办公室，工位和我的一样普通。当然，他有一个私人会议室。另外，员工互相看不到对方的职级，所以讨论技术问题时，不会因为级别而束手束脚。

在持续进步方面，公司搭建了强大的实验框架、功能开关，无论何时都可以测试上千个不同版本的 Facebook 服务，给开发人员创造能够迅速行动、快速迭代的环境。另外，公司的容错文化落到了实处。只要不是故意的，首次犯错都不会影响绩效。当然同样类型的错误重犯会有惩罚措施，另外有一些隐私、安全相关的红线也不能触碰。

在代码为王方面，所有新员工入职时都被要求参加一个长达 6 周的培训，叫 Bootcamp。这 6 周的时间，无论你是多高级别的管理者，都要学习公司的代码库、工具和方法，并实际编码完成任务。业内有许多工程师团队的管理者，并不愿亲自动手编写代码，这在 Facebook 是行不通的。因为，Facebook 更看重的是实践型人才。

在专注影响力方面，Facebook 有一个定期举办的 Hackathon（黑客马拉松）活动，让我们依照自己的创意开发原型产品，然后在公司范围内进行演示。这，就给了我们一个发挥想象力和能力来产生价值的机会。Facebook 最成功的产品有些就来自 Hackathon，包括时间线 (Timeline)、聊天、视频、点赞按钮等。

这样的流程、实践体现在公司的各个方面，不一而足。我在这里只是简单列举了些具体实践，详细、系统的分析会在后面的三篇文章中。

那么，这些文化的效果怎样呢？

从公司的长期快速发展和业界口碑来看，这些文化的效果显而易见，我就不再赘述了。这里，我就站在一名普通开发者的角度，来和你聊聊 Facebook 工程师文化对个人成长的效果吧。

Facebook 工程师文化的效果

Facebook 的工程师文化鼓励优化无止境、持续进步、代码为王、能力为王，也着实体现在工作的方方面面。这些都是强大的技术人员的重要特质，对个人技术成长和创造产品帮助非常大。而在 Facebook 工作几年后，你的做事方式自然而然会往这些方向靠拢，可以说是润物细无声。

比如，Move Fast and Break Things，是 Facebook 最有名的一句文化口号，也是对我影响最大的一句。

2010 年，我离开微软加入 Facebook 时，比较习惯瀑布式开发，非常谨慎，体会不到这句口号的精髓。入职后大概两个月，我从 Bootcamp 毕业加入了 Phabricator 团队。当时，有一个任务是查看 MySQL 数据库中开发人员的信息，我希望找到只读模式，来保证不会把数据库搞坏。

但因为我对公司的 MySQL 命令行工具不是很熟悉，就去问别人只读选项在哪里。他对我的问题很惊讶，了解到我的担忧后，他说“你小心一点不就行了吗”。可我还是不放心，自己花时间找到了只读模式的命令。但事实证明，在 Facebook 四年多的时间，我从没有误操作过 MySQL，花在寻找只读命令上的时间，属于浪费。

这件事儿对我的职业成长影响很大。我逐渐体会到 Move Fast and Break Things 的一层意思就是，要尽量快地去做事儿，不要担心搞砸，跟“Fail Harder”类似。后来，我愈发适应这种方式，对公司的贡献也大了很多。

比如，我在负责 Phabricator 时，带领两个人共同支撑了公司所有开发人员的代码审查、代码浏览工具，以及 Phabricator 和公司其他工具的集成、与开源社区的合作。说实话，我以前从没有接触过类似的工作内容，正是“Move Fast and Break Things”这种工作方式，让我能够快速行动、快速迭代、持续学习，最终顺利完成各项任务。

当时，我们三个人就支撑起了几千名开发者使用的一个核心开发工具，其中涉及了开发、测试、运维，效率之高可见一斑。这，正是工程师文化为我们带来的个人能力成长，和整个公司研发效能的提高。

小结

Facebook 的工程师文化，简单来说就是黑客之道，指的是能够打破常规，突破界限，完成任务的做事方式，其特质可以概括为优化无止境、持续进步、代码为王、能力为王。

正是这些特质，帮助 Facebook 迅速发展业务、实现创新，从而达成公司愿景。所以，Facebook 一直在文化宣传、流程和工具等方面推动其落地，效果确实非常好，实现了公司成功和个人成长的双赢。

我很感激在 Facebook 的这段经历，不断突破极限的黑客精神对我影响非常大。

在工作和生活中，我们都会面对一套现有的规则，告诉我们要做什么不要做什么，并认为我们感知到的这套规则就是事实真相。但其实，这套规则背后隐藏的另一套规则才是真正的规则。

认识到这一点，我们就能突破现有规则，以非常规的方式去完成一件事儿，也就是我们所说的创造奇迹。而黑客精神，正是突破感知规则，寻找真实规则的精神。

思考题

在 MySQL 的案例中，“要尽量快地去做事儿，不要担心搞砸”只是 Move Fast and Break Things 的一种理解。你知道这句话更常见的一种理解是什么吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

34 | Facebook工程师文化实践三大支柱之一做感兴趣的事

你好，我是葛俊。今天，我来和你继续聊 Facebook 的工程师文化及相关管理实践。

在上一篇文章中，我与你详细介绍了 Facebook 工程师文化的核心：黑客之道，并给出了一些落地实践。从这些实践中，我们可以体会到，这种工程师文化推行得非常成功，已经融入了 Facebook 的血液中。

也正因为如此，Facebook 的工程师文化大大激发了员工的创造性、生产力和凝聚力，对整体的研发高效能和良好的个人成长环境起到了关键作用。

与工程实践等方法论一样，知道好的实践还远远不够，我们更需要弄清楚的是，这些实践背后的原则，才能把它应用到自己的公司和团队。

所以，在接下来的三篇文章中，我会与你系统分析 Facebook 是如何真正落地工程师文化的。同时，我会结合我在其他公司、团队推行工程师文化的实战经验，与你分析落地工程师文化的切入点，以及需要注意的问题。

我希望这样的讲述思路，能够帮你更深入地理解这些实践和原则，将工程师文化更顺畅地引入自己的团队，提高研发效能。

Facebook 工程师文化落地的三大支柱

在讨论 Facebook 工程师文化实践落地的 how 之前，我们先看看 why 吧。

我们首先需要明确的是：归根结底，推行工程师文化的目的是，提高开发人员的积极性和创造性，从而更高效地进行软件生产，在竞争中取胜。而要最大化地激发开发人员的自我驱动能力，根本出发点是，开发者这个群体的特性。

每一名开发者首先都是知识工作者。管理学大师彼得 · 德鲁克（Peter Drucker）在《卓有成效的管理者》中说：“我们无法对知识工作者进行严密和细致的督导，我们只能协助他们。知识工作者本人必须自己管理自己，自觉地完成任务，自觉地做出贡献，自觉地追求工作效益。”从这个角度来看，每一个知识工作者，都是管理者。

也就是说，要提高开发者的效率，强管理不是办法，根本之道在于激发他们的内驱力。正因为如此，有效激励员工的积极性，正是硅谷高效能公司文化的共同点。

那，怎样才能有效激发开发者的积极性呢？在我看来，Facebook 主要关注以下三个原则：

让员工做感兴趣的事；

让员工拥有信息和权限；

用绩效调节员工和公司方向的一致性。

员工能做自己喜欢的事儿，并有施展拳脚的空间，自然能最大限度地激发积极性；再加上绩效的调节，能够让团队成员的积极性与公司利益更有效的对齐，从而最大化员工积极性带来的成果。

所以说，这三点就是 Facebook 工程师文化落地的三大支柱。

接下来，我们就先看看第一个支柱，让员工做感兴趣的事儿的一些具体实践吧。

让员工做感兴趣的事

我首先要说明的是，这里的感兴趣，不只是狭义地觉得任务有意思，而是开发者对工作的整体状况感兴趣，包括项目前景、技术栈、挑战性、团队成员等。

有研究证明，如果任务有意思、有挑战，同时又是加以努力就能完成的，员工的 engagement 和满意度就会非常高。那么，让团队成员灵活地选择岗位和转岗，就成了一个绕不开的话题。

但，团队成员灵活选择岗位和转岗，在短期内会直接给公司，尤其是小团队带来负面影响。另外，如果公司比较小的话，每个人都很关键，自由转岗更难实现。

接下来，我们就通过几个实践，看看 Facebook 是如何用好这把双刃剑的吧。Facebook 让员工做感兴趣的事，包括了入职、日常工作和转岗这 3 个场景。

其实，这 3 个实践，也并不是在 Facebook 成立之初就都有的，而是在不同的发展时期引入的。

第一个场景：入职时

在 Facebook，新员工入职时都要参加 Bootcamp，一方面可以帮助员工熟悉公司产品、研发流程和工具，另一方面可以帮助员工灵活选择岗位。

Bootcamp 的具体操作是：几乎所有的工程师在入职时，都没有明确会到哪一个团队工作。进入公司的前六周，新员工会先到一个特殊的区域办公，并从任务池中挑选任务工作。

这个任务池中的任务，是公司各个产品团队放进来的，一般是难度不太大、优先级不太高，但又能让新员工快速了解业务的任务。新员工通过完成任务来了解产品和流程，并通过沟通了解这个产品团队的成员。

在 Bootcamp 结束时，新员工和感兴趣的团队主管沟通，决定最后加入哪个团队，是一个双向选择的过程。不过，因为公司业务一直在扩张，很多团队都缺人，而且这些新员工的素质普遍不错，所以一般都是卖方市场，绝大部分新员工都能进入自己感兴趣的团队。

让新员工选择并进入自己感兴趣的团队，可以极大提高其积极性，还可以在员工间建立关系网，提高凝聚力，长期收益非常可观。但，这个实践的弊端是，有些团队可能一直招不到合适的成员，在短期内影响产品进度。

所以说，这个实践比较适合有一定规模并发展较快的公司，因为新员工多容易调节些。Facebook 也是在 2008 年员工数接近 1000 时，才开始实施的。如果你的公司也具备这些特点，可以考虑尝试 Bootcamp。

如果你想了解 Bootcamp 更多细节的话，可以参考这篇文章。

第二个场景：日常工作

在日常工作中，提高员工工作兴趣的一个实践是 Hackathon。

Hackathon 有一个有趣的原则是，鼓励大家尽量不要做与日常工作直接相关的项目，而是做一些感兴趣的项目。所以，我们在做 Hackathon 的时候，都是把它当作一个好玩的事儿，而不是当成任务来完成。

Hackathon 在开始的时候，还有一些好玩儿的仪式。组织者和参与者会敲锣打鼓，在公司里做一个游行，才正式宣布 Hackathon 开始。另外，Hackathon 一般会在公司的餐厅进行。餐厅会被摆放些桌椅、提供些小零食，被整理得很舒适，适合三四个人的小团队讨论问题。

值得一提的是，Hackathon 并不会明确要求做出可落地的产品，但有趣的是，大家感兴趣的项目往往会和工作相关，所以实际上产生了很多很有价值的项目。在上一篇文章中，我也提到了，Facebook 最成功的产品中有些就是来自 Hackathon，除了时间线（Timeline）、聊天、视频、点赞按钮外，更典型的是 Image Tagging 和 the Like Button(大拇指)。

总结来说，Hackathon 没有强制的业务目标，做不出东西也没关系，所以大家都很放松，抱着创造一个新东西的心情去做事，很爽。

在落地实践方面，Hackathon 不涉及转岗，所以比较容易实施。也正因为如此，国内不少公司都尝试过这条实践，也取得了不少成果。但需要注意的是，在实施时要注意些细节，否则会事倍功半。

第三个场景：转岗

在转岗上，Facebook 采用的是 Hackamonth，即离开当前工作岗位，去另一个团队全职工作一个月。每个员工在一个团队工作满一年后，都可以参加这个活动。

跟 Bootcamp 类似，Hackamonth 也有一个任务池。每个团队都会把一些一个月左右即可完成的、独立的项目，放入这个任务池里。有兴趣参加 Hackamonth 的员工，在这个池子中寻找感兴趣的任务，并跟新团队负责人确认之后，就可以和自己的主管提出参加 Hackamonth 的要求。

接下来，你需要和主管沟通做好安排，以确保这一个月，原工作岗位上的任务有人处理。安排妥当后，你就可以进入新团队，并直接到他们的办公区工作了。

Hackamonth 结束后，你可以决定加入新团队还是留在原来的团队，当然前提是你想加入的团队愿意接收你。这实际上是一个通过做项目的方式来让员工和他感兴趣的团队互相了解并双向选择的过程。

另外，Hackamonth 还提供了更进一步的灵活性。在做 Hackamonth 的时候，如果你觉得这个团队不是很满意，你也可以私下接触其他团队。如果双方觉得合适的话，等 Hackamonth 结束后，你可以选择加入这个团队。

Hackamonth 对提高开发人员的工作兴趣，有两大好处：

一是，开发人员可以比较自由地转岗，而且大概率能够找到感兴趣的项目；

二是，即使最终没有转岗，也可以通过这一个月的工作转变，减少 Burnout 的感觉。

我有一个同事在 Facebook 呆了五年，参加了三次 Hackamonth，但每次都回到原来的组。用他的话说就是，去度一个月的假。是不是很有意思呢？

但采用 Hackamonth 的弊端是，会直接影响原团队的工作进度，更容易对公司和团队造成短期伤害。与 Bootcamp 相比，Hackamonth 更不适合小公司。Facebook 是在 2011 年员工数达到几千时，才开始采用 Hackamonth 政策的。

在 Facebook，只要你在一个团队工作满一年了，即使团队主管不情愿，也没有权利阻止你去参加 Hackamonth。可见，Facebook 在执行 Hackamonth 政策上，再次果断选择了长期利益。

以上就是 Facebook 为达成让员工做感兴趣的事这个目标，在入职时、日常工作和转岗这三个场景下的 3 个重要实践。

接下来，我再和你分享些我在国内一家公司落地 Hackthon 的具体经验吧。

Hackathon 落地经验

当时，我所在团队的执行力非常强，但员工的自我驱动不够。考虑到对公司和团队的影响，我权衡之后决定引入 Hackathon。

在引入 Hackathon 时，我采用了和 Facebook 类似的方式：举行时间为周五和周六，下周一下午在团队内部进行演示和评选。

这样一来，只会占用一天多一点的工作时间，大家还可以选择周日继续进行。通常情况下，大家虽不会通宵，但也会做到比较晚，并且周日还会持续。

在这之前，公司其他团队也尝试过 Hackathon，但效果都不太好。所以，这次的 Hackathon，我制定了两大规则：

明确要求，Hackathon 的目的是 Have Fun，不追求产品落地，尽量不做与工作直接相关的任务。

评选结果时，由团队成员投票决定优胜者，而不是由专家或者管理层评选。

对这两个原则，我的主管和人事部门是有些怀疑的，但在我的坚持下还是执行了下来。

这次 Hackathon 下来，效果出奇得好：

大家热情高涨，80 人左右的开发团队基本可以产生 12 个项目；

虽然没有要求项目落地，但还是产生了很多有意思、有价值、可落地的项目。

比如，一次 Hackathon 时，3 个同事做了一个卫生间管理系统：登录网页即可查看办公楼的卫生间占用情况。这个项目，既解决了大家的痛点，又很有意思，所以高票当选第 1 名。

再比如，一次 Hackathon 时，几个运维同事做了一个 ChatOps 的项目。他们开发了一个聊天机器人放到聊天室中，在产品发布上线时，实时、自动发布部署进展，开发人员还可以直接询问聊天机器人当前的部署状态。这个项目，很大程度上解决了部署上线时团队的沟通不畅问题，所以成功落地到实际工作中。

这种形式的 Hackathon，在结束后仍对团队有正向影响：一是，团队氛围更活跃、成员间衔接更紧密了；二是，大家对技术更感兴趣，也更愿意讨论用技术实现各种项目的可能性了。

总结来讲，几乎所有包含开发人员的团队都可以尝试 Hackathon，但为了保证效果，需要注意些细节，尤其是上面我提到的两个规则。

小结

今天，我首先和你介绍了 Facebook 工程师文化落地的三大支柱：让员工做感兴趣的事、让员工拥有信息和权限，以及用绩效调节员工和公司方向的一致性。

然后，在让员工做感兴趣的事方面，我从入职、日常工作和转岗三个场景，和你分享了 Facebook 的 Bootcamp、Hackathon、Hackamonth 这三个实践。这些实践，都对提高员工内驱力有非常关键的作用。

在这三个实践中，Bootcamp 和 Hackamonth 比较适合规模较大（千人左右或者以上）且发展较快的公司。如果你的公司符合这样的特点，可以考虑落地这两个实践，并根据实际情况做一些调整。比如，引入 Bootcamp 后，如果有一些冷门团队新员工都不愿意去，怎么办呢？一个可能的办法是，对这些职位进行定向招聘，专门寻找感兴趣的员工。

而如果你的公司不具备这些特点的话，可以尝试 Hackathon 这个轻量级的实践。Hackathon 花费的时间不多，但执行合适的话，效果会非常好。

除此之外，作为技术管理者，我的建议是，在安排日常工作时可以多考虑团队成员的意向，尽量在条件允许的情况下，提高员工兴趣和任务的吻合度。

思考题

在你的公司或者团队，有什么措施让员工尽量做自己感兴趣的事吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

35 | Facebook工程师文化实践三大支柱之二拥有信息和权限

你好，我是葛俊。今天，我来和你聊聊，Facebook 工程师文化的第二大支柱：拥有信息和权限。

在上一篇文章中，我与你介绍了 Facebook 通过在入职、日常工作、转岗中的一些实践，来提高开发人员对所做工作的兴趣。有了兴趣之后，接下来就是提供空间让大家能够施展拳脚，在自己感兴趣的工作中自由发挥、充分发挥，也就是 Facebook 工程师文化的第二大支柱了。具体来说，第二个支柱包括让员工拥有信息和拥有权限两个子原则。

在我看来，Facebook 在这两个原则上的实践，对国内公司和团队有很大的参考价值。为什么这么说呢？因为我接触到的国内公司管理都相对保守，可以从与 Facebook 的具体实践对比中汲取经验，提升公司整体的研发效能。

在拥有信息方面，国内公司一般过于严格。信息的不透明，会降低员工的主人翁意识，并会因为信息不通畅导致工作效率和有效性的下降。所以，在我看来，国内公司还可以在信息方面做得更透明些。

在权限和信任方面，我接触到的国内公司，大都偏向于不信任的管理方法。这种方法难以让员工有主人翁的感觉，导致公司和员工互不信任。在这种情况下，又怎么期望员工能积极地为公司创造价值呢？

我始终认为，国内的开发人员和硅谷的开发人员一样，都有很强的创造性，能力也没什么大的差别；只要能给他们创造好的环境，提供信息并给予信任，就可以更好地激发他们的内驱力，从而提高公司和团队的研发效能。

接下来，我们就具体看看 Facebook 是怎么做的吧。

让员工拥有信息

作为知识工作者，拥有信息，是我们能够做出正确判断的一个必要条件。但信息的开放具有两面性，如果暴露过多的敏感信息，势必会对公司造成些负面影响。

面对这样一把双刃剑，Facebook 的态度是考虑风险，但并不是一味地避免风险，而是权衡公开信息的利弊，在确保基本安全的前提下尽量实现信息透明化。

接下来，我们一起看三个具体的案例吧。

案例 1：代码的共享

在 Facebook，几乎所有的代码都是全员共享，开发人员可以非常方便地查看这些代码。这，跟国内很多公司的情况差别非常大。

我在和很多国内公司的管理层谈到 Phabricator 时，对方的第一个问题通常就是如何管控代码仓的权限。比如，如何让前端开发人员看不到后端的代码，如何让后端开发人员看不到算法的代码，等等。

但其实，代码的共享可以在开发、调测时节省很多沟通成本，大幅提高效率。比如，某个前端开发人员发现后台 API 返回值不符合预期，就可以自己去查看对应的代码，即使他没能自己找到问题，也可以在和后端开发人员沟通时提供有价值的信息。

但如果他看不到后端代码的话，就只能直接求助于后端开发人员，一方面会浪费后端开发人员的时间（有可能这个问题前端人员就可以解决），另一方面双方的沟通效率也会因为信息不对称而大打折扣。

国内公司大多对代码权限控制得非常严格，这跟国内的 IP 保护不力有直接关系。不过在我看来，国内的代码管理还是太过严格，甚至有些不分青红皂白就对所有代码进行严格控制的情况。这，不但会降低开发人员的工作效率，还会降低团队之间的信任。

所以综合考虑，我们应该在可能的范围内，尽量放宽对代码的管控，对于那些即使泄露出去也没有太大关系的代码，要放松管控。这样做风险不大，但收益却可能会很大。

案例 2：看板（Dashboard）

在 Facebook，很多信息都会展示在公司墙上的显示屏上，包括很多业务的实时监控数据，甚至还有少量在其他公司认为比较敏感而不愿意显示给员工的信息，比如某些敏感服务的实时数据。此外，很多团队还会制作自己团队的看板去显示需要关注的信息。

一些敏感数据泄露出去，的确会给公司造成些许负面影响，而且确实曾经出现过信息泄露。所以，有员工就问扎克伯格，为什么要暴露这些信息。

扎克伯格的回答是：这些信息泄露确实会造成少量负面的影响，但这些信息可以极大地方便开发人员的日常工作，能够更好给用户提供价值，比如看板上的某一条信息可能在你做某个决策时起到关键作用，所以权衡利弊，我们决定开放这些信息。

这个回答，恰恰佐证了公司认同信息对激发开发人员研发效能的巨大作用。

关于信息开放，在国内比较容易落地的部分是，把可以显示的信息用看板显示出来。比如，线上服务使用情况可以拉近员工与用户的距离，让员工更直观地感受到业务的价值，从而提高工作积极性；又比如，当次发布进展的相关数据，可以让开发人员对当前进展更有掌控感，对交付更有紧迫感。

我们在落地看板展示时，需要注意两个问题：

一是，不要做给外人看，而是应该用来激励内部员工；

二是，不要展示个人负面排名，比如 Bug 数，这种公开的负向激励方式，容易降低团队成员的积极性。

案例 3：使用 Wiki 来记录信息

Facebook 在公司内部大量使用 Wiki 来记录信息，很多对流程要求不严格的信息都用 Wiki 来记录，包括部分设计文档、团队成员列表、新员工入职手册、个人笔记，甚至有些团队的 OKR 也在上面。

这样一来，我们在日常工作中遇到问题时，首先就是到内部 Wiki 上查找，而且一般都能找到有用的信息。这就使得绝大部分员工愿意在 Wiki 上添加信息，包括一些笔记、心得等。于是，形成了一个良性循环，Wiki 系统的内容越来越丰富，对开发人员的帮助越来越大。

但，我见到的很多国内公司，很多信息都是口口相传，或者是藏到某些文档里，需要花费大量的时间在查找信息上。

在我看来，在公司内部用内部公开的 Wiki 来记录信息，风险比较小，而且可以大幅提升效能，所以比较容易落地。作为管理者，我们可以考虑建立一个全公司的 Wiki，并采取措施鼓励团队进行知识方面的共享。

这里的一个技巧是，把权限设置为默认公开，对需要进行权限控制的内容再特殊处理收紧权限。这跟默认保密，有需要再特殊处理放开权限的操作相比，看似相差不大，却是在传递支持信息开放的基本态度，效果非常不错。你也可以试试这个方法。

让员工拥有权限

在能做感兴趣的事，又拥有了信息之后，最后一条就是让员工能够拥有权限去做事儿。这里，我也与你分享三个具体的案例吧。

案例 1：对于商业软件，先购买再获取授权

Facebook 对商业软件的态度是，相信每个开发人员会从公司利益出发，权衡一个软件是否需要购买，如果需要可以先斩后奏。具体的案例，你可以回顾下第 1 篇文章中的相关内容。

其实，国内有些公司也已经采用类似方式了，员工可以在一定数额下自行决定是否购买。我在跟这些公司的员工聊到这个政策时，能够明显感觉到他们的开心和对公司的认同感。

一般情况下，员工不会擅自做离谱的决定去购买某一款软件，所以这个操作比较安全，你也可以试试。

案例 2：鼓励代码上的互相贡献

在 Facebook，除了几乎所有的代码对全员公开外，公司还鼓励开发人员互相修改和提高对方的代码。比如，前端开发人员发现后端 API 返回结果不符合预期时，如果他能识别出后端代码的问题，便可以去直接去修复，然后提交一个 PR 给后端开发人员，对方接受之后即可入库。

又比如，你发现某一个产品的某一个功能有 Bug，也可以直接修改，只要通过 PR 和测试，就可以上线。

这在内部工具方面尤其明显，也是 Facebook 的内部工具超级强大的一个重要原因。因为内部工具代码上线的风险比较小，所以很多人在看到工具的问题时，会上手去进行提高，甚至自己开发一些新工具。

一个具体的例子是，Phabricator 在开源之前，很多功能和改进都是非内部工具团队的开发者贡献的（你可以再回顾下第 15 篇文章中的相关内容）。

如果你要落地这个实践的话，我有两个建议：

一是，先从风险小的代码试点，鼓励互相贡献；

二是，对这种非本职却对公司有贡献的工作，应该体现在绩效考评中。

案例 3：提供宽松的容错环境

Facebook 对待事故的态度是，更关注吸取经验教训，以及将来如何预防，而不是追责。这样的容错环境，能够让开发人员放开手脚，最大限度地释放自己的创造力和潜能，保证公司的持续快速发展。

在第 33 篇文章中，我提到在持续进步方面，Facebook 提供的宽松容错环境，只要不是故意犯错或者重复犯相同的错，一般不会被追责。Move Fast and Break Things，就是一个有力的证明。

我刚进入 Facebook 时，曾见过一个事故：有一个开发人员错误操作，把公司最大的 SVN 代码仓给搞挂了；公司找来两个 SVN 专家，花了一天半的时间才把整个代码仓恢复。这给公司造成了很大损失，但这个开发人员并没有因此被裁掉。还有些人因为误操作造成线上事故，影响了 Facebook 网站的功能，但只要不是故意犯错或者重复相同的错，也基本不会被追责。

如果你想落地宽松的容错环境这个实践的话，我推荐你尝试 Facebook 的 SEV 复盘。我在国内公司实践过 SEV，效果不错。

你可以再回顾下第 4和第 20篇文章中关于 SEV 的相关内容，以及出现问题之后，到底应不应该追责、应该怎样追责的问题。

“软件先购买再获取授权”“鼓励代码上的互相贡献”，以及“容错文化”三个实践，我都在国内一个 50 开发人员左右的创业公司引入过，效果都还不错。引入时，注意些上面提到的问题就可以了。

Facebook 之外的落地经验

接下来，我再与你分享两个，我在其他公司采取的让团队成员拥有信息和权限的实践吧。

案例 1：促进信息上下流通的实践

在 Facebook，每周五都会举办一个内部的 Q&A，员工可以通过现场或者线上提问的方式，问扎克伯格以及高管们任何问题。扎克伯格和其他高管都非常坦诚，尽量把可以公开的信息都坦诚相告，对公司信息上下流通的效果非常好。

离开 Facebook 之后，我在一个团队内，采用类似 Facebook 的方式进行了 Q&A，使用尽量坦诚的、开放的问答形式，不说套话。这个团队的特点是，执行力很强，但氛围有些沉闷。在坚持了两三次 Q&A 之后，气氛逐渐活跃，大家逐步敢问一些敏感的问题了。

事实证明，开发人员的思维非常活跃，也都很喜欢用活跃的方式进行沟通，以获取到更多的信息。这样做的好处是，加强了员工对公司、对团队的主人翁的精神，进而提高整体的研发效能。

落实 Q&A 还有一个小窍门是，大家可以用匿名写纸条的方式去提问。这个方法，在国内效果更好，很多人更愿意用这种方式去问一些敏感的问题。

案例 2：信息共享的相关实践

这个实践是，我在一个创业公司，采用 Wiki 的方式来推动全公司范围内的信息共享，内容权限默认设置为公开，只有特殊情况才可以设置为只对某些人公开。

落地这个实践，的确是一个长期的过程。我见到，国内很多开发人员，都不太习惯做分享，所以我推行 Wiki 时的两个方法，是坚持和以身作则。

在推广 Wiki 的第一个月，90% 的新内容都是我写的！后来，大家逐渐看到了这种信息共享的好处，也看到了我的坚持，就开始主动向 Wiki 中增加内容。半年后，我的贡献降到了 10%。

小结

今天，我与你分享了 Facebook 工程师文化落地实践的第二大原则，让员工拥有信息和权限。

因为软件开发，是知识性工作，所以拥有信息是高效开发的前提。Facebook 在这方面的实践，包括代码共享、看板和公司范围内公开的 Wiki 等。

而让员工拥有权限和信任，可以让员工以主人翁的感受去施展拳脚，最大限度地激发其内驱力。这方面，Facebook 的实践包括，信任员工进行一定额度下的软件自行采购、鼓励互相贡献代码，以及建设宽松的容错环境等。

从我个人在国内推行文化的实践经验来看，国内大多数软件公司，是适合让员工拥有更多的信息和权限的。而且我也看到，国内越来越多的公司意识到了这一点，在逐渐放开信息和权限，从而提高员工的内驱力和团队的研发效能。

我相信，我们会逐步找到更合适自己环境的平衡点，打造高研发效能的公司。

思考题

信息开放的确会导致信息泄露。今年 10 月份，Facebook 出现了一次 Q&A 被录音并泄露的事件，你知道扎克伯格是怎么处理的吗？你还能想到更好的处理方式吗？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

36 | Facebook工程师文化实践三大支柱之三绩效调节

你好，我是葛俊。今天，我来和你聊聊，Facebook 工程师文化的第三大支柱：绩效调节。

在前面两篇文章中，我与你详细介绍了 Facebook 如何通过让员工做感兴趣的事，以及让员工拥有信息和权限，来很好地激发员工的内驱力。但归根结底，工程师文化是为公司的业务发展而服务的。

那么，当员工的工作积极性被充分调动起来之后，Facebook 是怎样让员工的工作方向与公司的发展方向对齐的呢？这把利器就是绩效调节。今天，我们就具体看看 Facebook 是如何做绩效调节的吧。

绩效管理是一个很大的话题，其中 Facebook 帮助员工调节方向的管理方法主要包括两个：

一个持续反馈的方式，也就是说主管要随时给员工反馈，帮助其调节工作的方向和方式。

一套 360 度绩效考评系统进行每年两次的总结性绩效考评。

关于持续反馈，一般是在一对一（1-on-1）沟通中给反馈。一对一沟通是硅谷软件公司常见的一种管理沟通方法，一般每周一次每次半小时，固定时间，由主管和下级，或者和下级的下级做一对一沟通。

这个会议的主要作用是，从下往上的信息收集和从上往下的信息传递，是与员工沟通近期工作中值得肯定或需要调整内容的一个非常好的渠道。如果你想详细了解一对一沟通，可以参考“浅析一对一沟通”这篇文章。

比如，我在第 13 篇文章中提到，开始时我在代码审查上做得不太好，主管就及时在一对一沟通中指了出来，让我能够迅速调整方向。

而 360 度绩效考评系统，对帮助员工对齐方向起到了关键作用。今天这篇文章，我就与你具体说说它的原则、问题以及落地实践吧。

360 度绩效考评系统的核心是，通过收集同事间的反馈，来评价员工对公司的贡献。整个过程尽量关注公司的整体利益而不是小团队的局部利益，尽量做到收集客观具体的实例来评估贡献，而不是简单收集客观数字。整体来讲，这个方法可以比较客观、公正地评价员工对公司的贡献。

相比起来，国内的绩效管理政策，真正使用同事间反馈的很少，更多的是主管的评价。这种方式的好处是，容易产生强执行力，但缺点是容易出现维上、内斗的情况，从而引发较多内耗。

所以，通过介绍 Facebook 这一套不太一样的绩效管理系统，我希望能给你提供一个新的视角，从一些新的角度考虑如何更好地使用绩效考评这个工具，来提高团队的研发效能。

首先，我们来看看 Facebook 的 360 度绩效考评系统具体是怎样的吧。

360 度绩效考评系统

Facebook 的 360 度绩效考评系统，因为要通过收集同事间的反馈，来评价员工对公司的贡献。所以，请谁来评价你的工作、评价内容是什么、如何定结果，以及如何应用结果就是 4 个关键问题。

由谁给你写评价？

这套考评系统之所以称作是 360 度，就是因为反馈来源的覆盖面广，尽量收集每一个能够评价被考评对象贡献度的同事的反馈。这些同事具体包括：

被考评对象自选 1~2 个同事写评价；

主管指派 1~2 个同事写评价；

被考评人员自评；

主管的评价；

如果被考评者是管理者，他的所有直接下级都要给他写评价。

评价内容是什么？

对其他同事的评价，以及自评，核心问题就两个：

这个同事在上一个绩效考评周期中，对公司最大的 1~2 个贡献是什么？请用事例说明。

这个同事在上一个绩效考评周期中，如果在什么方面采取不同的做事方式，可以对公司有更大的贡献？

当然，对管理人员的评价还有一些管理相关的问题，个人自评还会有申请级别晋升的部分，对下级评价会有是否满足级别要求等额外内容。

如何定结果？

评定结果的方式是，汇总对被评估对象的所有评价，并以此为依据，由他的主管和同级的其他主管一起讨论决定绩效。

如何应用结果？

绩效考评结果包括：是否涨级、工资底薪是否要调节，以及奖金金额。这，与其他公司相比没什么特别的。但，值得一提的是，奖金数会综合公司年度表现、团队年度表现以及个人年度表现，使用公式计算得出，而且员工可以看到自己的计算公式，使得奖金数额的计算更加透明。

如果你想深入了解这个 360 度绩效考评系统，可以参考这两篇文章：文章 1、文章 2。

讲完了 360 度绩效考评的四个关键问题，接下来，我们再看看这个考评系统的两个重要原则：

绩效考评中关注贡献度（impact）；

提高绩效考评的客观性和公正性。

原则一：绩效考评中关注贡献度

360 度绩效考评系统要考察的两个核心问题，实际上就是表现的“好”和“坏”两部分。这是收集反馈最常见的内容，没有什么特别的地方。但 Facebook 的考察特别强调以下两点：

问题描述强调以贡献度为根本衡量标准。这里并没有提到这个人的技术水平怎样、开发效率怎样。因为技术和效率都是为贡献度服务的，一个员工即使技术再厉害，但对公司的贡献不够的话，绩效也不会好。

衡量贡献的维度，是对公司的贡献，而不是对团队的贡献。因为，对团队的贡献最终也要以对公司的贡献来衡量。这种方式可以避免团队的局部利益和公司的整体利益不一致的现象，让员工更关注公司的整体利益。

这两点，就能够让绩效反馈的收集专注于员工对公司的贡献，让每个员工的努力方向都和公司的方向对齐。

原则二：提高绩效考评的客观性和公正性

绩效考评的一个重点是，客观性和公正性。客观公正的评价可以让员工继续努力，反之则会极大地伤害士气，导致员工工作效率下降，甚至离职。所以，很多公司尝试使用数字化的方式来尽量提高绩效考评的客观性。也正因为如此，很多公司希望用数字化来衡量员工的开发效率。

但，由于软件开发行业的灵活性，用指标来衡量生产效率的效果不太好。你可以再回顾下第 2和第 3篇文章中的相关内容。

所以，Facebook 采用了这种通过收集人的主观反馈的方式来进行绩效考评。这一点，看似和数字化管理背道而驰，但这就是由软件开发行业的特性而决定的。所以，从实际结果来看，这种方式的效果反而很好。

关于提高绩效考评的客观性和公平性，Facebook 的实践主要包括以下 4 点：

在评价中，每一个对公司的贡献，都一定要有事例支持，有数字的话就更好了。

员工给其他同事评价的质量，也是他自己绩效的重要组成部分，来保证每个人都会认真填写尽量客观的评价。以我为例，在第一次给同事写评价时，写的比较简短、空洞，结果被主管两次打回重写。后来，我每次写绩效考评时都特别认真。

意见的收集，覆盖的人群比较全面，包括同事、自评、上下级。在给自己选择评价人时，要选择对自己工作比较了解的，从而能作出客观评价，而不能随便选择和自己关系好的同事。

最终决定绩效结果的讨论中，虽然主管的评价比重较大，但其他同事提供的评价占比也很可观。而且，在最终决定绩效的讨论中，同事的评价也是参会者可见的，尽量避免主管一言堂的情况。

以上，就是 Facebook 使用 360 度绩效考评系统时遵循的两条重要原则。通过这两条原则，360 度绩效考评系统做到了尽量客观公正评价员工对公司的贡献，并依此进行年终奖和股票的发放，从而提高了员工的努力方向和公司方向的一致性。

当然，这个系统也有一些缺陷。

360 度绩效考评系统的问题

360 度绩效考评系统依赖于人的主观反馈，所以很容易出现评价不客观的问题。虽然 Facebook 采取了上面提到的原则 2 中的 4 条实践来避免这样的问题，但仍会出现一个难以避免的情况，即人际关系好的人更容易得到好的评价。人之常情，关系的好坏会影响你对他人评价的客观性，不愿意对关系好的个人或和团队提出批评，进而导致一些问题暴露得不及时。

不过，从我的个人经验看，这个问题并不太严重。如果你考虑采用 360 度绩效考评系统的话，可以有意识地关注是否出现了问题被隐藏的情况。

接下来，我再与你分享下我在其他公司落地这套系统的经验吧。

绩效考评落地实战：360 考评系统

360 度绩效考评系统的确对软件开发公司有巨大好处，所以，我离开 Facebook 之后，在国内的一家公司引入了这套系统。具体的引入步骤包括：

制定职级系统，并详细描述每一个级别的技术预期和贡献度预期。

对公司的现有员工进行级别评定。

对整个考评系统，进行文档化、正式化，并在公司内宣讲。

正式使用这个制度，进行绩效考评。

整个流程下来效果还不错。虽然有少数员工不满意，但相比于之前主要由主管直接决定绩效的方式，普遍反馈这一套系统的确更客观公正。

在我看来，在推行这个系统时，有以下三点需要特别注意：

宣讲时，强调客观、公正评判个人对公司的贡献度，是采用这种评定方式的原因。

执行中，关注 Facebook 为保证客观公正性的四条实践。

确保反馈的保密性。为保证大家愿意写出一些真实想法，不能让员工知道对方对自己的评价。

值得一提的是，这个系统比较正规，所以有些员工会觉得太重。但在我看来，一套公平的绩效考评系统，能够大大激励员工朝着公司的前进方向而努力，因此花费的时间绝对是值得的。除非是只有几个开发人员的小公司，否则我都推荐你尝试这种绩效考评方式。而在几个人的小公司，即使评价系统还没有成型，也应该注意评价的客观公正性。

小结

今天，我与你介绍了 Facebook 怎样使用 360 度绩效考评系统的调节，来保证开发人员的努力方向和公司的方向保持一致，从而最大化地应用工程师文化释放出来的巨大能量。这种绩效考评方式，通过全面收集同事间的反馈来评定绩效，并通过一系列规则，提高客观性和公正性，驱动大家在工作中关注公司的全局利益。

虽然在执行过程中偶尔会出现因为人的主观因素，而不能客观反映事实的情况，但从 Facebook 员工的反馈来看，这套系统总体的执行效果很不错。它相对客观公正，而且能够驱动员工以公司整体利益为重。这对一套绩效考评系统来说，实属难能可贵。所以，我推荐你尝试这套系统。

Facebook 的工程师文化，强调尽量去激发员工的内驱力。从这个点出发，Facebook 从让员工做感兴趣的事、让员工拥有信息和权限、用绩效调节员工努力方向这三大方面，设计了很多对工程师成长有巨大好处，又会给企业带来巨大收益的实践。

如果你是一个管理者，可以以这些实践为参考，并尝试引入一些轻量级的实践，比如 Hackathon、SEV 流程、内部全员公开的 Wiki 系统等。

而如果你是个人开发者，也可以从工程师文化中汲取养分，用在自己的工作和学习中。比如，Move Fast and Break Things、Focus on Impact、持续进步、代码为王等文化，都对个人开发者非常有帮助。即使你所在公司没有这样的文化，你也可以培养自己按照这样的方式做事。

今天这篇文章就是专栏正文的最后一篇了，我已经从研发流程、工程方法、个人效能、管理和文化这 4 大方面，与你讲述了提升研发效能的各种原则和实践。但，学以致用才是学习的最终目的！所以我希望，你可以将这些知识，应用到你的工作中，真正提升团队和个人的研发效能。

写作专栏之余，我也在创业。12 月 6~7 日，我的公司 KodeRover 会在上海举办一个 10X 工程效能特训营，帮助个人实操打造“DevOps as a Service”体系，帮助企业提高研发效能、交付效能。通过这个特训营，你可以实践我在专栏中讲述的关于全栈开发、效能度量、环境服务化、自助化等方面的方法论。

在这里，我给订阅专栏的用户提供了一个福利：报名推荐人填写“极客时间葛俊”，个人报名 3 折优惠（需审核）；携企业组团赠送一张个人免费票。

思考题

在 360 度考评系统中，Facebook 鼓励员工公开自己对别人的评价，也确实有部分员工这样做了。你觉得，这样做的利弊是什么呢？

感谢你的收听，欢迎你在评论区给我留言分享你的观点，也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见！

结束语 | 超越昨天的自己，享受成长的快乐

最近几年，我不止一次听人提到，学习是反人性的。乍一听起来，似乎有些道理，因为学习过程往往会伴随着汗水。但仔细一琢磨，我发现完全不是那么回事儿。

学习，的确需要克服内在的惰性和环境限制。但，这就是世界的客观规律，我们只有努力去克服这些困难，才能通过学习实现成长。而成长带来价值和喜悦，绝非安逸、舒适带来的简单快乐可以比拟。

回想这 5 个月我创作《研发效率破局之道》专栏的过程，也正是这样，辛苦、有挑战，但价值非常大。

我写这个专栏的目的，是总结自己 15 年的软件行业从业经历，系统地分享我对高效研发的理解。而我的目标是，根据研发活动的特点，抽象出研发效能的模型，而不是堆砌花里胡哨的各种研发实践。

同时，考虑到利用碎片化时间收听专栏音频的用户，我希望每一个模块、每一篇文章的知识点，都有严密的知识结构，让你学到的知识点不是零散的，而是能够连成线、织成网，实现碎片化时间的高效利用。

所以，我创作这个专栏的思路是，系统分析研发效能模型中的各种方法论，并结合 Facebook 等高效能公司的具体实践，以及我在其他公司落地的经验，让你能够深入理解这些方法论的 why、how 和 what，进而活学活用，真正提高个人和团队的研发效能。

为此，我花了大量的时间去查阅资料、调研、整理和优化，并针对不同主题进行不同维度的讲解：针对方法论和文化，主要是系统化和实战经验相结合；而针对具体工具，则尽量给出具体的示例，帮你更直观地理解应用方法和技巧，因此使用了不少的截屏、录屏、代码输出，还在 GitHub 上专门创建了两个项目来进行演示。所以，每一篇文章的耗时，都超过了自己和极客时间编辑的预期。

除了这些挑战和辛苦外，这个过程对我来说价值巨大。

首先，这些内容能够为国内公司研发效能的提升带来价值，对我来说就意义重大。我切身体验过 Facebook 这样的高效能公司，也体验过研发效能不那么好的公司，特别清楚高效能可以带来公司业务发展和个人成长的质变。这，对国内正在从野蛮生长到精耕细作转变的软件行业，作用尤其巨大。

从留言反馈来看，我确实看到了这份价值。所以在这里，我要感谢你们的陪伴，你们的每一条留言，都是我坚持创作的巨大动力。与你们的沟通，是一件非常快乐的事情！也正因为如此，我几乎对每一条评论都做了详细解答。

除了能够产生价值外，这个过程也在帮我不断提高自己。

在我看来，学习包括三个层次：

第一层是被动学习，效果一般；

第二层是动手实践；

第三层是把知识教给别人，效果最好。

从专栏的创作中，我体会尤其深刻。为了保证逻辑的严谨性，我经常会从“你”的角度问自己一些问题，以此来发现盲点，然后做更深一步的调研。文章上线之后，你的提问也驱使我更进一步地思考。毫无疑问，这都加深了我对问题的理解。

所以，我希望你在学习了这些方法论和实践后，能够学以致用，提高自己和团队的研发效能，然后更进一步地，你能够把它们传播到你的团队中，影响到你的下级同事甚至是老板。这样一来，这些方法论和实践，就能产生更大的价值，而你对它们的理解也会更深刻。

虽然专栏已经更新结束了，但我们的交流仍会继续。如果你在实践这些方法论的过程中，有什么问题和想法的话，都可以直接留言给我，我会继续关注并回复你的留言。

“超越昨天的自己，享受成长的快乐”，是我的座右铭。每天进步一点点，除了能够产生价值外，学习和成长本身就能给我们带来巨大的快乐。这，也是我一直喜欢研发效能的重要原因。任何一个工作流程、工程方法都有提高的空间，我们需要做的，就是去发现这些可以改进的地方，然后对值得提高的部分进行优化。

其实，国内的软件行业，值得优化的地方比比皆是。我一直相信，国内软件研发人员的能力和创造性，绝不亚于硅谷那些高效能公司。只要我们的方向对了，并不断提高，就一定可以大幅提高团队和个人的研发效能，从而把时间花在最值得的地方。加油，我们一定可以！

最后，祝愿你在这个过程中，也能不断超越昨天的自己，享受成长的快乐！

在文章结尾，我为你准备了一份调查问卷，题目不多，希望你能抽出两三分钟填写一下。我非常希望听听你对这个专栏的意见和建议，期待你的反馈！