1

RSpec 添加了一个“描述”方法来做顶级命名空间。但是,他们不是简单地在任何类/模块之外定义方法,而是这样做:

# code from rspec-core/lib/rspec/core/dsl.rb 
module RSpec
  module Core
    # Adds the `describe` method to the top-level namespace.
    module DSL
      def describe(*args, &example_group_block)
        RSpec::Core::ExampleGroup.describe(*args, &example_group_block).register
      end
    end
  end
end

extend RSpec::Core::DSL
Module.send(:include, RSpec::Core::DSL)

与简单地在任何模块和类之外定义描述相比,使用这种技术有什么好处?(据我所知,DSL 模块在 rspec-core 的其他任何地方都没有使用。)

4

2 回答 2

3

几个月前,我进行了此更改,因此describe不再将其添加到系统中的每个对象中。如果您在顶层定义它:

def describe(*args)
end

...那么系统中的每个对象都会有一个私有describe方法。RSpec 并不拥有系统中的每个对象,也不应该随意添加describe每个对象。我们只希望 describe 方法在两个范围内可用:

describe MyClass do
end

(在顶层,脱离主要对象)

module MyModule
  describe MyClass do
  end
end

(在任何模块之外,因此您将描述嵌套在模块范围内)

将它放在一个模块中可以很容易地扩展到主对象(仅将其添加到该对象,而不是每个对象)并包含它Module(将其添加到所有模块)。

于 2012-10-10T20:47:32.467 回答
1

实际上,如果这就是代码中的全部内容,我真的不相信它会更好——如果有的话。一个常见的论点是,您可以通过检查方法所有者轻松检查 RSpec 是否负责在全局命名空间中添加此方法。不知何故,它从来没有觉得这是必要的,因为该方法的位置已经存储了该信息。

在任何范围之外定义方法将等同于在 Object 中定义私有实例方法:

class Object
  private
  def double(arg)
    arg * 2
  end
end

double(3)      # OK
3.double(3)    # Error: double is private
self.double(3) # Error: double is private

我认为隐私是一个有用的方面,因为它可以防止进行某些没有意义的方法调用,而问题中显示的代码缺少这些方法。

不过,在模块中定义方法有一个好处,但 RSpec 代码似乎没有使用它:使用module_function,不仅可以保留实例方法的私有性,还可以获得公共类方法。这意味着如果您有一个同名的实例方法,您仍然可以通过使用类方法版本来引用模块定义的方法。

一个常见的例子module_functionKernel模块,它包含了大多数类似函数的核心方法,比如puts(另一个是Math)。如果你在一个重新定义的类中puts,你仍然可以Kernel#puts在需要时显式使用:

class LikeAnIO
  def puts(string)
    @output << string
  end

  def do_work
    puts "foo" # inserts "foo" in @output
    Kernel.puts "foo" # inserts "foo" in $stdout
  end
end
于 2012-10-10T19:12:12.693 回答