/
Безпека

Застарілість коду - як зберегти сумісність

29. 07. 2022

Obsah článku

При розробці великих систем (наприклад, корпоративних додатків, спільних програмних пакетів, бібліотек, ...), де багато рівнів і розробників взаємодіють один з одним, виникає проблема, як управляти випуском нових версій коду.

Розглянемо приклад ситуації, коли ми хочемо розробити спільний пакет Composer для спільноти розробників.

Семантична версифікація

Перш ніж вирішувати проблему зворотної та прямої сумісності, потрібно з'ясувати, як відстежувати зміни в програмному забезпеченні. Наразі (2022 рік) найкращим способом версіонування всіх змін є Git. Доступ до репозиторію програмного забезпечення можна отримати, наприклад, через GitHub або GitLab. Кожна зміна програмного забезпечення має унікальний ідентифікатор, який ідентифікує кожну фіксацію та описує, що насправді відбулося.

Наступна стратегія добре спрацювала для мене при розвитку бібліотек:

На початку розробки створюється початкова фіксація в гілці master (або main), де фіксується основна файлова структура.

Для кожного нового запиту створюється окрема гілка з master, в якій ведеться робота. Коли зміна готова, майстру надсилається запит на злиття у вигляді "Pull request". Над запитом виконується перевірка коду, і якщо все гаразд, то зміна зливається в майстер.

Якщо гілка містить зворотньо несумісну зміну (BC break, від Back Compatibility Break), це повинно бути відповідним чином позначено. Метод позначення розривів БК обговорюється в наступних розділах.

Виробнича версія бібліотеки тегується за допомогою тегів, які мають наступну структуру (на основі Semantic Versioning 2.0.0):

Номер версії записуємо у форматі MAJOR.MINOR.PATCH. Нарощування номерів версій здійснюється наступним чином:

  • MAJOR - коли відбувається зміна, яка не має зворотної сумісності з іншими (API)
  • MINOR - коли додається функціональність зі збереженням зворотної сумісності
  • "PATCH" - коли виправляється помилка і зберігається зворотна сумісність

Використовуючи пре-релізи та додаючи метадані, можна уточнити інформацію. Наприклад: 1.0.0-альфа, 1.0.1-beta+2.

Детальніше про семантичну версифікацію можна прочитати на офіційному сайті: https://semver.org.

Пряма і зворотна сумісність

При розробці програмного забезпечення завжди слід думати про зворотну сумісність (нові функції та зміни повинні бути сумісними зі старим кодом), а в деяких випадках і про перспективну сумісність (поточні функції повинні бути сумісними з майбутніми змінами інтерфейсу).

Виконання обох завдань є дуже складним завданням. Не завжди є можливість внести зміни без порушення сумісності.

При внесенні змін завжди необхідно діяти поетапно і надавати користувачам достатньо часу для реакції на зміни.

У наступних розділах описано, як про це думати.

Етап 1: Позначення об'єкта як такого, що вибув

Основним типом загрози сумісності є видалення або перейменування функції, яка існувала в минулому. Найчастіше це пов'язано з тим, що змінилися аргументи, які приймає функція, або це стара логіка, яку по-новому потрібно обробляти.

На першому етапі старі частини кодексу мають бути позначені як застарілі, але жодним чином не змінені.

У PHP для цього є анотація @deprecated, яку слід писати безпосередньо над методами, функціями, властивостями, змінними, константами і взагалі всім застарілим кодом.

Також гарною практикою є написання причини, чому та чи інша річ амортизується, і як це буде змінено в майбутньому. Наприклад, дати назву новій функції або способу використання.

Реальний приклад маркування застарілого коду: Константи будуть прибрані, краще використовувати вбудований Enum (перерва в БК у зв'язку з переходом на новішу версію PHP):

class OrderNotification
{
/** @застаріло з 2022-05-24, використовуйте enum OrderNotificationType*/ /** @застаріло з 2022-05-24, використовуйте enum OrderNotificationType */
public const
TYPE_EMAIL = 'електронна пошта',
TYPE_SMS = 'текст';

Анотація @deprecated викличе лише мовчазне попередження для IDE (засобу розробки) та засобів компіляції. Він нічого не ламає.

Етап 2: Виклик нового методу/логіки

На другому етапі ми замінюємо стару реалізацію на нову, але використовуємо новий метод у старій реалізації. Це допоможе зберегти сумісність інтерфейсу непомітно для користувача.

Приклад: метод застарілий, оскільки замість нього створено новий статичний сервіс. Оскільки хтось може ним користуватися, то він просто позначає його як застарілий і внутрішньо називає нову реалізацію. Розробник в цілому може припустити, що в майбутньому цей метод буде повністю вилучений.

/** @застаріло з 2021-09-11 замість цього використовуйте Ip::get(). */
public static function userIp(): string
{
return Ip::get();
}

Етап 3: Зміна анотацій для статичного аналізу

Якщо ви використовуєте статичний аналіз, такий як PhpStan (настійно рекомендується!), Буде гарною ідеєю спочатку переписати анотації PHPDoc, перш ніж фактично змінювати типи даних. Статичний аналіз повідомить користувача про те, що щось зламано, але час виконання залишиться недоторканим.

Етап 4: Викидання повідомлення

На четвертій фазі викликається новий метод, і одночасно викидається помилка рівня "примітка". Додаток продовжує працювати, просто починає поступово накопичувати в системному журналі інформацію про те, що та чи інша функція застаріла і буде змінена або видалена. Відтепер ми будемо активно інформувати про подібні зміни. Розробник побачить помилки під час розробки або компіляції.

/** @застаріло з 2021-05-01, замість нього використовуйте UserMetaManager. */
public function getMeta(int $userId, string $key): ?string
{
trigger_error(__METHOD__ . ': Цей метод застарілий, використовуйте замість нього UserMetaManager.');
return $this->userMetaManager->get($userId, $key);
}

Етап 5: Кидання винятку

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

Етап 6: Повне видалення коду

На останньому етапі буде повністю видалено старий код. Якщо будь-який користувач не виправив залежності, його додаток буде перервано.

Серйозні порушення БК у чутливих сферах завжди повинні бути зроблені в наступному "ВЕЛИКОМУ" випуску і на них слід вказувати принаймні в одному "ВЕЛИКОМУ" випуску раніше шляхом розміщення відповідного повідомлення. Якщо цього не зробити, оновлення бібліотеки буде вкрай складним.

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
11.