instance_eval是如何工作的以及为什么DHH讨厌它?

时间:2010-06-18 16:43:50

标签: ruby-on-rails ruby

大约在his RailsConf presentation的19:00左右,David Heinemeier Hansson谈到instance_eval的缺点:

  

很长一段时间我咆哮而且狂热   反对instance_eval,这是   不使用屈服的概念   参数(如do |people|)和   然后直接do something然后   评估该块内的内容   你来自哪里的范围(我   甚至不知道这是否连贯   说明)

     

很长一段时间我都不喜欢那样   因为在某些方面感觉更复杂   感。如果你想把自己的   那里的代码你要去   触发已经存在的东西   那里?你要覆盖吗?   什么?当你屈服于   您可以链接的特定变量   一切都关闭,你可以知道   [你]并没有弄乱任何人   别的东西

这听起来很有意思,但是a)我不知道instance_eval如何起作用,而b)我不明白为什么它会变坏/增加复杂性。

有人可以解释一下吗?

3 个答案:

答案 0 :(得分:31)

instance_eval所做的是它在不同实例的上下文中运行块。换句话说,它改变了self的含义,这意味着它改变了实例方法和实例变量的含义。

这会产生认知断开:块运行的上下文不是它在屏幕上显示的上下文。

让我通过@Matt Briggs的例子稍微改变一下来证明。假设我们正在构建电子邮件而不是表单:

def mail
  builder = MailBuilder.new
  yield builder
  # executed after the block 
  # do stuff with builder 
end

mail do |f|
  f.subject @subject
  f.name    name
end

在这种情况下,@subject您的对象的实例变量,name您的类的方法。您可以使用漂亮的面向对象的分解并将主题存储在变量中。

def mail &block
  builder = MailBuilder.new
  builder.instance_eval &block
  # do stuff with builder 
end

mail do 
  subject @subject
  name    name # Huh?!?
end

的情况下,@subject邮件生成器对象的实例变量!甚至可能存在! (或者更糟糕的是,可能存在并包含一些完全愚蠢的值。)无法让您可以访问您的对象的实例变量。你如何调用对象的name方法?每次尝试调用它时,都会获得邮件构建器的方法。

基本上,instance_eval使您很难在DSL代码中使用自己的代码。因此,它实际上只应用于可能需要这种情况的情况。

答案 1 :(得分:17)

好的,所以这里的想法不是像这样的

form_for @obj do |f|
  f.text_field :field
end

你得到这样的东西

form_for @obj do 
  text_field :field
end

第一种方式非常简单,你最终得到一个看起来像这样的模式

def form_for
  b = FormBuilder.new
  yield b
  b.fields.each |f|
    # do stuff
  end
end

您产生了一个构建器对象,消费者在其上调用方法,之后您调用构建器对象上的方法来实际构建表单(或其他)

第二个更神奇

def form_for &block
  b = FormBuilder.new
  b.instance_eval &block
  b.fields.each |f|
    #do stuff
  end
end

在这个中,我们不是将构建器放到块中,而是在构建器的上下文中对块进行评估

第二个增加了复杂性,因为你有点玩游戏范围,你需要了解它,而消费者需要理解这一点,而编写你的构建者的人需要理解这一点。如果每个人都在同一个页面上,我不知道这在某种程度上是一件坏事,但我确实质疑成本与成本之间的关系,我的意思是,仅仅强调f是多么困难。在你的方法面前?

答案 2 :(得分:0)

这个想法是有点危险的,因为如果没有阅读处理你使用instance_eval的对象的所有代码,你永远不会确定你不打算破坏它。

另外,如果您更新了一个没有多少更改界面但更改了很多对象内部的库,那么您实际上可能会造成一些损害。