Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу).
Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Внимание! Прочитайте, пожалуйста, текст в правой колонке (внизу). Homepage Карта сайта Версия для печати

Джентльменский набор Web-разработчика   Ларри Уолл о Perl6   Наблы Система Orphus
 

16. Код и шаблон страницы, или понемногу о многом

[27 февраля 2002 г.]

В прошлом тысячелетии в Интернете было написано особенно много ругательных статей по поводу фразы «отделение представления от содержания». Не буду пересказывать все эти статьи (вы можете найти их сами, хотя бы при помощи незабвенного Яндекса, введя в качестве запроса нечто вроде «отделение представления от содержания неправильно бессмысленно»). Лично я полностью согласен с аргументами противников, вроде «если в документ вставлена картинка, генерируемая "на лету", то ее представление будет невозможно отделить от содержания документа, то есть смысла». Потому-то и предлагаю пользоваться гораздо более мягкой формулировкой — «отделение кода программы от шаблона страницы». Настоящая набла — об этом.

Лирическое отступление 
Мягкость формулировки, как обычно и бывает в таких случаях, следует из ее неопределенности: под «кодом» и «шаблоном» можно понимать все, что угодно. Далее эти термины будут уточнены.

Можно сказать уйму слов про код и шаблон страницы, однако эта набла имеет одно неприятное свойство: она ограничена в размере и, значит, не может разрастаться до 200 печатных страниц. Так что моя цель — кратко показать, где же тут собака зарыта, а там уж додумывайте сами.

Лирическое отступление 
Часто говорят, что достоинства и недостатки всегда компенсируются (читайте — если что-то улучшили, то всегда за счет ухудшения чего-то другого). Вера в этот принцип, воистину, сходна с верой в бога. Так что в дальшейшем я буду придерживаться обратной точки зрения, а именно: все-таки существуют несовершенные вещи и идеи, которые могут быть улучшены без потерь. Это я так, чтобы в дальнейшем не возвращаться.

Всего можно выделить четыре способа (вообще-то, даже пять) написания Web-скриптов, что, кстати, сильно отличает последние от обычных GUI-приложений. Давайте рассмотрим эти способы (не все из них приемлемы, кстати). Но вначале предположим для определенности, что результат работы скрипта пользователь видит, введя в браузере адрес http://somehost.com/news.html.

Чайник 

Расширение html, вообще говоря, не типично для «стандартных» скриптов, однако я предпочитаю его использовать, чтобы лишний раз не смущать пользователя.

Способ первый. Кодовая фраза: «лепи все в print».

Скрипты этого вида выглядят примерно так (на Perl):

#!/usr/bin/perl -w
use CGI::WebOut;
print "<HTML><BODY>\n";
print "<h2>Последние новости:</h2>";
open(local *F, "news.txt");
for(my $i=1; !eof(F) && $i<=5; $i++) {
    print "<li>$i-я новость: ".<F>;
}
print "</BODY></HTML>\n";

Чайник 

Не надо быть президентом компании Sun, чтобы заметить: сервлеты имеют точно такой же недостаток, как и приведенная выше «CGI-программа». Это не умаляет имеющихся у сервлетов достоинств, однако и не «возвышает» их, как это почему-то принято делать в обстановке энтузиастов.

Достоинства: а их нет вообще. См. дальше.

Недостатки: когда придет дизайнер в обнимку с HTML-верстальщиком (оба в Perl ни бум-бум), они смогут лишь принять на себя роль мучеников.

Лирическое отступление 
Не надо только говорить: «не умеет — научим, не хочет — заставим». Эта схема насильственная, а потому не проходит с приемлемым качеством практически никогда.

См. также: вторую, третью и четвертую наблы.

Способ второй. Кодовая фраза: «вставлять — так вставлять».

Большинство языков, предназначенных хоть чуть-чуть для Web-программирования, позволяют вставлять код программы в HTML-документ, а не наоборот. Тем не менее, иногда этим сильно злоупотребляют. Вот все тот же пример, на этот раз на PHP (для демонстрационных целей он несколько проще):

<HTML><BODY>
<h2>Последние новости:</h2>
<?
$f=fopen("news.txt","r");
for(my $i=1; !eof($f) && $i<=5; $i++) {
    print "<li>$i-я новость: ".fgets($f,1000);
}
?>
</BODY></HTML>

Достоинства: большинство print ушли.

Недостатки: они ушли недалеко, судя по тому, что дизайнер с верстальщиком все еще стонут. Но их оханье уже не напоминает пожарную сирену и даже не слышно в соседнем офисе.

