Остановите войну в Украине!

Культ кар­го CSS

Перевод «Cargo Cult CSS»

Перевод Влад Андерсен

Редактура Ольга Алексашенко Вадим Макеев

Все согласны с тем, что лучше иметь какую-то методологию для написания и поддержки CSS, чем не иметь никакой вовсе. Тем не менее, некоторые популярные среди разработчиков практики ухудшают как семантику кода, так и возможность его долгосрочной поддержки. Я хотел бы рассказать о проблемах с «методологиями фреймворков» CSS, и о том, как мы, веб-разработчики, можем разрешить эти проблемы.

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

Семантика Скопировать ссылку

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

Фил Карлтон

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

При написании HTML выразить семантику содержимого или интерфейса сайта/приложения можно тремя основными способами: с помощью типа элементов, из которых состоит разметка; id, которые используется для того, чтобы указать особый, индивидуальный элемент — и имен классов, которые нужны, чтобы описать набор элементов.

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

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

Нигде в спецификации атрибута class не сказано, для чего предназначен этот атрибут, кроме рекомендации, что его следует использовать для описания семантики содержимого. Несмотря на это, для веб-разработчиков стало совершенно обычным делом описывать атрибут class как «класс CSS» и т.п. Как замечает Тантек Челик:

Употребляя фразу «CSS-класс» или «имя CSS-класса», вы не просто неточно (неверно!) выражаетесь, а привязываете контекст отображения и саму структуру CSS к именам классов, что предполагает и поощряет использование неверного приема, когда названия классов становятся зависимыми от предполагаемого отображения.

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

Быстрый тест на то, является ли ваш HTML-код семантическим: можно ли использовать его в качестве публичного API?

Отличный пример: микроформаты. Единственная причина, по которой они возникли, в том, что создатели документов использовали семантические имена классов, чтобы разметить часто встречающиеся структуры: адреса или записи в календаре. За счет кодификации договоренностей, которые использовались для описания таких данных, разметка стала полезной не только для посетителей сайтов, но и для автоматических систем: программы смогли понимать и использовать эти данные так, как никто и не мог себе представить. Микроформаты демонстрируют всю пользу от семантических имен классов, так что печально, что на них иногда смотрят свысока.

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

Николас Галлахер

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

Поддержка Скопировать ссылку

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

  • Повторение/дублирование кода
  • Постоянство кода в продукте
  • Производительность

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

.box-standard {
    color: blue;
    border: 2px solid blue;
    border-radius: 5px;
    padding: 20px;
    font-family: Helvetica, Arial, sans-serif;
    font-weight: normal;
    font-size: 1rem;
    line-height: 1.4;
}

Разработчик может применить этот класс к любому числу элементов, и для всех них будет действовать этот стиль. Кроме того, другие элементы могут использовать не весь набор стилей, а переопределять конкретные свойства по мере необходимости, объявив отдельный класс ниже в коде CSS. Поскольку у обоих классов один и тот же уровень специфичности, более позднее определение получит приоритет.

    .box-special {
        color: red;
        border-color: red;
        font-weight: bold;
    }
<div class="box-standard box-special">
    Особенный квадратик!
</div>

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

Что действительно нужно здесь разработчику — это комбинация примесей и расширений, которые существуют в различных CSS-препроцессорах: например, Less и Sass. Набор связанных свойств можно сгруппировать как примесь или шаблон @extend, которым можно без проблем дать полностью презентационное название: он не проявляется нигде в сгенерированном коде (если вы не захотите, конечно — например, для отладки) и может быть встроен в любой CSS-селектор. Вот простой пример примеси в синтаксисе SCSS:

@mixin news-item($color) {
    border: 2px solid $color;
    border-radius: 5px;
    padding: 20px;
    font-family: Helvetica, Arial, sans-serif;
    font-weight: normal;
    font-size: 1rem;
    line-height: 1.4;
}

div.news {
    @include news-item(blue);
}

div.breaking {
    @include news-item(red);
    font-weight: bold;
}
<div class="news">
    Вот это новость!
</div>
<div class="breaking">
    Вот это важная новость!
</div>

Изменения в определениях стиля в примеси обязательно дойдут до селектора div.breaking, а разметка стала настолько гибко связанной, насколько вообще реально. Конечно, возможности писать примеси напрямую в CSS очень не хватает (к счастью, наконец она проходит процесс формализации), но если вы профессионально пишете CSS, нет никаких причин не использовать CSS-препроцессор.

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

<span style="display: block; font-family: Arial, sans-serif; font-size: 11px; color: blue; border-style: solid; border-color: blue; border-width: 2px; padding: 20px;">
    Квадратик!
</span>
<span style="display: block; font-family: Arial, sans-serif; font-size: 11px; color: blue; border-style: solid; border-color: blue; border-width: 2px; padding: 20px;">
    Ещё квадратик.
</span>
<span style="display: block; font-family: Arial, sans-serif; font-size: 11px; color: blue; border-style: solid; border-color: blue; border-width: 2px; padding: 20px;">
    И еще квадратик. Однако, тенденция?
</span>

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

<span class="display-block blue-box font-arial color-blue solid-blue-border padding-20">
    Танцуем, как в 90-е!
</span>
<span class="display-block blue-box font-arial color-blue solid-blue-border padding-20">
    Давно не заходили на K10K.net?
</span>
<span class="display-block blue-box font-arial color-blue solid-blue-border padding-20">
    Аааааааа!
</span>

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

