首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >仅在我自己的代码中使用Monkey patching方法(通过自动使用改进?)

仅在我自己的代码中使用Monkey patching方法(通过自动使用改进?)
EN

Stack Overflow用户
提问于 2019-07-30 14:05:21
回答 1查看 60关注 0票数 1

在我的Ruby (on Rails)项目中,我想禁止或限制使用标准库提供的一些方法。例如:我希望禁止调用Float#to_d,因为当有人在浮点型文字上使用该方法时,我出现了舍入错误。我想限制String#to_d只处理完全有效的字符串,因为我在'string'.to_d返回0.0时遇到了一些bug。

在全局范围内修补/覆盖这些方法当然不是一个好主意。它可能会打破一些依赖。

添加一个用来扫描代码使其不调用任何#to_d方法的链接器规则的问题是,它错误地限制了像Integer#to_d这样的合法方法的调用。当然,所有合法的方法都可以以不同的名称添加到相应的类中。但这需要添加大量的boilerplace (用于方法)并更改这些方法的所有调用。

我还考虑过使用改进。这将类似于猴子修补,但仅适用于使用细化的作用域。但是,必须将using语句添加到每个文件中将是丑陋且容易出错的。是否可以为我的项目中的每个文件自动激活优化,但不能为依赖项激活?

EN

回答 1

Stack Overflow用户

发布于 2019-07-30 18:16:08

如果你小心的话,你可以使用monkeypatch。我试着实现它,看看它是否有效。显然,你会受到很大的性能影响,所以我不建议在生产中使用它:P One可以通过记忆测试的位置来提高速度,而不是在每次调用方法时都向上查找目录树。

其思想是,如果在类上定义了原始方法,则将其移开,然后替换一个方法,该方法将检查您是否从代码中调用。我使用包含.git目录的目录作为您的项目目录;如果您的项目目录中直接有一个vendor目录,则它是例外的,就像项目目录之外的所有内容一样。如果您所在的位置是免税的,那么只需将其传递给保存的旧方法或沿继承链向上传递即可;如果不是,请尖叫foul。

代码语言:javascript
复制
require 'pathname'

PROJECT_CODE = Pathname.new(__dir__).ascend.find { |loc| (loc / '.git').directory? }
VENDOR_CODE = PROJECT_CODE / 'vendor'

class ForbiddenMethodError < StandardError; end

def forbid_method(klass, meth, &block)
  case
  when klass.instance_methods(false).include?(meth)
    old_meth = :"forbid_method_old_#{meth}"
    klass.alias_method old_meth, meth
  when klass.respond_to?(meth)
    old_meth = nil
  else
    raise ArgumentError, "No such method: #{klass}##{meth}"
  end

  klass.define_method(meth) do |*args|
    if !block || instance_exec(*args, &block)
      caller_loc = Pathname.new(caller_locations.first.path).expand_path
      caller_loc.ascend do |ancestor|
        case ancestor
        when PROJECT_CODE
          raise ForbiddenMethodError, "#{klass}##{meth}", caller[2..]
        when VENDOR_CODE
          break
        end
      end
    end

    if old_meth
      send(old_meth, *args)
    else
      super(*args)
    end
  end
end

这样,您就可以在项目代码中使Float#to_d和(有条件的) String#to_d失败:

代码语言:javascript
复制
require 'bigdecimal/util'
forbid_method(Float, :to_d)
forbid_method(String, :to_d) { !BigDecimal(self) rescue true }

如果你传递一个块,只有当块条件为真时,该函数才被禁止。(该块将传递该方法的所有参数,并将使用被禁止方法的接收器作为self执行。)

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/57265249

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档