码农的自我修养 - 如何阅读别人的代码

关键词:Dokuwiki, Doxygen, cscope, git , twitch.tv, freecodecamp.org

 

如何提高你的代码技艺,显然这个和写作一样,那就是不停的写,不停的读,然后不断重复。

Step 1,2,3:

1. Write More Code

2. Read More Code

3. Repeat Daily

对于学习和提高新技能(如编程)的一个最重要的方面是:接触到高数量、高质量的专业例子。所以不断的阅读理解高质量项目的代码,对提高自己代码水平很有帮助。

Exposure to high quantity, high quality examples of expertise is one of the two main factors that dictate how quickly and effectively people learn new skills. (The other is deliberate practice.)

关于阅读别人的代码,有可能你使用了一个开源库,你要解决某个问题而去查看它的代码。有可能你是一个项目开发组的一员,很多人一起协作完成这个项目,而项目中的代码要不断修改、维护,这就需要去理解别人写的代码。

可是,有的时候,当你阅读别人代码的时候是不是感到很痛苦。

确实,理解别人的代码会很难,感觉就像进了一个迷宫。这件事令人沮丧,有时会有挫败感,感觉自己太笨。可能是各种经验级别的程序员都会这样,包括STCer(自学成才者Self-Taught Coder)的码农,还有即使你是大神,你也会头疼。

你也是这样吗?

"我讨厌阅读别人的代码 "是所有经验水平的软件开发者的共同心声。可这是一项必要的技能,尤其是现在的程序的代码越来越庞大复杂,需要很多人在一个代码库上滚动开发,如果你用正确的观点和正确的工具来对待它,它可以是一个令人愉快和有启发性的体验。

我们讨厌阅读别人的代码的原因是我们没有自己写。这并不是说我们都有一个秘密的信念,认为我们是这个星球上最好的编码员,没有人可以像我们一样写代码。这是因为创建代码有一个紧张的思考过程,而一个被动的读者并没亲身体验从而不会了解其中的很多信息。

因为代码是一种模式的表示,而不是真正的模式。它是计算机可以读取的那个模式的代表。如果你是原作者,你的脑子里有那个模式,读代码只是刷新了这个模式。你实际上是从模式到代码,这是个简单的方向。但是,那些脑子里没有模式的人必须从代码到模式。而这要难得多。部分原因是许多编程语言在设计时考虑到了机器,而不是人类读者。

你在屏幕上看到的代码可能涉及到很多人。它可能涉及到辩论和合作。它可能花了几周的时间来确定一个版本,以符合一些没有记录的约束条件,而这些约束条件只有原作者的头脑中知道,但你不会知道这些。

作为一个读者,你所看到的只是成品,而且,除非你做一点调查,否则你所拥有的唯一背景信息就是屏幕上的其他文字。

而对自己写的代码而言,你已经了解了你自己的代码,从它的整体结构,到你使用的算法,到它们的数据范式,到你所做的具体决定,而且你以你喜欢的风格来写它。

你必须要弄清楚别人的代码是如何工作的。你必须弄清楚他们的数据范式,他们使用的算法,代码是如何流动的,你必须破译那些古怪的代码,他们的命名规则、格式等等都不是你所习惯的。

事实上,你自己的代码可能并不像你想象的那样清晰。试着回过头来读一读你5年前写的代码。你已经忘记了关于代码的大部分东西,在那个时候要把它拼凑起来是很难的,尤其是你的风格习惯从那时起就已经发生了变化。你很可能会发现,你当年的代码太复杂了。一个更干净的方法现在可能从你的经验中显而易见,或者只是一个更符合你当前方法的方法。

以下是关于代码理解方法的一些建议:

- 首先确定一下阅读代码的目的。

这要看你为什么要读它。

如果你试图追踪一个错误的话:阅读代码帮助你对系统有一个概览(数据流、控制流、主要模块),根据日志条目将错误隔离到一个特定的模块,然后通过代码追踪(真实的或模拟的)来查找bug。

