V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fumeboy
V2EX  ›  程序员

我的源码阅读法

  •  2
     
  •   fumeboy · 2023-05-02 21:48:49 +08:00 · 3811 次点击
    这是一个创建于 448 天前的主题,其中的信息可能已经有所发展或是发生改变。

    什么是源码

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

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

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

    • 编写是一个复杂度渐进的过程, 源码的符号数量和复杂度是逐渐增多的; 对于编写者来说, 一个符号可能放在 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)
    

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

    源码阅读技巧

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

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

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

    2. 给符号重新归类

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

    3. 阅读收益评估

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

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

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

    任何项目都有应对各种现实问题而添加的特殊补丁, 如果读者自己没有这些现实问题, 这部分的代码就可以直接删掉, 这样整体链路会更清晰和方便理解 典型的比如, 你是 macOS 用户, 然后服务器肯定是 Linux 系统, 那就把源代码中 windows 相关的部分都删了

    5. 识别公共知识

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

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

    公共知识的特征:

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

    6. 问 ChatGPT

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

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

    具体实现其实不太重要,除非他是个侧重于实现算法的项目.
    fumeboy
        13
    fumeboy  
    OP
       2023-05-03 14:25:14 +08:00
    @TK4E 第一段我就说明了, 读源码就是一个本末倒置的行为, 是一个类似于反汇编的行为
    ruanimal
        14
    ruanimal  
       2023-05-04 10:01:10 +08:00
    很奇怪的是,很多开源项目没有设计文档
    alexsunxl
        15
    alexsunxl  
       2023-05-04 11:01:22 +08:00   ❤️ 1
    @ruanimal 代码是开源的,但是决策不一定是开源的。越是大型项目越是如此。
    为什么这么多开源的基金会,为什么大厂要去支持开源基金会,是要去影响项目的决策
    asssfsdfw
        16
    asssfsdfw  
       2023-05-04 16:15:17 +08:00
    像编译器一样阅读-------->像 runtime 一样阅读
    haxk4201
        17
    haxk4201  
       2023-06-29 23:22:33 +08:00
    @yangzhezjgs 请教一下,你提到多数软件是树形结构。请问有没有其他结构的软件,可以给我一些例子嘛?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1044 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 22:55 · PVG 06:55 · LAX 15:55 · JFK 18:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.