Как переписать большой проект или безболезненный для бизнеса рефакторинг

Как переписать большой проект или безболезненный для бизнеса рефакторинг

Устаревание кода, трудности с поддержкой, непредсказуемые баги — эти термины один за другим появляются в жизни разработчика по мере разработки продукта. И если первое — это скорее интересы разработчика, то последнее — это прямая проблема бизнеса.

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

Разбор полетов

Проблемы

  1. Прибегает начальник с воплями «У нас ничего не работает, главный клиент под угрозой!»;
  2. или менеджер с просьбой прикрутить нереализуемую фишку;
  3. реже мы, разработчики, настолько устаем копаться в говне «легаси»-коде, что решаем переписать всё.

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

Задачи

  1. Перевести проект на современную архитектуру
  2. Обеспечить минимальные затраты на рефакторинг

Принципиальная схема реализации

Наш проект был изначально написан на Kohana, переписывали мы его на Symfony2, поэтому все примеры приведены в контексте этих систем. Однако, данный подход можно применять с любыми фреймворками. Необходимое требование — единая точка входа в приложение.

Изначально приложение обрабатывает запросы пользователя через точку входа «app_kohana.php»

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

Рефакторинг

Контроллер — обертка для старой системы

  1. Разворачиваем параллельно две системы (kohana + symfony)
  2. Меняем точку входа на новую (symfony)
  3. Организуем универсальный контроллер, который по умолчанию будет пробрасывать все запросы в старую систему

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

Первое, что приходит в голову — обернуть инклюд в 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();. Контроллер примет примерно следующий вид:

О чем еще стоит позаботиться

  1. Переименовываем app.php в app_kohana.php
  2. Точку входа симфони размещаем в app.php
  3. Profit

Жизнь после рефакторинга

Новые контроллеры

Все новые контроллеры мы стараемся писать в symfony. Разделение происходит на уровне роутинга, перед «универсальным» маршрутом дописывается нужный, и Kohana дальше не загружается. Пока мы пишем в новой системе только ajax-контроллеры, поэтому вопрос с переиспользованием шаблонов (Twig) остается открытым.

БД и Конфигурация

Как продолжать: вынесение функционала в сервисы

Дальнейшей движение из коханы в симфони очень хорошо укладывается в вынесение функционала в сервисы симфони и использовании их в старой системе через DI-контейнер. Так сложилось, что DI-компонент мы начали использовать до подключения симфони в проект, поэтому этот процесс прошел довольно гладко, но ничто не мешает делать это с нуля. Основной задачей будет прокинуть DI-контейнер из симфони в кохану. Мы сделали это в кохана-стиле через статическое свойство, в другом фреймворке можно найти соответствующий подход.

А дальше нужно провернуть еще пару махинаций, чтобы положить в этой свойство DI-контейнер между инициализацией коханы и выполнением кода контроллера. Для этого разделим наш файл инициализации app_kohana.php на две части, выделив непосредственно инициализацию системы и сам запуск контроллера.

Модифицируем наш контроллер, проделывая похожие с app_kohana.php операции, но добавляя между инклюдами проброс контейнера

После этого мы в старой системе можем использовать DI-контейнер и все объявленные в новой системе сервисы, включая EntityManager и новые модели доктрины.

Напоследок

Плюсы реализации

  • Мы сделали первый шаг для дальнейшего развития системы.
  • Новая система независима от старой. Весь новый код работает без участия старого
  • Минимум потраченного времени

Минусы реализации

  • Дополнительные накладные ресурсы на «обертку» во время работы со старой частью системы. Однако, по сравнению с задержками в старой системе, оверхедом (как по памяти, так и по процессору) можно пренебречь.
  • Новая система независима от старой. Мы не можем использовать старый код в новой, но это скорее плюс, раз уж мы решились переписывать.
  • Приходится поддерживать модели в двух местах.

Спасибо, что дочитали до конца, желаю успехов в рефакторинге, смахните накопившуюся пыль со старого кода! И простите за ужасные шрифты на диаграммах :(

📎📎📎📎📎📎📎📎📎📎