阅读代码的姿势

作为程序员坚持阅读代码是持续自我提升的有效方法之一。有心的程序员不仅要找到优秀代码阅读,更要注意阅读的方法,从整体架构掌握再逐步深入细节,先“广度优先”,再选自己感兴趣的方面进行“深度历险”。

一般地,在一个程序员的日常工作之中,绝大多数时间都是在「阅读代码」,而不是在「写代码」。但是,阅读代码往往是一件很枯燥的事情,尤其当遇到了一个不漂亮的设计,反抗的心理往往更加强烈。

事实上,变换一下习惯、思路和方法,代码阅读其实是一个很享受的过程。阅读代码的模式,实践和习惯,集大成者莫过于希腊作者Diomidis Spinellis的经典之作:Code Reading, The Open Source Perspective.。本文从另外一个视角出发,谈谈我自己阅读代码的一些习惯,期待找到更多知音的共鸣。

工欲善其事,必先利其器

首先,阅读代码之前先准备好一个称心如意的工具箱,包括IDE, UMLMind Maping等工具。我主要使用的编程语言包括C++, Scala, Java, Ruby;对于Scala, Java, Ruby编程,我更偏向使用JetBrain公司的产品;而对于C++编程,我依然还在使用Eclipse,因为Clion的特性还没有让我满意。

其次,高效地使用快捷键,这是一个良好的代码阅读习惯,它极大地提高了代码阅读的效率和质量。例如,查看类层次关系,函数调用链,方法引用点等等。

拔掉鼠标,减低对鼠标的依赖。当发现没有鼠标而导致工作无法进行下去时,尝试寻找对应的快捷键。通过日常的点滴积累,工作效率必然能够得到成倍的提高。

力行而后知之真

阅读代码一种常见的反模式就是「通过Debug的方式来阅读代码」。作者不推荐这种代码阅读的方式,其一,因为运行时线程间的切换很容易导致方向的迷失;其二,了解代码调用栈对于理解系统行为并非见得有效,因为其包含太多实现细节,不易发现问题的本质。

但在阅读代码之前,有几件事情是必须做的。其一,手动地构建一次工程,并运行测试用例;其二,亲自动手写几个Demo感受一下。

先将工程跑起来,目的不是为了Debug代码,而是在于了解工程构建的方式,及其认识系统的基本结构,并体会系统的使用方式。

如果条件允许,可以尝试使用ATDD的方式,发现和挖掘系统的行为。通过这个过程,将自己当成一个客户,思考系统的行为,这是理解系统最重要的基石。

发现领域模型

发现「领域模型」是阅读代码最重要的一个目标,因为领域模型是系统的灵魂所在。通过代码阅读,找到系统本质的模型,并通过自己的模式表达出来,你才能真正地Hold住了系统,否则一切都是空谈。

首要的任务,就是找到系统的边界,并能够以「抽象的思维」思考外部系统的行为特征。其次,寻找系统潜在的,并能表达系统的重要概念,及其它们之间的关联关系。

细节是魔鬼

纠结于细节,将导致代码阅读代码的效率和质量大大折扣。例如,日志打印,解决Bug的补丁实现,某版本分支的兼容方案,某变态用户需求的锤子代码等等。

阅读代码的一个常见的反模式就是「给代码做批注」。这是一个高耗低效,投入产出比极低的实践。越是优雅的系统,注释越少;越是复杂的系统,再多的注释也是于事无补。

我有一个代码阅读的习惯,为代码阅读建立一个单独的code-reading分支,一边阅读代码,一边删除这些无关的代码。

$ git checkout -b code-reading

删除这些噪声后,你会发现系统根本没有想象之中那么复杂。事实上,系统的复杂性,往往都是之前不成熟的设计和实现导致的额外复杂度。

适可而止

阅读代码的一个常见的反模式就是「一根筋走到底,不到黄河绝不死心」。程序员都拥有一颗好奇心,总是对不清楚的事情感兴趣。例如,消息是怎么发送出去的?任务调度工作原理是什么?数据存储怎么做到的等等;虽然这种勇气值得赞扬,但在代码阅读时绝对不值得鼓励。

还有另外一个常见的反模式就是「追踪函数调用栈」。这是一个极度枯燥的过程,常常导致思维的僵化;因为你永远活在作者的阴影下,完全没有自我。

我个人阅读代码的时候,函数调用栈深度绝不超过3,然后使用抽象的思维方式思考底层的调用。因为我发现,随着年龄的增长,曾今值得骄傲的记忆力,现在逐渐地变成自己的短板。当我尝试追踪过深的调用栈之后,之前的阅读信息完全地消失记忆了。

也就是说,我更习惯于「广度遍历」,而不习惯于「深度遍历」的阅读方式。这样,我才能找到系统隐晦存在的「分层概念」,并理顺系统的结构。

