Refactoring da się lubić

powrót do listy
Refactoring da się lubić

Code Refactor w Metapack – czyli jak przestać ukrywać i przemycać usprawnienia kodu „pod stołem”, a zacząć traktować je jak część codziennej pracy czerpiąc z tego satysfakcję

Tematem, którym dzisiaj się zajmę będzie „code refactor”. Śmiało można stwierdzić, że jeżeli chodzi o branżę IT, jest to dobrze znane pojęcie. Znane nie tylko programistom, którzy mają z nim do czynienia bezpośrednio podczas swojej pracy, ale również “biznesowi”, który przez „code refactor” wchodzi na wojenną ścieżkę i walczy ze swoimi zespołami developerskimi. Część osób czy działów utożsamia także „code refactor” ze stratą czasu i opóźnianiem wydania projektu. Z pełnym przekonaniem możemy również stwierdzić, iż „code refactor” to pojęcie przez tyle osób lubiane, co znienawidzone albo przynajmniej niedoceniane. Na szczęście w niniejszym artykule nie będziemy mówić o błędnym postrzeganiu pojęcia „code refactor”, ale o tym, jak robimy to w Metapack.

Chyba każdy z nas chciałby pracować w środowisku, gdzie kod, z którym mamy do czynienia jest przejrzysty, zrozumiały, dobrze napisany, a kolejne nowe funkcjonalności praktycznie dodają się same. Każdy chciałby móc łapać się za kolejne zadanie z „backloga” bez strachu i obawy przed tym z jakim kawałkiem kodu przyjdzie nam dzisiaj pracować, z jakimi nazwami zmiennych czeka nas starcie oraz ile czasu trzeba będzie poświęcić na lekturę metody lub klasy składającej się z wielu tysięcy linii. Nie mówiąc już o zrozumieniu tego co autor miał na myśli. Takie środowisko można by nazwać rajem dla developerów. Niestety jest ono dość rzadko spotykane w realnym świecie – gdzie terminy gonią, a klienci czekają na kolejne funkcjonalności.

Jednak to, że takie środowisko pracy nie jest łatwe do zbudowania, nie znaczy, że nie da się tego zrobić. Metapack jest doskonałym przykładem tego jak połączyć zapewnienie jakości z dotrzymywaniem terminów oraz tego jak skutecznie zbliżać się do wyżej opisanego miejsca pracy. Celem oczywiście jest zostanie jego zupełnym odzwierciedleniem.

Do rzeczy jednak. Jak to się stało, że w Metapack refaktoryzacja kodu stała się dla nas przyjemną częścią codziennej pracy? Jak udało nam się go połączyć z zadaniami dla klientów, tak aby cała nasza organizacja doskonale rozumiała jakie korzyści nam on przynosi? Jak tak naprawdę „code refactor” jest u nas przeprowadzany? Myślę, że warto zacząć tutaj właśnie od ostatniego punktu i po krótce (ponieważ nie jest to poradnik jak przeprowadzać „code refactor”) opisać jak to się u nas dzieje.

Jak w Metapack przeprowadzamy Code Refactor

Według mnie najlepiej zobrazuje to opis na podstawie „flow” naszej pracy z naciskiem właśnie na aspekt refaktoryzacji. Opiszę Wam w jakim momencie ją przeprowadzamy oraz kiedy decydujemy się na większe prace z nią związane. Następnie idąc nieco głębiej w narzędzia, które nam przy niej pomagają, opowiem jak można zmienić refaktoryzację w ciekawą pracę. Podpowiem też jak znaleźć argumenty przekonujące do refaktoryzacji osoby podchodzące do niej sceptycznie lub negatywnie.

Refactor jako część naszej pracy nad nową funkcjonalnością

Zacznijmy od momentu, w którym na naszym „backlogu” pojawia się nowe zadanie. Może to być na przykład jakaś funkcjonalność, którą należy dostarczyć klientowi. W firmie doszliśmy wspólnie do momentu, w którym osoby kierujące projektem czy właściciele produktu zdają sobie sprawę, że estymacja zawiera, a nawet powinna brać pod uwagę czas na podstawową refaktoryzację. Mam tu na myśli prace związane ze zmianą nazw zmiennych, pozbyciem się duplikatów, ekstrakcją części kodu do osobnych metod itd. Innymi słowy są to poprawki ułatwiające w przyszłości pracę nad modyfikowanym przez nas kodem. Skoro wiemy już, że estymacja zawiera już czas potrzebny na „code refactor”, to kiedy zostaje on przeprowadzony?

Code Refactor zaczyna się u nas praktycznie przed rozpoczęciem prac nad daną funkcjonalnością.

