阅读代码是架构设计的反向过程。架构设计是从需求出发,自上而下地分解与构建系统;而阅读代码是从实现出发,自下而上地还原架构思想。这个过程类似于反编译——不是指令级的反编译,而是需要根据代码反推更高维的设计意图。
编译过程是有损的:大部分软件实体的名字在编译过程中被去除。即使有符号文件,精确还原高级语言代码仍然非常难——它需要模型推理,需要识别出熟悉的"套路",然后按照套路进行还原。阅读源代码同样如此:一方面很难,另一方面必须有产出。有产出的学习过程,才是最好的学习方式。
交叉参考:本节是 [70讲 - 怎么写设计文档] 的反向视角——写文档是从设计到表达,读代码是从表达还原设计。
明确阅读目标是第一步,因为它决定了你能为此付出多大成本:
| 目标类型 | 投入深度 | 关键产出 |
|---|---|---|
| 评估引入第三方模块 | 低——理解概要设计即可 | 是否采用的决策 |
| 修复局部 Bug | 中——理解相关业务流程 | Bug 修复方案 |
| 以开源模块为榜样学习 | 中高——理解核心架构思想 | 可借鉴的设计范式 |
| 接手并长期维护模块 | 高——理解全部业务流程 | 完整的架构文档 |
阅读源代码的产出应该是什么?答案是构建这个程序的思路——也就是架构设计。
类比"智能反编译器"的工作方式,可以理解阅读代码的本质:
理解业务流程的方法论回归到最基础的公式:程序 = 数据结构 + 算法
如果已经有架构设计文档,坚持自己通过代码一步步反向理解就是愚蠢的。但必须记住:文档和代码很容易脱节。所以我们看到的很可能是过时版本的设计。即使如此,阅读过时的架构设计思想对理解源代码仍有极大帮助——在此基础上再看源代码,可以相互印证。发生冲突时,及时修改文档到与代码一致。
概要设计的关注点是:各软件实体的业务范畴,以及它们之间的关系。有了这些,就能理解系统架构设计的核心脉络。只有在必要的情况下,才深入研究实现机制。
哪怕团队共识管理很好、团队很默契、大家都很乐意写文档,这些都替代不了阅读代码这个基础活动。因为:
阅读代码的过程中,顺手修改几行明显风格不好的代码,应该被鼓励——这是随时随地消除臭味的好习惯。但必须遵循三条原则:
整理公开软件实体是阅读代码的第一步,现有工具能大幅加速:
go doc 可自动生成包的文档doxygen 支持几乎所有主流语言example 和 unit test 属于研究对象的使用方(客户),它们能辅助理解各软件实体的语义。结合:
可以初步推测出各软件实体的业务范畴及关系,然后通过阅读核心源代码来证实或证伪。
如果已有架构文档却不看,坚持从零开始通过代码反向理解——这是不必要的浪费。即使文档可能过时,它仍然提供了理解系统的起点和高维视角。
完整把一个系统的代码"读懂"需要极大精力。在没有明确目标的情况下试图全面理解,往往导致:
正确做法:根据目标确定投入深度,先理解概要设计,只在必要时深入研究实现。
阅读代码后不写文档,意味着下一次有人接手这个系统时,需要重新再来一次"反编译"。及时把理解写下来,形成架构文档的一部分,是知识传承的基本要求。
阅读代码是架构的反向过程:类似反编译,需要根据代码反推更高维的设计思想。核心产出是构建程序的思路(架构设计)。
明确目标决定投入深度:四种目标(评估、修 Bug、学习、接手维护)对应不同的投入深度,不要在没有目标的情况下试图完整读懂一个系统。
先文档后代码:有文档一定先看文档,与代码相互印证。冲突时更新文档。
理解概要设计是首要目标:各软件实体的业务范畴及关系,才是架构的核心脉络。深入研究实现只在必要时才进行。
程序 = 数据结构 + 算法:理清数据结构是理解业务流程的大半工作,再画出 UserStory 的 UML 时序图。
阅读代码可以顺手改代码,但有原则:改动不超过 10 行、语义完全一致(包括 corner case)、必须补全单元测试。
代码即文档不可替代:无论团队文档多么完善,阅读代码都是不可或缺的基础能力。
下一篇 [72讲 - 发布单元与版本管理] 将讨论软件工程中确定性管理的关键手段——版本管理。