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

Why shouldn't I extend an instance initialized by Struct.new?

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

Don't extend an instance initialized by Struct.new. Extending it introduces a superfluous class level and may also introduce weird errors if the file is required multiple times.

"a superfluous class level"到底是什么意思,可以引入什么样的"weird errors"?

(问是因为显然我们在过去几年没有遇到任何此类问题。)

多余的class关卡就是这个class到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

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

Struct.new 创建一个匿名 class 恰好是 Struct:

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

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

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

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

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

这是创建 Struct subclass 的常规方法。

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

class Foo < Struct.new(:foo)
end

Struct.new 创建一个匿名的 class (它没有分配给常量)并且 Foo subclasses 它,结果是:

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

显然,匿名 class 没有任何作用。

就像:

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) 返回的匿名 class 没有分配给常量,因此既不使用也不需要。