19 принципов разработки по БЭМ, или что должен знать каждый разработчик библиотек

БЭМ набирает популярность и становится актуальнее — например, недавно Google выпустил новую библиотеку блоков под названием Material Design Lite, реализованную по БЭМ-методологии. Команда БЭМ тоже не сидела без дела — мы выпустили новую версию библиотеки bem-components, на базе которой построены сайты и проекты не только Яндекса, но и других разработчиков.

Эти события натолкнули нас на мысль ещё раз вспомнить и рассказать вам, как сформировались принципы разработки библиотек в БЭМ-методологии. Надеемся, что многим это будет интересно и полезно. Итак, поехали.

image

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

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

Немного истории, или о том, как мы ходили по граблям


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

Но на практике не всегда получается так, как хочется…

Потерять себя, угождая всем


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

image

Мы начали разрабатывать универсальную библиотеку. Нам хотелось создать общие блоки для всех проектов настолько продуманными, чтобы они соответствовали требованиям каждого отдельного проекта. Мы учли все случаи использования блока (до самых редких и едва ли возможных) и попытались сделать блоки максимально удобными для пользователей. Так код каждого блока раздулся до неприличных размеров, а 90% случаев, которые мы предусмотрели, никем не использовались. Несмотря на это, нам приходилось все равно поддерживать весь этот код. Исправление любой ошибки затягивалось.

Скорость в приоритете


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

Больше прав пользователям, меньше — разработчикам


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

Нюансы стилевого оформления


Так все сайты, которые пользовались нашей библиотекой, имели одинаковый дизайн, мы не стали выносить стилевое оформление из ядра библиотеки. Чтобы реализовать нашу тему, мы сбрасывали общие стили и накладывали свои.

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

Злоупотребление значениями по умолчанию


Мы любили использовать значения по умолчанию для модификаторов. С одной стороны, это удобно, так как можно не указывать значение и получать рабочий контрол. С другой — значение по умолчанию всё же лишает разработчика возможности влиять на модификатор. То есть существует ограниченный ряд значений. Например, у нас есть кнопка и размеры к ней задаются через модификаторы (s, m, l, xl). Не указав модификатор, мы получали значение по умолчанию — m.

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

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

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

Как формировались принципы


Мы оценили масштабы прошлых бедствий и сделали выводы, собрав весь наш опыт в одной универсальной библиотеке — bem-components, новый релиз которой состоялся совсем недавно. Сейчас мы расскажем об основных трансформациях нашего подхода и о положительном опыте создания библиотек блоков.

Универсальный блок и блок, делающий всё и для всех — не одно и то же


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

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

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

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

Следует также отметить, что мы значительно сузили публичный API, тем самым дали себе больше свободы на изменение блоков, а пользователям — больше уверенности в том, что заявленный API стабилен и не будет часто меняться.

Полный и безоговорочный Open Source


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

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

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

Больше ясности в API


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

Архитектура блока должна продумываться заранее


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

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

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

Принципы разработки БЭМ-библиотек


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

image

1. Открытый исходный код


Разработка библиотеки ведется на GitHub, где доступны все задачи и планы. Любой разработчик может принять участие в работе над библиотекой: создать задачу с пожеланиями для команды или прислать pull request.

2. Простота использования


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

3. Минимализм


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

4. Покрытие тестами


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

5. Единообразие


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

6. Разделение на приватный и публичный API


Приватный API блока не должен использоваться вне этого блока.

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

7. Тонкая настройка пользователем


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

8. Поддержка нескольких тем


Библиотека должна поддерживать несколько тем. Это позволит корректно создавать правила для каждого стиля, а также избежать конфликтов с новыми темами. Сейчас основная тема БЭМ-библиотек — islands, в ней выполнен новый дизайн Яндекса. Реализация нескольких тем внутри одной библиотеки представлена в bem-components, где вместе с islands есть дополнительная тема simple.

9. Явное лучше неявного (в JavaScript API)


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

10. Явные умолчания


Умолчания должны быть явно задекларированы — это внешний API. Любые их изменения препятствуют обратной совместимости. Неявные и нигде не описанные умолчания создают проблемы в поддержке библиотеки.

11. Обработка ошибок


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

12. Предметная область БЭМ


Все, что может выражаться через БЭМ-сущность, должно через нее выражаться.

13. Модификаторы vs. специализированные поля


Модификатор — это заранее известный набор положений или состояний. Специализированное поле — заранее неизвестный набор вариативных значений.

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

14. Иерархия блоков


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

15. Оптимизация на уровне блока


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

16. Автоматизация рутинных процессов


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

17. Мобильные платформы без адаптивной верстки


Библиотека должна поддерживать мобильные платформы. Использовать адаптивную верстку не рекомендуется. Библиотека должна работать во всех браузерах, поддержка которых заявлена: допускаются деградации только стилевого оформления компонентов, но не их функциональности.

18. Доступность


Компоненты библиотеки должны быть доступны для чтения программами экранного доступа (accessibility), но расширять для этого публичный API не следует. Выставление всех необходимых ARIA-атрибутов реализуется на уровне шаблонов и скриптов.

19. Bleeding edge


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

Перечисленные принципы помогают нам развивать БЭМ-библиотеки: все желающие могут участвовать в разработке. Сформулировав для себя основные принципы разработки, нам также удалось разработать внутренние правила контрибъюта в наши библиотеки.

Пример следования принципам — библиотека bem-components

Мы создали библиотеку, руководствуясь перечисленными принципами. bem-components — это основной opensource-продукт команды БЭМ.

Библиотека bem-components содержит 22 готовых контрола. При этом она никак не ограничивает вас от добавления недостающей функциональности.

image

Мы предусмотрели различные способы подключения библиотеки в проект. Обо всех способах подключения читайте в описании библиотеки на нашем сайте.

Начиная с версии 2.3.0, библиотеку bem-components можно использовать по аналогии с Bootstrap — поставка в виде Dist. Теперь вы можете просто скопировать код библиотеки в проект и убедиться на конкретных примерах, как реализованы принципы в каждом блоке.

Поставка библиотеки в виде Dist предоставляет предсобранный CSS- и JavaScript-код и шаблоны. Библиотека подключается ссылками на страницу и не требует сборки.

Самый простой способ начать работать с библиотекой — подключить файлы с CDN. Для этого добавьте элементы <link> и <script> в HTML страницы:

<link rel="stylesheet" href="https://yastatic.net/bem-components/latest/desktop/bem-components.css">
<script src="https://yastatic.net/bem-components/latest/desktop/bem-components.js+bh.js"></script>

Схема подключения файла с CDN: //yastatic.net/название-библиотеки/версия/платформа/имя-файла.
Пример: https://yastatic.net/bem-components/latest/desktop/bem-components.dev.js+bh.js.

Другие способы подключения bem-components в виде Dist, а также подробные способы работы с библиотекой описаны в документации.

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

Удачи вам в написании своих проектов и побольше счастливых пользователей!

P.S. Обязательно пишите нам на форум о том, что понравилось, а что вызвало вопросы.

© Habrahabr.ru