CTO Vetmanager, PHP Developer, Ironman 70.3

Rector побеждает legacy

Ректор Мэм

Rector(далее просто - ректор) инструмент для быстрого обновления и рефакторинга php кода. Миссия Rector - удалим слово “legacy” из словаря.

У меня планируется доклад про этот инструмент на канале ЭФКО Цифровые Решения. Я постараюсь подготовить проговорить основные тезисы здесь в блоге.

Видео Презентация

План

Что такое Rector?

Rector - это инструмент командной строки, который умеет изменять код согласно заданным правилам. При помощи ректор вы сможете обновить ваш проект до новой версии php, обновить библиотеки такие как PHPUnit и даже используемый фреймворк. Ректор не линтер.

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

Если линтер сможет исправить расстановку пробелов, то ректор умеет делать настоящий рефакторинг.

Когда-то я работал на заводе и мой бригадир мне говорил: “Вова, ты думаешь я буду за ТЕБЯ делать СВОЮ работу?” и это про ректор. Он будет делать за вас вашу работу.

Выводы

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

Ректор НЕ победил legacy. Борьба продолжается.

О переспективах:

Будущее за ректороподобными проектами на всех языках. Триллионы строк кода выжигают миллиардные бюджеты на их поддержку и уборку легаси. Экономия даже 1% - коллосальные деньги. Если для вашего языка еще нет такого проекта - самое время его написать.

Ректор подойдет:

  • Много legacy и его нужно развивать
  • Нет денег, чтобы все переписать
  • Ваш код соответствует и выше PSR-4
  • Линтеров вам стало мало
  • Хочется переписывать, но не знаете с чего начать
  • У вас новый проект с сомнительной командой

Правила и наборы правил

Устновить ректор в проект легко, просто подключите зависимость в composer.

	composer require rector/rector --dev

Затем инициируйте ректор, это действие создаст файл rector.php, при помощи которого конфигурируется ректор для вашего проекта.

	vendor/bin/rector init

Я уже рассказывал здесь как мы обновляли код с php 5.3 на php 7.2 несколько лет назад. Суть в том, что мы запускали чекер, который находил то, что нужно переписать. Мы описывали правила, по которым нужно переписать код, а затем стажеры переписывали этот код.

Ректор работает так же, только без стажеров. Ректор умеет разбирать php при помощи библиотеки PHP-Parser.

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

Например:

 $items = [];
-foreach ($items as $key => $value) {
+foreach ($items as $value) {
     $result = $value;
 }

Ректор найдет конструкцию foreach в которой не используется переменная $key и ректор удалит её.

Правила в ректор объеденены в наборы для удобства подключения однотипных правил. При начальной инициализации включаются правила из набора DEAD_CODE.

Есть еще интересные наборы: CodeQuality, TypeDeclaration и наборы с фичами разных версий языка.

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

Проблемы автоматического рефакторинга

Установив ректор в свой репозиторий, можно начать применять правила к коду. Хорошая идея начать с дефолтного набора правил DEAD_CODE. Применяйте правила по одному к коду и запускайте тесты.

Я использовал набор DEAD_CODE добавляя 5-10 новых правил каждую неделю. Для этого нужно их скопировать из набора. В файле rector.php найдите строку $containerConfigurator->import(SetList::DEAD_CODE); зажмите CTRL и кликните на DEAD_CODE.

Затем перейдите в сам файл с правилами public const DEAD_CODE = __DIR__ . '/../../../config/set/dead-code.php'; кликаем под CTRL на dead-code.php который выглядят так:

    $services = $containerConfigurator->services();
    $services->set(\Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfFunctionExistsRector::class);
    $services->set(\Rector\DeadCode\Rector\If_\UnwrapFutureCompatibleIfPhpVersionRector::class);
    $services->set(\Rector\DeadCode\Rector\Cast\RecastingRemovalRector::class);
...

Закомментируйте строку $containerConfigurator->import(SetList::DEAD_CODE); и вставьте правила в ректор. Закомментируйте все правила и раскомментируйте их по одному правилу или группами.

RemoveEmptyClassMethod иногда удаляет нужные методы, например ломает все синглтоны, полечить можно если добавить внутрь метода какой-то код

    private function __clone()
    {
        throw new \Exception("I am a singleton!!!");
    }

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

class Tool {

	public function exec(): void
	{
		//$this->doWork('one');
		//$this->doWork('two');
	}

	private function doWork($name): void
	{
		...
	}

}

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

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

Ректор быстро улучшается, месяц назад правило RemoveUnusedNonEmptyArrayBeforeForeachRector не работало правильно.

Из кода:


if (count($array) > 0) {
    foreach( $array as $value ){
        echo $value."\n";
    }
}

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

    private function sum($arr)
    {
        $sum = 0;
        if (!empty($arr)) {            
            foreach ($arr as $item) {
                $sum += $item;
            }            
        }
        return $sum;
    }

Но в момент подготовки к докладу ректор удаляет условие только там, где он уверен в типе и повторить ошибку мне не удалось.

Проблемы использования Rector

На нашем проекте ректор проверяет около 250к строк и на моём рабочем буке запуск без кеша занимает 2m50.245s. Следующие уже быстрее. Это немало, комфорта разработчикам не добавляет.

Еще ректор очень плохо справляется со скриптовым лапшекодом. Особенно если в нем встречаются много require и include. Благодаря ректору я наше в проекте несколько таких подключений там где их не должно было быть и где программист их аккуратно спрятал за phpcs:ignore.

У нас есть часть кода, которая не привидена к PSR4/12 стандарту и ректор на этом куске ругается страшными ошибками. К чести ректора, он быстро развивается и многие участки кода, которые он раньше не переваривал, понимает обновлениями. Верю в развитие этого проекта.

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

Что нам дал ректор

Мы еще не использовали все правила ректор. Мы применили только набор DEAD_CODE и я обновил PHPUnit c 6й версии до последней.

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

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

Обновление PHPUnit заняло у меня до 2х часов, с постепенным применением правил и каждый рас с переходом на новую версию PHPUnit. При этом у нас более 1000 тестов юнит, интеграционные и тесты с использованием селениум. Ректор не справился только с Assert::assertContains() must be iterable, string given пришлось исправить это руками.

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

Rector в pipeline

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

Можно пойти дальше и внедрить автофикс в пайплайн. Хотя бы в качестве проверятора, но еще круче сделать автофикс. Я подготовил репозиторий где автофикс делается средствами github-actions. Я очень рекоммендую именно такой путь если у вас в компании есть ревью. Если ректор или линтер выругаются просто красным крестиком в пайплайне, то вы в 90% случаев будете применять тот же автофикс. Почему бы не сэкономить время, но если у вас не принято читать коммиты, то это может быть опасно.

Ссылки