P6到P7只用了两年,阅码无数的阿里大佬,是如何去阅读别人代码的?

最新互联网大厂面试真题、Java程序员面试策略(面试前的准备、面试中的技巧)请移步GitHubgit


1、为什么要读别人的代码?

咱们去阅读别人的代码,一般会带有必定的目的性。完整把一个系统的代码 “读懂” 须要极大的精力。因此明确阅读代码的目标很重要,由于它决定了你最终可以为这事付出多大的精力,或者说成本。程序员

大致来讲,咱们能够把目标分为这样几种类型:github

  • 我要评估是否引入某个第三方模块;
  • 我要给某个模块局部修改一个 Bug(多是由于使用的第三方模块遇到了一个问题,或者多是你的上级临时指定了一个模块的 Bug 给你);
  • 我要以某个开源模块为榜样去学习;
  • 我要接手并长期维护某个模块。

为何要把咱们的目标搞清楚?面试

由于读懂源代码真的很难,它实际上是架构的反向过程。它相似于反编译,可是并非指令级的反编译,而是须要根据指令反推更高维的思想。算法

咱们知道反编译软件可以将精确软件反编译为汇编,由于这个过程信息是无损的,只是一种等价变换。可是要让反编译软件可以精确还原出高级语言的代码,这就比较难。由于编译过程是有损的,大部分软件实体的名字已经在编译过程当中被去除了。固然,大部分编译器在编译时会同时生成符号文件。它主要用于 debug 用途。不然咱们在单步跟踪时,debug 软件就无法显示变量的名字。数据库

即便咱们可以拿到符号文件,精确还原出原始的高级语言的代码仍然很是难。它须要带必定的模型推理在里面,经过识别出这里面咱们熟悉的 “套路”,而后按照套路进行还原。咱们能够想像一下,“一个精确还原的智能反编译器” 是怎么工做的。编程

第一步,它须要识别出所采用的编程语言和编译器。这一般相对容易,一个很是粗陋的分类器就能够完成。尤为是不少编译器都有 “署名”,也就是在编程出的软件中带上本身签名的习惯。若是假设全部软件都有署名,那么这一步甚至不须要训练与学习。数据结构

第二步,经过软件的二进制,结合可选的符号文件(没有符号文件的结果是不少软件实体,好比类或函数的名字,会是一个随机分配的符号),加上它对该编译器的套路理解,就能够进行反编译了。架构

编译器的套路,就如同一我的的行为,持续进行观察学习,是能够造成总结的。这只须要反编译程序持续地学习足够多的该编译器所产生的样本。编程语言

我之因此拿反编译过程来类比,是但愿咱们可以理解,阅读源代码过程一方面是很难的,另外一方面来讲,也是须要有产出的。

有产出的学习过程,才是最好的学习方式。

P6到P7只用了两年,阅码无数的阿里大佬,是如何去阅读别人代码的?

那么阅读源代码的产出应该是什么?答案是,构建这个程序的思路,也就是架构设计。

2、理解架构的核心脉络

怎么作到?

首先,有文档,必定要先看文档。若是本来就已经有写过架构设计的文档,咱们还要坚持本身经过代码一步步去反向进行理解,那就太傻了。

可是,必定要记住文档和代码很容易发生脱节。因此咱们看到的极可能是上一版本的,甚至是最第一版本的设计。

就算已经发生过变化,阅读过期的架构设计思想对咱们理解源代码也会有极大的帮助做用。在这个基础上,咱们再看源代码,就能够相互进行印证。固然若是发生了冲突,咱们须要及时修改文档到与代码一致的版本。

看源代码,咱们首先要作到的是理解系统的概要设计。概要设计的关注点是各个软件实体的业务范畴,以及它们之间的关系。有了这些,咱们就可以理解这个系统的架构设计的核心脉络。

具体来讲,看源码的步骤应该是怎样的呢?

首先,把公开的软件实体(模块、类、函数、常量、全局变量等)的规格整理出来。

这一步每每有一些现成的工具。例如,对 Go 语言来讲,运行 go doc 就能够帮忙整理出一个自动生成的版本。一些开源工具例如 doxygen 也可以作到相似的事情,并且它支持几乎全部的主流语言。

固然这一步只能让咱们找到有哪些软件实体,以及它们的规格是什么样的。可是这些软件实体各自的业务范畴是什么,它们之间有什么关系?须要进一步分析。