如果你作为一个项目的新开发者,或编写一个插件,或进行设计审查:可能要阅读主循环,还要追踪事件和数据流。然后,根据需要,深入了解细节。

如果是出于教育目的(即,提高你的技能,或学习什么是 "好的代码"):确保你从一些已知的工作开始,这些代码写得很好,并有文档说明。

- 从何处出发?记住你面对的仅仅是代码。不管再复杂再庞大的项目,也只是有各种库、框架、外部API组成,也许一开始你不熟悉,但早晚你都会熟悉的。就像小学生觉得课程很难,但当小学毕业了,回头看,其实一切只是水到渠成的事。关于软件代码,我们端正心态,将庞大的项目代码分成各个模块、库、框架等,然后找出它们之间的依赖关系,逐个击破。

有一些模块可能是非常通用的,并与代码库的其他部分解耦。它们代表了更小、更容易消化的功能片段,在尝试处理更大的应用程序之前,你应该熟悉它们。

试着在其他一些源文件中找到对这些模块的引用,以了解它们的使用方式和时间。这可以让你感觉到它们是如何融入整个应用程序的。

- 对于非琐碎的代码,我首先会尝试了解大局:模块化结构。通常,我会画一个图表。我会看每个模块,以及主要的功能,并试图了解它们的作用,而不是关注细节代码。这让我了解代码的 "环境"。

这样做之后,我通常会知道 "关键代码 "是什么。然后,我将对其进行剖析,既通过阅读,也可能通过图解所使用的算法(如果合适)。我所寻找的是对代码所解决的问题的思考。

- 有时,我在阅读代码时,会注意到写代码的风格或方法,或解决一些小的细节问题。这些我都会保存在一个剪贴簿中(我为此保留了一个个人的dokuwiki),并附上观察结果的来源的反向链接。多年来,这些反向链接对我非常有用。

我有时会遇到一种我从未遇到过的算法或参考资料(世界之大,高手如云),并将其记下。举个例子,有一天我在Quora上读到关于快速反平方根算法的文章。它已经被使用了几十年,但我却从未遇到过这段精彩的代码。

我的建议是:主动阅读代码,而不是被动地阅读。以任何能让你在几个月后找到它们的形式做笔记。每隔一段时间,浏览一下这些笔记。

- 找到一个你感兴趣的点(Pick a specific point of interest); Find a loose thread,找一个不太复杂的线索作为起点; Follow the trail.

找出你知道的代码所做的一件事,然后从结尾开始向后追踪这些动作。例如,你知道你正在查看的代码最终会创建一个包含电影标题列表的文件。找出代码中的什么地方--具体的几行--它产生了那个文件。

然后,再向后退一步,找出它是如何将信息放在文件中的。

然后,再向后退一步,找出这些信息的来源。

以此类推...

让我们把这些相连的代码片段称为 "行动链"。

不可避免的是,使用这种方法将引导你穿过一堆不同领域的代码。这可能会让你对一些事情有很好的了解,比如。

代码主体是如何组织的(变量在哪里定义,不同类型的函数在哪里,等等)。

这个人的编码风格。

写代码的人是如何思考和解决问题的(这更难描述,但你看到的例子越多,它就越直观)。

通过这样做,你会逐渐开始理解越来越多的完整的代码。所以,在你开始的时候:

[一个对你来说没有什么意义的大的代码文件] 。

现在你将会看到:

[仍然是一个大的代码文件,但你现在明白了一些特定的部分] 。

这几乎就像你最初站在一个漆黑的房间里,然后,房间里的不同灯光一次次被打开,逐渐显示出房间外观的更多细节。

- 事实上,答案取决于你的目标是什么--以及代码的状态是什么。

如果我只是在寻找一个错误,我会尽可能少地阅读代码,主要依靠调试器来引导我到我需要关注的部分。

如果我将在代码中广泛地工作,我会使用任何文档来了解事物的整体结构--然后在需要时深入到各个功能中。由于我主要是用OOP语言工作--我首先会看一下类的结构。如果没有合适的文档--我会用 "Doxygen "来浏览代码--它能自动生成带有类结构等的文档--即使是完全没有文档的代码。然后,当我浏览这些类时--从基础类到高级类--我会添加Doxygen注释来提醒自己我发现了什么。

