Як працювати з legacy-системами

Насправді, по-хорошому статтю слід було б назвати так: «Як працювати з legacy-системами і зберігати психічне здоров'я». Будь-хто, хто має з ними справу, мене зрозуміє. Ця стаття — спроба узагальнення багаторічного досвіду знайомства з legacy-системами у вигляді набору підходів і практичних порад. Буду приводити приклади з власного досвіду, зокрема, роботи з успадкованою Java-системою.

До речі, матеріалів про роботу з legacy в структурованому вигляді майже немає — обидва джерела, присвячених саме їй, наведені в кінці матеріалу. І це при тому, що на legacy доводиться мало не половина всього аутсорсингу.

Особливості legacy

Legacy — в перекладі з англійської «спадщина», та ця важка спадковість. Майже всім доводилося, прийшовши в проект, отримати код десятирічної давності, написаний кимось іншим. Це і є успадкований код — тобто код істо(е)історичний, який часто буває настільки жахливий, що виявляється взагалі незрозуміло, як з ним працювати. І якщо нам дістається legacy-система, то ми, крім старого коду, також маємо:
— застарілі технології;
— неоднорідну архітектуру;
— нестача або навіть повна відсутність документації.

З усім цим нам потрібно розбиратися і якось жити далі. І тут без хорошого почуття гумору, мабуть, не обійтися — ті, хто сприймають життя занадто серйозно, зазвичай тікають відразу ж, як тільки побачать справжнє legacy.

Насправді, legacy-система — це не так вже страшно, і ось чому: якщо система жила всі ці десять років і до цих пір працює, значить, якийсь толк від неї є. Може бути, вона приносить хороші гроші (на відміну від вашого останнього стартапу на новітніх технологіях). Крім того, такий код системи щодо надійний, якщо він зміг так довго виживати в продакшне. Тому вносити в нього зміни потрібно з обережністю.

Перш за все, потрібно зрозуміти дві речі:
1. Ми не можемо нешанобливо ставитися до системи, яка заробляє мільйони, або до якої звертаються тисячі людей на день. Як би погано вона не була написана, цей огидний код дожив до продакшну і працює в режимі 24/7.
2. Саме ця система приносить реальні гроші, робота з нею пов'язана з великою відповідальністю. З самого початку ясно, що це не стартап у стіл, а те, з чим користувачі будуть працювати вже завтра. Це передбачає і дуже високу ціну помилки, причому справа тут не в претензії клієнта, а в реальному стані речей.

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

Зворотний інжиніринг

Для успішної роботи з успадкованими системами нам доведеться багато користуватися прийомами reverse engineering.

Перш за все, потрібно уважно читати код, щоб точно розуміти, як саме він працює. Це обов'язково — адже достатньої документації у нас, швидше за все, не буде. Якщо ми не зрозуміємо ходу думок автора, то будемо робити зміни, наслідки яких виявляться не цілком передбачуваними. Щоб убезпечити себе від цього, потрібно вникати ще і в суміжний код. І при цьому рухатися не тільки вшир, але й углиб, докопуючись до самого нутра. Звідки викликається метод з помилкою? Звідки викликається викликає його код? В legacy-проекті «call hierarchy» і «type hierarchy» використовується частіше, ніж що б то не було інше.

Звісно, доведеться проводити багато часу з відладчиком — по-перше, щоб знаходити помилки, і по-друге, щоб зрозуміти, як все працює — тому що логіка обов'язково буде такий, що по-людськи прочитати її ми не зможемо. Власне кажучи, дебажити потрібно буде взагалі всі, в тому числі і open source-бібліотеки. Навіть якщо проблема десь в Spring, значить, доведеться налагоджувати і, можливо, перезбирати Spring, якщо можливості його оновити не виявиться. Саме так нам неодноразово доводилося робити, причому не тільки зі Spring.

Що стосується документації, не зайвим буде вдатися до того, що я б назвав промислової археологією. Дуже корисно буває відкопати де-небудь стару документацію і поговорити з тими, хто пам'ятає, як писався дістався вам код. Можливо, десь є старий Confluence, можливо, хоча б дамп його бази, де ви що-то, може бути, і знайдете. Іноді це буває простіше, ніж сидіти з дебагером. Але нерідко там виявляться тільки документи, які не мають прямого відношення до коду, наприклад, керівництва з налаштування серверів, які все в принципі бояться чіпати.

