Сегодня хочу рассмотреть миграцию кода из далекого прошлого в современный фреймворк.
Наиболее частая ситуация, которую я могу привести в пример — str_repeat(‘очень-‘, 20) старый код, не знающий даже классов, планируется перенести или частично использовать в современном фреймворке, но переписывать тысячи строк и десятки зависимостей нет времени. Такое бывает, когда заказчик вдруг решает существенно модернизировать или развивать проект, который 10+ лет работал без изменений, а сапортил его один парттайм-олдскул-программист изредка перезагружая пару-тройку сервисов и восстанавливая пароли.
Должен отметить, что на эту статью меня натолкнуло описание «Garbage Wrapper» от search в комментариях к моей предыдущей статье.
Итак, представим, что вы уже вышли из депрессии после увиденного кода, кофе закончилось, и вот настал момент когда вы готовы начать и уже даже установили ваш любимый подходящий фреймворк, но…
После недолгого дебага выясняется, что весь проект построен на сотнях цепочек инклудов и «выдернуть» нужный кусок кода чтобы сделать из него сервис/модель невозможно.
Возьмем для примера классический файл той чудной noPSR-эпохи:
// legacy_lib.php include("settings.inc.php"); require("functions.php"); require_once("database.connection.php"); define('SOME_CONST', 'value'); $var1 = funcName(CONST_2); function get_Var2A($param1, $param2) { return functionFromAnotherInclude($param2, $param1); } class myClass { var $data = ''; function getData() { global $var1; // do somethig return get_Var2A($var1, SOME_CONST); } } include_once("specialCode.php"); $var2 = function needThis() { $obj = new myClass(); return unknownFunctionFromInclude() + $obj->getData(); } printr('{"param":' . $var2 . '; "var": ' . $var1 . '}');
На самом деле такой файл часто может достигать 1000+ строк и зависимостей в разы бывает больше.
Можно попытаться разнести этот код в классы, сервисы и тд. Но вероятность того, что он будет работать так же — устремится к нулю.
Нужно быстрое решение, которое даст возможность запустить задачу и сделать рефактор «плавнее» ну или вовсе забить отложить его на некоторое время.
Я не буду применять здесь канонические шаблоны проектирования потому что в таких ситуациях это очень субъективно. Предложу лишь воспользоваться подходами из двух этих: приспособленец (flyweight) и адаптер (adapter).
Приспособленец нам понадобится для запуска и псевдо-инкапсуляции легаси кода, а адаптер — для универсального доступа к нему.
Я сознательно не использую (термин|шаблон)ы: «фасад», «маппер», «декоратор» и тп. Несомненно, в зависимости от того, что содержит и какую структуру имеет легаси-файл(ы) — те или иные (термин|шаблон)ы могут быть более подходящими.
Я ставлю целью относительную универсальность, поэтому подразумеваю, что буду «адаптировать» результат приспособленца под нужды сервиса/модели.
Теперь подробнее о каждом.
Задачи приспособленца в моем случае заключаются в следующем:
- Подменить при необходимости директорию инклудов;
- Подключить необходимый файл;
- Буферизировать результат;
- Инкапсулировать глобальные переменные;
- Псевдо-инкапсулировать глобальные функции;
- Предоставить возможность доступа ко всему вышеперечисленному
Задачи адаптера:
- Настроить и создать приспособленца;
- Дать возможность работать с приспособленцем как с обычным объектом;
- Дать возможность переопределять любые методы и свойства;
- Быть супер-классом для «фасада», «маппера», «декоратора» и других структурных шаблонов
Что получаем в результате:
class MyLib extends LegacyAbstractAdapter { /** * Configure flyweight */ protected function configure() { $this ->setLegacyFile('legacy_lib.php') ->setLegacyPath('/path/to/includes') ; } } $myLib = new MyLib(); // получаем переменные $var1 = $myLib->var1; $var2 = $myLib->var2; // перезаписываем их $myLib->var1 = 'some new value'; // доступ к функциям $res1 = $myLib->get_Var2A($param1, $param2); $res2 = function needThis(); // получение результата выполнения файла $content = $myLib->getFlyweight()->getContent();
Теперь нам также доступна возможность декорировать, делать композиции и тд.
class MyLib extends LegacyAbstractAdapter { /** * Configure flyweight */ protected function configure() { $this ->setLegacyFile('legacy_lib.php') ->setLegacyPath('/path/to/includes') ; } // переопределенная функция public function needThis() { return 'dummy value'; } // декорирование функции public function get_Var2A($param1, $param2) { return '<font>' . $this->getFlyweight()->call('get_Var2A', [$param1, $param2]); . '</font>'; } // и тд. }
И, на мой взгляд, только в зависимости от содержания конечного класса «MyLib» — «адаптер» можно назвать как-то более подходяще.
Так же есть возможность доступа к объявленному внутри файла классу: создание инстанса, получение констант и вызов его статичных методов.
Хотя это можно сделать непосредственно обратившись к нему «по имени» — такая возможность присутствует для абстракции. На тот случай, если после рефактора такой класс перестанет существовать — достаточно будет лишь заменить один метод доступа к нему, а не все вызовы.
И, конечно же, есть ряд недостатков, о которых стоит сказать:
- Глобальные функции и классы продолжают быть глобальными и доступными «напрямую», этот подход только регламентирует доступ к ним, чтобы не «плодить» еще больше зависимого кода;
- Скорость работы. Проведя тест и обратившись к функциям 10 млн. раз — результат был получен за время, вдвое превышающее «нативный» способ. Здесь нужно учитывать нагрузку и оправданность. Хотя, на мой взгляд, в большинстве случаев это не будет существенной проблемой;
- «Синглтонность». Невозможно создать одновременно 2 приспособленца ввиду того, что инклуд можно выполнить только 1 раз
Резюме: если у вас нет нескольких месяцев на рефактор, но есть небольшой запас производительности — думаю вам это может пригодиться: github
Спасибо за внимание.
ссылка на оригинал статьи https://habrahabr.ru/post/316650/
Добавить комментарий