Projektowanie Obiektowe Refactoryzacja 1
Refactoryzacja Dokonywanie zmian w kodzie w celu uczynienia go latwiejszym do zrozumienia i wprowadzenia zmian, bez zmiany jego widocznego (zewnetrznego) zachowania. 2
Po co refaktoryzować? Refaktoryzacja polepsza projekt/architekture oprogramowania. Refaktoryzacja ulatwia zrozumienie kodu. Refaktoryzacja ulatwia szukanie bledow. Refaktoryzacja przyspiesza programowanie. 3
Zanim zrefaktoryzujesz Tests!!! Unit Regression Version Control 4
Is design dead? Czy refaktoryzacja umożliwia rezygnację z projektowania? 5
Code smells (stinks) Zapachy (smrody) kodu. If it stinks, change it. Grandma Beck Bloaters (Nadymacze) Object Orientation abusers Change preventers Dispensables Couplers 6
Bloaters Nadymacze Long Method Large Class Primitive Obsession Long Parameter List Data Clumps
Long Method Dlugie metody sa trudniejsze do zrozumienia. Powinny zostac podzielone na mniejsze metody ktorych nazwa adekwatnie oddaje ich funkcje. Dobrze nazwana krotka funkcja jest czytlniejsza niz kod. tmp=a;a=b;b=tmp; //swap(a,b); set[index[i]]=set[last]; index[last]=index[i];last ; Najczestsza refaktoryzacja w tym przypadku jest Extract Method Nowe srodowiska programistyczne ulatwiaja przegladanie takiego kodu umozliwiajac latwe nawigowanie pomiedzy wywolaniem fukcji, a jej kodem.
Large Class Rozbudowana klasa, rowniez jest trudna do zrozumienia. Czesto wskazuje na to, ze klasa ma za duzo opowiedzialnosci. Klasa powinna byc odpowidzialna wylacznie za jedno zadanie. Klasa moze miec za duzo zmiennych jak i za duzo metod. Najczestsza refaktoryzacja jest Extract Class.
Long Parameter List Podatna na bledy trudnosci z zapamietaniem kolejnosci i znaczenia i na zmiany
Data Clumps Kilka zmiennych (danych) ktore zawsze uzywane sa razem.
Primitive Obsession Uzywanie typow wbudowanych, zamiast klas dla prostych typow np. Money, Dlugosc it. Kompilator nie moze wykryc blednego przypisania typow. W C/C++ typedef wprowdza tylko alias, a nie nowy typ.
Object Orientation abusers Switch Statements Temporary Field Refused Bequest Alternative Classes with Different Interfaces
Switch Statements Czesto prowadzi do powielania kodu: ta sama instrukcja uzywana jest w wielu miejscach programu Dodanie nowego warunku wymaga znalezienia i zmiany wszystkich takich instrukcji. Nalezy rozwazyc wykorzystanie polimorfizmu. To moze wymagac utworzenia odpowiednich typow.
Temporary Field Przypadek pol, ktore sluza tylko kilku metodom, np. do przekazywania parametrow pomiedzy wywolaniami kilku metod. Zlozone struktury, Bufory itp. Nie odnosza sie do calej klasy.
Refused Bequest nie przyjeta darowizna Klasa dziedziczaca, nie wykorzytuje (nie przyjmuje) implementacji klasy nadrzednej, tzn dziedziczymy tylko czesc zachowania/implementacji. Moze to oznaczac zla strukture klas i nawet zlamanie zasady podstawiania Liskov. Klasa dziedziczaca, nie przyjmuje interfejsu klasy nadrzednej, np. dziedziczenie prywatne w C++. Dziedziczymy tylko implementacje.
Alternative Classes with Different Interfaces Metody w roznych klasach robia to samo, ale maja rozna sygnature/nazwy write, print etc. Dwie klasy robia podobne rzeczy. Nalezy alboje polaczyc, albo przeniesc wspolne zachowanie do nadklasy.
Change preventers Divergent Change Shotgun Surgery Parallel Inheritance Hierarchies
Divergent Change Zlamanie zasady pojedynczej odpowiedzialnosci. Klasa jest zmieniana czesto z kilku roznych powodow.
Shotgun Surgery Jedna zmiana, wymaga zmiany wielu klasach.
Parallel Inheritance Hierarchies Dwie lub wiecej rownoleglych hierarchi klas. Dodanie podklasy, w jednej hierarchi wymaga zmian w drugie. Zasadniczo jest odmiana zapachu Shotgun Surgery. Powstaje naturalnie, nie jest dla mnie jasne jak bardzo to smierdzi:)
Dispensables Lazy class Data class Duplicate Code Dead Code Speculative Generality
Data class Klasa ktora jest wylacznie opakowaniem na dane. Tzn. zawiera tylko pola oraz metody dostepowe. Wydaje mi sie, ze to kloci sie z zaleceniami dotyczacymi kilku innych zapachow...
Lazy class Klasa ktora robi za malo i nie zarabia na siebie.
Duplicate Code Straszny smrod! Powielanie tego samego lub bardzo podobnego kodu wielu miejscach. Zwykle wynik copy & paste. Utrudnia wprowadzanie zmian.
Dead Code Kod ktory nigdy nie jest wykonywany.
Speculative Generality Kod ktory jest pisany na zapas. Kod ogolny jest bardrdziej skomplikowany, a co za tym idzie trudniejszy do zrozumienia i utrzymania.
Couplers Feature Envy Inapriopriate Intimacy Message Chains Middle Man
Feature Envy Metoda/klasa ktors zbyt czesto korzysta z danych z innej klasy bardziej niz z danych z pol wlasnej klasy.
Inapriopriate Intimacy Klas ktora korzysta zby czesto z prywatnych pol innej klasy.
Message Chains Dluga sekwencja geterow.
Middle Man Naduzycie delegacji Klasa deleguje wiekszosc metod do innej klasy chyba, ze to jest proxy lub dekorator.
Inne Comments Incomplete Library Class 33
Comments Komentarze moga sluzyc jako dezodorant ktory przykrywa przykre zapachy. Komentarze moga sugerowac ktore czesci kodu sa niezrozumiale i wymagaja refaktoryzacji.
Incomplete Library Class Biblioteka ktora musimy dostosowac lub rozszerzyc do naszyhc potrzeb bez dostepu do jej kodu zrodlowego.
Refactorings (z Refaktoryzacja M. Fowler) Composing methods Moving Features Between Objects Organizing Data Simplifying Conditional Expression Making Method Calls Simpler Dealing with Generalization Big Refactorings
Rename method Extract method Move Method Extract Subclass Hide Delegate Introduce Foreign method Decompose Conditional
Rename method Dodaj nowa metode o nowej nazwie. Skopiuj do niej implementacje starej metody. Zamien implementacje startej metody na wywolanie nowej metody. Pozamieniaj w w kodzie wywolania starej nazwey na nowa nazwe. Usun stara metode. Jesli nie mozesz jej usunac bo jest czescia interfejsu, oznacz ja jako deprecated.
Extract method Stworz nowa metode o adekwatnej nazwie. Przekopiuj wyciagany kod ze starej metody do nowej. Sprawdz, czy kod zawiera zmienne lokalne (temporary) dla starej metody. Uzyj Split Temporary Variable jesli trzeba. Zmienne ktore wystepuja wylacznie w wyciaganym kodzie, stana sie zmiennymi lokalnymi nowej metody. Zmienne lokalne ktorych kod nie modyfikuje, stana sie parametrami nowej metody.
Extract Method modufikowane zmienne lokalne Znajdz zmiene lokalne ktore wyciagniety kod modyfikuje. Jesli jest tylko jedna, to sprobuj przypisac do niej wynik nowej funkcji. Jesli to trudne lub jest ich wiecej to zastosuj Replace Temp with Query. Jesli to nie pomoze, to zrezygnuj z faktoryzacji, lub zastosuj Replace Method with Method Object.
Split Temporary Variable FILE *out=fopen( test.cfg, r );... fclose(out); out=fopen( results.dat, w );... fclose(out);
Split Temporary Variable FILE *out=fopen( test.cfg, r );... fclose(out); out=fopen( results.dat, w );... fclose(out); const FILE *test_file=fopen( test.cfg, r );... fclose(test_file); const FILE *result_file=fopen( results.dat, w );... fclose(result_file);
Replace Temp with Query void some_method() { int total_n_pixels=n_pixels_in_row_*n_pixels_in_row_; for(int i=0;i<total_n_pixels;++i) { }...
Replace Temp with Query void some_method() { int total_n_pixels=n_pixels_in_row_*n_pixels_in_row_; for(int i=0;i<total_n_pixels;++i) { }... int total_n_pixels() const { return n_pixels_in_row_*n_pixels_in_row_; } void some_method() { for(int i=0;i<total_n_pixels();++i) { }...
Replace Method with Method Object. Jesli jakas metoda zawiera duzo zmiennych lokalnych (temporary) to mozemy ja zamienic w obiekt fukcyjny. Tworzymy nowa klase o takiej samej nazwie jak metoda. W tej klasie tworzymy pole dla obiektu ktory zawiera zamieniana metode oraz dla kazdego parametru i zmiennej lokalnej metody. Dodajemy konstrutor ktory przyjmuje orginalny obiekt i parametry. Tworzymy metode o nazwie np. compute(). Przenosimy do niej kod ze starej metody. Poniewaz zmienne lokalne sa teraz polami nowej klasy mozemy spokojnie podzielic metode na mniejsze
class SomeClass { int long_and_complicated_method(int arg) { int tmp1,tmp2,tmp3; return...; } }
class SomeClass { int class long_and_complicated_method(int LongAndComplicatedMethod { arg) { int LongAndComplicatedMethod(SomeClass tmp1,tmp2,tmp3; &some_class, int arg) :some_class_(some_class), arg_(arg) {}; int compute() {...} return private:...; } SomeClass &some_class_; int arg_; } int tmp1,tmp2,tmp3; }
class SomeClass { int class long_and_complicated_method(int LongAndComplicatedMethod { arg) { int LongAndComplicatedMethod(SomeClass tmp1,tmp2,tmp3; &some_class, int arg) :some_class_(some_class), SomeClass { arg_(arg) {}; int compute() {...} return private: int...; long_and_complicated_method(int arg) { } SomeClass return LongAndComplicatedClass(*this,arg).compute(); &some_class_; int } arg_; } int tmp1,tmp2,tmp3; } }
Automatyczna refaktoryzacja Wiekszosc srodowisk programistycznych posiada narzedzia do automatycznej rekatoryzacji. Zwykle jednak tylko kilku i to o okrojonych mozliwosciach. 49