Kiedy ktoś zaczyna pracować nad zadaniem, to pierwszym krokiem jest zastanowienie się, czy aktualna struktura kodu jest gotowa na nową funkcjonalność. Jeśli nie widzimy większych trudności czy zagrożeń związanych ze strukturą kodu, to poprawki, które możemy dodać na pierwszy rzut oka wprowadzamy od razu. Wliczamy je w normalną część zadania. W Metapack stawiamy bardzo duży nacisk na testy i ich automatyzację. Przy „refactorze” bardzo ważne jest, aby zmiany, które zaimplementujemy nie zmieniały zachowania aplikacji. U nas jest to potwierdzane właśnie przez testy, które wykonują się po każdym wpięciu i sprawdzają po kolei każdą funkcjonalność aplikacji. Mogą to być między innymi automatyczne testy funkcjonalne, testy jednostkowe, czy też dodatkowo testy „End-2-End” sprawdzające działanie aplikacji jako ogólnego procesu. Dzięki temu nie musimy angażować obu oczu i rąk testera do sprawdzenia, czy nasze usprawnienia nie spowodują zachwiania się czyjegoś biznesu. Skoro mamy już sprawdzone i przygotowane podłoże pod pracę, możemy zabierać się do działania. Implementujemy nową funkcjonalność i to właśnie tu zaczyna dziać się nasz „code refactor”. Przy okazji pracy nad czymś nowym zmieniamy niejasne nazwy zmiennych, poprawiamy rzeczy, które nam nie odpowiadają, pozbywamy się „warningów”, duplikatów i robimy te wszystkie małe zmiany, które nie wymagają od nas ogromnego nakładu pracy, a polepszają jakość kodu. Następnym etapem jest „code review” – dzieje się ono często. Wpięcia robimy małe przez co przeglądanie kodu jest przyjemniejsze. Pozwala nam to skupić się na danej części w stu procentach nie myśląc o tym, ile jeszcze linii mamy do sprawdzenia. W „code review” angażowane są osoby z różnych zespołów, aby nie opierać się tylko na opinii zespołu aktualnie pracującego nad zmianami. Często na tym etapie wspólnie z osobami zaangażowanymi oceniamy, czy przeprowadzona refaktoryzacja faktycznie ułatwia nam czytanie kodu, czy nazwy nam odpowiadają i czy wszystko jest przejrzyste. W efekcie, rozmowy te często prowadzą do zagorzałych dyskusji na temat tego, czy aby na pewno nie da się tu czegoś zrobić lepiej. I tym akcentem zamkniemy temat „refactoringu” wykonywanego przy okazji implementacji innych funkcjonalności, a przejdziemy zwinnie do planowanych prac związanych z „refactorem” i do tego jak przekonać do niego biznes.

Planowany „refactor”

Planowana refaktoryzacja to już zupełnie inna para kaloszy. W tym przypadku ze zwykłego developera zmieniamy się w wybitnego konserwatora zabytków. Dostajemy szansę na wyremontowanie starego Golfa, a nawet na sprawienie, że będzie miał więcej mocy niż nowsze Mercedesy. Ściślej mówiąc dostępujemy zaszczytu przywrócenia chwały i blasku często już zapomnianej przez developerów aplikacji. Planowany „refactor” może być u nas wynikiem wielu działań. Jednym z nich jest wyżej wymieniona dyskusja. Innym może być częste zaglądanie do danego kawałka kodu przy okazji zgłaszanych błędów. Jeszcze innym działaniem może być niezadowalający czas odpowiedzi aplikacji. Takie czynniki rozwijają nasze zwoje mózgowe i skłaniają do zadania sobie jednego bardzo ważnego pytania – co z tym kodem jest nie tak, czy potrzebny jest mu „code refactor”, jeśli tak, to jaki i co konkretnie chcemy dzięki niemu uzyskać? Ten konkretny cel jest u nas bardzo ważny. Jak doskonale zdajecie sobie sprawę „refactor” może trwać bardzo długo, a niekoniecznie o to chodzi. Pamiętajmy, że kod jest dobry nie wtedy, kiedy nie ma już więcej rzeczy, które można do niego dodać, a wtedy, kiedy nie ma już nic, co można by z niego wyrzucić. Dlatego kiedy już poprawimy i wyczyścimy co należy nie dodawajmy na siłę czegoś co nie poprawi jakości kodu. Aby uniknąć wiecznego upiększania aplikacji, kolejnym krokiem jest określenie sobie jasno celu „refactoringu”. Sprecyzowanie wymagań, które pozwolą nam stwierdzić, że zadanie zostało zakończone. Czasami wyznacznikiem zakończenia usprawnień może być coś niemierzalnego w prosty opisowy sposób. Coś czego nie da się umieścić na wykresie czy w raporcie. Wtedy moment zakończenia zadania określany jest np. przez wysoki poziom szczęścia developerów przeglądających „code review”. W większości przypadków przychodzi nam jednak z pomocą parę narzędzi, które badają to, na czym zależy nam równie mocno co na przejrzystości kodu – a mianowicie czas odpowiedzi aplikacji. W Metapack jest to jedna z ważniejszych metryk. Naturalnym więc wydaje się, że „refactor”, który poprawi nam tę właśnie metrykę jest tym, który nas interesuje. W usprawnieniach kodu opisanych w pierwszych punktach artykułu poprawiamy elementy, które nie mają aż tak dużego wpływu na wydajność aplikacji, więc skupiamy się głównie na jej niezmiennym zachowaniu i czytelności. Jak zachować się, kiedy wydajność usprawnianego procesu jest głównym celem refaktoryzacji? W Metapack mamy na to odpowiedź i poradziliśmy sobie z tym bardzo dobrze.

Wyznaczamy baseline dla czasu odpowiedzi aplikacji.

Pierwszym krokiem, który wykonujemy przed większymi pracami związanymi z „refactorem” jest wyznaczenie sobie „baseline’u” czasu odpowiedzi aplikacji. Przez „baseline” rozumiemy np. 95 i 99 percentyl czasu odpowiedzi dla konkretnego scenariusza testowego (rodzaj requestu, ilość requestów i ich rozkład w czasie), do którego porównamy czasy po wprowadzeniu zmian. W tym celu przychodzi nam z pomocą narzędzie o nazwie Gatling. Jest to open-source’owy framework wykorzystywany przez nas do przeprowadzania testów wydajnościowych. Sposób w jaki Gatling dokumentuje wyniki w zupełności pokrywa się z tym, czego oczekujemy od takiego narzędzia.

refactoring w metapack

refactoring w metapack
Rysunek 1 Gatling – przykładowa prezentacja wyników dla wybranych procesów jednej z naszych aplikacji

 

Przedstawia nam on szczegółowy raport z różnymi metrykami – informacje globalne, rozkład czasów w rozbiciu na percentyle i wiele innych. Raport jest przechowywany jako plik HTML dzięki czemu możemy zachować go do przyszłego porównania z nowymi wynikami. Opcji jest wiele, ale to wydaje się być tematem na osobny artykuł. Mając już wyznaczony „baseline” możemy przystąpić do przeprowadzania usprawnień. Czy teraz jednak przy każdej małej zmianie musimy odpalać te kosztowne czasowo testy przy użyciu Gatlinga? Bynajmniej. Aby potwierdzić, że zmiany, które wykonujemy w poszczególnej metodzie czy części kodu są dla nas korzystne wykorzystujemy kolejne świetne narzędzie jakim jest ANTS Performance Profiler. Wielu z nas zapewne miało w swoim życiu moment, gdzie wysnuliśmy stwierdzenie „Mój kod jest świetny, nie ma tu wiele rzeczy, które można by poprawić”. Tym, którzy przyznają się do takich słów naprawdę polecam uruchomić ANTS’a, a następnie podwijać rękawy i brać się za poprawki.

refactoring w metapack

refactoring w metapack
Rysunek 2 ANTS Performance Profiler – przykładowa prezentacja wyników

 

ANTS Performance Profiler to narzędzie bardzo usprawniające proces „refactoru” kodu. Choć można tu rozszerzyć jego zastosowanie do wielu innych rzeczy takich jak chociażby wyszukanie „wąskiego gardła” w aplikacji itp. ANTS podobnie do Gatlinga służy nam do badania wydajności. Skupia się on jednak bardziej na samym w sobie kodzie, zapytaniach bazodanowych i wydajności konkretnych ich części. U nas to narzędzie wykorzystywane jest bardzo intensywnie. Dogłębnie analizujemy wydajność poszczególnych implementacji, a następnie przystępujemy do wprowadzania usprawnień. Po wprowadzeniu zmian w kodzie uruchamiamy ponowną analizę ANTS’em i sprawdzamy, czy to co zrobiliśmy faktycznie poprawiło działanie aplikacji. Może wydawać się to mało interesujące: sprawdzanie wyników, przeprowadzanie analiz, ale uwierzcie mi na słowo – wprowadzenie poprawki w jednej linii kodu, która zwiększa wydajność pewnego procesu o 80% to coś co potrafi zmotywować i pozytywnie zaskoczyć.

