Ruby 元编程 - __FILE__ 和 __LINE__ 的作用
前言
Ruby 元编程中,经常可以看到各种 eval 和 heredoc 的结合。其中很多用 heredoc 的位置都会出现 __FILE__ 和 __LINE__ 这两个变量。本文介绍一下它们的作用,以及为什么要这样做。
正文
先看一个用 class_eval 动态生成实例方法的例子:
class A def self.my_attr_reader(*args) args.each do |method| class_eval <<-EOF, __FILE__, __LINE__ def #{method} raise "#{method}" end EOF end end end
这是模仿 Ruby 的 attr_reader 的代码,当然实现简化过了,关键是,它会抛出异常。我们待会再解释为什么这样做。现在先大概解释下代码。
class_eval 是 Ruby 中 eval 的一种,它的上下文是调用它的对象,也就是类 A 。它通常用来为类生成实例方法,用法就像上面代码中的那样。你可以传把一段 Ruby 代码写在一个字符串中传过去,class_eval 就会动态解释 Ruby 代码。这和 Javascript 中的 eval 十分类似。
heredoc 就是被 EOF 包起来的那部分。它实际上是一个多行的字符串,EOF 只是一个标识,代表 heredoc 的开始和结束,你可以随意换成其他玩意。用 heredoc 的好处之一是可以不用操心单引号和双引号的问题,还有就是把代码写在多行看得比较清楚。喜欢深挖的可以看看 这个链接。
第4行 EOF 后面的 __FILE__ 和 __LINE__ 是两个特殊变量,它们保存了当前文件的名字和当前代码的行数。其实 heredoc 后面不加这两个变量也可以。比如这样也是合法的:
class_eval <<-EOF EOF
那加了它们有什么好处呢?
主要还是在测试方面。我们可以用如下代码测试一下:
A.class_eval do my_attr_reader :name end a = A.new a.name # 会抛出异常,异常信息为 name
把这段代码和上面第一段放在一起,保存成 Ruby 文件(我保存成 test.rb),再用解释器执行,会看到如下的错误:
# test.rb:5:in `raise': exception object expected (TypeError) # from test.rb:5:in `name' # from test.rb:18
可以看到,异常信息会提示你错误出在 test.rb 第5行的 name 方法内。即使 name 方法是用元编程手段动态产生的。
现在我们把 EOF 后面的 __FILE__ 和 __LINE__ 去掉。再运行下代码,会看到如下的结果:
# (eval):2:in `raise': exception object expected (TypeError) # from (eval):2:in `name' # from test.rb:18
可以看到,异常信息最后是从 eval 中抛出来的,最多只能看到18行调用 name 的那一行出错了,但看不到 name 方法在哪定义的。这样对 debug 几乎没有帮助,尤其是当程序复杂的时候。比如 Rails 就使用了大量元编程技巧。
所以,__FILE__ 和 __LINE__ 在这里就是为了方便 debug 的。它们并不会对元编程动态创建的方法有任何影响。