1 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji Bartosz Walter
2 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (2) Motto „Nawet idiota potrafi napisać kod zrozumiały dla komputera. Prawdziwi programiści tworzą kod zrozumiały dla ludzi.” Martin Fowler
3 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (3) Plan wykładu Wprowadzenie Koszt refaktoryzacji Poprawność i jej weryfikacja Przykre zapachy w kodzie programu Metody identyfikacji przykrych zapachów
4 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (4) Plan wykładu Wprowadzenie
5 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (5) Motywacja Wysoki koszt pielęgnacji oprogramowania –Yourdon: do 80% kosztu użytkowania –Boehm: wytworzenie linii kodu: $30, pielęgnacja: $4000 Naturalny wzrost złożoności i entropii oprogramowania Prawa Lehmana: konieczna ciągła restrukturyzacja
6 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (6) Intuicyjna definicja void wykonaj() Opdyke (1991) Refaktoryzacja to: zmiana wewnętrznej struktury kodu programu która zwiększa jego czytelność i obniża koszt pielęgnacji ale nie zmienia jego obserwowalnego zachowania
7 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (7) Przykład: Extract Method Wyłączenie metody void scalarProduct(String[] params) { int[] x = prepareX(params); int[] y = prepareY(params); int[] product = computeXY(x, y); //... for (i = 0; i < x.length; i++) { out.println("X = " + x[i]); out.println("Y = " + y[i]); out.println("X * Y = " + product[i]); } void scalarProduct(String[] params) { int[] x = prepareX(params); int[] y = prepareY(params); int[] product = computeXY(x, y); //... printScalarProduct(x, y, product); void printScalarProduct(int[] x, int[]y, int[] product) { for (i = 0; i < x.length; i++) { out.println("X = " + x[i]); out.println("Y = " + y[i]); out.println("X * Y = " + product[i]); } Jakie są warunki wstępne poprawności tego przekształcenia?
8 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (8) Plan wykładu Koszt refaktoryzacji
9 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (9) Koszt refaktoryzacji Refaktoryzacja nie zwiększa funkcjonalności programu, ale kosztuje identyfikacja problemu przekształcenie kodu weryfikacja poprawności Koszt zależy od: własności języka programowania wsparcia ze strony narzędzi CASE natury wykonywanego przekształcenia liczby i jakości posiadanych testów
10 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (10) Eksperymentalna ocena kosztu refaktoryzacji Ad hoc 8 osób Testy & refaktoryzacja 6 osób Przyrost III: 1 funkcja Przyrost II: 2 funkcje Przyrost I: 2 funkcje Przyrost 0: szkielet
11 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (11) Porównanie cykli produkcyjnych Ad hoc (AH)Z testami i refaktoryzacją (TR) Refaktoryzacja Programowanie Testy akceptacyjne Testy jednostkowe Analiza Programowanie Testy akceptacyjne
12 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (12) Wyniki eksperymentu Pracochłonność każdego przyrostu
13 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (13) Narzut związany z refaktoryzacją
14 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (14) Kiedy refaktoryzacja się opłaca? Do trzech razy sztuka Przed implementacją nowej funkcji Identyfikacja obszaru zmienności Regularne przeglądy Stały zakres prac Projekty jednorazowe Przedwcześnie zatwierdzone interfejsy Niestabilny kod Przy bliskich terminach Niedokończona refaktoryzacja przypomina dług. Można z nim żyć, ale jest to kosztowne. Ward Cunningham
15 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (15) Plan wykładu Poprawność przekształceń refaktoryzacyjnych
16 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (16) Przykład: Inline Temp i = 0;... tab[0] = ++i; tab[1] = ++i; tab[2] = ++i;... i = 0; x = i++; tab[0] = x; tab[1] = x; tab[2] = x;... ? 123123 111111
17 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (17) Sposoby weryfikacji poprawności Analiza kodu Przypadki testowe void metoda() warunki początkowe warunki końcowe
18 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (18) Plan wykładu Przykre zapachy w kodzie programów
19 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (19) Przykre zapachy w kodzie programów Jeżeli brzydko pachnie, należy zmienić filozofia babci Kenta Becka dotycząca pielęgnacji niemowląt
20 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (20) Duplicated Code Nazwa Zduplikowany kod Objawy Identyczny lub podobny kod znajduje się w wielu miejscach systemu Rozwiązanie w jednej klasie: wyłącz wspólne fragmenty do nowej metody (Extract Method) w klasach o wspólnej nadklasie: wyłącz wspólne części (Extract Method), a następnie przesuń ją do nadklasy (Pull-up the Method) w klasach niezwiązanych: wyłącz wspólne części do nowej klasy (Extract Class) i deleguj do nich wywołania
21 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (21) Long Method Nazwa Długa metoda Objawy Metoda wykonuje w rzeczywistości wiele czynności Brak wsparcia ze strony metod niższego poziomu Rozwiązanie wyłącz fragmenty do nowych metod (Extract Method) wyłącz zmienne tymczasowe do zewnętrznych metod (Replace Temp with Query) wyłącz metodę do nowej klasy (Replace Method with Method Object) zmniejsz liczbę parametrów (Introduce Parameter Object lub Preserve Whole Object)
22 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (22) Large Class Nazwa Nadmiernie rozbudowana klasa Objawy Klasa posiada zbyt wiele odpowiedzialności Liczne klasy wewnętrzne, pola i metody Duża liczba metod upraszczających Rozwiązanie wydziel nową klasę i odwołuj się do niej za pomocą referencji (Extract Class), dziedziczenia (Extract Subclass lub Extract Superclass) lub korzystając z polimorfizmu (Extract Interface), a następnie przesuń do niej pola i metody (Pull up Member, Push Down Member, Move Member)
23 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (23) Switch Statements Nazwa Skomplikowane instrukcje warunkowe Objawy Metoda zawiera złożoną, wielopoziomową instrukcję if lub switch Rozwiązanie wyłącz wspólne fragmenty do nowej metody (Extract Method) wyłącz gałęzie instrukcji warunkowej do polimorficznych klas (Replace Conditional with Polimorphism/State) wyłącz gałęzie instrukcji warunkowej do podklas (Replace Conditional with Subclasses)
24 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (24) Message Chains Nazwa Łańcuchy wywołań metod Objawy łańcuch wywołań przez delegację naruszenie prawa Demeter Rozwiązanie usuń niepotrzebne wywołania i ukryj delegację (Hide Delegate) przesuń zbędne metody do klas w dalszej części łańcucha (Extract Method i Move Method)
25 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (25) Data Class Nazwa Pojemnik na dane Objawy Klasa jedynie przechowuje dane i nie posiada metod oferujących użyteczne funkcje (Data Transfer Object) Rozwiązanie przesuń część kodu klientów do klasy-pojemnika danych (Extract Method i Move Method) podziel dane klasy pomiędzy klientów i usuń ją (Inline Class)
26 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (26) Refused Bequest Nazwa Odrzucony spadek Objawy Podklasa nie wykorzystuje odziedziczonych metod i pól Rozwiązanie utwórz nową podklasę i przesuń do niej niechciane składowe z nadklasy (Push Down Member) jeżeli podklasa nie powinna posiadać interfejsu nadklasy, zastosuj delegację zamiast dziedziczenia (Replace Inheritance with Delegation)
27 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (27) Inappropriate Intimacy Nazwa Niewłaściwa hermetyzacja Objawy Klasa bezpośrednio odwołuje się do składowych wewnętrznych innej klasy Rozwiązanie przesuń składową do właściwej klasy (Move Member) ogranicz powiązania do jednokierunkowych (Change Bi- directional References to Unidirectional) wyłącz nową klasę ze wspólnych elementów obu klas (Extract Class) w przypadku dziedziczenia odseparuj nadklasę od podklasy (Replace Inheritance with Delegation)
28 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (28) Lazy Class Nazwa Bezużyteczna klasa Objawy Klasa nie posiada żadnej lub ograniczoną funkcjonalność Rozwiązanie przesuń wybrane funkcje z klas klienckich (Move Member) w przypadku podklas usuń klasę z hierarchii dziedziczenia (Collapse Hierarchy) podziel składowe pomiędzy jej klientów (Move Member) i usuń ją (Inline Class)
29 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (29) Feature Envy Nazwa Zazdrość o funkcje Objawy Metoda w klasie częściej korzysta z metod w obcych klasach niż we własnej Niska spójność klasy Rozwiązanie przesuń metodę do właściwej klasy (Move Method) przekaż referencję this do metody w drugiej klasie (wzorzec projektowy Visitor)
30 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (30) Shotgun Surgery Nazwa Odpryskowa modyfikacja Objawy Zmiana w jednym miejscu powoduje konieczność modyfikacji w innych Rozwiązanie umieść zmienne elementy wewnątrz jednej klasy (Extract Class, Move Method i Move Field) usuń zbędne klasy (Inline Class)
31 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (31) Plan wykładu Wybrane przekształcenia refaktoryzacyjne
32 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (32) Encapsulate Collection Problem Metoda get() w klasie właściciela zwraca kolekcję dostępną do modyfikacji Cel Przeniesienie odpowiedzialności za kolekcję do jej właściciela Mechanika dodaj w klasie właściciela metody add() i remove() dla kolekcji zmień bezpośrednie odwołania do metod add() i remove() kolekcji odwołaniami do metod jej właściciela zmień metodę get() zwracającą kolekcję, tak aby zwracała jej widok tylko do odczytu skompiluj i przetestuj opcjonalnie: zmień metodę get() tak, aby zwracała Iterator M. Fowler, 1999
33 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (33) Przykład public class Student { Collection wyklady; public Collection wyklady() { return wyklady; }
34 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (34) Przykład public class Student { Collection wyklady; public boolean dodajWyklad(Wyklad w) { return wyklady.add(w); } public boolean usunWyklad(Wyklad w) { return wyklady.remove (w); } public Collection wyklady() { return Collections.unmodifiableCollection(wyklady); }
35 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (35) Introduce Null Object Problem Referencje do klasy wymagają ciągłego porównywania z wartością null Cel Wprowadzenie obiektu reprezentującego wartość null Mechanika utwórz podklasę reprezentującą wartość null o nazwie NullKlasa utwórz w klasie i podklasie metodę isNull(); w klasie metoda zwraca false, w podklasie – true skompiluj klasy zastąp u klientów referencje null klasy instancjami podklasy zastąp warunki sprawdzające referencję null wywołaniem isNull() pokryj metody w podklasie zgodnie z semantyką obiektu reprezentującego null, usuń metody isNull() M. Fowler, 1999
36 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (36) Przykład Ksiazka ksiazka = null String autor = null; // operacje na zmiennej ksiazka if (ksiazka != null) { autor = ksiazka.autor(); } else { autor = ""; } public class Ksiazka { public String autor() { return autor; }
37 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (37) Przykład public class PustaKsiazka extends Ksiazka { public czyPusta() { return true; } Ksiazka ksiazka = new PustaKsiazka(); String autor; if (ksiazka.czyPusta()) { autor = ksiazka.autor(); } else { autor = ""; }
38 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (38) Przykład public class PustaKsiazka extends Ksiazka { public String autor() { return ""; } Ksiazka ksiazka = new PustaKsiazka(); String autor = ksiazka.autor();
39 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (39) Change Unidirectional Association with Bi
40 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (40) Przykład public class Tom { private Ksiazka ksiazka; Ksiazka ksiazka() { return Ksiazka; } void przypiszKsiazke(Ksiazka ksiazka) { this.ksiazka = ksiazka; } public class Ksiazka { //... }
41 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (41) Przykład public class Tom { private Ksiazka ksiazka; Ksiazka ksiazka() { return Ksiazka; } void przypiszKsiazke(Ksiazka ksiazka) { this.ksiazka = ksiazka; } public class Ksiazka { private Set tomy = new HashSet(); public Set __tomy() { return tomy; }
42 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (42) Przykład public class Tom { private Ksiazka ksiazka; Ksiazka ksiazka() { return Ksiazka; } void przypiszKsiazke(Ksiazka ksiazka) { if (ksiazka != null) ksiazka.__tomy().remove(this); this.ksiazka = ksiazka; if (ksiazka != null) ksiazka.__tomy().add(this); } public class Ksiazka { private Set tomy = new HashSet(); public Set __tomy() { return tomy; }
43 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (43) Replace Inheritance with Delegation Problem Podklasa niepotrzebnie dziedziczy pola i metody z nadklasy Cel Zastąpienie dziedziczenia jawną delegacją do dawnej nadklasy Mechanika utwórz w podklasie pole typu nadklasy i przypisz mu referencję this kolejno zmieniaj odwołania do metod nadklasy na odwołania przez delegację usuń dziedziczenie pomiędzy nadklasą i podklasą wprowadź metody delegujące do wykorzystywanych wcześniej metod dziedziczących z nadklasy skompiluj M. Fowler, 1999
44 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (44) Przykład public class KartaCzytelnicza { private Czytelnik czytelnik; public double naliczKare(int dni) { return 10 * dni; } public Czytelnik czytelnik() { return czytelnik; } public class KartaCzytelniczaUlgowa extends KartaCzytelnicza{ public double naliczKare(int dni) { return 0.4 * super.naliczKare(dni) }
45 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (45) Przykład public class KartaCzytelnicza { private Czytelnik czytalnik; public double naliczKare(int dni) { return 10 * dni; } public Czytelnik czytelnik() { return czytelnik; } public class KartaCzytelniczaUlgowa extends KartaCzytelnicza{ private KartaCzytelnicza karta = this; public double naliczKare(int dni) { return 0.4 * karta.naliczKare(dni) }
46 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (46) Przykład public class KartaCzytelnicza { //... } public class KartaCzytelniczaUlgowa { private KartaCzytelnicza karta; public KartaCzytelniczaUlgowa(KartaCzytelnicza karta) { this.karta = karta; } public double naliczKare(int dni) { return 0.4 * karta.naliczKare(dni) } public Czytelnik czytelnik() { return karta.czytelnik(); }
47 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (47) Replace Conditional with Polymorphism Problem Wyrażenie warunkowe wykonuje różne akcje w zależności od stanu obiektu Cel Wyłącz każdą gałąź instrukcji warunkowej do metody w podklasie reprezentującej stan obiektu Mechanika wyłącz wyrażenie warunkowe do nowej metody przenieś metodę do klasy abstrakcyjnej reprezentującej stan pokryj metodę w podklasach, kopiując do nich odpowiednią gałąź wyrażenia warunkowego skompiluj i przetestuj usuń przeniesioną gałąź w klasie abstrakcyjnej M. Fowler, 1999
48 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (48) Przykład public class KartaCzytelnicza { private TypKarty typKarty; public KartaCzytelnicza(TypKarty typKarty) { this.typKarty = typKarty; } public int kodTypuKarty() { return this.typKarty.kodTypuKarty(); } public double oplata() { return typKarty.oplata(); }
49 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (49) Przykład abstract public class TypKarty { public static final int JUNIOR = 0; public static final int STANDARD = 1; public static final int SENIOR = 2; public static TypKarty create(int kodTypuKarty) { switch (kodTypuKarty) { case JUNIOR: return new TypKartyJunior(); case STANDARD: return new TypKartyStandard(); case SENIOR: return new TypKartySenior(); } abstract public int typKarty(); public double oplata(KartaCzytelnictwa karta) { switch (kodTypuKarty) { case JUNIOR: return 0; case STANDARD: return 100 – (10 * okresCzytelnictwa); case SENIOR: return 50; }
50 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (50) Przykład abstract public class TypKarty { abstract public int typKarty(); abstract double oplata(KartaBiblioteczna karta); } public class TypKartyJunior extends TypKarty { public double oplata(KartaBiblioteczna karta) { return 0; } public class TypKartyStandard extends TypKarty { public double oplata(KartaBiblioteczna karta) { return 100 – (10 * karta.okresCzytelnictwa); } public class TypKartySenior extends TypKarty { public double oplata(KartaBiblioteczna karta) { return 50; }
51 Innowacyjne metody wytwarzania oprogramowania Wprowadzenie do refaktoryzacji (51) Podsumowanie Refaktoryzacja jest techniką umożliwiającą łatwiejszą i tańszą pielęgnację oprogramowania Refaktoryzacja zachowuje funkcjonalność programu Analiza statyczna i testowanie są metodami weryfikacji poprawności refaktoryzacji Przykry zapach w kodzie jest określeniem złej struktury programu wymagającej poprawy Identyfikacja przykrych zapachów wymaga złożonego mechanizmu wspomagania decyzji