CSS с душком. Возвращаемся к теме.

Источник: Code Smells in CSS Revisited.
Автор:  Harry Roberts.
Перевод: .

Ранее, еще в 2012 году мной была написана статья "Code Smells in CSS" (перевод здесь) о потенциальных антишаблонах CSS кодирования. Оглядываясь назад и вспоминая уже проделанную в этой области работу, хочется отметить, что мое мнение абсолютно не изменилось и я готов снова, даже спустя четыре года, подписаться под каждым своим словом. Более того, на сегодняшний день мне хотелось бы дополнить представленный ранее список новыми моментами. Опять же, я не собираюсь клеймить описываемые здесь приемы как абсолютно недопустимые, и то, что применительно к ним я использовал такой термин вовсе не означает, что эти методы будут неприемлемы в вашем конкретном случае. Но факт остается фактом: они все же слегка «попахивают».

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

Запах кода или так называемый «код с душком» в сфере программирования означает присутствие в исходном коде программы возможных признаков более серьезных проблем. Как в свою очередь выразился Мартин Фоулер (Martin Fowler) — «код с запашком является поверхностным показателем, свидетельствующим о более глубоких проблемах в системе». Имеет место и другой взгляд на это явление, основанный на принципах и качестве кодирования: «запахи — это определенные структуры кода, демонстрирующие нарушения фундаментальных принципов проектирования и негативно влияющие на функционал конечного продукта». Запахи кода, как правило, не являются багами и с технической точки зрения вполне корректны. Они совсем не обязательно приводят к сбоям в работе программы. Вместо этого они указывают на недостатки проектирования, которые могут стать причиной замедления процесса разработки или увеличения риска появления ошибок и сбоев при эксплуатации в будущем. Запахи плохого кода могут быть признаками появления факторов, способствующих так называемому техническому долгу. Роберт Сесил Мартин (Robert C. Martin) называет список запахов кода «системой ценностей» мастерства программирования.

Таким образом, технически запахи не всегда некорректны, они, скорее всего, являются безошибочным критерием качества кода.

@extend

Надеюсь в этом первом случае я буду четок и краток. Ранее мной неоднократно было сказано, что использование директивы @extend связано с различными побочными эффектами и ловушками. На сегодняшний день мое мнение на этот счет абсолютно не изменилось и я продолжаю настаивать на том, чтобы эта директива была отнесена к коду с душком. Нет, ставить на ней крест, конечно же, не следует, но в большинстве случаев проблем не избежать. Относитесь к ней с осторожностью.

Связанные с директивой @extend проблемы многообразны, но постараемся подытожить:

  • В плане производительности она более опасна, чем миксины. Gzip сжатие лучше работает с повторяющимся кодом, поэтому CSS файлы, содержащие повторения (к примеру, миксины) достигают большей степени сжатия.
  • Она прожорлива. Директива @extend будет «расширять» каждый встречающийся ей экземпляр класса, что, в конце концов, может привести к появлению невероятно длинных цепочек селекторов, похожих вот на такие.
  • Она перемещает компоненты вашей кодовой базы. В CSS исходный порядок очень важен. Поэтому изменение последовательности размещения селекторов является негативным явлением, которого нужно избегать.
  • Ее использование приводит к потере так называемого документального следа. @extend скрывает много сложностей вашего SASS кода, которые должны проявляться постепенно, в то время, как подход, использующий селекторы с отдельными классами, делает эту информацию более доступной, помещая ее в удобном для восприятия месте.

Конкатенация строк в классах.

Другим, используемым в SASS приемом, является операция объединения строк, т.е. имен ваших классов с помощью оператора &. Например, вот так:

.foo {
color: red;

&-bar {
font-weight: bold;
}

}

Что приводит к:

.foo {
color: red;
}

.foo-bar {
font-weight: bold;
}

Основное преимущество такого подхода — сжатость. То, что определение пространства класса .foo делается только один раз, свидетельствует о максимальном соблюдении принципа DRY-кодирования.

Но здесь, кстати, есть и недостаток, который не столь очевиден на первый взгляд. Дело в том, что строки "foo-bar" больше нет в нашем исходном коде. Все бы ничего, но если нам, к примеру, понадобиться обратиться к источнику стилей класса .foo-bar, прибегнув при этом к поиску по имеющейся кодовой базе, то все, что мы получим в результате, будет находиться в HTML коде или в лучшем случае в скомпилированном CSS файле (если таковой предусмотрен нашим проектом). То есть, возникает проблема поиска стилевых данных по классу .foo-bar.

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

Вы, конечно же, можете возразить, что эта проблема решается с помощью файлов-мапперов (Source Maps) или для того, чтобы найти стили, соответствующие, к примеру, классу .nav_item, то достаточно обратиться к файлу nav.scss. Но это не всегда работает. Разобраться в данном вопросе поможет представленная мной небольшая видео-презентация.

Сокращения свойства background.

Тема сокращенного синтаксиса свойства background была недавно затронута мной в соответствующей статье. Здесь же я постараюсь кратко изложить суть проблемы. Итак, написав:

.btn {
background: #f43059;
}

вы, вероятно, имели в виду:

.btn {
background-color: #f43059;
}

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

Присутствие таких способов определения данного свойства всегда настораживает меня, поскольку они, как правило, приводят к проблемам.

Появление ключевого селектора более одного раза.

