ruby:如何正确要求(以避免循环依赖)

发布于 2024-12-14 12:07:48 字数 1411 浏览 1 评论 0原文

今天我遇到了一个奇怪的问题: 在模块上出现“缺少方法”错误,但该方法在那里并且需要定义该模块的文件。经过一番搜索后,我发现了一个循环依赖,其中两个文件相互需要,现在我假设 ruby​​ 默默地中止了循环需要。


编辑开始:示例

文件'a.rb':

require './b.rb'

module A
    def self.do_something
        puts 'doing..'
    end
end

文件'b.rb':

require './a.rb'

module B
    def self.calling
        ::A.do_something
    end
end

B.calling

执行b.rb给出b.rb:5:in'calling':未初始化的常量A(NameError)。这两个文件都必须存在需求,因为它们打算从命令行单独运行(我省略了该代码以保持简短)。 所以 B.calling 必须存在。一种可能的解决方案是将需求包装在 if __FILE__ == $0 中,但这似乎不是正确的方法。

编辑 End


以避免这些难以发现的错误(顺便说一句,如果 require 抛出异常不是更好吗?),是否有一些关于如何构建项目以及在哪里需要什么的指南/规则?例如,如果我

module MainModule
  module SubModule
    module SubSubModule
    end
  end
end

应该在哪里需要子模块?全部在主中,还是只有子在主中,子子在子中?

任何帮助都会非常好。

摘要

forforfs 的回答和评论中讨论了为什么会发生这种情况的解释。

