Рефакторинг PHP-кода: пошаговое руководство
Три одинаковые мензурки с разным уровнем наполнения в ряд на бледной изометрической подложке, у пустой изумрудный шарик

Рефакторинг PHP-кода: пошаговое руководство

Рефакторинг PHP-кода — это улучшение структуры без смены поведения. Разбираем признаки, безопасный порядок, приёмы и инструменты: PHPStan, Rector, PHPUnit.

Рефакторинг PHP-кода: как навести порядок и не сломать проект

Рефакторинг PHP-кода — это последовательное улучшение его структуры без изменения поведения: вы разбиваете длинные методы, убираете дублирование, вводите понятные имена и типы, а тесты подтверждают, что всё работает как прежде.

Главное правило: сначала страховка в виде тестов и статического анализа, затем маленькие шаги, и только потом — красота.

Почти в любом PHP-проекте, которому больше двух-трёх лет, накапливается технический долг: контроллеры на тысячу строк, массивы вместо объектов, глобальные состояния и условия на пять уровней вложенности. Ниже — практическое руководство: как распознать проблемные места, в каком порядке действовать, какие приёмы использовать и чем автоматизировать рутину.

Что такое рефакторинг и чем он отличается от переписывания

Рефакторинг — это не «переписать всё красиво» и не добавление новых функций. Это серия небольших преобразований, каждое из которых сохраняет внешнее поведение системы. Именно поэтому он безопасен при правильном подходе: вы никогда не оказываетесь в ситуации, когда проект «разобран» на несколько недель и не запускается.

Переписывание с нуля устроено иначе: вы строите новую систему рядом со старой, а потом пытаетесь воспроизвести всю накопленную бизнес-логику, включая неочевидные исключения и «костыли», которые на самом деле были реакцией на реальные случаи. Такие проекты чаще всего затягиваются, а старый код продолжает жить параллельно. Поэтому для большинства PHP-приложений эволюционный рефакторинг выгоднее и надёжнее.

Отдельно стоит отделять рефакторинг от смены версии языка. Миграция с PHP 7.4 на 8.x — это обновление, но во многом оно решается теми же приёмами: типизация, readonly-свойства, enum, match, именованные аргументы. Многие из этих изменений можно автоматизировать, и об этом поговорим в разделе про инструменты.

признаки, что пора рефакторить

Шесть «запахов» в PHP-коде

01

Длинный метод

Метод не помещается на экран, а в имени есть «и»: validateAndSaveAndNotify. Ориентир — до 20–30 строк.

02

God-класс

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

03

Глубокая вложенность

Больше трёх уровней if и foreach. Условия читаются как лабиринт, а не как логика.

04

Магические значения

Числа и строки вроде status == 3 без объяснений. Смысл знает только автор, и то не всегда.

05

Дублирование

Один и тот же расчёт скопирован в пяти местах. Исправили в одном — забыли в четырёх.

06

Массивы вместо объектов

Данные передаются как $data['x']['y']. Нет типов, нет подсказок IDE, любая опечатка видна только в продакшене.

Когда рефакторить, а когда оставить как есть

Хороший ориентир — «правило бойскаута»: оставляйте код чище, чем он был до вас. Если вы пришли в модуль, чтобы добавить функцию, потратьте немного времени на то, чтобы сначала упростить место, куда эта функция встанет. Так порядок наводится постепенно, без отдельных многомесячных проектов.

Второй полезный принцип — «правило трёх»: первый раз вы пишете как получилось, второй раз морщитесь, но дублируете, а на третий раз выносите общую логику. Это защищает от преждевременных абстракций, которые бывают не менее вредны, чем копипаст.

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

Безопасный порядок рефакторинга PHP-проекта

  1. 01

    Зафиксируйте текущее поведение. Напишите тесты на участок, который собираетесь менять: юнит-тесты на PHPUnit или Pest, а для запутанного кода — характеризующие тесты, которые просто записывают то, что код делает сейчас.

  2. 02

    Подключите статический анализ и единый стиль. PHPStan или Psalm найдут несоответствия типов и мёртвый код, PHP CS Fixer приведёт форматирование к одному виду, чтобы в диффах не было шума.

  3. 03

    Выберите одну небольшую цель. Например, «разбить метод processOrder» или «убрать массивы из слоя оплаты». Формулировка должна помещаться в одну строку.

  4. 04

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

  5. 05

    Запускайте тесты и анализатор после каждого шага. Если что-то покраснело, причина почти всегда в последнем изменении — найти её дёшево.

  6. 06

    Не смешивайте рефакторинг с новыми функциями. Отдельный pull request на улучшения проще проверить на ревью и проще откатить, если что-то пойдёт не так.

  7. 07

    Измерьте результат. Сравните цикломатическую сложность, число ошибок анализатора, время на типовую правку. Без цифр трудно доказать пользу и выбрать следующую цель.

