极客时间已完结课程限时免费阅读

21 | 高效工作:Facebook的10x程序员效率心法

21 | 高效工作:Facebook的10x程序员效率心法-极客时间

21 | 高效工作:Facebook的10x程序员效率心法

讲述:葛俊

时长14:52大小13.62M

你好,我是葛俊。从今天这篇文章开始,我们就正式进入个人效能模块了。今天,我要和你分享的主题是,程序员如何高效地进行开发工作。
最近比较流行的一个说法是 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 的设计模式必定会出现。
所以,作为一名软件开发者,我们必须要持续学习。我之前在一家创业公司时,有一个刚大学毕业两年的同事,他有一个非常好的习惯,就是每天早上比其他同事早半个小时到办公室,专门来学习和提高自己。正是因为他的持续学习,使得他虽然工作时间不长,但在整个团队里一直处于技术领先的位置。你也可以借鉴这个方法,或者采用其他适合自己的方法来持续地提升自己。

思考题

我今天提到的关于分而治之的实践,哪一条对你触动最大呢?同时,也和我分享一下你在工作实践中的感受吧。
你还知道哪些编程技术方面的高效原则和实践吗?
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 5

提建议

上一篇
20 | 答疑篇:如何平衡短期收益和长期收益?
下一篇
22 | 深度工作:聚焦最有价值的事儿
 写留言

