Автор статьи: Александр Яровой
Разработчик департамента e-commerce КОРУС Консалтинг
Меня зовут Александр Яровой, я разработчик в департаменте e-commerce в ГК «КОРУС Консалтинг», и в этой статье хочу рассказать о том, как мы с командой внедрили в процессы разработки статический анализ на основе PHPStan. Результат внедрения инструмента покажу на примере проекта крупного производителя строительных материалов, который обратился к нам с задачей по автоматизации и цифровизации оптовых онлайн-продаж. Для заказчика мы разработали b2b-портал на базе «1С-Битрикс — Управление сайтом»: востребованной CMS среди наших клиентов, большинство из которых — представители крупного и среднего российского e-commerce и промышленной отрасли. Смотрим, что получилось.
Что такое статический анализ на PHPStan и кому он подойдет
На мой взгляд, статический анализ кода — это мастхэв на любом проекте, а особенно — на долгоиграющем. Если это не разовый лендинг или личный бложек, то PHPStan может стать полезным подспорьем в длительной разработке. Его главные преимущества — это простое подключение и минимально необходимая конфигурация на старте. Эти свойства упрощают работу текущей команде и последующим разработчикам, которые могут присоединиться к проекту в будущем.
Статический анализ на PHPStan нужен для того, чтобы искать в коде ошибки и недочеты. Это «придирчивый» инструмент, который позволяет выполнять как простые проверки на существование классов и функций, так и более комплексные — на несоответствие типов или уязвимости. При этом инструмент помогает покрывать проверками весь объем кода, а запускать саму программу не нужно.
Какие ошибки можно обнаружить с помощью анализа? Сверяемся с методичкой:
существование классов, используемых в instanceof, catch, typehints и других конструкциях
существование и доступность методов и функций, а также количество допустимых аргументов
возвращает ли метод тот же тип, который был декларирован
существование и видимость доступных свойств, а также укажет, если свойству присваивается другой тип, чем тот что заявлен
правильное количество параметров, передаваемых в вызовы sprintf/printf на основе строк формата
существование переменных при соблюдении областей условий и циклов
правильность строго разных приведенных переменных, которые могут выявить ложные результаты
ошибки при копировании чужого кода.
Всего существует 9 уровней строгости для проверки кода с помощью анализатора. По умолчанию PHPStan проверяет только тот код, в котором он уверен. Например, константы, создание экземпляров, методы, вызываемые в $this, статически вызываемые методы, функции и существующие классы в различных языковых конструкциях. При выборе более высокого уровня увеличивается количество аспектов, которые инструмент проверяет в коде. Так, если инструмент проверяет код на девятом уровне строгости в отношении легаси-программ, то в результате могут обнаружиться тысячи ошибок.
PHPStan при первом запуске на уже существующем проекте гарантирует неудобства текущим разработчикам, так как с высокой долей вероятности даже на 4-5 уровне строгости найдет сотни ошибок, которые потребуется исправить, прежде чем продолжить работу.
Почему мы решили внедрить инструмент в проект заказчика: цели и сложности при реализации
Теперь немного контекста о самом проекте. Помимо b2c-сегмента, наш департамент занимается разработкой и в рамках задач b2b-компаний на российском рынке электронной коммерции. В рамках этого материала мы говорим про b2b-портал, который позволяет автоматизировать оптовые продажи крупного производителя строительных материалов. Сфера клиентского бизнеса — это традиционная индустрия, в которой производители конкурируют за счет высококлассного клиентского обслуживания. Чтобы повысить качество клиентского сервиса, компании внедряют разные цифровые инструменты — в том числе и b2b-порталы. B2b-портал нужен для того, чтобы снизить объем ручной работы, выполняемой менеджерами закупщика или контрагента и менеджерами производителя, повысить эффективность их взаимодействия, увеличить качество клиентского опыта и объем продаж. Платформы также способны полностью перевести в онлайн сложный CJM и сократить цикл покупки с нескольких недель до нескольких часов.
Наш проект, по меркам e-commerce, еще достаточно молод — разработку ведем около полутора лет. Однако мы, конечно, успели столкнуться с рядом багов, которые возникли при адаптации готовых модулей под требования бизнеса. Поэтому мы решили внедрить статический анализ в процессы проверки кода перед его отправкой в репозиторий, чтобы повысить эффективность контроля, снизить число ошибок, упростить работу команды.
Еще до внедрения PHPStan в проект мы понимали, что нам придется столкнуться с некоторыми сложностями:
Проект был запущен в продакшн уже больше года назад. Соответственно кодовая база была довольно обширной (несколько сотен классов), что обещало нам немало ошибок от статического анализа. Это стандартная история при первичном запуске инструмента, и эту массу ошибок предстояло проработать.
Проект был запущен на основе готового решения «1С:Битрикс — Управление сайтом». 80% проекта работало на самописной надстройке над ORM с документированными классами сущностей и моделей для удобства работы с кодовой базой, поддержания SOLID и принципов «чистого кода». В то же время анализ мог выявить ошибки, связанные с ядром Битрикса и частью небольших, оставшихся от предыдущего ИТ-решения заказчика компонентов. Поэтому мы планировали скормить PHPStan код ядра, чтобы он знал о его существовании, но не сканировал на ошибки.
Поскольку ядро Битрикса не лежит в git проекта, а в рамках CI\CD подтягивается со стороннего репозитория, то до момента деплоя встроить статический анализатор в процесс не представлялось возможным. Соответственно, предстояло автоматизировать проверку на локальных машинах разработчиков, чтобы они не забывали запускать PHPStan перед отправкой коммитов в репозиторий.
После того как мы обрисовали цели и трудности, команда приступила к внедрению.
Как мы внедрили статический анализ на PHPStan: шаги и результаты
Для начала установили PHPStan на проект.
composer require --dev phpstan/phpstan
Далее необходимо было создать файл конфигурации phpstan.neon. Подробнее про файл конфигурации можно почитать в документации по ссылке.
Поскольку мы внедряли PHPStan не на новый, только стартующий проект, а на проект уже в продуктиве, то было принято решение стартовать с 5 уровня. Это позволило нам дальнейшем повышать уровень в рамках работы с техдолгом постепенно. Кроме того, 5 уровень подразумевает проверку типов аргументов, передаваемых методам и функциям (помимо проверок предыдущих уровней), что имеет большое значение при использовании strict_types=1.
В то же время включение 6 уровня (недостающие typehints) создавало слишком много ошибок. Их исправление заняло бы значительное количество времени, которым мы на тот момент не располагали.
Основной наш код (логика, модели, контроллеры и т.д.) мы выносили в отдельный модуль, поэтому натравили PHPStan именно на директорию с этим модулем. Такой шаг сразу решил нашу проблему с особенностями кода ядра Битрикса и сопутствующих компонентов.
При этом нам необходимо, чтобы PHPStan подтягивал для анализа классы из ядра, иначе на выходе мы получили бы тонну ошибок типа unknown class. PHPStan нам позволяет разделить анализируемый код (paths) и код, используемый «как данность», без поиска ошибок.
Кроме ядра добавили сторонние модули из других репозиториев, а также исключили из анализа (excludePaths) наши миграции.
В итоге получили весьма лаконичный конфиг для PHPStan.
parameters:
level: 5
scanDirectories:
- bitrix
- local/modules/psr3.logger
- local/modules/korus.entity
- local/modules/korus.api
- local/modules/korus.wsdl_generator
- local/php_interface
paths:
- local/modules/korus.project_xxx/lib
excludePaths:
- local/modules/korus.project_xxx/blueprint
Готово, теперь можем запустить PHPStan. Делается запуском команды в консоли.
[ERROR] Found 171 errors
Вот наиболее типовые и знакомые каждому разработчику:
# Method Korus\Entity\OrderEntity::prepare() overrides method Korus\Entity\AbstractEntity::prepare() but misses parameter #1 $formatDate.
# Parameter #1 $data of static method Korus\Tools\Exchange\SapDataExtractor::extract() expects string, null given.
# Instanceof between Bitrix\Main\ORM\Fields\ScalarField and Bitrix\Main\ORM\Fields\DatetimeField will always evaluate to false.
# Parameter #1 $chat_id of static method Korus\Entity\TelegramChatsEntity::getByChatId() expects string, int given.
# Static call to instance method Korus\Api\Facade\Request::header().
Как видим, большинство ошибок связаны с некорректным типом параметров, мертвыми ветками if-else и подобными, что никак не аффектило нас в текущей реализации, но в будущих доработках могло выстрелить неожиданными багами и неочевидным поведением.
Неплохо, могло быть и хуже. Теперь нужно закатать рукава, вооружиться PHPDoc и поправить все эти ошибки, чтобы в результате получить —
[OK] No errors
Теперь остался последний шаг, прежде чем внедрение статического анализатора считать успешным. Необходимо автоматически запускать проверку перед каждым коммитом разработчика.
Для этого воспользовались git-хуком pre-commit. Подробнее о git-хуках можно почитать в документации по ссылке.
Сначала создали файл pre-commit в папке .git проекта.
mv ./.git/hooks/pre-commit.sample ./.git/hooks/pre-commit
Затем открыли файл через любой текстовый редактор, удалили все, что там есть и вставили код.
#dfdf
#!/usr/bin/env bash
hr() {
local start=$'\e(0' end=$'\e(B' line='qqqqqqqqqqqqqqqq'
local cols=${COLUMNS:-$(tput cols)}
while ((${#line} < cols)); do line+="$line"; done
printf '%s%s%s\n' "$start" "${line:0:cols}" "$end"
}
bldwht='\033[1;37m'
txtgrn='\033[0;32m'
txtred='\033[0;31m'
txtrst='\033[0m'
command_files_to_check="${@:2}"
command_args=$1
#Здесь мы задаем команду, которую вместо обычного запуска из командной строки
#будет запускать git посредством хука
command_to_run="vendor/bin/phpstan analyse --no-progress ${command_args} ${command_files_to_check}"
echo -e "${bldwht}Running command ${txtgrn} vendor/bin/phpstan analyse ${command_args} ${txtrst}"
hr
command_result=`eval $command_to_run`
#Если PHPStan вернет там ошибку, то мы прерываем коммит и выдаем список ошибок #статического анализа
if [[ $command_result == *"ERROR"* ]]
then
hr
echo -en "${txtred}Errors detected by ${title}... ${txtrst} \n"
hr
echo "$command_result"
exit 1
fi
#Если же все хорошо, то отправляем коммит в репозиторий
exit 0
Готово! Вы великолепны. Теперь в случае ошибок PHPStan подсветит их разработчику на этапе коммита и не позволит отправить изменения в репозиторий, пока ошибки не будут устранены.
Что в итоге
Внедрение статического анализа в проверку кода позволило добиться нескольких важных результатов:
Нам удалось повысить эффективность проверки кода. Теперь мы используем анализ, чтобы проверить каждую новую фичу, любую доработку или рефакторинг, чтобы найти несоблюдение типов, случайные ошибки в коде или опечатки. Например, если удалили класс при рефакторинге, а подключение этого класса убрали не везде. Сейчас из проверки исключен человеческий фактор: любая подобная ошибка будет на 100% будет обнаружена, а не пропущена из-за невнимательности.
Выросла стабильность проекта. Благодаря внедрению в workflow работы с git код гарантированно запускается и работает.
Изменился подход к работе с техническим долгом. PHPStan наглядно показывает проблемные модули, которые требуют скорейшего рефакторинга.
Повысилось качество кода. В рамках развития проекта мы планируем плавно повысить уровень проверок PHPStan как минимум до 7, и держать уровень ошибок на нуле.
Дополнительно снизили человеческий фактор за счет автоматизации проверки на локальных машинах разработчиков.
Бонусный пункт: получили длинный бэклог по техническому долгу, и теперь нам всегда есть чем заняться ?
В будущем планируем использовать инструмент на всех новых проектах с самого старта разработки, а так же плавно внедрять его на уже запущенные проекты.
Комментарии (2)
motoronik
06.12.2024 05:33Если честно странная статья, всеравно что 10 лет назад написать о том что мы используем git как это круто ;)
Почемуто считал что все адекватные проекты уже лет 10 как используют линтеры, стат анализаторы, тесты и прочие радости жизни и это минимальный набор инструментов для разработки, а не "рокетсайнс"
danielsedoff
Не в теме современного PHP, но название PHPStan запоминается. Кажется, что это страна, где все пишут на старом PHP, ходят в длинной белой одежде и ездят на слонах.