解锁优秀源代码的方法与技巧总结(上)

读码破万卷,敲键若有神。html

概述

要成为做家, 须要阅读大量的文学做品;要成为一流的开发者,须要阅读大量的优秀代码。程序设计与开发,可视之为逻辑的武学;解锁优秀代码的能力,是开发者的内功心法之一。

java

优秀源代码就像一座宝库,里面藏着关于逻辑的奇珍异宝,很是值得一探哦!web

  • 解读设计思想,理解技术原理和实现,遇到相似问题时,可以应用到解决方案中;
  • 积累好的代码,胜于从新造轮子。

怎样才算真正理解了源代码呢? 通常作到以下几点:算法

  • 预分析与反馈总结。本身来设计,思考了哪些要素;优秀的实现,考虑了哪些要素。做个对比,可以有更多领悟和收获;
  • 设计思想。蕴含了哪些设计思想,记录下来;
  • 核心技术。哪些是核心技术点,是不可替换的 ?
  • 细节与发现。有哪些值得关注的细节 ? 有什么重要的发现 ?

写一篇源码阅读笔记,记录其中的设计思想、核心技术和细节,重要或出乎意料的发现。数据库

本文以 Guava.Cache 为例,探讨如何解读优秀源代码。

编程

导图


流程与步骤

STEP0. 了解核心使用场景及预分析json

能够到官网上去了解所要解读代码的核心使用场景,有哪些优点和不足。设计模式

思考一下:若是是本身来设计,会如何来作,考虑哪些因素? 作必定的思考后,再比对别人的实现,对比中能够有更多的收获和领悟。缓存

示例: Guava.Cache 用于建立一个可靠的本地缓存。 若是我来设计,会考虑多线程访问的并发安全性,好比使用 ConcurrentHashMap 。在 Guava 实现里,还考虑了缓存相关的统计,好比命中率,这对于衡量缓存效果是很是重要的;还记录了移除缓存的缘由和监听器等。

安全

STEP1. 了解系统的总体设计及概念

与常见业务系统冗长混乱的方法迥异, 优秀的系统一般会有一个优雅的总体设计, 将一组小而简洁的概念串联起来。 先了解系统的总体框架和所基于的基本概念, 会对理解系统的构造与运行有一个整体的引导做用。 同时, 在设计本身的系统时, 也会从中得到很好的启发。

示例: Guava.Cache ,基本概念以下:

  • KV : 缓存一般是 KV 结构,基本原理是 Hash 查找;
  • CacheBuilderSpec : 缓存的规格,用来设置缓存的一些基本参数,调节缓存的行为;
  • CacheLoader : 从 Key 值计算出 Value 值的计算函数;
  • CacheBuilder : 根据 CacheBuilderSpec 及 CacheLoader 建立具体的 LocalCache;
  • Cache : 缓存对象,存储必定时间期限的KV值;
  • ObjectPool : 对象池,缓存一般要使用到池,避免过分膨胀;
  • 并发 : 能够经过 Hash 分段表来增大并发吞吐量。

STEP2. 熟悉项目代码组织结构

优秀的开源项目一般会有清晰而有层次化的项目组织结构。

拿到项目源代码, 第一件事:概览项目组织结构, 弄清楚哪些主要模块(包), 各个模块(包) 的主要做用;各个模块(包) 下面有哪些主要类, 其做用如何。 这一步能够采用大胆猜想加API文档说明的方法来完成。

一般, 最顶层包每每包含与客户端使用直接相关的类和方法, 其下层的子包完成某个子功能或特殊模块。 此外, 优秀的项目代码一般会对系统所涉及的每一个点都有一个较好的抽象, 使用一个小而简洁的类或接口来表达, 而不是混杂在大的类中。 哪怕只是一些简单的颜色字符串, 也会尽力用枚举来实现它。不少 Javaweb 项目默认采用 Controller - Service - (Dao, RPC) - Model - Utils 或 API - Service - Dependency - Biz - Domain - Common - Config - Deploy 的总体组织模式, 这进一步下降了阅读 Java 项目的难度。

示例: 因为这只是个小的功能,所以都组织在包 com.google.guava.common.cache 下。 若是要看更复杂层次的包结构,能够看 com.google.code.json , junit, Spring 等。