精选留言(11)

  • Jxin
    2019-10-11
    1.应该是提高个人效能的实践,哪个更有体会吧? 2.关于追求完美,我有点体验。原本我也是事事追求完美。但在跟完郑烨老师的<10x程序员工作法>后,我认可了一个观点。过分的追求完美是在核心价值思考上的偷懒。所以后面对完美的追求就变成了边际成本投入和边际收益的平衡上,依旧是追求更好,但力度更小更精准,每一行代码在写上去前都有过斟酌。 3.关于自动化,我也有点体验。我曾抱怨过项目完全没有自动化测试这一块,但测试同学都是业务测试,很无奈,然后我就自己干,结果根本撑不起来,自动化case的添加都赶不上功能需求的添加。而后,经过和测试同学的讨论,我们采用了定时任务维护测试环境数据,加半自动case覆盖(部分需要人工验证)的方式,将回归测试case集先弄起来了。从中我吸取了一个经验,一切的目的是对任务close的追求,自动化只是常用的方式,编码只是工具。不一定都要自动化,也可以说完全自动化的追求亦是一种完美追求,不利于落地。采用优先落地持续迭代才是比较科学的方案,毕竟,这样能更早的享受到回归case的好处,虽然它一开始并没有那么好用。 4.我不建议采用太多设计模式,除非非常有把握或者说这么用基本是编程泛式,不然简单才是硬道理。我在重构时,为了结构优雅去重啥会引入一些设计模式。在针对某块业务性能调优时会引入。但往往快速迭代时都只是适当做下领域抽象就上了。 5.除了高效,感觉价值导向也很重要。程序员需要持续学习,而我现在的持续学习不大一样。我现在的持续学习,往往都是工作中有什么难点,然后针对性去学习,我的目标是自己的成长要在工作中产生价值,也就是随着自己的成长团队可以持续受惠。为此,我学习管理学,okr,来科学管理团队,学习软件工程和项目管理来把控开发过程,学习市场运营和产品设计来追求价值最大化和产品合理性,学习增长思维和经济学来对齐企业战略,导向团队和项目发展。但这么做有个很大弊端,沉默成本太多,大多是专用资源,在基本只看通用资源的当下面试场景太吃亏。而且越有价值越吃亏,和企业绑太死,发现太依赖企业成长情况,对个人职业发展也道不清是利是弊。对于这种尴尬的情况,老师您有什么见解?
    展开

    作者回复: 不好意思这两天特别忙,回复晚了一些。 每次@Jxin的发言都很高质量。这次一样。我逐条讲一下我的看法。 1. 我原先就是想问问看大家对我在"抽象和分而治之"部分提到的方法,哪一个你觉得有用。不过现在看来看,的确是"提高个人效能的实践,哪个更有体会"这个问题更能激发大家的思考 :) 2. "过分的追求完美是在核心价值思考上的偷懒"讲的真好! 3. 这个是实用主义的体现。完成目标才是最重要的。 4. 这是设计模式和简单化的权衡的问题。使用设计模式和简单化,都是方法而不是目的。目的是质量、性能、和可维护性这些东西。要根据目的选择方法。不过一般来说我会稍微偏向简单化一些。 5. 我等一下另外回复。

    共 7 条评论
    20
  • 技术修行者
    2019-10-12
    很久以前看到的一句话,也是经常给团队说的一句话:没有什么问题是分层不能解决的,如果不能解决,那再加一个分层。 抽象和分而治之是从事这个行业的基本素质。

    作者回复: 👍👍👍

    8
  • Y024
    2019-12-11
    《Kubernetes Patterns》已开源可免费下载:https://k8spatterns.io/

    作者回复: 谢谢分享!

    共 2 条评论
    4
  • 仙女的猪
    2019-10-11
    老师你提到了「Code Complete」这本书,真好最近买了,当我收到快递,看到这本书的厚度时,我已经傻眼了,基本上是一张身份证的宽度 所以希望您能给我一些建议,怎么来读这本书

    作者回复: 我当时花了蛮多时间慢慢看的。没有什么具体的方法。 现在回过头看,可以参考一些别人写得Summary,看看哪些部分最感兴趣,也可帮助自己更快掌握全局: - https://github.com/mgp/book-notes/blob/master/code-complete.markdown - https://medium.com/@crossphd/code-complete-review-chapter-1-welcome-to-software-construction-3284e15b0a4

    共 2 条评论
    3
  • 文中
    2020-04-14
    要重复超过三次,且机器做会更有效更迅速的事情,我就会将它自动化。 例如: - 要搭新项目的时候,要创建新工程,做一系列的 DevOps 相关配置、IaaS 资源申请和绑定,就可以通过一些脚手架来自动化 - 指标的上报做到 Controller 的注解和 RPC Client 的基类中,再在 Prometheus 实现无差别的默认监控报警规则,确保每次有新功能上线,指标上报和报警规则都能自动上线,不会有报警漏掉。有不合理的阈值,再增加额外的报警规则进行处理。有报警来了,通过将报警输入一个报警分析服务,自动捞出可能相关的系统日志,即可大致判断异常来源,自己问题自己爬起来修,不是自己问题就可以安心交给上下游的系统继续处理。 - 通过 flyway 恢复 在 git 中管理的 mysql 建表语句和基础数据,自动化测试数据维护。 - 整理大家的周报,做数据统计,一个个人看比较低效。让大家按规定格式上报,自己再写一个脚本来分析,省时省力。
    展开

    作者回复: 你的自动化做的很不错啊!谢谢分享!

    2
  • 技术修行者
    2019-10-12
    DRY对于日常工作来说也很重要,尤其是当你的工作和运维相关时,比如 1. 编译打包部署整个流程,不使用DevOps的话,会有大量的没有什么价值的重复工作。 2. 各种日常报表的生成和维护,可以自己开发程序或者用Excel宏来生成。

    作者回复: 是的。运维相关工作很多不需要界面,重复性也比较明显。DRY合适。我的经验,python和nodejs挺适合写一些自动化的小工具的。

    2
  • 兴国
    2019-10-11
    代码重复是在写代码时比较反感的地方,常量重复、逻辑重复等都会造成后期的难以维护。 代码提交前没有充分自测,提交后阻塞别人开发。这点也是平常需要加强的。 抽象和分而治之这点,有时在接到新需求时,也要考虑对现有的系统的影响范围以及是否能够在原来的系统上和代码上再次抽象,不只是新增代码。

    作者回复: 👍👍👍

    2
  • 李双
    2019-10-11
    抽象和分而治之!

    作者回复: Code Complete 这本书当年给我印象最深刻的一点就是关于复杂度处理的讨论。这么些年工作下来,的确程序写得好就是复杂性处理得好。

    2
  • BBQ
    2021-05-06
    感谢 Jason 老师,特别是老师提到的黄金圈方法,以及老师根据黄金圈的 why-what-how 组织内容。这也和有的老师收的先掌握事务的本质,形成树干,再丰富树枝不谋而合。比如之前也看过设计模式等,但看完后不明所以,在工作中也不能灵活应用。 今天才发现这些问题的本质都是如何更好的抽象和分而治之。从本质再看各种设计模式,J2EE模式,乃至 K8S模式,AI 模式,安排得明明白白的。
  • qeesung
    2019-11-08
    我认为TDD是一个高效的原则。在以往的开发过程中,总是先实现代码,然后再进行测试,最后发现无法测试,代码的抽象不够,耦合太严重,浓浓的坏代码的味道。 通过TDD: 1. 至少代码是可以测试的,后面如果重构,修改需求也可以很快迭代 2. 测试来驱动代码设计。整个开发过程反了过来,先写测试,然后再去开发,那么就会强迫你去让你的代码可测试,耦合严重的代码可没那么容易测试,那么在一定程度上可以减少代码耦合度,提升抽象度
    展开

    作者回复: Kent Beck 的这个Quora回答,推荐你看看。https://www.quora.com/Does-Kent-Beck-use-TDD-at-Facebook-How/answer/Kent-Beck 我很赞同他说的第一性原则:你对你的代码质量负责。TDD是实现这个目标的一个工具。适当的地方使用非常好!

    1
  • ヾ(◍°∇°◍)ノ゙
    2019-10-14
    每天来学习的同事主要学习技术,方法论还是工具呢?

    作者回复: 嗯,没人回答。你自己呢?

    共 4 条评论