Legacy — не е просто стар код. Това е работеща система, която носи пари на бизнеса, но забавя развитието. В разработката на мобилни приложения legacy може да бъде написано на Objective-C, да използва остарели библиотеки или архитектурни модели. Според доклада на CAST Software (2024), средната възраст на ред код в enterprise проекти надхвърля 14 години. Стратегията за работа с legacy определя дали то ще се превърне в спирачка или ще остане управляем актив.
Основни моменти
Legacy — код или система, които продължават да работят в продукция, но вече не отговарят на съвременните стандарти за качество. Legacy може да бъде написано на остарял език (напр. Objective-C вместо Swift), да използва неподдържани библиотеки или архитектурни модели, отдавна смятани за антимодели.
Ключовото свойство на legacy е липсата на тестове. Според определението на Michael Feathers (2004), legacy код е код без тестове. Ако поведението не може да бъде променено безопасно, системата е в статус legacy независимо от възрастта. Прясно написан код без модулни тестове — това е legacy от първия ден.
Legacy не е непременно лошо. Добре проектирана система на Java 8 може да бъде по-надеждна и разбираема от хаотичен код на Kotlin с корутини. Възрастта на кода не е показател за качество — важно е доколко системата подлежи на промени и разширение.
Всяка успешна система с времето става legacy. Това е естествен процес: технологиите се развиват по-бързо, отколкото кодът може да бъде преписан. Приложение, написано преди 5 години на Swift 2, днес е legacy, въпреки че в момента на създаването си е било модерно.
Бизнес стойността на legacy често се подценява. Системата работи стабилно, обработва транзакции, съхранява данни — преписването носи рискове. Според Standish Group (2024), 35% от проектите за пълно преписване завършват с неуспех. Икономически оправдано е не да се отървем от legacy, а да се научим да работим с него.
Най-добрите стратегии — постепенна миграция, капсулиране на стария код зад нови интерфейси и автоматизирано тестване. Legacy става проблем само когато престане да подлежи на промени с предвидима цена.
Липса на автоматични тестове — основният индикатор. Ако след промяна на един ред код, програмистът не може да пусне тестовете и да се увери, че нищо не се е счупило — пред вас е legacy. Допълнителен признак: процедурата за внедряване отнема часове и изисква ръчни действия.
Документацията не съответства на кода — още един маркер. Архитектурните диаграми са остарели, коментарите описват поведение, което вече се е променило. Time-to-ramp-up за нов програмист надхвърля месец — признак за висока сложност и ниска поддръжка на системата.
Допълнителни признаци: монолитна архитектура без ясни граници, ръчно тестване като основен метод за проверка, дълъг CI pipeline (повече от 30 минути), използване на библиотеки без актуални версии и невъзможност за актуализиране на зависимости без счупване на съседни модули.
Феноменът „крехък код” — промяна на едно място чупи три други. Това е следствие от тясно свързване (tight coupling), когато модулите знаят твърде много един за друг. Колкото по-високо е coupling, толкова по-бързо системата преминава в категорията legacy.
Намаляване на скоростта — основният риск. Добавянето на проста функция изисква часове за изучаване на кода и дни за тестване. Според Stripe (2024), програмистите прекарват 33% от времето си за преодоляване на технически дълг, който е пряко свързан с наличието на legacy модули в проекта.
Изтичане на експертиза — авторите на оригиналния код напускат компанията, а документацията е непълна. Новите програмисти се страхуват да пипат неразбираеми модули, което води до ефекта на „замръзнал код”: модулът не се развива, но продължава да работи. Bus factor на такива системи е критично нисък.
Сигурност — остарелите библиотеки съдържат известни уязвимости. Използването на OpenSSL 1.0.2 или остарели версии на Jackson в Java проекти е пряк път към инциденти със сигурността, които могат да струват на бизнеса репутация и клиенти.
Демотивация на екипа — работата с legacy без стратегия за подобряване намалява удовлетвореността на програмистите. Екипът престава да се гордее с продукта, текучеството на персонала расте, което още повече забавя развитието на системата.
Характеризационни тестове — първата стъпка преди каквато и да е промяна на legacy код. Пуснете кода на познати входни данни и запишете очаквания резултат. Тези тестове записват текущото поведение като спецификация. Golden master testing — вариант, при който изходните данни се сравняват с референтен файл.
Seam анализ — търсене на точки, където свързването може да бъде прекъснато без промяна на поведението. Michael Feathers различава няколко типа seam: preprocessor seam, object seam, link seam. Object seam — най-често срещаният: замяна на реален обект с тестов заглушка чрез интерфейс.
Sprout method и Sprout class — техники за добавяне на нов код до стария, а не вътре в него. Вместо да променяте съществуващ метод, създайте нов метод с необходимата логика и го извикайте от стария. Това минимизира риска от счупване на работещ код.
class LegacyPaymentProcessor {
def process(payment) {
// 200 реда legacy код, които не трябва да се пипат
logPayment(payment) // sprout method
}
def logPayment(payment) {
// нов код, добавен до legacy
}
}
Моделът Strangler Fig — препоръчителен подход за миграция на legacy. Новият модул се създава паралелно, трафикът постепенно се превключва от стария към новия. Старият модул естествено „умира”, когато спре да получава заявки. Моделът минимизира рисковете и позволява връщане при проблеми.
Branch by Abstraction — техника, при която се създава абстракция над старата и новата имплементация. Клиентският код преминава към абстракцията, старата имплементация постепенно се заменя с новата. Пример: замяна на мрежовия слой от AFNetworking на Alamofire чрез единен протокол NetworkService.
Поетапна миграция — разделяне на прехода на малки стъпки: капсулиране на стария модул → писане на тестове → създаване на нов модул → паралелно пускане → премахване на стария модул. Всяка стъпка завършва със стабилно състояние на системата, което позволява внедряване на промени по всяко време.
Често задавани въпроси
Пълното преписване е най-рискованият вариант. Само 25% от проектите Big Rewrite завършват успешно в срок. По-добре приложете модела Strangler Fig: сменяйте модулите постепенно, без спиране на продукта. Всяка итерация носи бизнес стойност, а рисковете се разпределят във времето.
Започнете с характеризационни тестове: пуснете модула на познати данни, запишете резултата. Golden master testing — прост начин да запишете поведението. Добавяйте тестове всеки път, когато пипнете ред код. След 6 месеца ще имате скелет, който защитава от регресии.
Ако системата е стабилна, не изисква чести промени и не влияе на скоростта на разработка на други модули — оставете я. „If it ain’t broken, don’t fix it” — разумен подход за изолирани legacy модули с ниска честота на промени. Пипайте кода само когато са необходими бизнес промени.
Използвайте семантично версиониране и актуализирайте на стъпки: patch → minor → major. За всяка библиотека пишете тестове за съвместимост. Dependabot или Renovate автоматизират създаването на PR за актуализации. Ако библиотеката е deprecated — планирайте замяна чрез абстракция.
Технически дълг — метафора за оценка на цената на отложени подобрения. Legacy — конкретна система или код, които вече са остарели. Техническият дълг може да се натрупа за месец, legacy изисква време. Не всеки технически дълг става legacy, но всяко legacy съдържа технически дълг.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също