发现她的美

三人行,必有我师焉。在代码阅读代码时,当发现好的设计,包括实现模式,习惯用法等,千万不要错过;否则过上一段时间,这次代码阅读对你来说就没有什么价值了。

当我发现一个好的设计时,我会尝试使用类图,状态机,时序图等方式来表达设计;如果发现潜在的不足,将自己的想法补充进去,将更加完美。

例如,当我阅读Hamcrest时,尝试画画类图,并体会它们之间关系,感受一下设计的美感,也是受益颇多的。

Hamcrest匹配器

尝试重构

因为这是一次代码阅读的过程,不会因为重构带来潜在风险的问题。在一些复杂的逻辑,通过重构的等价变换可以将其变得更加明晰,直观。

对于一个巨函数,我常常会提取出一个抽象的代码层次,以便发现它潜在的本质逻辑。例如,这是一个ArrayBuffer的实现,当需要在尾部添加一个元素时,既有的设计是这样子的。

def +=(elem: A): this.type = {
  if (size + 1 > array.length) {
    var newSize: Long = array.length
    while (n > newSize)
      newSize *= 2
    newSize = math.min(newSize, Int.MaxValue).toInt

    val newArray = new Array[AnyRef](newSize)
    System.arraycopy(array, 0, newArray, 0, size)
    array = newArray
  }
  array(size) = elem.asInstanceOf[AnyRef]
  size += 1
  this
}

这段代码给阅读造成了极大的障碍,我会通过快速的函数提取,发现逻辑的主干。

def +=(elem: A): this.type = {
  if (atCapacity)
    grow()
  addElement(elem)
}

至于atCapacity, grow, addElement是怎么实现的,压根不用关心,因为我已经达到阅读代码的效果了。

形式化

当阅读代码时,有部分人习惯画程序的「流程图」。相反,我几乎从来不会画「流程图」,因为流程图反映了太多的实现细节,而不能深刻地反映算法的本质。

我更倾向于使用「形式化」的方式来描述问题。它拥有数学的美感,简洁的表达方式,及其高度抽象的思维,对挖掘问题本质极其关键。

例如,对于FizzBuzzWhizz的问题,相对于冗长的文字描述,流程图等方式,形式化的方式将更加简单,并富有表达力。

3, 5, 7为输入,形式化后描述后,可清晰地挖掘出问题的本质所在。

r1: times(3) => Fizz ||
    times(5) => Buzz ||
    times(7) => Whizz

r2: times(3) && times(5) && times(7) => FizzBuzzWhizz ||
    times(3) && times(5) => FizzBuzz  ||
    times(3) && times(7) => FizzWhizz ||
    times(5) && times(7) => BuzzWhizz

r3: contains(3) => Fizz

rd: others => string of others

spec: r3 || r2 || r1 || rd

实例化

实例化是认识问题的一种重要方法,当逻辑非常复杂时,一个简单例子往往使自己豁然开朗。在理想的情况下,实例化可以做成自动化的测试用例,并以此描述系统的行为。

如果存在某个算法和实现都相当复杂时,也可以通过实例化探究算法的工作原理,这对于理解问题本身大有益处。

Spark中划分DAG算法为例。假设GFinalRDD,从后往前按照RDD的依赖关系,依次识别出各个Stage的起始边界。

Stage划分算法

  • Stage 3的划分:

    1. GB之间是Narrow Dependency,规约为同一Stage(3);
    2. BA之间是Wide DependencyA为新的FinalRDD,递归调用此过程;
    3. GF之间是Wide DependencyF为新的FinalRDD,递归调用此过程;
  • Stage 1的划分
    1. A没有父亲RDDStage(1)划分结束。特殊地Stage(1)仅包含RDD A
  • Stage 2的划分:
    1. RDD之间的关系都为Narrow Dependency,规约为同一个Stage(2);
    2. 直至RDD C, E,因没有父亲RDDStage(2)划分结束;

最终,形成了Stage的依赖关系,依次提交Stage(TaskSet)TaskScheduler进行调度执行。

独乐乐不如众乐乐

与他人分享你的经验,也许可以找到更多的启发;尤其对于熟知该领域的人沟通,如果是Owner就更好了,更能得到意外的惊喜和收获。

也可以通过各种渠道,收集他人的经验,并结合自己的思考,推敲出自己的理解,如此才能将知识放入自己的囊中。

原文链接:http://www.jianshu.com/p/3e6d4c520719

时间: 2024-10-26 07:16:16

阅读代码的姿势的相关文章

从阅读Discuz的核心代码并给出注释的经历分析程序员该如何阅读代码?

