Автоматизация исправлений ошибок оформления кода с помощью Git и PHP Coding Standards Fixer

Все мы знаем про стандарты оформления кода в PHP сообществе и пытаемся им следовать. Несмотря на это, мы все люди и допускаем ошибки. Это значит, что у нас есть несколько вариантов решения данной проблемы:

1) Править все ошибки стиля вручную.

2) Использовать плагины для IDE или другие инструменты для запуска в терминале.

Эти два варианта не те, которые я хотел бы использовать. На самом деле есть еще один вариант — все автоматизировать. Я буду использовать Git и PHP CS Fixer, но несмотря на это принципы очень просты и вы легко сможете применить их к вашему языку программирования и системе контроля версий. Продолжайте читать, чтобы избавиться от рутины и стать более продуктивным разработчиком.

Почему не первые два варианта?

Правка ошибок вручную

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

— Рутина. Представьте, что вы правите ошибки оформления кода день за днем. Разве вам не хотелось бы заняться чем-то более интересным? Уверен, что хотелось бы!

— Трата времени. Прочитайте пункт выше.

— Отсутствие постоянства. Вы хотите исключить все ошибки оформления кода из вашего проекта, верно? Если же вы делаете проверку стиля вручную, время от времени, то все это со временем приведет к дополнительным комитам в Git, содержащим только исправления форматирования кода. Это будет выглядеть плохо:

  1. add feature 3
  2. code style fixes
  3. add feature 2
  4. add feature 1

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

Еще один момент: это также усложнит code review и сделает его менее продуктивным. Вместо того, чтобы сфокусироваться на серьезных и важных частях кода из pull request’а, ревьювер будет отвлекаться на посторонний шум — правки по стилю кода.

Плагины для IDE

Данный подход не так плох как первый, но:

— Плагина для вашего IDE может и не быть.

— Вам понадобится настроить ваше IDE таким образом, чтобы оно запускало PHP CS fixer при каждом соханении файла. Только таким образом вы добьетесь постоянства. Если же ваше IDE такого не умеет, значит вам не повезло.

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

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

Запуск инструментов в терминале

Если вам не повезло с IDE, тогда данный вариант скорее всего тот, с которым вам прийдется иметь дело. Тем не менее, он также имеет недостатки:

— Отсутствие постоянства. Снова, вы должны не забывать запускать PHP CS fixer вручную в терминале.

— Трата времени. Представьте себе следующую ситуацию: вы добавили несколько файлов в staging область в Git. Затем вы вспомнили, что еще не запускали исправление оформления кода. Вы запускаете его и исправленные версии ваших файлов появляются в working области. Вам снова необходимо делать git add.

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

Автоматизация

Я хочу описать пару простых шагов, которые улучшат картину в целом. Если вы все еще страдаете от использования SVN, советую прочитать Git-SVN bridge.

Чтобы следовать принятым соглашениям, необходимо выполнить следующие шаги:

1) Проверять и исправлять ошибки в оформлении кода на клиенте еще до того, как он сделает комит и тем более до того, как код попадет в главный репозиторий.

2) Т.к. мы не можем доверять проверкам на клиенте, нам также потребуется настроить дополнительные проверки на стороне сервера.

В этот раз я опишу реализацию только первого шага.

Автоматизация на клиенте

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

В PHP среде у нас есть отличный инструмент — PHP Coding Standards Fixer. Он позволяет вам проверять ваш код на наличие ошибок или даже исправлять их. Т.к. нашей целью является автоматизация исправления подобных ошибок, то и fixer мы будем использовать в соответствующем режиме.

Git pre-commit хук

В Git мы имеем pre-commit хук. Как можно догадаться из его названия, его основная цель предельно проста: запустить данный файл перед окончательным подтверждением комита. Если данный хук вернет 0, то комит будет разрешен, если 1 — комит будет запрещен.

Все хуки находятся в папке .git/hooks в корневой папке вашего проекта. Там вы можете найти файлы с расширением *.sample и ознакомиться с примерами. Все что нам сейчас необходимо — это создать pre-commit файл или же переименовать pre-commit.sample в pre-commit. После этого Git будет запускать его каждый раз, когда вы будете делать комит.

Отлично! Давайте более подробно опишем наши требования к самой автоматизации.

Требования к автоматизации

Если вы не знакомы с состояниями файлов в Git, советую прочитать The Three States in Git.

1) В первую очередь, я не хочу чтобы fixer каждый раз обрабатывал все файлы в моем проекте. Необходимо чтобы он анализировал и правил только те файлы, которые добавлены в staging область и будут в комите. Это ускорит процесс.

2) Очевидное: fixer должен именно исправлять ошибки, а не показывать их.

3) После исправлений файлы должны быть автоматически добавлены в staging area.

