W świecie programowania istnieje pewien dobrze znany, choć rzadko lubiany bohater drugiego planu. Nie przemawia na stand-upach, nie zbiera lajków na LinkedInie, ale prędzej czy później każdy programista się z nim spotka. To system legacy. Brzmi groźnie? Być może. Ale czy naprawdę zasługuje na tak złą sławę, jaką zwykle mu przypisujemy?
Pytanie nie „czy”, tylko „kiedy”…
Systemy klasy legacy to codzienność programistów zarówno tych z wieloletnim doświadczeniem, jak i osób, które dopiero co przesiadły się z bootcampa do projektu komercyjnego. To nie jest pytanie czy taki system się trafi.
To raczej kwestia kiedy i jak bardzo zaboli.
I choć systemom towarzyszy aura frustracji, dezorientacji i narzekań przy kawie, warto spojrzeć na nie z innej perspektywy. Legacy to nie tylko spaghetti kod i zapomniane technologie. To także potężne źródło wiedzy. Tylko trzeba wiedzieć, jak ją wydobyć.
Definicji jest wiele, chociaż problem jeden
Co właściwie oznacza „legacy system”? To zależy, kogo zapytamy.
Dla niektórych to każdy kod, którego się nie napisało samemu. W tej definicji nawet wczorajszy merge kolegi może być „legacy”. Inni uznają za takie systemy te, których zmiana przypomina zabawę z wieżą z zapałek – porusz jedną, a wszystko runie.
Michael Feathers, autor książki Working Effectively with Legacy Code, definiuje system legacy jako taki, który nie posiada porządnych testów automatycznych. I coś w tym jest! Brak testów często oznacza strach przed dotykaniem kodu, a to pierwszy krok do frustracji.
Ale najtrafniejszą definicją wydaje się ta, która łączy długi cykl życia systemu z jego kruchą strukturą i niską testowalnością. Legacy to system, który był tworzony przez lata, przez różnych ludzi, często bez spójnej wizji. Jest pełen ukrytych zależności, nieoczywistych powiązań i braku izolacji. I nadal… działa. Jakoś.
Strach ma wiele linii kodu
Legacy systemy przerażają nie tylko przez swoją objętość. To, co naprawdę budzi niepokój, to nieprzewidywalność. Zmiana w jednym module skutkuje błędem w innym, pozornie niezwiązanym, miejscu. Debugowanie trwa godzinami. Testy (jeśli są) nie przechodzą. Komentarze, jeśli istnieją, są nieaktualne. A dokumentacja… cóż, przeważnie jest „w głowie Marka, który już tu nie pracuje”.
Dlatego właśnie legacy często kojarzy się z brakiem rozwoju zawodowego. Bo jak tu ćwiczyć czyste architektury, kiedy walczysz z null pointerem w 15-letnim kodzie bez testów?
Ale to tylko jedna strona medalu.
Bo z drugiej strony legacy to gotowa lekcja o tym, jak systemy starzeją się z czasem. Jak z pozornie prostego modelu robi się potwór, którego nikt nie ogarnia. Jak błędne decyzje na poziomie modelowania i architektury kumulują się, aż w końcu nie wiadomo, gdzie leży prawdziwa granica między modułami.
To właśnie w takich systemach najlepiej widać różnicę między „ładnym kodem” a dobrym modelem. Bo kod może być schludny, mieć krótkie metody, dobre nazwy, a mimo to implementować model tak sprzeczny z rzeczywistością biznesową, że każdy nowy feature to walka o przetrwanie.
Zdarzają się też przypadki odwrotne: model ma sens, ale jego implementacja kuleje. To już problem czysto techniczny – i ten da się rozwiązać znanymi technikami refaktoryzacji.
3 warstwy bólu i naprawy
Nie każdy problem w legacy systemie jest równy, dlatego warto je klasyfikować – choćby po to, by wiedzieć, na co mamy realny wpływ.
- Refaktoryzacja mechaniczna
To klasyczne „code smells” – za długie metody, zagnieżdżone ify, nieczytelne pętle. Typowe dla tutoriali refaktoryzacyjnych. Często nasze IDE samo podpowiada, co zrobić.
Ważne, ale… to nie one zabijają systemy.
- Refaktoryzacja architektoniczna
Tu zaczynają się schody. Złe granice obiektów, zbyt wiele odpowiedzialności w jednym module, brak rozdzielenia na konteksty. Problemy z testowaniem, współbieżnością, wydajnością.
Tu nie pomoże Ctrl + Alt + M. Trzeba zrozumieć domenę, zmapować zależności i często przebudować fundamenty.
- Refaktoryzacja strategiczna
To już nie tylko zmiana kodu. To zmiana modelu, który reprezentuje logikę biznesową. Często oznacza to zmianę całego podejścia do architektury systemu.
To jak wymiana silnika w locie. Ryzykowne. Kosztowne. Ale czasem konieczne.
Wszyscy mają te same problemy (tylko inne nazwy)
Można pracować nad systemem zamawiania taksówek, nad systemem księgowym czy platformą e-learningową. Chociaż domeny są różne to meta-problemy są bardzo podobne:
- nadmiarowe zależności;
- brak izolacji kontekstów;
- przeciążone modele;
- zła komunikacja w zespole, skutkująca niespójną implementacją.
O teorii można czytać godzinami, ale nic nie zastąpi zmierzenia się z prawdziwym systemem, który żyje własnym życiem, ma historię i kilka trupów w szafie.
Właśnie dlatego szkolenie Legacy Fighter oparte jest na projekcie Cabs – symulacji systemu dla usług taksówkarskich. Jest tam wszystko, czego można się spodziewać po systemie z kilkuletnim stażem: krzyżujące się zależności, fragmenty kodu, które trudno zrozumieć nawet po trzecim git blame, i logiczne luki, które wymagają czegoś więcej niż refactor -> rename.
To nie jest sterylne demo z tutoriala. To żywy poligon, na którym można uczyć się zarówno mechanicznego czyszczenia kodu, jak i rozpoznawania głębszych problemów architektonicznych czy modelowych niedopasowań do rzeczywistości biznesowej.
Największa wartość? Wiedza, którą zyskujesz, działa poza tym jednym projektem. Niezależnie, czy Twój system to platforma e-commerce, CRM czy aplikacja do zarządzania flotą. Mechanizmy, które poznajesz w Cabs, są uniwersalne, bo uczą nie tylko jak „czyścić kod”, ale też jak myśleć o kodzie jako o reprezentacji biznesu. A to już wiedza transferowalna do każdego projektu.
Mniej strachu, więcej zrozumienia
System legacy nie jest potworem. To naturalny etap życia każdego większego systemu. Tak jak człowiek się starzeje, tak samo kod ewoluuje, adaptuje się, łata, przeżywa migracje, regresje i nieoczekiwane zachowania produkcyjne o 2 w nocy.
Można się na to złościć. Ale można też potraktować to jako szansę: na naukę, na refleksję, na projektowanie lepszych systemów w przyszłości.