STEP3. 确立目标,缩小范围

在开始阅读源代码以前, 内心要定一个目标去驱动阅读。 好比说, 要探究 proxools 链接池有时获取不到数据库链接的问题, 或者弄清楚 extjs 分页控件 PagingBar & java 线程池 ThreadPoolExecutor 的内部实现。 先攻取一个小分支, 将阅读范围缩小到容易完成的程度。物理学家一上来也不是能一会儿弄懂整个宇宙的。

示例:研究 Guava.Cache ,主要是想弄清楚里面的原理和实现。

STEP4. 找到切入点

能够从外部行为切入。 阅读 API 文档, 理解其外部行为,弄清楚设计所针对的需求目标,写个 Demo 单步调试。

还能够根据实际关注点切入。 好比:使用 Proxool 链接池, 我更关心是如何获取数据库链接的, 能够从 ProxoolDataSource.getConnection 方法切入; 使用线程池, 能够从 ThreadPoolExecutor 切入; 可使用 junit ,从 TestCase 切入。 通常来讲,从所使用的客户端类切入是一个不错的选择。

示例:能够编写一个 LocalCache 的 Demo ,而后单步调试进入。


STEP5. 锁定主要和核心的类与方法

任何设计都会隐式或显式地有“关键角色” 与 “支撑角色” 的分工。阅读源代码并非盲目漫无目的的行为, 而是要先锁定主要和核心的类与方法, 做为阅读的引路灯。在 Guava.Cache 里,核心类就是 LocalCache ,而 CacheLoader, CacheBuilderSpec, CacheBuilder 都是支撑类。

STEP6: 标记主要流程, 绘制协做交互图

跳过各类细节, 主要集中于弄清楚主要流程, 由哪些模块、类以及哪些方法参与, 标记、绘制协做交互图。

示例: 建立 CacheBuilderSpec -> 建立 CacheLoader -> 建立 Cache 实现 -> 使用 Cache

STEP7: 分解细节,各个击破

完成 STEP6 以后, 一般对该框架已经有了一个总体的理解, 虽然还有不少细节不清楚。 不要紧! 优秀源码为了追求灵活性和可扩展性,具备更强的缜密度, 相对比业务系统代码更难理解一些。 尤为是细节盘根错节,相互依赖影响。

一行行代码去读,是比较笨拙的;能够将这些细节分离和提炼出多个关注点,理解关注点是如何实现的,在关注点的导引下去阅读这些细节性的代码,会更加高效一点。将多个关注点分解成多个小任务, 各个击破。 这一步须要反复屡次地进行, 可能须要查阅不少知识点和接触一些“阴暗之处”, 才能逐渐抵达系统的“真相”, 最终做出对其优缺点的合理的综合评定。

这一步比较困难,须要一些技巧和耐心。单独再来讨论这个话题。

STEP8: 实践,再实践!

由易入难,按部就班。

  • 不含复杂技术点的独立类。好比 Integer , 只要编程基础就能读懂。
  • 不含复杂技术点的含有少许交互的框架。好比 JDK Collection,commons-collections, 须要数据结构与算法基础。
  • 不含复杂技术点的交互度适中的简易框架。好比 junit ,主要是类与交互的设计。
  • 含有并发但不含交互的独立类。好比 AbstractQueuedSynchronizer , 并发工具的基础抽象类。
  • 含有并发并只含有少许交互的简单框架。好比 ThreadExecutor , Google.Cache 。
  • ???
  • 含有大量技术点和交互的实用性框架,好比 Spring , Struts 等。

模式识别与积累

代码编写和程序设计都有一些模式能够遵循。 熟悉这些代码与设计模式对理解源代码也有很好的帮助。 好比, 面向对象系统中就一般有以下几种基本模式。

独立类

独立类完成其独立的功能,会引用到其它类的方法, 但交互不会复杂。 例如 java.util.Arrays , java.lang.Integer 类;

支撑类

支撑类用来表达一个小而简洁的抽象, 它一般不直接拿来用, 而是被其它类引用来构造更复杂的功能, 好比 java.util.AbstractMap$SimpleEntry;