通常来讲,下一步我会先看 example、unit test 等。这些属于咱们研究对象的客户,也就是使用方。它们可以辅助咱们理解各个软件实体的语义。

经过软件实体的规格、说明文档、example、unit test 等信息,咱们根据这些已知信息,甚至包括软件实体的名字自己背后隐含的语义理解,咱们能够初步推测出各个软件实体的业务范畴,以及它们之间的关系。

接下来,咱们须要进一步证明或证伪咱们的结论。若是证伪了,咱们须要从新梳理各个软件实体之间的关系。怎么去证明或证伪?咱们选重点的类或函数,经过看它们的源代码来理解其业务流程,以此印证咱们的猜想。

固然,若是你可以找到以前作过这块业务的人,不要犹豫,尽量找到他们而且争取一个小时左右的交流机会,并提早准备好本身遇到迷惑的问题列表。这会大幅缩短你理解整个系统的过程。

最后,确保咱们正确理解了系统,就须要将结论写下来,造成文档。这样,下一次有其余同窗接手这个系统的时候,就不至于须要从新再来一次 “反编译”。

3、理解业务的实现机制

业务系统的概要设计、接口理清楚后,一般来讲,咱们对这个系统就初步有谱了。若是咱们是评估第三方模块要不要采纳等相对轻的目标,那么到此基本就能够告一段落了。

只有在必要的状况下,咱们才研究实现机制。刚才咱们谈到系统架构梳理过程当中,咱们也部分涉及了源代码理解。可是,须要明确的是,前面咱们研究部分核心代码的实现,其目的仍是为了确认咱们对业务划分猜想的正确性,而不是为了实现机制自己。

研究实现是很是费时的,毕竟系统的 UserStory 数量上就有不少。把一个个 UserStory 的具体业务流程都研究清楚写下来,是很是耗时的。若是这个业务系统不是咱们接下来重点投入的方向,就不必在这方面去过分投入。

这时候目标就很重要。

若是咱们只是顺带解决一下遇到的 Bug,不管是用第三方代码遇到的,仍是上级随手安排的临时任务,咱们天然把关注点放在要解决的 Bug 自己相关的业务流程上。

若是咱们是接手一个新的业务系统,咱们也没有精力马上把全部细节都搞清楚。这时候咱们须要梳理的是关键业务流程。

怎么搞清楚业务流程?

程序 = 数据结构 + 算法

仍是这个基础的公式。要搞清楚业务流程,接下来要作的事情是,把这些业务流程相关的数据结构先理清楚。

数据结构是容易梳理的,类的成员变量、数据库的表结构,一般都有快速提取的方式。除了MongoDB 可能会难一些,由于弱 schema 的缘由,咱们须要经过阅读代码的方式去理解schema。更麻烦的是,咱们不肯定历史上经历过多少轮的 schema 变动,这经过最新版本的源代码极可能看不出来。一个不当心,咱们就可能会处理到非预期 schema 的数据。理清楚数据结构,事情就解决了大半。

剩下来就是理各个 UserStory 的业务流程,并给这些业务流程画出它的 UML 时序图。这个过程随时能够补充。因此咱们挑选对咱们当前工做最为相关的来作就行了。

最后,仍是一样地,咱们要及时把咱们整理的结论写下来,变成架构文档的一部分。这样随着愈来愈多人去补充完整架构设计文档,才有可能把咱们的项目从混沌状态解脱出来。

结语

对于任何一个项目团队来讲,阅读代码的能力都极其重要。哪怕你以为你的团队共识管理很好,团队很默契,你们的工程习惯也很好,也都很乐意写文档,但这些都替代不了阅读代码这个基础活动。

阅读代码是不可或缺的能力。

为何这么说?由于:代码即文档,代码是理解一致性更强的文档。

另外,做为一个小补充,咱们须要指出的一点是:阅读代码的结果,有时不必定仅仅是架构设计文档的补充与完善。咱们有时也会顺手修改几行代码。

这是正常现象,并且应该被鼓励。为何鼓励改代码?是由于咱们鼓励随时随地消除臭味。改几行明显风格不太好的代码,是很是好的一件事情。

可是咱们也要有原则。

  • 其一,不作大的改动,好比限定单个函数内的改动不能超过 10 行。
  • 其二,确保改动先后的语义彻底一致。这种一致须要包括全部 corner case 上的语义一致,例如错误码,条件语句的边界等。
  • 其三,无论多自信,有改动就须要补全相关的单元测试,确保修改代码的条件边界都被覆盖。