程序员如何高效地进行开发工作?
最近比较流行的一个说法是 10x 程序员,也就是 10 倍程序员,意思是一个好的程序员,
工作效率可以达到普通程序员的 10 倍。要做到这一点并不容易,我们需要在编程技术、工作方式、工具使用等方面全面提高。
第一条原则:抽象和分而治之
拿到一个任务之后,我们要做的首先就是进行模块的定义,也就是抽象,然后对其分而治之。
为方便理解,我再和你分享一个在 Facebook 时,几个前后端开发者同时开发一个功能的案例吧。
这个功能由一个前端开发者和两个后端开发者完成,整个研发过程至少涉及 3 个抽象和分而治之的操作:第一步,前后端模块进行自然的拆分。这时,前后端开发者一定会一块儿认真讨论,明确前后端代码运行时的流程,后端需要提供的 API,以及交付这些 API 的时间。
第二步,两个后端开发者对后端工作进行拆分,确定各自的工作任务和边界。
第三步,每个开发者对自己负责的部分再进行抽象和拆分。
在这个过程中,一定要明确模块之间的依赖关系,尽快确定接口规格和可调用性。比如,在前后端的拆分中,常常会采用这几个步骤处理 API:
1. 前后端开发者一起讨论,明确需要的 API。
2. 后端人员会先实现 API 的 Mock,返回符合格式规范的数据。在这个过程中,后端开发者会尽快发出代码审查的要求给另一个后端和前端开发者,以确保格式正确。
3. Mock 实现之后尽快推到主仓的 master 上 (也就是 origin/master),并尽快将其部署到内部测试环境,让前端开发者可以使用内部测试环境进行开发和调试。
4. 这些 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 时冲突的可能性。
第三条原则:不要做重复的事情
不做重复的事情,是很多开发模式的基础,也是我们非常熟悉的一条开发原则了。比如,我们把一段经常使用的代码封装到一个函数里,在使用它的地方直接调用这个函数。
代码逻辑的重复,不仅仅是工作量的浪费,还会大大降低代码的质量和可维护性。所以,我们在开发时,需要留意重复的代码逻辑,并进行适当的处理。
具体来说,首先是寻找重复的逻辑和代码。在动手实现功能之前,我们会花一些时间在内部代码仓和知识库中进行查找,寻找是否有类似的功能实现,以及一些底层可以复用的库,过程中也可以直接联系类似功能的实现者进行讨论和寻求帮助。另外,有一些 IDE,比如Intellij IDEA,可以在编码的过程中自动探测项目中可能的代码重复。
找到重复的逻辑和代码之后,主要的处理方式是,把共同的部分抽象出来,封装到一个模块、类或者函数等结构中去。
如果在开发新功能时发现有需要重构的地方,一个常见的有效办法是,先用几个提交完成重构,然后再基于重构用几个提交实现新功能。