Рефакторинг CSS: Стратегия, тестирование и сопровождение
После анализа CSS и его слабых мест, а также после того, как руководство дало зелёный свет
рефакторингу проекта, пора приступать к работе. Команде необходимо согласовать внутренние стандарты кода и лучшие практики, спланировать стратегию рефакторинга и наметить индивидуальные задачи. Необходимо создать набор визуальных регрессионных тестов и план сопровождения для внедрения новых стандартов и лучших практик в будущем.
В первой части мы рассмотрели побочные эффекты некачественной кодовой базы CSS для конечных пользователей, команд разработчиков и руководства. Сопровождение, расширение и работа с некачественной кодовой базой затруднены и часто требуют дополнительных затрат времени и ресурсов. Прежде чем выносить предложение о рефакторинге на рассмотрение руководства и заинтересованных сторон, полезно подкрепить это предложение какими-либо достоверными данными о состоянии кодовой базы — не только для того, чтобы убедить руководство, но и для того, чтобы иметь измеримую цель процесса рефакторинга.
Предположим, что руководство одобрило рефакторинг проекта CSS. Команда разработчиков проанализировала и выявила слабые места в кодовой базе CSS и определила целевые задачи рефакторинга (размер файла, специфика, определения цветов и т.д.). В этой статье мы подробно рассмотрим сам процесс рефакторинга, стратегию инкрементного рефакторинга, визуальное регрессионное тестирование и сопровождение отрефакторенной кодовой базы.
Подготовка и планирование
Перед началом процесса рефакторинга команде необходимо проанализировать проблемы кодовой базы и данные аудита состояния CSS (размер CSS-файла, сложность селекторов, дублирование свойств и деклараций и т.д.) и обсудить отдельные проблемы, как к ним подходить и какие трудности ожидать. Эти проблемы и трудности специфичны для кодовой базы и могут затруднить процесс рефакторинга или тестирования. Как уже говорилось в предыдущей статье, важно установить внутренние правила и стандарты кодовой базы и документировать их, чтобы убедиться, что команда находится на одной волне и имеет более единый и стандартизированный подход к рефакторингу.
Кроме того, команде необходимо наметить отдельные задачи по рефакторингу и установить сроки завершения рефакторинга проекта, учитывая текущие задачи и следя за тем, чтобы рефакторинг проекта не помешал команде решать срочные задачи или работать над запланированными функциями. Перед тем как оценить продолжительность и объём работ по рефакторингу проекта, команде необходимо проконсультироваться с руководством о краткосрочных планах и скорректировать свои оценки и объём работ с учётом запланированных функций и процедур регулярного сопровождения.
В отличие от обычных исправлений ошибок и функций, процесс рефакторинга практически не даёт видимых и измеримых изменений во фронтэнде, и руководство не может самостоятельно отслеживать ход работ. Важно наладить прозрачную коммуникацию, чтобы руководство и другие заинтересованные стороны проекта были в курсе хода и результатов рефакторинга. Для эффективной коммуникации и взаимодействия между членами команды и руководством, а также в качестве быстрого и простого инструмента управления задачами можно использовать онлайновые средства совместной работы, такие как Miro или MURAL.
Кристоф Рейнартц отметил важность прозрачности и чёткой коммуникации во время работы команды trivago над рефакторингом CSS проекта.
Единственным нашим оружием была коммуникация и чёткое представление всей компании о ходе работ и возникающих проблемах. Мы решили создать очень простую доску Kanban, организовали проектный стендап и проектный канал Slack, а также держали руководство и компанию в курсе событий через нашу внутреннюю социальную сеть.
Наиболее важным элементом планирования процесса рефакторинга является сохранение как можно меньшего объёма задач рефакторинга CSS. Это делает задачи более управляемыми, их легче тестировать и интегрировать.
Гарри Робертс называет эти задачи туннелями рефакторинга
. Например, рефакторинг всей кодовой базы на соответствие методологии БЭМ за один раз может привести к значительному улучшению кодовой базы и процесса разработки. На первый взгляд это может показаться простой задачей поиска и замены. Однако эта задача затрагивает все элементы на каждой странице (высокий масштаб), и команда не может сразу увидеть свет в конце тоннеля
; в процессе работы может сломаться множество вещей, неожиданные проблемы могут замедлить прогресс, и никто не может сказать, когда задача будет завершена. Команда может потратить несколько дней или недель на работу над ней и рискует натолкнуться на стену, накопить дополнительный технический долг или сделать кодовую базу ещё менее здоровой. В итоге команда либо отказывается от задачи, либо начинает все сначала, теряя при этом время и ресурсы.
В отличие от этого, улучшение CSS только навигационного компонента — задача меньшего масштаба и гораздо более управляемая и выполнимая. Кроме того, её легче тестировать и проверять. Эта задача может быть решена за несколько дней. Даже при наличии потенциальных проблем и трудностей, замедляющих выполнение задачи, существует высокая вероятность успеха. В процессе работы над задачей команда всегда может "видеть конец тоннеля", поскольку знает, что задача будет решена, когда будет завершён рефакторинг компонента и все проблемы, связанные с ним, будут устранены.
Наконец, команде необходимо согласовать стратегию рефакторинга и метод регрессионного тестирования. Именно здесь процесс рефакторинга становится сложным — рефакторинг должен быть максимально оптимизирован и не должен вносить никаких регрессий или ошибок.
Давайте рассмотрим одну из наиболее эффективных стратегий рефакторинга CSS и посмотрим, как можно использовать её для быстрого и эффективного улучшения кодовой базы.
Стратегия инкрементного рефакторинга
Рефакторинг — это сложный процесс, который гораздо сложнее, чем простое удаление устаревшего кода и замена его отрефакторенным. Кроме того, необходимо интегрировать отрефакторенную кодовую базу с унаследованной, избежать регрессий, случайных удалений кода, предотвратить конфликты таблиц стилей и т.д. Чтобы избежать этих проблем, я бы рекомендовал использовать стратегию инкрементного (или гранулярного) рефакторинга.
На мой взгляд, это одна из самых безопасных, логичных и рекомендуемых стратегий рефакторинга CSS, с которыми я сталкивался до сих пор. Гарри Робертс описал эту стратегию в 2017 г., и с тех пор, как я впервые услышал о ней, она стала моей личной стратегией рефакторинга CSS.
Давайте рассмотрим эту стратегию пошагово.
Шаг 1. Выбрать компонент и разработать его изолированно
Эта стратегия основана на том, что отдельные задачи имеют небольшой объём, а значит, проводить рефакторинг проекта нужно компонент за компонентом. Рекомендуется начинать с маломасштабных задач (отдельные компоненты), а затем переходить к более масштабным задачам (глобальные стили).
В зависимости от структуры проекта и CSS-селекторов отдельные стили компонентов состоят из комбинации стилей компонентов (классов) и глобальных стилей (широко распространённых стилей элементов). Как компонентные, так и глобальные стили могут быть источником проблем в кодовой базе и могут потребовать рефакторинга.
Рассмотрим наиболее распространённые проблемы кодовой базы CSS, которые могут затрагивать отдельный компонент. Компонентные (классовые) селекторы могут быть слишком сложными, их трудно повторно использовать, или они могут иметь высокую специфичность и принудительно применять конкретную разметку или структуру. Глобальные (элементные) селекторы могут быть жадными и пропускать нежелательные стили в несколько компонентов, что необходимо устранить с помощью высокоспецифичных селекторов компонентов.
Выбрав компонент для рефакторинга (задача более низкого уровня), необходимо разработать его в изолированной среде, вдали от устаревшего кода, его слабых мест и конфликтующих селекторов. Это также хорошая возможность улучшить HTML-разметку, убрать лишние вложенности, использовать более удачные имена классов CSS, применять атрибуты ARIA
и т.д.
Для этого необязательно настраивать целую систему сборки, можно даже использовать CodePen для пересборки отдельных компонентов. Чтобы избежать конфликтов со старыми именами классов и более чётко отделить отрефакторенный код от легаси кода, мы будем использовать префикс .rf-
в селекторах имён классов CSS.
Шаг 2. Слияние с легаси кодовой базой и исправление ошибок
После того как мы закончили перестройку компонента в изолированной среде, необходимо заменить устаревшую HTML-разметку на отрефакторенную (новая структура HTML, имена классов, атрибуты и т.д.) и добавить отрефакторенный CSS компонента наряду с устаревшим CSS.
Мы не хотим действовать слишком поспешно и сразу удалять устаревшие стили. Внося слишком много изменений одновременно, мы потеряем возможность отслеживать проблемы, которые могут возникнуть из-за конфликтующих кодовых баз и многочисленных изменений. Пока что давайте заменим разметку и добавим отрефакторенный CSS в существующую кодовую базу и посмотрим, что получится. Следует помнить, что для предотвращения конфликтов со старой кодовой базой в именах отрефакторенных CSS классов должен присутствовать префикс rf-
.
Устаревшие CSS и глобальные стили компонента могут вызывать неожиданные побочные эффекты и передавать нежелательные стили в отрефакторенный компонент. В отрефакторенной кодовой базе может отсутствовать ошибочный CSS, необходимый для устранения этих побочных эффектов. Поскольку эти стили имеют более широкий охват и могут повлиять на другие компоненты, мы не можем просто отредактировать проблемный код напрямую. Для решения этих конфликтов необходимо использовать другой подход.
Нам необходимо создать отдельный CSS-файл, который мы можем назвать overrides.css
или defend.css
и который будет содержать хаки, высокоспецифичный код, борющийся с нежелательными утечками стилей из старой кодовой базы.
overrides.css
, который будет содержать специфические селекторы, обеспечивающие работу отрефакторенного кода с легаси кодом. Это временный файл, который будет удалён после удаления легаси кода. Пока же добавьте специфические переопределения стилей, чтобы отменить стили, применяемые в унаследованных стилях, и проверьте, все ли работает так, как ожидалось.
Если вы заметили какие-либо проблемы, проверьте, не отсутствуют ли в отрефакторенном компоненте какие-либо стили, вернувшись в изолированную среду, или не просачиваются ли в компонент какие-либо другие стили, которые необходимо переопределить. Если после добавления этих переопределений компонент выглядит и работает так, как ожидалось, удалите устаревший код отрефакторенного компонента и проверьте, не возникнут ли какие-либо проблемы. Удалите из файла overrides.css
неаккуратный код и проверьте его снова.
В зависимости от конкретного случая, вероятно, не удастся сразу удалить все переопределения. Например, если проблема кроется в глобальном селекторе элемента, который передаёт стили в другие компоненты, также нуждающиеся в рефакторинге. В таких случаях мы не будем рисковать, расширяя область действия задачи и запроса на исправление, а лучше подождём, пока не закончим рефакторинг всех компонентов, и приступим к решению задач, требующих больших усилий, после устранения той же зависимости от стилей во всех остальных компонентах.
В некотором смысле файл overrides.css
можно рассматривать как временный список TODO для рефакторинга жадных и широкомасштабных селекторов элементов. Также следует рассмотреть возможность обновления списка задач, чтобы включить в него новые обнаруженные проблемы. Обязательно добавляйте полезные комментарии в файл overrides.css
, чтобы другие члены команды были в курсе происходящего и сразу понимали, почему и в ответ на какой селектор было применено то или иное переопределение.
/* overrides.css */
/* Сбрасывает размеры, навязанные ".sidebar > div" в "sidebar.css" */
.sidebar > .card {
min-width: 0;
}
/* Сбрасывает размер шрифта, навязанный ".hero-container" в "hero.css "*/
.card {
font-size: 18px;
Шаг 3. Тестирование, слияние и повторение
После того как отрефакторенный компонент будет успешно интегрирован с традиционной кодовой базой, создайте Pull Request и запустите автоматизированное визуальное регрессионное тестирование, чтобы выявить все незамеченные проблемы и устранить их перед слиянием в одну из основных веток git. Визуальное регрессионное тестирование можно рассматривать как последнюю линию обороны перед слиянием отдельных запросов на выгрузку. Более подробно о визуальном регрессионном тестировании мы расскажем в одном из следующих разделов этой статьи.
Теперь промойте и повторите эти три шага, пока кодовая база не будет отрефакторена, а файл overrides.css
не станет пустым и его можно будет безопасно удалить.
Шаг 4. Переход от компонентов к глобальным стилям
Предположим, что мы провели рефакторинг всех отдельных компонентов с малой распространённостью, и в файле overrides.css
остались только глобальные селекторы элементов с широким охватом. Это вполне реальный случай, поскольку многие проблемы с CSS возникают из-за того, что широко распространённые селекторы элементов просачиваются через стили в несколько компонентов.
Занявшись сначала отдельными компонентами и оградив их от побочных эффектов глобального CSS с помощью файла overrides.css
, мы сделали эти более сложные задачи более управляемыми и менее рискованными. Мы можем более безопасно, чем раньше, перейти к рефакторингу глобальных CSS-стилей и удалить дублирующиеся стили из отдельных компонентов, заменив их общими стилями и утилитами для элементов — кнопок, ссылок, изображений, контейнеров, входов, сеток и т.д. Таким образом, мы постепенно удалим код из нашего импровизированного TODO-файла overrides.css
и дублирующийся код, повторяющийся в нескольких компонентах.
Применим те же три шага стратегии инкрементального рефакторинга, начав с разработки и тестирования стилей в изоляции.
Далее необходимо добавить отрефакторенные глобальные стили в кодовую базу. Мы можем столкнуться с теми же проблемами при объединении двух кодовых баз и добавить необходимые переопределения в файле overrides.css
. Однако в этот раз мы можем ожидать, что, постепенно удаляя старые стили, мы также сможем постепенно удалить переопределения, которые мы ввели для борьбы с этими нежелательными побочными эффектами.
Недостатком изолированной разработки компонентов могут быть стили элементов, которые используются совместно несколькими компонентами, — элементы руководства по стилю, такие как кнопки, входы, заголовки и т.д. При разработке этих элементов в изоляции от старой кодовой базы мы не имеем доступа к старой версии руководства по стилю. Кроме того, мы не хотим создавать зависимости между старой и отрефакторенной кодовой базой.
Поэтому в дальнейшем проще удалить дублирующиеся блоки кода и перенести эти стили в отдельные, более общие компоненты и селекторы руководства по стилю. Это позволяет устранить эти изменения в самом конце и с меньшим риском, поскольку мы работаем с гораздо более здоровой и последовательной кодовой базой, а не с грязной, непоследовательной и глючной старой кодовой базой. Конечно, непредвиденные побочные эффекты и ошибки все равно могут возникнуть, и их следует выявлять с помощью средств визуального регрессионного тестирования, о которых мы расскажем в одном из следующих разделов статьи.
Когда был завершён рефакторинг кодовой базы и мы удалили все временные TODO-элементы из файла overrides.css
, мы можем смело удалить его и получить отрефакторенную и улучшенную CSS-кодовую базу.
Пример инкрементного рефакторинга CSS
Применим стратегию инкрементного рефакторинга для рефакторинга простой страницы, состоящей из элемента заголовка и двух компонентов карточки в компоненте сетки. Каждый элемент карточки состоит из изображения, заголовка, подзаголовка, описания и кнопки и размещён в двух колоночной сетке с горизонтальным и вертикальным интервалом.
Как видите, мы имеем неоптимальную кодовую базу CSS с различными проблемами специфичности, переопределениями, дублированием кода и некоторыми случаями отмены стилей.
h1, h2 {
margin-top: 0;
margin-bottom: 0.75em;
line-height: 1.3;
font-size: 2.5em;
font-family: serif;
}
/* ... */
.card h2 {
font-family: Helvetica, Arial, sans-serif;
margin-bottom: 0.5em;
line-height: initial;
font-size: 1.5em;
}
Компонент .card
также использует селекторы высокой специфичности, что обеспечивает соблюдение определённой структуры HTML и позволяет стилям просачиваться в другие элементы внутри компонентов card
.
/* Элемент должен следовать определённой структуре HTML, чтобы к нему были применены эти стили */
.card h2 > small {
/* ... */
}
/* Эти стили будут проникать во все элементы div в компоненте card */
.card div {
padding: 2em 1.5em 1em;
}
/* Эти стили будут проникать во все элементы p в компоненте card */
.card p {
font-size: 0.9em;
margin-top: 0;
}
Мы начнём с компонентов с наименьшим охватом и самых верхних дочерних компонентов, поэтому рефакторингу подвергнем компонент card
, который является самым верхним дочерним компонентом страницы, т.е. компонент card
является дочерним компонентом компонента grid
. Мы будем разрабатывать компонент card
изолированно и использовать согласованные стандарты и лучшие практики.
Используя БЭМ, мы заменим жадные селекторы с широким охватом на простые селекторы классов и воспользуемся пользовательскими свойствами CSS для замены непоследовательных, жёстко закодированных значений цвета в строке. Мы также добавим несколько CSS для разработки компонента, которые не будем копировать в существующую кодовую базу.
Мы используем префикс rf-
для новых CSS-классов, чтобы избежать конфликтов имён классов и отличить отрефакторенные стили от устаревших для лучшей организации кода и упрощения отладки. Это также позволит нам следить за ходом рефакторинга.
.rf-card {
color: var(--color-text);
background: var(--color-background);
}
.rf-card__figure {
margin: 0;
}
.rf-card__title {
line-height: 1.3;
margin-top: 0;
margin-bottom: 0.5em;
}
Мы собираемся заменить устаревшую разметку на отрефакторенную и добавить отрефакторенные стили в кодовую базу CSS. Мы не будем пока удалять старые стили. Необходимо проверить, не просочились ли в отрефакторенный компонент стили из других унаследованных селекторов.
Из-за проблем с глобальными селекторами в обновлённый компонент просочились нежелательные переопределения стилей — были сброшены свойства шрифта заголовка и изменён размер шрифта внутри компонента карточки.
.grid {
/* ... */
font-size: 24px;
}
h1, h2 {
/* ... */
font-size: 2.5em;
font-family: Georgia, "Times New Roman", Times, serif;
}
Нам необходимо добавить переопределения стиля в файл overrides.css
, чтобы убрать нежелательные побочные эффекты от других селекторов. Мы также собираемся прокомментировать каждое переопределение, чтобы знать, какой селектор вызвал проблему.
/* Предотвращает переопределение размера шрифта .grid */
.rf-card {
font-size: 16px;
}
/* Предотвращает переопределение размера шрифта h1, h2 */
.rf-card__title {
font-family: Helvetica, Arial, sans-serif;
font-size: 1.5em;
}
Теперь мы знаем, что в какой-то момент нам придётся провести рефакторинг компонента .grid
и глобальный селектор h1, h2
. Именно поэтому мы можем рассматривать его как список TODO — эти просочившиеся стили могут вызвать проблемы в других компонентах, они объективно ошибочны и применяют стили, которые сбрасываются в большинстве случаев использования.
Давайте удалим из проекта легаси стили .card
и посмотрим, все ли в порядке. Мы можем проверить, можно ли удалить какие-либо стили из файла overrides.css
, однако сразу узнаем, что ни одно из переопределений не связано со стилями легаси компонента card
, а относится к другим компонентам и селекторам элементов.
Теперь, когда компонент завершён рефакторинг card
, давайте проверим наш импровизированный список TODO и посмотрим, что делать дальше. У нас есть выбор между:
- Рефакторинг компонента сетки — меньшая область охвата (короткий туннель),
- Рефакторинг глобальных стилей — более высокая область охвата (более длинный туннель).
Мы возьмём компонент с наименьшим охватом, в данном случае компонент сетки, и применим тот же подход. Начнём с изолированной разработки компонента сетки. Мы можем использовать разметку компонента card
для тестирования, стили компонента card
не будут влиять на сетку и не помогут нам в разработке компонента, поэтому мы можем не использовать их в изолированном компоненте для более простой CSS-разметки.
Заменим HTML-разметку сетки отрефакторенной разметкой, добавим отрефакторенные стили в кодовую базу CSS и проверим, нужно ли добавлять какие-либо стили в файл overrides.css
, чтобы устранить конфликты таблиц стилей или утечку стилей.
Мы видим, что новых проблем не возникло, поэтому можно приступать к удалению старых стилей .grid
. Также проверим, содержит ли файл overrides.css
какие-либо стили, применяемые для устаревшего селектора .grid
.
/* Сбросить переопределение размера шрифта .grid */
.rf-card {
font-size: 16px;
}
Именно поэтому полезно документировать правила переопределения. Мы можем смело удалить этот селектор и перейти к последнему пункту нашего импровизированного списка TODO — селекторам элементов заголовков. Мы снова проделаем те же шаги — создадим отрефакторенную разметку в изоляции, заменим HTML-разметку и добавим отрефакторенные стили в таблицу стилей.
<h1 class="rf-title rf-title--size-regular rf-title--spacing-regular">Featured galleries</h1>
.rf-title {
font-family: Georgia, "Times New Roman", Times, serif;
}
.rf-title--size-regular {
line-height: 1.3;
font-size: 2.5em;
}
.rf-title--spacing-regular {
margin-top: 0;
margin-bottom: 0.75em;
}
Мы проверим, есть ли какие-либо проблемы, и подтвердим, что обновлённые разметка и таблица стилей не привнесли никаких новых проблем, а также удалим из таблицы стилей селектор устаревших элементов h1, h2
. Наконец, проверим файл overrides.css
и удалим стили, связанные с селектором устаревших элементов.
Теперь файл overrides.css
пуст, и мы провели рефакторинг компонентов card
, grid
и title
в нашем проекте. Наша кодовая база стала гораздо более здоровой и последовательной по сравнению с исходной точкой — мы можем добавлять элементы в компонент сетки и новые варианты заголовков без необходимости отменять утёкшие стили.
Тем не менее есть несколько изменений, которые можно сделать для улучшения нашей кодовой базы. Поскольку мы разрабатывали компоненты изолированно, то, вероятно, несколько раз перестраивали одни и те же компоненты руководства по стилю и создавали дублированный код. Например, button
— это компонент руководства по стилям, а мы привязали эти стили к компоненту card
.
/* Отрефакторенные стили кнопок, привязанные к компоненту */
.rf-card__link {
color: var(--color-text-negative);
background-color: var(--color-cta);
padding: 1em;
display: flex;
justify-content: center;
text-decoration: none;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
}
Если любой другой компонент использует те же стили кнопок, это означает, что мы будем писать те же стили каждый раз, когда разрабатываем их изолированно. Мы можем провести рефакторинг этих дубликатов в отдельный компонент .button
и заменить дублирующиеся стили на общие стили руководства по стилю. Однако у нас уже есть унаследованный селектор .button
, которому необходимо провести рефакторинг, поэтому мы также можем удалить легаси селектор button
.
Несмотря на то, что мы переходим к рефакторингу элементов с более высокой степенью охвата, кодовая база по сравнению с начальной точкой стала гораздо более здоровой и последовательной, поэтому риск ниже, а задача гораздо более выполнима. Кроме того, мы можем не беспокоиться, что изменения в самых верхних дочерних компонентах отменят любые изменения в общем селекторе.
/* Некорректные легаси стили кнопок */
.button {
border: 0;
display: block;
max-width: 200px !important;
text-align: center;
margin: 1em auto;
padding: 1em;
text-transform: uppercase;
letter-spacing: 0.05em;
курсор: указатель;
font-weight: bold;
text-decoration: none;
}
.cta {
max-width: none !important;
margin-bottom: 0;
color: #fff;
background-color: darkred;
margin-top: 1em;
}
Мы можем использовать тот же инкрементный подход к перестройке компонента кнопки в отдельности, обновить разметку и добавить отрефакторенные стили в таблицу стилей. Проведём быструю проверку на наличие конфликтов и ошибок в таблице стилей, заметим, что ничего не изменилось, и удалим устаревшую разметку кнопки и стили кнопки, относящиеся к компоненту.
.rf-button {
display: flex;
justify-content: center;
text-decoration: none;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
}
.rf-button--regular {
padding: 1em;
}
.rf-button--cta {
color: var(--color-text-negative);
background-color: var(--color-cta);
}
/* Прежде - стили кнопок, привязанные к компоненту карточки */
<a class="rf-card__link" href="#">View gallery</a>
/* После - Общие стили компонента кнопки */
<a class="rf-button rf-button--regular rf-button--cta" href="#">View gallery</a>
После завершения рефакторинга мы можем использовать метод поиска и замены для очистки кодовой базы от префиксов rf-
и рассматривать существующую кодовую базу как новый стандарт. Таким образом, при расширении кодовой базы не возникнет несоответствий в именовании или принудительного именования префиксов rf-
, что может вызвать проблемы при последующем рефакторинге.
Тестирование и предотвращение регрессий
Независимо от объёма задач рефакторинга и того, насколько хорошо команда следует шагам стратегии инкрементального рефакторинга, ошибки и регрессии могут возникнуть. В идеале мы хотим избежать возникновения проблем и регрессий в кодовой базе — конфликтующих стилей, отсутствующих стилей, неправильной разметки, утечки стилей из других элементов и т.д.
Использование автоматизированных средств визуального регрессионного тестирования, таких как Percy или Chromatic, на уровне Pull Request позволяет ускорить тестирование, быстро и своевременно устранять регрессии и быть уверенным в том, что ошибки не попадут на живой
сайт. Эти инструменты позволяют делать скриншоты отдельных страниц и компонентов, сравнивать изменения в стиле и компоновке и уведомлять разработчиков о любых визуальных изменениях, которые могут произойти из-за изменений в разметке или CSS.
Поскольку в обычных случаях процесс рефакторинга CSS не должен приводить к каким-либо визуальным изменениям, а также в зависимости от задачи и проблем в унаследованной кодовой базе, процесс визуального регрессионного тестирования может быть простым — проверить, произошли ли вообще какие-либо визуальные изменения, и проверить, были ли эти изменения преднамеренными или непреднамеренными.
В зависимости от проекта инструменты тестирования необязательно должны быть сложными или изощрёнными, чтобы быть эффективными. Во время рефакторинга кодовой базы CSS Института Сандэнса команда разработчиков использовала простую статическую страницу руководства по стилю, созданную на Jekyll, для тестирования отрефакторенных компонентов.
Одним из непредвиденных последствий выполнения рефакторинга в абстракции на экземпляре Jekyll стало то, что теперь мы можем публиковать его на страницах Github в качестве живого руководства по стилю (убран из публичного доступа или удалён). Это стало бесценным ресурсом для нашей команды разработчиков и для внешних поставщиков.
После того как задачи по рефакторингу CSS будут решены и отрефакторенный код будет готов к выпуску, команда может также рассмотреть возможность проведения A/B-тестирования для проверки влияния отрефакторенной кодовой базы на пользователей. Например, если целью рефакторинга было уменьшение общего размера CSS-файла, то A/B-тестирование может дать значительные улучшения для мобильных пользователей, и эти результаты также могут быть полезны для заинтересованных сторон и руководства проекта. Именно так команда Trivago подошла к развёртыванию своего масштабного проекта по рефакторингу.
(...) мы смогли выпустить техническую миграцию в виде A/B-теста. Мы тестировали миграцию в течение одной недели, получили положительные результаты на мобильных устройствах, где mobile-first оправдал себя, и приняли миграцию всего через четыре недели.
Отслеживание хода выполнения рефакторинга
Kanban board, GitHub issues, GitHub project board и стандартные средства управления проектами могут отлично справляться с отслеживанием хода рефакторинга. Однако в зависимости от инструментов и организации проекта, может быть сложно оценить прогресс на основе каждой страницы или быстро проверить, какие компоненты нуждаются в рефакторинге.
Именно здесь на помощь приходят наши CSS-классы с префиксом .rf
. Гарри Робертс подробно рассказал о преимуществах использования префикса. В итоге эти классы не только позволяют разработчикам чётко отделить переработанную CSS-кодовую базу от устаревшей, но и быстро показать прогресс менеджерам проекта и другим заинтересованным лицам на каждой странице.
Например, руководство может решить проверить эффект от рефакторинга на ранней стадии, развернув только отрефакторенный код главной страницы, и захочет узнать, когда компоненты главной страницы будут отрефакторены и готовы к A/B-тестированию.
Вместо того чтобы тратить время на сравнение компонентов домашней страницы с имеющимися задачами на доске Kanban, разработчики могут просто временно добавить следующие стили для выделения отрефакторенных компонентов, имеющих префикс rf-
в имени класса, и компонентов, нуждающихся в рефакторинге. Таким образом, они могут реорганизовать задачи и определить приоритетность рефакторинга компонентов домашней страницы.
/* Выделяет все отрефакторенные компоненты */
[class*="rf-"] {
контур: 5px solid green;
}
/* Выделяет все компоненты, которые не подвергались рефакторингу */
body *:not([class]) {
outline: 5px solid red;
}
Сопровождение отрефакторенной кодовой базы
После завершения рефакторинга команда разработчиков должна позаботиться о поддержании здоровья кодовой базы в обозримом будущем — будут разрабатываться новые функции, некоторые из них могут быть даже поспешными и привести к образованию технического долга, будут также разрабатываться различные исправления ошибок и т.д. В общем, команда разработчиков должна следить за тем, чтобы здоровье кодовой базы оставалось стабильным до тех пор, пока она за неё отвечает.
Технический долг, который может привести к потенциально ошибочному CSS-коду, должен быть выделен, задокументирован и реализован в отдельном CSS-файле, который часто называют shame.css
.
Важно документировать правила и лучшие практики, которые были установлены и применены в ходе рефакторинга проекта. Наличие этих правил в письменном виде позволяет проводить стандартизированные обзоры кода, ускорять процесс включения в проект новых членов команды, упрощать передачу проекта и т.д.
Некоторые из правил и лучших практик также могут быть реализованы и задокументированы с помощью автоматизированных средств проверки кода, таких как stylelint. Андрей Ситник, автор таких широко используемых инструментов разработки CSS, как PostCSS и Autoprefixer, отмечает, что инструменты автоматического линтинга могут сделать обзоры кода и внедрение более простыми и менее напряжёнными.
Однако автоматическая проверка кода — не единственная причина для внедрения Stylelint в ваш проект. Она может быть очень полезна при принятии новых разработчиков в команду: много времени (и нервов!) тратится на проверку кода до тех пор, пока младшие разработчики не будут полностью ознакомлены с принятыми стандартами и лучшими практиками работы с кодом. Stylelint может сделать этот процесс гораздо менее напряжённым для всех.
Кроме того, команда может создать шаблон Pull Request и включить в него контрольный список стандартов и лучших практик, а также ссылку на документ с правилами работы с кодом проекта, чтобы разработчики, подающие Pull Request, могли самостоятельно проверить код и убедиться, что он соответствует согласованным стандартам и лучшим практикам.
Заключение
Стратегия инкрементного рефакторинга является одним из наиболее безопасных и рекомендуемых подходов к рефакторингу CSS. Команда разработчиков должна рефакторить кодовую базу компонент за компонентом, чтобы задачи имели небольшой объём и были управляемыми. Отдельные компоненты должны разрабатываться изолированно — вдали от ошибочного кода — и затем объединяться с существующей кодовой базой. Проблемы, которые могут возникнуть из-за конфликтующих кодовых баз, могут быть решены путём добавления временного CSS-файла, содержащего все необходимые переопределения для устранения конфликтов в стилях CSS. После этого можно удалить устаревший код целевого компонента, и этот процесс продолжается до тех пор, пока не будет закончен рефакторинг всех компонентов и пока временный CSS-файл, содержащий переопределения, не станет пустым.
Такие инструменты визуального регрессионного тестирования, как Percy и Chromatic, могут использоваться для тестирования и выявления любых регрессий и нежелательных изменений на уровне Pull Request, чтобы разработчики могли устранить эти проблемы до того, как отрефакторенный код будет развернут на живом сайте.
Разработчики могут использовать A/B-тестирование и средства мониторинга, чтобы убедиться, что рефакторинг не оказывает негативного влияния на производительность и пользовательский опыт, прежде чем окончательно запустить отрефакторенный проект на живом
сайте. Разработчикам также необходимо убедиться, что согласованные стандарты и лучшие практики используются в проекте и в дальнейшем для поддержания здоровья и качества кодовой базы.