如果代码没有很好地缩进/排版,我会按照公司的标准或我个人的喜好来设置的。

但这并不容易。

抵制重写的诱惑是非常困难的。

从头开始重写代码比弄清现有的东西更容易,这是非常诱人的(偶尔也是真的)。 

- 对源代码进行索引。我主要使用cscope,并将其与vim编辑器一起使用。这有助于在任何给定的函数的前面和后面移动,找到不同函数的定义。

- 一般情况下,拿到一份代码,先要找到main函数,找到程序的入口,看下启动过程的配置过程,看下包含了哪些头文件,初始化了哪些类,还有一些设置选项是如何设置的。

- 这在很大程度上取决于项目和编码风格,所以很难给出一个具体的方法,但以下是一般规则。

首先要自上而下地理解代码应该做什么和为什么,而最初忽略如何做。例如,数据科学代码通常将数据集(如表格或CSV文件)作为输入和输出,所以你可以通过查看读取的数据和产生的数据来了解管道的每一部分。然后,你可以通过查看输入的来源,或输出的用途来继续。单元测试和文档是了解代码应该做什么的其他好方法。

了解代码库中使用的设计模式:例如,它主要是以功能性的还是以面向对象的方式编写的?一旦你对代码的组织方式有了了解,就可以利用这一点来弄清楚代码的具体主要部分是什么,以及你将如何浏览它们。

在这一点上,你应该开始对代码做一些事情:写一些测试,重构一些你认为不会有什么影响的东西。然后改变一些你认为会产生变化的东西,看看发生的事情是否和你预测的一样。尽量确保你有非常快的迭代--能够在30秒内做出改变,会让你对代码的感觉比需要5分钟测试的改变快得多。

- 不能运行的代码是没有意义的。所以在尝试理解某些代码前,要在本地机器上搭建好代码可以运行的环境。要保证环境的一致性,比如一个团队的成员,使用一个统一的开发环境。在阅读理解代码前,先编译运行一下代码的生成程序或者使用生成的库的功能。还可以在代码里加上打印log来运行,这样就能直观的理解代码执行的行为、控制流和详细数据。

- 如果对于要理解的代码,不管是库也好,还是某个软件项目也好,先寻找其正式的文档,大概了解一下相关内容,然后再根据具体情况,详细查阅。

- 学会挖掘更多的信息

当你第一次潜入一个成熟的代码库时,你可能不觉得自己是一个开发者。你可能觉得自己更像一个考古学家,一个私人调查员,或者一个圣经学者。这很好,因为你有一堆铲子供你使用。

如果你有幸在一个从一开始就处于版本控制中的代码库上工作,那就庆祝吧。你可以访问大量的元数据,这将使你理解代码和上下文的工作更加容易。我假设你使用的是Git,但如果你使用的是SVN,也是一样的。

git blame

你可以在一个文件上使用git blame来获取每一行的作者姓名、最后修改日期和提交哈希值。熟悉作者的情况。如果你运气好,可能只有几个人,而且他们可能都还在和你一起工作,所以你可以把他们作为一种资源。如果你不走运,可能会有几十个你从来没有听说过的作者。

不管怎么说,试着了解一下谁是主要的贡献者。如果你遇到了一个你无法理解的奇怪的功能,可以用git blame来找出作者,并追踪他或她来询问。

git log

使用 git log 来查看整个 repo 的提交历史。这个命令会打印提交信息,所以如果您想搜索提交信息中提到某个功能的提交,别忘了使用grep:git log | grep someFunction -C 3 (-C 3 会显示您的匹配信息的三行内容)。

git log 还可以用 -p 标志显示单个文件的历史:git log -p index.js。注意那些最近一直在修改东西的人,这样你就知道在出现问题时该从哪里下手。

