【问题标题】:How does instance_eval work and why does DHH hate it?instance_eval 是如何工作的,为什么 DHH 讨厌它?
【发布时间】:2010-06-18 16:43:49
【问题描述】:

his RailsConf presentation 大约 19:00 时,David Heinemeier Hansson 谈到了 instance_eval 的缺点:

很长一段时间我都在咆哮和咆哮 反对instance_eval,这是 不使用 yield 的概念 参数(如do |people|)和 直接do something 然后 评估该块中的内容 你来自哪里的范围(我 甚至不知道这是否连贯 解释)

很长一段时间我都不喜欢那样 因为在某些情况下感觉更复杂 感觉。如果你想把自己的 你要去那里的代码 触发已经存在的东西 那里?你要覆盖吗 某物?当你产生一个 您可以链接的特定变量 一切都结束了,你可以知道 [你]不惹任何人 别人的东西

这听起来很有趣,但是 a) 我不知道 instance_eval 首先是如何工作的,b) 我不明白为什么它会不好/增加复杂性。

谁能解释一下?

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:

    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
    

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

    def mail &block
      builder = MailBuilder.new
      builder.instance_eval &block
      # do stuff with builder 
    end
    
    mail do 
      subject @subject
      name    name # Huh?!?
    end
    

    this 的情况下,@subjectmail builder 对象的实例变量!它甚至可能不存在! (或者更糟糕的是,它可能存在并包含一些完全愚蠢的值。)没有办法让你访问你的对象的实例变量。您甚至如何调用对象的name 方法?每次尝试调用它时,都会得到 mail builder 的 方法。

    基本上,instance_eval 使您很难在 DSL 代码中使用您自己的代码。所以,它真的应该只在几乎没有可能需要它的情况下使用。

    【讨论】:

    • 这是一个很好的解释,但在某些情况下我发现你别无选择,只能使用 instance_eval,因为 proc 对象是在另一个上下文中创建的,但你想运行它们在完全不同的环境中。
    • 我认为这在创建小型 DSL 时最有用。 github.com/state-machines/state_machines 非常有效地做到了这一点。
    • #yield 变体的一个非常方便的功能是使用正确的 YARD 标签可以获得方法自动补全。
    【解决方案2】:

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

    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 有多难。在你的方法面前?

    【讨论】:

      【解决方案3】:

      这个想法是有点危险,因为如果不阅读处理您使用 instance_eval 的对象的所有代码,您永远无法确定自己不会破坏某些东西。

      另外,如果你更新了一个库,它并没有改变太多界面,但改变了很多对象内部结构,你真的可能会造成一些损害。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-07
        • 2010-09-05
        • 2010-09-22
        • 2014-02-06
        • 2010-10-12
        • 2010-11-08
        相关资源
        最近更新 更多