Adam Kukołowicz
Adam KukołowiczCo-founder @ Bulldogjob

Jak nie zwariować przy pracy z Legacy code

Sprawdź, czym dokładnie jest legacy code i jak odpowiednio podejść do pracy z nim.
3.01.20216 min
Jak nie zwariować przy pracy z Legacy code

Większość programistów prędzej czy później trafi na projekt, który będzie zawierać legacy code. Warto być odpowiednio przygotowanym na takie spotkanie, gdyż każde z nich jest unikalnym przeżyciem, które gwarantuje sporą dawkę stresu, poczucia bezsilności i irytacji. Jak zatem podejść do tematu, żeby nie zwariować i osiągnąć upragniony cel?

Legacy code – z czym to się je?

Należałoby rozpocząć od przybliżenia definicji legacy code. Będzie to kluczowe dla zrozumienia opisywanej problematyki. Polskie tłumaczenie podaje, że jest to „kod odziedziczony”.

Nazwa ta nie jest jednak do końca odpowiednia. Pierwotnie oznaczał on kod, który jest zależny od niewspieranej wersji systemu, języka, frameworka czy innego oprogramowania. Utrzymuje się go, ponieważ nadal działa i daje oczekiwane rezultaty.

Pojęcie legacy code z czasem zaczęło ewoluować w społeczności programistycznej, dlatego ta definicja, mimo że jest prawidłowa, może nie być wystarczająca.

Temat ten podejmuje Michael Feathers w książce zatytułowanej „Working Effectively with Legacy Code”. Posługuje się w niej pojęciem legacy code rozumianym jako „kod bez testów”. Można więc stworzyć aplikację od zera, która już w dniu wypuszczenia będzie zabytkiem.

To jednak nie jest w tu najważniejsze. Powodem, dla którego testy według Feathersa są tak istotne, jest możliwość sprawdzenia, czy po zmianie system nadal będzie działał w oczekiwany sposób. Dodam tylko, że moim zdaniem źle napisane testy są równie, a niekiedy nawet bardziej niebezpieczne niż ich brak.

Jakie mogą być powody tego, że programista nie napisał testów lub zrobił to po prostu źle? Być może nie wiedział jak się za to zabrać. Innym czynnikiem mógł być na przykład "brak czasu". W pierwszym wypadku już samo to gwarantuje bardzo niską jakość kodu, a w drugim może wskazywać na problemy z architekturą aplikacji czy zarządzaniem projektem.


Kod o słabej jakości bardzo ciężko się testuje. Zazwyczaj, gdy patrzymy na kod legacy, jest to w mniejszym lub większym stopniu spaghetti rozwijane bez większego zastanowienia nad sposobem długoterminowego utrzymania systemu.

Podsumowując, Legacy Code charakteryzuje brak, bądź złe pokrycie testami, co pogłębia bałagan w kodzie i architekturze. Jeżeli dołączymy do tego łamanie dobrych praktyk, utrwalane często przez „pokolenia” developerów, to otrzymamy niezbyt przyjemny obrazek. Z tym wszystkim musimy sobie radzić my – niewinni całej sytuacji.

Czy naprawdę jest aż tak źle?

Nie ma oczywiście co wyolbrzymiać. W najgorszym przypadku, trafimy na spaghetti bez testów, w którym każda zmiana może przynieść zupełnie nieoczekiwane problemy, a to jest naprawdę irytujące.

W rzeczywistości rzadko natrafiamy na taką sytuację. Systemy tego rodzaju nadal utrzymuje się, ponieważ cały czas działają i prawdopodobnie zarabiają pieniądze (z których część idzie na twoją pensję).

Reprezentują realną wartość biznesową, lecz aby pieniądze się zgadzały, należy raz na jakiś czas zrobić w systemie generalne porządki. Może to być refactoring lub ustabilizowanie systemu. Żaden biznes nie może sobie przecież pozwolić, żeby jego produkt przestał całkowicie działać.

Jak się okazuje, przepisanie systemu od nowa, w 98% przypadków nie jest dobrym rozwiązaniem, bo wygeneruje olbrzymie koszty, a nie ma gwarancji, że będzie lepszy od poprzedniego - zarówno pod względem biznesowym, jak również kosztów utrzymania.

Wymienić można także więcej plusów pracy nad systemami legacy, o czym pisał Robert Pankowiecki na blogu Arkency (polecam lekturę).

Akcja!

Wyobraź sobie, że siadasz do pracy nad tego typu systemem i masz dopisać nowy feature. Zdajesz sobie sprawę, że gdy go zrobisz, jedyną metodą sprawdzenia czy działa, będzie ręczne przeklikanie nowej funkcji.

Chyba że zmienisz kod w paru innych miejscach… Przy okazji analizujesz kod coraz głębiej i okazuje się, że pół systemu jest ze sobą splątane, dlatego pewnie kilkanaście procent istniejących testów trzeba będzie naprawić po dorobieniu feature’a. Musisz zdecydować, co zrobić.

Złe wybory przy pracy z Legacy code

Podczas pracy z Legacy code najczęściej popełniane są dwa błędy.


Przepisywanie od początku po swojemu całego kodu

Taka taktyka wymaga dogłębnej analizy i szczegółowej wiedzy o systemie, a koszt pozyskania tej wiedzy bywa bardzo wysoki. Trwa to dość długo i może kosztować sporo stresu.