Використовуючи ці прийоми, рано чи пізно ви почнете більш або менш розуміти код. Але щоб ваші зусилля не пішли прахом, ви повинні обов'язково відразу ж документувати результати своїх досліджень — для цього я раджу малювати блок-схеми або діаграми послідовності (sequence diagrams). Звичайно, вам буде лінь, але робити це потрібно точно — через півроку без документації ви самі в цьому коді будете копатися як в перший раз. А якщо через півроку з кодом будете працювати вже не ви, ваші послідовники будуть дуже вдячні вам за наявну документацію.

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

Процес розробки

Отже, що потрібно і що не потрібно робити:

Не переписувати. Найважливіше тут — вчасно бити себе по руках і не намагатися переписати весь код заново. Прикиньте, скільки людино-років для цього потрібно — навряд чи замовник захоче витратити стільки грошей на перероблення того, що вже і так працює. Це стосується не тільки системи в цілому, але і будь-якої її частини. Вам, звичайно, можуть дати тиждень на те, щоб у всьому розібратися, і ще тиждень на те, щоб щось виправити. Але навряд чи дадуть два місяці на написання частини системи заново.

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

Дотримуватися бізнес-інтереси. Потрібно завжди пам'ятати, що будь-які завдання обумовлені насамперед цінністю для бізнесу. Якщо ви не доведете замовнику необхідність тих чи інших змін з точки зору бізнесу, цих змін не буде. А для того, щоб переконати замовника, ви повинні спробувати встати на його місце і зрозуміти його інтереси. Зокрема, якщо вам хочеться провести переформатування тільки тому, що код погано читається, вам не дадуть цього зробити, і з цим потрібно змиритися. Якщо вже зовсім несила, реорганізовувати код можна по-тихому і потроху, розмазуючи роботу з бізнес-тикетам. Або переконати замовника в тому, що це, наприклад, скоротить час, необхідний для пошуку помилок, а значить, в кінцевому підсумку скоротить витрати.

Тестувати. Зрозуміло, що тестування необхідно в будь-якому проекті. Але при роботі з legacy-системами тестування потрібно приділяти особливу увагу ще й тому, що вплив внесених змін не завжди передбачувано. Тестувальників знадобиться не менше, ніж розробників, в іншому випадку у вас повинно бути все просто неймовірно добре з автоматизацією.

У нашому проекті тестування складалося з наступних фаз:
1. Верифікація, коли реалізований функціонал фічі перевіряється в окремій гілці.
2. Стабілізація, коли перевіряється гілка релізу, в якій всі фічі злиті разом.
3. Сертифікація, коли все те ж саме проганяється ще раз на трохи інших тест-кейсах в сертифікаційному оточенні, максимально наближеному до продакшну за характеристиками заліза і конфігурації.

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

Формалізувати DevOps і реліз. При роботі з legacy-системою важливо налагодити все, що стосується DevOps та інших практик, безпосередньо не пов'язаних з розробкою. Зокрема, дуже добре спільно з девопсами на стороні замовника прописати певну процедуру релізу, кожен крок якої буде суворо документований. Тільки тоді процес стає передбачуваним і зрозумілим для кожного з учасників.

Релізна процедура може бути, наприклад, такою. Коли закінчується розробка і пройдені дві або три фази тестування, ми пишемо лист DevOps-команді за 36 годин до передбачуваного часу релізу. Після цього зідзвонюємося з девопсами і обговорюємо всі зміни оточень (ми повідомляємо їм про всі зміни в БД і конфігурації). На кожну зміну у нас є документ (тікет в Jira). Потім під час релізу всі причетні до цього збираються разом, і кожен говорить, що він зараз робить: «Ми залили базу», «Ми перезапустили такі сервери», «Ми пішли проганяти регресійні тести в робочому оточенні». Якщо ж щось йде не так, запускається процедура відкату релізу, точно описана в початковому документі на реліз — без такого документа ми обов'язково що-небудь забудемо або напутаем (згадайте, у який час доби зазвичай відбуваються релізи).

Вибудувати branching strategy. Основні моделі бренчинга давно описані на сайті того ж Atlassian, їх можна адаптувати під ваші потреби. Головне — ні в якому разі не коммитить зміни відразу в транк: повинні бути stable trunk і feature branches. Я раджу робити релізи з релізних гілок, а не з транк. Тобто у вас є транк, від якого відходять гілки на конкретні фічі, відповідні тикетам в Jira. Коли ви закінчили розробку в спринті, ви збираєте окрему релізну гілку з готових фіч і її сертифицируете. Якщо ж щось піде не так, з такої гілки можна буде легко усунути те, що з якоїсь причини з релізу в результаті випадає. Коли ж реліз відбувся, релізна гілка вливається в stable trunk.

