
О чём это?
Многие начинают писать проект для работы с 1 малой задачей, не подразумевая, что данная история приведёт к многопользовательской системе управления, ну допустим, контентом или упаси, производством. И всё вроде здорово и классно, всё работает, пока не начинаешь понимать, что тот код, который написан — состоит целиком и полностью из костылей и хардкода. Возникает насущная проблема: при добавлении новой фичи, приходится с этим кодом очень долго и долго возиться, вспоминая “а что же там такое написано то было?” и проклинать себя в прошлом. В частности данная статья предназначена для начинающих разработчиков, которым необходимость рефакторинга на первом этапе не очевидна, а сегодня они попали в неприятную ситуацию.
Допустим, на входе мы имеем жёсткий код с вёрсткой, запросами, костылями, неподдающийся иногда даже прочтению.
Как это поможет
Что приобретёт система в результате рефакторинга?
— Структурированность и модульность кода.
— Разделение UI и логики.
— Возможность написания UNIT тестов.
— Возможность написания API только для авторизированных в API приложений.
— Более простой подход к переносу на другую БД.
Частный пример случая ‘а как бы это сделать и с чего начать?’. Я предлагаю заниматься подобными исправлении последовательно, и при этом, не завершив второй этап, не начинать браться за третий. Код мы будем исправлять в рабочей системе, заранее лучше написать функциональные тесты, чтобы понимать что всё работает как надо.
Этапы
1. Алгоритмический подход.
Просмотрим наш ужасный исходный код с вёрсткой, неподдающийся тестированию (и вероятно повторяющийся в других местах). Просто и незамысловато, но последовательность подобного кода — порождает кучу известных неприятностей.
index.php
<h1>Пользователи</h1> <? $DB = new DBConnector; $DB->query(‘SELECT * FROM users LIMIT 10’); if($DB->get_num_rows()){ while($user = $DB->fetch_row()){ echo ‘<p>’.$user[‘name’].’</p>’; } } ?>
2. Процедурный подход.
Почему бы не написать функцию и не вызывать её в других местах данной системы? Переход к процедурному программированию поможет сократить код, хоть и на примере 1 вызова — выглядит страшнее.
functions.php
$DB = new DBConnector function GetUsers(){ global $DB; $DB->query(‘SELECT * FROM users LIMIT 10’); if($DB->get_num_rows()){ while($user[] = $DB->fetch_row()); return $users; } else { return array(); } } ?>
index.php
<h1>Пользователи</h1> <? include ‘functions.php’; $users = GetUsers(); foreach($users as $u) { echo ‘<p>’.$u.’</p>’; } ?>
Комментарий: Уже лучше. Если перевести все запросы к такому виду, то точно получится список неких functions.php, работающих с каким-то модулем. Таким образом модель уже отделена от UI и каждая функция может быть протестирована, так же как и последовательность вызовов таких функций.
3. Объектно-ориентированный подход.
Зачем нужно подключать все функции в системе, если достаточно работать только с необходимым набором функциональности? Например на странице простого списка пользователей — нет необходимости подключать весь список функций, таких, например как задача, или проект. Почему бы не создать класс работы с модулем, от которого будет создаваться объект работы только с этим модулем? Можно построить архитектуру так, что наследование функций одноимённых параметров методов Get (add / edit) просто-напросто будет наследоваться от какого-то суперкласса, который порождает классы работы с модулями. Также, есть вероятность, что не всегда потребуется обращение к БД для какой-либо функциональности, так значит мы сможем вообще к ней подключаться.
Напишем базовый абстрактный суперкласс, который будет подключаться к БД и иметь метод получения списка некоторых записей. От него наследуем класс работы с пользователями, создадим объект и получим записи.
BaseClass.php
abstract class BaseClass{ protected $dataBase; protected $moduleName; function __construct(){ $this->dataBase = new DBConnector; } // Наша функция получения списка с заданием количества function Get($limit=10){ $DB->query(‘SELECT * FROM ‘.$this->moduleName.’ LIMIT ’.$limit); if($DB->get_num_rows()){ while($user[] = $DB->fetch_row()); return $users; } else { return array(); } } }
UserClass.php
include ‘BaseClass.php’ class UserClass extend BaseClass{ function __construct (){ parent::construct(‘users’); } }
index.php
<h1>Пользователи</h1> <? include ‘UserClass.php’; $users = new UserClass; $users = $users->get(); //например так foreach($users as $u) { echo ‘<p>’.$u.’</p>’; } ?>
Комментарий: Ещё лучше, хоть и много кода, верно? Дело в том, что эти самые два класса — есть первый шаг к написанию API. Почему так? Дело в том, что запросы через RESTFull API делаются по адресу и очень часто там встречаются названия модулей. В зависимости от адреса можно подключать какой-либо класс (или подключить ClassLoader, благо их сейчас пруд-пруди) и вызывать тот набор функций, который дозволен данному пользователю (можно вставить обработчик прав или ролей, но это уже другая история).
Соль
Суть статьи в том, что рефакторинг практически всегда возможен и зачастую необходим даже в самой безвыходной ситуации. Все любят красивый код, но зачастую времени его писать нет и несомненно это плохая практика.
В данном случае приведены 3 этапа, в результате выполнения которых, система получит разграничение UI с логикой приложения. Это позволит подготовить вашу, перегруженную кривым кодом, систему к возможности написания Unit тестов, написания RESTfull API и к чувству самоудовлетворённости за проделанную работу. Для многих — это, конечно же, может быть очевидно, но когда просят написать стороннее приложение, или предоставить API, иногда приходится краснеть.
PS: Буду рад любым комментариям и, возможно, ссылкам на другие методики и варианты решения проблемы схожего характера.
