English
🏷️

Когда CSS-код с душком  

Это статья из старого первого блога, который сохранился только на archive.org. Все старые статьи помечены тегом 🚨 старое

Перевел тут на досуге очередную статейку.

На этот раз от британского разработчика Гарри Робертса (Harry Roberts). Оригинальное название «Code smells in CSS».

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

Недавно Крис Койер (Chris Coyier) ответил на чей-то вопрос:

На основании чего вы можете сказать, что от вашего кода попахивает? Каковы признаки того, что код малофункционален или что веб-разработчик выполнил работу нехорошо? На что вы смотрите в коде, чтобы определить его качество?

Я подумал, что смог бы расширить отличный ответ Криса своим собственным, дополнительным взглядом на вещи.

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

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

⚔️ Отмена стилей переопределением 

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

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

Любой подобный набор CSS-правил:

CSS

    border-bottom: none;
    padding: 0;
    float: none;
    margin-left: 0;
    

— плохие новости. Если вам нужно удалить рамки — возможно вы добавили их слишком рано. Это действительно трудно объяснить, поэтому я продолжу следующим простым примером:

CSS

    h2 {
      font-size: 2em;
      margin-bottom: 0.5em;
      padding-bottom: 0.5em;
      border-bottom: 1px solid #ccc;
    }
    

Здесь мы добавили всем заголовкам H2 наши обычные размер шрифта font-size и отступ margin, а также небольшой внутренний отступ padding и нижнюю рамку, чтобы визуально отделить заголовок от нижеследующих элементов страницы. Но возможно, в определенных обстоятельствах, нам эта нижняя рамка и паддинг не нужны. Тогда мы удовлетворимся чем-то вроде этого:

CSS

    h2 {
      font-size: 2em;
      margin-bottom: 0.5em;
      padding-bottom: 0.5em;
      border-bottom: 1px solid #ccc;
    }

    .no-border {
      padding-bottom: 0;
      border-bottom: none;
    }
    

Итого имеем десять строк CSS и уродливое название класса. Нижеследующая конструкция была бы лучшим выходом:

CSS

    h2 {
      font-size: 2em;
      margin-bottom: 0.5em;
    }

    .headline {
      padding-bottom: 0.5em;
      border-bottom: 1px solid #ccc;
    }
    

Теперь у нас восемь строк CSS, мы ничего не отменяем и располагаем осмысленным названием класса.

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

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

Как только я вижу CSS, который отменяет предыдущее оформление — я вполне уверен, что кое-что неудачно спроектировано и что порядок в котором элементы были построены\написаны требует переделки.

🔮 Магические числа 

Это выносит мне мозг особенно. Я ненавижу магические числа.

Магическое число — это число, которое используется «просто потому, что работает». Возьмем следующий пример:

CSS

    .site-nav {
        [styles]
        }
        .site-nav > li:hover .dropdown {
            position: absolute;
            top: 37px;
            left: 0;
            }
    

top: 37px; здесь — магическое число. Это работает по-видимому от того, что элемент li внутри .site-nav случился быть 37 пикселей в высоту, от нижней границы которого должно появляться выпадающее меню .dropdown.

Проблема том, что число в 37 пикселей здесь полностью зависит от обстоятельств и мы не должны ему верить. Что если кто-то изменит font-size в .site-nav и теперь все будет высотой 29 пикселей? Число 37 более не делает то, что должно, и следующий разработчик должен об этом знать, чтобы изменить число.

И что же произойдет если Chrome отобразит элемент li высотой в 37, а IE — в 36 пикселей? Это число сработает только в одном конкретном случае.

Никогда не используйте подобные значения, только потому что они работают. Будет гораздо лучше, если в этой ситуации мы заменим top:37px; на top:100%;, что будет означать «отступ во всю высоту элемента».

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

Наличие магических цифр — плохая новость. Они скоро устаревают, они сбивают с толку других разработчиков, их нельзя объяснить, им нельзя доверять.

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

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

Избегайте магических значений.

🚂 Уточняющие селекторы 

Уточняющие — это селекторы похожие на эти:

CSS

    ul.nav {}
    a.button {}
    div.header {}
    

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

Упомянутые выше селекторы должны быть переписаны так:

CSS

    .nav {}
    .button {}
    .header {}
    

Теперь я знаю, что я могу применить .nav к ol, а .button к input и что когда сайт будет перенесен на HTML5, я могу быстро поменять элемент div шапки сайта на элемент header, не беспокоясь об оформлении.

Что касается производительности, то это не такая уж и большая проблема, однако она всетаки имеет место быть. Зачем заставлять браузер искать .button среди элементов a, когда вы просто можете сказать, чтобы он просто нашел .button и все? Уточнением селекторов вы повышаете нагрузку на браузер.

Можно привести и более экстремальные примеры:

CSS

    ul.nav li.active a {}
    div.header a.logo img {}
    .content ul.features a.button {}
    

Все эти селекторы можно уменьшить или вообще переписать следующим образом:

CSS

    .nav .active a {}
    .logo > img {}
    .features-button {}
    

Это поможет нам:

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

📌 Жестко заданные абсолютные значения 

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

CSS

  h1 {
      font-size: 24px;
      line-height: 32px;
      }
  

line-height: 32px; — не круто, это должно выглядеть так: line-height:1.333