Еще одна цель этой методологии — обеспечить высокую производительность. Это зависит от многих факторов: какой клиент заходит на веб-страницу, какая у него скорость соединения, был ли контент закэширован и т.п. На базовом уровне сильнее всего влияют на производительность CSS три фактора:

  • Количество HTTP-запросов
  • Статус кэширования
  • Размер документа

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

Однако отдавать на выходе такой CSS-документ не вполне верно: если вы используете маленькие селекторы с меньшим количеством правил, а привязки в большей степени выводите в разметку (с большим количеством более длинных имен классов) — то количество передаваемых данных не идет вниз, а наоборот смещается из CSS-файла (который обычно агрессивно кэшируется и отдается через CDN) внутрь документа, который, как правило, не кэшируется совсем.

Сторонники методологий CSS-фреймворков часто говорят, что если оставлять селекторы короткими, это хорошо влияет на производительность. Этот тезис давно опровергнут: современные движки браузеров достаточно эффективны, и практически во всех сложных случаях (за исключением разве что самых экстремальных) выигрыш в скорости от переписывания селекторов пренебрежительно мал. Хотя фронтенд-производительность при этом безусловно важно иметь в виду:

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

Нильс Маттейс

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

.list-link {
    font-weight: bold;
    text-decoration: none;
}

или:

ul.members li a {
    font-weight: bold;
    text-decoration: none;
}

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

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

Эрик Рэймонд

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

Я оставлю последнее слово о производительности за Мэттом Уилкоксом, который безупречно выражает свою мысль:

Производительность — проблема на уровне браузера, а не что-то, чем должны заниматься (или обходить стороной) разработчики HTML и CSS. Если мы пишем валидный и вменяемый HTML или CSS, мы не должны опускаться до смехотворных правил только для того, чтобы увеличить скорость, с которой загружается данная конкретная страница. И мы действительно не должны: ведь с каждым релизом браузеры становятся быстрее. О производительности стоит больше всего волноваться тем ребятам, которые занимаются JS. А если вы хотите оптимизировать свою связку HTML и CSS — делайте это в соответствии с передовыми практиками, а не для лучшей производительности. «Плохая» производительность починится сама, когда выйдут новые браузеры, а низкокачественный, непонятный и сложный для поддержки код — нет.

Что же мы можем сделать? Скопировать ссылку

Фронтенд-разработчики тихо и с успехом в течение многих лет разрабатывали крупные веб-проекты без использования методологий CSS-фреймворков. Тот простой факт, что эти крупные проекты обошлись без использования OOCSS, БЭМ и других методологий, показывает, что можно вполне эффективно работать и без них. Вот несколько рекомендаций от людей, с которыми я обсуждал написание CSS, а также — несколько предложений, основанных на моем личном опыте.

Ради всего святого, используйте id!

В ряде методологий CSS-фреймворков считается, что использовать id в CSS — плохая идея. Объяснение этого следующее: id обладают большой специфичностью, поэтому переопределить правила, описанные для id, можно только с помощью (презираемого) модификатора !important. Я абсолютно не согласен с этим: цитируя Ангуса Кролла, «стратегия избегания беспокоит меня тем, что нельзя освоить язык, не зная его с ног до головы, а страх и уклонение — враги знания». Если уж вы профессиональный веб-разработчик, вы должны понимать, как работает специфичность и как она влияет на селекторы, которые вы пишете. Кроме того, id — совершенно полноправная часть структуры документа, и у них есть очень полезные функциональные и семантические свойства.

Пишите селекторы так, чтобы они были настолько специфическими и настолько описательными, насколько это нужно: ни больше, ни меньше.

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

Не существует модулей для повторного использования (до того, как они действительно не станут повторно использоваться); делайте рефакторинг, переписывайте, выбрасывайте лишний код каждый день.

Страх написать слишком специфичный CSS по сути является преждевременной оптимизацией. Пишите ровно тот CSS, который требуется для наличествующей задачи: когда станет очевидно, что его нужно использовать в другом месте, сделайте рефакторинг и разбейте его на примеси — но только в этот момент, ни минутой раньше! Может быть, на следующей неделе ваши приоритеты изменятся, и всю эту функцию вообще придется выбросить? Вы совсем не хотите, чтобы через полгода вы оказались лицом к лицу с пачкой ненужных селекторов из имен классов, смысла которых никто не понимает, равно как и того, что случится, если их удалить.

Много файлов и процесс сборки.

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

Разрабатывайте статические прототипы. Делайте скриншоты всего, чего можно.

Сайты и приложения обычно формируются из набора общих шаблонов: форма с подписями и полями ввода, список ссылок внутри элемента <nav>, список описаний и значений и т.п. Когда вы беретесь разрабатывать какой-то новый элемент функциональности, начните с того, чтобы сделать разметку, которая адекватно описывает нужную вам структуру как статический HTML. Это послужит вам не только как предмет для будущего тестирования, не ломается ли ваш код, но и для того, чтобы новые члены команды сразу видели, что уже есть и чем можно пользоваться. Если делать скриншоты этих страниц или всего приложения (вручную или с помощью автоматического набора тестов), это может быть полезным для определения ошибок с помощью методики постоянной интеграции.

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

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

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

Луис Салливен

Благодарности Скопировать ссылку

Пока я писал эту статью, мне очень помогали своим советом Марк Норман Фрэнсис, Брэд Райт, Росс Бранигес, Джейк Арчибальд и Патрик Гриффитс.

Ссылки Скопировать ссылку