Ключевым является селектор, к которому, собственно, и применяются стили. Другими словами это целевой селектор. Как правило, хотя и не всегда, он находится непосредственно перед открывающей фигурной скобкой ({). Если рассматривать такой CSS:

.foo {}

nav li .bar {}

.promo a,
.promo .btn {}

то ключевыми селекторами будут:

  • .foo
  • .bar
  • a и
  • .btn соответственно.

Если мы проверим имеющуюся кодовую базу утилитой ack на наличие класса .btn, то получим в результате что-то подобное этому:

.btn {}

.header .btn,
.header .btn:hover {}

.sidebar .btn {}

.modal .btn {}

.page aside .btn {}

nav .btn {}

Помимо того, что многое из показанного просто является некачественным CSS кодом, основная проблема, которую я пытаюсь здесь обозначить, заключается в многократном определении стилей для класса .btn. А это значит, что:

  1. Ни один из имеющихся источников стилей кнопки не может однозначно сказать мне как эта кнопка должна выглядеть.
  2. Множество изменений свидетельствуют о том, что классу .btn соответствует масса потенциальных вариантов стилей. Причина — мутирующий CSS.

Первое, что приходит мне на ум при виде подобного CSS, так это то, что любая связанная, в данном случае с кнопками, работа будет осложнена огромным пространством, отслеживание связей стилей в котором будет, мягко говоря, непростой задачей. Более того, изменения, вносимые в оформление кнопок, могут привести к непредсказуемым побочным эффектам, проявляющимся в разных местах. Именно это является одной из ключевых проблем, связанных с мутирующим CSS кодом.

Старайтесь придерживаться принципа БЭМ в кодировании, который подразумевает создание совершенно новых классов. Что-то, например, вот этого:

.btn {}

.btn—large {}

.btn—primary {}

.btn—ghost {}

Каждый ключевой селектор является уникальным.

Присутствие класса в файле другого компонента.

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

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

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

  • В файле стилей самого элемента.
  • В файле, отвечающем за вызванные определенным контекстом изменения стилей.

Предположим у нас имеется следующий CSS:

.btn {
[styles]
}

.modal .btn {
font-size: 0.75em;
}

Как быть с правилом .modal .btn {}? Где разместить?

Его место в файле .btn. Определенно.

Мы должны делать все возможное для группирования отвечающих за стилизацию объекта (соответствующего ключевому селектору) правил. В нашем примере в роли объекта выступает класс .btn именно ему мы должны уделить максимум внимания. Что касается класса .modal, то он представляет собой исключительно контекст и поэтому должен рассматриваться нами лишь как окружение интересующего нас объекта. Цель стилизации — сам объект. На этом и основывается наше решение, заключающееся в том, чтобы не переносить последнее правило в другой файл, поскольку оно имеет непосредственное отношение к оформлению того же объекта, что и верхнее правило.

Первопричиной такого выбора является ни что иное как коллокация, т.е. удобное размещение всех каким-либо образом имеющих отношение к объекту (кнопке) правил в одном месте. И если вам, к примеру, понадобиться просмотреть стили присутствующих в моем проекте кнопок, то все они будут доступны вашему вниманию. Не нужно перебирать десяток различных файлов в поисках нужной информации. Стоит лишь открыть соответствующий файл — _components.buttons.scss

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

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

Возьмите себе за правило всегда задаваться вопросом: «что я оформляю X или Y?» И если ответ X, то ваше CSS правило должно находиться в x.css, в противном случае его место в y.css. Все просто.

БЭМ миксы.

Хочется сразу отметить, что я бы на самом деле не стал писать такой CSS. Лучше уж прибегнуть к БЭМ миксам. Но это из другой оперы. Итак, к примеру, вместо этого:

// файл _components.buttons.scss

.btn {
[стили]
}

.modal .btn {
[стили]
}

// файл _components.modal.scss

.modal {
[стили]
}

мы выберем такой вариант:

// файл _components.buttons.scss

.btn {
[стили]
}

// файл _components.modal.scss

.modal {
[стили]
}

.modal__btn {
[стили]
}

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

<div class="modal">
<button class="btn modal__btn">Dismiss</button>
</div>

Это называется БЭМ микс, с помощью которого мы представили совершенно новый класс, предназначенный для оформления определенного вида кнопки (modal). При таком подходе вопрос «где находится то или иное правило» отпадает сам собой. Более того, путем отсутствия вложенности достигается снижение специфичности, а благодаря исключению повторения класса .btn мы избегаем мутации кода. Магия!

CSS @import.

Что касается директивы @import, то ее можно отнести не просто к категории кода с душком, я бы выразился по-другому: ее использование, это невероятно вредная практика. Основной удар приходится на производительность, поскольку эта директива приводит к отсрочке загрузки определенного CSS кода до тех пор, пока он не понадобится. Упрощенный вариант действий, выполняемых в процессе загрузки кода, запрашиваемого с помощью @import, выглядит примерно вот так:

  1. получение HTML файла, в котором содержится запрос на CSS файл.
  2. получение CSS файла, содержащего запрос на другой CSS код;
  3. получение необходимого CSS файла;
  4. начало рендеринга страницы.

Если же подгружаемый с помощью директивы @import CSS код втиснуть в единый (общий) файл, то последовательность процесса упроститься до следующего вида:

  1. получение HTML файла, содержащего запрос на CSS файл;
  2. получение CSS файла;
  3. начало рендеринга страницы.

Когда наличия нескольких файлов все таки не избежать (нужно, скажем, подключить Google Fonts), то вместо директивы @import лучше использовать два тега <link />. И если в плане компактности это будет выглядеть не совсем идеально (лучше, конечно же, содержать все зависимые компоненты кода в своих CSS файлах), но производительность при этом пострадает намного меньше:

  1. получение HTML документа, который ссылается на два CSS файла;
  2. получение двух CSS файлов;
  3. начало рендеринга страницы.

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

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *