Template Toolkit — различия между версиями
(→«Не используйте Template Toolkit!») |
м (→«Не используйте Template Toolkit!») |
||
(не показано 6 промежуточных версий этого же участника) | |||
Строка 1: | Строка 1: | ||
{{CPAN|Template::Toolkit}} (а также TTk или просто TT) — шаблонизатор, Perl-библиотека для отделения уровня представления от уровня приложения. Короче говоря, относительно простой язык программирования, упрощающий генерацию динамических страниц, в основном, хотя и не обязательно — [[rupedia:HTML|HTML]]-страниц. | {{CPAN|Template::Toolkit}} (а также TTk или просто TT) — шаблонизатор, Perl-библиотека для отделения уровня представления от уровня приложения. Короче говоря, относительно простой язык программирования, упрощающий генерацию динамических страниц, в основном, хотя и не обязательно — [[rupedia:HTML|HTML]]-страниц. | ||
− | Статья [[rupedia:Template_Toolkit|Template Toolkit]] в русскоязычной Википедии | + | Статья [[rupedia:Template_Toolkit|Template Toolkit]] в русскоязычной Википедии пытается намекнуть нам на то, что по сути своей TT — «легковесная» библиотека, в отличие от «полновесных» фреймворков для построения веб-приложений. |
== «Не используйте Template Toolkit!» == | == «Не используйте Template Toolkit!» == | ||
Строка 7: | Строка 7: | ||
Или же очередная серия «[[:Категория:Заметки об идиотизме|Заметок об идиотизме]]». | Или же очередная серия «[[:Категория:Заметки об идиотизме|Заметок об идиотизме]]». | ||
− | + | Все проблемы TT происходят от того, что он ''тьюринг-полный''. Реализовать на нём можно ''ну абсолютно всё, что угодно''. Можно писать на TT, не используя нативный Perl-код вообще. «Всех» проблем, по сути, две — высокая средняя сложность кода шаблонов и очень низкая производительность. Идея о простых шаблонах, которые может редактировать даже дизайнер, накрывается медным тазом. Для примера достаточно взглянуть на шаблоны и код Bugzilla. | |
− | + | Забавно, что ''синтаксис'' TT, за исключением извращений, логичен, и даже в [[VMX-Template|своём шаблонизаторе]] я пришёл к похожему синтаксису, в том числе и к передаче переменных единым Perl-хешем. С другой стороны, а что же значит «за исключением извращений»? Раскроем смысл фразы — логично в синтаксисе TT всё, кроме: | |
− | + | * '''{{CPAN|Template::Manual::VMethods|Виртуальных методов}}''': зачем изобретать свой достаточно сложный язык, где «всё — объект», почему не использовать обычные вызовы функций? | |
+ | * '''Блоков''': «функции» в шаблонах вредны — их полезно выносить в отдельные шаблоны, а если таковых очень много — это, скорее всего, какие-то сообщения, в этом случае их можно объединять в хеш-таблицу и в один файл «локализации», как делает MediaWiki (см. например, [http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/languages/messages/MessagesRu.php?revision=60129&view=markup русскую локализацию]). | ||
+ | * '''Фильтров''': чем фильтр не функция (имеется ввиду ''внешняя'' функция), зачем вводить дублирующий функционал? | ||
+ | * '''Оператора выбора''' (SWITCH): его нет даже в самом Perl’е и почему-то никто не страдает, зачем же он в шаблонах? | ||
+ | * '''Макросов''': чем макрос не блок? Чем макрос не функция? Часто вы используете C-препроцессор в Perl-коде? | ||
+ | * '''Встраивания Perl-кода''': вредно для простоты шаблонов, неудобно (ибо Stash) и, возможно, опасно. | ||
+ | * '''{{CPAN|Template::Stash|Stash}}''''а (от англ. ''stash'' — тайник, «заначка»): производительность TT страдает в первую очередь именно от него — зачем-то введённого отдельного уровня хранения данных — по сути, собственной реализации хеш-таблицы со множеством наворотов, проверок и вообще лишних действий. «Зачем-то», по всей видимости — для обеспечения нетривиальных возможностей вроде установки функции-getter’а вместо значение переменной шаблона (нафиг в шаблонах такие хаки?). | ||
+ | * '''Try/catch''': про то, что это очередная возможность усложнить шаблоны, упоминать как-то даже глупо. Исключения, как известно, придуманы не для шаблонов, а для сложных (по уровню вложенности вызовов функций) конфигураций обработки ошибок, которая в шаблонах опять-таки не полезна, а вредна. Можно было бы подумать, что это всё для обработки ошибок приложения, но лично я считаю, что шаблон ''не должен вызывать функций приложения'' — его предназначение заключается как раз в изоляции уровня приложения от уровня представления. | ||
+ | * '''«Обёрток»''' (wrapper’ов): чем это не фильтр, а чем фильтр не функция? | ||
+ | * '''TT не восстанавливает значение счётчика после выхода из FOREACH''': это поведение не так страшно в языках программирования типа PHP, где всё равно переменная обычно попадает в локальную область видимости функции, однако в шаблонах, особенно в сложных, приводит к различным относительно трудноуловимым багам. | ||
+ | |||
+ | Таким образом, остаётся ''приблизительно'' следующий набор: скаляры, хеши и массивы, функции, условный оператор, оператор цикла и включение другого шаблона. То есть, простейшие элементы. Так вот, именно их синтаксис в TT и логичен :-) | ||
+ | |||
+ | Кстати, раз обычный Stash — просто кошмар неиллюзорный, а Template Toolkit был (да и до сих пор) весьма популярен, в районе года 2000 появилась его XS-версия (то есть, переписанная на C) — {{CPAN|Template::Stash::XS}}. После этого TT-шаблонам, всё-таки, полегчало. Но, во-первых, Stash никогда не был единственным замедляющим работу TT уровнем, во-вторых, даже Stash::XS всё равно работает не так быстро, как мог бы, а в-третьих, Stash::XS долгое время имел различные баги — например, поддержка UTF-8 в {{CPAN|Template::Stash::XS}} появилась только 4 июля 2009 года. И ещё Stash::XS менее безопасен — делает меньше проверок на ошибки. Пример раскопок по данной причине: [http://m0r1k.livejournal.com/11794.html Perl + mod_perl + gdb]. | ||
+ | |||
+ | Update от 25.06.2010. Ещё одним TT’шным идиотизмом является способ получения имени текущего шаблона, «в котором находишься». Его можно взять из обычного Stash’а, из '''специальных''' переменных с именами (!) «component» и «template». Гениально. Ну хотя бы «_» в начале имени поставили, или хотя бы верхним регистром написали. А так — конфликты обеспечены, та же Bugzilla на них нарывается. И опять-таки самое ужасное в том, что получение текущего шаблона вообще нелогично смешивать с данными шаблона — его логично вообще отделить в функцию, либо «сельф» какой-нибудь завести (<tt>$self/this</tt>). | ||
=== Пример: Bugzilla === | === Пример: Bugzilla === | ||
Строка 31: | Строка 46: | ||
Но есть и беда: Bugzilla всё ещё использует Template Toolkit, а разрабатывается длительное время и большим числом людей разной степени вшивости :) в итоге люди начинают использовать ''ну, прямо-таки все'' возможности Template Toolkit’а, что ведёт к уже упомянутому анти-паттерну разработки: перемещению 50 % логики в шаблоны. | Но есть и беда: Bugzilla всё ещё использует Template Toolkit, а разрабатывается длительное время и большим числом людей разной степени вшивости :) в итоге люди начинают использовать ''ну, прямо-таки все'' возможности Template Toolkit’а, что ведёт к уже упомянутому анти-паттерну разработки: перемещению 50 % логики в шаблоны. | ||
− | ==== …И её тормоза ==== | + | ==== …И её тормоза ==== |
Практически это приводит к следующим эффектам. Например, открытие бага с большим числом комментариев (например, с 703-емя — это наш максимум в [http://www.custis.ru/ ЗИС]'е) в Bugzilla 2.x, даже с учётом того, что это CGI-скрипт (то есть перезапускается интерпретатор), занимает примерно 1.5 секунды, а в 3.x, даже с учётом того, что это mod_perl (то есть интерпретатор не перезапускается и модули тоже не перезагружаются — ''хочется быстрее, да?'')… примерно 22 секунды, из которых 6 тратятся на работу {{CPAN|Template::Stash}}. После некоторых манипуляций средней хитрости время с 22 сек было доведено до 5.5 секунд, но это всё равно уже никак не 1.5 секунды 2-ой багзиллы, а для дальнейшего прогресса «локальных» улучшений уже мало — нужно глобально менять, так сказать, «архитектурные элементы». Та же история, хоть и менее катастрофическая, с поиском багов — было полсекунды, стало полторы. Некритично, конечно, но где же оно, заявленное «this greatly improves performance and highly decreases the memory footprint»?.. | Практически это приводит к следующим эффектам. Например, открытие бага с большим числом комментариев (например, с 703-емя — это наш максимум в [http://www.custis.ru/ ЗИС]'е) в Bugzilla 2.x, даже с учётом того, что это CGI-скрипт (то есть перезапускается интерпретатор), занимает примерно 1.5 секунды, а в 3.x, даже с учётом того, что это mod_perl (то есть интерпретатор не перезапускается и модули тоже не перезагружаются — ''хочется быстрее, да?'')… примерно 22 секунды, из которых 6 тратятся на работу {{CPAN|Template::Stash}}. После некоторых манипуляций средней хитрости время с 22 сек было доведено до 5.5 секунд, но это всё равно уже никак не 1.5 секунды 2-ой багзиллы, а для дальнейшего прогресса «локальных» улучшений уже мало — нужно глобально менять, так сказать, «архитектурные элементы». Та же история, хоть и менее катастрофическая, с поиском багов — было полсекунды, стало полторы. Некритично, конечно, но где же оно, заявленное «this greatly improves performance and highly decreases the memory footprint»?.. |
Текущая версия на 19:24, 5 октября 2010
Template::Toolkit (а также TTk или просто TT) — шаблонизатор, Perl-библиотека для отделения уровня представления от уровня приложения. Короче говоря, относительно простой язык программирования, упрощающий генерацию динамических страниц, в основном, хотя и не обязательно — HTML-страниц.
Статья Template Toolkit в русскоязычной Википедии пытается намекнуть нам на то, что по сути своей TT — «легковесная» библиотека, в отличие от «полновесных» фреймворков для построения веб-приложений.
Содержание
«Не используйте Template Toolkit!»
Или же очередная серия «Заметок об идиотизме».
Все проблемы TT происходят от того, что он тьюринг-полный. Реализовать на нём можно ну абсолютно всё, что угодно. Можно писать на TT, не используя нативный Perl-код вообще. «Всех» проблем, по сути, две — высокая средняя сложность кода шаблонов и очень низкая производительность. Идея о простых шаблонах, которые может редактировать даже дизайнер, накрывается медным тазом. Для примера достаточно взглянуть на шаблоны и код Bugzilla.
Забавно, что синтаксис TT, за исключением извращений, логичен, и даже в своём шаблонизаторе я пришёл к похожему синтаксису, в том числе и к передаче переменных единым Perl-хешем. С другой стороны, а что же значит «за исключением извращений»? Раскроем смысл фразы — логично в синтаксисе TT всё, кроме:
- Виртуальных методов: зачем изобретать свой достаточно сложный язык, где «всё — объект», почему не использовать обычные вызовы функций?
- Блоков: «функции» в шаблонах вредны — их полезно выносить в отдельные шаблоны, а если таковых очень много — это, скорее всего, какие-то сообщения, в этом случае их можно объединять в хеш-таблицу и в один файл «локализации», как делает MediaWiki (см. например, русскую локализацию).
- Фильтров: чем фильтр не функция (имеется ввиду внешняя функция), зачем вводить дублирующий функционал?
- Оператора выбора (SWITCH): его нет даже в самом Perl’е и почему-то никто не страдает, зачем же он в шаблонах?
- Макросов: чем макрос не блок? Чем макрос не функция? Часто вы используете C-препроцессор в Perl-коде?
- Встраивания Perl-кода: вредно для простоты шаблонов, неудобно (ибо Stash) и, возможно, опасно.
- Stash'а (от англ. stash — тайник, «заначка»): производительность TT страдает в первую очередь именно от него — зачем-то введённого отдельного уровня хранения данных — по сути, собственной реализации хеш-таблицы со множеством наворотов, проверок и вообще лишних действий. «Зачем-то», по всей видимости — для обеспечения нетривиальных возможностей вроде установки функции-getter’а вместо значение переменной шаблона (нафиг в шаблонах такие хаки?).
- Try/catch: про то, что это очередная возможность усложнить шаблоны, упоминать как-то даже глупо. Исключения, как известно, придуманы не для шаблонов, а для сложных (по уровню вложенности вызовов функций) конфигураций обработки ошибок, которая в шаблонах опять-таки не полезна, а вредна. Можно было бы подумать, что это всё для обработки ошибок приложения, но лично я считаю, что шаблон не должен вызывать функций приложения — его предназначение заключается как раз в изоляции уровня приложения от уровня представления.
- «Обёрток» (wrapper’ов): чем это не фильтр, а чем фильтр не функция?
- TT не восстанавливает значение счётчика после выхода из FOREACH: это поведение не так страшно в языках программирования типа PHP, где всё равно переменная обычно попадает в локальную область видимости функции, однако в шаблонах, особенно в сложных, приводит к различным относительно трудноуловимым багам.
Таким образом, остаётся приблизительно следующий набор: скаляры, хеши и массивы, функции, условный оператор, оператор цикла и включение другого шаблона. То есть, простейшие элементы. Так вот, именно их синтаксис в TT и логичен :-)
Кстати, раз обычный Stash — просто кошмар неиллюзорный, а Template Toolkit был (да и до сих пор) весьма популярен, в районе года 2000 появилась его XS-версия (то есть, переписанная на C) — Template::Stash::XS. После этого TT-шаблонам, всё-таки, полегчало. Но, во-первых, Stash никогда не был единственным замедляющим работу TT уровнем, во-вторых, даже Stash::XS всё равно работает не так быстро, как мог бы, а в-третьих, Stash::XS долгое время имел различные баги — например, поддержка UTF-8 в Template::Stash::XS появилась только 4 июля 2009 года. И ещё Stash::XS менее безопасен — делает меньше проверок на ошибки. Пример раскопок по данной причине: Perl + mod_perl + gdb.
Update от 25.06.2010. Ещё одним TT’шным идиотизмом является способ получения имени текущего шаблона, «в котором находишься». Его можно взять из обычного Stash’а, из специальных переменных с именами (!) «component» и «template». Гениально. Ну хотя бы «_» в начале имени поставили, или хотя бы верхним регистром написали. А так — конфликты обеспечены, та же Bugzilla на них нарывается. И опять-таки самое ужасное в том, что получение текущего шаблона вообще нелогично смешивать с данными шаблона — его логично вообще отделить в функцию, либо «сельф» какой-нибудь завести ($self/this).
Пример: Bugzilla
Bugzilla — популярный и распространённый баг-трекер, «система контроля ошибок», разработанная организацией Mozilla, написанный на Perl’е.
Изначально Bugzilla вообще не была веб-приложением, а работала на основе библиотеки Tk. Однако это было давно и неправда и, как и распространённые версии 2.x, так и версии 3.x используют Template Toolkit.
Bugzilla 2
Bugzilla 2, с точки зрения качества кода, вообще была очень сомнительным поделием, поддерживающим только запуск через CGI и содержащим большое количество старого кода, рассчитанного на то, что недоступны ни транзакции, ни даже библиотека Perl DBI, файлы-вложения хранились в виде BLOB’ов в базе, существовала дурацкая вещь под названием «versioncache» (кэш данных в файле с дампнутым перловым хешем), интерфейс был весьма аскетичен и местами неудобен, и т. д. и т. п. Тем не менее, свои задача Bugzilla 2 выполняла хорошо, и почти никаких проблем с надёжностью не испытывала. «Почти» — то есть, кроме необходимости иногда пробивать versioncache при некоторых изменениях и время от времени падающих MyISAM-таблиц в MySQL. Для генерации страниц Bugzilla 2 использует Template Toolkit и, несмотря на CGI, показывает приличную производительность (нет, конечно же не веб-2.0, но для баг-трекера вполне) благодаря относительной простоте кода и, что важно, шаблонов.
Bugzilla 3…
Bugzilla 3 же показала большой прогресс в области удаления старого кода и написания нового. Появились транзакции, DBI, mod_perl, поддержка UTF-8, более приятный интерфейс, защита от дублирования изменений, для почты стали наконец-то использоваться нормальные (ну, такие же) шаблоны для писем (это с большой оговоркой: если Template Toolkit вообще считать нормальным). Также авторы начали понимать, что Bugzillе совершенно необходимы возможности расширения и постепенно добавляют таковые — в 3.0 появляются Custom Fields (дополнительные поля у багов) и чуть-чуть хуков, в 3.2 настраиваемый жизненный цикл бага, поддержка расширений авторизации и ещё чуть-чуть хуков, в 3.4 Custom Fields стали умнее и т. п.
По поводу ума Custom Fields в 3.4, правда, тоже не обошлось без глупости — возможность ограничить значение поля значением какого-то другого поля появилась, то есть, например, сделать, чтобы у разных продуктов были разные значения вашего поля, теперь можно. А сделать, чтобы некоторое значение показывалось только в двух продуктах, но не в остальных — нельзя…
Но есть и беда: Bugzilla всё ещё использует Template Toolkit, а разрабатывается длительное время и большим числом людей разной степени вшивости :) в итоге люди начинают использовать ну, прямо-таки все возможности Template Toolkit’а, что ведёт к уже упомянутому анти-паттерну разработки: перемещению 50 % логики в шаблоны.
…И её тормоза
Практически это приводит к следующим эффектам. Например, открытие бага с большим числом комментариев (например, с 703-емя — это наш максимум в ЗИС'е) в Bugzilla 2.x, даже с учётом того, что это CGI-скрипт (то есть перезапускается интерпретатор), занимает примерно 1.5 секунды, а в 3.x, даже с учётом того, что это mod_perl (то есть интерпретатор не перезапускается и модули тоже не перезагружаются — хочется быстрее, да?)… примерно 22 секунды, из которых 6 тратятся на работу Template::Stash. После некоторых манипуляций средней хитрости время с 22 сек было доведено до 5.5 секунд, но это всё равно уже никак не 1.5 секунды 2-ой багзиллы, а для дальнейшего прогресса «локальных» улучшений уже мало — нужно глобально менять, так сказать, «архитектурные элементы». Та же история, хоть и менее катастрофическая, с поиском багов — было полсекунды, стало полторы. Некритично, конечно, но где же оно, заявленное «this greatly improves performance and highly decreases the memory footprint»?..
А в процессе манипуляций был выявлен замечательный идиотизм, который надо, во-первых, увековечить, а во-вторых, и зафиксить тоже не помешает. Код багзиллы, к счастью, доступен online на Mozilla Cross-Referencer. Мы будем рассматривать ветку Bugzilla 3.4 (вдруг потом баг всё-таки пофиксят). Но вообще он присутствует и в 3.2, и в 3.0.
Итак. В коде Bugzilla::Template есть такая замечательная функция, как quoteUrls, отвечающая за преобразование слов «bug XXXX» и «comment XX» в комментариях в ссылки на баги и оборачивание обычных ссылок и e-mail адресов в гипертекстовые ссылки. А в ней есть следующая строчка:
161 sub quoteUrls { … 247 my $bug_word = get_text('term', { term => 'bug' });
Строчка относится к получению настоящего названия термина «баг» — в Bugzilla 3 можно баги называть не багами, а «задачами», «проблемами», «требованиями» и вообще как душе угодно. Функция quoteUrls при выводе страницы по разу вызывается для каждого комментария. А что делает get_text? Обратимся к модулю Bugzilla::Util:
597 sub get_text { 598 my ($name, $vars) = @_; 599 my $template = Bugzilla->template_inner; 600 $vars ||= {}; 601 $vars->{'message'} = $name; 602 my $message; 603 $template->process('global/message.txt.tmpl', $vars, \$message) 604 || ThrowTemplateError($template->error()); 605 # Remove the indenting that exists in messages.html.tmpl. 606 $message =~ s/^ //gm; 607 return $message; 608 }
А теперь заглянем в шаблон global/message.txt.tmpl — он почти ничего не делает, но включает в себя другой шаблон: global/messages.html.tmpl, отвечающий за вывод огромного числа различных информационных сообщений (между прочим, цепочкой ELSIF’ов вместо кучи блоков и именного PROCESS), и в частности — отдельным ELSIF’ом — вывод термина [% terms.$term %]
, ради которого, собственно, и вызывается get_text('term', { term => 'bug' })
. Иными словами, это 33 килобайта, выполняющиеся заново при обработке каждого коммента к багу. В нашем случае — каждого из 703… На это уходило почти 10 секунд из всех 20-и, то есть почти 50 % времени.
Порассуждаем:
- Во-первых, функция get_text с первым аргументом 'term' вызывается в коде Bugzilla только в одном месте (см.выше), а все term’ы содержатся в отдельном шаблоне global/variables.none.tmpl. Поэтому получение термина можно вынести в отдельную функцию.
- Во-вторых, нет никакой нужды выбирать нужную функцию в global/messages.html.tmpl цепочкой ELSIF’ов — можно применить блоки Template Toolkit’а, или даже просто отдельные шаблончики. Хотя самое забавное, что время-то как раз съедали не ELSIF’ы, а постоянные дёрганья global/field-descs.none.tmpl, представляющего собой по сути всего лишь пару вызовов Template::Stash::set(), это очередная загадка TT.
- В-третьих, что самое главное — нет никакой нужды фильтровать тексты комментариев заново при каждом выводе страницы! Зачем выполнять лишнюю работу? Фильтрованный HTML-текст комментариев можно просто сохранить в базу данных рядом с нефильтрованным и спокойно выводить вообще без преобразования.
Вторая глобальная манипуляция — это именно разрешение Template::Stash::XS, зачем-то специально запрещённого в коде Bugzilla 3.x. Наверное, это имело под собой какие-то основания, но когда баг открывается 20 секунд, все основания идут в задницу, и это позволило сэкономить ещё где-то 5 секунд.
Другие манипуляции были более мелкие — отказ от Text::Wrap и оптимизация регулярных выражений в quoteUrls — и дали соответственный выигрыш: 1.5 секунды.
Данные профилировщика
Чтобы не быть голословным, я даже приведу данные профилировщика. Использовался Devel::NYTProf, написанный ребятами из New York Times (кстати, это очень хороший Perl-профилировщик, достойный отдельной статьи). Профилировалась Bugzilla не под mod_perl’ом, а под HTTP::Server::Simple — проблем там никаких не возникает, потому что он тоже CGI (см. Платформы для запуска Perl веб-приложений). Всё тестировалось на «супер-баге» с 703 комментами.
- Bugzilla 2.x (2.19.3).
- Bugzilla 3.x изначально (3.4.1).
- Bugzilla 3.x + Template::Stash::XS.
- Bugzilla 3.x + оптимизация get_text('term').
- Bugzilla 3.x + отказ от Text::Wrap.
- Bugzilla 3.x + оптимизация регэкспов в quoteUrls.
- Неулучшенное — поиск багов в Bugzilla 3.x.
А ещё отсюда видно, что даже после оптимизации больше всего времени всё равно отъедает TT, и что осуществляется 61282 вызова Template::Stash::XS::get(), съедающих почти секунду времени. Такое количество какбэ намекает на возросшую (во многом бесполезно) сложность шаблонов Bugzilla.
Так-то.
Ссылки
- VMX-Template — Описание собственного (моего) шаблонизатора.
- http://ctpp.havoc.ru/template_benchmarks.html - Сравнение производительности Perl-шаблонизаторов авторами CTPP. В тесте «всех порвал», разумеется, именно CTPP.