StringTemplate


Продолжается подписка на наши издания! Вы не забыли подписаться?

StringTemplate

Автор: Terence Parr
Университет Сан Франциско,
Опубликовано: 21.02.2006

Введение

Большинство программ, производящих генерацию кода или текста, являются неструктурированными кусками логики генерации, перемешанными с выражениями print. Главная причина этого – отсутствие подходящих средств и формализмов. Правильный формализм – формализм грамматики вывода, потому что генерируются не случайные символы, а предложения на языке вывода. Это аналогично использованию грамматики для описания структуры исходных предложений. Вместо создания парсера вручную большинство программистов будет использовать генератор парсеров. Подобным образом, для генерации текста нужен некий «unparser». Наиболее удобное представление грамматики вывода – это движок шаблонов, такой, как StringTemplate.

Движок шаблонов – это просто кодогенератор, создающий текст по шаблонам, которые в действительности являются просто «документами с пустотами», в которые можно вставить значения. StringTemplate разбивает шаблон на куски текста и выражения-атрибуты, которые по умолчанию заключены в знаки $ (чтобы их было легче заметить в HTML-файле). StringTemplate игнорирует все, кроме этих выражений, расценивая остальное как текст, который нужно выдать при вызове StringTemplate.toString(). Например, в следующем шаблоне есть две части, литерал и ссылка на атрибут name:

Hello, $name$

Использовать шаблоны в Java-коде очень просто. Следующий пример выведет "Hello, World":

StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());

StringTemplate – это не «система», не «движок» и не «сервер»; это библиотека, в которой наиболее интересны два класса: StringTemplate и StringTemplateGroup. Вы можете создать StringTemplate напрямую в Java-коде, можете загрузить шаблон из файла, или загрузить файл с несколькими шаблонами (файл группы шаблонов).

Мотивация и философия

StringTemplate родилась и выросла в процессе разработки http://www.jGuru.com. Потребность в динамическом создании Web-страниц привела к созданию многочисленных шаблонных движков, пытающихся упростить разработку Web-приложений, увеличить гибкость, снизить стоимость поддержки и дать возможность параллельной разработки кода и HTML. Эти соблазнительные достоинства, вызвавшие распространение шаблонных движков, полностью вытекают из одного принципа: отделения спецификации бизнес-логики страницы и данных от спецификации представления информации на странице. Эти шаблонные движки, в сущности, являются реакцией на полнейшую путаницу в спецификациях, порождаемую JSP. Благодаря раздельным инкапсулированным спецификациям шаблонные движки способствуют многократному использованию компонентов, сменных интерфейсов сайтов, единых точек замены общих компонентов и повышению общей ясности системы. В сфере кодогенерации разделение вида и представления гарантирует возможность перенастройки.

При разработке StringTemplate я вспомнил книгу Брука «Мифический человеко-месяц», где он идентифицировал концептуальную целостность (conceptual integrity) как ключевой ингредиент системы. Например, в Unix все является потоком. Моя концепция, если позволите, полное разделение модели и представления:

StringTemplate должна быть настолько проста, последовательна и мощна, насколько это возможно без ущерба для полного разделения модели и представления.

Я беспощадно сравниваю с этим стандартом все потенциальные возможности и функции. Годы спустя, однако, я пошел на некоторые уступки в угоду практичности, которые можно счесть легким нарушением принципов разделения модели и представления. StringTemplate по-прежнему обеспечивает разделение, в то же время предоставляя отличную функциональность.

Мой выбор возможностей языка и инструментов диктуется моими потребностями. Инструменты эволюционируют вместе с моими потребностями. Я почти не занимался возможностями типа «backtracking». Далее, я действительно много работал, чтобы сделать этот маленький язык самодостаточным и согласующимся с синтаксисом и метафорами других языков. Существует очень немного особых случаев, и правила рассмотрения атрибутов/шаблонов имеют смысл даже если они незнакомы или кажутся странными на первый взгляд. В языке все служит для решения реальных задач.

После рассмотрения сотен файлов шаблонов, созданных мной за годы при разработке jGuru.com (а сейчас – ANTLR v3), я обнаружил, что мне нужны только четыре основные операции (с некоторыми вариациями):

