Szybki przewodnik po refaktoryzacji dla początkujących
Marta Świątkowska
Junior Software Engineer
Być może piszę o czymś oczywistym dla wielu, ale być może nie dla wszystkich. Refaktoryzacja to moim zdaniem skomplikowany temat, ponieważ polega na zmianie kodu bez wpływu na jego działanie.
Dlatego dla niektórych jest niezrozumiałe, że refaktoryzacja jest właściwie dziedziną programowania, a także bardzo ważną częścią pracy programisty. Kod nieustannie ewoluuje, będzie modyfikowany tak długo, jak długo będzie istniała możliwość dodawania nowych funkcjonalności. Może on jednak przybrać formę, która nie pozwala już na efektywne dodawanie nowych funkcjonalności i łatwiej byłoby napisać cały program od nowa.
Czym jest refaktoryzacja?
Zazwyczaj słyszy się odpowiedź, że jest to zmiana struktury kodu poprzez zastosowanie serii przekształceń refaktoryzacyjnych bez wpływu na obserwowalne zachowanie kodu. To prawda. Ostatnio natknąłem się również na definicję Martin Fowler w swojej książce "Ulepszanie projektu istniejącego kodeksu" gdzie opisuje refaktoryzacja jako "wprowadzanie dużych zmian małymi krokami". Opisuje on refaktoryzacja jako zmiana kodu nie wpływająca na jego działanie, ale podkreśla, że należy to robić małymi krokami.
Książka zaleca również, aby refaktoryzacja nie wpływa na działanie kodu i zaznacza, że nie ma to żadnego wpływu na zaliczenie testów w dowolnym momencie. Opisuje krok po kroku, jak bezpiecznie wykonać refaktoryzacja. Podobała mi się jego książka, ponieważ opisuje proste sztuczki, które można wykorzystać w codziennej pracy.
Dlaczego potrzebujemy refaktoryzacji?
Najczęściej może być potrzebna, gdy chcemy wprowadzić nową funkcjonalność, a kod w obecnej wersji na to nie pozwala lub byłoby to trudniejsze bez zmian w kodzie. Przydaje się również w przypadkach, gdy dodawanie kolejnych funkcji jest nieopłacalne czasowo, tzn. szybciej byłoby przepisać kod od zera. Myślę, że czasami zapomina się, że refaktoryzacja może sprawić, że kod będzie czystszy i bardziej czytelny. Martin pisze w swojej książce, jak przeprowadza refaktoryzację, gdy czuje nieprzyjemne zapachy w kodzie i, jak to ujął, "zawsze pozostawia miejsce na lepsze". I tu mnie zaskoczył widząc refaktoryzację jako element codziennej pracy z kodem. Dla mnie kody są często niezrozumiałe, czytanie tego to trochę doświadczenie, bo kod jest często nieintuicyjny.
Cechą wyróżniającą dobrze zaprojektowany program jest jego modułowośćdzięki czemu wystarczy znać tylko niewielką część kodu, aby wprowadzić większość modyfikacji. Modułowość ułatwia także nowym osobom wdrożenie się i rozpoczęcie wydajniejszej pracy. Aby osiągnąć tę modułowość, powiązane elementy programu muszą być zgrupowane razem, a połączenia muszą być zrozumiałe i łatwe do znalezienia. Nie ma jednej praktycznej zasady, jak to zrobić. W miarę poznawania i rozumienia coraz większej części tego, jak kod ma działać lepiej, można grupować elementy, ale czasami trzeba też testować i sprawdzać.
Jedną z zasad refaktoryzacji w YAGNIJest to akronim od "You Aren't Gonna Need It" i wywodzi się od Programowanie eXtreme (XP) używany głównie w Zwinnośćrozwój oprogramowania zespoły. Długa historia w skrócie, YAGNI mówi, że należy robić tylko rzeczy aktualne. Zasadniczo oznacza to, że nawet jeśli coś może być potrzebne w przyszłości, nie powinno być robione teraz. Ale nie możemy też zmiażdżyć dalszych rozszerzeń i tutaj ważna staje się modułowość.
Mówiąc o refaktoryzacjaNależy jednak wspomnieć o jednym z najważniejszych elementów, czyli testach. W refaktoryzacja, musimy wiedzieć, że kod nadal działa, ponieważ refaktoryzacja nie zmienia sposobu działania, ale jego strukturę, więc wszystkie testy muszą zostać zaliczone. Najlepiej jest uruchamiać testy dla części kodu, nad którą pracujemy, po każdej małej transformacji. Daje nam to potwierdzenie, że wszystko działa tak jak powinno i skraca czas całej operacji. O tym właśnie mówi Martin w swojej książce - uruchamiać testy tak często, jak to możliwe, aby nie cofać się o krok i nie tracić czasu na szukanie transformacji, która coś zepsuła.
Refaktoryzacja kodu Bez testowania jest to uciążliwe i istnieje duża szansa, że coś pójdzie nie tak. Jeśli to możliwe, najlepiej byłoby dodać przynajmniej kilka podstawowych testów, które dadzą nam trochę pewności, że kod działa.
Transformacje wymienione poniżej są tylko przykładami, ale są naprawdę pomocne w codziennym programowaniu:
Wyodrębnianie funkcji i zmiennych - jeśli funkcja jest zbyt długa, sprawdź, czy istnieją jakieś pomniejsze funkcje, które można wyodrębnić. To samo dotyczy długich linii. Te przekształcenia mogą pomóc w znalezieniu duplikatów w kodzie. Dzięki Small Functions kod staje się bardziej przejrzysty i zrozumiały,
Zmiana nazw funkcji i zmiennych - stosowanie prawidłowej konwencji nazewnictwa jest niezbędne do dobrego programowania. Dobrze dobrane nazwy zmiennych mogą wiele powiedzieć o kodzie,
Grupowanie funkcji w klasę - ta zmiana jest pomocna, gdy dwie klasy wykonują podobne operacje, ponieważ może skrócić długość klasy,
Overriding the Nested Statement - jeśli warunek sprawdza się dla specjalnego przypadku, należy wydać instrukcję return, gdy warunek wystąpi. Tego typu testy są często określane jako klauzula ochronna. Zastąpienie zagnieżdżonej instrukcji warunkowej instrukcją wyjścia zmienia nacisk w kodzie. Konstrukcja if-else przypisuje równą wagę obu wariantom. Dla osoby czytającej kod jest to informacja, że każdy z nich jest równie prawdopodobny i ważny,
Wprowadzenie specjalnego przypadku - jeśli używasz niektórych warunków w swoim kodzie wiele razy, warto utworzyć dla nich osobną strukturę. W rezultacie większość sprawdzeń przypadków specjalnych można zastąpić prostymi wywołaniami funkcji. Często spotykaną wartością, która wymaga specjalnego przetwarzania jest null. Dlatego też wzorzec ten jest często nazywany obiektem zerowym. Podejście to może być jednak stosowane w każdym szczególnym przypadku,
Zastąpienie polimorfizmu instrukcji warunkowej.
Przykład
To jest artykuł o refaktoryzacja i potrzebny jest przykład. Poniżej chcę pokazać prostą próbkę refaktoryzacji z wykorzystaniem Zastępowanie zagnieżdżonej instrukcji i Zastąpienie polimorfizmu instrukcji warunkowej. Powiedzmy, że mamy funkcję programu, która zwraca hash z informacjami o tym, jak podlewać rośliny w prawdziwym życiu. Takie informacje prawdopodobnie znajdowałyby się w modelu, ale w tym przykładzie mamy je w funkcji.
def watering_info(plant)
result = {}
if plant.is_a? Suculent || plant.is_a? Cactus
result = { water_amount: "A little bit " , how_to: "Od dołu", watering_duration: "2 weeks" }
elsif plant.is_a? Alocasia || plant.is_a? Maranta
result = { water_amount: "Duża ilość", how_to: "Jak wolisz", watering_duration: "5 dni" }
elsif plant.is_a? Peperomia
result = { water_amount: "Dicent amount",
how_to: "Od dołu! Nie lubią wody na liściach",
watering_duration: "1 week" }
else
result = { water_amount: "Dicent amount",
how_to: "Jak wolisz",
watering_duration: "1 week"
}
end
return result
end
Pomysł polega na zmianie, jeśli ma powrócić:
if plant.isa? Suculent || plant.isa? Kaktus
result = { wateramount: "A little bit " , howto: "Od dołu",
I tak dalej, aż dojdziemy do funkcji, która wygląda tak:
def watering_info(plant)
return result = { wateramount: "Trochę " , howto: "Od dołu", wateringduration: "2 tygodnie" } if plant.isa? Suculent || plant.is_a? Cactus
return result = { wateramount: "Duża ilość", howto: "Jak wolisz", wateringduration: "5 dni" } if plant.isa? Alocasia || plant.is_a? Maranta
return result = { water_amount: "Dicent amount",
howto: "Od dołu! Nie lubią wody na liściach",
wateringduration: "1 tydzień" } if plant.is_a? Peperomia
return result = { water_amount: "Dicent amount",
how_to: "Jak wolisz",
watering_duration: "1 week"
}
end
Na samym końcu mieliśmy już wynik zwrotny. Dobrym nawykiem jest robienie tego krok po kroku i testowanie każdej zmiany. Mógłbyś zastąpić ten blok if przypadkiem switch i od razu wyglądałoby to lepiej i nie musiałbyś za każdym razem sprawdzać wszystkich ifów. Wyglądałoby to tak:
def watering_info(plant)
swich plant.class.to_string
case Suculent, Cactus
{ wateramount: "A little bit " , howto: "Od dołu", watering_duration: "2 weeks" }
case Alocasia, Maranta
{ wateramount: "Duża ilość", howto: "Jak wolisz", watering_duration: "5 days" }
case Peperomia
{ water_amount: "Dicent amount",
how_to: "Od dołu! Nie lubią wody na liściach",
watering_duration: "1 week" }
else
{ water_amount: "Dicent amount",
how_to: "Jak wolisz",
watering_duration: "1 week" }
koniec
end
Następnie można zastosować Zastąpienie polimorfizmu instrukcji warunkowych. Ma to na celu utworzenie klasy z funkcją, która zwraca prawidłową wartość i przełącza je we właściwe miejsca.
Klasa Suculent
...
def watering_info()
return { wateramount: "Trochę " , howto: "Od dołu", watering_duration: "2 weeks" }
end
end
class Kaktus
...
def watering_info()
return { wateramount: "Trochę " , howto: "Od dołu", watering_duration: "2 weeks" }
end
end
class Alocasia
...
def watering_info
return { wateramount: "Duża ilość", howto: "Jak wolisz", watering_duration: "5 days" }
end
end
class Maranta
...
def watering_info
return { wateramount: "Duża ilość", howto: "Jak wolisz", watering_duration: "5 days" }
end
end
class Peperomia
...
def watering_info
return { water_amount: "Dicent amount",
how_to: "Od dołu! Nie lubią wody na liściach",
watering_duration: "1 week" }
end
end
klasa Roślina
...
def watering_info
return { water_amount: "Dicent amount",
how_to: "Jak wolisz",
watering_duration: "1 week" }
koniec
end
W głównej funkcji watering_infofunction kod będzie wyglądał następująco:
def watering_info(plant)
plant.map(&:watering_info)
end
Oczywiście funkcję tę można usunąć i zastąpić jej treścią. W tym przykładzie chciałem zaprezentować ogólny wzorzec refaktoryzacji.
Podsumowanie
Refaktoryzacja to ważny temat. Mam nadzieję, że ten artykuł był zachętą do przeczytania więcej. Te umiejętności refaktoryzacji pomóc w wyłapywaniu błędów i doskonaleniu warsztatu czystego kodu. Polecam lekturę książki Martina (Improving the Design of Existing Code), która jest dość podstawowym i pomocnym zbiorem zasad dotyczących czystego kodu. refaktoryzacja. Autor pokazuje różne transformacje krok po kroku z pełnym wyjaśnieniem i motywacją oraz wskazówkami, jak uniknąć błędów w refaktoryzacja. Ze względu na swoją wszechstronność, jest to wspaniała książka dla frontendowców i programiści backendu.