четверг, 22 сентября 2011 г.

Генерация Html в стиле Clojure

Недавно прочел очень интересный срач спор о том, как вообще true-программеры генерят html. Я даже не ожидал, что могу не знать такого интересного способа, хотя он лежит буквально на виду.
Основные тезисы дискуссии ниже (оппонента так затроллили, что он от стыда удалил все свои комменты, и теперь там по сути одна гневная проповедь weavejester'a).

1. Html и css -- дизайнерам, код -- кодерам. И смотри не перепутай, Кутузау!
Weavejester: "The problem with using HTML templating languages is that they quickly become impractical as the complexity of the page increases. Maintaining a file that's a mishmash of HTML from a designer and code from a developer is, in my experience, far more trouble than it's worth."

2. Html только для контента, весь look-n-feel делает CSS.
Weavejester: "Secondly, I'm of the opinion that if the presentation of your HTML page cannot be easily changed through CSS alone, then your HTML is probably not well-designed. Rather than trying to construct HTML purely based around a particular presentation layout, perhaps a better approach would be to construct the HTML independent of the presentation, and then to iteratively tweak this purely semantic structure into something that can be easily altered by CSS into the desired design.
...
That's not to say all our HTML is perfectly formed straight off the bat. Far from it. But when we find a part of our design can't easily be altered by CSS alone, we try to fix the HTML so that it can be. Eventually one gets to the point where the HTML rarely needs to be altered, except when new content or features are added."

3. Дизайнер делает только layout. Весь контент, все внутренности html-а делает программист.
Weavejester: "I don't disagree that the UI can change; I'm just of the opinion that written correctly, you can (more or less) isolate the content from the presentation. If anything, it's easier to change a set of well defined functions, or a file of CSS, than it is to alter a clunky mashup of HTML and inline code."
vagif: "...designers make changes to html as opposed to developers by saying that they are familiar with html and have the necessary tools.
But this is awfully wrong. When you change html it is either look'n'feel (colors,background,fonts,layout,icons,banners,etc) or structural change (add / remove some elements like buttons, dropdowns, edit boxes etc.)
In first case it is never continuous work. You usually decide and finish look'n'feel as one unit of work, and then just utilize the result of designer's work by wrapping it in libraries/templates/includes so developers would not waste their time to deal with those settings in each particular page.
Structural change on the other hand is where the actual bulk of changes happens. And it is NEVER a designer's work, but always a developer's. SO i was very surprised when you told that your designers often change things "in a middle". What the heck do they change ? Color schemes ? new banners every week ? you could not decide upon the layout of your web site, so you change it that often ?"

4. True-way: дизайнер делает html-страницу, программер преобразовывает ее в набор функций, генерирующих этот самый html. После этого программер может редактировать полученный результат как код, а не как html.
Weavejester: "As a developer, I'd much rather the designer send me a file of HTML I can convert into a concise set of functions."

То есть я в корне неверно воспринимал hiccup! Этот движок темплейтов не используется для генерации страницы вручную. Дизайнерское поделие переводится в формат, который воспринимает hiccup, а затем программер может его редактировать как код, а не как html! Вот уж действительно true-разделение логики и представления!


Пример:

(ns webtest.core
  (:require [pl.danieljanus.tagsoup :as ts]
            [hiccup.core :as h]
            [clojure.contrib.duck-streams :as io]))

(def data (slurp "data/data.html"))
(def parsed (ts/parse-string data))
(def generated (h/html parsed))

(io/spit "data/output.html" generated)

 В файле проекта должно быть:


(defproject webtest "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.2.1"]
                 [org.clojure/clojure-contrib "1.2.0"]
                 [clj-tagsoup "0.2.6"]
                 [hiccup "0.3.6"]])

Надо сказать, что хотя результат незначительно отличался по размеру, в броузере сгенерированная html-страница выглядела абсолютно идентичной оригиналу.

16 комментариев:

  1. А можно узнать, что со скоростью генерации? Вы проводили к-то тесты?

    ОтветитьУдалить
  2. Тесты не проводил, но в теории, скорость должна быть выше, чем у template-движков вроде erb, fleet, jsp. Связано это с тем, что исходник не надо парсить, чтобы найти в нем теги и обработать их. Все уже готовое, распарсенное в набор hiccup-выражений. Единственный известный мне движок, который, возможно, сравнится в скорости -- это Google Closure Templates. У гугловского движка есть возможность распарсить шаблон заранее, а подставлять значения в теги -- позднее, во время исполнения программы.

    ОтветитьУдалить
  3. Перевод разметки в код плох тем, что затрудняет внесение корректив дизайнером. Работа дизайнера и программиста перестаёт параллелиться.

    GCT не единственный движок, который компилирует предварительно код. Даже Smarty (тьфу! тьфу! тьфу!) и тот компилирует и кэширует код.

    ОтветитьУдалить
  4. Так в том-то все и дело, что дизайнер не вечно вносит такие коррективы. CSS подправить, изображения сменить -- это да. HTML-контент после какого-то этапа уже не меняется, и над ним начинает работать программист. Собственно об этом и речь в том споре. Weavejester утверждает, что после того, как основной layout сделан, дизайнер больше не лезет в html. Все менюшки, кнопочки и прочую фигню программер добавляет самостоятельно.
    Кстати да, хотелось бы услышать еще мнений на этот счет. Со своим я пока не определился, т.к. еще мало использовал hiccup. Но идея мне пришлась по душе.

    ОтветитьУдалить
  5. Это если проект не очень большой. Если же это долгострой, то требования к дизайну будут меняться регулярно.
    Посмотрите, например, на "одноклассников" -- там дизайн не перестаёт меняться никогда.
    Дизайн и программирование -- это не последовательные этапы проекта, а части цикла.

    ИМХО, такой подход (hiccup) неплохо подойдёт для генерации виджетов и прочей мелочёвки, которую не целесообразно выносить в шаблонизатор. А вот для сайта в целом я бы предпочёл шаблонизатор.

    Да и натаскать дизайнера на пользование шаблонизатором не проблема. :) В этом смысле удобно сделан jekyll.

    ОтветитьУдалить
  6. Сложная страница обычно выглядит как базовый layout плюс набор include-ов для отдельных фрагментов страницы. Дизайнер не изменяет эти фрагменты поотдельности, а фигачит страницу целиком. Поэтому, как ни крути, но в случае больших изменений оба подхода будут предполагать переделывание шаблонов целиком.
    Если нужны изменения look-n-feel (любые, вплоть до того, чтобы поменять местами div-ы), все это делается через CSS. Html это не касается вообще, так что дизайнер справится без внесения изменений в шаблон.
    Если нужны изменения в содержательную часть -- их делает программист, и тут уж лучше писать на языке программирования, чем на смеси html и безобразных тегов вроде Дизайнер не запрограммирует логику представления страницы. Дизайнер может дать только один или несколько статичных состояний страницы, которые программист все равно будет вынужден перегонять в свой программный код, будь то jsp, jsf или erb. Смесь html и логики страницы -- есть гуано по определению. Есть примеры полной лажы, например, PHP без фреймворков, есть способы посильнее, вроде Clojure+Enlive. Но так или иначе, все стремятся оставить в html минимум логики. Hiccup делает это радикально: он вообще отказывается от html, как от мусорки. Нет html-- нет и места, где можно намешать кода с данными.

    ОтветитьУдалить
  7. Это слишком идеализированная картина. Зачастую не получается абстрагироваться от look в html. Приходится вносить всякие промежуточные теги, которые нужны только для дизайна. Абстракция "протекает".

    Hiccup заносит "мусор" в код. Это тот же PHP, только со скобочками.

    ОтветитьУдалить
  8. Приведи пример, а то я что-то никак не пойму, о каких промежуточных тегах ты говоришь.

    ОтветитьУдалить
  9. Легко!

    Например есть ненумерованный список (ul), и надо чтобы маркеры были другого цвета, не такого как текст. Тут два варианта:
    1. list-style-image -- это не решение, а подмена условия;
    2. дать li цвет маркера, содержимое поместить в span и дать span'у цвет текста.
    Этот span семантически не нужен.

    Другой вариант: http://blog.mozilla.com/webdev/2009/02/20/cross-browser-inline-block/ Появляется не семантичный div.

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

    Хватит?

    ОтветитьУдалить
  10. Ну вообще-то даже дизайнеры сейчас пытаются не выносить стили в html, все стилевые вещи держат в CSS. Так что list-style-image -- концептуально более верное решение, т.к. позволяет разделить html и стили. Стили менять можно часто, в зависимости от желаний заказчика, а html останется прежним.

    По ссылке сходил. Программить такое -- легко. Каждый элемент из этого примера -- это фрагмент страницы, который и состоит из дополнительных дивов и прочего. Он маленький, редактировать этот фрагмент просто. Он не является частью layout-а и хранится отдельно, в маленьком файле.

    Я же просил пример, реализовать который было бы значительно проще на помеси html с управляющими тегами, чем на CSS и генераторе html.

    ОтветитьУдалить
  11. "Концептуально более верное решение" -- это словоблудие. Заказчику приспичит сделать цвет маркера меняющимся в каждой строке. Как тут обойтись без правки html? Никак! Css не у дел.

    Дело не в "легко" или сложно. Дело в засорени кода.
    Например: есть список товаров в интернет-магазине, его сделали ul/li. Пришёл заказчик и говорит: "хочу чтобы было не списком, а плиточкой". Приехали! Дизайнеру надо менять html, а не только css. А в случае hiccup ещё и *код* (тут дизайнер вахуе).

    Как всё-таки сделать несколько бэкграундов "концептуально"? На чистом css-то?

    Бля! Где я утверждал, что надо мешать html и код? Где?!

    ОтветитьУдалить
  12. > Как тут обойтись без правки html?
    Делаешь уникальный айдишник элементам html-а, делаешь для них стили. Для этого CSS и придумали.

    > Пришёл заказчик и говорит: "хочу чтобы было не списком, а плиточкой". Приехали! Дизайнеру надо менять html, а не только css. А в случае hiccup ещё и *код* (тут дизайнер вахуе).
    В данном конкретном примере вообще никаких проблем нету. Изменил html -- ради Бога, я его назад clj-tagsoup-ом конвертирую в набор конструкций hiccup и продолжаю редактировать. Это то, о чем я говорил выше: структурные изменения в странице в обоих подходах повлекут за собой одинаково сложные переделки. Дизайнер при этом даже не догадывается, hiccup я юзаю или еще какой jsf.

    > Как всё-таки сделать несколько бэкграундов "концептуально"? На чистом css-то?
    А при чем здесь это? Речь о том, насколько подход hiccup вообще применим к разработке сложных сайтов. В твоем примере нужно изменить структуру html-страницы. Изменяй, конечно, в чем проблемы-то? По сложности этот подход одинаков как в в erb, так и в hiccup.

    Hiccup проще в другом: в разработке наполнения страницы разными элементами. Наиболее популярные решения (jsf, asp.net и т.д.) предлагают DSL-и на основе xml и собственных управляющих конструкций. Хорошая идея, реализация отвратительная: никто не любит xml, да в нем еще и html намешан. Hiccup предлагает иной путь: полный отказ от html и замена его на чистый clojure-код. Внутри него уже нет такого различия между кодом и логикой, как между теми же html и erb. То, что делает hiccup -- чем-то похоже на библиотеку описания интерфейсов Seasaw.

    ОтветитьУдалить
  13. > Делаешь уникальный айдишник элементам html-а, делаешь для них стили. Для этого CSS и придумали.
    Заранее и всем?

    > ... конвертирую в набор конструкций hiccup и продолжаю редактировать...
    К этому моменту код мог претерпеть изменения. Merge?

    Перенос html в код -- это и есть PHP.

    (html [:ul
    (for [x (range 1 4)]
    [:li x])])

    <ul>
    ?> for ($x = 1; $x <= 4; ++$x) { <?
    <li><?= $x ?></li>
    ?> } <?
    </ul>

    Разница чисто синтаксическая.

    ОтветитьУдалить
  14. > Заранее и всем?
    Тем, кому надо. CSS позволяет изменять стили элементов разными способами.

    > Перенос html в код -- это и есть PHP.
    Weavejester: "The problem with using HTML templating languages is that they quickly become impractical as the complexity of the page increases. Maintaining a file that's a mishmash of HTML from a designer and code from a developer is, in my experience, far more trouble than it's worth."

    ОтветитьУдалить
  15. > Тем, кому надо.
    Так заранее не известно, кому надо.

    > Weavejester...
    Это всего лишь мнение / опыт одного человека. Это ничего не доказывает. Мой опыт показывает обратное.

    ОтветитьУдалить
  16. > Так заранее не известно, кому надо.
    Если делаешь html вручную -- то известно заранее. Если генеришь автоматом -- то можешь рассчитать. В любом случае, подходящий вариант найдется.

    > Это всего лишь мнение / опыт одного человека. Это ничего не доказывает. Мой опыт показывает обратное.
    Weavejester говорит о ситуации, в которой html-а и кода в нем намешано столько, что поддержка такой страницы становится черезчур сложной. Тебя эта ситуация устраивает -- ну ладно, а если справляешься -- то и вообще молодец. Weavejester-у этот способ не по душе из-за слишком большого количества кода в html-е. Его разработка (hiccup) позволяет вообще избавиться от "сырого" html-а, заменив его еDSL-ем на Clojure. Этот способ обладает рядом преимуществ. Не приходится заморачиваться о множестве вещей: как передавать параметры в шаблон, как поудобнее разбить шаблон на модули, как правильно вызвать нужную функциональность и т.д. Всё это проблемы решаемые, но со сложностями. Например, не так уж просто разбить большой шаблон на множество reusable-частей. Очень мало движков позволяют хранить несколько модулей в одном файле -- уже одно это создает множество преград. В hiccup -- это вообще не проблема, т.к. это обычный eDSL на Clojure, и туда можно добавить свои функции.

    Hiccup-подход интересен уже тем, что аналогичный способ генерации шаблонов использовал Пол Грэм в своем ViaWeb. До прочтения спора weavejester-а с тем анонимным товарищем я тоже не мог взять в толк, почему Пол Грэм для своего супер-успешного стартапа выбрал такой странный подход. Впрочем, если заинтересовался, то на сайте Пола есть несколько статей с подробностями о том, как делали ViaWeb. Их интереснее читать в ориганле, чем по моим пересказам.

    ОтветитьУдалить