Теория языков поддерживает мое предположение, что даже минимальный движок StringTemplate, только с этими возможностями, является очень мощным – такой движок способен генерировать контекстно-свободные языки (см. Enforcing Strict Model-View Separation in Template Engines, http://www.cs.usfca.edu/~parrt/papers/ mvc.templates.pdf); например, большинство языков программирования обладают контекстно-свободной грамматикой, равно как и любые XML-страницы, чья форма может быть выражена с помощью DTD.

Обычные для императивного языка программирования возможности типа задания переменных, циклов, арифметических выражений, произвольные вызовы методов не только не нужны, они – именно то, что неправильно в JSP. Вспомним, что JSP разрешает произвольные Java-выражения и операторы, позволяя программистам вставлять в шаблоны вычисления и логику. Просмотр шаблонных движков открывает неприглядную правду: все, кроме некоторых – это законченные по Тьюрингу языки, как и JSP. Кто-то может возразить, что они хуже, чем JSP, так как используют языки, специфичные для этих шаблонных движков. Многие создатели инструментов, очевидно, потеряли суть исходно решаемой проблемы. Мы, программисты, часто попадаемся на красивые реализации, но фокусироваться надо на том, что нужно, а не на том, что можно создать.

Тот факт, что StringTemplate не позволяет делать такие вещи, как присваивание (то есть отсутствуют побочные эффекты), должен заставить вас с подозрением отнестись к движкам, позволяющим такие вещи. Я гарантирую, что шаблоны в кодогенераторе ANTLR v3 куда сложнее, чем, например, любая Web-страница, созданная для использования с другими шаблонными движками, и я пока не нашел ситуации, где бы мне потребовалось присваивание. Если ваши шаблоны выглядят как программы – у вас, скорее всего, полностью перемешаны модель и представление.

Предоставляя различные опасные возможности типа присваивания, способствующие использованию в шаблонах вычислений и логики, многие движки упускают ключевые элементы. Для порождающего программирования и трансляции языков совершенно необходима определенная семантика языка. То есть рекурсия. Шаблонный движок без рекурсии скорее всего не сможет генерировать рекурсивные структуры, например, вложенные таблицы или вложенные блоки кода.

Еще одна возможность языка StringTemplate, отсутствующая в других движках – отложенные, или «ленивые» вычисления (lazy-evaluation). Атрибуты в StringTemplate вычисляются «лениво» в том смысле, что ссылка на атрибут «а» на самом деле не запускает механизм просмотра данных до тех пор, пока от шаблона не потребуется выдать текст. Отложенные вычисления удивительно полезны при генерации как кода, так и Web-страниц, поскольку такое разделение команд позволяет коду задавать атрибуты, когда это удобно или эффективно, и не обязательно перед созданием шаблона, ссылающегося на эти атрибуты. Например, сложная Web-страница может состоять из множества вложенных шаблонов, многие из которых ссылаются на $userName$, но значение userName может не задаваться моделью до момента, когда вся страница должна быть выведена в текст через toString(). Можно создать сложную страницу, задавая значения атрибутов в любом удобном порядке.

StringTemplate реализует форму отложенных вычислений «для бедных», просто требуя, чтобы все атрибуты обрабатывались априори. То есть все атрибуты должны быть обработаны и вставлены в шаблону до вывода текста; это так называемый «метод вставки»(push method), большинство же шаблонов использует «метод вытягивания (pull method)». Метод вытягивания выглядит привычнее, так как программисты ошибочно рассматривают шаблоны как программы, но «вытягивание» атрибутов влечет за собой зависимость от порядка обработки. Представьте простую Web-страницу, которая выводит список имен (используя нотацию некоторого мифического шаблонного движка):

<html>
<body>
<ol>
$foreach n in names$
  <li>$n$</li>
$end$
</ol>
There are $numberNames$ names.
</body>
</html>

При использовании «метод вытягивания» ссылка на names вызывает model.getNames(), предположительно загружающий список имен из БД. Ссылка на numberNames вызывает model.getNumberNames(), который неизбежно использует внутреннюю структуру данных, рассчитанную getNames(), для вычисления names.size() и т.п. Теперь представьте, что дизайнер передвинул ссылку numberNames в тег <title>, до ссылки на names в конструкции foreach. Имена еще не загружены, что в худшем случае породит null pointer-исключение, а в лучшем – пустой заголовок. Эти зависимости придется учитывать, и заставить getNumberNames() вызывать getNames() – из-за изменений в шаблоне. Меня поражает, что другие авторы шаблонных движков, с которыми я общался, считают, что это нормально. Каждый раз, когда удается заставить компьютер автоматически сделать за меня что-то, исключающее целый класс ошибок программирования, я это покупаю! Очевидная аналогия здесь – автоматическая сборка мусора.

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

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

........................
"С полным содержанием данной статьи можно ознакомиться в печатной версии журнала"

Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.

Copyright © 1994-2016 ООО "К-Пресс"