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