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 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.
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.
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.
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.
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.
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
}
}
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
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.
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.
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.
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ę.
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
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.
Przeczytaj również