В последние годы PHP динамично развивался с появлением новых версий языка, содержащих ряд новых фич, депрекаций и более строгий синтаксис. Кроме того, библиотеки и фреймворки, такие как Symfony, постоянно претерпевают изменения: каждые полгода появляются новые минорные релизы, а каждые два года - мажорные, наиболее существенные. Для нас в Westwing очень важно регулярно обновлять имеющуюся кодовую базу, чтобы она оставалась работоспособной, безопасной и актуальной. К счастью, в сообществе PHP появилось множество инструментов, способствующих улучшению кодовой базы.
Прежде всего — единый стиль кода
Стиль кода является одним из основных аспектов качества кода, но его проверка и исправление вручную могут занимать много времени и быть утомительными. Человеческий глаз может не заметить все пропущенные пробелы, чрезмерно длинную строку или другие проблемы со стилем кода. По мере добавления или модификации нового кода могут возникать дополнительные проблемы. Поэтому автоматизация проверок и устранения ошибок стиля кода — это простой способ оптимизировать процесс проверки кода и обеспечить единообразное форматирование в команде. Вот почему этот инструмент является обязательным для всех PHP-проектов в Westwing.
Несмотря на то, что многие разработчики используют инструменты форматирования в своих IDE (Integrated Development Environment — интегрированная среда разработки), индивидуальные предпочтения и настройки могут приводить к незначительным различиям при форматировании файлов кода. С помощью таких инструментов для определения стиля кода PHP, как PHP Coding Standards Fixer и PHP CodeSniffer, можно определить и обеспечить соблюдение конкретных стандартов кодирования, включая PSR-12: Extended Coding Style и Doctrine Coding Standard, для всех членов команды. Вы даже можете интегрировать эти инструменты в пайплайн непрерывной интеграции или IDE для автоматической проверки стиля кода при каждом его изменении. Благодаря наличию плагинов, предлагаемых сообществом для многих инструментов, можно легко расширить возможности программы проверки стиля кода, включив в нее функции контроля совместимости определенных версий PHP и т.д.
Следует отметить, что SonarQube не рекомендуется использовать для проверки стиля кода, поскольку в ней отсутствует возможность автоматического исправления существующих проблем. Это может занять много времени, поэтому лучше использовать инструменты, способные автоматически вносить исправления.
Статический анализ
Когда речь заходит о повышении качества кода, статический анализ является важнейшим инструментом, которым нельзя пренебрегать. Статический анализ проверяет код, не приступая к его выполнению. Он позволяет обнаружить такие проблемы, как отсутствие обработки исключений, неправильные типы переменных, мертвый код и другие ошибки, которые можно найти. Статический анализ предоставляет нам ценную информацию и контекст, которые мы можем использовать при написании более качественных тестов. Он проверяет отсутствующие подсказки типа и приведения типа, что позволяет избежать ошибок. Для выполнения статического анализа в PHP существует несколько отличных инструментов, включая Psalm, PHPStan и PHP Mess Detector. Каждый инструмент может выявлять схожие проблемы, но предлагать при этом различные проверки. Поэтому в своей кодовой базе целесообразно использовать несколько инструментов.
Psalm и PHPStan включают в себя разные полезные расширения, улучшающие анализ и обеспечивающие интеграцию с такими популярными библиотеками, как Symfony и Doctrine. Хотя Sonarqube не рекомендуется использовать при автоматизации стиля кода, он является отличным инструментом в статическом анализе, если в нем отключены правила форматирования кода. Он вычисляет такие метрики, как количество дублирующихся строк в коде и его сложность. Более того Sonarqube отслеживает изменения данных показателей с течением времени, позволяя увидеть, как вы улучшили свой код, что дает нам важную информацию о состоянии проектов Westwing.
С помощью статического анализа можно быстрее получить обратную связь по своему коду, не прибегая к посторонней помощи. Статический анализ проверяет типы возвратов и потенциальные юзкейсы, что экономит время и гарантирует корректность кода. В целом статический анализ является незаменимым инструментом для тех, кто стремится повысить качество своего кода.
Помимо средств статического анализа, с помощью абстрактного синтаксического дерева, представляющего нашу кодовую базу, можно создавать еще более продвинутые решения.
Архитектурные тесты
Каждое приложение имеет архитектуру, которая обычно описывается в Confluence или через набор правил, определяющих зависимости между модулями или слоями кода. Часто эти правила нигде не документируются, а передаются от разработчика к разработчику каждый месяц. В результате новые члены команды могут и не знать об этих правилах, поэтому им приходится знакомиться с ними, спрашивая у коллег, или же допуская ошибки и получая обратную связь.
Хорошо, что существуют инструменты, позволяющие документировать и проверять кодовую базу на соответствие этим правилам. Например, в Domain-Driven Design (предметно-ориентированное проектирование) вы можете столкнуться с луковичной архитектурой, когда классы доменного уровня не могут зависеть от классов на уровне приложения или инфраструктуры. Аналогично, приложения могут зависеть от доменного уровня, но не от уровня инфраструктуры. Если разработчики знакомы с этими правилами, то следовать им относительно просто. Однако ошибки все же возможны, например, когда разработчик использует не тот класс, что может быть не замечено при проверке кода.
Убедиться в том, что эти правила всегда соблюдаются, можно с помощью архитектурных тестов. Такие инструменты, как PHP Architecture Tester и Deptrack, позволяют удостовериться в том, что все зависимости класса принадлежат разрешенным пространствам имен. Данные инструменты работают схожим образом, но PHP Architecture Tester обеспечивает большую гибкость при определении конфигурации для работы с инструментом. Используя настоящие архитектурные тесты, вы можете убедиться, что ваша кодовая база соответствует заданной архитектуре, и это делает ее более надежной и простой в сопровождении.
Зависимости
Статический анализ является важным аспектом поддержки проекта. Но поддержание зависимостей в актуальном состоянии может оказаться непростой задачей, особенно когда речь идет о сложных проектах, содержащих множество зависимостей, многие из которых также могут иметь свои собственные зависимости. Обновление сразу нескольких зависимостей для апгрейда версии PHP может оказаться трудным вопросом, поэтому лучше всего поддерживать зависимости в актуальном состоянии и внедрять изменения небольшими патчами. Такой подход не только гарантирует своевременность в получении исправлений для системы безопасности, но и позволяет не производить одновременное изменение сразу нескольких зависимостей, а сделать это атомарно.
К счастью, существуют инструменты, которые могут помочь в этом процессе. В действительности, они обеспечивают получение последних версий библиотек для ваших проектов, а также создают пул-реквест при появлении новой версии. Вы можете сконфигурировать эти инструменты с целью получения минорных или патч-обновлений, и они будут создавать новый пул-реквест для одного обновления библиотеки за раз. Решение о том, следует ли смерджить пул-реквест или пропустить обновление, если оно не нужно, принимается вами самостоятельно.
Чтобы убедиться в том, что ваш код продолжает работать после каждого обновления, необходимо иметь хороший набор интеграционных тестов и инструмент статического анализа, способный обнаружить небольшие разрушительные изменения, которые могут произойти у ваших вендоров. Без таких инструментов мерджинг и обновление зависимостей может оказаться рискованным мероприятием.
Есть два инструмента, которые могут помочь в данном процессе и это Dependabot и Renovate. Оба они обладают схожим набором функциональных возможностей и предоставляют на Github полезные действия для управления проектом. Если вы не используете Github, то можете интегрировать оба бота с выбранной вами платформой репозиториев.
Когда речь идет о зависимостях, добавить новые зачастую проще, чем удалить ненужные. Однако если вы хотите проверить, сколько зависимостей на самом деле необходимо в вашем проекте, то можете воспользоваться инструментом Composer-unused. Он использует статический анализ для поиска библиотек, которые вы больше не используете в своей кодовой базе или ее зависимостях. Анализируя зависимости в кодовой базе, он выявляет ненужные библиотеки и помогает сократить количество зависимостей в файле composer.json.
Composer-unused доступен в виде плагина для композера, отдельной библиотеки, управляемой композером, или отдельного архивного файла PHP, в зависимости от ваших предпочтений. Это полезный инструмент, который поможет навести порядок в зависимостях и оптимизировать ваш проект.
Рефакторинг кода
Работа с Abstract Syntax Tree (абстрактное синтаксическое дерево) позволяет не только анализировать код, но и модифицировать его. Однако при обновлении кода до новой версии PHP или новой версии фреймворка можно столкнуться со значительным количеством депрекаций и разрушительных изменений. Исправление этих проблем вручную, даже с использованием лучших приемов PHPStorm, может занять месяцы. К счастью, большую часть этих процессов можно легко автоматизировать с помощью Rector. Данный инструмент позволяет автоматически рефакторить код в соответствии с последней версией PHP или конкретной библиотекой. Готовые наборы правил охватывают все основные версии PHP, а также обновления для таких популярных фреймворков, как PHPUnit, Symfony и Doctrine.
Помимо работы с апгрейдами внешних библиотек, Rector также полезен для ваших собственных библиотек и кода. С помощью более чем 400 определенных правил вы можете справиться с большинством задач рефакторинга, таких как переименование, добавление типов, изменение видимости, удаление мертвого кода или добавление ранних возвратов. Самое приятное, что при этом отсутствует риск человеческой ошибки, так как Rector берет на себя все заботы о процессе рефакторинга. Конфигурация проста, установка и добавление базовых настроек займет не более часа.
Резюме
В компании Westwing мы интегрировали инструменты для стилизации и статического анализа кода в каждый CI/CD-пайплайн — это позволяет нам быть уверенными в качестве наших инструментов. Также мы включили в наш рабочий процесс бота RenovateBot, который ежедневно обновляет наши зависимости. Еще мы используем Composer Unused и Rector — но вместо того, чтобы внедрять их в CI, нам показалось, что они будут более полезны при работе над обновлениями PHP/библиотек. Благодаря им мы можем работать быстрее и обеспечивать качество нашей кодовой базы.
Существует множество инструментов, которые могут быть использованы в ваших проектах, но среди них есть те, которые я считаю необходимыми. Однако, начиная совершенствовать свой проект, не стоит добавлять все инструменты сразу. Лучше всего внедрять их постепенно, по одному инструменту за раз. Я настоятельно рекомендую имплементировать их в том порядке, который представлен в этой статье: начинать с наиболее простых в интеграции и постепенно углубляться в более сложные.
Материал подготовлен в преддверии старта курса "PHP Developer. Professional".
plFlok
Меня в таких схемах всегда смущает, что история коммитов и, как следствие, блейминг кода превращается в какую-то кашу иногда. Поэтому интересно мнение публики: это точно хороший совет - переносить исправление кода на уровень ci, а не на уровень правил автоформатирования в ide?
Ksoo
Обычно после того как ты в PR видишь коммит от линтера, ты сам делаешь squash и идешь настраивать IDE чтобы она перед сохранением форматировала.
garm
В одном из проектов, над которым я работал, автоматическое форматирование было сделано на уровне pre-commit-hook. Причем использовалась наивная реализация, когда из хука запускается программа форматирования, которая форматирует код всего проекта, но никак не обновляет данные для коммита. Это приводило к тому, что после коммита в рабочей копии появлялись изменения с правильным форматированием файлов, но они не попадали коммит.
Я бы использовал автоформатирование на двух уровнях: изменяющее, на уровне IDE, и проверяющее, на уровне CI. Конечно, это должна быть одна и та же программа с одинаковым набором правил.
laatoo
проверяющее будет запрещать деплоить, пока линтер не доволен, или что?
plFlok
будет запрещать мёрджить, вимдимо
persona
Это не проблема, гит сейчас умеет игнорировать определенные изменения при блейме. Нужно всего лишь добавить хешы нужных коммитов в файл .git-blame-ignore-revs . Подробнее тут.
laatoo
это проблема, потому что этим придется заниматься: сидеть и коммиты переписывать из лога в файл.
дичь какая)
persona
Нет, этим не нужно заниматься, этим занимаются автоматизированные инструменты. Коммитят свои исправления и затем коммитят хеш коммита в этот файл.
laatoo
история потом выглядит примерно так?
то есть после каждого коммита есть мусорный коммит с "подтиранием" линтером?
persona
Для новых фич это не надо, т.к. для них массовые изменения автотулзами не нужны.
В нашем случае это используется для массового рефакторинга сделанных автотулзами. Или для изменений сделанных ботами в полностью автоматическом режиме, например обновился корпоративный стандарт кода, бот обновляет зависимость с правилами для phpcs, запускает переформат кода, коммитит изменения и создает pr. Человек должен только его заапрувить. Что там два коммита вместо одного честно говоря совершенно всё равно.