Legacy w tworzeniu aplikacji — czym jest, ryzyka i strategie pracy

Autor: IT Sectr Opublikowano: 2026-07-27 Czas czytania: 7 min

Legacy — to nie tylko stary kod. To działający system, który przynosi firmie pieniądze, ale hamuje rozwój. W tworzeniu aplikacji mobilnych legacy może być napisane w Objective-C, korzystać z przestarzałych bibliotek lub wzorców architektonicznych. Według raportu CAST Software (2024), średni wiek linii kodu w projektach enterprise przekracza 14 lat. Strategia pracy z legacy decyduje, czy stanie się on hamulcem, czy pozostanie zarządzalnym aktywem.

Najważniejsze

  • Legacy — kod, który działa na produkcji, ale wykorzystuje przestarzałe technologie lub podejścia
  • Utrzymanie legacy wymaga zrozumienia historycznych decyzji i ostrożnej refaktoryzacji
  • Strategia migracji — stopniowa wymiana modułów bez zatrzymywania produktu za pomocą Strangler Fig
  • Testowanie legacy — testy charakteryzacyjne utrwalają bieżące zachowanie przed refaktoryzacją
  • Wiek kodu sam w sobie nie jest problemem — problemem jest brak testów i wizji architektonicznej

Czym jest legacy w tworzeniu aplikacji

Legacy — kod lub system, które nadal działają na produkcji, ale nie spełniają już współczesnych standardów jakości. Legacy może być napisane w przestarzałym języku (np. Objective-C zamiast Swift), korzystać z nieobsługiwanych bibliotek lub wzorców architektonicznych, które dawno uznano za antywzorce.

Kluczową cechą legacy jest brak testów. Zgodnie z definicją Michaela Feathersa (2004), legacy code to kod bez testów. Jeśli nie można bezpiecznie zmienić zachowania, system ma status legacy niezależnie od wieku. Świeży kod bez testów jednostkowych — to legacy pierwszego dnia.

Legacy niekoniecznie jest złe. Dobrze zaprojektowany system w Javie 8 może być bardziej niezawodny i zrozumiały niż chaotyczny kod w Kotlinie z coroutinami. Wiek kodu nie jest wskaźnikiem jakości — ważne jest, na ile system poddaje się zmianom i rozbudowie.

Dlaczego legacy code to normalna rzecz

Każdy udany system z czasem staje się legacy. To naturalny proces: technologie rozwijają się szybciej, niż kod może być przepisany. Aplikacja napisana 5 lat temu w Swift 2 dziś jest legacy, choć w momencie tworzenia była nowoczesna.

Wartość biznesowa legacy jest często niedoceniana. System działa stabilnie, przetwarza transakcje, przechowuje dane — przepisywanie niesie ryzyko. Według Standish Group (2024), 35% projektów całkowitego przepisywania kończy się porażką. Ekonomicznie uzasadnione jest nie pozbywanie się legacy, ale nauczenie się z nim pracy.

Najlepsze strategie — stopniowa migracja, hermetyzacja starego kodu za nowymi interfejsami i zautomatyzowane testowanie. Legacy staje się problemem tylko wtedy, gdy przestaje poddawać się zmianom o przewidywalnym koszcie.

Główne oznaki systemu legacy

Brak automatycznych testów — główny wskaźnik. Jeśli po zmianie jednej linii kodu programista nie może uruchomić testów i upewnić się, że nic się nie zepsuło — masz do czynienia z legacy. Dodatkowa oznaka: procedura wdrożenia zajmuje godziny i wymaga ręcznych działań.

Dokumentacja nie odpowiada kodowi — kolejny marker. Diagramy architektoniczne są nieaktualne, komentarze opisują zachowanie, które już się zmieniło. Time-to-ramp-up dla nowego programisty przekracza miesiąc — oznaka wysokiej złożoności i niskiej utrzymywalności systemu.

Dodatkowe oznaki: monolityczna architektura bez wyraźnych granic, ręczne testowanie jako główna metoda weryfikacji, długi pipeline CI (ponad 30 minut), korzystanie z bibliotek bez aktualnych wersji i brak możliwości aktualizacji zależności bez psucia sąsiednich modułów.

Fenomen „kruchego kodu” — zmiana w jednym miejscu psuje trzy inne. To konsekwencja ścisłego sprzężenia (tight coupling), gdy moduły wiedzą o sobie zbyt wiele. Im wyższy coupling, tym szybciej system przechodzi do kategorii legacy.

Ryzyka pracy z przestarzałym kodem

Spowolnienie tempa — główne ryzyko. Dodanie prostej funkcji wymaga godzin na studiowanie kodu i dni na testowanie. Według Stripe (2024), programiści spędzają 33% czasu na pokonywanie długu technicznego, który jest bezpośrednio związany z obecnością modułów legacy w projekcie.

Utrata wiedzy — autorzy oryginalnego kodu odchodzą z firmy, a dokumentacja jest niekompletna. Nowi programiści boją się dotykać niezrozumiałych modułów, co prowadzi do efektu „zastygłego kodu”: moduł się nie rozwija, ale nadal działa. Bus factor takich systemów jest krytycznie niski.

Bezpieczeństwo — przestarzałe biblioteki zawierają znane podatności. Używanie OpenSSL 1.0.2 lub przestarzałych wersji Jacksona w projektach Java to prosta droga do incydentów bezpieczeństwa, które mogą kosztować firmę reputację i klientów.

