Pobieranie prezentacji. Proszę czekać

Pobieranie prezentacji. Proszę czekać

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

Podobne prezentacje


Prezentacja na temat: "ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod"— Zapis prezentacji:

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

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

3 zły kod wygląda TAK ...

4 ... jest „sztywny” i „delikatny”
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

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

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

7 Prosty przykład kopiowanie znaków z klawiatury na drukarkę
public class Copier { public static void Copy() { int c; while((c=Keyboard.Read()) != -1) Printer.Write(c); } Simple design is good because is simple and cheap, but it does not necessarily mean is also cheap to maintain Good design should take into account possible change (be careful to not overdesign the software), but it can also be simple. The clue is to catch the moment when you start see the smells, then the change of architecture is the absolute must.

8 ... drobna zmiana ... Z klawiatury lub z czytnika taśmy
public class Copier { public static bool rdFlag = false; public static void Copy() { int c; while((c= (rdFlag ? PaperTape.Read() : Keyboard.Read()) != -1) Printer.Write(c); } Changes in program coppier done without change in architecture.

9 ..i następna ... Na drukarke lub ekran public class Copier {
public static bool rdFlag = false; public static bool ptFlag = false; public static void Copy() { int c; while((c= (rdFlag ? PaperTape.Read() : Keyboard.Read()) != -1) if (ptFlag) Screen.Write(c); else Printer.Write(c); } More changes equal more problems

10 ...i już nie jest prosty... 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 After some number of change lines of original code is less then patches.

11 Wymagania się zmieniają ...
Zawsze lub przynajmniej czasami zwłaszcza w kontekście iteracyjnej realizacji projektu Changes are always so it is worth to take them into account in design phase or at least in the moment when the change occurs.

12 ... być gotowym na zmiany public class KeyboardReader : {
public int Read() { return Keyboard.Read(); } } public class PrinterWriter : { public Write(int c) { return Printer.Write(c); } public class Copier { public static KeyboardReader reader = new KeyboardReader(); public static PrinterWriter writer = new PrinterWriter(); public static void Copy() { int c; while((c=(reader.Read())) != -1) writer.Write(c); This is a simple example of the design, which is change friendly

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

14 Lepszykod wygląda TAK ...

15 public interface IReader { public int Read() ; }
public class KeyboardReader : IReader{ public int Read() { return Keyboard.Read(); } public class Copier { public static IReader reader = new KeyboardReader(); public static IWriter writer = new PrinterWriter(); public static void Copy() { int c; while((c=(reader.Read())) != -1) writer.Write(c); Example of the code with interfaces.

16 public class Copier { IReader reader; IWriter writer; public Copier (IReader newReader, IWriter writer) { reader = newReader; writer = newWriter; } public static void Copy() { ... Example of the code with interfaces.

17 Dobra separacja kodu 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 The problem here may be solved by the use of inteface Additionally interface is more legible then class even if we are looking in the public methods

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

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

20 SRP - przykład problemów
public interface Modem { public void Dial(string pno); public void Hangup(); public void Send(char c); public char Recv(); } 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 Single responsibility rule

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

22 SRP - przykład problemów
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

23 SRP - Możliwe rozwiązanie
Single responsibility rule

24 Moment! Obiekty powinny hermetyzować swoją zawartość
Czy obiekty powinny mieć wiedzę: Jak zapisać samego siebie? Jak raportować swój stan? 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! Single responsibility rule

25 (OCP) Open/Close Principle
Jednostki programowe (klasy, moduły, funkcje, itd.) powinny być otwarte na rozszerzenia a zamknięte na zmiany Robert C. Martin 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

26 OCP - Otwartość na rozszerzanie
Zapewniają np. wzorce projektowe: It is a particular software design pattern, whereby algorithms can be selected on-the-fly at runtime. So behaviors of a class should not be inherited, instead they should be encapsulated using interfaces. This allows better decoupling between the behavior and the class that uses the behavior. The behavior can be changed without breaking the classes that use it, and the classes can switch between behaviors by changing the specific implementation used without requiring any significant code changes. Behaviors can also be changed at run-time as well as at design-time. A template method defines the program skeleton of an algorithm. The algorithm itself is made abstract, and the subclasses override the abstract methods to provide concrete behavior. First a class is created that provides the basic steps of an algorithm design. These steps are implemented using abstract methods. Later on, subclasses change the abstract methods to implement real actions. Thus the general algorithm is saved in one place but the concrete steps may be changed by the subclasses. strategia metoda szablonowa Które rozwiązanie wybrać ?

27 Które rozwiazanie wybrać?
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 Dziedziczenie implementacji niekoniecznie Jeśli nie ma dodatkowych wskazówek agregacja może być lepszym rozwiazaniem! It is a particular software design pattern, whereby algorithms can be selected on-the-fly at runtime. So behaviors of a class should not be inherited, instead they should be encapsulated using interfaces. This allows better decoupling between the behavior and the class that uses the behavior. The behavior can be changed without breaking the classes that use it, and the classes can switch between behaviors by changing the specific implementation used without requiring any significant code changes. Behaviors can also be changed at run-time as well as at design-time. A template method defines the program skeleton of an algorithm. The algorithm itself is made abstract, and the subclasses override the abstract methods to provide concrete behavior. First a class is created that provides the basic steps of an algorithm design. These steps are implemented using abstract methods. Later on, subclasses change the abstract methods to implement real actions. Thus the general algorithm is saved in one place but the concrete steps may be changed by the subclasses.

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

29 (LSP) Liskov Substitution Principle
Podklasy muszą być logicznie zgodne z typami bazowymi. TapeReader : KeyboardReader ??? Dziedziczenie oznacza „jest szczególnym przypadkiem” We need to be careful how we are designing the class hierarchy

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

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

32 LSP – Szkolny przykład (3)
Unit Testing LSP – Szkolny przykład (3) void foo (Rectangle r) { r.SetWidth(32); // calls Rectangle.SetWidth } 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! 32

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

34 ISP - uwagi Należy unikać obszernych interfejsów
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. Interface pollution Too wide interfaces are easy to do but harder to use

35 (DIP) Dependency-Inversion Principle
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 KeyboardReader zależą od interfejsu IReader, ale nie zależą od siebie Interfejs jest bardziej przejrzysty niż klasa Also known as Inversion of control. Informally, it is expressed by the Hollywood Principle - "Don't call us, we'll call you". When excercised it needs to be applied wise, as always. Nothing is for free. The price paid for that additional flexibility is increased complexity, but in many cases this makes sense (the same case for coppier example).

36 Unit Testing DIP - Przykład 36

37 DIP - podsumowanie Podstawa dobrego OOD Interfejsy należą do klientów
Unit Testing DIP - podsumowanie 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 37

38 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” Interface pollution Too wide interfaces are easy to do but harder to use


Pobierz ppt "ZTO Wprowadzenie do TDD, SOLID - Jak pisać dobry kod"

Podobne prezentacje


Reklamy Google