Wyślij zapytanie Dołącz do Sii

Pewnie każdy z nas – programistów i inżynierów oprogramowania – od czasu do czasu, a może nieustannie, pracuje z „Legacy code”.

Załóżmy scenariusz: dołączamy do zespołu, który dobrze sobie radzi, ale potrzebuje więcej rąk do pracy. W aplikacji, nad którą pracują, od lat wszystko działa. Nie ma poważnych zgłoszeń o błędach. Jedynym problemem jest dodawanie nowych funkcji biznesowych. Udaje się to, ponieważ są tam doświadczeni programiści, znający na pamięć większość zawiłości tej aplikacji. Jest czas i ogólnie małe, planowane zmiany.

Tu właśnie leży problem – potrzeba nowych funkcji biznesowych, a czas nagli. Pracujemy z kodem „Legacy”. Pierwszy pomysł, jaki przychodzi do głowy – zatrudnić więcej programistów. Ale czy nowi programiści natychmiast zwiększą wydajność? Alternatywą może być refaktoryzacja. Świetny pomysł – każdy przytakuje, no poza Product Ownerem (PO).

Czym jest refaktoryzacja?

Szybkie spojrzenie na definicję w Wikipedii:

Refaktoryzacja (czasem też refaktoring, ang. refactoring) – proces wprowadzania zmian w projekcie/programie, w wyniku których zasadniczo nie zmienia się funkcjonalność. Celem refaktoryzacji jest więc nie wytwarzanie nowej funkcjonalności, ale utrzymywanie odpowiedniej, wysokiej jakości organizacji systemu (…).

Refaktoryzacja Legacy code

Porozmawiajmy zatem o refaktoryzacji legacy code. Może tym razem będzie lepiej niż zwykle. Ponieważ zazwyczaj, kojarzy mi się to z walką z mitologiczną Hydrą. Odcinamy głowę – jest sukces – ale już za chwilę na jej miejsce wyrastają dwie nowe, czyli nasze błędy w kodzie, niezgodności z innymi funkcjonalnościami.

Zejdźmy zatem na ziemię i rozważmy opcje:

  • Napisać całość od nowa, wstrzymać projekt (powiedzmy na rok), przygotować wszystko jak najlepiej, idealnie.
    • Tak, ale czy możemy sobie na to pozwolić? Nie wspierać klienta przez ten czas, nie odpowiadać na zgłoszenia i potrzeby?
  • To może nie rok, ale 6 miesięcy, refaktoryzując wszystko, czego potrzebujemy.
    • I tu pojawiają się te same problemy…

W takim razie co możemy zrobić?

Na początek, doceńmy legacy kod aplikacji. On działa, nie ma wielu błędów, a co najważniejsze – istnieje i funkcjonuje.

Zastanówmy się, co chcemy osiągnąć dzięki refaktoryzacji:

  • Czy chcemy tylko poprawić czytelność kodu?
  • Może wyprzeć starą technologię i aktualizować komponenty do nowszych wersji?
  • A może jest potrzeba dynamicznej rozbudowy aplikacji, a bez refaktoryzacji nie będzie to możliwe?

Koniecznie zadajmy pytanie, jakie obszary aplikacji będą rozwijane w przyszłości albo z jakimi zazwyczaj mamy największy problem. Może właśnie te moduły powinniśmy refaktoryzować.

Nie trzeba zmieniać części aplikacji, która działa, nie ma błędów i nie będzie rozwijana. Prawdopodobnie byłoby to nieopłacalne.

Przeanalizujmy zatem prosty wykres poniżej. Najlepszym kandydatem będzie część aplikacji o dużej złożoności kodu, gdzie zachodzi najwięcej zmian.

Jak poradzić sobie z refaktoryzacją?

Chciałbym zaproponować kilka sposobów radzenia sobie z naszym kodem – Hydrą.
Znając już potrzeby i problemy, ułóżmy plan długoterminowy, który będzie zakładał konkretne, duże zmiany, jakich zazwyczaj nie jesteśmy wstanie bezpiecznie wprowadzić w krótkim czasie. Taki plan może zakładać np. zmianę działającego komponentu w jednej części aplikacji na nowszą wersję lub wprowadzenie jednolitego wzorca projektowego.

