源码阅读应该得到什么?

这个文章从几年前写的内容中整理得来。

我们平时会接触到不少开源项目, 很多人都期望能够通过阅读源码理解更多东西,把从中学到的东西用于平时的框架使用、设计和编码实现过程。我在日常工作中,也经常需要通过阅读一些源码来来定位分析生产问题。

不过,浩瀚的代码量,应该关注什么? 作为一个程序员,分享一下经验:

  1. 多补充理论知识,虽然很花时间,但从实现反推理论是非常难理解的。
  2. 最好有一些使用经验。这是背景,从使用中来,到使用中去,去找相关特性是如何实现的。
  3. 最好阅读一下官方文档,了解使用的场景、高层设计、特性介绍。做到心中有数。
  4. 提前学习一下设计模式还是有用的,不熟悉设计模式真的被绕晕,心态崩溃。
  5. 很多源码书会画很庞大的类图,时序图等。但用途并不大,主要是被动接收记不住,还是自己不断脑补模型。
  6. 细节还是要带着问题去跟踪,一次跟踪一小部分,就是一次要有一个关注点。
  7. 好好理解这句话。烂程序员关心的是代码。好程序员关心的是数据结构和它们之间的关系。
  8. 建议使用Maven等高大上工具关联源码,应该多调试调试。
  9. 单步调试帮助不大,应该提高代码”猜想”能力,大步调试过去,验证自己的猜测。
  10. 凑数的,找个感兴趣的开始吧。

无论是开源代码,还是商业应用代码,其实都是比较复杂的,但模块和模块之间其实还是有一定边界的。就像一个超大的房子一样,有很多房间,你只是房子里边逛逛,没有打开房门进去看看,那收获也不多,但是直接打开房门进去了,就很难知道房间所在的位置。所以要真的搞清楚,得经常在整体和局部之间来回切换,很是累人。

所以终极技巧,还是得带着问题学习效果更好,看公司里边的项目代码,很多人都会带问题看,但看开源代码就很容易忘记目的。挑几个疑惑看看别人怎么处理就是收获。

上面废话很多,下面来个示例。

commons httpclinet 3.x 这是一个很好用的库,实际上也是比较容易理解的,是一个不错的起点。

我阅读的时候,列了一下自己的关注点或者问题列表。

从使用角度来看

  • 如何打开HttpClient库的详细日志
  • httpclient默认参数有哪些?如User Agent、Charset怎么进行定制?
  • HttpClient能用于多线程环境么?如何应用?
  • uri和content中的字符编码是怎么处理的? 例如像处理带中文的路径。
  • 连接超时、读取超时、读写缓冲区、还有禁用Nagle等常见属性如何设置?
  • 命令行参数、HttpClient、HttpMethod、HostConfiguration都可以设置params,有区别? 参数在框架中是怎样存储的?
  • HttpClient会自动处理请求跳转? 会不会出现死循环? 怎样的响应才会自动跳转? 如何关闭这个特性
  • HttpClient的自动重试功能是怎样? 怎么进行定制?

从数据结构的角度看

  • httpclient的连接是怎样管理的? 内部是连接池结构是怎样的?
  • HttpClient、HttpMethod(GetMethod、PostMethod)、HostConfiguration、HttpConnectionManager、HttpClientParams、HttpConnection各自的关系和职责是什么?
  • HttpClient是使用java自带的HttpUrlConnection实现的么? 报文是怎么组装和解析的?
  • 多次调用之间的cookie怎么管理的?
  • chunked模式是怎么实现的? 多大刷一次?

总之,我们要列举各种问题(取决于你对这个项目的应用场景、特性的属性程度),把理论知识融入里边,例如这个库涉及网络,那网络会涉及超时、连接管理之类,那我们就可以看看代码是怎么实现这块内容的。

以commons-httpclient为例,这当中涉及到哪些理论基础呢? 简单列举一下:

  1. java弱引用、克隆特性
  2. 线程协调、中断机制
  3. 网络编码、http协议
  4. tcp相关参数的含义
  5. 常见对象池化技术

大家可以参考这个,在阅读的时候,列出问题列表,然后从源码中找找答案,最好要做好记录,做到能够解答问题。

以上面为例,如何分析连接池管理的数据结构? 我们记录如下。

从本质来说,整个链接管理的结构是:

  • 维护一个connectionPool, 内部维护一个mapHosts,它的结构是Map<HostConfiguration, HostConnectionPool> HostConnectionPool的内部结构是两个链表,freeConnections维护空闲连接,waitingThreads维护正在等待连接的线程

  • 还有一个静态的ReferenceQueue和ReferenceQueueThread,用来实现防止某些连接丢失的情况。至于是怎么做到丢失的连接能够回到ReferenceQueue,就是通过对HttpConnection进行加强得到的。

  • 实际上通过管理器得到的HttpConnection是经过层层代理的。层次是这样的: HttpConnectionAdapter – HttpConnectionWithReference – HttpConnection。

  • HttpConnectionWithReference就是通过一个WeakReference关联到ReferenceQueue,这样根据弱引用特性,一定可被回收就会进入ReferenceQueue从而被线程扫描到。

  • 为了实现闲时连接关闭的功能,使用了IdleConnectionHandler,它的内部结构是有个Map记录空闲连接(在releaseConnection的时候放入)和当时的时间。

代码就是围绕这些核心数据结构进行操作的。通过上面的基本结构,就可以实现以下特性:

  1. 总连接数和每主机总连接数限制。连接数默认50,每host默认2连接(经常容易忽略,通常需要定制)
  2. 能够防止连接丢失。如上面所说,拿出去的HttpConnection是加强过的,会关联到一个静态的ReferenceQueue,并使用一个线程ReferenceQueueThread不停监听并回收。
  3. 实现了获取连接的超时限制。就是通过waitingThreads实现的。当找不到空闲连接的时候就添加进去,然后wait。如果有releaseConnection的时候,就会interupt等待的第一个线程。我个人认为线程有可能被interupt之后仍然占用不到连接,从而排到最后去,真是悲剧。
  4. 能够支持连接闲时关闭。这个默认是需要主动触发,通过IdleConnectionHandler比较时间来实现的。

最后,这个类是支持多线程的,主要是使用了同步机制,锁定ConnectionPool对象实现的。
另外,HttpConnection对象实际上并不是一个正式的连接,要open之后才会真的建立连接。
实际上也是一个lazy代理对象,可以避免整个连接管理操作不被阻塞,只有到实际操作时open的时候才连接。

总结一下,细节是魔鬼 ,主要是说掌握细节非常难,但从另外一个侧面也可以反映出,我们很容易深陷其中细节,只见树木不见森林,所以大家要经常提醒自己,自己阅读代码的目的是什么。


原文始发于微信公众号(程序员的胡思乱想):源码阅读应该得到什么?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/22600.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!