Контролювати якість коду. І нарешті, code review — це, здавалося б, досить очевидна практика, до якої вдаються чомусь далеко не у всіх проектах. Дуже добре, якщо кожна частина коду перевіряється більш ніж однією людиною. Навіть у дуже сильній команді в процесі code review обов'язково виявляються якісь косяки, а якщо дивляться кілька осіб, кількість виявлених косяків зростає. Іноді найстрашніше знаходить третій або четвертий reviewer. Але щоб уникнути як саботажу, так і зайвого фанатизму, необхідно домовитися, скільки review достатньо для того, щоб вважати фічу готовою.

Для перевірки можна використовувати пул-реквесты (звичайно, якщо у вас Git), далі є Crucible і FishEye — обидва прикручуються до Jira. І нарешті існує дуже зручний інструмент Review Board, який працює і з SVN, і з Git. Він дозволяє послати запит на перевірку коду, який збере в собі всі зміни в даному feauture branch.

Управління проектом

Підбір команди. Найперше, що повинен пам'ятати Team Lead або PM при наборі людей в проект — далеко не всім розробникам підходить робота з legacy-системами. Навіть якщо людина пише чудовий код, не факт, що він зможе цілими днями сидіти з дебагером або документувати чужий код. Для роботи з legacy, крім технічних навичок, потрібні ще певні особистісні якості — гарне почуття гумору, самоіронія і, звичайно ж, терпіння. На ці якості потрібно звертати увагу при підборі людей в команду, а якщо хтось не зійшовся з legacy характерами, то не воювати з ним, а замінювати. Заміна людини в проекті в подібному випадку не вовчий квиток, а полегшення і для нього, і для команди.

Дурні питання. Тимлид не повинен соромитися задавати команді «дурні» питання і нагадувати про всіх перерахованих вище прийомів роботи. «Я накотив свіжий код, і тепер нічого не працює!» — «А яку гілку ти взяв? А базу оновив? А що в логах? А дебажил?» Незважаючи на всю свою простоту, такі діалоги — невід'ємний елемент нашої роботи, і в особливості з legacy-проектами. Потрібно тримати все в голові і не втомлюватися знову і знову нагадувати про якихось очевидних і не дуже очевидні речі. Без цього, повірте, не обійтися!

Процес, або «тут так прийнято». В силу американської плинності кадрів нові менеджери з боку замовника приходять в проект частіше, ніж нам хотілося б. І багато хто з них, ще не розібравшись у специфіці legacy, намагаються впроваджувати практики та рішення зі свого попереднього досвіду. Їм потрібно терпляче пояснювати, чому тут прийнято саме так, а не по книжці. Спочатку такі речі донести буває важко, але в кінцевому підсумку або замовник погодиться з вами, або ви разом прийдете до компромісного рішення.

У роботі з legacy-системами дійсно важливий правильно вибудуваний, зрозумілий і прозорий процес: Jira (або аналог) обов'язково повинна відображати реальний стан справ в даний момент. Всі вимоги повинні бути ясно сформульовані, а процеси чітко прописані. Вся ця Jira-бюрократія точно окупиться, сильно знизивши ступінь ентропії у проекті. Так, коли до вас прийде замовник і вимагає терміново зробити нову фічу, ви зможете просто показати заповнений розклад. Тоді він легше зможе зрозуміти, що чимось доведеться пожертвувати.

Що стосується эстимэйта (ви ж використовуєте Planning Poker, правда?), то оцінювати завжди потрібно з запасом, щоб бути готовим до сюрпризів — як ми вже говорили, впливу в незнайомому нам коді часто неясні й часом може вилазити щось зовсім несподіване і в несподіваних місцях. Так, у нас в проекті був випадок, коли зміни в простому CSS зламали частину бізнес-логіки: хтось поставив в JS перевірку на колір елемента інтерфейсу.

Бізнес, tech debt і SWAT. При роботі з legacy-системами потрібно намагатися протистояти потоку бізнес-вимог, які замовник буде вам безперервно постачати. Замовник не завжди усвідомлює ризики, пов'язані зі стабільністю системи, тому вам доведеться постійно про них нагадувати. Боротися з цими ризиками можна двома способами: балансуванням бізнес і стабілізаційних завдань у кожному спринті або окремими стабілізаційними проектами. Оптимальним здається баланс 70 на 30, коли 30% часу кожного спринту ви займаєтеся стабілізацією. Втім, замовник швидше за все не дасть вам зробити все, що ви хочете — тому записуйте технічний борг по мірі виявлення. З цього tech debt ви будете брати завдання на вищезазначені 30%. А може, замовник погодиться на стабілізаційний проект, особливо, якщо ви покажете йому tech debt у відповідь на запитання, чому все в черговий раз впало.