Refaktoryzacja strategiczna

W ten sposób uświadomimy sobie cel naszej pracy. Cel jest ważny, ponieważ bez niego możemy utknąć w ślepym zaułku, dążyć do perfekcji, pracować w nieskończoność i nigdy nie zakończyć refaktoryzacji.

Everyday refactoring

Proponuję, dążyć do celu metodą małych kroków. Może będzie trzeba podzielić działania na kilka mniejszych części.
Pracujmy codziennie z kodem, zwłaszcza przy okazji wprowadzania zmian i poprawek wymaganych przez użytkowników. Pamiętajmy jednak o naszym celu nadrzędnym, strategicznym. W ten sposób zapewnimy ciągłe wsparcie dla aplikacji i zbliżymy się do konkretnego planowanego efektu.

Potrzebujemy narzędzi

Konieczne będą narzędzia takie jak wzorce projektowe, dobre praktyki, SOLID. Może wprowadzenie albo usunięcie warstwy przyniesie korzyść?

Oprócz narzędzi warto, jeśli jest to możliwe, poprosić autora lub inne osoby pracujące z kodem o pomoc. Pomocny okaże się każdy, kto zna system, także od strony użytkownika. Krótka rozmowa na temat funkcji programu, niuansów, historii zmian wokół kluczowych elementów przyniesie wiele korzyści.

Refaktoryzacja – szkic

W przypadku, kiedy zaczynamy, a dany obszar jest skomplikowany, rozpocznijmy od tej metody. Co do zasady ta refaktoryzacja będzie do wyrzucenia „do kosza”. Celem działania jest rozpoznanie kodu – jak połączono go z innymi modułami i jakie ma zależności.

Możemy wyjąć całą klasę do oddzielnego projektu i spróbować wydzielić odpowiedzialności. Jeśli to możliwe – przetestować. A później wyciągnijmy wnioski.

Dzięki temu będziemy skupieni na konkretnej części, nie całości, na tym samym poziomie złożoności. Pomoże nam to wrócić do właściwej refaktoryzacji z gotowym przepisem na sukces.

Narzędzia Everyday refactoring

Rozpoznaliśmy już część aplikacji i schodzimy niżej o kolejne poziomy. Na początku, czytając kod, zastosujmy tymczasowe komentarze. Pozwolą nam lepiej zrozumieć całość, sposób działania i zależności.
Zastosujmy nowe nazwy dla metod, klas – tam, gdzie uważamy to za słuszne.

Dekomponujmy długie metody na mniejsze części. Co to znaczy długie? Może takie, które nie mieszczą się na ekranie komputera (nie, obracanie ekranu pionowo, nie jest rozwiązaniem).
Istnieje metoda szacowania złożoności kodu (Złożoność cyklomatyczna McCabe’a).

Dla każdej instrukcji warunkowej, przekierowania, pętli (IF, ELSE, FOR, BREAK) dodajemy +1 do naszej złożoności. Przyjmijmy, że dla złożoności powyżej 6, powinniśmy rozdzielić metodę na mniejsze części.

Ułatwić cały proces może także sortowanie metod w klasie – tematycznie, jedne obok drugich.
Idąc krok dalej, oznaczmy metody publiczne, opiszmy je interfejsem. Stąd już prosta droga do wydzielenia części do osobnych klas – odpowiedzialności.

Testy i metody prywatne do oddzielnych odpowiedzialności

Skupmy się przez chwilę na szczegółach. Zauważyliśmy, że w danej refaktoryzowanej klasie jest metoda prywatna. Na tyle skomplikowana, że warto byłoby ją przetestować. Zamiast zmieniać ją na publiczną, proponuję przenieść ją do nowej klasy i dopiero wówczas dokonać zmiany.