Интерлиньяж всегда должен устанавливаться относительно, с целью сделать высоту строки более податливой и гибкой. Наверняка вы хотели бы быть уверены в том, что при изменении font-size заголовка h1, интерлиньяж изменялся бы вслед за ним. Жестко заданный line-height означает, что если вы захотите модифицировать h1, вероятно придете к такому коду:

CSS

  h1 {
      font-size: 24px;
      line-height: 32px;
      }
  
  /**
  * Main site 'h1'
  */
  .site-title{
      font-size: 36px;
      line-height: 48px;
      }
  

В этом примере мы постоянно должны прибегать к заданию фиксированного line-height так как первоначальный интерлиньяж недостаточно гибко задан. С безразмерным и/или относительным line-height нам было бы нужно только задать размер шрифта:

CSS

  h1 {
      font-size: 24px;
      line-height: 1.333;
      }
  
  /**
  * Main site 'h1'
  */
  .site-title{
      font-size: 36px;
      }
  

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

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

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

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

👊 Грубая сила 

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

CSS

  .foo {
      margin-left: -3px;
      position: relative;
      z-index: 99999;
      height: 59px;
      float: left;
      }
  

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

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

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

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

💣 Опасные селекторы 

Опасный селектор — это селектор с очень широкой специфичностью. Вот наиболее ясный и понятный пример опасного селектора:

CSS

  div {
      background-color: #ffc;
      padding: 1em;
      }
  

Любой разработчик завопит в ужасе увидев подобное. С какой стати вы решили подвергнуть ковровой бомбардировке каждый div на вашем сайте? Хороший вопрос, так и все же, почему кому-то может понадобится, скажем, вот такой селектор: aside ? Вот такой: header или такой: ul ? У подобных селекторов очень и очень обобщенная специфичность, что в конечном счете приводит нас к необходимости переопределять CSS-правила, о чем уже говорилось выше.

Давайте более подробно остановимся на примере с header

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

CSS

  header {
      padding: 1em;
      background-color: #BADA55;
      color: #fff;
      margin-bottom: 20px;
      }
  

… тогда это уже не очень хорошо. Элемент header не означает шапку сайта, в соответствии со спецификацией header может быть использован многократно на одной странице во многих контекстах. Шапка сайта должна оформляться, к примеру, с помощью такого селектора: .site-header .

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

Убедитесь, что у ваших селекторов адекватная специфичность.

Обратите внимание на следующий код:

CSS

  ul {
      font-weight: bold;
      }
  
  header .media {
      float: left;
      }
  

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

☢️ Необдуманное применение !important 

!important — это нормально и это один из важнейших инструментов. Однако использовать !important следует только в определенных ситуациях.

!important нужно использовать не бездумно, для повсеместной отмены чего угодно, — а с умом и осознанно.

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

Например, вы уверены в том, что хотите, чтобы ошибки всегда оформлялись красным, в этом случае подобное правило более чем нормально:

CSS

  .error-text {
      color: #c00 !important;
      }
  

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

В данном случае мы добавляем !important с умом, осознанно, потому что хотим, чтобы ошибки всегда выделялись красным.

Применять !important плохо там, где это делается опрометчиво, то есть когда эта декларация используется, чтобы избежать какой-то специфической проблемы, не до конца в ней разобравшись. Это необдуманное использование !important и это плохо.

Бездумное использование !important — только еще большее запутывание, обусловленное плохо-сформированным CSS. Оно не решает никаких проблем, а лишь устраняет симптомы. Проблемы все еще существуют, но теперь, с добавлением слоя с большей специфичностью, может потребоваться еще большая специфичность для перекрытия существующей.

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

🔑 ID-селекторы 

ID-селекторы довольно специфичная тема для меня и для больших команд. Я уже писал ранее о том, что использование ID — плохая практика из-за их высокого приоритета. Их не имеет смысла никому использовать и они никогда не должны применяться в CSS. Используйте ID в HTML для обозначения фрагментов кода, а также для навешивания JavaScript-логики, но в CSS — никогда.

Причины просты:

Если все вышеперечисленное не сможет убедить вас не использовать ID, тогда уж я не знаю что сможет.

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

Небольшое упражнение: попробуйте элегантно решить эту проблему. Подсказка: это не элегантно, так же как и это.

🎀 Малосодержательные имена классов 

Малосодержательное — это имя класса, которое недостаточно конкретно описывает его назначение. Вообразите класс .card. Что он делает?

Это имя класса очень неконкретное. Недостаточно информативные имена классов это очень плохо по двум главным причинам:

Первая причина проста. Что .card означает? Что оформляет? Карточку в системе управления проектами Trello? Класс, с помощью которого вы оформляете игральные карты на сайте игры в покер? Или это имя относится к изображению кредитной карты? Очень трудно понять из подобного, бедного смыслом, имени класса. Представим, что это имя означает кредитную карту. Тогда этот класс был бы гораздо лучше, если его назвать .credit-card-image . Намного длиннее, да, но и намного лучше, черт побери!

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

Этого можно избежать используя более содержательные имена классов. Такие классы как: .card, .user и подобные им слишком неинформативны, что затрудняет быстрое понимание их назначения, а также легко может вызвать случайное повторное использование/переопределение.

🏁 Заключение 

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

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

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