我的源码阅读法

2023-05-02 21:48:49 +08:00
 fumeboy

什么是源码

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

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

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

对阅读过程的心理预估

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

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

4040 次点击
所在节点    程序员
17 条回复
JeffersonHuang
2023-05-02 21:55:45 +08:00
我现在是直接第 6 步
cpstar
2023-05-02 22:04:01 +08:00
从 2005 年实操编程以来,大量通过逆向学旧写新,甚至当初 php 就是看代码看了个语言大概。逆向的过程看懂了原作者的思路,看出了可能存在的不足,甚至带着字节码的知识还能“破解”直接修改编译代码。这个过程很是惬意。
Drumming
2023-05-02 22:26:02 +08:00
现在直接第 6 步,但是以前学习思路确实是读源码,读文档
ChrisFreeMan
2023-05-02 22:49:00 +08:00
interesting.
yangzhezjgs
2023-05-02 22:55:19 +08:00
我觉得楼主思考挺深入的,分享一下我的看法:
1.很多代码还是有固定的一些套路,软件的控制流很多都是事件驱动+控制反转,如果是网络相关程序多半有专门的协议解析和协议对应的函数映射表。
2.多数软件都是树形结构,最上层是控制模块,负责创建其他的类,调度,监控等,最底层是具体的数据结构,通常存储了一些控制信息和数据信息,中间通常是具体的管理操作,负责对底层的数据结构进行操作。
3.注意形成两个视图,一个是初始化的静态视图,就是软件启动后创建了哪些数据结构,映射表,控制类等,另一个是动态的运行时视图,即运行时事件发生后,事件驱动架构如何调用其他类,以网络相关为例,就是要理解一次网络请求处理的完整过程。
lhx2008
2023-05-02 23:00:48 +08:00
但是我一直觉得看逻辑没有什么用,关键是搞清楚核心模块的 interface 交互就行了,细节也记不住
Nitroethane
2023-05-02 23:13:19 +08:00
我觉得还有一条比较重要,就是读的时候是从整体到局部细节再到整体的一个过程。从整体开始是先熟悉大致流程,接着到局部细节是指重点关注自己比较关心的那部分的具体逻辑,最后再到整体是思考总结为什么代码要这样写。
GeekGao
2023-05-03 00:53:21 +08:00
ps: 编读边注释
ns09005264
2023-05-03 01:51:11 +08:00
看别人的源码好痛苦,很难快速的理解他们的思路,加上更新迭代,有些地方的逻辑就变得很复杂。只能猜测编写者的思路,还要一边调试才能搞清楚为什么这么写。
我觉得最难的地方是对方法参数的理解,编写者知道每个参数的作用范围,里面都有哪些值,这些值用在什么地方。
阅读者就很难看清这点,因为一个参数会分散引用到不同的调用栈里去。参数的值具体的样子是什么,都有哪些边界,这些东西光看是很难理解。目前的对我最有效的还是调式阅读法。
artnowben
2023-05-03 08:33:04 +08:00
我的经验是,就着一个功能点,先使用,边调试,边阅读代码。我读 DPDK 、nginx 、LVS 就是这么来的。
有些项目文档比较丰富,有些项目社区比较活跃,还可以直接去问作者。
在 dperf 社区里,有不少小伙伴直接去提源码的一些问题,其实作者对这种 issue 还是挺喜欢的,能阅读源码的人,未来可能会来一起维护这个项目。
likunyan
2023-05-03 09:09:09 +08:00
Xdebug
TK4E
2023-05-03 12:06:19 +08:00
看源码不是为了看代码而是为了学习对方解决问题的方法啊,通过代码来反推方法简直就是本末倒置啊.

一般直接看项目的依赖就可以猜测出大致的实现方式,然后看看代码的文件结构来找出模块以及结构体之间的关系.

具体实现其实不太重要,除非他是个侧重于实现算法的项目.
fumeboy
2023-05-03 14:25:14 +08:00
@TK4E 第一段我就说明了, 读源码就是一个本末倒置的行为, 是一个类似于反汇编的行为
ruanimal
2023-05-04 10:01:10 +08:00
很奇怪的是,很多开源项目没有设计文档
alexsunxl
2023-05-04 11:01:22 +08:00
@ruanimal 代码是开源的,但是决策不一定是开源的。越是大型项目越是如此。
为什么这么多开源的基金会,为什么大厂要去支持开源基金会,是要去影响项目的决策
asssfsdfw
2023-05-04 16:15:17 +08:00
像编译器一样阅读-------->像 runtime 一样阅读
RgPr16Lrb1R2zZdJ
2023-06-29 23:22:33 +08:00
@yangzhezjgs 请教一下,你提到多数软件是树形结构。请问有没有其他结构的软件,可以给我一些例子嘛?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/936914

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX