你好,我是葛俊。从今天这篇文章开始,我们就正式进入个人效能模块了。今天,我要和你分享的主题是,程序员如何高效地进行开发工作。
最近比较流行的一个说法是10x程序员,也就是10倍程序员,意思是一个好的程序员,工作效率可以达到普通程序员的10倍。要做到这一点并不容易,我们需要在编程技术、工作方式、工具使用等方面全面提高。
今天这篇文章,我将聚焦于如何提高自己的编程技术,给出在实践中被证明有效的3条原则,包括抽象和分而治之、快速迭代,以及DRY(Don’t Repeat Yourself),并针对每条原则给出几个高效实践。而关于工作方式、工具使用等方面的内容,我会在后面几篇文章中与你详细讨论。
虽然我们面对的世界非常复杂,但大脑只能同时处理有限的信息,那怎么平衡这个有限和复杂之间的矛盾呢?
最好的办法是,把一个系统拆分为几个有限的子系统,每个子系统涵盖某一方面的内容,并将其复杂性隐藏起来,只对外暴露关键信息。
这样,我们在研究这个系统的时候,就无需考虑其子系统的细节,从而对整个系统进行有效的思考。如果我们需要深入了解某一个子系统,再打开这个子系统来看即可。
以此类推,如果这个子系统还是很复杂,我们可以再对其进行拆分。这样一来,在任何时候,我们思考时面对的元素都是有限的,复杂度也下降到了大脑的能力范围之内,从而完成对一个复杂系统的理解和处理。
这个拆分处理的过程,就是我们常说的分而治之;而用子系统来隐藏一个领域的内部细节,就是抽象。抽象和分而治之,是我们理解世界的基础。
比如,我们在了解一张简单的桌子时,首先想到的是它由1个桌面和4条桌腿组成。那么,桌面和桌腿就是子系统:桌面就是一个抽象,代表实现摆放物品功能的一个平面;桌腿也是一个抽象,代表支撑桌面的结构。
如果我们需要进一步了解桌面或者桌腿这两个子系统,可以再进一步去看它们的细节,比如两者都有形状、重量、材料、颜色等。但如果一上来就考虑这些细节的话,我们对桌子的理解就会陷入无尽的细节当中,无法快速形成对整个桌子的认知。
软件开发也是这个道理,我们必须做好抽象和分而治之,才能做出好的程序。
所以,拿到一个任务之后,我们要做的首先就是进行模块的定义,也就是抽象,然后对其分而治之。
为方便理解,我再和你分享一个在Facebook时,几个前后端开发者同时开发一个功能的案例吧。
这个功能由一个前端开发者和两个后端开发者完成,整个研发过程至少涉及3个抽象和分而治之的操作:
在这个过程中,一定要明确模块之间的依赖关系,尽快确定接口规格和可调用性。比如,在前后端的拆分中,常常会采用这几个步骤处理API:
通过这样的操作,前后端的任务拆分就顺利完成了。
提高抽象和分而治之效率的一个技巧是,在设计代码架构时注意寻找合适的设计模式。
设计模式指的是,设计过程中可以反复使用的、可以解决特定问题的设计方法,最经典的莫过于《设计模式:可复用面向对象软件的基础》中列举的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。
代码逻辑的重复,不仅仅是工作量的浪费,还会大大降低代码的质量和可维护性。所以,我们在开发时,需要留意重复的代码逻辑,并进行适当的处理。
具体来说,首先是寻找重复的逻辑和代码。在动手实现功能之前,我们会花一些时间在内部代码仓和知识库中进行查找,寻找是否有类似的功能实现,以及一些底层可以复用的库,过程中也可以直接联系类似功能的实现者进行讨论和寻求帮助。另外,有一些IDE,比如Intellij IDEA,可以在编码的过程中自动探测项目中可能的代码重复。
找到重复的逻辑和代码之后,主要的处理方式是,把共同的部分抽象出来,封装到一个模块、类或者函数等结构中去。
如果在开发新功能时发现有需要重构的地方,一个常见的有效办法是,先用几个提交完成重构,然后再基于重构用几个提交实现新功能。
在编程工作中,除了代码的重复外,比较常见的还有流程的重复。比如测试中,我们常常需要重复地产生一些测试数据,运行完测试之后再把这些数据删除。
这些重复的流程也需要DRY,最主要的办法是自动化。以重复的测试数据产生、删除流程为例,一般的做法是,编写脚本进行自动化,当然有些时候也需要写一些复杂的可执行程序来生成数据。
流程重复还有一个特点是,它常常和团队相关,也就是说很多成员可能都会重复某些操作,这样的操作更值得自动化。比如,团队的很多成员常常都需要产生测试数据,这时我推荐你主动对其进行自动化、通用化,并提交到代码仓的工具文件夹中供团队使用。
今天,我针对如何使自己成长为10x程序员,首先给出了在编程技术方面的3个原则,分别是抽象和分而治之、快速迭代,以及DRY。然后,针对每一条原则,我给出了Facebook高效开发者的一些常用实践。
其实,我们还可以从这3条原则中延伸出其他很多有效的实践。
比如,好的代码注释。对子系统设计进行合理的注解,可以方便其他开发者在不同的抽象层面对软件结构有更直观的了解。而且如果系统拆分得当的话,需要注释的地方就会比较少。又比如,代码的设计时审查,就是帮助我们及早进行架构讨论,从而实现快速迭代。
为方便你理解并运用到自己的开发工作中,我将这些实践总结到了一张表格中,如下所示。
另外,关于编程技术的高效实践也是不断演化和发展的。以设计模式为例,最近几年又出现了针对Kubernetes开发场景的模式,你可以参考《Kubernetes Patterns》这本书;针对云原生(Cloud Native)开发,也有了业界比较认可的12-factor原则等。将来必定还会有其他新的设计模式产生。比如,伴随着AI的逐渐成熟,针对AI的设计模式必定会出现。
所以,作为一名软件开发者,我们必须要持续学习。我之前在一家创业公司时,有一个刚大学毕业两年的同事,他有一个非常好的习惯,就是每天早上比其他同事早半个小时到办公室,专门来学习和提高自己。正是因为他的持续学习,使得他虽然工作时间不长,但在整个团队里一直处于技术领先的位置。你也可以借鉴这个方法,或者采用其他适合自己的方法来持续地提升自己。
感谢你的收听,欢迎你在评论区给我留言分享你的观点,也欢迎你把这篇文章分享给更多的朋友一起阅读。我们下期再见!
评论