См. также: восьмую наблу, чтобы посмотреть, как нечто подобное реализуется в Perl.

До сих пор я говорил о способах, непригодных для нормальной работы (тут, по чьему-то образному выражению, я уворачиваюсь от тухлого помидора). Теперь мы подходим к методам, которые вполне работоспособны.

Лирическое отступление 
И это несмотря на то, что существует множество скриптов, написанных «плохим» стилем — хорошо еще, что они не на ассемблере. А взять книгу по сервлетам, так и вообще с ума сойдешь от обилия примеров, большинство которых — копия «второго способа», но на JSP.

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

Чайник 

Слово «мало» тут надо понимать в смысле «как можно меньше», потому что совсем без кода (или его заменителя) в шаблоне обойтись будет тяжело. Дальше я поясню этот момент подробнее. Сейчас же важно запомнить: в файле с кодом программы вообще нет никакого HTML-оформления, а в шаблоне — лишь минимум кода.

Способ третий. Кодовая фраза: «я программист, и это звучит гордо».

Если вы помните, пользователь за последними новостями обращается к URI /news.html.

Чайник 

Под URI я здесь и далее понимаю часть URL, следующую после имени хоста и, возможно, номера порта.

В соответствии с кодовой фразой программист гордо помещает на место /news.html следующий скрипт (например, на PHP):

$f=fopen("news.txt","r");
for($News=array(); !feof($f);) {
    $News[]=trim(fgets($f,10000));
}
include "$DOCUMENT_ROOT/_templates/news.html";

Этот скрипт считывает из файла с новостями все данные и помещает их в массив $News. Заметьте также, что из новостей удаляются начальные пробелы и конечные переводы строк: это соответствует концепции независимости результатов работы программы от оформления шаблона. В конце управление передается шаблону, расположенному в какой-нибудь недоступной для браузера директории, — /_templates/news.html:

<HTML><BODY>
<h2>Последние новости:</h2>
<?for($i=0; $i<5; $i++) {?>
    <li><?=$i?>-я новость:
        <?=$News[$i]?>
<?}?>
</BODY></HTML>

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

Лирическое отступление 
Судя по разглагольствованиям в недрах http://www.sun.com, они («люди на Солнце») называют это «моделью 2», но только по отношению к сервлетам. Они также утверждают, что такой способ удобен для работы. Может быть, товарищи слишком много писали под Windows (или под что-то еще) GUI-шных приложений, которые действительно всегда устроены именно таким образом. Но Web-программирование — это все-таки Web-программирование.

Достоинства: навсегда исчезли абсолютно все вызовы print. Их заменил PHP-шный оператор <?=...?>, выводящий в контексте кода значение выражения в скобках. Шаблон страницы наконец-то стал более-менее понятен дизайнеру.

Лирическое отступление 
Положа руку на сердце, было бы гораздо удобнее вместо длинного <?=$i?> написать просто $i. Однако в PHP так сделать почему-то нельзя. Как Perl помогает решить эту проблему, рассказано в восьмой набле.

Недостатки: дизайнер, правящий файл шаблона /_templates/news.html, захотел вставить в него картинку и положил GIF-файл рядом — в директории /_templates. Затем он вставил в HTML нечто вроде <img src=image.gif> и с удивлением обнаружил, что на месте картинки ему показывается шиш с крестиком. Ну правильно — ведь в момент работы шаблона браузер пользователя находится в директории /, то есть, там же, где и «ведущий» скрипт. Но откуда дизайнеру знать, где в недрах дерева сайта затерялась программа? И куда же ему, наконец, положить картинку?..

Чайник 

Использование абсолютных путей типа /img/image.gif не предлагать — пример утопичен по своей простоте. Кроме того, не всегда хочется захламлять директорию картинок изображениями, которые не будут использоваться нигде, кроме как в определенном разделе сайта.

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

См. также: некуда уже смотреть, надо просто все взвесить.

Есть также и разновидность «третьего способа», которая позволяет (как будто бы) вообще обходиться без кода в шаблонах. Ее любители (обычно фанатики MySQL, хранящие в базе данных все, что только можно: картинки, файлы, деньги, драгоценности...) часто создают огромное количество файлов, каждый содержащий малый кусочек всего шаблона. Например, тело цикла, шапку, концовку и т. д. записывают в разные файлы. Затем эти «кусочки» радостно подгружаются в коде программы и — тяп-ляп, «глядите, — работает!». Такой метод имеет еще больше недостатков, чем при использовании «чистого третьего способа», поскольку распыляет задачу создания окончательной HTML-страницы для браузера между шаблонами и кодом программы. А значит, он недалеко ушел от способа номер два.

