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

awe*_*ndt 15 ruby rubocop

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

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

究竟什么意思是"多余的阶级"以及可能引入什么样的"奇怪错误"?

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

Ste*_*fan 9

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

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

s.ancestors
#=> [#<Class:0x00007fdbc21a0270>, Struct, Enumerable, Object, Kernel, BasicObject]
Run Code Online (Sandbox Code Playgroud)

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

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

Foo.ancestors
#=> [Foo, Struct, Enumerable, Object, Kernel, BasicObject]
Run Code Online (Sandbox Code Playgroud)

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

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

class Foo < Struct.new(:foo)
end
Run Code Online (Sandbox Code Playgroud)

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

Foo.ancestors
#=> [Foo, #<Class:0x00007fee94191f38>, Struct, Enumerable, Object, Kernel, BasicObject]
Run Code Online (Sandbox Code Playgroud)

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

就像是:

class Bar
end

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

Foo.ancestors
#=> [Foo, Bar, Object, Kernel, BasicObject]
Run Code Online (Sandbox Code Playgroud)

而不是:

class Bar
end

class Foo < Class.new(Bar)
end

Foo.ancestors
#=> [Foo, #<Class:0x00007fdb870e7198>, Bar, Object, Kernel, BasicObject]
Run Code Online (Sandbox Code Playgroud)

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


Ale*_*kin 5

多余的阶级等级正是这个阶级的结局Struct.new

\n\n

这里有更详细的解释和源代码参考。

\n\n

该警察的拉取请求还包含一个有价值的示例:

\n\n
Person = Struct.new(:first, :last) do\n  SEPARATOR = \' \'.freeze\n  def name\n    [first, last].join(SEPARATOR)\n  end\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n

不等于:

\n\n
class Person < Struct.new(:first, :last)\n  SEPARATOR = \' \'.freeze\n  def name\n    [first, last].join(SEPARATOR)\n  end\nend\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

我相信常量查找主要被称为 \xe2\x80\x9cweird 错误。\xe2\x80\x9d

\n

  • 如果没有解释为什么好/坏,那么好与坏的例子就没有多大帮助。 (4认同)