- 使用代码版本管理工具,追溯代码的历史修改。在要理解的代码源文件上运行git blame,看看它的哪些部分最近有变化。最近改变的一大块代码可能会让你了解开发团队最近几周所面临的一些挑战。也许他们引入了一个新的库,也许他们一直在努力配置一个效果不太好的库,也许只是有一些需要定期更新的模板代码。

你可以查看任何你想要的提交,并把它当作项目中最新的提交来运行。你可能想查看一些难以追踪的错误开始出现之前的最后一次良好的提交,或者你可能只是觉得无聊,想从历史的角度看看你的项目在你进入它之前的几年的情况。

如果你的项目被托管在GitHub或类似的地方,你可以通过阅读issues、pull requests和code reviews来获得大量的观点。注意那些被讨论最多问题,这些可能是你最终会遇到的痛点,你会提前知道如何处理它们。

- 仔细寻找代码中的各种注释,对于代码的各种细节,穿插在代码中的注释有时会给出关键的指示。尤其是一些特殊逻辑,比如是针对某些特定情况的处理,或者特定bug的修正,还可能是一些临时解决方案还有待完善的代码。

但换种角度看,有时只能把注释看作是某种提示,一些过期的没人维护的注释读取后会更让人迷惑。程序员的眼睛会自动掠过那些绿色的注释字体,所以可能这些注释解释的东西早已不存在了。

- 使用各种工具。如果能够通过debug工具设置断点(break points),那直接调试来了解程序的具体运行情况,比如单步执行,查看具体的数据流和控制流信息,这是程序员的最终兵器。

对于程序员来讲,除了把代码编译成可执行程序外,本来就有很多工具协助我们调查代码。比如编译器会提示警告、IDE有很多代码分析工具等。

还有代码编辑工具,有很多方便理解代码的功能,包括查找一个函数被引用的地方、查找变量被使用的地方等。

- Read the specs。阅读说明书或规格书。规格书是最新的注释。阅读单元规范(unit specs)以弄清函数和模块应该做什么,以及它们被设计用来处理什么样的边缘情况。阅读集成规范(integration specs),以弄清用户将如何与你的应用程序互动,以及你的应用程序支持什么样的工作流。

- 在理解代码过程中,亲自为代码添加注释。 遇到不理解的,可以先记下来,等以后时机合适再处理。按照你的需要,对有效代码进行注释。这样把注释写出来,可以更好的在脑海里形成清晰完整的想法,并记录下来以防遗忘。

- 通过微重构来实际介入代码的设计和逻辑,比如使用重构工具来对某些变量或函数进行改名,使其更适合其实际用途。通过修改代码,在心理上会建立一定的优势,把这些代码当作自己的代码,增加了成就感和自信心。而且修改过后,代码和你的思路就会更好的统一和同步起来,有利于更好地进行后续理解。不过,请先在本地branch上修改,并保存修改的历史纪录,如果有修改不合适的地方,可以退回到某个版本。是否将你的修改提交到正式代码库,将根据情况而定。

比如你可以使用合适的重构工具,发现变量或方法的名字与代码中的名字不一致,可以在本地分支中重新命名它们。这样做在名称和代码的意图之间进行直接的思维映射,而不是多了一个额外层次。

在代码中,变量和函数的名字是至关重要的,编码时,没有什么比给变量和函数起一个好名字更重要和更废脑筋的事情了。因为代码的意义和作用,就是通过变量名或函数名来表达的,而不仅是满足语法要求,随便一个名字作为标识就可以的。

- 为需要理解的代码写单元测试用例,如果已经有了,可以直接使用。通过运行单元测试用例,可以将这部分代码作为独立的沙箱来观察其行为。可以设置不同的条件和数据,来深入了解这部分代码的行为。为了更好的理解代码,可以先不使用assert机制,让代码自由执行,观察其运行结果。

- 除了代码级别的单元测试用例,其他的可运行的测试也可以使用。比如一些代码实现了某些功能,你可以直接通过测试产品的某些功能,来观察和理解软件所实现的功能。因为软件的源头是用户需求,而用户需求应该全部反应在各种测试用例上。测试可能很冗长、繁琐,但要保持耐心,完整的理解代码的功能的方方面面,不靠充分完整的测试是很难办到的。