Gdy duża zmiana zostanie przeprowadzona na gorąco, to jest duże prawdopodobieństwo, że system przestanie działać poprawnie. Wcześniejsze pokolenia developerów pracowały nad rozwiązaniem konkretnych problemów, o których ty możesz nie mieć nawet pojęcia. Czas pokazał, że te rozwiązania zadziałały – nawet jeżeli są źle napisane.

Wchodzi tu kwestia długu technologicznego. Skokowa zmiana generuje dodatkowe koszty związane z dłuższym okresem stabilizacji i naprawy innych funkcji, których ta zmiana dotknęła w niezamierzony sposób. Zazwyczaj nie zgadza się na to projekt manager, a i dla nas taka sytuacja nie będzie zbyt przyjemna.


Zrobię to tak, jak zrobili to inni przede mną

Jeżeli ważna jest dla Ciebie jakość kodu, to rzadko będzie to satysfakcjonujące rozwiązanie. Z punktu widzenia systemu oznacza to dalsze zaciąganie długu technologicznego. Należy przy tym pamiętać, że od tego długu płaci się odsetki. W pewnym momencie może to sparaliżować rozwój oprogramowania.

Dobrym przykładem jest tu moje własne doświadczenie zawodowe. Pracowałem dla klienta, który w 2012 roku odziedziczył spory system. Co prawda działał, ale nie był dobrze napisany.

Od tego czasu nowy właściciel kodu napisał mnóstwo kodu, wzorując się na starym stylu. Moim zadaniem był upgrade rozwiązań najbardziej przestarzałych, ale miałem okazję podglądać pracę teamu pracującego nad rozwojem tego oprogramowania. Jakie były konsekwencje kontynuowania starej polityki?

Wydanie kolejnych wersji miało opóźnienie od 2 tygodni do ponad miesiąca (przy dwumiesięcznych wydaniach - sic!). Moment wgrania na produkcję był równoznaczny z gaszeniem pożaru, tickety były zamykane i otwierane na przemian po kilka razy, ze względu na problemy znalezione w późniejszym czasie (stan na koniec 2015).

Dobra wiadomość jest taka, że powoli wychodzą na prostą (stan na grudzień 2017), bo zerwali z częścią złych przyzwyczajeń :)

Jaka droga jest właściwa?

Kluczowe przy pracy z takim kodem jest nastawienie. Powtarzanie sobie, że ten kod jest słaby niczego nie zmieni, a jedynie obniży morale. Co zatem należy zrobić?

Po pierwsze – skoncentruj się na zadaniu, które masz do wykonania. W przypadku problemów, zastanów się, czy realnie możesz je rozwiązać w sensowny sposób. Zapytaj innych programistów, z czego wynika takie rozwiązanie.

Często będą w stanie powiedzieć coś o historii tego kawałka kodu. Postawa pro-aktywna to podstawa, ale przygotuj się, że nie każdy pomysł będziesz w stanie zrealizować.

Z moich obserwacji wynika, że w większości przypadków najbardziej optymalne jest robienie małych usprawnień. Polega to na tym, że jeżeli pracuję nad jakąś częścią kodu, to za każdym razem staram się ją zostawić w lepszym stanie niż ją zastałem.

Przykładem usprawnienia jest dopisanie testów na niesprawdzone przypadki, które w rzeczywistości mogą wystąpić. Lokalny refactoring klasy, tak by była bardziej zgodna z dobrymi praktykami (ale nie musi być od razu perfekcyjna!), czy wprowadzenie brakującej abstrakcji (a jeszcze lepiej usunięcie takiej, która jest zbędna).

Te małe zmiany są dopuszczalne tam, gdzie widzę i rozumiem zależności z innymi częściami systemu. Co do zasady, nie modyfikuję tego, czego jeszcze nie rozumiem.

Ważne jest pokrywanie tych zmian testami, dzięki którym kolejne "pokolenia" będą miały ułatwione zadania tego typu. Te usprawnienia procentują – często inny developer przyjmie podobne rozwiązanie, by rozwiązać taki sam problem. Z czasem może się to przekształcić w wyraźną zmianę.

Fakt, że była oddolna – czyli mniejsze komponenty uległy zmianie jako pierwsze – przekłada się później na niższy koszt kolejnych usprawnień na wyższych poziomach.


To, o czym piszę nie zawsze jest łatwe, czasami może okazać się wręcz niemożliwe. Trzeba mieć otwarty umysł i rozpatrywać różne rozwiązania. Warto również przeczytać wspomnianą książkę Michaela Feathersa, w której szczegółowo opisane są konkretne techniki radzenia sobie nawet z bardzo ciężkimi przypadkami.

Porady końcowe

Legacy code to nie koniec świata, więc nie ma czym się stresować. Warto robić swoje i czerpać wnioski, o tym, czego nie należy robić. Owszem, brzmi to ogólnikowo i takie jest w rzeczywistości. To właśnie o ogólne podejście do problemu chodzi. Przy odrobinie szczęścia i inteligentej pracy, już po paru miesiącach dostrzeżemy efekty naszych zmian.

Kiedyś pracowałem też przy projekcie, w którym przy każdym wydaniu musiała być dostępna grupa „ochotników” do gaszenia potencjalnego pożaru, jednak gdy odchodziłem nowa wersja i jej wgranie do klienta nie było już niczym nadzwyczajnym. Nie był to efekt wielkich refactoringów i ciągłego przepisywania poszczególnych serwisów.

To suma małych zmian wewnątrz kodu i coraz lepsze otestowanie były tu decydujące.

<p>Loading...</p>