每个程序员都会碰到的问题,都有自己的方法。在一周内看完一个大模块近10000行的代码后,我总结一下对自己来说比较有用的经验。
本文首发公众号——吹水小镇
背景
从加入搜索中心到刚才看完代码之前,其实我对当前搜索架构的底层细节了解不多,只知道大体上是要建索引,索引要分库,请求来了之后要切词建语法树,再求交然后打分得到结果。
模块的总体流程和大致架构都有所了解,但具体各个大步骤里面包含哪些小步骤,每个小步骤里用的数据结构长什么样,我都不是很清楚。
因此在年底这个难得的时间,我决定把底层架构代码系统地看一下。由于代码量比较大,我开始思考看代码有没有“捷径”可走?有没有一种方法能既不是走马观花又能节省时间?下面我给出一点经验,下面的步骤可以认为是有顺序的。
了解代码大致流程
这一步其实都会。简单来说就是先了解模块整体的大致流程,再对应到代码中找不同入口,知道大步骤是什么。
举个例子:
- 在C++里面就找main函数,看下在main函数里,初始化了哪些对象(例如执行了Init函数);
- 这些被初始化的对象最终在哪里被使用,调用的是什么方法;
- 如果碰到面条代码(就是所有逻辑写到一个函数,不分小函数),先尝试分出逻辑块,不用分得细;
- 最后把上面得到的一个流程的蓝图与自己原本理解的模块架构、流程进行对应,哪一块代码对应的是哪个小模块,哪个逻辑对应的是哪一步数据传输、计算。
从入口函数开始,深度优先遍历
从这一步开始就精看代码,而使用的路径是每遇到一个函数,就进去看,例如server端代码最常见的就是在main函数碰到某个对象的Init,或者对于某个Dispatch函数,下面有并列的几个接口函数,或者对于异步线程,在执行“start”之后直接看它的循环逻辑(通常是Entry函数)。
不要看那些还没使用到的函数以及逻辑。
直接看函数实现细节,跳过变量声明定义
我把这一步简称为“盲人摸象”。
其实最大收获在这一步,我发现大多数情况下不看一个变量、一个class的声明定义其实影响不大,除非你从这个变量名根本没法看出它是做什么的,例如 “a”,”tmp” 这样的变量。
通常需要你系统了解的代码,本身会相对注意编码风格以及变量命名的问题。
我们都知道变量命名得好可以帮助大家理解代码,但我是这次才发现命名良好的变量能够跳过大量不必要看的代码,避免在多个文件之间来回切换,很好地保持了思维的连续性。
举个例子,当你看到这段代码的时候:
m_score_mgr.PushToTopKScores(score);
m_score_mgr.PushToTopKScores(score_list);
你可以不需要急着去看 m_score_mgr 是个什么类型,它的成员有哪些, 只需要知道 m_score_mgr 它有一个方法,可以接收一个分数列表,并且排序出 Top K就行了。
这样当你看到这个下面这行代码时:
top_k_score_list = m_score_mgr.GetTopKScores();
你就很自然想到这一步的输出来自于上面代码的输入。至于这两部之间详细逻辑是怎么样的,可以后面等你需要了解细节的时候再进去看。
复杂的类,理解清楚成员变量发生变化的逻辑
最后一步,就是自己重新把代码的逻辑结构描述一遍,知道其中的实现难点以及关键思路。