Как переписать большой проект или безболезненный для бизнеса рефакторинг
Устаревание кода, трудности с поддержкой, непредсказуемые баги — эти термины один за другим появляются в жизни разработчика по мере разработки продукта. И если первое — это скорее интересы разработчика, то последнее — это прямая проблема бизнеса.
В этой статье я хочу поделиться опытом переписывания большого проекта и как бонус привести пару кусков кода, которые помогли нам и, надеюсь, помогут вам начать этот интересный путь.
Разбор полетов
Проблемы
- Прибегает начальник с воплями «У нас ничего не работает, главный клиент под угрозой!»;
- или менеджер с просьбой прикрутить нереализуемую фишку;
- реже мы, разработчики, настолько устаем копаться в говне «легаси»-коде, что решаем переписать всё.
Насчет последнего пункта нужно добавить, что ситуацию с новым человеком в команде, который рвется всё переписать, я не рассматриваю, однако он может запросто аргументировать описанный подход для развития проекта.
Задачи
- Перевести проект на современную архитектуру
- Обеспечить минимальные затраты на рефакторинг
Принципиальная схема реализации
Наш проект был изначально написан на Kohana, переписывали мы его на Symfony2, поэтому все примеры приведены в контексте этих систем. Однако, данный подход можно применять с любыми фреймворками. Необходимое требование — единая точка входа в приложение.
Изначально приложение обрабатывает запросы пользователя через точку входа «app_kohana.php»
Мы будем оборачивать начальную точку входа в новой системе, организовывая своеобразный «прокси».
Рефакторинг
Контроллер — обертка для старой системы
- Разворачиваем параллельно две системы (kohana + symfony)
- Меняем точку входа на новую (symfony)
- Организуем универсальный контроллер, который по умолчанию будет пробрасывать все запросы в старую систему
И если с первыми двумя пунктами проблем возникнуть не должно, то третий представляет интерес, потому как в нем могут обнаружиться подводные камни.
Первое, что приходит в голову — обернуть инклюд в ob_start. Так и сделаем:
В таком формате система уже работает, но спустя какое-то время прилетает первый баг. Например, некорректная обработка ajax-ошибок. Или на сайте ошибки отдаются с кодом 200 вместо 404.
Тут мы понимаем, что буфер проглатывает заголовки, поэтому их нужно обрабатывать явным образом
После этого полёт нормальный.
Проблемы старой системы, влияющие на функционирование новой
У нас в системе нашлись места, где в конце работы контроллера радостно вызывался exit(). Это практикуется, например, в Yii (CApplication::end()). Особой головной боли это не доставляет до тех пор, пока не начинаешь использовать событийную модель в новой системе и обрабатывать события, случающиеся после выполнения контроллера. Самый яркий пример — Symfony Profiler, который прекращает работать для запросов с exit'ом. Данный случай нужно иметь в виду и при необходимости предпринимать соответствующие меры.
ob_end_*()Необдуманное использование функций ob_end легко может поломать работу новой системы, очистив буфер нового прокси-контроллера. Следует так же иметь в виду.
Kohana_Controller_Template::$auto_renderПеременная отвечает за автоматическую отрисовку полученных из контроллера данных в глобальном шаблоне (может сильно зависеть от используемого шаблонизатора). Во время адаптации новой системы это может сэкономить время на отладку в местах, где, например, json выводится простым echo $json; exit();. Контроллер примет примерно следующий вид:
О чем еще стоит позаботиться
- Переименовываем app.php в app_kohana.php
- Точку входа симфони размещаем в app.php
- Profit
Жизнь после рефакторинга
Новые контроллеры
Все новые контроллеры мы стараемся писать в symfony. Разделение происходит на уровне роутинга, перед «универсальным» маршрутом дописывается нужный, и Kohana дальше не загружается. Пока мы пишем в новой системе только ajax-контроллеры, поэтому вопрос с переиспользованием шаблонов (Twig) остается открытым.
БД и Конфигурация
Как продолжать: вынесение функционала в сервисы
Дальнейшей движение из коханы в симфони очень хорошо укладывается в вынесение функционала в сервисы симфони и использовании их в старой системе через DI-контейнер. Так сложилось, что DI-компонент мы начали использовать до подключения симфони в проект, поэтому этот процесс прошел довольно гладко, но ничто не мешает делать это с нуля. Основной задачей будет прокинуть DI-контейнер из симфони в кохану. Мы сделали это в кохана-стиле через статическое свойство, в другом фреймворке можно найти соответствующий подход.
А дальше нужно провернуть еще пару махинаций, чтобы положить в этой свойство DI-контейнер между инициализацией коханы и выполнением кода контроллера. Для этого разделим наш файл инициализации app_kohana.php на две части, выделив непосредственно инициализацию системы и сам запуск контроллера.
Модифицируем наш контроллер, проделывая похожие с app_kohana.php операции, но добавляя между инклюдами проброс контейнера
После этого мы в старой системе можем использовать DI-контейнер и все объявленные в новой системе сервисы, включая EntityManager и новые модели доктрины.
Напоследок
Плюсы реализации
- Мы сделали первый шаг для дальнейшего развития системы.
- Новая система независима от старой. Весь новый код работает без участия старого
- Минимум потраченного времени
Минусы реализации
- Дополнительные накладные ресурсы на «обертку» во время работы со старой частью системы. Однако, по сравнению с задержками в старой системе, оверхедом (как по памяти, так и по процессору) можно пренебречь.
- Новая система независима от старой. Мы не можем использовать старый код в новой, но это скорее плюс, раз уж мы решились переписывать.
- Приходится поддерживать модели в двух местах.
Спасибо, что дочитали до конца, желаю успехов в рефакторинге, смахните накопившуюся пыль со старого кода! И простите за ужасные шрифты на диаграммах :(