阅读大规模代码:挑战与实践(2)不要直接开始阅读代码

目录

  1. 引言 链接

    • 1.1 阅读大型、复杂项目代码的挑战

    • 1.2 阅读代码的机会成本

    • 1.3 本系列文章的目的

  2. 准备阅读代码 链接

    • 2.1 阅读需求文档和设计文档

    • 2.2 以用户角度深度体验程序

  3. 宏观理解代码结构

    • 3.1 抓大放小:宏观视角的重要性

    • 3.2 建立概念手册:记录关键抽象和接口

    • 3.3 分析目录树

      • 3.3.1 命名推测用途

      • 3.3.2 文件结构猜测功能

      • 3.3.3 代码层面的功能推断

  4. 深入代码细节

    • 4.1 阅读测试代码:单元测试的洞察

    • 4.2 函数分析:追踪输入变量的来源

    • 4.3 过程块理解

      • 4.3.1 排除Guard语句,找到核心逻辑

      • 4.3.2 利用命名猜测功能

      • 4.3.3 倒序阅读:追溯参数构造

  5. 分析交互流程

    • 5.1 交互流程法:用户交互到输出的全流程
  6. 调用关系和深层次逻辑

    • 6.1 可视化调用关系
      • 6.1.1 画布法

      • 6.1.2 树形结构法

  7. 辅助工具的使用

    • 7.1 利用AI理解代码
  8. 专项深入

    • 8.1 阅读算法:理解概念和算法背景

    • 8.2 心态调节:将代码视为己出

  9. 结语

    • 阅读大型代码的心得与建议

阅读代码取得成效的关键,是建立了一个可靠的心智模型(Mental Model),此时你会感到自己就是这个系统的作者

阅读需求文档和设计文档

阅读代码最大的误区是直接阅读代码。我举一个例子你就懂了。

Inkscaple

这是一个开源的矢量图形编辑器,名为 Inkscaple。它光 commit 数量就接近 30000,代码量保守估计接近百万,而且基本都是 C++ 代码,是一个非常大的项目。

请问,如果你没有亲自使用过 Inkscaple,不知道它有哪些功能,与某些 UI 如何交互,交互之后会大致发生什么,你就直接去读源代码,你能读懂吗?就举一个非常简单的例子,你不亲自使用这个软件,能从代码中梳理出用户绘制一个多边形所涉及的 UI 交互和经历的调用链路吗?这几乎不可能,哪怕是你是 C++ 专家。

这一章节之所以放到如此靠前的位置,是因为它真的太重要了,一旦没有做好这一点,会有大量的时间被浪费掉,真的,比你想象的要多得多。

因此,本章节我提出两个公式。

第一个公式 E = k * e^(rU)

阅读代码的效率(我们可以称之为E)与业务理解程度(我们可以称之为U)成指数级正比的关系


$$
E = k \cdot e^{rU}
$$

在这个公式中:

  • $E$ 表示阅读代码的效率。

  • $U$ 表示业务理解的程度。

  • $k$ 是一个比例常数,表示当 $U = 0$ 时的基础效率。

  • $r$ 是一个正的常数,表示业务理解对效率提升的影响程度。

  • $e$ 是自然对数的底数(约等于2.71828),这里表示指数关系。

这只是一个简单的模型,实际情况可能更加复杂,我只是想强调业务理解对阅读代码的重要性。

这些道理谁都懂,但是一旦开始着手,很多人就会忘记这个公式。他们会直接开始看代码,搭建代码运行环境等等。常见症状:

  1. 发现时间浪费在研究一个函数是干什么的。

  2. 知道了一些模块的作用,但不知道这些模块为什么存在。

  3. 重新写一遍的效率可能比你阅读代码的效率还要高。

“业务理解”是一个非常宽泛的词语,所以第二个公式我们会更加具体说明。

第二个公式:R > A > I

在阅读代码之前,首先应该理解需求,其次是理解设计,最后才是阅读代码。这个顺序非常重要,因为:

  1. 理解需求(Requirements):这是基础,涉及到了为什么要建造这个系统以及这个系统需要解决哪些问题。这个问题涉及到哪些参与者,他们之间如何交互,产生了哪些需要解决的业务需求,为了解决这些需求,系统需要提供哪些功能。

  2. 理解设计(Architecture):设计文档通常描述了系统是如何构建的,包括它的架构、组件、数据流、接口等。理解设计可以帮助你知道代码中的每一部分是如何和其他部分协作的,以及它们是如何共同解决业务需求的。

  3. 阅读代码(Implementation):这是实现阶段,是具体实现设计和满足需求的地方。如果没有前两个步骤的理解,你可能会迷失在代码的细节中,无法把握代码的整体结构和目的。

不妨以 XFS 文件系统为例,其设计文档(在这里)包含了关于 XFS 的算法和数据结构的详细信息。如果你在阅读源码之前先阅读这个设计文档,你会对 XFS 如何在内部工作有一个清晰的理解,这将使得阅读实际的代码变得更加容易和有意义。你会知道哪些数据结构用于表示文件系统中的文件和目录,你会知道哪些结构是磁盘结构,哪些是内存结构,哪些是用于数据交换的中间结构。这样,当你实际查看代码时,你会更容易理解它的逻辑和结构。

不是所有情况下你都能找到或者有权限访问文档,因此你需要尽可能多地收集信息,例如别人的博客、论文、演讲等等。以及利用好源代码中的配置文件、测试用例、注释。

以用户角度深度体验程序

在你打算以任何方式研究实现之前,最好以用户的角度深度体验程序,极致情况下,你可以把自己当成一个测试工程师。这是一种有效的准备阅读代码的方法。通过模拟用户的使用场景和操作流程,你能够更好地理解程序的功能和交互方式。这种体验可以帮助你建立起对程序整体流程和模块之间的关联性的直观认识,从而更有针对性地进行代码阅读和分析。

注意:深度体验程序并不意味着你要覆盖所有的情况和功能,实际上这和你的初心相关。只有很少的情况你需要深入体验所有的功能,多数情况下我们只是涉及到某个子集。还是以 XFS 为例,他的日志系统是一个非常复杂的模块,但是如果你只是想了解 XFS 的基本工作原理,磁盘结构等,你可以只体验一下基本的格式化、文件读写操作,用 xfs_check,xfs_db,xfs_info 等工具查看文件系统的状态,这样就足够了。文档同理,你可能最开始只需要阅读核心概念、宏观介绍的文档。因为一开始你可能并不真的理解文档,但是在阅读代码的过程中,你会发现你需要回头阅读文档,这时候你就会发现你已经有了一些基础的理解,经过这样的交叉迭代,你最终会得到一个坚实的心智模型。


上述两个步骤,能够让你在深入代码之前建立起对系统整体的认知,并且有利于你更加高效地理解和分析代码。这将为接下来的代码阅读和理解工作奠定坚实的基础。