我应该在NilClass中包含Enumerable吗?

时间:2016-01-04 22:38:33

标签: ruby-on-rails ruby enums

我使用的rails应用程序中常见的错误之一是nil类错误。具体来说,我经常会看到像" NoMethodError:undefined method`每个'为零:NilClass"。

这些都很烦人,通常是良性的,并且在整个地方挤满了可怕的无法检查代码。

以下是一个例子:

[3] pry(main)> def test(enum)
[3] pry(main)*   enum.each { |x| puts x }
[3] pry(main)* end
=> nil
[4] pry(main)> test(nil)
NoMethodError: undefined method `each' for nil:NilClass
from (pry):2:in `test'
[5] pry(main)>

如果我只是输入一个除非枚举,或者返回除非responds_to?(:each),则很容易修复。但是,如果我不想让这些检查混淆我的代码呢?

嗯,我想到了一个想法,我想知道这是不是一个坏主意。

[5] pry(main)> class NilClass
[5] pry(main)*   include Enumerable
[5] pry(main)*
[5] pry(main)*   def each; end
[5] pry(main)* end
=> nil
[6] pry(main)> test(nil)
=> nil
[7] pry(main)>

我无法想象任何库依赖于nil上的NoMethodError来驱动逻辑(如果它们听起来像是我不应该使用的库)。此外,这提供了各种好处,因为现在可以使用整个枚举方法:

[12] pry(main)> Enumerable.public_instance_methods(false).each { |method| puts "#{method.to_s.ljust(20)} => #{nil.send(method) {} }" if nil.method(method).parameters.empty? }
to_a                 => []
sort                 => []
sort_by              => []
find_all             => []
select               => []
reject               => []
collect              => []
map                  => []
flat_map             => []
collect_concat       => []
partition            => [[], []]
group_by             => {}
all?                 => true
any?                 => false
one?                 => false
none?                => true
min                  =>
max                  =>
minmax               => [nil, nil]
min_by               =>
max_by               =>
minmax_by            => [nil, nil]
take_while           => []
drop_while           => []
lazy                 => #<Enumerator::Lazy:0x007f84623c9d38>

所以我的问题是:

我应该这样做吗?

修改1

我想指出函数式语言(和rust)通常不会实现nil类。我的实际意图是将Nil变为空集,以便对nil的操作表现得像一个空集。

2 个答案:

答案 0 :(得分:6)

  

具体来说,我经常看到像“NoMethodError:undefined方法`每个'为nil:NilClass”这样的错误。

     

[...]

     

如果我只是放一个除非枚举的返回,或者除非responds_to?(:each)之外返回,那么很容易修复。

     

[...]

     

我应该这样做吗?

绝对不是。如果该错误经常发生,那么这意味着您的代码测试不良且开发不佳。

频繁NoMethodError表示您未能正确处理您在输入中收到的数据,应将其视为您需要修复的错误,而不是沉默

解决方案是开始隔离崩溃的每段代码。编写一个重现错误的测试并相应地处理输入。如果您希望传递一个值并传递一个nil,那么您需要了解为什么存在nil,修复代码并确保使用测试来避免回归。

答案 1 :(得分:2)

处理您所描述的问题有更微妙的方法。

对于您可以控制的方法,您可以确保设置了返回值:

def should_return_array
  # some code
  arr || []
end

def should_return_hash
  # some code
  options || {}
end

使用可能返回Enumerablenil的方法时,您可以使用相同类型的构造:

opts = may_return_hash_or_nil || {}
opts.each {|k, v|  }

arr = may_return_array_or_nil || []
arr.each {|v| }

opts = may_return_hash_or_nil
(opts || {}).each {|k, v|  }

arr = may_return_array_or_nil
(arr || []).each {|v| }