在ruby中使用nil上缺少的方法_是个好主意吗

在ruby中使用nil上缺少的方法_是个好主意吗,ruby,Ruby,在ruby中,我厌倦了在像这样使用对象之前不断检查对象是否具有所有必需的属性和子属性 if (obj && obj[:a] && obj[:a][:b] && obj[:a][:b][:c] && obj[:a][:b][:c] > 0) # do something useful end 通过在NilClass上定义method\u missing以返回nil来避免这种情况是一个好主意吗 class NilClas

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

if (obj && obj[:a] && obj[:a][:b] && obj[:a][:b][:c] && obj[:a][:b][:c] > 0)
    # do something useful
end
通过在
NilClass
上定义
method\u missing
以返回
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
if(obj[:a][:b][:c]>0)
#做些有用的事
其他的

#如果使用Hash.fetch的obj[:a][:b][:c]的默认行为将允许您在它不存在时返回false并短路该条件。

正如在评论中提到的,如果你真的不想检查这个对象是否是散列,你就不能这么做
fetch(:a,{})。fetch(:b,{})。fetch(:c,false)
您还没有弄清楚什么是
obj
,但我假设它是一个散列

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

这个问题在stackoverflow上被反复提出,有几种解决方案,但最新也是最好的方法是使用附带的
Hash#dig


如果你没有预料到,有上千种东西会被打破。例如,你不会注意到很多方法名的拼写错误,因为它们会被
方法\u missing
悄悄地吃掉

尽管如此,对于许多人来说,你的建议如果使用得当,是一件值得拥有的东西。因此,Ruby 2.3将包含以下特殊语法:

对于散列,您可以在Ruby 2.3中使用类似的方法:

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

假设
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


(请参阅)

方法\u缺少
会影响性能,因为它只会在所有其他操作以及额外的上下文分配等之后进行检查。 常见的模式是使用遇到的名称定义方法,但情况并非如此

另外,如果
nil
在代码中的其他地方意外显示,则可能会给自己带来数小时痛苦的调试时间

最好的解决方案是使用已经提到的
散列#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

我认为
method\u missing
是这项工作的错误工具。相反,我将提取一个方法

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

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

我想到了“玩火”这个词。一个简单的例子:
([“鸡”、“猪”].index(“chikcent”)+1==1)?1:2#=>2
。这里
:+
调用
方法\u missing
。这个反例似乎不是那么反-毕竟。。。看起来写的是:如果有
chikchen
,那么返回
1
,否则返回
2
,这正是这个方法所缺少的。如果你能找到一个现实生活中的反例,那就太好了。也许我的问题会变得毫无意义。你会同意,如果“chicken”没有拼写错误,那么将返回
1
。这里
index(“chikchen”)
返回
nil
方法缺失
导致
nil+1
返回
nil
nil==1
计算值
false
,因此拼写错误将导致餐桌上的惊喜。是的,但如果不使用
方法缺失
,这个例子看起来还是很奇怪。我绝不会在现实生活中这样使用它,因为众所周知,
#index
可以返回
nil
。相反,我会使用
if([…].index(…)#find else#not found
if([…].index(…)| |-1)+1==1)
。在这两种情况下,
method_missing
给出了相同的结果。
{a:1}.fetch(:a,{}).fetch(:b,{})#=>NoMethodError:1:Fixnum
的未定义方法'fetch'。Cary,错误在您的示例中,
{a:1}
1
已成功从中获取,而不是一个hash@Vasfed你的意思是什么<代码>1。fetch(:b,{})引发异常。大概,OP想要一个优美的结论。我的例子的要点是,一个人想要
h[:a][:b]
如果这个对象存在,而不知道
h
是什么样子。@CarySwoveland,你是对的,应该是
(h | |{})。fetch(…
我可以想象xaxa和他/她的领导之间的对话:“如果你不介意的话,我想等待v.2.3,这样我就可以使用
dig
”这个(第二个版本带有
rescue nil
)似乎是目前为止最好的解决方案。出于给出的原因,我倾向于第一个版本,但我不能说它“更好”“。第一个应用于
散列
es。而第二个可以应用于响应
[]
的任何对象,这是他们计划引入此功能的好消息
o #=>{:a=>{"yes"=>1}}
k #=> :a

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

o #=> 1,
k #=> 1
1[1] = 0
> (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
def positive_value_for_c? obj
  begin
    obj[:a][:b][:c] > 0
  rescue NoMethodError
    false
  end
end