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

ruby:如何正确地要求(避免循环依赖),ruby,dependencies,dependency-management,circular-dependency,Ruby,Dependencies,Dependency Management,Circular Dependency,今天我面临一个奇怪的问题: 模块上出现“缺少方法”错误,但该方法存在,并且需要定义模块的文件。经过一些搜索之后,我发现了一个循环依赖项,其中两个文件相互需要,现在我假设ruby以静默方式中止循环需要 编辑开始:示例 文件“a.rb”: require './b.rb' module A def self.do_something puts 'doing..' end end 文件“b.rb”: require './a.rb' module B d

今天我面临一个奇怪的问题: 模块上出现“缺少方法”错误,但该方法存在,并且需要定义模块的文件。经过一些搜索之后,我发现了一个循环依赖项,其中两个文件相互需要,现在我假设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:未初始化的常量A(namererror)
。这两个文件都必须有requires,因为它们要从命令行独立运行(我编写了这段代码以保持简短)。 所以B.电话必须在那里。一个可能的解决方案是将requires包装到
if uuu FILE\uuuu==0
,但这似乎不是正确的方法

编辑结束


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

module MainModule
  module SubModule
    module SubSubModule
    end
  end
end
在哪里需要子模块?全部在主目录中,还是只有主目录中的子目录和子目录中的子目录

任何帮助都会很好

总结 ForFS的回答和评论中讨论了为什么会发生这种情况

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

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

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

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

  • 如果一个文件需要来自同一库中另一个文件的代码,我会在需要代码的文件中使用
    require\u relative

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

  • 据我所知,Ruby需要按照要求的顺序进行操作,因此循环依赖关系并不重要

    (Ruby v1.9.2)

    在回答有关显示循环依赖性问题的示例的评论时:

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

    $irb
    需要“/Volumes/RubyProjects/Test/stackoverflow8057625/b.rb”
    =>正确
    B.呼叫
    正在做..
    =>零


    希望您已经知道的几件基本事情:

  • Ruby是解释的,而不是编译的,因此您不能执行解释器没有看到的任何代码

  • require
    仅将文件中的代码插入程序的该点,换句话说,程序顶部的
    require
    将在底部的
    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 
    
    希望这能解释其他人给你的好答案,如果你仔细想想,为什么很难回答你的最后一个问题:在哪里放置require语句。在Ruby中,你需要的是文件而不是模块,因此在代码中放置require的位置取决于文件的组织方式

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

    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 
    
    结果:

    #=> 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
    
    只要您确保模块文件(a.rb和b.rb)的第一行是
    require'delay'
    ,并且模块末尾包含delay,一切都应该正常


    最后一点注意:只有当您无法将定义代码与模块执行调用解耦时,此实现才有意义。

    此外,请注意(与
    加载不同)
    )多个
    requires
    到同一个文件只会导致一次加载该文件。感谢您的回答。但是,正如您可以尝试使用我添加的示例一样,循环requires存在一些问题。是的,这是可行的,但是这些文件应该可以自己运行,因此
    B.calling
    必须在那里(请参见编辑)。或者这只是一个非常糟糕的想法,只会带来麻烦吗?正如@sheldonh在上面所说的,现在代码过于简单,如果我必须对其进行评论,我只会说不要这样编写。如果两个文件都需要单独运行,但共享行为,那么我建议采用该共享行为,并将其放在第三个文件中,这两个文件都需要因此,倾向于将“库”类型代码(不独立运行的东西)与“可运行的”类型代码(独立运行的东西)分开,这样B.calling就会在它自己的文件中
    #=> 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