Псевдо-инкапсуляция легаси include-ов когда нет времени рефакторить

от автора

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

Наиболее частая ситуация, которую я могу привести в пример — 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).

Приспособленец нам понадобится для запуска и псевдо-инкапсуляции легаси кода, а адаптер — для универсального доступа к нему.
Я сознательно не использую (термин|шаблон)ы: «фасад», «маппер», «декоратор» и тп. Несомненно, в зависимости от того, что содержит и какую структуру имеет легаси-файл(ы) — те или иные (термин|шаблон)ы могут быть более подходящими.
Я ставлю целью относительную универсальность, поэтому подразумеваю, что буду «адаптировать» результат приспособленца под нужды сервиса/модели.

Теперь подробнее о каждом.

Задачи приспособленца в моем случае заключаются в следующем:

  1. Подменить при необходимости директорию инклудов;
  2. Подключить необходимый файл;
  3. Буферизировать результат;
  4. Инкапсулировать глобальные переменные;
  5. Псевдо-инкапсулировать глобальные функции;
  6. Предоставить возможность доступа ко всему вышеперечисленному

Задачи адаптера:

  1. Настроить и создать приспособленца;
  2. Дать возможность работать с приспособленцем как с обычным объектом;
  3. Дать возможность переопределять любые методы и свойства;
  4. Быть супер-классом для «фасада», «маппера», «декоратора» и других структурных шаблонов

Что получаем в результате:

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/


Комментарии

Добавить комментарий

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