Рубрики
Uncategorized

Как я рефакторингую устаревший проект Zend 1

На моей нынешней работе я поддерживал и развивал унаследованный проект Zend 1 большую часть года… Помеченный php, устаревший.

На моей нынешней работе я поддерживал и развивал унаследованный проект Zend 1 большую часть года. Надо сказать, что это худшая кодовая база, которую я когда-либо видел, с примерами из учебников многих антипаттернов, спагетти jQuery, скопированного кода и чрезмерно сложных методов. Это довольно типичный пример проекта, построенного на более старой платформе MVC неопытными разработчиками (я отвечал за создание подобных вещей в мои дни CodeIgniter).

В этой статье я расскажу о некоторых шагах, которые я предпринял, чтобы помочь взять этот унаследованный проект под контроль. Не все из них завершены на момент написания статьи, но все они помогли сделать этот явно дерьмовый проект несколько лучше. Работая с этим устаревшим проектом, я нашел книгу Пола Джонса Модернизация устаревших приложений на PHP очень полезной, и если вы работаете над аналогичным устаревшим проектом, я настоятельно рекомендую инвестировать в копию. Я также нашел Создание источников чтобы быть полезным ресурсом для определения используемых шаблонов, стратегий рефакторинга и применимых шаблонов проектирования.

Когда я впервые начал работать над проектом, репозиторий находился в Subversion и был абсолютно колоссальным – проверка его заняла два часа! Излишне говорить, что моим первым действием было перенести его в Git. Я использовал этот пост в качестве руководства, и это было довольно просто, но заняло весь мой первый день.

Следующая работа включала внесение некоторых изменений в базу данных. К сожалению, Zend 1 не включает миграции, и никто не добавил стороннее решение. Поэтому я провел некоторое исследование и наткнулся на Phinx , который представляет собой отдельный пакет миграции с запуском командной строки. Используя это, было просто начать добавлять миграции, чтобы внести любые необходимые изменения в структуру базы данных и приспособления.

В проекте использовался Composer, но только в ограниченной степени – сам фреймворк находился в папке библиотека/ , и здесь также хранилось несколько других зависимостей. Каталог поставщик/ также был проверен в системе управления версиями. Поэтому я удалил папку поставщика из Git и добавил zendframework/zendframework1 как зависимость. Это значительно уменьшило размер хранилища.

Там было ужасно много прокомментированного кода. Некоторые из них были даже закомментированы неправильно (PHP-код закомментирован комментариями HTML). Я придерживаюсь мнения, что код с комментариями лучше удалять без раздумий, так как его можно извлечь из системы управления версиями, и это может привести к путанице, поэтому я удалял любой код с комментариями, с которым сталкивался.

Одной из самых больших проблем с базой кода был высокий уровень дублирования – много кода, особенно на уровне представления, было скопировано и вставлено. Запуск PHPCPD в репозитории показал, что, не включая просмотры, около 12% кодовой базы было скопировано и вставлено, что является ужасающим количеством. Поэтому я начал агрессивно перерабатывать дублирующийся код в помощников и черты. На сегодняшний день количество дублирования без учета просмотров составляет около 2,6%, что, очевидно, является большим улучшением.

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

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

$media = new Application_Model_Media;
$media = $media->find(1);

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

Я начал с создания интерфейсов для методов, которые я хотел перенести, и заставил модели реализовать их. Затем я перенес эти методы из модели в классы репозитория и изменил все ссылки на них, прежде чем удалить интерфейсы из моделей. Теперь типичный запрос на поиск выглядит следующим образом:

$media = App\Repository\Media::find(1);

Это еще не сделано, но более половины из них были перенесены.

Как только это будет сделано, я смогу рассмотреть возможность рефакторинга логики в моделях, чтобы упростить их работу – прямо сейчас в каждой модели есть специальные сеттеры и геттеры (а также некоторая ужасная логика для их заполнения), и я рассматриваю возможность внесения изменений в них, чтобы разрешить доступ к свойствам через __get() и __set() магические методы. Другой вариант – рассмотреть возможность переноса уровня базы данных в Doctrine, поскольку таким образом мы сможем повторно использовать геттеры и сеттеры, но я еще не принял твердого решения по этому поводу.