Если файлы из staging области исправлены fixer’ом, то их исправленные версии попадут в working directory. Если мы их не добавим опять в staging область, то в комите получим старую версию файлов без исправлений.

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

4) Какие бы файлы не были добавлены в комит, fixer должен проверять только *.php файлы, т.к. нет смысла проверять файлы с другими расширениями.

5) Необходима возможность использовать конфигурационный файл для fixer’а без исправления кода в pre-commit хуке.

Хорошо, требования сформированы, давайте реализуем их.

Файл .git/hooks/pre-commit

  1. #!/usr/bin/env php
  2. <?php
  3.  
  4. // получаем список имен файлов из staging области
  5. exec(‘git diff —cached —name-only’, $stagedFiles);
  6.  
  7. $fixedFiles = [];
  8. foreach ($stagedFiles as $fileName) {
  9.   // проверяем только .php файлы
  10.   // is_file — необходимо чтобы избежать проблем с переименованными и удаленными файлами, добавленными в комит.
  11. if (preg_match(‘/\.php$/’, $fileName) && is_file($fileName)) {
  12. exec(sprintf(‘php-cs-fixer fix %s -q’, $fileName), $output, $exitCode);
  13.  
  14. // 1 — этот код значит, что в файле были ошибки и они были исправлены
  15.   if ($exitCode === 1) {
  16.   // добавляем исправленный файл обратно в staging область
  17.   exec(‘git add ‘ . $fileName);
  18.   $fixedFiles [] = $fileName;
  19.   }
  20. }
  21. }
  22.  
  23. if (count($fixedFiles)) {
  24. echo sprintf(«Code style fixes were applied to the following files:\n\n%s\n\nFiles were added to the commit after fixes.\n\n», implode(«\n», $fixedFiles));
  25. }
  26.  
  27. // allow commit
  28. exit(0);

Это все! Теперь каждый раз перед комитом Git будет запускать pre-commit файл и все ошибки относящиеся к  оформлению кода будут исправлены на клиенте автоматически.

Не забудьте выставить права на файл:

  1. chmod +x .git/hooks/precommit

Тестируем PHP CS Fixer

Для теста создадим несколько файлов:

1) with-issues.php (этот файл мы добавим в комит).

  1. <?
  2.  
  3. function sum ($a, $b) {
  4.  
  5.  
  6. return $a+$b;
  7. }

2) unstaged.php (этот оставим в working directory).

  1. <?
  2.  
  3. function sadNews () {return ‘I will not be fixed :(‘;}

3) test.txt (текстовый файл нужен нам для того чтобы проверить, что fixer игнорирует их).

  1. <?php
  2.  
  3. function test ()
  4.  
  5. {return uniqid();}

Файлы созданы. Давайте добавим пару из них в staging область:

  1. git add withissues.php
  2. git add test.txt

Если выполнить git status, то мы увидим:

  1. On branch master
  2.  
  3. Changes to be committed:
  4.  
  5. (use «git reset HEAD <file>…» to unstage)
  6.  
  7. new file: withissues.php
  8.  
  9. new file: test.txt
  10.  
  11. Untracked files:
  12.  
  13. (use «git add <file>…» to include in what will be committed)
  14.  
  15. unstaged.php

Как мы видим, только два файла будут добавлены в комит. Давайте закомитим их:

  1. git commit m ‘the php cs fixer automation test’

Вывод команды:

  1. Code style fixes were applied to the following files:
  2.  
  3. withissues.php
  4.  
  5. Files were added to the commit after fixes.
  6.  
  7. [master 753b5bd] automated code style check
  8. 2 files changed, 11 insertions(+)
  9. create mode 100644 withissues.php
  10. create mode 100644 test.txt

Из этого следует, что наши файлы были добавлены в комит, при этом PHP fixer исправил только один из них, с расширением .php. Также хочу обратить ваше внимание на то, что fixer не проверил unstaged.php файл т.к. мы его не добавлили в staging область. Это именно то, что нам нужно.

Если выполнить git status еще раз, вы увидите, что unstaged.php по прежнему там.

Давайте посмотрим файл with-issues.php и убедимся, что fixer исправил его:

  1. cat withissues.php
  1. <?php
  2.  
  3. function sum($a, $b)
  4. {
  5. return $a + $b;
  6. }

Как мы видим, все в порядке. Еще один момент, который мы не проверили — использование конфиг файла для fixer’а.

Кастомный конфиг файл

PHP Coding Standards Fixer по умолчанию пытается найти .php_cs файл в корневой папке вашего проекта. Если вы хотите использовать кастомный конфиг файл, то просто добавьте его и напишите в нем ваши настройки. Файл должен вернуть Symfony\CS\ConfigInterface интерфейс.

Важно

Вы не можете хранить pre-commit файл в репозитории (он не передастся при git clone), поэтому вам необходимо убедиться, что он есть у всех членов вашей команды.

Выводы

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