【问题标题】:How to correct `Style/ClassVars` in RuboCop?如何纠正 RuboCop 中的“Style/ClassVars”?
【发布时间】:2023-03-31 11:45:01
【问题描述】:

我看到issue 在 Ruby 中使用类变量;但是,RuboCop 的 documentation 似乎不足以解决该问题。

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

irb 0.9.6 中使用Ruby 2.5.1 执行提供的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

所以,不。我们显然不能毫无冒犯地访问类变量。 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 在这里想表达什么?

【问题讨论】:

    标签: ruby rubocop


    【解决方案1】:

    您缺少变量范围之间的差异。

    class A
      @test = 42
    end
    

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

    A.instance_variable_get(:@test)
    #⇒ 42
    

    你可以为这个变量定义一个访问器:

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

    它在实例之间共享,并且要从实例中访问它,您应该参考类:

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

    以下代码在类实例上声明了一个实例变量:

    class A
      def initialize
        @test = 42
      end
    end
    

    可以从Ainstances访问:

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

    类变量在类层次结构中使用时有一些缺点,这 [可能] 为什么 Rubocop 建议不要使用类变量(或其他任何建议 - 老实说,我从未使用过它,因为它带来的危害大于帮助 IMSO。)

    在您的第一个 sn-p 中,您错过了 @。正确的代码是:

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

    【讨论】:

    • @Nate 好吧,这是与类级别的实例变量相反的类变量的期望行为,因此 Rubocop 最好闭嘴(或解释意图,以便人们不需要发布类似的问题以上关于SO。)
    • @Nate 类变量在需要收集一些基类范围的信息并使其可以从整个层次结构树分支中访问时非常有用。例如,ActiveRecord::Base 可以将所有实例(直接和间接)存储在类变量中。这就是类变量的发明目的。
    • @Nate 对他们文档中的意图是正确的,但 Aleksei 对其他一切都是正确的。 RuboCop 驱逐类变量并提供虚假更正是不合理的。
    • @RWDJ 如果您确实关心 Rubocop 文档,请在其 repo 中创建问题;这比坐在这里等待陌生人疯狂猜测更有意义:)
    • 当然有。正如警察建议的那样,使用类级别的实例变量。这在所有子类之间共享,但不会让您有可能让子类意外更改父类的变量。如果您需要实例来访问您的类实例变量,那么您可能应该使用 reader 方法。如果你使用 Rails,还可以查看class_attribute,它有很多继承特性,这个评论太多了。
    【解决方案2】:

    最佳答案的精简版:

    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
    

    【讨论】:

      【解决方案3】:

      在 2023 年初,问题仍然存在。因为 rubocop 文档不是发布有关 ruby​​ 中 OOP 复杂性的信息的地方。

      不喜欢使用类变量来自于我们使用类继承时的意外行为。但是我们喜欢看代码,而不是看描述,文档清楚地写着:

      为类变量设置值时必须小心;如果一个类已经被继承,改变一个类变量的值也会影响继承的类。这意味着使用类实例变量几乎总是更好。

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

      我确认 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 程序的一种方法是继承该类并对它进行自己的更改。通常项目很复杂,有很多隐含的依赖(说实话=)),如果直接对类进行更改,项目就会崩溃。因此,我们使用继承,将子类用于具有自己设置的新服务中,或者子类改变程序一部分的行为。如果在继承过程中,子类突然改变了基类,那么继承就失去了意义!失去了灵活性。

      但是任何问题都需要在上下文中查看。如果你一个人在写一个微型项目,那么@@ var 没有任何问题。你只需要理解。

      【讨论】:

        猜你喜欢
        • 2021-05-01
        • 1970-01-01
        • 2019-10-09
        • 2023-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-23
        • 1970-01-01
        相关资源
        最近更新 更多