Четвертый способ. Кодовая фраза: «разделяй и властвуй».

Как вы думаете, кто из команды разработчиков менее сообразителен в технических вопросах (вроде выбора места расположения для картинки, когда шаблон вызывается неизвестно кем): программист или дизайнер/верстальщик?.. Конечно же, дизайнер. А потому программист должен стараться всячески упрощать ему жизнь (особенно если это одновременно упрощает жизнь и самому программисту, см. одно из лирических отступлений в начале наблы). А значит, раз хочет дизайнер поместить картинку в ту же директорию, что и шаблон (или даже скопировать шаблон страницы под другим именем в надежде, что он заработает) — быть по сему.

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

Лирическое отступление 
Корпорация Sun называет этот трюк «моделью 1» и утверждает, что настоящие профессионалы не боятся трудностей. Они забыли, наверное, что мудрый человек просто не попадает в такие ситуации, из которых умный с блеском выкручивается.

Итак, поместим шаблон на место /news.html (да-да, вы не ослышались — именно шаблон!), а скрипт — в недоступную извне директорию. И будем включать скрипт из шаблона, а не наоборот. Вот так:

<?include "$DOCUMENT_ROOT/_kernel/news.php"?>
<HTML><BODY>
<h2>Последние новости:</h2>
<?for($i=0; $i<5; $i++) {?>
    <li><?=$i?>-я новость:
        <?=$News[$i]?>
<?}?>
</BODY></HTML>

Итак, еще раз: теперь шаблон первичен, а скрипт — вторичен. Код же програмы будет храниться в /_kernel/news.php:

$f=fopen("news.txt","r");
for($News=array(); !feof($f);) {
    $News[]=trim(fgets($f,10000));
}

Лирическое отступление 
Кстати, к вопросу о первичности. Вот представьте, что вы сидите перед монитором и пытаетесь попасть мышью в полосу прокрутки на окне. С чем вы тогда общаетесь: с окном и линейкой прокрутки, или же с той программой, которая обрабатывает движения курсора (на ассемблере), перерисовывает линейку прокрутки (на Си) и подкачивает данные с диска (опять на ассемблере)?.. Иными словами, вы ведь «разговариваете» именно с интерфейсом объекта, а не с его «внутренностями», не так ли?.. Так почему же идея использовать шаблон в качестве первичного элемента действует на некоторых так ошарашивающе?..

Давайте сразу же оговорим единственный недостаток этой модели. Он невелик: всего один лишний вызов include (напоминаю, что шаблонизатор способен устранить и это, но, чтобы раззадорить ваш интерес, я откладываю подробное рассмотрение этого вопроса до одной из следующих набл).

Теперь о достоинствах. Одно из них мы уже рассмотрели: дерево сайта с точки зрения дизайнера (и подвластных ему шаблонов) начинает выглядеть точно так же, как и с точки зрения пользователя. Не забывайте, что дизайнер — в большой степени пользователь со всеми вытекающими из этого последствиями. Да, и теперь можно не задумываться о расположении картинок: допускается хранить их в той же директории (или в поддиректории), где расположен шаблон.

Второе достоинство вытекает из первого: так как шаблон теперь полностью автономен (он лишь обращается к коду программы, чтобы тот выдал ему данные), мы можем его (шаблон) размножать и копировать в любую директорию на сайте: он заработает откуда угодно. Это похоже на идеологию Drag&Drop.

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

Третий плюс, пожалуй, самый впечатляющий: теперь любой шаблон может быть построен на компонентной основе. Поясню, что это означает. Предположим, ваш шаблон страницы новостей модернизируется, и теперь вам нужно добавить в его конец вывод, например, погоды на завтра. Погода — это некоторый текст (строка), которая формируется в отдельном генераторе данных (к примеру, /_kernel/weather.php). Чтобы было интереснее, предположим также, что этот генератор данных был написан давным-давно каким-нибудь сторонним программистом, который и слыхом не слыхивал ни о вашем шаблоне, ни тем более о блоке новостей на странице.

Посмотрите, как просто и изящно решается поставленная задача с использованием «четвертого способа»:

<HTML><BODY>

