在ruby中使用nil上的method_missing是个好主意

时间:2015-11-16 16:51:18

标签: ruby

在ruby中,我厌倦了在使用之前不断检查对象是否具有所有必需的属性和子属性,如

if (obj && obj[:a] && obj[:a][:b] && obj[:a][:b][:c] && obj[:a][:b][:c] > 0)
    # do something useful
end

通过在method_missing上定义NilClass以返回nil来避免这种情况是否是一个好主意?

class NilClass
    def method_missing(method_name, *args, &block)
        nil
    end
end

通过正确的比较,我可以使用默认值来处理所有事情 我甚至可以比较其他表达式,比如

if (obj[:a][:b][:c] > 0)
    # do something useful
else
    # default behaviour if obj[:a][:b][:c] <= 0 or obj[:a][:b][:c] is undefined
end

在特殊情况下,我总是可以手动检查nil

什么可以被打破?

6 个答案:

答案 0 :(得分:3)

如果Hash.fetch不存在并且短路有条件,则允许您返回false。 http://ruby-doc.org/core-1.9.3/Hash.html#method-i-fetch

if obj.fetch(:a, {}).fetch(:b, {}).fetch(:c, false)
  #useful stuff
end

正如评论中提到的,如果你真的不想检查这个对象是不是哈希你不能做 (obj||{}).fetch(:a, {}).fetch(:b, {}).fetch(:c, false)

答案 1 :(得分:2)

你还没有明确= "=""X"A1是什么,但我认为它是一个哈希。

如果它会破坏某些东西并不是很明显,但是有更好的方法而不是定义这样的方法,所以你应该避免这样做。

这个问题已在stackoverflow上反复提出,有几个解决方案,但最新和最好的方法是obj使用Ruby 2.3

Hash#dig

答案 2 :(得分:2)

假设obj是一个哈希,并且最里面的值都不是nil,那么你就不必为等待Hash#dig的你的治疗做好准备:

v = get_it_if_you_can(obj, :a, :b, :c)
if v
  # do something
end

其中:

def get_it_if_you_can(h, *args)
  args.reduce(h) { |o,k| o && (o.is_a?(Hash) ? o[k] : nil) }
end

例如:

h = { a: { b: { c: "yes" } } }

get_it_if_you_can(h, :a )             #=> {:b=>{:c=>"yes"}} 
get_it_if_you_can(h, :a, :b )         #=> {:c=>"yes"} 
get_it_if_you_can(h, :a, :b, :c )     #=> "yes" 
get_it_if_you_can(h, :b, :a, :c )     #=> nil 
get_it_if_you_can(h, :a, :b, :c, :d ) #=> nil 

另一种方式:

def get_it_if_you_can(h, *args)
  args.reduce(h) { |o,k| o[k] } rescue nil
end

get_it_if_you_can(h, :a )             #=> {:b=>{:c=>"yes"}} 
get_it_if_you_can(h, :a, :b )         #=> {:c=>"yes"} 
get_it_if_you_can(h, :a, :b, :c )     #=> "yes" 
get_it_if_you_can(h, :b, :a, :c )     #=> nil 
get_it_if_you_can(h, :a, :b, :c, :d ) #=> nil 

我也不喜欢这样,因为其他错误可能会被掩盖:

def get_it_if_you_can(h, *args)
  args.redcue(h) { |o,k| o[k] } rescue nil
end

get_it_if_you_can(h, :a, :b, :c )     #=> nil 

即使我们试图缩小异常类,这个错误也会滑落:

def get_it_if_you_can(h, *args)
  begin
    args.redcue(h) { |o,k| o[k] }
  rescue NoMethodError
    nil
  end
end

这是解救异常的另一个问题。如果:

h = { a: { "yes"=>1 } }
你可能会期待:

get_it_if_you_can(h, :a, "yes", 1)

返回nil,但返回0。这是因为块变量的连续值如下:

o #=>{:a=>{"yes"=>1}}
k #=> :a

o #=> {"yes"=>1}
k #=> "yes"

o #=> 1,
k #=> 1

1[1] = 0

(见Fixnum#[]

答案 3 :(得分:1)

如果你不指望它们,有很多东西可以破坏。例如。你不会注意到许多方法名称的拼写错误,因为它们会被method_missing默默地吃掉。

但是,对于许多人来说,如果使用得当,你提出的建议是理想的。因此,Ruby 2.3将包含a save navigation operator的特殊语法:

foo&.bar

对于哈希,您可以在Ruby 2.3中使用Hash#dig,类似于:

if obj && obj.dig(:a, :b, :c, default: 0) > 0
  # ...
end

答案 4 :(得分:0)

method_missing影响性能,因为它仅在其他所有内容之后进行检查,并附加上下文分配等。 常见的模式是定义遇到名称的方法,但事实并非如此。

另外,如果nil意外地显示在代码中的其他位置,您可能会花费几个小时的痛苦调试时间。

最佳解决方案是使用已经提到的Hash#fetch,或者如果你想成为激进的 - 当你的密钥丢失时,让你的哈希值返回一个特殊的空哈希

> (h = {a:1}).default = Class.new(Hash){ define_method(:default){|k| self }; define_method(:nil?){true}; define_method(:==){|a|a==nil}}.new
> h[:foo][:bar]
{}
> h[:foo][:bar].nil?
true

答案 5 :(得分:0)

我认为method_missing是错误的工具。相反,我会提取一个方法。

你还没有解释obj[:a][:b][:c] > 0的重要性,所以我猜:

def positive_value_for_c? obj
  begin
    obj[:a][:b][:c] > 0
  rescue NoMethodError
    false
  end
end