BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

使用单实例类来处理对象元信息

| 作者 Werner Schuster 关注 6 他的粉丝 ,译者 曹云飞 关注 0 他的粉丝 发布于 2008年1月22日. 估计阅读时间: 10 分钟 | 都知道硅谷人工智能做的好,你知道 硅谷的运维技术 也值得参考吗?QCon上海带你探索其中的奥义

假设你有大量的对象 —— 一个对象图 ——它们是一些操作或者API调用的结果。任务:分析数据并将分析结果作为对象图的元数据。

这么说过于抽象?请考虑一个编辑器是如何工作的:解析器产生一个代表代码的树状结构的解析树(或者抽象语法树,AST)。然后:许多算法遍历解析树,处理数据:收集在符号表中的符号,做类型推论,使用类型做数据类型检查,等等。

但是请稍等:最后两步处理有问题:类型推论代码在哪里存贮它产生的数据为类型检查者使用?最方便的方式是将数据存储到需要数据的地方 —— 例如,如果推论出(AST中的)一个表达式节点返回类型Foo,那么最好就在这个节点中存储该信息。

为了说明我们的解决方案,我们会看一些AST方面的工具 —— 不是一个编译器,而是与编译器有类似需求的工具。在Ruby中,ParseTree类库以AST的形式返回Ruby源代码。例子:

[:vcall, :obj, :say_hello, [:array, [:lit, 42], [:lvar, :foo]]] 

这是一些以ParseTree AST方式展现的Ruby代码。由于此处是一篇文章而不是一个虚拟机,以对象和组织为一个图的对象引用的方式来说明问题有点困难 —— 所以我们使用ParseTree的 s-expr形式来表示树。S-exprs是嵌套的列表,每个列表代表树中的一个节点,列表的第一个元素表明了节点的类型。在上例中,该节点代表对一个虚拟方法(vcall)的调用。参数包括方法接收者(即被调用的方法中的“self”对象),方法名称和方法的参数。

我们讨论的工具可以是一个类型推论器,静态分析工具或者自动重构工具。对于这类工具的工作需求之一是类型推论,也就是,决定变量或者表达式的类型。例如,这个AST子句代表了一个对方法to_s的调用:

[:vcall, :obj, :to_s] 

从上面的句子中可以收集到什么信息,可以用这些信息做什么?例如,返回值的类型是难以决定的 —— 但是既然它是to_s方法,它会返回代表一个对象的字符串 —— 那就让我们认为它返回”String”类型。这不是必然为真的 —— 这是一个猜测。对于诸如代码补全之类的工具,这个推测已经足够好了 —— 100%的精确在此情况下是不可能的。在某些情况下,可以收集到更多信息,分析器可以确定更精确的信息。

分析器处理解析树,用分析产生的元数据来注解AST。为了保持代码的模块化,将分析器与元数据的消费者分离是个好主意。元数据的消费者可能是遍历AST并使用元数据做某些事的代码。例如,Ruby编辑器可以高亮一个覆盖了其超类中方法的方法。

解决方案

让我们长话短说:这里是一个ParseTree节点的注解方案:

node = [:vcall, :obj, :to_s] 
def node.set_metadata(key, value)
 @_metadata ||= Hash.new
 @_metadata[key] = value
end
def node.metadata
@_metadata ||= {}
end
node.set_metadata(:type, :String)

这段代码是做什么的?

秘密武器:单实例类

在Ruby中每个对象都是一个类的实例。不象许多其他OOP语言,Ruby允许你改变一个对象的类。不要把改变对象的类与打开类(open class)相混淆。在Ruby中,修改一个类是可能的 —— 甚至在运行时。单实例类同样是可以修改的 —— 只不过它们的改变仅仅影响一个对象的类。看起来差别似乎很小 —— 但是它对于限制修改类对该类对象带来的影响有很大的好处。另一方面,打开类对类定义的修改影响了所有的代码和所有相应对象。打开类是有用的,但是也可能会和其他人的代码搅和在一起,而且如果有太多的代码在公共类上作打开动作,可能会与已有方法发生名称冲突。在ParseTree节点这个案例中 —— 这些节点是普通的Ruby数组 —— 意味着仅仅真正被使用的数组对象才受到影响 —— 而不是在堆中的所有的数组都受到了影响。