- 没有什么在代码里添加某个功能,或者查找某个bug,能够更快的理解代码了。一旦你修改了很多bug或者添加了不少功能,慢慢的这些代码你一看到就胸有成竹了。

- 识别代码中的style,方便理解代码后在修改代码时要遵循编码规范。一个项目中应使用同一个编码规范,这样方便大家理解和修改别人的代码。扩展开来说,不只是如何命名变量名、代码中空格如何使用,括号的安放,还有其他的一些也可以列入规范。比如,一些约定的宏定义的使用、一些相似代码的复用、一些重用数据结构的使用方式等。总之,在代码中保持一致性很重要,尽可能让别人看起来这些代码是出自一个人或一个团队之手。

- 代码看起来什么样,就好比一个人的外表。如果你看到一段垃圾代码,那也差不多也会认为写这个代码的人一定面目可憎。所以你自己写代码时,也要写的漂亮些才好。

- Make use of good patterns. 保持良好的编程范式或者叫设计模式,这也是良好的编程实践之一。在阅读别人的代码时,你会识别出这些良好的设计模式,可能有些设计模式你自己也在用,这样就能更快的理解代码。

使用良好的编程模式,包括但不限于以下方面:不要重复,包括定义意义相同的变量、相同功能的函数等,同一个东西应该只有一份;设计可扩展的代码;用最大的安全性来构建代码;效率或性能也要考虑。

- 如果代码的控制流或数据流太过复杂,你就需要一些工具来帮助你,比如使用流程图。

除了流程图,使用伪代码或者其他类似的工具也可以, 比如你如果熟悉思维导图,就用这个工具也可以。

在你自己写程序前,将流程图或伪代码写出来也是很有帮助的,或者把注释当作类似的描述工具也是可以的。

- 对于良好的面向对象的编程,通过类的名字和方法的名字就能理解其功能,而不需要查看其实现细节。

- Ask questions。可以问些问题。自己拿到代码琢磨固然是锻炼提高自己理解代码水平的最好方法,只有自己动手动脑,才能记忆深刻并化为己用。但这也有缺点。首先,这会花费较多的时间,遇到很多不清楚的地方,都要自己不断推演,不断猜测,来复原代码本来的思路或意图,而如果有条件向代码作者或理解代码的人请教一些问题,可能会少走弯路,或者获得一些更好的方法。比如,可以问一下不清楚的概念问题、需求问题,或者问一些资料的来源方便自己去查找。毕竟一切都有成本,关注我们需要关注的,其他的如果已经有现成的答案,为何不用。其次,对于代码来说,如果能找到第一作者是最好的,就向一篇文章一样,只有作者才真正的知道最初的意图是什么。和代码的作者聊聊,才能找到一切的源头,才可以毫无顾忌的继续前进。

- Stay positive and don‘t get lost. 保持冷静的心态。对于未知的东西,内心的抵触情绪是不自觉就会产生的。尤其当你拿到的代码很垃圾的时候,你可能很崩溃,你可能会抓狂,还会诅咒那个写出这些代码的人。可这些都于事无补。你应该庆幸,别人已经做了一个能用的方案。不管怎样,再烂的代码,只要完成了该有的功能和各种需求,就是可交付的代码,这也就是代码存在的意义。代码本身没有用,代码编译后的程序运行起来,才是最终客户评判的标准。

如果codebase代码写的很好,你可以进行吸取和学习其中的优点。如果写的很烂,可能是作者水平有限,或者时间不足,而这正好,给了你机会,来优化这些代码,使后面的人可能更好的理解和修改这些代码。

也不要认为代码理解是一个线性过程,也许你花了很多时间也不会有什么成效。也不要指望代码理解程度达到100%。不断关注重要的细节和深挖每个问题的答案,你会发现理解越来越快。

