Скачать презентацию ZTO Wprowadzenie do TDD SOLID — Jak pisać Скачать презентацию ZTO Wprowadzenie do TDD SOLID — Jak pisać

7ce2961d165ec867c2dc85f164985e84.ppt

  • Количество слайдов: 38

ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod Krzysztof Manuszewski ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod Krzysztof Manuszewski

Trudno jest testować zły kod. . . Trudno jest testować zły kod. . .

ZŁY KOD WYGLĄDA TAK. . . ZŁY KOD WYGLĄDA TAK. . .

. . . jest „sztywny” i „delikatny” q q q Nowe błedy pojawiają się . . . jest „sztywny” i „delikatny” q q q Nowe błedy pojawiają się w obszarach, które zdają sie być niezwiazane ze zmienianą funkcjonalnoscią Pozornie drobne zmiany indukują poważne zmiany w wielu miejscach kodu i/lub skutkuja trudnymi do przewidzenia błędami Trudno przewidzieć wpływ nawet drobnych zmian Trudno przewidzieć czas oraz koszty rozwoju projektu/poprawek Zespół prorgramistów traci wiarygodność Menedżerowie niechętnie godza się na zmiany

. . . i trudny do „reużycia” Potrzebne elementy zależą od niepotrzebnych n Ryzyko . . . i trudny do „reużycia” Potrzebne elementy zależą od niepotrzebnych n Ryzyko ekstrakcji potrzebnego kodu jest duże a koszt wiekszy niż napisanie opotrzebnej funkcjonalności od podstaw n

Bezpośrednie źródła problemów Praca z cudzym kodem n Pośpiech n Zmiany, ciagłe zmiany n Bezpośrednie źródła problemów Praca z cudzym kodem n Pośpiech n Zmiany, ciagłe zmiany n Niedostateczna/niejasna specyfikacja n . . . a może przyczyną jest nienajlepsza architektura kodu

