Статьи

Рефакторинг от спагетти PHP

В этой статье реализован шаблон Beginner.

Иногда вы должны сделать шаг назад от дискуссий о связях, сплоченности, шаблонах и катах, чтобы научить тех из нас, кто обладает процедурным мышлением. В этой статье я надеюсь дать некоторые начальные советы для членов сообщества PHP, которые готовы отказаться от концепции OneSingleProcedure (TM), чтобы охватить объектный мир. В частности, перечислить некоторые специфические проблемы, с которыми сталкиваются стандартные приложения PHP и которые следует учитывать при реинжиниринге существующей системы или создании новых объектов; и следить за (единичной) тестируемостью, которая значительно улучшается при введении объектов поверх процедур или просто спагетти.

OneSingleProcedure (TM)

PHP-программист старого стиля, когда сталкивается с новой функцией, пишет такой вид скрипта:

<?php
$connection = mysql_connect(...); // not really different with PDO
$parameter = $_GET['name'] ? $_GET['name'] : '';
if (!validate($parameter)) {
    echo "<p>Error!</p>";
    exit();
}
mysql_query("INSERT INTO ... ");
echo "<p>Success!</p>";

Суть критики заключается не в том, что mysql _ * () небезопасен, а в том, что этот скрипт сочетает в себе множество различных проблем, с которыми PHP-программисты часто сталкиваются, и которые они должны научиться разделять:

  • домен HTTP: параметры GET и POST, заголовки и, соответственно, распечатка ответа в UTF-8
  • доступ к базе данных (и источнику данных в целом): подключения, запросы или получение данных из веб-служб
  • логика домена, такая как валидация и код приложения

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

декомпозиция

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

  • E / R моделирование приводит нас к разделению проблемы на таблицы базы данных и их строки. Это то, что разработчики PHP обычно являются экспертами.
  • Процедурная декомпозиция (сверху вниз) декомпозирует OneSingleProcedure (TM) на более мелкие, которые, в свою очередь, состоят из нескольких вызовов функций. Обычно эта декомпозиция выполняется для извлечения кода для повторного использования, как в методе validate (), который мы видели ранее, и строго основана на времени: каждая подпрограмма является шагом для выполнения.
  • Разложение Парнаса , которое мы должны пытаться применять в объектах и ​​классах, основано на сокрытии информации: каждая часть скрывает проектное решение или техническую проблему; эти части могут индивидуально измениться в будущем, не затрагивая целое, или могут быть реинжинирированы сами по себе, когда запрос на изменение приведен в соответствие с существующей декомпозицией.

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

Но я просто хотел вставить строку в таблицу сообщений !

Нет, ты не сделал; ты хотел:

  • отобразить некоторые входные данные HTTP, такие как текстовые и двоичные файлы, в набор структур данных в памяти, таких как массивы и другие переменные.
  • Проверьте этот ввод, чтобы проверить его соответствие правилам домена (например, «Текст сообщения не должен быть пустым»).
  • Измените постоянное состояние приложения, вставив новую строку.
  • Создайте HTML-ответ для браузера пользователя.

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

<?php
class ForumPostsTest extends PHPUnit_Framework_TestCase
{
    public function testAPostIsSavedAfterValidationAndAParagraphResponseIsShown()
    {
        // apart from the overly long test name, what code could I write in order to test newpost.php?
    }
}

Вывод

Как правило, имейте в виду, что если вы пишете любые два элемента из списка HTTP, validate (), SQL, HTML в одном и том же исходном файле, что-то пошло не так. Требуется время, чтобы забыть процедурное мышление и начать разлагать обязанности вместо данных; эмуляция существующих архитектур и их расслоение — это не точка прибытия, а хорошее начало для написания чего-то более понятного, чем сценарий на 4000 строк.