解剖复杂语言和框架的方法

一、我的思想

1.1 三要素

我总是忘记,我记录一下吧。

在我心中一直有一个困扰就是如何剖析比较复杂框架、应用的过程。举个例子,比如Ruby中把 Monkey Patching 当做特性, 比如高度的动态性,可以任意对象增加方法,可以使用复杂的继承比如Rails,也可以在全局注入 DSL 方法比如 Sinatra ……

魔法有很多,我们如何观察魔法呢?我们有什么方式可以梳理这种动态性。和复杂性。

我仔细想了一些这种问题。我给出一些我的思考。

不论是看源码,看执行,自己写代码,debugger…… 本质上这些行为是有几个必备的要素的:

1. 入口
2. 观测方法/方式
3. 步进信息

这是很抽象的维度,但是可以寻找这几个要素。举个例子吧,比如我最近遇到的,如何debugger线上问题,线上是一个Hybrid页面,嵌套在App里。

如何解决debugger线上的问题呢?

1) 首先就是寻找入口,如何可以让app没有入口的条件下,进入。 这有好多方式了

  1. 给你一个灰度
  2. 使用约定的scheme跳转进入
  3. 使用约定的菜单、配置banner进入 …… 总之你要解决入口问题。

2) 其次就是观测方法,程序一般的方法是通过日志。也可以是debugger,也可以是一些数据仪表。总之只要是描述我们关心的信息。能表征变化的即可。

3) 再者就是一种步进信息,这里我说的很抽象。实际上程序是一个时间的函数。任何程序都是如此。所以,前面的方法只是铺垫,观察出一个点,那么随着交互、输入数据、时间变化……程序是不是会发生变化,输出会有什么不同。这种 diff 就是步进信息。

观测的方式总要找到这三个要素。或者可以有目标解决这三点。

1.2 以Ruby为例

我想解决的就是面对复杂的Ruby应用,别人写的,我自己写的,或者Ruby自己我怎么剖析他。

你可能看到过很多的文档、注释甚至原理性的书籍,但是往往不够。因为他们可能出现滞后,永远描述的不是你正在经历的东西。

我怎么去剖析 sinatra呢(抱歉的是Rails没看过)?

1.入口

启动的Sinatra应用是一个入口。

  • web是一个切入口
  • 程序本身也是一个切入口。
  • pry在Ruby的范畴里也是一个入口 ……

我选择了不同的入口,从而进入不同的切面去观察这件事情。

2.观察的方式&步进信息

讨论一下方式,方式其实有很多种。这些方式都伴随着过程

  • 最常见的是 log:

    log就是一种时间信息。最朴素又管用

  • debugger:

    我们自己写的东西log可能比较合适。但是涉及到应用、框架、语言底层超出我们控制的范围,log可能就不奏效了。

    当然你也可以吧源码拿过来编译,注入log也可以,但是这个方法对编译的语言会失效。

    这里可以使用debugger工具,debugger是一种黏贴进程的方式,输出每步内存信息。

    这里也是我推荐追踪源码、追踪复杂框架的一种方式。

    我的思考:

    我是习惯于log的,以致于我总是忽略debuger这种思想的存在。所以我在研究外部的东西是有弱点的。就是我的观察方式和抓瞎差不多。

    debugger是一个好的方式。代码本质上是最好的文档。代码也是时间的函数。debugger好的在于可以让内存中变量的状态得到展现。比如在动态语言中,模块、方法、局部变量如何变化。这其实就是我们关心的。log是一种两头的测量方式,来获得这种变化信息。debugger更加细颗粒度。debugger的方式更加朴素——向解释器/编译器伸手要答案。

  • 内部自省/反射的方法

    这个更多是配合log和debugger把内部的变量内容暴露出来。

    是一种由内而外的观察。

  • IDE

    工程化的IDE可能会提供一些信息。有的信息来自于上面。有的信息来自于IDE对工程性的理解。这里就是额外的扫描依赖库、代码本身建立了索引联系。从来揭示一些关系。

题外话:如何列出 Ruby中所有的get方法来自的模块和文件? 我之前一致想要找到这样的方法。似乎是没有的。IDE是可以做到的,因为IDE会扫描工程本身和依赖库,他是可以额外建立索引的。 如果想找到一个Ruby自身的方式可能没有。Ruby提供一个 method 的方法,他可以返回最先找到的一个方法的 owner、source_location。 其实我后面想想这是合理的 —— Ruby内部是维护了一个Map解构,这个解构再运行中一直在改变,一些书 比如《Ruby原理剖析》讲了一些内部的细节。内部是一个结构体,通过偏移和索引来工作。Ruby不关心全览,Ruby就是执行到什么就输出什么。所以method输出最近遇到的一个是合理的。如果Ruby内部存在一个全览的方法,那意味着Ruby一定扫描全部代码了,很显然没有。 所以Ruby作为一个动态语言,其实他是一个执行的过程。很多东西需要执行中检验或者看到。当然所有程序都是这样,但是Ruby更加强调着一点。 所以保证它的稳定性,需要用单侧,使用运行的东西时刻的保证运行时符合预期。 其实观察原理,其实就是要观察动态的过程。也就是上面提到的步进信息。

Mark24

Everything can Mix.