Почему эта ошибка Sorbet считается ссылкой на динамическую константу?

Я получаю следующую ошибку в 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?


person Peter Nixey    schedule 22.07.2020    source источник


Ответы (1)


Должен ли я пропустить константу и вместо этого просто определить ее во вспомогательном методе: 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

→ Посмотреть на сорбете .run


Мне интересно узнать, почему такой шаблон проектирования является (подразумеваемой) плохой практикой.

Сорбет действует поэтапно. Во-первых, он узнает обо всех классах/модулях/константах в кодовой базе. Затем он узнает обо всех методах этих классов/модулей. Затем он узнает о типах этих методов. И, наконец, он узнает о типах локальных переменных в этих методах.

Когда Sorbet ищет, является ли (...)::MIN_AUTH действительно существующей константой, он не знает ничего, кроме констант, которые были определены до сих пор, ни методов, ни локальных переменных. self — это, по сути, локальная переменная, а .class — это метод, который можно было бы переопределить в дочернем классе. Поскольку он не знает ни о локальных переменных, ни о методах, он сообщает динамическую ссылку на константу. self.class — это произвольное выражение, а не статическая константа.

Так что, возможно, возникает следующий вопрос: почему Sorbet сначала навязывает этот, казалось бы, произвольный порядок разрешения констант? Две главные причины:

  • Скорость. Требуется меньше анализа, чтобы сказать, что это динамическая ссылка на константу, и попросить программиста реорганизовать код, чем для разрешения циклических ссылок. Учитывая, что существует относительно простой рефакторинг (о котором вы упоминаете), это кажется выгодным компромиссом, позволяющим ускорить каждую последующую проверку типов.

  • Читаемость. self.class::MIN_AUTH по сути является динамической диспетчеризацией с помощью алгоритмов постоянного разрешения Ruby. И на самом деле, постоянное разрешение во многих отношениях труднее понять, чем разрешение метода, потому что на него влияет как вложенность модулей, так и иерархия наследования (тогда как поиск метода зависит только от наследования). Полагаться на сложный поиск и отправку труднее читать, чем просто использовать методы, с которыми люди более знакомы (особенно при переходе с других языков на Ruby).

person jez    schedule 22.07.2020
comment
Такой полезный ответ прямо из первых уст. Спасибо за всю отличную работу, которую вы, ребята, проделали над этим @jez, это фантастическое дополнение к Ruby. Пока у меня есть, могу ли я узнать, используете ли вы (и можете ли поделиться) файл защиты или аналогичный, который запускает Sorbet в фоновом режиме? Я запускаю его вручную, но мне бы хотелось разделить панель и запустить его «при сохранении» вместе с моими тестами. - person Peter Nixey; 26.07.2020
comment
Я редко использую Сорбет. Почти вся моя работа связана с кодом C++, который реализует Sorbet. В нашем сообществе Slack есть много людей, которые используют Sorbet (sorbet.org/slack); Я призываю вас присоединиться и спросить там. - person jez; 27.07.2020