<!-- Блок новостей -->
<?include "$DOCUMENT_ROOT/_kernel/news.php"?>
<h2>Последние новости:</h2>
<?for($i=0; $i<5; $i++) {?>
    <li><?=$i?>-я новость:
        <?=$News[$i]?>
<?}?>
<hr>

<!-- Блок погоды -->
<?include "$DOCUMENT_ROOT/_kernel/weather.php"?>
По сообщениям синоптиков, дождь, объявленный 
на сегодня, переносится на завтра на те же часы. 
Также завтра ожидается: <?=$Weather?>

</BODY></HTML>

В роли компонент выступают генераторы данных. Любая компонентная модель характеризуется следующими свойствами (выдумываю на ходу):

  1. Компоненты независимы.
  2. Компоненты повторно используемы, причем как по отдельности, так и в наборе.
  3. Каждая компонента в идеале «не знает», кто ее использует.
  4. Легко добавлять собственные компоненты в систему.

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

Чайник 

Если бы корпорация Sun продавала на базаре помидоры, как бы она поступила: раздала помидоры прохожим, чтобы те самостоятельно навязывали часть плодов выбранным ими же покупателям, или же выложила бы все помидоры в одном лотке и выдавала бы их клиентам в затребованном количестве?.. Первый вариант кажется совершенно абсурдным! Между тем, именно так выглядит «способ третий», или «модель 2» — называйте, как хотите, — если вспомнить, что он исключает возможность использования компонентной модели (где компоненты — это помидоры). Ну да ладно, пример, может быть, и не очень удачный, но выбор все равно за «программистами с Солнца».

Наконец, последняя точка над «i». Возможно, кто-то возразит, что компонентная модель делает невозможной экстренную передачу управления другой странице на сервере — например, переадресацию на страницу авторизации, если пользователь зашел в запретную зону. Но дело все в том, что генераторы данных устроены с учетом двух моментов. Во-первых, они могут напрямую работать со всеми данными, пришедшими из формы, а значит, выполнять активную работу — к примеру, добавлять сообщение в гостевую книгу. И во-вторых, в любой момент им разрешено сделать эту самую злосчастную переадресацию на тот адрес, который им заблагорассудится. При этом работа страницы завершается, что, в общем-то, и требовалось.

Пятый способ. Пароль: «шаблонизатор».

Если помните, нам так и не удалось избавиться от одного, казалось бы, неустранимого недостатка в шаблонах. Я о малом, но все же использовании кода на языке программирования, недоступном дизайнеру. Есть ли выход?..

Конечно. Идея опять же проста: «если муж не идет к жене, то жена идет к Магомету». В смысле, если дизайнер не понимает языка программирования, переделаем этот язык так, чтобы он стал понятным. Что, звучит невероятно сложно?.. В общем-то, это не очень далеко от истины и называется Templating System, или шаблонизатор (так, прошу отнестись с пониманием — по-моему, звучит забавно, но не как ругательство).

Пока неясно, что точно должно входить в концепцию идеального шаблонизатора, но конец, похоже, уже виден, потому что проблемой наконец-то заинтересовались. Знаете, бывают такие задачи, про которые вы можете сказать: «предчувствую, она имеет точное решение». Это как у математиков: доказать существование решения — на 90% решить задачу.

Про шаблонизатор я обязательно буду еще писать в следующих наблах, а пока хочу лишь донести отдельные моменты, существенные в данной статье:

  • Шаблонизатор позволяет отказаться от программирования как такового в шаблонах, сохраняя в то же время все преимущества, присущие «четвертому способу». А значит, он являет собой новый (пусть и небольшой) HTML-подобный язык, по определению доступный дизайнеру (так уж он задумывается).
  • Система управления шаблонами должна полностью поддерживать идеологию наследования данных (например, когда страница, расположенная в некотором разделе сайта, может «свободно для дизайнера» обращаться к данным, характерным для этого раздела). В частности, это означает поддержку идеологии ЧПУ («Человекопонятный URL»), или «хлебных крошек», как ее называют.
  • Многоуровневая система кэширования. В частности, отслеживание изменения страницы, если меняются ее составные части (чего так не хватает в сервлетах и JSP нашей любимой Sun). С помощью кэширования можно и нужно добиться одного важного момента: шаблонизатор в среднем должен быть даже быстрее, чем «традиционный» CGI-скрипт.
  • Система работы с определяемыми пользователем тэгами (опять же, доступная дизайнеру). Примером оглушительной неудачи в этом вопросе (на мой взгляд) можно считать пользовательские тэги в JSP 1.2.

