Skip to content

本文转载自 V2EX

如何阅读源码?

什么是源码

源码是一个终点静态、复杂度静态、边界清晰的学习对象, 它有静态的学习内容、学习目标、学习结果。

源码是编写出来的, 编写者有一个自己的编写者上下文, 而之所以阅读源码, 是因为缺乏编写过程的上下文, 只有一个初始空白的读者上下文; 之间的关系类似于汇编和反汇编, 关系分别是 “从人类想法到代码书写” 和 “从代码书写到人类想法”。


编写者上下文和读者上下文是有显著区别的:

  • 编写是一个复杂度渐进的过程, 源码的符号数量和复杂度是逐渐增多的; 对于编写者来说, 一个符号可能放在 a 文件可以, 放在 b 文件也可以, 因为这个符号是一个增量的简单记忆, 所以编写者自己感知不到负担。
  • 阅读则初始就需要面对已经成型的源码, 对于阅读者来说, 在上下文空白的情况下, 一个符号为什么放在 a 而不放在 b 会是一个源码理解的干扰, 甚至函数名为什么叫 A 不叫 B 也会带来困惑, 因为这带来了附加的模糊的信息, 文件 a 和文件 b 的符号有某种依赖关系, 同时也可能不符合读者的代码习惯或者代码洁癖。

对阅读过程的心理预估

源码的阅读过程是“先苦后甜”的, 并大致有这样一个模型:

START ->        Symbol main
       Symbol deep(1) deep(1) deep(1)
    Symbol deep(2) deep(2) deep(2) deep(2)
       Symbol deep(3) deep(3) deep(3)
 END  ->        Symbol deep(max)

起始时, 在一无所知的情况下, 阅读一个符号会接触到更多的未知符号, 即“学的越多越无知”, 但是源码的内容是有限的, 因此必然会到一个阶段, 就是新增的未知符号从越来越多变为越来越少

Summary

其核心思想,和 “先把薄的书读厚,再把厚的书读薄” 不谋而合

源码阅读技巧

1. 如何起步: 像编译器一样阅读

确定核心目标后, 再确定一个核心目标相关的“小”目标: 不要一开始就找 main 文件开始阅读(但可以浏览), 从 main 文件开始阅读的未知符号数量是最多的, 应当从 main 链路中找到一个相对独立的模块, 作为单次的小目标消化局部复杂度, 然后最终通过 “链接” 小目标的学习结果, 消化整体的复杂度。

Note

需要注意, 初次挑选的小目标, 可能还是很大, 目标应当继续缩小。

2. 给符号重新归类

编写者的编写习惯和读者的编写习惯是不一致的, 特别是大型项目有 N 多新的老的编写者的情况下, 代码质量其实未必佳, 因此最好阅读的时候, 按照自己的编写习惯调整一下源代码, 比如该放到 a 文件却放到 b 文件的符号就给它挪个位置, 比如某个函数只有一处调用, 那么就和调用方放到邻近的位置等等。

3. 阅读收益评估

Tip

有些源码文件没有阅读的必要, 比如工具函数等。

源码文件可以通过一些手段预估它是否适合阅读, 比如我写了一个工具统计一个文件的注释行数占总行数的百分比, 百分比越高, 则内容应该越容易理解, 那么就优先看注释多的文件

4. 删除不关心的特殊分支

任何项目都有应对各种现实问题而添加的特殊补丁, 如果读者自己没有这些现实问题, 这部分的代码就可以直接删掉, 这样整体链路会更清晰和方便理解。

Example

典型的比如, 你是 macOS 用户, 然后服务器肯定是 Linux 系统, 那就把源代码中 windows 相关的部分都删了。

5. 识别公共知识

源码包含两种知识, 借用面向对象的术语, 可以叫公共知识和私有知识 公共知识就是业内通用的知识, 私有知识就是源码作者自己发明的一些文件数据结构、处理算法 比如, ELF 文件格式是公共知识, 而 Go 独有的 go object file, 就是私有知识。

公共知识, 源码中往往不会进行说明, 因为源码作者自己肯定知道, 同时他也不会从读者角度去考虑进行说明, 所以读者自己要识别出源码中使用了这部分公共知识并从“课外”学习。

Tip

公共知识的特征:

  • 奇奇怪怪的缩写; 不排除现代也有人习惯用缩写, 但是很多缩写往往来自于早期的 Linux C 开源项目
  • 特别的、突然出现的、源码别的地方不会用到的名词

6. 问 ChatGPT 😏

总结

  1. 模块式阅读,逐个击破
  2. 依照自己习惯重构符号
  3. 先阅读注释多的,不阅读工具函数
  4. 删除与自己不相关的分支
  5. 识别公共知识,并单独进行理解