2010-05-17 Заметки Об Идиотизме - Bugzilla 3.6: New Extensions System
"In accordance with UNIX philosophy, Perl gives you enough rope to hang yourself." — Larry Wall
(«Согласно философии UNIX, Perl предоставляет пользователю верёвку, достаточно длинную для того, чтобы на ней можно было повеситься.»)
…И программисты это делают очень часто. Прекрасная иллюстрация этой цитаты — новая система расширений Bugzilla 3.6. Если кратко, то я даже не знал, что в перле можно сделать такое извращение. И лучше бы не узнавал.
Если расписать подетальнее, идея у авторов была такая — пусть каждое расширение будет подклассом Bugzilla::Extension, вся его конфигурация будет задаваться константами в этом классе, а все хуки (hooks) будут методами этого класса. Идея на первый взгляд неплохая, однако во-первых ведёт к различным проблемам сама по себе, а во-вторых имеет ужасную реализацию.
Старая система расширений была, конечно, не очень хороша, но зато очень проста: есть папочка со скриптами *.pl, каждый из которых представляет 1 (один) хук, то есть по сути 1 (одну) функцию, вторая папочка с хуками в шаблоны, действующими по такому же принципу (положил файл, получай установленный хук), третья папочка «lib», в которую можно складывать свои модули, и т. п. Её тоже уже пару раз рефакторили, в конце концов она пришла к описанному виду.
Теперь о проблемах нового подхода. Первая проблема (выдержка из perldoc Bugzilla::Extension):
If Your Extension Needs Certain Modules In Order To Compile
If your extension needs a particular Perl module in order to compile, then you have a «chicken and egg» problem--in order to read REQUIRED_MODULES, we have to compile your extension. In order to compile your extension, we need to already have the modules in REQUIRED_MODULES!
To get around this problem, Bugzilla allows you to have an additional file, besides Extension.pm, called Config.pm, that contains just REQUIRED_MODULES. If you have a Config.pm, it must also contain the NAME constant, instead of your main Extension.pm containing the NAME constant.
Иными словами, это проблема «курицы и яйца» — если основному классу расширения для загрузки нужны какие-то Perl модули (он их делает use), то их логично прописать в REQUIRED_MODULES, чтобы об их необходимости напомнил checksetup.pl. Однако REQUIRED_MODULES сами находятся в том же классе, и поэтому не могут быть загружены.
Решение? Ну конечно, вынести их в отдельный файл. Замечательно. Его называют «конфиг». Но теперь вторая проблема — хочется, чтобы REQUIRED_MODULES были видимы как константа в основном классе расширения. Решение? <мысли авторов> Ээээ. Ну давайте и основной файл расширения, и конфиг будут относиться к одному и тому же пакету! То есть один пакет на два файла. Причём, в одном из них сказано, что он подкласс Bugzilla::Extension, а в другом (в конфиге) — нет. … — Уже начинаются извраты.
Дальше полёт фантазии продолжается и авторы решают, что класть пакет «по-нормальному», то есть по пути в ФС, совпадающему с именем пакета (extensions/foo/Bugzilla/Extension/Foo.pm) неудобно, слишком глубоко. Решение? Ну конечно, сделать ручную загрузку этих файлов через require и модификацию %INC. Теперь пакет Bugzilla::Extension::Foo будет составляться из двух файлов, расположенных по адресам extensions/foo/Extension.pm и extensions/foo/Config.pm — чувствуете, как изврат крепчает?
Следующие изыскания приводят авторов к мысли, что, наверное, нужно сделать всё офигенно настраиваемое и чтобы расширение могло задавать свои пути в @INC. И, конечно, простая модификация @INC на этапе загрузки расширения из (хотя бы из константы) их не устраивает. И чтобы загружаемые пакеты лежали по нормальным путям — тоже. Пусть пути extensions/foo/lib/Test.pm соответствует пакет Bugzilla::Extension::Foo::Test. Дальше они делают, чтобы пути включения возвращал метод объекта (не статический!) my_inc, который, тем не менее, можно переопределять и в Config.pm, а по умолчанию используется метод объекта Bugzilla::Extension. Теперь вспоминаем, что Config.pm у нас — не подкласс Bugzilla::Extension, и поэтому спокойно сказать $foo->my_inc мы не можем, нам надо написать дополнительную функцию вызова, которая сначала посмотрит, есть ли там my_inc, а уж потом ручками вызовет Bugzilla::Extension::my_inc.
А теперь подумаем, как же можно сделать так, чтобы @INC модифицировался, но после создания объекта расширения? Оказывается, в Perlе в @INC можно добавить не только текстовый путь, а также ссылку на функцию, которая что-нибудь будет возвращать. Причём возвращать она может не только путь. Также она может возвращать ссылку на файловый дескриптор открытого каталога. Чем не преминули воспользоваться авторы Bugzilla.
К каким проблемам это ведёт?
- К проблемам с перезагрузкой модулей, например Apache2::Reload
- К невозможности расширению переопределить какой-нибудь базовый модуль
- К поломке обратной совместимости, хотя расширений и так не было
- Но самое главное, к «узакониванию бардака», когда модуль физически лежит по «неправильному» пути!
Для поиска, выдержка из Changelog’а Bugzilla 3.6:
Bugzilla has a brand-new Extensions system. The system is consistent, fast, and fully documented. It makes it possible to easily extend Bugzilla’s code and user interface to add new features or change existing features. There’s even a script that will create the basic layout of an extension for you, to help you get started. For more information about the new system, see the Extensions documentation.
If you had written any extensions using Bugzilla’s previous extensions system, there is a script to help convert old extensions into the new format.
[ Хронологический вид ]Комментарии
Войдите, чтобы комментировать.