Refaktoryzacja
Refaktoryzacja (czasem też refaktoring[1], ang. refactoring) – proces wprowadzania zmian w projekcie/programie, w wyniku których zasadniczo nie zmienia się funkcjonalność. Celem refaktoryzacji jest więc nie wytwarzanie nowej funkcjonalności, ale utrzymywanie odpowiedniej, wysokiej jakości organizacji systemu. W ramach refaktoryzacji podejmowane są następujące działania:
- modyfikowanie elementów systemu w celu wpasowania ich w przyjęte standardy i wzorce
- poszukiwanie nowych standardów i wzorców, które pojawiły się w systemie w trakcie jego rozwoju i ich precyzyjne definiowanie (łącznie z wpasowywaniem istniejących elementów w te definicje).
Dzięki refaktoryzacji w systemie ogranicza się redundancję (nadmiarowość, np. istnienie wielu obiektów i procedur o takiej samej lub bardzo zbliżonej funkcjonalności, a mających niezależne implementacje, czyli stosując regułę DRY) i wprowadza standardy. W przypadku systemów o architekturach wielowarstwowych, refaktoryzacja jest jednym z istotnych czynników gwarantujących zachowanie silnej separacji warstw systemu i ich przejrzystej struktury.
Refaktoryzacja jest kosztowna, ale jest istotnym elementem zarządzania projektem informatycznym. W szczególności zaś przy dużych i złożonych projektach koszt dobrze prowadzonej refaktoryzacji powinien być zrekompensowany dużo niższym kosztem wprowadzania późniejszych zmian w projekcie, szczególnie zmian o charakterze globalnym (przekrojowym), dotykających wielu modułów funkcjonalnych jednocześnie. W przypadku projektów obarczonych dużym ryzykiem niepowodzenia (np. z powodu niestabilnych wymagań funkcjonalnych klienta) regularnie prowadzona refaktoryzacja wydaje się być nieodzowna.
Narzędzia wspomagające refaktoryzację należą do narzędzi CASE i możemy je dziś spotkać jako narzędzia wbudowane w zintegrowane środowiska programistyczne.
Przykłady refaktoryzacji
Usuwanie nadmiarowych konstruktorów
Konieczność zastosowania tego typu metody refaktoryzacji następuje najczęściej w przypadku, gdy programista chcąc szybko zastosować nową funkcjonalność, rozszerza klasę o kolejne konstruktory z nowymi parametrami. Podajmy przykład oparty na systemie obliczającym wartość pożyczki:
Pozyczka(wartosc); Pozyczka(wartosc, oprocentowanie); Pozyczka(wartosc, oprocentowanie, czasSplaty); Pozyczka(wartosc, oprocentowanie, czasSplaty, ryzyko); Pozyczka(wartość, oprocentowanie, czasSplaty, ryzyko, status);
Początkowo w kodzie programu istniał jeden konstruktor o nazwie „Pozyczka” z jednym parametrem (wartosc). W miarę dodawania nowych funkcjonalności pojawiała się potrzeba tworzenia konstruktorów „Pozyczka” o większej liczbie parametrów. W ten sposób powstało pięć metod o tej samej nazwie, lecz różnym działaniu. Taki kod zazwyczaj wymaga refaktoryzacji, gdyż nazwa metody powinna jednoznacznie wskazywać, za jaką funkcjonalność systemu metoda odpowiada. Przy dalszym rozwoju kodu tej klasy mogą pojawić się problemy z implementacją wynikające z niezrozumienia faktycznej roli konstruktora. Po refaktoryzacji kod może mieć postać:
Pozyczka(wartosc, oprocentowanie, czasSplaty, ryzyko, status); ustalWartosc(wartosc); stworzPozyczke(wartosc, oprocentowanie); dajPozyczke(wartosc, oprocentowanie, czasSplaty); sprawdzPozyczke(wartosc, oprocentowanie, czasSplaty, ryzyko);
Tworzenie łańcucha konstruktorów
Stosowanie łańcucha konstruktorów jako metody refaktoryzacyjnej jest wskazane w typowych przypadkach nadmiarowości kodu w klasach o wielu konstruktorach, które nadpisują po sobie kod. Sztandarowym przykładem takiej sytuacji jest przypisywanie wartości parametrów do pól w każdym konstruktorze klasy:
Pozyczka(wartosc) { this.wartosc = wartosc; } Pozyczka(wartosc, oprocentowanie) { this.wartosc = wartosc; this.oprocentowanie = oprocentowanie; } Pozyczka(wartosc, oprocentowanie, czasSplaty) { this.wartosc = wartosc; this.oprocentowanie = oprocentowanie; this.czasSplaty = czasSplaty; }
Można zauważyć, że zamiast każdorazowego przypisywania wartości można utworzyć tzw. łańcuch, który pozwoli na uproszczenie kodu:
Pozyczka(wartosc) { this(wartosc, null); } Pozyczka(wartosc, oprocentowanie) { this(wartosc, oprocentowanie, null); } Pozyczka(wartosc, oprocentowanie, czasSplaty) { this.wartosc = wartosc; this.oprocentowanie = oprocentowanie; this.czasSplaty = czasSplaty; }
W przypadku, gdy łańcuch konstruktorów jest długi kolejne wywołania mogą okazać się mało wydajne. Alternatywą w takim przypadku jest zastosowanie konstruktora uniwersalnego (który przyjmuje wszystkie możliwe parametry):
Pozyczka(wartosc) { this(wartosc, null, null); } Pozyczka(wartosc, oprocentowanie) { this(wartosc, oprocentowanie, null); } Pozyczka(wartosc, oprocentowanie, czasSplaty) { this.wartosc = wartosc; this.oprocentowanie = oprocentowanie; this.czasSplaty = czasSplaty; }
Zastosowanie tych metod pozwala na ustalenie jednego miejsca przypisania wartości parametrów do pól. W takim przypadku konieczne jest wychwytywanie wszystkich możliwych zerowych wartości parametrów (tzw. null pointerów), albo zdefiniowanie i użycie wartości domyślnych.
Przypisy
Linki zewnętrzne
- Strona Martina Fowlera na temat refaktoryzacji (ang.)
- Martin Fowler - Software Design in the 21st Century prelekcja Martina Fowlera na temat refaktoryzacji z przykładami (ang.)