Плохой дизайн этого приложения затрудняет тестирование, поэтому сейчас покрытие плохое. Я использовал Behat для создания базового набора приемочных тестов для некоторых наиболее фундаментальных функций, но они хрупки и могут быть нарушены изменениями базы данных. Я также добавил несколько (еще более хрупких) тестов golden master, используя технику, которую я упомяну в более позднем сообщении в блоге. У меня есть модульные тесты для трех постоянных классов и некоторых классов утилит, которые я добавил, но они далеки от желаемого уровня.

Контроллеры Fat – это анти-шаблон, который я видел и действительно отвечал за себя в прошлом, и в этом проекте их много – запуск детектора беспорядка PHP на них довольно отрезвляющий. Подавляющее большинство кодовой базы сосредоточено в контроллерах, и потребуется много времени, чтобы преобразовать ее в другие классы.

В Zend 1 действительно есть концепция помощников контроллера, и это было полезно для удаления некоторых дубликатов кода, в то время как более общий код был переработан в черты. Кроме того, утилиты, которые я добавил, включают класс коллекции в стиле Laravel, и с его помощью я смог переработать множество довольно сложных операций с массивами в гораздо более простую обработку цепных коллекций. Однако для этого все равно потребуется много усилий.

Отсутствие достойной системы событий вызвало особые проблемы, когда меня попросили добавить отслеживание того, когда пользователь просматривает определенные ресурсы, поэтому я использовал для этого пакет событий PHP League . Я также начал переносить некоторые другие функции в прослушиватели событий, но это еще одна вещь, которая займет много времени.

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

Недавно меня попросили добавить две новые модели в интерфейс администратора, и я решил, что стоит использовать новый подход, а не добавлять еще больше спагетти jQuery. Angular 1 находится на выходе, так что это не вариант, и Angular 2 + потребует использования машинописи, что, вероятно, будет проблематичным в контексте устаревшего приложения, а также сложности, являющейся проблемой. Vue был возможностью, но я всегда чувствую, что Vue пытается сделать слишком много. Вместо этого я решил пойти на React, потому что:

  • Мне всегда нравилось работать с React, хотя в прошлом у меня было не так много возможностей сделать это.
  • Мы используем Laravel Mix для обработки файлов CSS и JS (его можно использовать в проектах, отличных от Laravel), и в нем есть предустановка для React
  • React хорошо подходит для постепенного добавления в существующие проекты без необходимости полной перезаписи (в конце концов, он работает для Facebook …), поэтому с ним было просто сделать один модальный
  • Его легко тестировать – вы можете использовать тесты моментальных снимков, чтобы проверить, что он остается согласованным, а с помощью Enzyme легко перемещаться по отображаемому компоненту для других тестов

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

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

Когда я начал работать над проектом, он работал на старом сервере под управлением PHP 5.4, но были планы по переходу на новый сервер под управлением PHP 5.6. Отсутствие тестов затрудняло проверку того, что он не сломается в версии 5.6, но, используя совместимость с PHP и CodeSniffer, я смог найти большинство проблем. Я запустил его на PHP 5.6 локально во время разработки, чтобы любая новая разработка была выполнена на более современной версии. В конце концов, переход на новый сервер прошел довольно гладко.

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

Поскольку Zend 1 предшествовал пространствам имен PHP, код не был пространством имен. Это то, что я планирую исправить – классы форм и моделей должны быть простыми для пространства имен, но контроллеры немного более проблематичны. Я жду завершения репозиториев, прежде чем посмотрю на это.

Существующее решение для ведения журнала было не таким уж и замечательным. В нем были драйверы для нескольких различных решений для ведения журнала, но ничего особенно современного – одно из них предназначалось для ныне прекращенного расширения Firebug для Firefox. Тем не менее, он был довольно похож на PSR-3, так что заменить его не составило большого труда. Я установил Monolog и внес изменения в файл начальной загрузки, чтобы сохранить его в качестве регистратора в реестре Zend – таким образом, мы могли настроить множество различных обработчиков. Теперь я подключаю его к выделенному каналу Slack, когда возникает ошибка при постановке или производстве, что значительно облегчает обнаружение проблем. Это также упростило бы настройку многих других обработчиков журналов, таких как стек ELK.

