为什么我不应该扩展Struct.new初始化的实例?

时间:2018-03-15 08:33:54

标签: ruby rubocop

我们有一个遗留代码库,其中rubocop报告了一些我永远无法解决的错误:

  

不要扩展由Struct.new初始化的实例。   扩展它引入了一个多余的类级别,也可能会介绍   如果文件需要多次,则会出现奇怪的错误。

“多余的班级”究竟是什么意思,可能会引入什么样的“奇怪错误”?

(问,因为很明显我们在过去几年没有遇到任何这样的问题。)

2 个答案:

答案 0 :(得分:7)

Struct.new创建一个恰好是Struct的子类的匿名类:

s = Struct.new(:foo)
#=> #<Class:0x00007fdbc21a0270>

s.ancestors
#=> [#<Class:0x00007fdbc21a0270>, Struct, Enumerable, Object, Kernel, BasicObject]

您可以将该匿名类分配给常量以命名它:

Foo = Struct.new(:foo)
#=> Foo

Foo.ancestors
#=> [Foo, Struct, Enumerable, Object, Kernel, BasicObject]

这是创建Struct子类的常规方法。

另一方面,您的遗留代码似乎包含以下内容:

class Foo < Struct.new(:foo)
end

Struct.new创建一个匿名类(它没有分配给常量)和Foo子类,导致:

Foo.ancestors
#=> [Foo, #<Class:0x00007fee94191f38>, Struct, Enumerable, Object, Kernel, BasicObject]

显然,匿名课程没有任何用途。

就像:

class Bar
end

class Foo < Bar   # or Foo = Class.new(Bar)
end

Foo.ancestors
#=> [Foo, Bar, Object, Kernel, BasicObject]

而不是:

class Bar
end

class Foo < Class.new(Bar)
end

Foo.ancestors
#=> [Foo, #<Class:0x00007fdb870e7198>, Bar, Object, Kernel, BasicObject]

后一个示例中由Class.new(Bar)返回的匿名类未分配给常量,因此既不使用也不需要。

答案 1 :(得分:3)

多余的班级正是这门班级Struct.new

Here is the reference使用源代码进行更详细的解释。

pull request on this cop还包含一个有价值的示例:

Person = Struct.new(:first, :last) do
  SEPARATOR = ' '.freeze
  def name
    [first, last].join(SEPARATOR)
  end
end

不等同于:

class Person < Struct.new(:first, :last)
  SEPARATOR = ' '.freeze
  def name
    [first, last].join(SEPARATOR)
  end
end

前者创建::Person::SEPARATOR,而后者创建::Person::Person::SEPARATOR

我认为常量查找主要被称为“奇怪的错误。”