Po co refaktoryzować?

  Nie twierdzę, że refaktoryzacja jest lekarstwem na wszelkie programistyczne bolączki. Jednak z pewnością jest wartościowym narzędziem, parą kleszczy, które pozwalają trzymać kod w potrzasku i panować nad nim w pełni. Refaktoryzacja może (i powinna!) być stosowana dla osiągnięcia kilku celów, które omawiam poniżej.   Refaktoryzacja ulepsza projekt   Bez refaktoryzacji projekt oprogramowania będzie ulegał stopniowemu rozkładowi. Podczas wprowadzania zmian realizujących krótkoterminowe cele, często bez pełnego zrozumienia projektu, kod traci strukturę. Coraz trudniej jest zrozumieć oryginalny zamysł, czytając kod. Refaktoryzacja to sprzątanie. Elementy, które znalazły się w złych miejscach, są z nich usuwane. Proces rozkładu projektu można porównać do kuli śniegowej zwiększającej swoją objętość z każdym przebytym metrem — im trudniej zrozumieć projekt, tym trudniej go zachować i tym szybciej się on psuje. Regularna refaktoryzacja pozwala uratować strukturę.   Źle zaprojektowany kod z reguły jest dłuższy, często dlatego, że te same operacje są powtarzane w wielu miejscach. Dlatego ważną kwestią jest usuwanie zduplikowanego kodu. Zmniejszenie ilości kodu raczej nie sprawi, że system zacznie działać szybciej. Wywrze jednak ogromny wpływ na łatwość wprowadzania zmian. Im więcej kodu, tym trudniej poprawnie go modyfikować. Zmienisz pewien fragment, a tymczasem okazuje się, że system wcale nie robi tego, co miał, gdyż w innym miejscu znajduje się podobny kawałek, który właściwie robi to samo, choć w odrobinę innym kontekście. Dzięki usunięciu duplikatów zyskujesz pewność, że kod każdy problem rozwiązuje dokładnie raz. Jest to klucz do dobrego projektu.   Refaktoryzacja poprawia czytelność kodu   Programowanie pod wieloma względami przypomina rozmowę z komputerem. Piszesz kod, który mówi komputerowi, co ma robić, a on odpowiada dokładnie w żądany sposób. W miarę kodowania zmniejsza się rozdźwięk pomiędzy tym, co chcesz, by komputer robił, a tym, co każesz mu robić. Chodzi zatem o precyzyjne wyartykułowanie wymagań. Istnieje jednak jeszcze jeden użytkownik kodu, o którym często zapominamy. Za parę miesięcy ktoś może zostać zmuszony do przeczytania Twojego kodu w celu wprowadzenia w nim zmian. Kogo obchodzi parę dodatkowych cyklów kompilatora? Natomiast dodatkowy tydzień zmarnowany przez osobę wprowadzającą drobną zmianę tylko dlatego, że nie może ona zrozumieć kodu, to spory problem.   Często, gdy próbujemy sprawić, by kod zadziałał, zapominamy o przyszłym programiście. Wprowadzenie zmian poprawiających czytelność kodu wymaga zmiany rytmu pracy. Refaktoryzacja okazuje się naprawdę przydatna. Na wejściu mamy kod, który działa, ale jego struktura nie jest idealna. Odrobina wysiłku poświęconego na refaktoryzację sprawi, że kod sam będzie wyraźnie komunikował swoje przeznaczenie. Celem jest jasne wyrażenie intencji.   Nie jestem takim znowu altruistą — często to ja sam bywam tym przyszłym programistą. W takim wypadku refaktoryzacja ma jeszcze większe znaczenie. Jako programista jestem szalenie leniwy. Jednym z przejawów mojego lenistwa jest to, że nigdy nie pamiętam szczegółów pisanego przez siebie kodu. Prawdę mówiąc, z premedytacją zapominam o rzeczach, które mogę sprawdzić, gdyż boje się, że mój mózg w końcu przepełni się i nie wytrzyma. Wszystko, o czym powinienem pamiętać, staram się umieścić w kodzie, by móc łatwo do tego sięgnąć. Dzięki temu mniej martwię się o komórki mózgowe mordowane przez Old Peculier[2] [Jackson].   Czytelność kodu poddanego refaktoryzacji można wykorzystać w jeszcze jeden sposób. Stosuję refaktoryzację, gdy muszę zrozumieć nieznany mi kod. Gdy uda mi się pojąć cel paru linii kodu, nie poprzestaję na mentalnej satysfakcji. Przekształcam kod tak, by oddawał moje rozumienie, po czym testuję, by upewnić się, że miałem rację. Na początku tego typu przekształcenia ograniczam do szczegółów. W miarę zwiększania czytelności kodu zaczynam zauważać rzeczy, które wcześniej nie były widoczne. Gdybym nie zmienił kodu, zapewne nigdy bym do nich nie doszedł, gdyż nie jestem na tyle mądry, by zwizualizować wszystko w mojej głowie. Ralph Johnson porównuje te wczesne przekształcenia do usuwania brudu z szyby, by można było zobaczyć, co jest za oknem. Refaktoryzacja pozwala mi wznieść się na poziom rozumienia problemu nieosiągalny w żaden inny sposób.   Refaktoryzacja pomaga znaleźć błędy   Lepsze rozumienie kodu ułatwia mi wykrywanie błędów. Przyznaję, nie jestem w tym oszałamiająco dobry. Znam osoby, które potrafią przeczytać fragment kodu i wytknąć błędy — ja nie. Zauważyłem za to, że podczas refaktoryzacji, gdy poświęcam wiele wysiłku na zrozumienie, co kod ma robić, wkraczam na nowy poziom. Dzięki poprawie struktury programu jestem w stanie poczynić i potwierdzić lub obalić założenia, a w końcu zauważyć, że niektóre rzeczy nie grają.   Kent Beck często mówi o samym sobie: „Nie jestem rewelacyjnym programistą. Jestem dobrym programistą z rewelacyjnymi nawykami”. Refaktoryzacja pozwala mi być lepszym w wytwarzaniu stabilnego kodu.   Refaktoryzacja przyspiesza programowanie   Wszystkie wcześniejsze zalety składają się na ostatnią: refaktoryzacja przyspiesza proces wytwarzania kodu. Jest to nieco sprzeczne z intuicją. Gdy mówię o refaktoryzacji, dla większości słuchaczy jest oczywiste, że zwiększa ona jakość kodu. Lepszy projekt, czytelność, mniej błędów — to poprawia jakość. Tylko jakim cudem te kosztowne operacje mogłyby nie spowalniać programowania?   Jestem głęboko przekonany, że dobry projekt jest niezbędny do szybkiego wytwarzania kodu. Właściwie cała idea dobrego projektu ma na celu przyspieszenie programowania. Bez przemyślanego projektu można szybko zaprogramować pewien fragment, jednak brak planowania zaczyna się mścić. Gdy próbujesz zrozumieć system i odszukać duplikaty, które także należy zmienić, tracisz czas. Nowe funkcjonalności wymagają coraz więcej kodowania, gdyż musisz dorabiać łatki do łatek łatających oryginalny kod.   Dobry projekt to podstawa szybkości produkcji oprogramowania. Refaktoryzacja przyspiesza ten proces, ponieważ chroni projekt przed rozkładem, a nawet go ulepsza.  

