ruby的加载机制和rails_自动加载机制_对比

阅读更多

 

prefer:http://urbanautomaton.com/blog/2013/08/27/rails-autoloading-hell/

参考这篇文章,总结的已经很好了,有改动和补充。

Ruby Constant Lookup(注意这里是Ruby的)

Constant lookup in Ruby is reasonably simple, once you know the rules, but it isn’t always totally intuitive. When you refer to a constant in a given lexical scope, that constant is searched for in:

  1. Each entry in Module.nesting
  2. Each entry in Module.nesting.first.ancestors
  3. Each entry in Object.ancestors if Module.nesting.first is nil or a module.

关于Module.nesting 可以参考文档:http://ruby-doc.org/core-2.1.2/Module.html#method-c-nesting

翻译一下:

虽然汉语并不是一个精确的语言,但意境和直觉的表达,不论松本先生还是哪个程序猿,都是一样的。

1. 从当前作用域开始,逐层向上,寻找直接可视的目标

2. 从当前作用域的祖先们里寻找目标(2比1优先级低为什么?因为这点不符合第一直接可视要求,想想看很多的include进来的东西,哪里能看见呢!谁要是这么写程序,就是程序员公敌)

3. 寻找顶层作用域内的目标(这也符合直觉,因为顶层作用域是公用的,放最后也符合习惯;)

 

大大的缺陷

缺陷在第二步,从当前作用域的祖先们里寻找目标。

对一个程序员来说,最好的是能准确的写出自己用哪个名字空间内的哪个目标。

这个第二步中的默认规则显然违背了这个原理,因为如果我要用祖先的作用域内的目标,我可以写明使用这个目标,即多写几个字母,把目标的命名空间写上。

举例来说:

找A::B::C

就只应该找1. A::B空间内的C,或者3. 唯一默认空间内的::C

而不应该去找A::C,因为如果程序员想用A::C,则需要写明,只需要多写几个字母。

 

这个缺陷将会导致什么问题呢?

不明确的东西,就要有一个统一的默认值。而如果默认的寻找路径是多个,就必须保证不能交叉。否则就麻烦了。

找A::B::C,结果可能找到A::C和::C,这可不是我们愿意的,一旦程序大了,谁去理清楚这个玩意儿。最后还不是得程序员埋单,要么还得写清楚,要么就报错搞死人。

期待ruby后面改掉这个糟粕呀!!

 

测试代码改了下:

 

C = "At the top level"

module A
  C = "In A"
end

module A
  module Y
    #none
  end
end

module A
  module B
    include A::Y

    module X

    end

    puts "----B----"
    puts "Module.nesting=#{Module.nesting.inspect}" # => [A::B, A]
    puts "Module.nesting.first=#{Module.nesting.first}"
    puts "Module.nesting.first.ancestors=#{Module.nesting.first.ancestors.inspect}"
    puts "Object.ancestors=#{Object.ancestors}"
    puts C              # => "In A"
    puts "----B----"
  end
end

module A::B
  puts Module.nesting # => [A::B]
  puts C              # => "At the top level"
end

 你要是能写对,可真不错。理解了这个,下面的问题才能更好理解。

----B----

Module.nesting=[A::B, A]

Module.nesting.first=A::B

Module.nesting.first.ancestors=[A::B, A::Y]

Object.ancestors=[Object, Kernel, BasicObject]

In A

----B----

A::B

At the top level

 

Process finished with exit code 0

 

Ruby's Autoload

http://fantaxy025025.iteye.com/blog/2098356 因为篇幅,拎出去写了分析,这里。

 

Rails Constant Autoloading

实现原理

    使用了钩子方法:Module#const_missing

查找和加载步骤:

1. 使用ruby加载。

2. 如果报错,则用rails机制寻找并加载文件:MyModule::SomeClass # => my_module/some_class.rb

 

rails是跑在ruby上的。并没有修改ruby的寻找机制,只是出错了才处理加载,去加载常量对应的文件。

上文分析了ruby寻找常量时,在命名空间上一层一层的找法的缺陷。

rails也遵循了ruby的寻找方法,区别在于,rails会去加载对应的文件。

When Foo::Bar::Baz is referred to, then, Rails will attempt to load the following constants in turn, until it finds one that is already loaded:

  • Foo::Bar::Baz | 对应autoload_paths/foo/bar/baz.rb
  • Foo::Baz | 对应autoload_paths/foo/baz.rb
  • Baz | 对应autoload_paths/baz.rb

这样也会有加载的文件产生交叉的问题。

As soon as an already-loaded constant Baz is encountered, Rails knows this cannot be the Baz it is looking for, and the algorithm raises a NameError.

这个是rails的机制,原因是什么?

因为ruby找不到的常量,rails只认为是因为没有加载对应的文件,其实这也是rails做的唯一工作

而如果已经存在一个这样的常量了,而ruby找不到,那么rails也不能做太多的工作。

 

rails为什么不去掉中间这种不好的加载方法呢?

    是可以去掉的。但很多人写的ruby代码,使用了2这种默认机制,如果去掉,就会导致自动加载不能用了。为了兼顾ruby,只能这样了。

 

 

Rails 自动加载的缺陷

# 同ruby一样,第二种加载容易导致交叉。

  详细见上面分析。

 

# 依赖加载顺序

  因为找不到相应的常量时,就会去找相应的文件。运行时,执行的不同顺序,会导致加载文件的顺序发生变化。这样可能引发很多问题,比如不同文件定义的常量同名了,但运行到不同的分支导致加载了不同的常量,这样的bug很难排查。

 

# 上下文信息丢失

  ruby中的常量有多重,class, module, 字面常量比如Cache="MEM"这种。rails只收到const_missing,并没有收到上下文信息。

  比如const_missing接收到Foo::Bar::Baz 这个常量有很多种表现形式,比如上面的简单的就3种了,还有不少变种,比如下面两种module的写法就是不同的,更别提module和class了。 

 

module Foo
  module Bar
    #sth
  end
end

 

module Foo::Bar
    #sth
  end
end

 

class Foo
  class Bar
    #sth
  end
end

 

rails接收不到上下文信息,所以导致rails只认为是第一种方式。

 

理解了文章的一些内容,再看 http://urbanautomaton.com/blog/2013/08/27/rails-autoloading-hell/

这个文章就好理解了。很多东西还是有其内在的原因的,死记只能解一时只用。 

 

+

+

+

——

+

+

+

你可能感兴趣的:(ruby的加载机制和rails_自动加载机制_对比)