PHP та легасі-код: стратегія виживання, міграції та рефакторингу в епоху ШІ
Передмова:
Зараз на ринку з’явилася величезна кількість інструментів кодогенерації та оптимізації у вигляді різноманітних нейромереж, які чудово справляються з поставленим завданням у тих випадках, коли код написаний хоча б трохи структуровано, з використанням загальноприйнятих патернів та підходів на більш-менш сучасних фреймворках. Але як підступитися до проекту, якщо вам дістався древній код з тонною магії, закинутими ділянками та незрозумілою логікою(flow)? З чого починати та за що хапатися?
Необхідно приводити код до сучасного вигляду, або, простіше кажучи, рефакторити. Є чудова книга Мартіна Фаулера — «Рефакторинг наявного коду», яка описує базові технології виправлення коду.
Отже, вам дістався дрімучий легасі, і є завдання привести його до сучасного вигляду. Перше завдання, яке завжди стоїть перед технарями — пояснити бізнесу необхідність рефакторингу. З появою нейромереж це стало набагато простіше: здешевлення підтримки. Найчастіше старий легасі-код у процесі свого життя обростає дедалі більшою кількістю «елегантних рішень», і складність зростає в геометричній прогресії. Для підтримки кодової бази, що розширюється, з часом потрібно все більше розробників, здатних утримувати в голові всі елементи проекту. З появою нейромереж значну частину механічної роботи можна перекласти на агента, а творчу складову залишити невеликій кількості висококваліфікованих розробників без необхідності постійно розширювати штат.
Після того, як буде вирішено найскладніше завдання в рефакторингу, а саме — переконати бізнес у його необхідності та виділити на це ресурси, можна приступати до найпростішої частини — безпосередньо до планування та реалізації.
Етап 1. Планування кінцевого результату.
Цей етап необхідно реалізовувати одразу після прийняття рішення про рефакторинг, а, можливо, і до (наявність готового плану прискорить прийняття рішення, особливо якщо в плані вже підраховано бюджет).
Якщо ви читали книгу М. Фаулера «Рефакторинг», то ви маєте знати, що першим етапом є написання тестів. Але це не зовсім так: написання тестів — це перший етап технічного рефакторингу, а першим етапом технологічного рефакторингу є збір необхідної документації та опис роботи проекту.
Коли ви описуєте основний принцип роботи проекту, які абстракції в ньому присутні та як взаємодіють, то краще почати з опитування бізнесу, а зовсім не з читання старої (з величезною часткою ймовірності застарілої, неповної та неактуальної) документації і не зі спілкування з техніками/розробниками, оскільки технічна команда добре знає, як працює код, але вони не знають, навіщо він це робить.
Результатом цієї роботи мають стати створені діаграми модулів та їхніх взаємодій, визначені ключові сутності (агрегати), сервіси, події та життєвий цикл програми. На цьому етапі цілком можна використовувати нейромережу для формування діаграм та документації.
Після того, як ви склали схему вашого проекту, слід поспілкуватися з технічною командою для визначення того, як ця схема реалізована в проекті. І тут на вас вже очікують сюрпризи — цілком може виявитися, що деякі сутності та фічі в коді зовсім зайві, а найнеобхідніші ключові агрегати ніде не описані і рівномірно розмазані «іфчиками» та магією по всьому проекту. Також, маючи схему «як має бути з точки зору бізнесу» та «як воно написано в коді», ви виявите значну частину умовностей та розбіжностей, які вже точно можна буде визначити як «це фіча і це потрібно залишити» або «це легасі-код, який можна переписати/видалити». Найчастіше до 30% (а іноді й набагато більше: бували випадки, коли цей відсоток сягав і 70%) функціоналу вже не використовується і підлягає видаленню.
Маючи на руках бізнес-модель проекту та поточне кодове рішення, ви з легкістю можете визначити кінцеву мету рефакторингу:
- Який фреймворк має бути використаний (примітка на 2026 рік: якщо ви пишете на PHP, то з величезною часткою ймовірності це або Symfony, або Laravel).
- Яка версія оточення (тип і версія БД, який брокер черг, який кеш тощо).
- Архітектура та структура проекту.
Тепер, маючи уявлення про те, як код має виглядати, можна приступати безпосередньо до рефакторингу. Зовсім не обов’язково, що це бачення збережеться до кінця: невеликі зміни в процесі рефакторингу будуть відбуватися, але, маючи глобальну картину, можна з легкістю планувати подальші кроки.
Етап 2. Підготовка оточення.
Завдання оточення — впровадити інструменти контролю якості та змусити розробників (за їхнього бажання чи без нього) дотримуватися вже імплементованих стандартів. До цих інструментів належать статичні аналізатори коду та автоматичні тести (Unit або системні).
Перевіряти слід перед кожним комітом або при мержі пул-реквесту. Чудовим варіантом є додавання пайплайнів та правил у систему контролю версій (найімовірніше, Git, але можуть бути й інші варіанти), які блокують пул-реквест, якщо перевірка коду не пройдена.
Зрозуміло, якщо раніше жодних аналізаторів не було підключено, на початкових етапах перевірка видаватиме неймовірно величезну кількість помилок і просто причепити аналізатор повноцінно одразу не вийде. Тож стратегія наступна: додається обраний аналізатор коду (я віддаю перевагу PHPStan або Psalm) і конфігурується ігнорування всіх наявних помилок. Додається в пайплайн обов’язкова перевірка — це як мінімум гарантує, що нові помилки не з’являться. Надалі в процесі рефакторингу помилки будуть по одній виключатися з ігнору та виправлятися в коді, і так доти, доки повністю не буде впроваджено максимально жорсткий стандарт.
Другою обов’язковою перевіркою є тести. Якщо вони у вас вже є і покривають всі 100% функціоналу — чудово. Якщо їх у вас немає — сумно, але це можна виправити. У цьому дуже добре допомагає ШІ-агент.
Детально пояснювати, як пишуться тести, чим вони відрізняються один від одного і яких видів бувають, я не буду — для цього існує вже дуже багато книг і є навіть цілі відділи тестування. Досить пам’ятати одне золоте правило: перед внесенням будь-яких змін необхідно написати тест, що перевіряє функціональність даного коду. Бажано описувати тести на мінімально доступному рівні — але слід дивитися за станом коду.
Є чудовий опис — «піраміда тестування», у якій детально описується принцип мігрування тестів (раджу почитати). У двох словах: якщо у вас немає можливості написати unit-тести (ваш код недостатньо декомпозований, має забагато залежностей або його поведінка неоднозначна), то пишіть тести вищого рівня (аж до системних тестів). І вже маючи тести (зафіксовану поведінку), ви можете декомпозувати ваш функціонал на дрібніші юніти, покриваючи надалі вже тестами саме їх (при повному покритті старий системний тест стає непотрібним, і його можна видалити).
Етап 3. Приведення коду до однозначності та стандартів.
На даному етапі слід прокачати «антимагію» в коді. Іншими словами — усунути магічні методи та незрозумілості. До такого належать:
- Використання змінних змінних (їх можна замінити на суворий switch або match) — кількість змінних завжди кінцева і визначена в коді, тож магія цілком замінна.
Приклад коду:public function doSomeWithVar(string $varName){$this->doSome($this->$varName);}
слід замінити на:
public function doSomeWithVar(string $varName)
{
switch ($varName) {
case ’fieldOne’: $this->doSome($this->fieldOne); break;
case ’fieldTwo’: $this->doSome($this->fieldTwo); break;
case ’fieldThree’: $this->doSome($this->fieldThree); break;
}
}
(А якщо вам уже доступна версія PHP 8+, ви можете замінити switch на match). Попри віузальне збільшення коду, ви вносите в нього сувору однозначність і контролюєте місця виклику та використання кожного з параметрів. Також це дозволяє контролювати типізацію, а простий пошук в IDE підсвітить вам місця використання методів та параметрів. Аналогічно використання методів та класів має бути замінено з «магії» на суворе використання.
- Підключення класів через конкатенацію назв — у даному випадку також можна розпакувати у switch, оскільки кількість можливих класів кінцева.
Приклад, що часто зустрічається у фабриках:
PHP
public static function createSomething($type, $param1, $param2)
{
$class = __CLASS__.ucfirst($type);
if (!class_exists($class)) {
throw new UndefinedTypeException($type);
}
return new $class($param1, $param2);
}
Можна сміливо замінити на:
PHP
public static function createSomething($type, $param1, $param2)
{
switch($type) {
case 'caseOne':
return new CaseOne($param1, $param2);
case 'caseTwo':
return new CaseTwo($param1, $param2);
default:
throw new UndefinedTypeException($type);
}
}
Це дуже добре сприяє читності коду, його однозначності та розумінню того, де і коли той чи інший клас використовується.
- Різноманітні магічні рядки та числа виносяться в константи. Кожен метод має належати до якогось класу або інтерфейсу. if(method_exists($obj, $name)) замінюється на if($obj instanceof SomeInterface).
- Усі змінні, які отримуються через магічні __get та __set, мають бути описані як реальні змінні або хоча б описані в PHPDoc класу атрибутом @property.
- Усім полям, аргументам та методам має бути надано опис типів — за можливості в коді, але якщо такої можливості немає — хоча б у PHPDoc.
На даному етапі вам повноцінно стане у пригоді ваш аналізатор коду — він з легкістю виявить вищезазначені місця при відключенні відповідних ігнорів. Також тут можна активно використовувати ШІ-агента: переважна частина цієї роботи не потребує творчого мислення і є цілком рутинною, яку агент здатний виконати дуже швидко та якісно. Додавати ігнори аннотаціями в коді в даному випадку вкрай небажано, оскільки повноцінна імплементація цих стандартів дуже сильно розв’язуює руки для подальшої роботи.
Етап 4. Стандартизація коду.
На даному етапі вже виконується знайома робота з впровадження стандартів коду, але з меншою вимогливістю — складні моменти можна обертати в ігнор аннотаціями для подальшого рефакторингу.
Робота ведеться за вже налагодженою схемою:
- Відключається один з ігнорів у статаналізаторі.
- Виправляється код.
- Приступаємо до наступного ігнору.
Різниця з попереднім етапом полягає в тому, що складні кейси, які потребують значних переробок, не виправляються негайно, а ігноруються анотаціями в коді, і для кожного ігнору створюється задача в таск-менеджері (Jira або інше). Мета етапу — максимально підключити перевірки для старого коду та включити все для нового, зафіксувавши фронт робіт для майбутнього рефакторингу.
Етап 5. Початок функціонального рефакторингу.
Коли стандарти коду впроваджені, починається виправлення ділянок, раніше закинутих в ігнор. Такі зміни вже потребують аналізу: чому виникла помилка і як її виправити. Рішення можуть варіюватися від незначного рефакторингу до глибокої переробки. Кожне завдання має свою складність, а маркер ігнору сприймайте як маяк: «з цим кодом щось не так».
Етап 6. Інкапсуляція сервісів та базовий рефакторинг.
Маючи повністю підготовлений стабільний код, тепер можна без побоювань переміщувати класи, виносити методи, створювати DTO замість пачки параметрів. Тут ми просто беремо книгу М. Фаулера і виконуємо всі вказівки по черзі: усуваємо «місця з душком», максимально знижуємо зв’язаність (coupling) і підвищуємо згуртованість (cohesion).
Етап 7. Фінальний. Остаточний переїзд на обраний фреймворк/архітектуру.
Цей етап — кінцева мета міграції. Після виконання всіх попередніх етапів, ваш код стає настільки зрозумілим, що вже немає великої різниці, на якому саме фреймворку він написаний. Достатньо імплементувати заплановану архітектуру, а преїзд на конкретний фреймворк найчастіше вже й не потрібен, оскільки система вже і так достатньо зрозуміла і максимально керована.
Проте, за необхідності все ж можна переїхати — і з цим з легкістю впорається ШІ-агент буквально за пару легких промптів.
Немає коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів