如何在 RuboCop 中更正 `Style/ClassVars`?

How to correct `Style/ClassVars` in RuboCop?

我看到 issue with using class variables with Ruby; however, it seems RuboCop's documentation 对于如何解决问题是不够的。

现在,我可以忽略它了。鉴于我的项目,这并不重要。但是,我只想知道 Rubocop 试图告诉我做什么,因为它没有意义。

Ruby 2.5.1irb 0.9.6 中执行提供的 code 得到:

class A
  @test = 10
end
#=> 10
class A
  def test
    @@test # you can access class variable without offense
  end
end
#=> :test
A.new.test
Traceback (most recent call last):
        3: from /Users/Ricky/.rbenv/versions/2.5.1/bin/irb:11:in `<main>'
        2: from (irb):12
        1: from (irb):9:in `test'
NameError (uninitialized class variable @@test in A)
Did you mean?  @test

所以,没有。显然,我们不能毫无冒犯地访问 class 变量。 irb 非常 被冒犯了。但是,ruby 建议使用 @test。也许这只是一个错字?让我们试试吧:

class A
  @test = 10
  def test
    @test # you can access class variable without offense
  end
end
#=> :test
A.new.test
#=> nil

因此,从未定义实例变量。 RuboCop 在这里想说什么?

您忽略了变量范围之间的区别。

class A
  @test = 42
end

上面在class范围内声明了一个实例变量。它可以访问为

A.instance_variable_get(:@test)
#⇒ 42

您可以为此变量定义访问器:

class A
  @test = 42
  def self.test
    @test
  end
end

A.test #⇒ 42

它在实例之间共享,要从实例访问它,您应该参考 class:

#     ⇓⇓⇓⇓⇓ HERE
A.new.class.test #⇒ 42

以下代码在 class 实例:

上声明了一个实例变量
class A
  def initialize
    @test = 42
  end
end

可以从 A 个实例 访问:

A.new.instance_variable_get(:@test)
#⇒ 42

Class 变量在 class 层次结构中使用时有一些缺点,这就是 [可能] 为什么 Rubocop 建议不要使用 class 变量(或它建议的任何东西——老实说从来没有用过它,因为它带来的伤害多于帮助恕我直言。)

在您的第一个片段中,您错过了 @。正确的代码是:

class A
# ⇓⇓ HERE
  @@test = 10
end
class A
  def test
    @@test # you can access class variable without offense
  end
end

最佳答案的浓缩版:

class A
  @test = 10
  def test
    @test # the instance's instance variable, which will be nil
    class.instance_variable_get(:@thing) # the class's instance variable, which you set to 10
  end
end

2023年初,问题依旧。因为 rubocop 文档不是 post 有关 ruby.

中 OOP 复杂性的信息的地方

不喜欢使用 class 变量来自于我们使用 class 继承时的意外行为。但是我们爱看代码,不爱看描述,文档里明明写着:

You have to be careful when setting a value for a class variable; if a class has been inherited, changing the value of a class variable also affects the inheriting classes. This means that it's almost always better to use a class instance variable instead.

我想补充 Alexey Matyushkin 的回答,并用简单的例子展示 class 变量的行为。并解释这会导致什么。

我确认 rubocop 文档中的代码是某种胡说八道:

# good
class A
  @test = 10
end

class A
  def test
    @@test # you can access class variable without offense
  end
end

class A
  def self.test(name)
    class_variable_get("@@#{name}") # you can access without offense
  end
end

begin
  puts A.new.test
rescue => e
  puts e.message
end

begin
  puts A.test 'test'
rescue => e
  puts e.message
end

puts "RUBY_VERSION: #{RUBY_VERSION}"

=>>>

uninitialized class variable @@test in A
Did you mean?  @test
uninitialized class variable @@test in A
Did you mean?  @test
RUBY_VERSION: 2.5.3

rubocop 真正想告诉我们的是什么。

puts 'When we use "classic" class variables:'
class A
  @@var = 10
  cattr_accessor :var
end
class Aa < A
end
puts Aa.var, '- the child class has inherited the methods and the value of the variable.'
Aa.var = 20
puts A.var, '- but the variable of the parent class was implicitly changed (bad)!'

puts 'When we use class instance variables:'
class B
  @test = 10
  class << self
    attr_accessor :test
  end
end
class Bb < B
end
puts Bb.test, '- the child class has inherited the methods, but not the value of the variable (this is also bad)!'
Bb.test = 20
puts B.test, '- a change in the child class does not lead to a change in the parent.'

=>>>

When we use "classic" class variables:
10
- the child class has inherited the methods and the value of the variable.
20
- but the variable of the parent class was implicitly changed (bad)!
When we use class instance variables:

- the child class has inherited the methods, but not the value of the variable (this is also bad)!
10
- a change in the child class does not lead to a change in the parent.

这有什么大不了的?这有什么害处?

修改 BIG 程序的一种方法是继承 class 并对其进行自己的更改。通常项目很复杂,有很多隐式依赖(说实话 =)),如果你直接对 class 进行更改,项目会崩溃。因此,我们使用继承,child class 用于具有自己设置的新服务,或者 child class 改变程序的一部分的行为。而如果在继承的过程中,childclass突然改变了基class,那么继承就失去了意义!失去灵活性。

但任何问题都需要结合上下文来看待。如果你是一个人在写一个微型项目,那么@@ var 也没什么问题。你只需要理解。