Замечу снова, что некоторые из этих вопросов (особенно о тэгах) до сих пор относятся к крайне мало разработанным. Поэтому не спрашивайте подробностей — их просто пока нет, есть лишь работающие прототипы, не полностью удовлетворяющие поставленным требованиям.

Под конец приведу пример шаблона страницы, запускаемой на шаблонизаторе, который может уже быть отнесен к почти идеальному варианту. Чтобы далеко не ходить за примерами, возьмем опять нашу страницу новостей с информацией о погоде в нижней части. Вот она:

<HTML><BODY>

<!-- Блок новостей -->
<h2>Последние новости:</h2>
<DATASRC src="News">
<FOREACH src=News>
    <li>$i-я новость: $news
</FOREACH>
<hr>

<!-- Блок погоды -->
<DATASRC src="Weather">
По сообщениям синоптиков, дождь, объявленный 
на сегодня, переносится на завтра на те же часы. 
Также завтра ожидается: $weather

</BODY></HTML>

Хотя изменения в этом коде кажутся смехотворными для программиста (ха-ха, просто позаменяли for на тэг <FOREACH>, ха-ха, сейчас умру), на самом деле они весьма существенны. Вы видите, что тут действительно использован новый язык «программирования», состоящий из тэгов циклического повторения, условного оператора и т. д. И, соответственно, шаблоны, выглядящие таким образом, претендуют на стиль, приближенный к идеальному. Во всяком случае, мне так кажется.

Ответы на вопросы

А что, этот сайт (http://www.dklab.ru) построен на «настоящем» шаблонизаторе?..
Да. Вернее, на его прототипе.

Фирма Sun — авторитет. Она, наверное, не будет давать плохих советов.
Наверное. Но возьмем тогда Microsoft, который будет покрупнее Sun. Вы видели, что из себя представляет BAT-язык (язык командного интерпретатора) Windows и DOS?.. И после этого вы все еще считаете, что крупные компании не могут предлагать решения «на коленке»?.. Почему бы и нет?..

В примерах имеются недостатки. Например, генератор данных новостной системы берет новости из текстового файла, расположенного непонятно где. А если у нас две новостных системы?.. Им же надо использовать разные файлы...
Да. Но это всего лишь примеры. Конечно, реальный шаблонизатор в тэге <DATASRС> должен заботиться обо всех подобных мелочах — например, позволять передавать генератору данных некоторые параметры. Поэтому способ подключения генератора данных при помощи PHP-шного include так просто не проходит.

Почему столько опечаток?..
Для исправлений есть система Orphus, которая видна в верхней части страницы. Как показывает практика, автор практически никогда не может исправить все опечатки в своем тексте. Нужен кто-то со стороны, кем и являются читатели: если вы заметили опечатку, вам достаточно просто выделить ее мышью и нажать Ctrl+Enter. Все остальное я сделаю сам.

 
Рекламный блок
   

На странице:
    16. Код и шаблон страницы, или понемногу о многом
Способ первый. Кодовая фраза: «лепи все в print».
Способ второй. Кодовая фраза: «вставлять — так вставлять».
Способ третий. Кодовая фраза: «я программист, и это звучит гордо».
Четвертый способ. Кодовая фраза: «разделяй и властвуй».
Пятый способ. Пароль: «шаблонизатор».
Ответы на вопросы

Важное объявление:
    автор категорически против копирования и распространения в Интернете всех статей «Куроводства» с возрастом, меньшим 6 месяцев. Печальный опыт «расползания» чрезвычайно устаревших ошибочных версий статьи про Apache действительно объясняет такое решение.

Орфография на «Куроводстве»:
    если вы заметили орфографическую, стилистическую или другую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter. Выделенный текст будет немедленно отослан вебмастеру, а Вы даже ничего и не заметите — настолько быстро все произойдет.

На заметку:
    если вы уже вскипели насчет дизайна этой страницы, то присмотритесь повнимательнее к названию, почитайте FAQ, сходите по лебедевским местам, как это уже предлагалось выше. Можно ли считать пародию плагиатом? Надеюсь, что нет.

Параметры этой страницы
   
GZip

Ссылки от спонсоров
    http://www.dom-climata.ru/catalog/products/hyndai_kaseta/


Дмитрий Котеров | 27 февраля 2002 г. ©1999-2016 | Генеральный спонсор: Хостинг «Джино» | Контакт Вернуться к оглавлению