这是一篇译制博客文章,原博客为 Learn from Source Code (an Effective Way to Grow for Beginners) ,原作者为Nick Mose 。 我 向 Nick 申请获得了此文的中文翻译权与中文翻译文章发布权。
#前言
上周我在和一位年轻程序员聊天时,他问到我:“如何阅读源码?”,我们讨论了一段时间,我还列举了几种有效阅读源码的方式。 然后他说:“你应该就这个话题写篇文章,这对初学者很有帮助,而且这种经验无法从书籍和教程中获得。” 那么开始吧,下面是我关于阅读源码的小技巧。
#为什么我们需要读源码
我们程序员每天都要和源码打交道。经过数年的学习,大多数程序员可以“写”代码,或者至少是拷贝并修改代码。 而且,我们教授编程的方式强调编写代码的艺术,而不是如何阅读代码。 当我说“阅读代码”,我是指有意地专门阅读代码。
众所周知,编程和写作有诸多相同之处。 唐纳德·克努特甚至引入了文学编程(literate programming) 编程范式。 编程与写作有相同的理念:表达我们的想法 。 还记得你在学校是怎么学习写作的吗?我们的写作能力来源于从小学开始直到现在的大量的文本阅读。 多年以来,我们阅读了不同难度的伟大作家的作品,并练习了多种写作技巧。
“如果你没时间读,你就没时间(或工具)写,就这么简单。” —— 斯蒂芬·金,《写作这回事:创作生涯回忆录》
正如斯蒂芬·金所观察到的那样,一个作家必须广泛而频繁地阅读,才能形成自己的声音, 并学会写出促使读者拿起书并痴读的句式和故事结构。 和读书一样,有意地阅读代码可以帮助程序员加速成长,尤其是对中级(intermediate)程序员而言。 这样做有三个好处。
#站在巨人的肩膀上
我们从他人身上学习。优秀的源代码就像文学杰作,它不仅仅只提供了知识和信息,还提供了启迪。
通过浏览 Linux 内核、Redis、Nginx、Rails 或其他著名项目, 你可以从全球范围的成千上万的顶级程序员那里汲取智慧。 在这些项目中可以找到无数的良好编程示例、编程范式选择、设计和架构。 向他人学习的另一个好处是能够避免常见的坑,大多数坑早已被他人踩过。
#解决困难问题
在你的职业生涯中,你终将会碰到谷歌都无法解决的问题。 如果你还没碰到过这种问题,这只是因为你编程的时间还不足够长 :)。 阅读源码是调查这类问题的好方法,也是学习新东西的好机会。
#扩展你的边界
大多数程序员只在少数特别领域编过程。 一般而言,如果你不时常推自己一把,你的编程技能会维持在你同事间的平均水平。 不要满足于修补 bug 或在现有系统中添加琐碎特性的工作。 相反,你可以试着扩展到一个新的领域,持续尝试找到一个你在日常工作中接触不到、但你感兴趣的领域。 这将从整体上拓宽你对编程的理解。
#应该读什么样的源码
综上,阅读源码是有益的。 那么下一个问题,有这么多优秀作品可供选择,我们该选择并阅读什么样的源码呢? 你必须从选择目标开始。如果不在这个步骤上下点功夫,你从源码中学习的效果就会打折扣。 这里有一些典型场景:
-
当你想学习一门新语言。学新语言可不只是学会语法 。 不管怎样,阅读源码是一个非常有效的学习新语言的方式。我从rust-rosetta 项目中学到了很多 Rust 语言知识。Rosetta Code 是一个收集同一批通用任务在不同语言上的解决方案的项目,这是一个可用来学习新语言的有用资源。
-
当你想了解一个特殊的算法或实现。 例如,我们都会使用标准库中的 sort 函数,你有没有好奇过它是怎么实现的? 或者当你要使用 Redis 中的 Set 结构,它是用什么数据结构实现的? 为了解决这些疑惑,你只需要读源码中与之相关的实现部分,通常只有很少的文件或函数。
-
当你在特殊的框架中编程。这意味着你对该框架已经有了一定的经验, 这是个阅读一些框架本身的源码的好机会。 很显然,了解框架的源码有助于提高你对框架的理解。
-
当你想拓展进入新的领域,你可以阅读这个领域的经典著名的项目的源码。 比如说,如果你在做 Web 开发的工作,你对分布式系统感兴趣吗? 如果你的答案是“是”而且你懂 Golang,也许etcd 是你的选择。你想钻研操作系统的内部构成吗?那么也许xv6 是一个好的开始。我们处在一个许多优秀开源项目都托管在了 Github 的好时代,请试着寻找一些这种项目。
记住,选择与你当前的编程技能与知识水平相当的项目。 如果你选择了远超你当前技能水平的项目,最终你会感到沮丧。 读一些相对较小的项目,接着读更大的项目。 如果目前你不能理解某些特定的代码片段,这意味着你有个知识缺口(knowledge gap)。 把代码放到一边去,试着读一些相关的书、论文或其他文档,当你更有信心时再回来接着读代码。 我们总能在一个模式中取得进展:读(代码、书、论文),写,更多的读,更多的写。
#如何读源码
《How to read a book》 是一本指导人进行明智地阅读的书。作为初学者,我们值得投入时间和精力去思考我们应该如何阅读代码。 阅读代码不是件容易的事。 光是阅读源码是不够的,你要试着去理解他人的设计和想法。
#预先准备
为了更有效率地阅读代码,你需要提前在手边准备这些东西:
-
一个你可以熟练使用的编辑器。你需要拥有快速搜索关键字或变量名的能力。 有时你需要查找函数的引用和定义。和你的编辑器相处融洽些。 为了更加有效率,试着学习仅使用键盘操作编辑器。 这会使你专注于代码而不受打扰(译:指额外思考编辑器操作)。
-
掌握基本的 Git 或其他版本控制工具的技能,这样你就能比较代码在版本间的差异。
-
与源码有关的文档。文档可以为你的阅读提供参考,尤其是设计文档、编码规范等文档。
-
具有一定的编程语言与设计模式的知识和经验。这对(阅读)大项目是强制性的。 如果你很了解一门编程语言,你也会了解关于源码组织与编程范式的最佳实践。 当然,这需要时间来积累。要有耐心。
#流程与技巧
阅读过程不是线性的。你不会就那么一个接一个地读源文件。 相反,大多数时候我们会从顶到底地阅读代码。 下面是一些更有效率阅读代码的小技巧:
#结合上下文阅读代码
当你阅读代码时,请持续提出问题。 例如,如果一个应用有缓存策略,一个好问题就是:如果键无效了会怎样?缓存中的值如何更新? 带着这些问题阅读代码,就是结合上下文。或者说因为你有了一个目标,你会变得享受阅读的过程。 你甚至可以自己做一些假设,然后在代码中寻找验证。
你有点像侦探:你想发现代码的真相,代码的逻辑,代码是如何像故事一般上下流动的。
#把实例跑起来并与之交互
源码就像乐高积木,只是已经组装好了。 如果你想了解它们是怎么组装在一起的,你需要和它交互,有时甚至要把它拆开。 阅读同一模块的老版本同样有帮助。从 Git 中阅读版本差异,试着弄清楚特定的特性是如何实现的 (修改日志在这个场景很有用)。 举个例子,我发现 Lua 的第一个版本相当简单,这可以帮助我了解作者最初的设计理念。
Debug 是另一种与代码交互的方式。试着在代码中加一些断点(或打印一些变量值), 然后弄明白打印到控制台中的所有输出。
如果你对代码了解比较透彻了,试着对代码做一些修改,重新 build 并把它跑起来。 最简单的方式是试着调整配置项,去看不同配置的运行结果。 之后你可以试着添加一些细微的特性。 如果这些特性对其他人也有用,你应该把代码贡献到上游。
#了解数据结构间的关系
“糟糕的程序员担心代码,优秀的程序员担心数据结构和它们的关系。” -Linus Torvalds
数据结构是一个程序中最重要的元素。用笔或者你喜欢的其他工具画出数据结构间的关系。 这个图就是源码的映射。你会在阅读过程中时常参考这个图。一些工具比如scitools 可以用来生成 UML 类图。 (译:这个方法用在写代码中能节约翻 Model 声明文件的时间,推荐用纸笔,不占屏幕)
#了解模块间的依赖关系与边界
大项目中会包含许多模块,一个模块经常只拥有单一职责。 这有助于我们减少代码复杂度,在适当的层级上做抽象。 模块的接口是抽象的边界,我们可以一个接一个地阅读模块。 如果你在阅读一个使用 Make 构建的C/C++项目,Makefile 是了解模块间如何组织的好切入点。
边界本身也很有用。优秀的代码组织得很好,变量名与函数名的命名风格体现着可读性。 你不需要阅读全部源文件,你可以忽略不重要的或你熟悉的部分。 如果你确定一个模块是仅仅是为了被解析而设计的(just designed for parsing), 那么你已经大致了解了它的功能;那么你就可以跳过不读这个模块。 当然,这将大大节约时间。
#使用测试用例
测试用例也是帮助代码理解的一个很好的补充。测试用例就是文档。 当你在阅读一个类时,试着把对应的测试代码一起读了。 测试用例能帮你弄清一个类的接口,和该类的典型用法。 集成测试用例可以让你顺着走过程序的整体流程,适合输入一些特殊值并 debug 运行。
#点评
为什么不在花了不少时间阅读一个项目后,写一篇代码点评呢?就像写一篇书评一样。 你可以写下代码中好的和不好的部分,还可以记下你从中学到了什么。 攥写这类文章可以帮助你阐明自己的理解,也有助于其他人阅读源码。
#一些好书
我发现阅读代码是一个远超我想象的广泛话题。没有系统性训练该技能的方法。 总而言之,不断练习,找到你自己的方式。下面是一些帮助你提升代码阅读能力的好书:
《Design Patterns: Elements of Reusable Object-Oriented Software》
《Clean Architecture: A Craftsman’s Guide to Software Structure and Design》
《How to Read a Book: The Classic Guide to Intelligent Reading》
啊哈,这本书对程序员也很有用。
#译者的话
这篇文章是为苦于不知从何开始阅读陌生项目代码的人(包括我)准备的。 截止撰文时间为止,我认为这篇文章是在同话题中较为务实的一篇, 不仅包含理念,还包含不少具体可实践的建议,因此我想把它分享给更多人。 而且我想如果它有中文版本,愿意读下去的人会多一些,我也方便向周围的人分享,于是我向 Nic 申请了翻译权。 我把标题改了改,这样搜索“源码”“学习”、“阅读源码”都能搜到。
这篇文章对我而言,最大作用是帮我突破了“不需要有意地专门阅读代码”的心理障碍。 我一直以为既然“写代码的时间中十之六七都是花在读既有代码上”(语出《Clean Code》), 就没必要再花时间专门读代码,我大错特错。
个人经验:驱动我“专门读代码”的最大动机是好奇心,和小孩拆小物件为了看内部构造差不多。 学习 OO 设计模式,我建议阅读《Head First 设计模式》 ,这本书超有趣,比四人帮那本删减了一些不常用的模式,但是你能轻松读下去。
阅读了解项目代码是参与(开源)项目的第一步,希望这篇文章能帮助你参与到心仪的项目中去。 这也可以帮助你在工作中了解同组同事的工作,而“了解同组同事的工作对工作有诸多潜在益处” 。 来吧,花点时间挑一个看上眼的项目(或者就读你手头的项目别人写的部分), 找到你最感兴趣的功能,读一读它是怎么实现的。