- Know the code. 理解代码后再动手修改代码。在开始优化代码或添加功能前,你要理解代码是如何工作的。你对代码理解的越多,修改代码时就更容易,会有事半功倍的效果。如果你对代码的理解不够,盲目修改代码,一是可能改起来会很吃力,另外还可能引入新的问题,那你不但没有前进,还后退的。

- Keep changes small. 一次只改动一点。理解代码的主要目的是能够修改和维护代码,所以在我们理解过程中,就可能已经开始对代码进行修改。但一定要忍住遇到的每个问题都想一次搞定,最好的实践是对原代码进行最小的必要改动,只要能够满足当前需要或者完成当前任务即可。如果你改的太多,也许一切顺利,但也许会搞得一团糟,然后要么花费很大的代价将代码整理清除,要么就要退回以前的版本,那些新加入的改动就都白做了。

- Expect to Find Garbage. 妥善处理垃圾代码。代码理解中你会发现从来没人使用的函数,或者整个文件没人用,或者一段被注释了很多年没人管的代码。不要停下来为此花费时间,不要考虑这些没用的代码,也不要担心将这些东西删除掉。如果这些代码的存在是有原因的,会有人在code review中将其标记,然后下一个阅读代码的人就不会遇到同样的麻烦了。

-  尝试观看现场直播的编程(或与同伴结对编程),这对理解别人代码和加强自己变成技能多有好处。

Free Code Camp在其最近的Medium博客更新中宣布,他们将在Twitch.tv上推动增加直播编程的小时数。 

- 理解代码就好像是构建代码的逆向工程。复杂的程序,也是从无到有,从简单原型,然后不断添加新功能,修改bug,优化性能,一步步的演进到当前的状态。所以在我们理解一段代码时,参考代码的变化历史和其中经历的各种情况,就能还原出很多有用的东西,有助于我们完全掌握和理解这些代码。

------------------------------------------------ 

当你对阅读理解别人的代码驾轻就熟时,就形成了一个奇妙的自我循环:你阅读了更多的代码;你获得了更快、更有效地理解代码的能力;因此你能够消费更多的代码;等等。

而且还不止于此:你也会在自己的编码中看到巨大的积极收益。怎么说呢?

你将能够更快地理解你在自己的编程过程中不可避免地参考的代码样本和例子(例如,来自在线课程的东西;或来自StackOverflow帖子的片段)。

你将能够一目了然地理解你过去写的代码。(而且,不可避免的是,在以后的工作中,你会和很多不同的代码片段一起工作,所以这种能力会得到很大的回报)。

最终,这将转化为:

更少的停顿

更多的进展

这=更多的乐趣和更多的享受。

不断的多次去重复上面的建议去理解代码,你会迅速增加对整个代码库中越来越多部分的理解。

就像黑漆漆的房间里的某些部分逐渐被照亮一样,代码的某些部分也逐渐为你 "点亮",因为你了解了它们的功能。

这样做的原因是,在所有情况下,一组代码都是为了解决一个(或多个)复杂问题。所以你总是有那些 "行动链 "贯穿其中。

你越能理解代码的不同部分是如何连接的,你就越能对整个代码库有一个整体的理解。

而且,随着时间的推移,你看到的(好的)代码越多,就越容易阅读和理解所有的代码,也就越快地做到这一点。

它强调了接触高数量、高质量的专业实例的重要性。

在编程中,"高质量的专业范例"=其他程序员写的好代码。

参考:

https://dev.to/islam/how-to-read-others-code-i17

https://www.codecademy.com/resources/blog/how-to-work-with-code-written-by-someone-else/

How to Read Other People's Code – Eight Things to Remember

How to quickly and effectively read other people’s code – Self-Taught Coders

https://www.quora.com/Programming-What-is-the-best-way-to-read-other-programmers-code

https://www.quora.com/Why-is-reading-someone-elses-code-harder-than-reading-and-creating-your-own-code

https://youtu.be/xnYFV00_btU

10 Techniques That Will Make You Understand Other People’s Code Better - Fluent C++

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜流冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值