Тесты как страховка: что делать, если их нет

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

Такие тесты не красивы, зато они дают главное — сигнал «поведение изменилось». Когда вокруг проблемного места появилась сетка, можно аккуратно выделять зависимости, внедрять интерфейсы и постепенно заменять грубые тесты нормальными юнит-тестами.

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

пример: было / стало

Тот же расчёт скидки, но читаемый и типизированный

до
function calc($o) {
    if ($o['status'] == 1) {
        if ($o['total'] > 5000) {
            return $o['total'] * 0.9;
        } else {
            return $o['total'];
        }
    } else {
        return 0;
    }
}
  • магические числа 1, 5000, 0.9
  • вложенность в три уровня
  • массив без типов и подсказок IDE
после
enum OrderStatus: int
{
    case Paid = 1;
}

final class DiscountCalculator
{
    private const THRESHOLD = 5000;
    private const RATE = 0.9;

    public function total(Order $order): float
    {
        if ($order->status !== OrderStatus::Paid) {
            return 0.0;
        }

        return $order->total > self::THRESHOLD
            ? $order->total * self::RATE
            : $order->total;
    }
}
  • имена вместо магических значений
  • ранний возврат, один уровень вложенности
  • типы и объект Order, работает автодополнение

Какие приёмы дают максимальный эффект

В PHP-проектах большую часть пользы приносят несколько простых техник. Извлечение метода превращает «простыню» в набор именованных шагов. Ранний возврат (guard clauses) убирает лишние вложенные if и делает основной сценарий заметным. Замена магических значений константами или enum даёт коду язык предметной области.

Отдельная история — отказ от «массивов-мешков». Value Object и DTO дают типы, валидацию на входе и автодополнение в редакторе. В современном PHP это почти бесплатно: constructor promotion и readonly-свойства позволяют описать объект буквально в несколько строк.

Наконец, внедрение зависимостей. Вместо вызовов new внутри методов и статических обращений к глобальному состоянию класс получает нужные объекты через конструктор. Это основа тестируемости: подменить мок проще, чем перепаивать работающий код. Ниже сведены основные приёмы в одной таблице.

ПриёмЧто решаетРезультат
Извлечение методаДлинные методы, смешение задачКод читается как план действий
Ранний возвратГлубокая вложенность условийОсновной сценарий виден сразу
Enum и константыМагические числа и строкиСмысл значений ясен из имён
Value Object / DTOМассивы вместо структур данныхТипы, автодополнение, меньше ошибок
Constructor promotion + readonlyШаблонный код в конструкторахМеньше строк, неизменяемость данных
Внедрение зависимостейЖёсткие связи, статика, new внутри логикиКод легко покрывается тестами

Автоматизация: пусть рутину делают инструменты

Значительную часть механической работы можно доверить инструментам. Они не заменяют инженерное мышление, но экономят часы и снижают риск человеческих ошибок, особенно на больших кодовых базах.

Статический анализ стоит подключать с первого дня: начните с невысокого уровня строгости и постепенно повышайте его, фиксируя «базовую линию» существующих ошибок, чтобы новые не появлялись. Так вы не утонете в тысячах замечаний, а качество будет расти только вверх.

Инструменты, которые стоит добавить в PHP-проект

  1. 01

    PHPStan и Psalm — статический анализ типов, поиск мёртвого кода и потенциальных ошибок без запуска программы.

  2. 02

    Rector — автоматические преобразования кода: обновление до новых версий PHP, замена устаревших конструкций, добавление типов.

  3. 03

    PHP CS Fixer и PHP_CodeSniffer — единый стиль кода и чистые диффы без споров о форматировании.

  4. 04

    PHPUnit и Pest — регрессионные тесты, которые подтверждают, что поведение не изменилось.

  5. 05

    PHPMD и метрики сложности — подсветка слишком длинных методов и перегруженных классов.

  6. 06

    Infection — мутационное тестирование, показывающее, насколько тесты действительно ловят ошибки.

  7. 07

    PhpStorm — безопасные встроенные рефакторинги: переименование, извлечение метода, перенос класса с обновлением ссылок.

Хорошая практика — встроить эти инструменты в CI: каждый pull request проходит тесты, анализатор и проверку стиля. Тогда качество кода перестаёт зависеть от внимательности отдельных людей и становится свойством процесса. А рефакторинг превращается из героического подвига в повседневную привычку.

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