再换个角度来看:使用单实例类,对于类的改变是局部化的,仅对产生和使用单实例类的代码有影响 —— 这些变化对于外界永远是不可见的。而另一方面,打开类是全局性的改变:一个类名称是一个全局的变量,例如“String”指向代表字符串的类对象。就像作用域较小的变量(本地变量,成员变量)应该比作用域较大的全局变量更优先使用,在单实例类中作出的变化也应该优先于打开类。

当然,选择哪个方案(单实例或者打开类)取决于具体的情况。对于打开类,类的改变发生一次 —— 在改变之后,该类的所有对象都拥有了增加的方法。对于单实例类,类的改变(即创建单实例类,改变对象的类指针指向单实例类)在每次改变的时候都发生。

如下所示,语法是简单的:

def object_variable.method_name()
# code
end

指向对象的变量前缀于方法名。一种更灵活的更模块化的方式是使用混入(Mixin)来做。混入允许将处理某些方面的方法集合在一个模块中然后将这些方法一次性的混入到类中。例如:

module Metadata 
 def set_metadata(key, val)
  @_metadata[key] = val
 end 
 def metadata
  @_metadata ||= {}
 end
end
x = [:vcall, :obj, :to_s]
x.extend Metadata
x.set_metadata(:type, :String) 

混入允许将定义在一个模块中的方法混入一个类中 —— 在这里,混入的是节点对象的单实例类。再次重申:现在只有这个对象具有这些混入的方法。

另一个例子 —— 除去死代码

如果静态分析器代码的例子过于抽象,那么让我们尝试另外一个工具:一个死代码移除器。“死代码”是实际上没有做任何事情的代码,它们被除去后不会影响程序的行为。例如这样的代码:

for x in [1,2,3] do
end

根据我们的注解概念,我们可以找出这类代码并象这样来注解:

node.metadata(:dead_code, :true) 

但是将代码标记为死的仅仅是第一步 —— 现在我们需要除去它们。在这里我们会容易看到将代码分析和具体动作分离的意义。一个IDE也许仅仅希望高亮某些代码为死代码,但是IDE也可能提供了一种简单的方式(一种快速修复)来除去代码。

如何除去这段死代码?当然 ,可以将节点从AST中取出然后使用Ruby2Ruby来生成Ruby源代码。但是,这不是一个非常优美的解决方案:Ruby2Ruby将ParseTree AST转换为Ruby源代码,但是它会丢失很多有用的信息,例如格式(空格)和注释。在IDE或者重构/清理工具中丢失这些信息是不可接受的。

元数据来救场了:节点可以用注解来记录来源的位置 —— 例如:在起初的源代码中的什么地方发现了这个特定的节点。有了这个信息就容易了:清理代码只需要扫描代码,发现所有标记为dead_code的节点并根据元数据中的源代码位置的字符偏移量将这些节点在源文件中删除。(当然 一旦这些节点被删除了,剩下的节点的偏移量会变化。这个问题可以通过简单地跟踪被删除的字符数量,并将此数量从实际的偏移量中减去来解决。这样,就不必重新解析和分析代码了)。

结论

这篇文章中的例子着重于语言方面的工具 —— 但是文章的思想是适用于所有的对象集合的。在任何对象图需要被注解的地方,也许是在独立开发的解析器的多个处理步骤中,将注解与节点一起保存是很方便的。如果对象图的类不是在开发者的控制之下而且在其他地方被广泛使用(例如,在ParseTree中的数组类),单实例类是个好的选择。对于其他情况,打开类也许是一个更好的方案。

本文所提到的特殊工具的实现:ParseTree在Ruby 1.8. x、Rubinius和JRuby (jparsetree)中都有实现。JRuby中的ParseTree版本增加了每个节点的来源位置信息,所以更容易修改源文件。愿你也能想出一些工具的创意并实现它们 —— 用Ruby是很容易的。

查看英文原文Using singleton classes for object metadata


译者简介: 曹云飞,西安交通大学计算机软件硕士。现就职于Ethos,热衷于计算机理论与应用技术的钻研,软件架构与敏捷开发,目前从事consumer product方面的工作。参与InfoQ中文站内容建设,请邮件至china-editorial@infoq.com

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT