Як я працював над шаблонізаторами й пришов до простоти

Я витратив досить багато часу на розробку шаблонізаторів і маю що розповісти про свій досвід. У цій публікації я розкажу що думаю про шаблонізатори, генерацію HTML, як змінювалися мої уявлення про них та про декілька власних реалізацій шаблонізаторів на Golang.

Ідеї

У цій частині я перерахую декілька ідей, стосовно шаблонів та генерації HTML, які мені подобаються. Ця частина буде тезисною, аби зберегти власний час та час читача.

Презентація – це функція. Презентація будь-чого – це функція від будь-чого до строки, якщо говорити про текстову презентацію. Така функція має наступну узагальнену сигнатуру: any -> string.

Насправді немає необхідності писати таку поліморфну функцію і достатною написати окремі функції-презентації для множини об’єктів, що мають знаходитися у тому чи іншому контексті презентації.

Наприклад, ми маємо декілька контекстів презентації для публікації у блозі: сторінка публікації, список публікацій з назвами та короткими описами, посилання на публікації, що містять лише назви. Таким чином для публікації ми маємо написати три окремі функції презентації з наступною сигнатурою: Publication -> string.

Шаблон – це функція. Шаблон відрізняється від презентації тим, що будується не навколо певних типів даних, а існує ніби окремо. Шаблон має місця для ін’єкції наповнення. Усі ін’єкції – це строкові відображення значень певних типів даних. Якщо презентації можуть мати певні обчислення чи т.з. “логіку“, то шаблони мають бути статичними із місцями для ін’єкції значень, що заздалегідь не відомі. Іншими словами, шаблони не мають містити умовних операторів чи циклів.

Typesafety та аналіз ризиків. Колись давно я писав на Ruby і не думав про типи. Потім я познайомився з Erlang, OCaml, Golang та почав робити велику ставку на типи, намагався створювати потужні й виразні системи типів, які б гарантували безпеку. Ще трохи пізніше я задумався над тим, що яку б систему типів не придумав – вона завжди “протікає”. Чим більше я намагаюсь досягти ідеалу, тим більш складний код я пишу. Він не вирішує задач бізнесу та все складніший для внесення змін.

Наприклад, у Go2HTML я намагався написати безпечний з точки зору типів HTML шаблонізатор і для цього мені довелося вивчати всю специфікацію HTML та реалізовувати її у Go2HTML. Цей проєкт я так і не завершив, бо чим більше занурювався в нього – тим ще більше треба було зануритися. Тільки уявіть собі, що в HTML ~140 стандартних тегів із десятками різноманітних атрибутів. Усі теги мають декілька спіліних атрибутів, але більшість специфічна для кожного тегу. Деякі атрибути посилаються на інші елементи (як то for у label), деякі не можуть використовуватися разом і т.д. І це ще не кажучи про web-components та власні теги, які може створювати кожен розробник. Реалізувати стандарт повністю дуже складно, особливо, коли працюєш один. Крім того, яка взагалі ціна помилки у HTML?

HTML розроблено з вбудованим принципом elegant degradation/fault tolerance/graceful degradation. Браузер намагається відобразити усе, що можливо, навіть якщо HTML не валідний. Важко уявити ситуацію, коли б помилка у HTML могла б завдати суттевих збитків. У 99% випадків користувачі її взагалі не помітять. У 1% випадків Вас повідомлять про помилку і Ви її швидко виправите. Запобігти усіх можливих помилок неможливо. Запобігти переважної більшості помилок – дуже дорого і довго. Поки Ви будете боротися з вигаданими помилками – ваш бізнес буде втрачати. До уваги треба брати реальні потреби користувачів, а не нікому не потрібну якість.

HTML – первинний. Одна з найбільших проблем веб-розробки полягає у тому, що розробники цього не розуміють, а більшість т.з. веб-дизайнерів – то є веб-малярі, які зовсім не розуміють специфіки вебу. Детальніше про первинність HTML я писав у публікації [скоріше за все] Ви розумієте веб-розробку не правильно дуже раджу її прочитати та поширити.

Оскільки HTML первинний, ми маємо спочатку писати HTML, а вже потім його якось оформлювати. HTML має диктувати CSS, а не навпаки. Таким чином варто замислитися над генерацією CSS із HTML. Завдяки цьому ми позбудемося переускладненого та мертвого CSS з купою можливих регресій після кожного оновлення.