本文标签:   程序员 php Discuz的核心代码 框架 深度学习框架 阅读优秀的代码,是技术水平成长的最佳途径.记得每个进来的新人,我都做过阅读优秀代码的要求,但几乎都只能坚持很少一段时间而已. 前晚大家还在开玩笑的讨论,都是因为看了前人的一些写法,才学会了一些乱七八糟的花招. 晚上我又开始重新阅读Discuz的核心代码,花了1h多的时间,才完成一个core文件的注释. 注释后的代码: <?php /** * [Discuz!] (C)2001-2099 Comsenz Inc. * Th

[Android阅读代码]android-async-http源码学习一

android-async-http 下载地址 一个比较常用的Http请求库,基于org.apache.http对http操作进行封装. 特点: 1.每一个HTTP请求发生在UI线程之外,Client通过回调处理HTTP请求的结果,使得Client代码逻辑清晰 2.每一个请求使用线程池管理执行 3.支持gzip , cookie等功能 4.支持自动重试连接功能 [Android阅读代码]android-async-http源码学习一,布布扣,bubuko.com

阅读代码的方法

1.如何阅读大段代码 当我们在阅读一大段代码时 需要先理清楚框架,因为在代码里存在太多的细节,而我们大脑并不能同时处理很多信息. 框架是什么,框架就是抽象的,重要的,宏观的. 怎么才能认识到框架 我们可以在大脑里先设想下这个框架应该是怎样的,需要做哪些操作,然后在代码里找证据,适当时进行纠正. 先框架 再细节 最终在大脑中得到整体 框架 + 细节 -> 整体 2.阅读代码的一种方法 与实际运行情况进行对比来加深和纠正我们对代码的认识. ps:哲学高度:天下大事必做于细,天下难事必做与易.

阅读代码分析工具Understand 2.0试用

Understand 2.0是一款源码阅读分析软件,功能强大.试用过一段时间后,感觉相当不错,确实能够大大提高代码阅读效率. 因为Understand功能十分强大,本文不可能详尽地介绍它的全部功能,所以仅仅列举本人觉得比較重要或有特色的功能,以做抛砖引玉之举. Understand 2.0能够从http://www.scitools.com/下载到,安装后能够试用15天. 使用Understand阅读代码前.要先创建一个Project,然后把全部的源码文件增加到这个Project里.这里我创建了

微软资深软件工程师:阅读代码真的很难

编者按:原文作者EricLippert是一名资深软件设计工程师,从1996年起一直在微软开发部门任职,协助设计并实现VBScript. JScript.JScript.NET.Windows Script Host.Visual Studio Tools for Office 和 C#. Escalation的工程师JeremyK在他的博客中问到: 你是怎么教人们快速深入挖掘不熟悉的代码(不是自己所写的)?我学习如何编程的方法很传统 —— 自己动手编码.但我现在很纠结:到底是集中精神阅读源码,还

从《如何高效阅读》到如何阅读代码

?<如何高效学习>是一本主张通过整体性学习的方法来提高学习效率的书.书中结合信息的几种不同类型(随意的.观点的.过程的.具体的.抽象的),从信息的获取.理解.拓展.纠错和应用这几个逐步深入的层次,提出了如何高效学习的假说,指出了不少具有实用价值的方法,例如快速阅读.笔记流.比喻内化.图表.纠错和项目应用等. 刚看这本书的时候,我是一口气大约在一个多小时快读完的,记了十几条笔记.后面两天都回头来翻一番书后的总结和自己的笔记,每次都有些新的感想.这里尤其想整理的是:这本书给自己在阅读代码方面的几点

怎么阅读代码

一段程序,或者是一个长篇代码,我们应该怎么阅读 1.首先,你必须搞清楚这个类所处的位置,也就是你当前看的是那一部分的,哪一模块的,这就提出了一个概念,你必须要把你看的程序分成一个个模块 他的父类有哪些,他实现了哪些接口 2.这个类的切入点在哪,这个最重要,必须找到切入点,否则你读起程序来只会一头雾水 比如有的类,找他的初始化部分

如何阅读代码(译)

英文原文地址:https://spin.atomicobject.com/2017/06/01/how-to-read-code/?utm_source=wanqu.co&utm_campaign=Wanqu+Daily&utm_medium=website 作者:WILLIAM SHAWN "我讨厌阅读别人的代码"在所有经验层次的程序员中都普遍存在着这个问题.然而,这又是一个必备技能,特别是程序员直接使用现成代码时,如果你以正确的角度和正确的工具来处理,那将是一场很享

【009】阅读代码

阅读下面的代码,思考代码运行结果是什么? void swap(int x,int y) { int t; t =x; x=y; y=t; } void main() { int a=3, b=4; cout<<"a="<<a<<", b="<<b<<endl; swap(a, b); cout<<"a="<<a<<", b="<