在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
。
什么可以被打破?
答案 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