Prosty przykład n n kopiowanie znaków z klawiatury na drukarkę public class Copier { Prosty przykład n n kopiowanie znaków z klawiatury na drukarkę public class Copier { public static void Copy() { int c; while((c=Keyboard. Read()) != -1) Printer. Write(c); } }

. . . drobna zmiana. . . n Z klawiatury lub z czytnika taśmy . . . drobna zmiana. . . n Z klawiatury lub z czytnika taśmy public class Copier { public static bool rd. Flag = false; public static void Copy() { int c; while((c= (rd. Flag ? Paper. Tape. Read() : Keyboard. Read()) != -1) Printer. Write(c); } }

. . i następna. . . n Na drukarke lub ekran public class Copier . . i następna. . . n Na drukarke lub ekran public class Copier { public static bool rd. Flag = false; public static bool pt. Flag = false; public static void Copy() { int c; while((c= (rd. Flag ? Paper. Tape. Read() : Keyboard. Read()) != -1) if (pt. Flag) Screen. Write(c); else Printer. Write(c); } }

. . . i już nie jest prosty. . . n n n Więcej . . . i już nie jest prosty. . . n n n Więcej źródeł i ujść danych Obsługa błędów I/O errors Przekodowywanie znaków Logowanie przekodowanych znaków do pliku Zmiana formatu tekstu w oparciu o kontekst (np justowanie tekstu

Wymagania się zmieniają. . . Zawsze lub przynajmniej czasami zwłaszcza w kontekście iteracyjnej realizacji Wymagania się zmieniają. . . Zawsze lub przynajmniej czasami zwłaszcza w kontekście iteracyjnej realizacji projektu

. . . być gotowym na zmiany public class Keyboard. Reader : { public . . . być gotowym na zmiany public class Keyboard. Reader : { public int Read() { return Keyboard. Read(); } } public class Printer. Writer : { public Write(int c) { return Printer. Write(c); } } public class Copier { public static Keyboard. Reader reader = new Keyboard. Reader(); public static Printer. Writer writer = new Printer. Writer(); public static void Copy() { int c; while((c=(reader. Read())) != -1) writer. Write(c); }

. . . to podzielić odpowiedzialność Wyraźna separcja kodu: twórcy klas Printer i Writer . . . to podzielić odpowiedzialność Wyraźna separcja kodu: twórcy klas Printer i Writer nie muszą znać ani rozumieć logiki kopiowania ale: n Zmiany w sposobie obsługi drukarki/klawiatury: ¨ wymuszają zmiany w klasie kopier (typy atrybutów) ¨ wymuszają rekompilację klasy „Copier” n n Implementujący klasę Copier musi znać i umieć tworzyć obiekty klas Printer, Keyboard. Reader Klasa Screen musi dziedziczyć po Printer (choć nie ma nic wspólnego z drukarką)

LEPSZYKOD WYGLĄDA TAK. . . LEPSZYKOD WYGLĄDA TAK. . .

public interface IReader { public int Read() ; } public class Keyboard. Reader : public interface IReader { public int Read() ; } public class Keyboard. Reader : IReader{ public int Read() { return Keyboard. Read(); } } public class Copier { public static IReader reader = new Keyboard. Reader(); public static IWriter writer = new Printer. Writer(); public static void Copy() { int c; while((c=(reader. Read())) != -1) writer. Write(c); }

public class Copier { IReader reader; IWriter writer; public Copier (IReader new. Reader, IWriter public class Copier { IReader reader; IWriter writer; public Copier (IReader new. Reader, IWriter writer) { reader = new. Reader; writer = new. Writer; } public static void Copy() {. . . } }

Dobra separacja kodu n n n Klasa Copier nie zależy od Printer ani od Dobra separacja kodu n n n Klasa Copier nie zależy od Printer ani od Reader Klasy Printer ani Reader nie zależą od Copier Wszystkie klasy (usługobiorcy i usługodawcy zależą od interfejsu) Zmiany interfejs-u sa jedynym powodem do zmian w większych obszarach kodu Interfejs stanowi specyfikację kontraktu pomiędzy 2 stronami – klientem i dostarczycielem pewnej funkcjonalności

S. O. L. I. D. - ny kod • • • SRP: OCP: LSP: S. O. L. I. D. - ny kod • • • SRP: OCP: LSP: ISP: DIP: The Single Responsibility Principle The Open/Close Principle The Liskov Substitution Principle The Interface Segregation Principle The Dependency Inversion Principle

(SRP) Single-Responsibility Principle Klasa powinna mieć pojedynczy powód do zmian. Klasa Printer jest odpowiedzialna (SRP) Single-Responsibility Principle Klasa powinna mieć pojedynczy powód do zmian. Klasa Printer jest odpowiedzialna za pisanie na drukarkę n Klasa Copier jest odpowiedzialna za proces kopiowania n

SRP - przykład problemów public interface Modem { public void Dial(string pno); public void SRP - przykład problemów public interface Modem { public void Dial(string pno); public void Hangup(); public void Send(char c); public char Recv(); } n n 2 odpowiedzialności razem – zarządzanie połaczeniem i transmisja danych Jeżeli klienci korzystają z nich oddzielnie (prawdopodobne) zmiany w sygnaturze Dial wywołają konieczność rekompilacji klientów, którzy korzystają jedynie z Send/Recv

SRP - przykład problemów n Nie zmusza do realizacji obu funkcjonalności przez jedną klasę SRP - przykład problemów n Nie zmusza do realizacji obu funkcjonalności przez jedną klasę – chociaz dalej jest to mozliwe

SRP - przykład problemów n n n Zmiany dowolnego z 3 aspektów oznaczają zmiany SRP - przykład problemów n n n Zmiany dowolnego z 3 aspektów oznaczają zmiany w klasie Employee Łatwo wprowadzić błąd do pobocznej funkcjonalności. Testować trzeba cała klasę. Pożądane jest ograniczenie funkcjonalności w obrębie zmienianej klasy

SRP - Możliwe rozwiązanie SRP - Możliwe rozwiązanie

Moment! n n Obiekty powinny hermetyzować swoją zawartość Czy obiekty powinny mieć wiedzę: Jak Moment! n n Obiekty powinny hermetyzować swoją zawartość Czy obiekty powinny mieć wiedzę: Jak zapisać samego siebie? ¨ Jak raportować swój stan? ¨ n To nie jest tak istotne! ¨ Filozofia, która kryje się za OO nie jest w tym wypadku tak istotna ¨ Podstawowym celem jest ograniczenie propagacji zmian w systemie ¨ System ma być łatwy w utrzymaniu i modularny!

(OCP) Open/Close Principle Jednostki programowe (klasy, moduły, funkcje, itd. ) powinny być otwarte na (OCP) Open/Close Principle Jednostki programowe (klasy, moduły, funkcje, itd. ) powinny być otwarte na rozszerzenia a zamknięte na zmiany Robert C. Martin n n Do kopiarki mogą być łatwo dodawane nowe typy czytników/pisarzy bez zmian (no prawie bez) w klasie Copier Ale „bez zmian” oznacza też, że klasa Copier nie powinna sama tworzyć obiektów, z których korzysta -> rozwiązywanie zależności

OCP - Otwartość na rozszerzanie n Zapewniają np. wzorce projektowe: strategia n metoda szablonowa OCP - Otwartość na rozszerzanie n Zapewniają np. wzorce projektowe: strategia n metoda szablonowa Które rozwiązanie wybrać ?

Które rozwiazanie wybrać? n Dziedziczenie wprowadza silniejsze zwiazki miedzy klasami: ¨ Agregacja daje możliwość Które rozwiazanie wybrać? n Dziedziczenie wprowadza silniejsze zwiazki miedzy klasami: ¨ Agregacja daje możliwość zmiany zachowania w czasie wykonania ¨ Agregacje dają możliwość niezależnego określania zachowania w różnych obszaarch (niezaleznych strategii) Dziedziczenie interfejsu jest naturalne n Dziedziczenie implementacji niekoniecznie n Jeśli nie ma dodatkowych wskazówek agregacja może być lepszym rozwiazaniem! n

OCP – co ze zmianami? Nie można zapobiec wszystkim zmianom n Kluczowe jest rozpoznanie OCP – co ze zmianami? Nie można zapobiec wszystkim zmianom n Kluczowe jest rozpoznanie co może się zmieniać często lub co bedzie trudno zmienić n Dodanie nowej funkcjonalności powinno być łatwe n

(LSP) Liskov Substitution Principle Podklasy muszą być logicznie zgodne z typami bazowymi. n n (LSP) Liskov Substitution Principle Podklasy muszą być logicznie zgodne z typami bazowymi. n n Tape. Reader : Keyboard. Reader ? ? ? Dziedziczenie oznacza „jest szczególnym przypadkiem”

LSP – Szkolny przykład (1) public class Rectangle { private Point top. Left; private LSP – Szkolny przykład (1) public class Rectangle { private Point top. Left; private double width; private double height; public double Width { get { return width; } set { width = value; } } public double Height { get { return height; } set { height = value; } } }

Unit Testing LSP – Szkolny przykład (2) public class Rectangle { private Point top. Unit Testing LSP – Szkolny przykład (2) public class Rectangle { private Point top. Left; private double width; private double height; public virtual double Width { get { return width; } set { width = value; } } public class Square : Rectangle { public override double Width { set { base. Width = value; base. Height = value; } } public override double Height { set { base. Height = value; base. Width = value; } } public virtual double Height { get { return height; } set { height = value; } }

Unit Testing LSP – Szkolny przykład (3) void foo (Rectangle r) { r. Set. Unit Testing LSP – Szkolny przykład (3) void foo (Rectangle r) { r. Set. Width(32); // calls Rectangle. Set. Width } Co bedzie gdy przekażemy obiekt typu Square? A poniżej ? void goo (Rectangle r) { r. Width = 5; r. Height = 4; if(r. Area() != 20) throw new Exception("Bad area!"); } Square nie zachowuje się jak szczególny przypadek prostokąta!

(ISP) Interface Seggregation Principle Klasa nie powinna zależeć od tego, czego nie używa. n (ISP) Interface Seggregation Principle Klasa nie powinna zależeć od tego, czego nie używa. n Interfejs IReader powinien być oddzielny od IWriter. n „Tłuste” klasy wprowadzają zwykle silne związki ze swoimi klientami Zmiana wymuszona przez jednego z klientów dotyka pozostałych n

ISP - uwagi n Należy unikać obszernych interfejsów ¨ ¨ ¨ n n n ISP - uwagi n Należy unikać obszernych interfejsów ¨ ¨ ¨ n n n Interfejs (kontrakt) powinien być spójny Interfejs (kontrakt) nie powinien być zbyt szeroki wszystkie implementujące go klasy będą musiały dostarczyć kompletu metod (np. WCF: 3 -5 -9) Lepsze są wąskie, zorientowane na role interfejsy Jeżeli nie można uniknąć „tłustych” klas - te powinny być prezentowane klientom za pośrednictwem zbioru wąskich, zorientowanych na role interfejsów Oddzielni klienci mogą oznaczać oddzielne interfejsy Interfejsy moga dziedziczyc po sobie – można dostarczyć pojedynczą referencją do obiektu implementującego wiele interfejsów.

(DIP) Dependency-Inversion Principle 1. 2. n n n Wysokopoziomowe moduły nie powinny zależeć od (DIP) Dependency-Inversion Principle 1. 2. n n n Wysokopoziomowe moduły nie powinny zależeć od nispopoziomowych (ani odwrotnie). Jedne i drugie powinny zależeć od abstrakcji (kontraktów). Abstrakcje (kontrakty) nie powinny zależeć od szczegółów implementacyjnych. Implementacja powinna zależeć od abstrakcji (kontraktu). Hollywood principle: „Don‘t call us – we will call you” Copier i Keyboard. Reader zależą od interfejsu IReader, ale nie zależą od siebie Interfejs jest bardziej przejrzysty niż klasa

Unit Testing DIP - Przykład Unit Testing DIP - Przykład

Unit Testing DIP - podsumowanie n n Podstawa dobrego OOD Interfejsy należą do klientów Unit Testing DIP - podsumowanie n n Podstawa dobrego OOD Interfejsy należą do klientów Implementacja zmienia się często, interfejsy rzadko „Podejrzane” sytuacje: ¨ Zmienna odwołuje się do nieabstrakcyjnej klasy ¨ Klasa dziedziczy po nieabstrakcyjnej klasie ¨ Metoda nadpisuje konkretną implementację z klasy bazowej

Zalecana lektura Robert C. C. Martin „Agile Principles, Patterns and Practices in C#” [przykłady Zalecana lektura Robert C. C. Martin „Agile Principles, Patterns and Practices in C#” [przykłady są na tej podstawie] Robert C. C. Martin „Czysty kod" Warto zobaczyc screencast Martina z NDC 2009 „Robert C. Martin - Clean Design, SOLID Principles I and II. wmv”