源码是一个终点静态、复杂度静态、边界清晰的学习对象, 它有静态的学习内容、学习目标、学习结果
源码是编写出来的, 编写者有一个自己的编写者上下文, 而之所以阅读源码, 是因为缺乏编写过程的上下文, 只有一个初始空白的读者上下文; 之间的关系类似于汇编和反汇编, 关系分别是 “从人类想法到代码书写” 和 “从代码书写到人类想法”
编写者上下文和读者上下文是有显著区别的:
对阅读过程的心理预估
源码的阅读过程是“先苦后甜”的, 并大致有这样一个模型:
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)
起始时, 在一无所知的情况下, 阅读一个符号会接触到更多的未知符号, 即“学的越多越无知”, 但是源码的内容是有限的, 因此必然会到一个阶段, 就是新增的未知符号从越来越多变为越来越少
源码阅读技巧
1. 如何起步: 像编译器一样阅读
确定核心目标后, 再确定一个核心目标相关的“小”目标:
不要一开始就找 main 文件开始阅读(但可以浏览), 从 main 文件开始阅读的未知符号数量是最多的, 应当从 main 链路中找到一个相对独立的模块, 作为单次的小目标消化局部复杂度, 然后最终通过 “链接” 小目标的学习结果, 消化整体的复杂度
需要注意, 初次挑选的小目标, 可能还是很大, 目标应当继续缩小
2. 给符号重新归类
编写者的编写习惯和读者的编写习惯是不一致的, 特别是大型项目有 N 多新的老的编写者的情况下, 代码质量其实未必佳, 因此最好阅读的时候, 按照自己的编写习惯调整一下源代码, 比如该放到 a 文件却放到 b 文件的符号就给它挪个位置, 比如某个函数只有一处调用, 那么就和调用方放到邻近的位置等等
3. 阅读收益评估
有些源码文件没有阅读的必要, 比如工具函数等
源码文件可以通过一些手段预估它是否适合阅读, 比如我写了一个工具统计一个文件的注释行数占总行数的百分比, 百分比越高, 则内容应该越容易理解, 那么就优先看注释多的文件
4. 删除不关心的特殊分支
任何项目都有应对各种现实问题而添加的特殊补丁, 如果读者自己没有这些现实问题, 这部分的代码就可以直接删掉, 这样整体链路会更清晰和方便理解
典型的比如, 你是 macOS 用户, 然后服务器肯定是 Linux 系统, 那就把源代码中 windows 相关的部分都删了
5. 识别公共知识
源码包含两种知识, 借用面向对象的术语, 可以叫公共知识和私有知识
公共知识就是业内通用的知识, 私有知识就是源码作者自己发明的一些文件数据结构、处理算法
比如, ELF 文件格式是公共知识, 而 Go 独有的 go object file, 就是私有知识
公共知识, 源码中往往不会进行说明, 因为源码作者自己肯定知道, 同时他也不会从读者角度去考虑进行说明, 所以读者自己要识别出源码中使用了这部分公共知识并从“课外”学习
公共知识的特征:
6. 问 ChatGPT