HBase源码|源码分析怎么做?
近日在知乎上看到一个关于源码分析的提问《程序员阅读源码是一种什么心态?源码对编程意义何在?如何才能更好阅读代码?》,正好结合对比读完的两本讲解 Spring 源码的书《Spring 源码深度解析(第 2 版)》(简称《Spring》)和《深入理解 Spring MVC 源代码》(简称《Spring MVC》),聊一聊阅读的体验,顺便讲一讲阅读分析怎么做、怎么写这回事。
首先要明确的是,源码分析的入手点是软件而不是源代码。软件( Software )是一个宽泛的概念,包括应用程序、工具箱和框架等等。软件可以说是由代码组成的,那么我们强调入手点是软件而不是源代码的原因是什么呢?
源码分析从源代码入手,就容易落入具体实现的窠臼当中;而代码构成的软件整体,有其被创造的背景、要解决的问题、演进过程中面临的困难和决策,以及最终所为用户认知的形态。源码分析从软件整体入手,才能够脱离技术人员对技术本身的痴迷的影响,从务实的角度讲解代码要解决什么问题、代码如何解决问题的以及代码为什么要这么解决问题。
从这个角度讲,《Spring》开篇讲解 Spring 框架的整体架构一节写得较为出彩。它首先展示了一张 Spring 框架中模块划分图,大致如下。
我们且不论这张图的时效性和准确性,但是通过这张图我们很明确地能知道作者打算把 Spring 大致分成核心容器模块、面向切面编程模块、数据访问模块和 Web 应用模块来讲解。实际上,《Spring》第一篇对这些模块简明扼要的总结是全书仅有的的少数精华之一。
只不过在我看到《Spring MVC》中更新版的基本模块划分图之后,我就知道《Spring》全书仅有的精华原来是来自全网总结的公认精华。这就跟某 Flink 实战书籍唯一算得上精华的第一章是因为它大体是对《Streaming 101》和《Streaming 102》的洗稿一样。
回到主题,作为源码分析书籍,从代码的框架以及评论不同模块的意图入手是合适的。这跟我们此前强调的从软件角度切入是不矛盾的,因为我们好歹没看到代码,而想要阅读源码分析的读者大抵也对应用方式比较了解或不想了解,不需要刻意以实例引入。
在提纲挈领分析对象之后,其实就可以从实例和问题点出发逐级下降分析软件了。不过囿于写书的框架,大部分书籍会介绍环境的搭建这样枯燥混字数的内容。在这一点上两本书都有莫名其妙的构建细节,但总体控制在 20 页以内,也无可厚非。
前面提过,源码分析的目的是讲解代码要解决什么问题、代码如何解决问题的以及代码为什么要这么解决问题。在这个目标的实现上《Spring》和《Spring MVC》大相径庭。
《Spring MVC》的讲解按照目录分成三个部分。
第一部分主要从使用的角度入手,由开发者最熟悉的功能切入,讲解了基本组件包括控制器、模型和视图在抽象层面上是如何被支持的。在此基础上对 MVC 模型最承担逻辑的控制器展开了详细的介绍,尤其是平时容易被终端开发者忽略的请求是如何进入框架和流程以及返回值是如何交付给请求方的。最后用简短的篇幅简略地介绍了 WebFlux 的发展趋势和一些常见的配置项。
第二部分接着从源码切入,首先介绍了源码阅读的一些技术技巧,再对 Spring MVC 框架的启动、MVC 框架请求分发的核心 DispatcherServlet 类的功能、RequestMapping 的查找原理和请求处理方法的执行过程一一进行具体的介绍。每个细节部分也是按照这种主题加解决方法的模式,先抛出一个问题,抽象地讨论解决思路,再结合代码讲解关键细节,最后简略地做完整性补充和扩展讨论。
结合每个部分论述的时候穿插的图片、习惯性地分点阐述问题,以及代码块在样式上的区分以及量上的克制(虽然有些地方还是多了),这本《深入理解 Spring MVC 源代码》确实值得推荐。
《Spring》则完全不同,在几个方面上都踩了大坑。
第一个是啰嗦,代码占据了主要的篇幅,这是搞源码分析最忌讳的事情。
源码分析重要的是分析,是以软件为切入点,从务实的角度,讲解代码要解决什么问题、代码如何解决问题的以及代码为什么要这么解决问题,绝对不是大篇幅的复制粘贴真正的源代码。
稍有经验的开发者都知道,真正的源代码大概只有 1/5 是业务逻辑,其中核心逻辑少得可怜。剩下的 4/5 中大概有 2/5 是日志等旁路逻辑,以及 2/5 是数据校验和异常处理。通俗地说,你啪地贴出来整页整页的源代码,谁想看?谁一下看得懂啊?尤其是《Spring》里面大段大段的贴 XML 配置,水字数混厚度印厕纸也不能这么搞啊。
你会在《Spring》中看到这种句子。
很遗憾,在这个代码中我们还是没有看到想要看到的代码。
是的,就是这句代码,我们的历程犹如剥洋葱一样,一层一层的直到最内部的代码实现,虽然很简单。
这一功能委托给了某方法去实现(转入某方法,又委托给了某某方法,转入某某方法)。
…
我想也不用再往下列举也能很简单地看出问题来了。
我来读你的源码分析大作,当然是要听你是怎么看待这个软件解决问题的方式的了。不是想跟着你就像我自己读代码一样一行一行跳转来跳转去,搞清楚每行代码的调用栈,搞清楚每个 if-else 是要筛选哪个细枝末节的特化配置特殊情况。
《Spring》的行文读下来就是作者自己读代码的时候的流水账,没什么思考,全是机械的代码跳转动作和直译 if-else 内容。这么些内容做成视频教程都嫌啰嗦,不要说以书籍的方式展示了。
客观来说,《Spring》里还是有些有用的分析的,但是它们都淹没在整页整页的代码里,淹没在代码行内注释的评注里了。
这里就引出来第二个问题,不分轻重。
《Spring MVC》介绍 WebFlux 框架的时候仅使用了 15 页的篇幅,从需求背景到技术背景,再到使用的不同和对比 WebMVC 的工业应用都讲出了最关键的要点。此外除了为了完整性讲述的视图知识,几乎全都围绕 MVC 处理请求这个核心链路,从核心过程、核心类 DispatcherServlet 以及核心注解 RequestMapping 出发讲解内容。所有的扩展性知识和旁路内容都相应地克制了篇幅。
《Spring》则不同。我想每个对 Spring 框架稍有了解的开发者,都知道 Spring 核心容器部分最重要的就是 Bean 的创建和管理,也就是依赖注入和 Bean 的生命周期流转。《Spring》在介绍 Bean 的创建和管理之前,用神奇的剥洋葱方式,带领你剥了怎么读 XML 文件、怎么验证 XML 文件,怎么解析 XML 标签等等乱七八糟的问题。
暂且不说 XML 配置只是一种实现,现在注解驱动编程在 Spring 社区已经占据绝对上风。怎么读取、验证 XML 文件跟 Spring 有什么紧要的关系?这都是一些外围的、旁路的知识,XML 的规范、细节和技巧自己都能写出一本书来。
我们在编程的时候强调不要测试三方库的行为,应该测试业务逻辑的行为。这是因为软件之间应该有清晰的限界上下文。源码分析也一样,一本讲 Spring 的书大概率不会突然出现聊 GC 的问题的时候聊到 JVM 的垃圾回收器的细节里面去,同样的对于 XML 这种依赖技术一笔带过或者用少于一页的篇幅介绍上面几个问题的要点知识即可。不然,读者究竟是来看你分析什么源码的呢?
在开发者不关心的内容上着墨过多,就会导致关键内容相比之下占比减少。
《Spring》的作者在文中时不时咋呼某一行【藏得很深】的代码有多么重要的作用。殊不知这样的代码在代码水平上是欠缺的。因为我们人主观地第一眼难免会根据代码的篇幅来判断代码内容的重要性,所以同等重要的代码最好占据同等的篇幅。在一堆不重要的逻辑中淹没一行关键的代码是不应当的。
当初我读 Flink 代码里实现 stopWithSavepoint 功能如何通过毒丸消息逐步下发停止任务的时候,就被委托跳来跳去和藏在大型方法里的那一句终止语句给坑到了,实在是不好发现。理想情况下,委托和代理应该只在需要的时候引入,同等级别的语句应该占据相同的篇幅。上面的例子里跟终止语句平级的那一坨内容应当被抽取到一个单独的方法里面去。
回到书评,Bean 的创建和加载,BeanFactory 的行为和 ApplicationContext 的行为及其差异,这些重要的内容在《Spring》中跟 XML 的读取篇幅一致,比大部分开发者不会用的 SpEL 甚至还少一些。这样分析源代码,难怪乎某些知乎用户呼吁初学者别读源代码。早早地陷入到不重要的细节,抓小放大,蹉跎时间。这怎么能行呢?
还有另一本 Spring 讲解的书叫《Spring 5 核心原理与 30 个类手写实战》,这本书唯一有用的就是 Spring 中常用的设计模式一节,还算有实践的分析价值。其他的无论是 30 页的配环境还是手写实战里又回到了各种代码细节,不堪卒读。
最后一点,是关于图表的。
我们知道,人读文字的效率是不如图表的,图表具有直观的表达能力。《Spring MVC》和《Spring》都有不少的图表,但是在图表的类型上却有所不同。
《Spring MVC》的图表多为模块逻辑划分图,IDEA 截取的关键数据结构图和浏览器截取的代码效果图;《Spring》的图表多为 UML 图,在最后一章中大量使用了时序图。
这其中最有意义的是模块的逻辑划分和交互图,因为这个是我们从一个高的抽象层次来理解一个模块的助力。在日常方案设计中,我也总会对需求设计的领域和内容做一个模块的逻辑划分图以及交互图来帮助自己梳理思路,并且用于方案评审和产品沟通,对于模块划分的意见分歧和产品意图的理解偏差能够很快被发现、讨论并解决。
其次比较有意义的是时序图。对于关键的交互流程来说,时序图是一种细化的交互流程表达方式,往往能够指导代码的实现以及排查交互的问题。不过,在实践当中我发现直接画时序图很容易过早的陷入细节,抽象改动需要不停改动倍数的细节。这一功能更好的高层次替代品是流程图,即核心流程包括哪些关键步骤、关键判断和关键结果。流程图是逻辑步骤最小粒度的图示,更有可能跟意图改变做到一一对应。
UML 图真的是没什么大用。《领域驱动设计》里面就提过,很多 UML 图在某些地方过于细致,同时在某些地方又有很多遗漏。细致的点在于人们认为必须将所有要编码的对象都放到建模工具中,而细节过多的结果是不分轻重。UML 图已经是时代的眼泪了,请放过它。
源码分析该怎么做?从上面的对比阅读里总结下来的就一个中心和两个基本点。
一个中心是源码分析要从软件入手而不是源代码本身入手,从务实的角度分析代码要解决什么问题、代码如何解决问题的以及代码为什么要这么解决问题。
两个基本点包括分析的过程要逐层深入,从高层次的抽象完整地讲解,低层次的细节只需要讲解要点;以及抓大放小,对旁路的、不重要的内容一笔带过或完全不提。
最后在技巧上,不要选择 UML 图或者复杂的时序图作为辅助说明的手段,它们只会把事情变得更复杂;应该选择模块划分图、模块交互图和功能流程图作为辅助说明的手段。不得不提的是,不要大段大段地贴源代码!