Clockwork – мое обычное решение для отладки PHP, и отсутствие его поддержки в Zend 1 затрудняло работу. К счастью, довольно просто реализовать свои собственные источники данных для Clockwork. Я настроил его на использование вышеупомянутого регистратора в качестве источника данных, а также профилировщика Zend 1 . Я также добавил источник данных для реализации событий и добавил глобальную вспомогательную функцию clock() , а также функцию для компонента Symfony VarDumper. Вместе они дают мне достаточно хороший опыт отладки.

Я уже упоминал, что в последнее время часто использую консольный компонент Symfony, и именно поэтому этот проект. Zend 1 не поставляется с каким-либо консольным средством выполнения задач, и нам нужен был простой способ выполнения определенных задач, таких как:

  • Настройка хранимой процедуры
  • Анонимизация пользовательских данных с помощью Faker
  • Восстановление длительности аудио- и видеофайлов

Кроме того, мне нужна была интерактивная оболочка в стиле Laravel Tinker. Я смог сделать это с помощью Psych и консольных компонентов. Для устаревших проектов, в которых отсутствует средство выполнения консольных задач, стоит подумать о его добавлении.

Система конфигурации в Zend 1 совершенно болезненна – она требует, чтобы вы определили там несколько сред. Я интегрировал DotEnv, но только часть конфигурации была перенесена, так что там еще много работы.

Кодовая база находится в гораздо лучшем состоянии, чем была, но предстоит еще очень многое сделать. Zend 1, по-видимому, все еще работает с PHP 7.1, но не с 7.2, поэтому в какой-то момент нам, скорее всего, придется полностью оставить Zend 1. Этот процесс уже начался с того, что мы отказались от Zend_Log для Monolog, и со временем я планирую заменить различные компоненты Zend 1 другими пакетами, либо из более новых версий Zend Framework, либо из других источников. Несмотря на то, что существует множество статей о переносе Zend 1 на более поздние версии, очень немногие из них на самом деле, похоже, вдаются в подробности – конечно, нет ничего более полезного, чем пошаговое руководство.

Уровень базы данных особенно плох, и рефакторинг некоторых методов в классы репозитория – это только первый шаг к тому, чтобы взять это под контроль. Как только это будет закончено, я собираюсь начать просматривать модели и посмотреть, будут ли какие-либо другие методы иметь больше смысла в качестве статических методов в репозитории, и, возможно, переименовать некоторые из них. Затем мы можем подумать о возможности постепенного перехода на другой интерфейс базы данных (либо на более новую версию Zend DB, либо на Доктрину), либо о рефакторинге существующих моделей, чтобы они имели меньше шаблонов, используя магические методы вместо геттеров и сеттеров.

Внедрение зависимостей необходимо в какой-то момент, но сейчас это непрактично – контроллеры Zend 1 реализуют интерфейс, определяющий аргументы конструктора, поэтому вы не можете передавать какие-либо дополнительные параметры, поэтому вам придется подождать, пока контроллеры больше не будут использовать Zend 1. Я использую реестр Zend в качестве контейнера DI для бедных, поскольку он позволяет совместно использовать один объект во всем приложении, но в долгосрочной перспективе это не очень хорошее решение.

Маршрутизация также болезненна – все маршруты Zend 1 хранятся в файле начальной загрузки. Я бы предпочел использовать что-то вроде league/route , что позволило бы обрабатывать разные методы HTTP для одного и того же маршрута с использованием разных методов контроллера, что облегчает разделение обработки запросов GET и POST.

Я также хочу в какой-то момент настроить систему очередей для обработки видео- и аудиоконтента – в настоящее время она обрабатывается с помощью команды оболочки из PHP, что означает, что вы не сможете легко получить обратную связь, если что-то пойдет не так. Перенос этого в систему очередей, подкрепленную чем-то вроде Redis, очень помог бы.

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

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

Оригинал: “https://dev.to/matthewbdaly/how-im-refactoring-a-zend-1-legacy-project-4gfe”