0

我在 Sorbet 中收到以下错误:

lib/guardian.rb:24: Dynamic constant references are unsupported https://srb.help/5001
    24 |      self.class::MIN_AUTH || raise("Minimum auth must be specified")

Guardian 类具有以下结构

class Guardian
  MIN_AUTH = AuthLevel.new(:STRONG)
  # ...

  def min_auth
    self.class::MIN_AUTH || raise("Minimum auth must be specified")
  end
end

它旨在使其可以在子对象上进行更改。它还被设计成如果它没有在子对象上指定,我们会听到它。我很想知道为什么这样的设计模式是(隐含的)不好的做法。我是否应该跳过常量而只在辅助方法中定义它:get_min_auth

4

1 回答 1

2

我是否应该跳过常量而只在辅助方法中定义它:get_min_auth

是的,就是这样。

# typed: true
class AuthLevel < T::Struct
  const :level, Symbol
end

class Guardian
  def get_min_auth
    AuthLevel.new(level: :STRONG)
  end

  def min_auth
    get_min_auth || raise("Minimum auth must be specified")
  end
end

→ 在 sorbet.run 上查看

要真正了解为什么在这里使用方法更好:您可以使用抽象方法来要求子类实现该方法。没有办法要求子类定义一个常量。这将使您的整个示例基本上消失,因为您不再需要raise. 它会变成一个静态类型错误:

# typed: true
class AuthLevel < T::Struct
  const :level, Symbol
end

class Guardian
  extend T::Sig
  extend T::Helpers
  abstract!

  sig {abstract.returns(AuthLevel)}
  def min_auth
  end
end

class MyGuardian < Guardian
end

→ 在 sorbet.run 上查看


我很想知道为什么这样的设计模式是(隐含的)不好的做法。

冰糕分阶段工作。首先,它了解代码库中的所有类/模块/常量。然后,它了解这些类/模块上的所有方法。然后它了解这些方法的类型。最后,它了解这些方法中局部变量的类型。

当 Sorbet 想要查看(...)::MIN_AUTH一个常量是否实际存在时,它只知道到目前为止已经定义的常量,而不知道方法和局部变量。self本质上是一个局部变量,并且.class是一个可以在子类上被覆盖的方法。由于它既不知道局部变量也不知道方法,因此它报告一个动态常量引用。self.class是“任意表达式”,而不是静态常量。

所以也许下一个问题是:为什么 Sorbet 会首先强加这种看似任意的解析常量的顺序?最大的两个原因:

  • 速度。说“这是一个动态常量引用”并要求程序员重构代码比允许循环引用需要更少的分析。鉴于有一个相对容易的重构(你提到),这似乎是一个值得的权衡,以使每个后续类型检查运行得更快。

  • 可读性self.class::MIN_AUTH本质上是通过 Ruby 的常量解析算法进行动态调度。事实上,常量解析在很多方面比方法解析更难理解,因为它受模块嵌套和继承层次结构的影响(而方法查找只受继承影响)。依靠复杂的查找和分派比仅仅使用人们更熟悉的方法(尤其是从其他语言转向 Ruby)更难阅读。

于 2020-07-22T23:46:28.573 回答