Типичные ошибки при рефакторинге PHP-кода

Большинство неудачных рефакторингов ломаются не из-за сложности кода, а из-за нарушения процесса.

Как делают неправильно

  • Начинают менять код без тестов и проверки поведения.
  • Смешивают в одном коммите рефакторинг, багфикс и новую функцию.
  • Пытаются «переписать всё за один спринт» и замораживают разработку.
  • Вводят сложные паттерны там, где хватило бы простой функции.
  • Не согласуют изменения с командой и бизнесом, а потом удивляются конфликтам.

Как делать правильно

  1. 01

    Сначала тесты и статический анализ, затем изменения.

  2. 02

    Один коммит — одна цель, рефакторинг отдельно от функциональности.

  3. 03

    Дробить работу на маленькие шаги и выкатывать их постепенно.

  4. 04

    Выбирать самое простое решение, которое устраняет конкретную проблему.

  5. 05

    Заранее договориться о приоритетах и доле времени на технический долг.

Как встроить рефакторинг в рабочий процесс

Самый устойчивый подход — не выделять рефакторинг в отдельный «проект», а заложить его в ритм команды. Например, 10–20% времени спринта отдаётся на улучшение кода, а все задачи по долгу фиксируются в трекере с понятной оценкой выгоды: «сократит время правок в модуле заказов» или «уберёт класс ошибок с неверными типами».

Для бизнеса аргументы лучше формулировать в деньгах и рисках. Сколько часов уходит на типовую доработку в проблемном модуле? Сколько багов в месяц приходит именно оттуда? За сколько новый разработчик входит в проект? Ответы на эти вопросы обычно убедительнее любых рассуждений о «чистоте кода».

Ниже — пример плана на месяц для среднего PHP-проекта. Цифры условные, но последовательность хорошо работает на практике.

пример плана на месяц

Рефакторинг по неделям: от страховки к результату

нед. 01

Страховка

Подключить PHPStan с базовой линией, PHP CS Fixer и CI. Написать тесты на самый рискованный модуль.

нед. 02

Быстрые победы

Убрать дублирование, заменить магические значения на enum и константы, ввести ранние возвраты.

нед. 03

Структура

Выделить сервисы из God-класса, заменить массивы на DTO, внедрить зависимости через конструктор.

нед. 04

Закрепление

Поднять уровень анализатора, замерить метрики и выбрать следующий модуль по данным.

Перед слиянием изменений проверьте пять пунктов: тесты зелёные, PHPStan не показывает новых ошибок, стиль приведён к единому, в диффе нет изменений поведения, а коммиты небольшие и понятно названы.

Итоги: как подойти к рефакторингу PHP-кода

Рефакторинг — это не разовая акция, а регулярная практика: маленькие безопасные шаги под защитой тестов и анализатора.
  1. 01

    Определите проблемные места по признакам: длинные методы, God-классы, вложенность, дублирование, массивы вместо объектов.

  2. 02

    Начните со страховки — тестов, статического анализа и единого стиля.

  3. 03

    Двигайтесь мелкими шагами: один приём — один коммит, проверка после каждого.

  4. 04

    Используйте Rector, PHPStan, PHP CS Fixer и встроенные инструменты IDE, чтобы автоматизировать рутину.

  5. 05

    Измеряйте результат и закладывайте долю времени на технический долг в каждом спринте.

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

Часто задаваемые вопросы

Нужен рефакторинг PHP-проекта без остановки разработки и потери функций?

Проведём аудит кода, составим план по приоритетам и покажем, что исправлять в первую очередь.

Бесплатный аудитПлан работ за 2 дняБез остановки релизов

Более 50 PHP-проектов приведено в порядок

Политика конфиденциальности

При оставлении заявки на ресурсе «https://gurucontext.ru» пользователи предоставляют следующие сведения:

  • Имя
  • Контактный телефон или Telegram
  • Адрес сайта пользователя (не обязательно)

Также администрация сайта получает данные об IP-адресе посетителей, типе браузера, времени нахождения на сайте и прочие подобные сведения через сервисы статистики.

Использование информации

Вся полученная информация используется администрацией «https://gurucontext.ru» исключительно в целях связи с клиентом.

Защита персональных данных

Компания «https://gurucontext.ru» обязуется не разглашать сведения, полученные от пользователей, и хранит их в защищённом виде.

Предоставление данных третьим лицам

Полученные сведения не передаются третьим лицам, за исключением случаев исполнения обязательств перед клиентом (с его разрешения) и обоснованных требований закона.

Контакты

Телефон: +7 (499) 955-47-00.
E-mail: info@gurucontext.ru.