Mamy już zatem za sobą proces tworzenia „baseline’u” dla czasu odpowiedzi aplikacji. Przeszliśmy przez narzędzie, którym weryfikujemy na bieżąco, czy nasze działania zbliżają nas do celu naszego „refactoringu”. Czy można zrobić coś jeszcze? W Metapack twierdzimy, że tak. Mając na uwadze to, aby „refactor” nie trwał wieki zazwyczaj bardziej skupiamy się na usprawnieniu procesów zachodzących w naszej aplikacji aniżeli poprawieniu konkretnej linijki – bo nie o ilość poprawionych linijek tu chodzi. Czasami jednak, kiedy czas na to pozwoli (a przy „refactoringu” branym pod uwagę w na etapie planowania zazwyczaj pozwala) możemy przyjrzeć się dokładnej temu co dany proces wykorzystuje. Możemy pobawić się w detektywa Sherlocka Holmes’a i sprawdzić czy Linq wykorzystane w kodzie jest optymalne, czy wybrany sposób łączenia stringów jest odpowiedni do tego co chcemy osiągnąć. Tu jak asa z rękawa wyciągamy kolejne narzędzie, a mianowicie BenchmarkDotNet.

refactoring w metapack
Rysunek BenchmarkDotNet – przykładowa prezentacja wyników paru z naszych implementacji

 

Jest to prosty i przyjemny w obsłudze „framework” do porównywania czasów wykonania konkretnych implementacji w kodzie. Nie odnosi się to jedynie do najmniejszych jego części. Porównywać możemy tu naprawdę skomplikowane implementacje. Prezentacja wyników wygląda jak powyżej. Jest ona też modyfikowalna, przez co możemy dopasować te narzędzie pod siebie. Podczas prac z BenchmarkDotNet można często zostać zaskoczonym czymś o czym nie wiedzieliśmy, poznać niuanse pewnych komend czy metod. Kiedy to wszystko już za nami możemy powiedzieć, że to koniec. Teraz wystarczy przeprowadzić kompleksowe testy wydajnościowe Gatlingiem, porównać wyniki i być dumnym ze swoich osiągnięć. Dzielnie dobrnęliśmy do mety procesu planowanego „refactoru”. Ale co z odpowiedzią na pytanie postawione w tytule?

Jak przestać ukrywać i przemycać usprawnienia kodu pod stołem, a zacząć traktować je jak część codziennej pracy czerpiąc z tego satysfakcję?

Najprostszym sposobem na to, aby przestać się ukrywać z optymalizacjami kodu, aby przestać traktować je jak coś na co nikt nie da nam pozwolenia jest udokumentowanie i przedstawienie wyników przed i po refaktoryzacji. Wiem, że nie zawsze jest to takie proste, bo aby móc pokazać wyniki PO refaktoryzacji trzeba uzyskać na nią zgodę PRZED samym procesem „refactoringu”. Ludzie, którzy jeszcze nie doceniają tego typu prac niekoniecznie zmienią zdanie i wyrażą nam taką zgodę słowami typu: „zmniejszamy takim działaniem dług techniczny” (co w przypadku developerów jest bardzo ważnym argumentem), „Marek teraz szybciej czyta kod”, „Mamy 150 linijek kodu mniej”, czy „zobaczcie zmienna »someValue« nazywa się teraz »priceInDollars«”.

W takich przypadkach zaproponowałbym przeprowadzenie z biznesem rozmowy na temat korzyści wynikających z samego refaktoryzowania przedstawiając symulację chociażby na zasadzie: „jeśli dziś »zrefaktoruję« ten kod przez X czasu to prognozuję, że przyśpieszę jego działanie o taki procent, który pozwoli na wykonanie przez niego Y razy więcej operacji w danym czasie i tym samym analogicznie większy zarobek”. Czasami trzeba pokazać, że mimo, iż „refactor” nie zmienia zachowania aplikacji ze strony funkcjonalnej, to na pewno zmienia podejście i zachowanie użytkowników naszej aplikacji, którzy dzięki naszej „dodatkowej” pracy z uśmiechem na twarzy zauważają mniejszą częstotliwość błędów, szybszą obsługę nowych zadań, a także same w sobie lepsze czasy odpowiedzi naszych procesów.

To chyba wszystko w temacie tego jak „Code Refactor” wygląda w Metapack. Jeśli poczuliście się zainteresowani tym tematem i liczycie na więcej to zachęcam do zapoznania się z tym podejściem typowo w praktyce. Tymczasem życzę miłego dnia i dziękuję tym, którzy wytrwali ze mną do końca 😊

Autor:

Adam, Lider zespołu Ninja
Arkadiusz,
Engineering Team Lead

Poprzedni artykuł

100% pracy nad własnym produktem

przeczytaj

Następny artykuł

Trzy lata w Metapack – historia Cezarego

przeczytaj