Kiedy refaktoryzować?

  Często jestem pytany o najlepszy czas na refaktoryzację. Czy należy do każdych paru miesięcy pracy dokładać dwa tygodnie na refaktoryzację?   W prawie wszystkich wypadkach jestem przeciwnikiem wydzielania czasu na refaktoryzację. W moim wyobrażeniu jest to proces ciągły. Nie istnieje coś takiego, jak decyzja o przystąpieniu do refaktoryzacji. Refaktoryzujesz, gdy chcesz zrobić coś innego, a przekształcenie kodu to ułatwi.   Zasada trzech   Oto co doradził mi Don Roberts. Gdy robisz coś po raz pierwszy, po prostu to robisz. Za drugim razem, gdy robisz coś podobnego, trochę się krzywisz, ale i tak duplikujesz. Gdy natrafiasz na ten sam problem po raz trzeci, pora na refaktoryzację. Uwaga: Do trzech razy sztuka — za trzecim refaktoryzujesz.   Refaktoryzacja przed dodaniem nowej funkcji   Najczęściej przeprowadzam refaktoryzację, gdy chcę dodać do programu nową funkcjonalność. Po pierwsze, refaktoryzacja pozwala mi lepiej zrozumieć kod, który mam zmieniać, niezależnie od tego, czy to ja jestem jego autorem, czy napisał go ktoś inny. Za każdym razem, gdy muszę analizować kod, by zrozumieć jego działanie, zadaję sobie pytanie, czy refaktoryzacja poprawi jego czytelność. Następnie refaktoryzuję. Częściowo robię to, by uprzyjemnić sobie następne odwiedziny w danym miejscu, ale moim celem jest także pełniejsze zrozumienie kodu, przez który przechodzę.   Kolejnym katalizatorem jest projekt utrudniający dodanie funkcjonalności. Patrzę na kod i myślę sobie: „Gdybym tylko zaprojektował to inaczej, dodanie tej funkcjonalności byłoby takie proste…”. Zamiast rozpaczać nad błędami młodości, poprawiam je, refaktoryzując. Znowu — robię to trochę z myślą o przyszłości, ale przede wszystkim dlatego, że to najszybsze rozwiązanie. Po refaktoryzacji dodanie nowej funkcjonalności przebiega bez zakłóceń.   Refaktoryzacja po wykryciu błędu   Podczas poprawiania błędów refaktoryzacja przydaje się jako narzędzie do zwiększania czytelności kodu. Gdy próbuję zrozumieć kod, pomagam sobie, stosując przekształcenia. Często ten proces aktywnej pracy z kodem ułatwia znalezienie błędu. Można spojrzeć na sprawę tak, że raport o błędzie to informacja o potrzebie refaktoryzacji. Najwyraźniej kod nie był wystarczająco czytelny, by zauważyć błąd od razu.   Refaktoryzacja podczas inspekcji kodu   Niektóre firmy regularnie przeprowadzają inspekcje kodu (ang. code review). Te, które tego nie robią, z pewnością zyskałyby na ich stosowaniu. Inspekcja kodu wspiera dzielenie się wiedzą wewnątrz zespołu. Doświadczeni programiści przekazują wiedzę początkującym. Więcej osób rozumie więcej aspektów złożonego systemu. Inspekcje zwiększają także czytelność kodu. Mogę uważać, że mój kod jest przejrzysty, jednak zmienię zdanie, gdy zespół nic z niego nie pojmie. To nieuniknione — trudno postawić się na miejscu osoby nieobeznanej z tematem, nad którym pracujemy. Dodatkowy zysk z inspekcji to możliwość poznania pomysłów innych osób. Liczba dobrych pomysłów, które przychodzą mi do głowy w ciągu tygodnia, jest ograniczona. Wkład innych osób ułatwia mi życie, dlatego cieszę się z każdej inspekcji.   Zauważyłem, że refaktoryzacja ułatwia mi przeprowadzanie inspekcji cudzego kodu. Przedtem czytałem kod, rozumiałem go do pewnego stopnia i sugerowałem poprawki. Teraz, gdy mam pewien pomysł, zastanawiam się, jak go zrealizować i czy refaktoryzacja w tym pomoże. Jeśli tak — zaczynam działać. Po wprowadzeniu paru przekształceń widzę wyraźnie, jaki wpływ na kod mają moje sugestie. Nie muszę ich sobie wyobrażać — widzę je. W rezultacie mogę zaproponować dodatkowe pomysły, na które nie wpadłbym bez refaktoryzacji.   Dzięki refaktoryzacji inspekcja kodu zwraca bardziej konkretne wyniki. Są nimi nie tylko sugestie, ale także przykłady implementacji. Ostatecznie całe to ćwiczenie prowadzi do poczucia spełnienia.   By proces się powiódł, konieczne jest stworzenie niewielkich grup inspekcyjnych. Z mojego doświadczenia wiem, że najlepiej sprawdzają się pary: autor kodu i jeden inspektor. Inspektor proponuje zmiany i wraz z autorem podejmuje decyzję na temat tego, czy można je łatwo dodać. Jeśli tak — od razu je wprowadzają.   W przypadku większych projektów z reguły lepiej pozyskać więcej opinii od większej grupy osób. Prezentacja kodu nie zawsze jest najlepszym rozwiązaniem. Osobiście preferuję diagramy UML oraz przechodzenie przez scenariusze z użyciem kart CRC (ang. Class — Responsibility — Cooperation, czyli klasa — odpowiedzialność — współpraca). Podsumowując: przeprowadzam inspekcje projektu w zespołach, a inspekcje kodu w parach.   Idea aktywnej inspekcji kodu jest intensywnie wykorzystywana w programowaniu w parach, znanemu z paradygmatu programowania ekstremalnego [Beck, XP]. Technika ta zakłada pracę dwóch programistów na jednej maszynie. Dzięki temu procesy inspekcji kodu oraz refaktoryzacji są wplecione w proces kodowania.   

Dlaczego refaktoryzacja działa? — Kent Beck Programy mają wartość dwojakiego typu: to, co mogą zrobić dla Ciebie dzisiaj, i to, co mogą zrobić jutro. W większości przypadków podczas programowania koncentrujemy się na tym, co program ma dla nas zrobić dzisiaj. Niezależnie od tego, czy naprawiamy błąd, czy dodajemy nową funkcjonalność, podnosimy dzisiejszą wartość programu, zwiększając jego możliwości. Niektórzy programiści potrzebują wiele czasu, by zrozumieć, że to, co system robi dzisiaj, to nie wszystko. Jeśli dzisiaj wykonasz swoją pracę w taki sposób, że uniemożliwisz sobie wykonanie jutrzejszej, przegrasz. Oczywiście, wiesz dobrze, co masz zrobić dzisiaj, ale nie masz pewności na temat jutra. Może to, może tamto, a może coś, czego dzisiaj nawet nie potrafisz sobie wyobrazić.   Wiem, co mam zrobić dzisiaj. Nie posiadam wystarczającej wiedzy na temat jutra. Jednak jeśli nie będę myślał o jutrze, na pewno sobie z nim nie poradzę.   Refaktoryzacja to jeden ze sposobów ukrócenia tej męki. Gdy orientujesz się, że wczorajsza decyzja dzisiaj nie ma już sensu, zmieniasz ją i możesz przystąpić do dzisiejszej pracy. Jutro odkryjesz, że część Twoich decyzji nadal jest naiwna i znów je zmienisz.   Co sprawia, że trudno jest zmieniać programy? W tej chwili przychodzą mi do głowy następujące cztery powody:
  • nieczytelny kod,
  • duplikowanie logiki,
  • struktura, w której dodanie nowej funkcjonalności psuje działanie istniejącego kodu,
  • złożona logika warunkowa.
Dążymy zatem do programów, które są czytelne, mają logikę definiowaną w jednym, określonym miejscu, nie pozwalają na wprowadzanie zmian psujących działanie istniejącego kodu i których logika warunkowa jest wyrażona w możliwie prosty sposób.   Refaktoryzacja to proces podnoszenia wartości programu nie poprzez zmianę zachowania, ale poprzez nadanie mu wartościowych cech przyspieszających programowanie.

  --- Tekst pochodzi z książki "Refaktoryzacja. Ulepszanie struktury istniejącego kodu" (Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts, Erich Gamma) - Wyd. Helion 2017.   Sprawdź książkę >>   Sprawdź eBook >>