Для екстрених випадків я раджу мати SWAT — «групи спеціального призначення», які зможуть швидко вирішувати непередбачені проблеми в будь-який час доби. Адже якщо система раптом заваляться, замовник тут же почне вам дзвонити і в 2 години ночі, і в 4 ранку — це ми перевірили на власному досвіді. Тому добре буває домовитися, хто в який час чергує на випадок таких пригод. Це повинні бути швидко розуміють люди, які можуть сидіти допізна, відповідно, найчастіше, не сімейні. Але основне їх завдання — це все-таки, брейнстормінг днем. В цьому є особливий інженерний кайф — у стресовій ситуації оперативно знаходити помилки в чужому коді, розуміючи, що рятуєш світ в рамках окремо взятої системи.

Приклади оптимізації

А тепер коротко розповім про способи оптимізації, якими ми користувалися в різний час.

По-перше, потрібно відмовитися від традиції щоденних перезапусків, якщо так було прийнято в проекті. Однак робити це треба, звичайно, з обережністю — продовжувати перевіряти логи і стежити за всім, що може призвести до падіння системи, і боротися з цим. У нас була система, які перезапускалась кожну ніч, так як не могла прожити й двох діб з-за memory та інших leaks — тепер же вона цілком стабільно працює від релізу до релізу два-три тижні (за рідкісними винятками, про які ми зазвичай дізнаємося в 4 ранку).

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

Дуже важливий момент — прохід по всіх податків та складання окремого епіка. Бувають, звичайно, замовники, які довго не дають доступу до продакшн-ловга. У нас, наприклад, так тривало півроку, після чого стався переломний момент, коли нас попросили подивитися логи продакшну. Перегляд затягнувся на всю ніч. У системі, що працювала, як вважалося, штатно і стабільно, нормальні логи траплялися лише іноді — в основному ж записи були зі зрушенням вправо і починалися з «at». Це були суцільні стектрейсы, і їх набиралося на десятки мегабайт на добу. Звичайно, ми завели епік в Jira і створили тікети на окремі exceptions. Потім нам довелося кілька місяців вибивати час на стабілізаційний проект. У підсумку ми виправили безліч помилок, про які ніхто не здогадувався, і зробили логи інформативними. Тепер будь стектрейс в них — дійсно ознака нештатної ситуації.

Ще раджу звертати увагу на третьесторонние залежності як на front-end (Google Tag Manager, Adobe Tag Manager тощо), так і на back-end. Наприклад, якщо у нас на сторінці є JavaScript зі сторонніх ресурсів, потрібно подивитися, загорнуті ці скрипти в try..catch блоки. У нас були випадки, коли сайт падав із-за того, що ламався якийсь скрипт на стороні. Також важливо передбачати можливість недоступність будь-яких зовнішніх ресурсів.

Ну і останнє: стежте за всім, чим тільки можна, і грамотно агрегируйте логи. Адже у вас може бути 12 продакшн-серверів, і вас можуть попросити їх подивитися логи, що точно потрібно робити не через tail. Ми використовували ELK — в'язку Elastic search — Logstash — Kibana. Дуже корисний моніторинг: ми навісили Java Melody на всі сервери і отримали величезну кількість нової інформації, на підставі якої багато виправили, ощаслививши замовника.

P. S. Корисні посилання:
— Віктор Поліщук: «Legacy Projects. How To Win The Race» — доповідь російською про роботу з успадкованими системами, заснований на конкретних прикладах з досвіду доповідача.
— Michael Feathers: «Working Effectively with Legacy Code» — книга по темі, яку я, чесно кажучи, не читав. В основному вона про рефакторинг. У відкритому доступі є велика презентація автора з тією ж назвою, за якою ви зможете зрозуміти, чи варто купувати цю книгу.

Опубліковано: 07/11/16 @ 08:00
Розділ Різне

Рекомендуємо:

Java дайджест #29: Make Java great again
17 успішних кейсів, як збільшити трафік сайту
Кейс: Зростання трафіку для магазину виробів з штучного каменю з 600 до 6000 чоловік в місяць
Кар'єра в IT: посада HTML coder (верстальник)
Як українські IT-компанії відсвяткували Хелловін 2016