【问题标题】:Is there a neat way to mark an entire module full of methods as deprecated?有没有一种巧妙的方法可以将一个充满方法的整个模块标记为已弃用?
【发布时间】:2018-06-22 13:03:03
【问题描述】:

我有一个 ruby​​ 类,其中包含许多将不再使用的逻辑,但我需要保留(至少一段时间)以实现向后兼容性。

我的计划是将这些方法移动到像LegacyStuff 这样的模块中,并将其包含在类中。我想知道是否有一种巧妙的方法来添加一些东西,这样当调用模块中的任何方法时,都会生成一个警告,而不必将warn 语句实际添加到每个单独的方法体中。

我想我正在寻找的是整个模块上的“调用前”或“调用后”挂钩之类的行为。我想这里的子问题是“这是个好主意吗?”

【问题讨论】:

  • “这是一个好主意吗?” -- 你有一个这么大的模块,以至于你甚至想到了这个问题,这才是真正的问题 IMO!至少你现在正在改变它的工作方式......
  • 你可以用一些method_added魔法来做到这一点。
  • 你可以(大量)调整我对另一个问题stackoverflow.com/a/50908899/681520 的回答来做到这一点,至少我认为它应该给你一些提示。
  • @Beejamin “我写 ruby​​ 已经 10 年了,从来没有使用过 method_added”——我也是。除非你是重度 MP,否则你根本不需要它。 :)
  • @engineersmnky 使用Module#prepend 可以更轻松地执行此类操作。我故意写了代码没有 Module#prepend作为挑战的关键部分:)

标签: ruby


【解决方案1】:

虽然这不是您所要求的(“一次标记所有方法”),并且在 ruby​​ 本身或像 rails 这样的大型框架中可能存在类似的东西,但它可能对某些人仍然有用。

module DeprecatedMethods
  def deprecated(method_name)
    prepend(Module.new do
      define_method method_name do |*args|
        puts "calling deprecated method #{method_name}"
        super(*args)
      end
    end)
  end

end

module AncientCode
  extend DeprecatedMethods

  deprecated def foo # selectively mark methods as deprecated
    puts "doing foo"
  end

  def bar
    puts "doing bar"
  end
end

class Host
  include AncientCode
end

host = Host.new
host.foo
host.bar
# >> calling deprecated method foo
# >> doing foo
# >> doing bar

目前,这会为每个已弃用的方法注入一个模块,这有点浪费。您可以通过执行以下操作将所有已弃用的方法放在一个模块中:

deprecated_methods :foo, :bar

【讨论】:

  • 另外,将它与method_added 结合起来并获得全面弃用的东西是微不足道的。
【解决方案2】:

代码

首先创建一个包含单个模块方法setup 的模块。该模块可以根据需要放入所需的文件中。此模块方法将从包含要包含在给定类中的实例方法的模块中调用。使用别名,它会修改这些实例方法以在执行其余代码之前打印一条消息(包含方法名称)。

模块添加消息

module AddMessage
  def self.setup(mod, msg_pre, msg_post)
    mod.instance_methods(false).
        each do |m|
          am = "_#{m}".to_sym
          mod.send(:alias_method, am, m)
          mod.send(:private, am)
          mod.send(:define_method, m) do |*args, &block|
            puts "%s %s %s" % [msg_pre, m, msg_post] 
            send(am, *args, &block)
          end
        end
  end
end

要包含在类中的模块

module LegacyStuff
  def old1
    "hi"
  end
  def old2(a, b)
    yield(a, b)
  end
  AddMessage.setup(self, "Warning:", "is deprecated")
end

AddMessage::setup 被传递三个参数,调用模块的名称以及用于形成警告消息的消息 prefix 和消息 suffix。当该模块中的实例方法 m 由类实例执行时,会在执行剩余计算之前打印消息 "Warning: #{m} is deprecated"(例如,"Warning: old1 is deprecated")。

使用

LegacyStuff 只是包含在一个类中。

class C
  include LegacyStuff
  # <constants, methods and so forth go here>
end

c = C.new
c.old1
  # Warning: old1 is deprecated
  #=> "hi"
c.old2(1,2) { |a,b| a+b }
  # Warning: old2 is deprecated
  #=> 3
c.cat
  #=> NoMethodError: undefined method `cat' for #<C:0x000000008ef0a8>

模块方法说明AddMessage:setup

此方法使用以下(通常不太熟悉的)方法:Module#instance_methodsModule#alias_methodModule#privateModule#define_method

对模块mod中定义的每个实例方法m执行以下三个步骤(例如,数组LegacyStuff.instance_methods(false)的元素)。

mod.send(:alias_method, am, m)

为方法创建别名 am(例如,_:old1 代表 old1)。

mod.send(:private, am)

将别名 am 设为私有方法(例如,_old1)。

mod.send(:define_method, m) do |*args, &amp;block| ... end

重新定义方法m(例如old1)打印指示字符串,然后执行别名am(例如_old1)。

【讨论】:

  • 有趣的方法。不过,如果可能的话,我最好希望包含在 LegacyStuff 模块中的东西。
  • 我希望这个 Take #24 是一个总结。
  • 这很漂亮。感谢所有的努力 - 出色的答案和解释。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-28
  • 2021-02-15
  • 2014-12-08
  • 1970-01-01
  • 1970-01-01
  • 2018-02-28
相关资源
最近更新 更多