Wiele organizacji działa na oprogramowaniu, które ma już swoje lata i zostało napisane w technologiach przestarzałych lub przy użyciu podejść, które obecnie nie są już stosowane. Choć może się wydawać, że to marginalne sytuacje, w rzeczywistości zdarzają się dość często. To właśnie w takich przypadkach pojawia się problem, jak radzić sobie z tzw. "kodem legacy".
W dzisiejszym artykule przyjrzymy się temu, co oznacza praca z legacy code, jakie wyzwania ze sobą niesie oraz jakie strategie i narzędzia można zastosować, aby ułatwić sobie to zadanie.
Czym jest aplikacja legacy?
Aplikacja legacy w dużym skrócie to oprogramowanie, które jest nadal w użyciu, lecz nie spełnia aktualnych standardów, używa nieaktualnych lub wycofanych bibliotek czy frameworków. Kod w takiej aplikacji bardzo często jest nieczytelny, przestarzały pod względem składni i bardzo ciężki do utrzymania i wprowadzania modyfikacji. Utrzymywanie aplikacji legacy prowadzi do powstania olbrzymiego długu technologicznego.
Czym jest dług technologiczny?
Dług technologiczny to metafora używana w kontekście rozwoju oprogramowania, oznaczająca akumulację problemów, które wynikają z wyborów technologicznych, nieodpowiednich praktyk programistycznych, braku renowacji w istniejącym kodzie źródłowym, lub infrastrukturze systemu. Dług ten powoduje opóźnienia w projektach, zwiększa koszty utrzymania, utrudnia skalowalność i ogranicza zdolność dostosowania się do zmian na rynku.
Dlaczego warto zniwelować dług technologiczny?
Niwelowanie długu technologicznego jest kluczowe dla zwiększenia wydajności, obniżenia kosztów utrzymania, zwiększenia elastyczności, bezpieczeństwa i konkurencyjności. Poprawa jakości kodu i infrastruktury przyczynia się do zadowolenia klientów i atrakcyjności dla pracowników. Dodatkowo pomaga zachować aktualną wiedzę na temat systemu.
Legacy code: jak sobie z nim poradzić?
Praca z kodem legacy wymaga sporego doświadczenia i rzadko spotyka się, aby tego typu zadania zlecano programistom z niewielkim stażem. Jako że kod w takich aplikacjach przeważnie jest kilku lub kilkunastoletni, deweloperzy z krótkim stażem mogą mieć problemy ze zrozumieniem i czytelnością kodu, ponieważ przyzwyczajeni są do ułatwień i skróconych składni. Stary kod może być w pewnym stopniu nieczytelny, ponieważ używa on przestarzałych składni, adnotacji i struktur danych.
Podnoszenie do aktualnej wersji frameworków i bibliotek
Jeśli to możliwe, projekty legacy można starać się ”reanimować” podbijając wersje aktualnych frameworków oraz wykorzystywanych bibliotek. Idealnie, gdy jest możliwość podniesienia przestarzałych wersji do aktualnych. Może się to jednak wiązać z potrzebą modyfikacji kodu, gdyż poszczególne frameworki/biblioteki modyfikują/dodają/usuwają swoje funkcje wraz z nowymi wersjami. Bardzo pomocna okazuje się dokumentacja, gdzie często można znaleźć poradniki przeprowadzające krok po kroku przez proces upgradowania wersji.
Często zdarza się, że proces aktualizacji musi być stopniowy. Oznacza to, że nie można przeskoczyć o dwie lub trzy wersje danego frameworka/biblioteki. Należy robić to stopniowo, podnosząc kod o jedną wersję na raz. Gdy wszystko przebiegnie bezproblemowo, możemy podnieść oprogramowanie do kolejnej wersji.
Refaktoryzacja kodu
Oprócz aktualizowania oprogramowania można pokusić się o refaktoryzację kodu. Czym jest refaktoryzacja? Refaktoryzacja kodu to proces polegający na restrukturyzacji i oczyszczaniu istniejącego kodu źródłowego bez zmieniania jego zachowania od strony użytkownika. Przykłady refaktoryzacji to ekstrakcja powtarzającego się kodu do funkcji, zmiennej lub klasy, poprawa nazw zmiennych i funkcji, usuwanie nieużywanego kodu czy rozdzielenie zbyt długich funkcji na mniejsze i bardziej zrozumiałe fragmenty.
Proces refaktoryzacji powinien odbywać się wyłącznie po zapoznaniu się i zrozumieniu logiki biznesowej refaktoryzowanego fragmentu kodu. Na końcu refaktoryzacji idealnie, gdyby dane wyjściowe z systemu pozostały w niezmienionej formie tak, aby nie było potrzeby modyfikowania reszty systemu. Jeżeli mamy szczęście i w aplikacji istnieją testy, w takim przypadku robiąc refaktoryzację trzeba zwracać uwagę, aby zmiany w kodzie nie powodowały failów na testach.
Czy warto dokonywać dodatkowych zmian?
Proces refaktoryzacji można realizować jako osobne zadania dewelopera, jeżeli środki i czas na to pozwala, ale można również zastosować zasadę “The Boy Scouting Rule”, która brzmi “Always leave the code you are working on a little bit better than you found it” (Zawsze zostawiaj kod, nad którym pracujesz, w trochę lepszym stanie, niż go zastałeś).
Realizując prace programistyczne w postaci bug fixów lub implementując nowe funkcjonalności, warto pokusić się o zmiany w kodzie, które poprawią jego jakość, nawet gdy nie są one związane z realizowanym zadaniem. Najdrobniejsza zmiana jak dopisanie brakującego typowania lub poprawienie naming convention jest krokiem naprzód w celu zniwelowania długu technologicznego.
Zastosuj reverse engineering
Reverse Engineering (inżynieria odwrotna) polega na analizie kodu w celu zrozumienia, czemu służy dany jego fragment oraz jaka jest jego rola w większej całości. To czasochłonna metoda, jednak wymagana, aby zmiany, które chcemy wprowadzić, nie przyczyniły się do powstania nowych bugów w aplikacji lub całkowicie nie zaburzyły działania poszczególnych procesów.
Reverse Engineering jest bardzo ważnym zagadnieniem w pracy z kodem legacy. Często aplikacje legacy nie posiadają żadnej dokumentacji. Może zdarzyć się też tak, że jest on na tyle szczątkowa, że nie przyniesie nam żadnych istotnych informacji. Czy jeśli projekt posiada dokumentację to możemy odetchnąć z ulgą? Niestety, w projektach legacy często jest ona nieaktualna, więc ostatecznie może okazać się nieprzydatna. Z tego powodu nie zaleca się bezgranicznego zaufania dokumentacji, gdyż potrafi wprowadzić w błąd poprzez błędne i nieaktualne wpisy.
Wprowadź dobre praktyki
Kod legacy bardzo często charakteryzuje się ubytkami na poziomie jakości i czytelności. Pomocne w walce z tym mogą okazać się wszelkiego rodzaju standardy, dobre praktyki i wzorce projektowe. Za ich pomocą możemy sprawić, że kod staje się ujednolicony i czytelniejszy zarówno dla nas, jak i pozostałych deweloperów. Dzięki temu na dalszych etapach w pracę z kodem legacy łatwo można włączyć innych programistów.
Kilkoma z zalecanych reguł i zasad są między innymi:
SOLID (single responsibility, open/closed, Liskov substitution, interface segregation, dependency inversion),
KISS (Keep It Simple, Stupid)
DRY (Don’t Repeat Yourself).
“Ifologia” w projektach legacy jest kolejnym nagminnie spotykanym problemem. W walce z nią pomocne stają się wzorce projektowania takie jak na przykład: strategy, chain of responsibility lub state.
Jak można zapobiec dezaktualizacji oprogramowania?
Praca z legacy nie jest łatwym zadaniem. Zdecydowanie jest procesem bardziej czasochłonnym niż praca ze świeżymi projektami budowanymi w zgodzie z popularnymi standardami. Niestety, każda aplikacja, która nie jest na bieżąco rozwijana, po pewnym czasie wejdzie w stan legacy, zwiększając dług technologiczny, a tym samym zwiększając koszty utrzymania i późniejszych modyfikacji.
Dlatego ważne jest, aby nie zapominać o aktualizowaniu i utrzymaniu aplikacji w dobrej formie. Pozostawianie ich w stanie stagnacji może doprowadzić do zestarzenia się aplikacji co będzie skutkować, iż stanie się ona bezużyteczna lub nienadająca się do dalszego rozwoju.