Контроль складності. Майже кожен проєкт скочується в безодню складнощів. Контроль складності – це найбільша проблема, яка безмежно вдорожчує та сповільнює розробку та розвиток. Ми маємо будь що контролювати складність, використовувати найпростіші рішення та інструменти.

Швидкість виконання та швидкість розробки мають значення. Між двома векторами розвитку необхідно шукати третій – збалансований вектор, який складається із багатьох тактик і використання того чи іншого шаблонізатору – це не найголовніша.

Go2HTML та Go2CSS

Одним з найперших підходів до розробки шаблонізатора була розробка шаблонізатора go2html. Go2HTML є внутрішньою предметно-орієнтованою мовою (eDSL), приклад використання якою можна подивитися у тестах.

eDSL Go2HTML побудований на основі об’єктів – конфігураторів, що інкапсулюють в собі код побудови. В Go2HTML я також зберігав древовидну структуру шаблону та одночасно компілював його у пласку структуру, більш ефективну за дерево для генерації результуючої строки.

Також в Go2HTML я використовував своєрідну in-memory файлову систему для організації шаблонів.

Код Go2HTML досить складний, оскільки я не зміг передати його іншій людині для підтримки, аби зайнятися іншими, більш наближеними до бізнесу проєктами. Мета також досить складна – typesafe шаблони. Я від цієї мети/вимоги відмовився, оскільки зрозумів, що воно тих зусиль не варте.

За генерацію CSS із HTML мала відповідати окрема бібліотека – Go2CSS, яку я також не доробив, оскільки більше не бачив у цьому сенсу та тому що не зміг передати її розробку іншому розробнику, аби сфокусувати увагу на бізнесі.

Загалом розробка Go2HTML і Go2CSS була досить цікавим досвідом, який дозволив провести багато експериментів з розробки eDSL на Golang та прийти до розуміння того, що мені необхідно щось більш просте та менш функціональне. Також, під час роботи над Go2HTML я досить добре ознайомився зі специфікаціями HTML5 та CSS3, до чого, у іншому випадку, не мав би такой сильної мотивації.

gt

Після того, як я розчарувався у підході Go2HTML, я вирішив розробити більш просте рішення із вбудованою генерацією CSS із HTML – gt (приклад використання eDSL в тестах). gt також є експериментом з використання проміжного “брудного” етапу/коду, який я інкапсулював у limbo.

Ідея з Limbo досить проста: ми допускаємо, що eDSL код може бути не валідним і описану на ньому конфігурацію ми зберігаємо у об’єкті Limbo, з якого вже генеруємо об’єкт Universe, що містить у собі вже валідовані та скомпільовані шаблони. Використання Limbo є способом розробки валідних шаблонів без використання більш складної системи типів.

Якщо концепція розділення на Limbo та Universe здається складною, то можете уявити собі код у Limbo як код, який пише розробник, а Universe – як скомпільований із нього машинний код, для якого було виконано статичний аналіз коду, перевірки коректності та оптимізації.

Поки я працював над gt, мені прийшла ідея ще більш простого шаблонізатора із вбудованою генерацією CSS із HTML.

dt

dt – це останній та найбільш простий варіант шаблонізатора із вбудованою генерацією CSS із HTML. Приклад використання eDSL dt можна побачити у тестах.

Go2HTMLgtdt
955847190
Кількість строк коду без урахування тестів та недописаного коду. Також Go2HTML не містить безпосередньо у собі генерації CSS із HTML.

У dt я викинув усе зайве, включно з будь-якими перевірками коректності й надав розробнику повну свободу у організації коду. Якщо розробнику необхідно використати один шаблон всередині іншого – це виконується через звичайну текстову ін’єкцію результату рендеренгу “вкладеного” шаблону. Іншими словами, усі шаблони є пласкими й нічого не знають про існування інших шаблонів.

Висновки

Розробка складних рішень мені здається проявом деякої схибленності на контролі та страху помилок. Раніше я вважав, що код бібліотек повинен мати захищені від дурнів інтерфейси. Поекспериментувавши з таким підходом, я помітив, що захист від дурня коштує дуже дорого, у більшості випадків, значно більше, аніж реалізація власне того, що необхідно бізнесу, або того, що необхідно досвідченому розробнику аби підвищити власну ефективність.

Розробляйте прості рішення для себе і бізнесу. Не думайте про дурнів.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *