window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster już istnieje') } else { w.LeadBooster = { q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: function (n) { this.q.push({ t: 't', n: n }) }, } } })() thecodest, Author at The Codest - Page 8 of 18

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:

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",

Do

return { water_amount: "Trochę " , how_to: "Od dołu",watering_duration: "2 tygodnie" } if plant.is_a? Suculent || plant.is_a? Kaktus

return { waterilość: "Trochę", jakdo: "Od dołu",nawadnianieduration: "2 tygodnie" } if plant.isa? Suculent || plant.is_a? Kaktus

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.

Zostań młodszym programistą Ruby

Czytaj więcej

GraphQL Ruby. Co z wydajnością?

Szyny i inne środki transportu

Rails Development z TMUX, Vim, Fzf + Ripgrep

pl_PLPolish