Demotywacja zespołu — praca z legacy bez strategii jego ulepszania obniża satysfakcję programistów. Zespół przestaje być dumny z produktu, rotacja kadr rośnie, co jeszcze bardziej spowalnia rozwój systemu.

Strategie refaktoryzacji legacy

Testy charakteryzacyjne — pierwszy krok przed jakąkolwiek zmianą kodu legacy. Uruchom kod na znanych danych wejściowych i zapisz oczekiwane wyjście. Testy te utrwalają bieżące zachowanie jako specyfikację. Golden master testing — wariant, w którym dane wyjściowe porównuje się z plikiem referencyjnym.

Analiza seam — poszukiwanie punktów, w których można przerwać powiązania bez zmiany zachowania. Michael Feathers wyróżnia kilka typów seam: preprocessor seam, object seam, link seam. Object seam — najczęstszy: zastąpienie rzeczywistego obiektu testową imitacją przez interfejs.

Sprout method i Sprout class — techniki dodawania nowego kodu obok starego, a nie w jego wnętrzu. Zamiast modyfikować istniejącą metodę, utwórz nową metodę z potrzebną logiką i wywołuj ją ze starej. Minimalizuje to ryzyko zepsucia działającego kodu.

Przykład: dodanie logowania w legacy

groovy
class LegacyPaymentProcessor {
    def process(payment) {
        // 200 linii kodu legacy, których nie należy dotykać
        logPayment(payment) // sprout method
    }
    def logPayment(payment) {
        // nowy kod dodany obok legacy
    }
}

Migracja na nowoczesny stos technologiczny

Strangler Fig pattern — zalecane podejście do migracji legacy. Nowy moduł jest tworzony równolegle, ruch stopniowo przełączany ze starego na nowy. Stary moduł „umiera” naturalnie, gdy przestaje otrzymywać zapytania. Wzorzec minimalizuje ryzyko i pozwala na wycofanie zmian w razie problemów.

Branch by Abstraction — technika, w której tworzy się abstrakcję nad starą i nową implementacją. Kod klienta przełącza się na abstrakcję, stara implementacja jest stopniowo zastępowana nową. Przykład: zastąpienie warstwy sieciowej z AFNetworking na Alamofire przez wspólny protokół NetworkService.

Migracja etapowa — podział przejścia na małe kroki: hermetyzacja starego modułu → pisanie testów → utworzenie nowego modułu → równoległe uruchomienie → usunięcie starego modułu. Każdy krok kończy się stabilnym stanem systemu, co pozwala na wdrożenie zmian w dowolnym momencie.

Często zadawane pytania

Czy trzeba całkowicie przepisywać legacy?

Całkowite przepisanie to najbardziej ryzykowna opcja. Tylko 25% projektów Big Rewrite kończy się sukcesem w terminie. Lepiej stosować wzorzec Strangler Fig: wymieniaj moduły stopniowo, bez zatrzymywania produktu. Każda iteracja przynosi wartość biznesową, a ryzyka rozkładają się w czasie.

Jak rozpocząć refaktoryzację legacy bez testów?

Zacznij od testów charakteryzacyjnych: uruchom moduł na znanych danych, zapisz wynik. Golden master testing — prosty sposób na utrwalenie zachowania. Dodawaj testy za każdym razem, gdy dotykasz linii kodu. Po 6 miesiącach będziesz mieć szkielet chroniący przed regresjami.

Kiedy bardziej opłaca się nie ruszać legacy?

Jeśli system jest stabilny, nie wymaga częstych zmian i nie wpływa na szybkość tworzenia innych modułów — zostaw go. „If it ain’t broken, don’t fix it” — rozsądne podejście dla izolowanych modułów legacy o niskiej częstotliwości zmian. Ruszaj kod tylko wtedy, gdy trzeba wprowadzić zmiany biznesowe.

Jak zaktualizować zależności w projekcie legacy?

Używaj semantic versioning i aktualizuj krokami: patch → minor → major. Dla każdej biblioteki pisz testy zgodności. Dependabot lub Renovate automatyzują tworzenie PR do aktualizacji. Jeśli biblioteka jest deprecated — planuj wymianę przez abstrakcję.

Czym legacy różni się od długu technicznego?

Dług techniczny — metafora do oceny kosztów odłożonych ulepszeń. Legacy — konkretny system lub kod, które już się zestarzały. Dług techniczny można nagromadzić w miesiąc, legacy wymaga czasu. Nie każdy dług techniczny staje się legacy, ale każde legacy zawiera dług techniczny.

Podsumowanie

  • Legacy — kod bez testów, niezależnie od wieku. Świeży kod bez pokrycia to legacy pierwszego dnia
  • Wiek kodu — nie jest problemem. Problemem jest silne sprzężenie, brak testów i dokumentacji
  • Testy charakteryzacyjne — pierwszy krok przed jakąkolwiek zmianą modułu legacy w celu utrwalenia zachowania
  • Strangler Fig pattern — bezpieczna strategia migracji ze stopniową wymianą modułów
  • Sprout method — technika dodawania nowego kodu obok starego bez ryzyka zepsucia
  • 35% całkowitych przepisań kończy się porażką — migracja etapowa jest bezpieczniejsza niż Big Rewrite
  • Izolowane legacy o niskiej częstotliwości zmian lepiej nie ruszać

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również