Najprawdopodobniej jest to gotowa osobna odpowiedzialność. Stara klasa staje się coraz prostsza. Od tego momentu możemy dużo łatwiej przeprowadzić test wcześniejszej metody. Przetestować możemy manualnie, a najlepiej napisać test (jednostkowy, integracyjny). W ten sposób zweryfikujemy poprawność działania partii kodu.

Anti-corruption layer

Zastosowaliśmy wszystkie techniki i znane nam chwyty, ale jednak w pewnym momencie utknęliśmy. Logika biznesowa, choć legacy, to działa.

A może problemem jest biblioteka zewnętrzna, której nie możemy aktualizować? Postaraj się ograniczyć używanie kodu legacy poprzez warstwy abstrakcji tak, aby rodzaj biblioteki był jedynie implementacją, a jej elementy nie były przekazywany pomiędzy innymi częściami aplikacji.

W takiej sytuacji zastosować możemy anti-corruption layer. Czyli warstwę pośrednią, odcinającą nas od kodu legacy. Będzie komunikować nasz nowy kod ze starym. Budujemy warstwę abstrakcji wywołującą funkcje starej części.

Pamiętajmy jednak, aby nie przekazywać dalej poza anti-corruption layer żadnych elementów z kodu legacy. Dodajmy własne implementacje obiektów. W ten sposób zamykamy kod legacy „pod pokrywką”. Nadal tam jest, ale nie musimy się już zastanawiać, jak on działa. Mamy własną warstwę komunikacji.

Podsumowanie

Refaktoryzacja to proces wprowadzania zmian w kodzie – zasadniczo nie zmienia funkcjonalności, a poprawia jakość kodu. W tym artykule poznaliśmy kilka sposobów radzenia sobie z refaktoryzacją kodu.
Z pewnością każdy znajdzie dla siebie konkretne zastosowanie, nie zawsze będzie konieczne wykorzystanie wszystkich jednocześnie.

Poznając dany problem, należy dobierać odpowiednie narzędzia oraz cele. Ich obranie zawsze jest kluczowe, ponieważ na tej podstawie stworzymy plan.

Pamiętajmy również o pracy zespołowej – konsultujmy plany z innymi i wspierajmy się wiedzą o aplikacji. W ten sposób można owocniej realizować założone nadrzędne plany długoterminowe.

Źródła

Ocena:
Autor
Avatar
Piotr Robakowski

Programista .Net z ponad 10-letnim stażem zawodowym. Doświadczenie w technologiach: .Net, SQL Server, WinForms, ASP.Net oraz DevExpress. Brał udział w migracji systemów do nowych technologii. Największy sukces to migracja aplikacji health care z ponad 20-letnią historią kodu źródłowego. Wolny czas wypełnia rower górski, snowboard, a także amatorski sport samochodowy.

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany.

  • Zastanawia mnie, czy refaktoryzacja poprzez wprowadzenie nowej warstwy pośredniej nie powoduje zmniejszenia wydajności (zwiększenia wymagań sprzętowych kodu)?

  • Dzięki za pytanie. Też nad tym często się zastanawiam. Wprowadzenie warstwy pośredniej może przynieść spadek wydajności. Wszystko zależy od implementacji, liczby wywołań danej części kodu.
    W refaktoryzacji dodatkowa warstwa powinna pomóc w odejściu od kodu legacy.
    Co do wydajności wywołań polecam artykuł: https://blog.51cto.com/u_15080025/3680165

Może Cię również zainteresować

Pokaż więcej postów

Bądź na bieżąco

Zapisz się do naszego newslettera i otrzymuj najświeższe informacje ze świata Sii.

Otrzymaj ofertę

Jeśli chcesz dowiedzieć się więcej na temat oferty Sii, skontaktuj się z nami.

Wyślij zapytanie Wyślij zapytanie

Natalia Competency Center Director

Get an offer

Dołącz do Sii

Znajdź idealną pracę – zapoznaj się z naszą ofertą rekrutacyjną i aplikuj.

APLIKUJ APLIKUJ

Paweł Process Owner

Join Sii

ZATWIERDŹ

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?