继承型交互关系

继承型的交互关系遵循"接口-抽象类-具体类" 的模式: 接口规定行为规范, 抽象类完成用于定制的骨架实现, 具体类则实现具体完整的功能,能够直接拿来用。 例如经典“三段式” : Map -> AbstractMap -> HashMap 。

继承性交互关系,要体会接口是怎么设计的,为何须要定义这些方法;要体会抽象类是怎么设计的,如何将通用流程和钩子方法提炼出来。

委托型交互关系

委托型交互遵循“封装或代理”模式,将一部分或所有的功能实现委托转发给其它类的实现, 可能会作一点封装或代理操做。好比 ForwardingLoadingCache , 将缓存操做所有转发给另外一个缓存。

组合与混合

实际应用中, 一般是多种模式混合而成。好比 :

  • class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> 是“接口-抽象类-具体实现”的继承性交互关系;
  • static class LocalLoadingCache<K, V> extends LocalManualCache<K, V> implements LoadingCache<K, V> 是“接口-实现类-子类”的继承性交互关系;
  • public interface LoadingCache<K, V> extends Cache<K, V>, Function<K, V> 是 “接口-接口”的继承关系;
  • LocalLoadingCache 的缓存操做实现,是委托给 LocalCache 来实现的,是“实现类-实现类”的代理关系;
  • ReentrantLock 委托 Sync 实现,而 Sync 继承自 AbstractQueuedSynchronizer。

在解决具体设计问题时, 会应用到一些常见的设计模式, 好比单例模式、观察者模式、装饰器模式、代理模式、责任链模式等, 熟悉这些设计模式也是必要的, 详见《设计模式-可复用面向对象软件的基础》 一书,在 “软件设计要素初探:基础设计模式概览”一文中亦有简短的总结。

技巧与手段

  • 边阅读边注释。对读过的地方作些必要的注释, 主要突出其用途,必要的实现细节; 能够边读边作些笔记。

  • 搭建环境, 运行, 调试。搭建好源码环境,写个单测,运行起来, 而后设置断点调试, 观察结果, 很好的跟踪方式;

  • 从简单着手, 善于分解小任务。对 Spring , Tomcat 的源代码无从下手? 从 JDK, Junit 看起; 看不懂 Extjs Grid 的代码? 从 Label, ComboBox 着手; 一个庞大的类看的吃力? 不妨将其分解成多个小任务, 各个击破。

  • 找点其它的事情作作。阅读源代码可不是这世界最美妙的事情。 若是起初不是很适应的话, 能够先读若干函数, 而后作点其它事, 好比活动活动, 听听歌看看视频, 而后再回来阅读, 反复如此, 来逐渐加强这种耐心和适应感。

心理锻炼

神秘有难度

一般会认为优秀源代码很牛逼,反而敬而远之,不敢入宝山而探之。 其实,优秀源代码很日常,与平常所写的代码,是一样的原材料和材质。不一样的是: 1. 组织条理性更强; 2. 逻辑更缜密。 这不正是所须要学习的地方吗?

此外,总会遇到看不懂,想不通的地方; 这时, 可能须要弥补下基础知识(数据结构与算法、设计模式、并发、网络协议、系统原理等), 也可能学习到某种高级技巧(函数式、位运算、字节码等), 必定不要放过这种学习机会。

耐心与毅力

阅读源码很好滴锻炼耐心和毅力,而耐心和毅力均是难得的品质。 阅读源码,起初是有些艰难,可是,一旦攻下,就为后面的前进打下很好的铺垫了。

强化练习

有两种模式:

  • 初期,能够固定时段,好比天天早上或晚上半小时阅读源代码,创建反射弧;
  • 中期,能够集中时段,强化阅读大量源代码。 有时候 “自虐”一下,会有更大的成长。


实际障碍

英语能力

emmm... 忘了说关键的一点了,阅读源代码须要必定的英语能力。 怎么办呢 ? 学呀,备个英文词典呗!

没有时间

想作必定会挤出时间。欲望要足够强烈。

小结

就像任何一门技艺同样, 阅读源代码的技能也要从基础一点一点的训练, 直到娴熟、炉火纯青的境界。

相关文章
相关标签/搜索