到目前为止,最佳实践(正如lain所指出或暗示的那样)似乎如下(如果我错了,请纠正我):

  1. 将顶部命名空间中的每个模块或类放在以模块/类命名的文件中。在我的示例中,这将是 1 个名为“main_module.rb”的文件。 如果有子模块或子类,则创建一个以模块/类命名的目录(在我的示例中为目录“main_module”,并将子类/子模块的文件放入其中(在示例 1 中名为“sub_module.rb”的文件)对命名空间的每个级别都
  2. 需要逐步重复此操作(在示例中,MainModule 需要 SubModule,而 . Submodule 将需要 SubSubModule
  3. 将运行代码中的“运行”代码与“定义”代码分开,需要一次顶级模块/类,因此因为 2.现在您的所有库功能都应该可用,并且您可以运行任何定义的方法,

感谢所有回答/评论的人,这对我帮助很大!

today i was facing a strange problem:
got a 'missing method' error on a module, but the method was there and the file where the module was defined was required. After some searching i found a circular dependency, where 2 files required each other, and now i assume ruby silently aborts circular requires.


Edit Begin: Example

File 'a.rb':

require './b.rb'

module A
    def self.do_something
        puts 'doing..'
    end
end

File 'b.rb':

require './a.rb'

module B
    def self.calling
        ::A.do_something
    end
end

B.calling

Executing b.rb gives b.rb:5:in 'calling': uninitialized constant A (NameError). The requires have to be there for both files as they are intended to be run on their own from command line (i ommitted that code to keep it short).
So the B.calling has to be there. One possible solution is to wrap the requires in if __FILE__ == $0, but that does not seem the right way to go.

Edit End


to avoid these hard-to-find errors (wouldn't it be nicer if the require threw an exception, by the way?), are there some guidelines/rules on how to structure a project and where to require what? For example, if i have

module MainModule
  module SubModule
    module SubSubModule
    end
  end
end

where should i require the submodules? all in the main, or only the sub in the main and the subsub in the sub?

any help would be very nice.

Summary

An explanation why this happens is discussed in forforfs answer and comments.

So far best practice (as pointed out or hinted to by lain) seems to be the following (please correct me if i'm wrong):

  1. put every module or class in the top namespace in a file named after the module/class. in my example this would be 1 file named 'main_module.rb.'
    if there are submodules or subclasses, create a directory named after the module/class (in my example a directory 'main_module', and put the files for the subclasses/submodules in there (in the example 1 file named 'sub_module.rb'). repeat this for every level of your namespace.
  2. require step-by-step (in the example, the MainModule would require the SubModule, and the Submodule would require the SubSubModule)
  3. separate 'running' code from 'defining' code. in the running code require once your top-level module/class, so because of 2. all your library functionality should now be available, and you can run any defined methods.

thanks to everyone who answered/commented, it helped me a lot!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

二手情话 2024-12-21 12:07:49

不久前在 Ruby 邮件列表上询问过这个问题后,当我的库中曾经有一个文件只是为了需要一些东西时,我更改了这两条规则:

  1. 如果一个文件需要来自同一库中另一个文件的代码,我在需要代码的文件中使用 require_relative

  2. 如果文件需要来自不同库的代码,我会在需要代码的文件中使用 require

据我了解,Ruby 按照要求的顺序进行请求,因此循环依赖关系并不重要。

(Ruby v1.9.2)

回答关于显示循环依赖问题的示例的评论:

实际上,该示例的问题不在于 require 是循环的,而是在之前调用了 B.calling要求已完成。如果你从 b.rb 中删除 B.calling ,它就可以正常工作。例如,在 irb 中,代码文件中没有 B.calling,但随后运行:

$ irb
需要 '/Volumes/RubyProjects/Test/stackoverflow8057625/b.rb'
=>真实
B. 呼叫
做..
=>无

After asking about this on the Ruby mailing list a while back, when I used to have a file in my libraries just for requiring things, I changed to these two rules:

  1. If a file needs code from another in the same library, I use require_relative in the file that needs the code.

  2. If a file needs code from a different library, I use require in the file that needs the code.

As far as I understand it, Ruby requires in the order it is asked to, and so it doesn't matter about circular dependencies.

(Ruby v1.9.2)

In answer to the comment about the example showing circular dependency problems:

actually, the problem with the example isn't that the requires are circular, but that B.calling is called before the requires have completed. If you remove the B.calling from b.rb it works fine. For example, in irb without B.calling in the code file but run afterwards:

$ irb
require '/Volumes/RubyProjects/Test/stackoverflow8057625/b.rb'
=> true
B.calling
doing..
=> nil

凉月流沐 2024-12-21 12:07:49

希望您已经了解一些基本知识:

  1. Ruby 是解释型的,而不是编译型的,因此您无法执行解释器未见过的任何代码。

  2. require 只是将文件中的代码插入到程序的该位置,换句话说,程序顶部的 require 将在 < code>require 在底部。

(注意:编辑以考虑 require 语句行为)
所以如果你要这样做:
ruby a.rb 这是 ruby​​ 解释器将看到并执行的内容:

#load file b.rb <- from require './b.rb' in 'a.rb' file

#load file a.rb <- from require './a.rb' in 'b.rb' file
  #this runs because a.rb has not yet been required

#second attempt to load b.rb but is ignored <- from require './b.rb' in 'a.rb' file

#finish loading the rest of a.rb

module A
  def self.do_something
    puts 'doing..'
  end
end

#finish loading the rest of b.rb

module B
  def self.calling
    ::A.do_something
  end
end
B.calling

#Works because everything is defined 

如果您先运行 b,ruby b.rb,解释器将看到:

#load file a.rb <- from require './a.rb' in 'b.rb' file

#load file b.rb <- from require './b.rb' in 'a.rb' file
  #this runs because b.rb has not yet been required

#second attempt to load a.rb but is ignored <- from require './a.rb' in 'b.rb' file

#finish loading the rest of b.rb
module B
  def self.calling
    ::A.do_something
  end
end
B.calling #NameError, ::A.do_something hasn't been defined yet.

希望这解释了其他人给了你很好的答案,如果你想一想,为什么很难回答你最后一个关于在哪里放置 require 语句的问题。使用 Ruby,您需要的是文件而不是模块,因此您将需求放在代码中的位置取决于文件的组织方式。

如果您绝对需要能够定义模块并以随机顺序执行方法,那么您可以实现类似的方法来收集对尚不存在的模块的调用,然后在它们出现时调用它们。

module Delay
  @@q = {}  
  def self.call_mod(*args) #args format is method_name, mod_name, *args
    mod_name = args.shift
    method_name = args.shift
    #remaining args are still in args
    mod = Object.const_get(mod_name.to_sym)
    mod.send(method_name.to_sym, *args)
  end

  def self.exec(mod_name, *args)
    begin
      args.unshift(mod_name)
      self.call_mod(*args)
    rescue NameError, NoMethodError
      @@q[mod_name] ||= []
      @@q[mod_name] << args
    end
  end

  def self.included(mod)
    #get queued methods
    q_list = @@q[mod.name.to_sym]
    return unless q_list
    #execute delayed methods now that module exists
    q_list.each do |args|
      self.call_mod(*args)
    end
  end
end 

请务必先定义 Delay 模块,然后使用 Delay.exec(:B, :calling, any_other_args),而不是调用 B.calling。因此,如果在“延迟”模块之后有以下内容:

Delay.exec(:B, :calling)   #Module B is not defined

module B
  def self.calling
    ::A.do_something
  end
  include Delay #must be *after* any callable method defs
end

module A
  def self.do_something
    puts 'doing..'
  end
  include Delay #must be *after* any callable method defs
end

结果:

#=> doing..

最后一步是将代码分解为文件。一种方法可能是拥有三个文件,

delay.rb   #holds just Delay module
a.rb       #holds the A module and any calls to other modules 
b.rb       #holds the B module and any calls to other modules

只要您确保 require 'delay' 是模块文件的第一行(a.rb 和 b.rb),并且 Delay 包含在模块文件的末尾模块,一切应该可以工作。

最后注意:仅当您无法将定义代码与模块执行调用分离时,此实现才有意义。

A couple of basic things that you hopefully already know:

  1. Ruby is interpreted, not compiled, so you can't execute any code that hasn't been seen by the interpreter.

  2. require just inserts the code from the file into that point of the program, in other words, a require at the top of the program will be interpreted before a require at the bottom.

(Note: Edited to account for require statements behavior)
So if you were to do:
ruby a.rb this is what the ruby interpreter would see and execute:

#load file b.rb <- from require './b.rb' in 'a.rb' file

#load file a.rb <- from require './a.rb' in 'b.rb' file
  #this runs because a.rb has not yet been required

#second attempt to load b.rb but is ignored <- from require './b.rb' in 'a.rb' file

#finish loading the rest of a.rb

module A
  def self.do_something
    puts 'doing..'
  end
end

#finish loading the rest of b.rb

module B
  def self.calling
    ::A.do_something
  end
end
B.calling

#Works because everything is defined 

If instead you ran b first, ruby b.rb, the interpreter would see:

#load file a.rb <- from require './a.rb' in 'b.rb' file

#load file b.rb <- from require './b.rb' in 'a.rb' file
  #this runs because b.rb has not yet been required

#second attempt to load a.rb but is ignored <- from require './a.rb' in 'b.rb' file

#finish loading the rest of b.rb
module B
  def self.calling
    ::A.do_something
  end
end
B.calling #NameError, ::A.do_something hasn't been defined yet.

Hopefully this explains the good answers the others have given you, and if you think about it, why it's hard to answer your last question about where to put require statements. With Ruby, you're requiring files not modules, so where you put the require in your code, depends on how your files are organized.

If you absolutely need to be able to have modules defined and methods execute in random order, then you could implement something like this to collect calls on modules that don't yet exist, and then call them when they pop into being.

module Delay
  @@q = {}  
  def self.call_mod(*args) #args format is method_name, mod_name, *args
    mod_name = args.shift
    method_name = args.shift
    #remaining args are still in args
    mod = Object.const_get(mod_name.to_sym)
    mod.send(method_name.to_sym, *args)
  end

  def self.exec(mod_name, *args)
    begin
      args.unshift(mod_name)
      self.call_mod(*args)
    rescue NameError, NoMethodError
      @@q[mod_name] ||= []
      @@q[mod_name] << args
    end
  end

  def self.included(mod)
    #get queued methods
    q_list = @@q[mod.name.to_sym]
    return unless q_list
    #execute delayed methods now that module exists
    q_list.each do |args|
      self.call_mod(*args)
    end
  end
end 

Be sure to define the Delay module first and then rather than calling B.calling you would use Delay.exec(:B, :calling, any_other_args). So if you have this after the Delay module:

Delay.exec(:B, :calling)   #Module B is not defined

module B
  def self.calling
    ::A.do_something
  end
  include Delay #must be *after* any callable method defs
end

module A
  def self.do_something
    puts 'doing..'
  end
  include Delay #must be *after* any callable method defs
end

Results in:

#=> doing..

Final step is to break the code up into files. One approach could be to have three files

delay.rb   #holds just Delay module
a.rb       #holds the A module and any calls to other modules 
b.rb       #holds the B module and any calls to other modules

As long as you make sure require 'delay' is the first line of the module files (a.rb and b.rb) and Delay included at the end of the module, things should work.

Final Note: This implementation only makes sense if you cannot decouple your definition code from the module execution calls.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文