Tytuł oryginału: Working Effectively with Legacy Code · Nie bierze jednak żadnej...

60

Transcript of Tytuł oryginału: Working Effectively with Legacy Code · Nie bierze jednak żadnej...

Tytuł oryginału: Working Effectively with Legacy Code

Tłumaczenie: Ireneusz Jakóbik

Projekt okładki: Studio Gravite/OlsztynObarek, Pokoński, Pazdrijowski, Zaprucki

ISBN: 978-83-246-8317-8

Authorized translation from the English language edition, entitled: WORKING EFFECTIVELY WITH LEGACY CODE; ISBN 0131177052; by Michael C. Feathers; published by Pearson Education, Inc, publishing as Prentice Hall.Copyright © 2005 by Pearson Education, Inc.

All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. Polish language edition published by HELION S.A., Copyright © 2014.

Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji.

Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli.

Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie bierze jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Wydawnictwo HELION nie ponosi również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce.

Materiały graficzne na okładce zostały wykorzystane za zgodą Shutterstock Images LLC.

Wydawnictwo HELIONul. Kościuszki 1c, 44-100 GLIWICEtel. 32 231 22 19, 32 230 98 63e-mail: [email protected]: http://helion.pl (księgarnia internetowa, katalog książek)

Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/prazak.zip

Drogi Czytelniku!Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/prazakMożesz tam wpisać swoje uwagi, spostrzeżenia, recenzję.

Printed in Poland.

• Kup książkę• Poleć książkę • Oceń książkę

• Księgarnia internetowa• Lubię to! » Nasza społeczność

Spis treści

Słowo wstępne ........................................................................................................................... 9

Przedmowa ............................................................................................................................. 11

Wstęp ...................................................................................................................................... 17

Część I: Mechanika zmian ........................................................................................ 19

Rozdział 1. Zmiany w oprogramowaniu ................................................................................ 21Cztery powody wprowadzania zmian w oprogramowaniu .............................................21Ryzykowna zmiana ................................................................................................................25

Rozdział 2. Praca z informacją zwrotną ................................................................................ 27Co to jest testowanie jednostkowe? .....................................................................................30Testy wyższego poziomu .......................................................................................................32Pokrycie testami .....................................................................................................................33Algorytm dokonywania zmian w cudzym kodzie .............................................................36

Rozdział 3. Rozpoznanie i separowanie ................................................................................. 39Fałszywi współpracownicy ....................................................................................................41

Rozdział 4. Model spoinowy ................................................................................................... 47Ogromny arkusz z tekstem ...................................................................................................47Spoiny ......................................................................................................................................48Rodzaje spoin ..........................................................................................................................51

Rozdział 5. Narzędzia ............................................................................................................. 63Narzędzia do automatycznej refaktoryzacji .......................................................................63Obiekty pozorowane ..............................................................................................................65Jarzmo testowania jednostkowego ......................................................................................66Ogólne jarzmo testowe ..........................................................................................................71

Część II: Zmiany w oprogramowaniu ...................................................................... 73

Rozdział 6. Nie mam zbyt wiele czasu, a muszę to zmienić .................................................. 75Kiełkowanie metody ..............................................................................................................77Kiełkowanie klasy ...................................................................................................................80Opakowywanie metody .........................................................................................................85Opakowywanie klasy .............................................................................................................88Podsumowanie .......................................................................................................................93

Poleć książkęKup książkę

6 SPIS TREŚCI

Rozdział 7. Dokonanie zmiany trwa całą wieczność ............................................................. 95Zrozumienie ............................................................................................................................95Opóźnienie ..............................................................................................................................96Usuwanie zależności ..............................................................................................................97Podsumowanie .................................................................................................................... 102

Rozdział 8. Jak mogę dodać nową funkcjonalność? ............................................................ 103Programowanie sterowane testami ................................................................................... 104Programowanie różnicowe ................................................................................................ 110Podsumowanie .................................................................................................................... 119

Rozdział 9. Nie mogę umieścić tej klasy w jarzmie testowym ............................................. 121Przypadek irytującego parametru ..................................................................................... 121Przypadek ukrytej zależności ............................................................................................. 128Przypadek konstrukcyjnego kłębowiska .......................................................................... 131Przypadek irytującej zależności globalnej ........................................................................ 133Przypadek straszliwych zależności dyrektyw include .................................................... 141Przypadek cebulowego parametru .................................................................................... 144Przypadek zaliasowanego parametru ............................................................................... 147

Rozdział 10. Nie mogę uruchomić tej metody w jarzmie testowym ................................... 151Przypadek ukrytej metody ................................................................................................. 152Przypadek „pomocnych” funkcji języka .......................................................................... 155Przypadek niewykrywalnych skutków ubocznych ......................................................... 158

Rozdział 11. Muszę dokonać zmian. Które metody powinienem przetestować? ............... 165Myślenie o skutkach ............................................................................................................ 166Śledzenie w przód ................................................................................................................ 171Propagacja skutków ............................................................................................................ 176Narzędzia do wyszukiwania skutków ............................................................................... 177Wyciąganie wniosków z analizy skutków ........................................................................ 179Upraszczanie schematów skutków ................................................................................... 180

Rozdział 12. Muszę dokonać wielu zmian w jednym miejscu. Czy powinienempousuwać zależności we wszystkich klasach, których te zmiany dotyczą? ................. 183

Punkty przechwycenia ........................................................................................................ 184Ocena projektu z punktami zwężenia .............................................................................. 191Pułapki w punktach zwężenia ........................................................................................... 192

Rozdział 13. Muszę dokonać zmian, ale nie wiem, jakie testy napisać ............................... 195Testy charakteryzujące ....................................................................................................... 196Charakteryzowanie klas ..................................................................................................... 199Testowanie ukierunkowane ............................................................................................... 200Heurystyka pisania testów charakteryzujących .............................................................. 205

Rozdział 14. Dobijają mnie zależności biblioteczne ........................................................... 207

Rozdział 15. Cała moja aplikacja to wywołania API ........................................................... 209

Poleć książkęKup książkę

SPIS TREŚCI 7

Rozdział 16. Nie rozumiem wystarczająco dobrze kodu, żeby go zmienić ........................ 219Notatki i rysunki .................................................................................................................. 220Adnotowanie listingów ...................................................................................................... 221Szybka refaktoryzacja ......................................................................................................... 222Usuwanie nieużywanego kodu .......................................................................................... 223

Rozdział 17. Moja aplikacja nie ma struktury ..................................................................... 225Opowiadanie historii systemu ........................................................................................... 226Puste karty CRC .................................................................................................................. 230Analiza rozmowy ................................................................................................................ 232

Rozdział 18. Przeszkadza mi mój testowy kod .................................................................... 235Konwencje nazewnicze klas ............................................................................................... 235Lokalizacja testu .................................................................................................................. 236

Rozdział 19. Mój projekt nie jest zorientowany obiektowo. Jak mogę bezpieczniewprowadzać zmiany? ...................................................................................................... 239

Prosty przypadek ................................................................................................................. 240Przypadek trudny ................................................................................................................ 241Dodawanie nowego zachowania ....................................................................................... 244Korzystanie z przewagi zorientowania obiektowego ..................................................... 247Wszystko jest zorientowane obiektowo ........................................................................... 250

Rozdział 20. Ta klasa jest za duża, a ja nie chcę, żeby stała się jeszcze większa .................. 253Dostrzeganie odpowiedzialności ...................................................................................... 257Inne techniki ........................................................................................................................ 269Posuwanie się naprzód ....................................................................................................... 270Po wyodrębnieniu klasy ..................................................................................................... 273

Rozdział 21. Wszędzie zmieniam ten sam kod .................................................................... 275Pierwsze kroki ...................................................................................................................... 278

Rozdział 22. Muszę zmienić monstrualną metodę, lecz nie mogę napisać do niej testów .... 293Rodzaje monstrów .............................................................................................................. 294Stawianie czoła monstrom przy wsparciu automatycznej refaktoryzacji ................... 297Wyzwanie ręcznej refaktoryzacji ...................................................................................... 300Strategia ................................................................................................................................ 307

Rozdział 23. Skąd mam wiedzieć, czy czegoś nie psuję? ...................................................... 311Superświadome edytowanie .............................................................................................. 312Edytowanie jednego elementu naraz ................................................................................ 313Zachowywanie sygnatur ..................................................................................................... 314Wsparcie kompilatora ........................................................................................................ 317Programowanie w parach .................................................................................................. 318

Rozdział 24. Czujemy się przytłoczeni. Czy nie będzie chociaż trochę lepiej? ................... 321

Poleć książkęKup książkę

8 SPIS TREŚCI

Część III: Techniki usuwania zależności ............................................................... 325

Rozdział 25. Techniki usuwania zależności ......................................................................... 327Adaptacja parametru .......................................................................................................... 328Wyłonienie obiektu metody .............................................................................................. 332Uzupełnianie definicji ........................................................................................................ 338Hermetyzacja referencji globalnej .................................................................................... 340Upublicznienie metody statycznej .................................................................................... 346Wyodrębnienie i przesłonięcie wywołania ...................................................................... 349Wyodrębnienie i przesłonięcie metody wytwórczej ...................................................... 351Wyodrębnienie i przesłonięcie gettera ............................................................................. 353Wyodrębnienie implementera .......................................................................................... 356Wyodrębnienie interfejsu .................................................................................................... 361Wprowadzenie delegatora instancji ................................................................................. 367Wprowadzenie statycznego settera ................................................................................... 370Zastępowanie biblioteki ..................................................................................................... 375Parametryzacja konstruktora ............................................................................................ 377Parametryzacja metody ...................................................................................................... 381Uproszczenie parametru .................................................................................................... 383Przesunięcie funkcjonalności w górę hierarchii ............................................................. 386Przesunięcie zależności w dół hierarchii .......................................................................... 390Zastąpienie funkcji wskaźnikiem do funkcji ................................................................... 393Zastąpienie referencji globalnej getterem ........................................................................ 396Utworzenie podklasy i przesłonięcie metody .................................................................. 398Zastąpienie zmiennej instancji .......................................................................................... 401Przedefiniowanie szablonu ................................................................................................ 405Przedefiniowanie tekstu ..................................................................................................... 409

Dodatek: Refaktoryzacja ...................................................................................................... 411Wyodrębnianie metody ...................................................................................................... 411

Słownik .................................................................................................................................. 415

Skorowidz ............................................................................................................................. 417

Poleć książkęKup książkę

Rozdział 9.

Nie mogę umieścić tej klasyw jarzmie testowym

Będzie ciężko. Gdyby utworzenie instancji klasy w jarzmie testowym zawsze było łatwe,książka ta byłaby o wiele krótsza. Niestety, często zadanie to jest trudne.

Oto cztery najczęściej występujące problemy, które napotykamy: 1. Nie da się w prosty sposób utworzyć obiektów klasy. 2. Nie da się w prosty sposób przeprowadzić procesu budowy jarzma testowego

z umieszczoną w nim klasą. 3. Korzystanie z konstruktora, którego potrzebujemy użyć, wywołuje skutki uboczne. 4. Konstruktor wykonuje sporo pracy, a my musimy ją rozpoznać.

W rozdziale tym zajmiemy się serią przykładów, które skupiają się na tych problemach,z uwzględnieniem różnych języków. Istnieje więcej niż tylko jeden sposób poradzeniasobie z każdym z tych problemów. Zaznajomienie się z tymi przykładami jest jednakniezłą metodą na poznanie całego arsenału technik usuwania zależności i nauczeniasię, które z nich wybrać i jak je stosować w określonych sytuacjach.

Przypadek irytującego parametru

Kiedy muszę wprowadzić zmianę w cudzym systemie, zwykle początkowo jestem nasta-wiony bardzo optymistycznie. Nie mam pojęcia dlaczego. Na ile tylko mogę, próbuję byćrealistą, ale optymizm zawsze się przebija. „Hej”, mówię do siebie (albo do kolegi), „wy-gląda na to, że będzie łatwo. Musimy tylko co tam trochę stentegowa , i po robocie”.Wszystko to brzmi prosto, kiedy się o tym mówi, aż bierzemy się do klasy Co Tam (czym-kolwiek by ona była) i się jej przyglądamy. „No dobra. Musimy więc dodać metodę tutaj,zmienić inną metodę tam i oczywiście wrzucić całość do jarzma testowego”. W tym

Poleć książkęKup książkę

122 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

momencie zaczynam nieco wątpić. „Motyla noga! Wygląda na to, że najprostszy kon-struktor w tej klasie przyjmuje trzy parametry, ale — dodaję optymistycznie — być możeutworzenie obiektu wcale nie będzie takie trudne”.

Zajmijmy się przykładem i zobaczmy, czy mój optymizm ma podstawy, czy jest tylkomechanizmem obronnym.

W kodzie systemu realizującego płatności znajduje się nieprzetestowana klasa Javyo nazwie CreditValidator.

public class CreditValidator{ public CreditValidator(RGHConnection connection, CreditMaster master, String validatorID) { ... } Certificate validateCustomer(Customer customer) throws InvalidCredit { ... } ...}

Jedną z wielu funkcji tej klasy jest informowanie nas, czy klienci mają otwarty kredyt.Jeśli tak, otrzymujemy certyfikat informujący o wysokości kredytu. Jeśli nie, klasa zgłaszawyjątek.

Nasza misja, o ile tylko ją przyjmiemy, polega na dodaniu do tej klasy nowej metody.Metoda będzie nosić nazwę getValidationPercent, a jej zadaniem będzie informowanienas o odsetku udanych wywołań metody validateCustomer w czasie działania walidatora.

Od czego zaczniemy?Kiedy musimy utworzyć obiekt w jarzmie testowym, często najlepszym podejściem jest

po prostu próba jego utworzenia. Moglibyśmy przeprowadzić rozległą analizę, aby dowie-dzieć się, dlaczego będzie (albo też nie będzie) to łatwe bądź trudne, ale równie prostebędzie utworzenie klasy testowej jUnit, wprowadzenie do niej kodu i skompilowanie go.

public void testCreate() { CreditValidator validator = new CreditValidator();}

Najlepszym sposobem na stwierdzenie, czy będziesz mieć kłopoty podczas tworzenia in-stancji klasy w jarzmie testowym, jest po prostu próba jej utworzenia. Napisz przypadektestowy i spróbuj w jego ramach utworzyć obiekt. Kompilator powie Ci, czego potrzebu-jesz, aby odnieść sukces.

To jest test konstrukcyjny. Testy konstrukcyjne wyglądają trochę dziwnie. Kiedy piszętaki test, zwykle nie umieszczam w nim asercji. Po prostu próbuję skonstruować obiekt.Później, gdy mam już możliwość tworzenia obiektów w jarzmie testowym, zwykle pozby-wam się takiego testu albo zmieniam jego nazwę, żebym mógł go wykorzystać do spraw-dzenia czegoś ważniejszego.

Poleć książkęKup książkę

PRZYPADEK IRYTUJĄCEGO PARAMETRU 123

Wróćmy jednak do naszego przykładu.Do konstruktora nie dodaliśmy jeszcze żadnych argumentów, więc kompilator narzeka.

Mówi nam, że dla klasy CreditValidator nie ma domyślnego konstruktora. Szperającw kodzie, odkrywamy, że potrzebujemy klasy RGHConnection, klasy CreditMaster orazhasła. Klasy te mają tylko po jednym konstruktorze i wyglądają następująco:

public class RGHConnection{ public RGHConnection(int port, String Name, string passwd) throws IOException { ... }}

public class CreditMaster{ public CreditMaster(String filename, boolean isLocal) { ... }}

Kiedy konstruowany jest obiekt klasy RGHConnection, łączy się on z serwerem.Podczas połączenia pobierane są z serwera wszystkie dane potrzebne do zweryfikowaniakredytu klienta.

Druga klasa, CreditMaster, przekazuje nam informacje polityczne, z których korzy-stamy, podejmując decyzje kredytowe. Podczas konstruowania obiekt klasy CreditMasterwczytuje informacje z pliku i zapisuje je w pamięci, abyśmy mogli z nich skorzystać.

Wygląda więc na to, że umieszczenie tej klasy w jarzmie testowym będzie dość łatwe,prawda? Nie tak szybko. Możemy napisać test, ale czy damy radę z nim pracować?

public void testCreate() throws Exception { RGHConnection connection = new RGHConnection(DEFAULT_PORT, "admin", "rii8ii9s"); CreditMaster master = new CreditMaster("crm2.mas", true); CreditValidator validator = new CreditValidator( connection, master, "a");}

Okazuje się, że nawiązywanie przez metodę RGHConnection połączenia z serweremw czasie testu nie jest dobrym pomysłem. Zabiera to sporo czasu, a serwer nie zawszeodpowiada. Z kolei klasa CreditMaster nie stwarza problemów. Kiedy tworzymy jej in-stancję, plik jest wczytywany szybko, poza tym jest on tylko do odczytu, w związku z czymnie musimy się obawiać, że testy go uszkodzą.

Kiedy chcemy utworzyć walidator, prawdziwą przeszkodę stanowi klasa RGHConnection— jest ona irytującym parametrem. Gdybyśmy mogli utworzyć jakiś rodzaj fałszywegoobiektu tej klasy i sprawić, że CreditValidator uwierzy, iż komunikuje się z autentycznymobiektem, moglibyśmy uniknąć całej masy problemów związanych z połączeniem. Spójrzmyna metody, które udostępnia klasa RGHConnection (rysunek 9.1).

Poleć książkęKup książkę

124 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

Rysunek 9.1. Klasa RGHConnection

Wygląda na to, że klasa RGHConnection zawiera zbiór metod, które obsługują mecha-nizm nawiązywania połączenia: connect, disconnect i retry, a także kilka metod biz-nesowych, takich jak RFDIReportFor i ACTIOReportFor. Pisząc naszą nową metodę dla klasyCreditValidator, będziemy musieli wywołać metodę RFDIReportFor, aby uzyskać wszyst-kie potrzebne nam informacje. Zazwyczaj dane te pochodzą z serwera, ale ponieważchcemy uniknąć nawiązywania rzeczywistego połączenia, będziemy musieli znaleźć jakiśsposób na ich udostępnienie przez nas.

W tym przypadku najlepszą metodą na utworzenie fałszywego obiektu będzie wyod-rębnienie interfejsu (361) w odniesieniu do klasy RGHConnection. Jeśli dysponujesz narzę-dziem, które wspiera refaktoryzację, prawdopodobnie udostępnia ono wyodrębnianieinterfejsu. Jeżeli Twoje środowisko programistyczne nie wspiera tej techniki, pamiętaj,że wyodrębnianie interfejsu jest na tyle proste, że można je wykonać ręcznie.

Po wyodrębnieniu interfejsu (361) otrzymujemy taką strukturę, jak pokazano narysunku 9.2.

Rysunek 9.2. Klasa RGHConnection po wyodrębnieniu interfejsu

Możemy przystąpić do pisania testów, tworząc niedużą fałszywą klasę, która udo-stępnia potrzebne nam raporty:

public class FakeConnection implements IRGHConnection{

Poleć książkęKup książkę

PRZYPADEK IRYTUJĄCEGO PARAMETRU 125

public RFDIReport report;

public void connect() {} public void disconnect() {} public RFDIReport RFDIReportFor(int id) { return report; } public ACTIOReport ACTIOReportFor(int customerID) { return null; }}

Mając tę klasę, możemy rozpocząć tworzenie takich testów:void testNoSuccess() throws Exception { CreditMaster master = new CreditMaster("crm2.mas", true); IRGHConnection connection = new FakeConnection(); CreditValidator validator = new CreditValidator( connection, master, "a"); connection.report = new RFDIReport(...);

Certificate result = validator.validateCustomer(new Customer(...));

assertEquals(Certificate.VALID, result.getStatus());}

Klasa FakeConnection jest trochę dziwna. Jak często w ogóle piszemy metody, którenie mają żadnego ciała i tylko zwracają pustą wartość? Co gorsza, ma ona publicznązmienną, której każdy może nadać taką wartość, jaką tylko zechce. Wydaje się, że klasa tanarusza wszelkie obowiązujące reguły. Otóż w rzeczywistości ich wcale nie narusza. Re-guły dla klas umożliwiających przeprowadzanie testów są inne. Kod klasy FakeConnectionnie jest kodem produkcyjnym. Nigdy nie zostanie uruchomiony w naszej pełnej aplikacji— będzie działać wyłącznie w jarzmie testowym.

Teraz, kiedy możemy już utworzyć walidator, mamy możliwość napisania metodygetValidationPercent. Oto test, który ją weryfikuje:void testAllPassed100Percent() throws Exception { CreditMaster master = new CreditMaster("crm2.mas", true); IRGHConnection connection = new FakeConnection("admin", "rii8ii9s"); CreditValidator validator = new CreditValidator( connection, master, "a"); connection.report = new RFDIReport(...); Certificate result = validator.validateCustomer(new Customer(...)); assertEquals(100.0, validator.getValidationPercent(), THRESHOLD);}

Kod testowy a kod produkcyjnyKod testowy nie musi spełniać tych samych standardów co kod produkcyjny. Zazwyczajnie mam nic przeciwko naruszaniu zasady hermetyzacji poprzez tworzenie zmiennych pu-blicznych, jeśli uprości to pisanie testów. Kod testowy powinien być jednak przejrzysty;powinien być łatwy do zrozumienia i prosty w modyfikowaniu.

Spójrz na testy testNoSuccess i testAllPassed100Percent w tym przykładzie. Czy zawierająone powielony kod? Tak. Powtórzone są trzy pierwsze wiersze. Powinny one zostać wyod-rębnione i umieszczone w jednym miejscu — metodzie setUp() tej klasy testowej.

Poleć książkęKup książkę

126 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

Test ten sprawdza, czy procent uwierzytelnienia wynosi mniej więcej 100.0, gdyotrzymujemy pojedynczy, ważny certyfikat kredytu.

Test działa poprawnie, ale pisząc kod metody getValidationPercent, zauważamy cośinteresującego. Okazuje się, że nie będzie ona w ogóle używać metody CreditMaster.Dlaczego więc ją piszemy i przekazujemy do obiektu klasy CreditValidator? Możewcale nie musimy tego robić? Instancję klasy CreditValidator moglibyśmy utworzyćw naszym teście następująco:

CreditValidator validator = new CreditValidator(connection, null, "a");

Czy jeszcze się nie pogubiłeś?Sposób, w jaki ludzie reagują na tego typu kod, sporo mówi o systemach, z którymi oni

pracują. Jeżeli widząc ten kod, powiedziałeś: „O, świetnie. Do konstruktora przekazywanajest wartość null. W naszym systemie ciągle tak robimy”, prawdopodobnie zajmujeszsię dość paskudnym systemem. Zapewne wszędzie masz w nim porozrzucane instrukcjesprawdzające występowanie pustej wartości i pełno kodu warunkowego określającego, comożna, a co trzeba z nią zrobić. Z drugiej jednak strony, jeśli spojrzałeś na powyższykod i stwierdziłeś: „Z tym facetem chyba jest coś nie tak! Przekazywanie w systemiewartości null? Czy on w ogóle ma o czymkolwiek pojęcie?” — otóż tym z Was z tej drugiejgrupy (a przynajmniej tym, którzy nadal to czytają i nie zamknęli z rozmachem tejksiążki w księgarni) — chciałbym powiedzieć coś takiego: pamiętajcie, że robimy to tylkow testach. Najgorsze, co się może stać, to to, że jakiś fragment kodu spróbuje skorzystaćz tej zmiennej. W naszym przypadku środowisko uruchomieniowe Javy zgłosi wyjątek.Ponieważ jarzmo wyłapuje wszystkie wyjątki zgłaszane podczas testów, dość szybkodowiemy się, czy jakiś parametr jest w ogóle używany.

Przekazywanie pustej wartości

Kiedy piszesz testy, a pewien obiekt wymaga parametru, który jest trudny do utworzenia,rozważ przekazanie po prostu pustej wartości. Jeżeli parametr ten zostanie użyty podczaswykonywania się testu, kod zgłosi wyjątek, który zostanie wychwycony przez jarzmo testowe.Jeśli musisz mieć zachowanie, które istotnie wymaga obiektu, to będziesz mógł go skonstru-ować i przekazać jako parametr.

Przekazywanie pustej wartości jest w niektórych językach bardzo wygodną techniką. Dobrzesię ona sprawdza w Javie i C# oraz niemal każdym języku, który zgłasza wyjątek, gdy w czasiedziałania programu zostanie użyta pusta referencja. Z tego wynika, że przekazywanie pustejwartości nie jest dobrym pomysłem w przypadku C i C++, chyba że masz pewność, iż programuruchomieniowy wykryje błędy związane z pustymi wskaźnikami. W przeciwnym razie bę-dziesz mieć do czynienia z testami, które się tajemniczo wysypują — o ile będziesz mieć szczęście.Jeśli zabraknie Ci szczęścia, Twoje testy będą po prostu bezobjawowo i beznadziejnie złe.W czasie działania będą niszczyć pamięć, a Ty nigdy się o tym nie dowiesz.

Kiedy pracuję w Javie, często zaczynam od następującego testu, a parametry uzupeł-niam w miarę potrzeb.

Poleć książkęKup książkę

PRZYPADEK IRYTUJĄCEGO PARAMETRU 127

public void testCreate() { CreditValidator validator = new CreditValidator(null, null, "a");}

Najważniejsze do zapamiętania jest to, aby nie przekazywać pustej wartości w kodzieprodukcyjnym, chyba że nie masz innego wyboru. Wiem, że niektóre biblioteki tego odCiebie oczekują, ale kiedy piszesz nowy kod, masz lepszą alternatywę. Jeśli kusi Cię użyciepustej wartości w kodzie produkcyjnym, znajdź miejsca, w których są one zwracane i po-bierane, po czym weź pod uwagę inne rozwiązanie. Zastanów się nad użyciem wzorcapusty obiekt.

Wzorzec pusty obiekt

Wzorzec pusty obiekt to sposób na uniknięcie użycia pustych wartości w programach. Mamyna przykład metodę, która zwraca dane pracownika po otrzymaniu jego numeru identyfikacyj-nego. Co powinno zostać zwrócone, jeśli nie ma pracownika o podanym numerze?

for(Iterator it = idList.iterator(); it.hasNext(); ) { EmployeeID id = (EmployeeID)it.next(); Employee e = finder.getEmployeeForID(id); e.pay();}

Mamy kilka możliwości. Możemy podjąć decyzję o zgłaszaniu wyjątków, dzięki czemu nie będzietrzeba niczego zwracać, ale takie rozwiązanie zmusiłoby klienty do jawnej obsługi błędów. Mo-glibyśmy także zwracać pustą wartość, lecz wówczas klienty musieliby jawnie sprawdzać, czy jejnie otrzymują.

Jest jeszcze trzecie rozwiązanie. Czy powyższy kod tak naprawdę sprawdza, czy istnieje pra-cownik, któremu należy zapłacić? Czy musi to robić? A gdybyśmy tak mieli klasę o nazwieNullEmployee? Instancja tej klasy nie ma nazwiska ani adresu, a kiedy polecisz jej dokonaniewypłaty, po prostu nic nie zrobi.

W takich kontekstach puste obiekty mogą być przydatne — pomagają one chronić klienty przedjawną obsługą błędów. Chociaż puste obiekty są pomocne, powinieneś zachować ostrożność,gdy z nich korzystasz. Oto przykład niewłaściwego sposobu liczenia pracowników, którym wy-płacono wynagrodzenie:

int employeesPaid = 0;for(Iterator it = idList.iterator(); it.hasNext(); ) { EmployeeID id = (EmployeeID)it.next(); Employee e = finder.getEmployeeForID(id); e.pay(); mployeesPaid++; // b d!}

Jeśli któryś ze zwróconych pracowników jest pracownikiem pustym, to zliczenie będzie błędne.

Puste obiekty są przydatne zwłaszcza wtedy, gdy klient nie musi sprawdzać, czy dana operacja siępowiodła. W wielu przypadkach możemy tak dopracować nasz projekt, abyśmy mieli do czy-nienia z takim właśnie rozwiązaniem.

Poleć książkęKup książkę

128 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

Przekazanie pustej wartości i wyodrębnienie interfejsu (361) to dwa sposoby naporadzenie sobie z irytującymi parametrami. Czasami można jednak skorzystać z jeszczeinnej możliwości. Jeżeli sprawiająca problemy zależność w parametrze nie jest bezpo-średnio zakodowana w swoim konstruktorze, w celu pozbycia się jej możemy skorzystaćz techniki tworzenia podklasy i przesłaniania metody (398). W tym przypadku rozwią-zanie takie byłoby możliwe. Jeśli konstruktor klasy RGHConnection używa metody connectw celu nawiązania połączenia, moglibyśmy pozbyć się zależności, przesłaniając wywołanieconnect() w testowanej podklasie. Tworzenie podklasy i przesłanianie metody (398) możebyć bardzo przydatnym sposobem usuwania zależności, ale musimy mieć pewność, że niezmieniamy zachowania, które chcemy przetestować, gdy z niego korzystamy.

Przypadek ukrytej zależności

Niektóre klasy bywają podstępne. Patrzymy na nie, znajdujemy konstruktor, któregochcemy użyć, i próbujemy go wywołać. Wtedy trach! Napotykamy przeszkodę. Jednąz najczęściej występujących przeszkód jest ukryta zależność — konstruktor korzysta z za-sobów, do których nie mamy łatwego dostępu w naszym jarzmie testowym. Zobaczymytaką sytuację w następnym przykładzie; źle zaprojektowaną klasę w C++, która obsługujelistę mailingową:

class mailing_list_dispatcher{public: mailing_list_dispatcher (); virtual ~mailing_list_dispatcher;

void send_message(const std::string& message); void add_recipient(const mail_txm_id id, const mail_address& address); ...

private: mail_service *service; int status;};

Oto fragment konstruktora tej klasy. Alokuje ona obiekt mail_service za pomocą in-strukcji new na liście inicjatora konstruktora. To kiepski styl, ale potem jest jeszcze gorzej.Konstruktor wykonuje sporo szczegółowej pracy z tym obiektem; korzysta też z magicznejliczby 12. Co ma oznaczać to 12?

mailing_list_dispatcher::mailing_list_dispatcher(): service(new mail_service), status(MAIL_OKAY){ const int client_type = 12; service->connect(); if (service->get_status() == MS_AVAILABLE) {

Poleć książkęKup książkę

PRZYPADEK UKRYTEJ ZALEŻNOŚCI 129

service->register(this, client_type, MARK_MESSAGES_OFF); service->set_param(client_type, ML_NOBOUNCE | ML_REPEATOFF); } else status = MAIL_OFFLINE; ...}

Podczas testu możemy utworzyć instancję tej klasy, ale chyba nie przyniesie nam onawiększych korzyści. Przede wszystkim musimy połączyć się z bibliotekami pocztowymii skonfigurować system pocztowy, aby obsługiwał rejestrowanie. Jeśli w trakcie testówużyjemy funkcji send_message, to naprawdę wyślemy maile do ludzi. Automatyczne te-stowanie tej funkcjonalności będzie trudne, chyba że skonfigurujemy specjalną skrzynkępocztową i będziemy się z nią regularnie łączyć, czekając na nadejście wiadomości. Takierozwiązanie byłoby dobre podczas całościowych testów systemu, ale wszystko, co chcemyteraz zrobić, to tylko dodać do klasy kilka przetestowanych funkcjonalności, więc sposóbten byłby lekką przesadą. Jak moglibyśmy utworzyć prosty obiekt w celu dodania jakiejśnowej funkcjonalności?

Podstawowy problem w tym przypadku polega na tym, że zależność od obiektumail_service jest ukryta w konstruktorze mailing_list_dispatcher. Gdyby istniał jakiśsposób na zastąpienie tego obiektu fałszywką, moglibyśmy dokonać rozpoznania, posługującsię fałszywym obiektem i uzyskać informację zwrotną podczas modyfikowania klasy.

Jedną z technik, które możemy wykorzystać, jest parametryzacja konstruktora (377).Przy jej pomocy wyciągamy na zewnątrz zależność istniejącą w konstruktorze, przekazującją do konstruktora.

Oto jak wygląda kod konstruktora po sparametryzowaniu konstruktora (377):

mailing_list_dispatcher::mailing_list_dispatcher(mail_service *service): status(MAIL_OKAY){ const int client_type = 12; service->connect(); if (service->get_status() == MS_AVAILABLE) { service->register(this, client_type, MARK_MESSAGES_OFF); service->set_param(client_type, ML_NOBOUNCE | ML_REPEATOFF); } else status = MAIL_OFFLINE; ...}

Jedyna różnica tak naprawdę sprowadza się do tego, że obiekt mail_service jest two-rzony poza klasą i do niej przekazywany. Być może nie wygląda to na znaczne usprawnie-nie, ale teraz mamy ogromne możliwości. Możemy skorzystać z wyodrębniania interfejsu(361), aby uzyskać interfejs dla mail_service. Jeden z implementerów tego interfejsu możebyć klasą produkcyjną, która faktycznie wysyła maile. Inny może być fałszywą klasą, rozpo-znającą to, co robimy podczas testów, oraz informującą nas, że istotnie to się stało.

Poleć książkęKup książkę

130 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

Parametryzacja konstruktora (377) jest bardzo wygodnym sposobem na przesu-nięcie zależności z konstruktora na zewnątrz, jednak programiści nie biorą go zbyt częstopod uwagę. Jedną z przeszkód stanowi fakt, że wiele osób sądzi, iż w celu przekazanianowego parametru konieczna będzie zmiana wszystkich klientów klasy, co jednak niejest prawdą. Możemy sobie z tym poradzić następująco. Najpierw wyodrębniamy ciałokonstruktora i tworzymy z niego nową metodę o nazwie initialize. W przeciwieństwiedo większości innych sposobów wyodrębniania metod możemy to całkiem bezpieczniezrobić bez testów, gdyż w trakcie tej czynności zachowujemy sygnatury (314).

void mailing_list_dispatcher::initialize(mail_service *service){ status = MAIL_OKAY; const int client_type = 12; service.connect(); if (service->get_status() == MS_AVAILABLE) { service->register(this, client_type, MARK_MESSAGES_OFF); service->set_param(client_type, ML_NOBOUNCE | ML_REPEATOFF); } else status = MAIL_OFFLINE; ...}

mailing_list_dispatcher::mailing_list_dispatcher(mail_service *service){ initialize(service);}

Teraz możemy udostępnić konstruktor, który ma oryginalną sygnaturę. Testy mogąwywoływać konstruktor sparametryzowany przez mail_service, natomiast klienty mogąwywoływać go w poniższy sposób; nie muszą wiedzieć, że coś uległo zmianie.

mailing_list_dispatcher::mailing_list_dispatcher(){ initialize(new mail_service);}

Tego typu refaktoryzacja jest jeszcze łatwiejsza w takich językach jak C# i Java, po-nieważ możemy w nich wywoływać konstruktory z poziomu innych konstruktorów.

Gdybyśmy na przykład robili coś podobnego w C#, wynikowy kod mógłby wyglądaćnastępująco:

public class MailingListDispatcher{ public MailingListDispatcher() : this(new MailService()) {}

public MailingListDispatcher(MailService service) { ... }}

Poleć książkęKup książkę

PRZYPADEK KONSTRUKCYJNEGO KŁĘBOWISKA 131

Z zależnościami ukrytymi w konstruktorach można sobie radzić za pomocą wielutechnik. Zwykle możemy zastosować wyodrębnianie i przesłanianie gettera (353), wyod-rębnianie i przesłanianie metody wytwórczej (351) oraz zastępowanie zmiennej instan-cji (401), najbardziej jednak lubię korzystać z parametryzacji konstruktora (377).Kiedy w konstruktorze tworzony jest obiekt i nie ma on żadnych zależności konstrukcyj-nych, łatwą do zastosowania techniką będzie właśnie parametryzacja konstruktora.

Przypadek konstrukcyjnego kłębowiska

Parametryzacja konstruktora (377) jest jedną z najprostszych technik, z których mo-żemy skorzystać w celu usunięcia zależności ukrytych w konstruktorze. Jest też techniką,po którą często sięgam w pierwszej kolejności. Niestety, nie zawsze jest ona najlepszymwyborem. Jeśli konstruktor tworzy sporą liczbę obiektów lub ma dostęp do wielu zmien-nych globalnych, możemy w rezultacie uzyskać bardzo długą listę parametrów. W gorszychsytuacjach konstruktor tworzy kilka obiektów, po czym używa ich do utworzenia kolej-nych obiektów, tak jak poniżej:

class WatercolorPane{public: WatercolorPane(Form *border, WashBrush *brush, Pattern *backdrop) { ... anteriorPanel = new Panel(border); anteriorPanel->setBorderColor(brush->getForeColor()); backgroundPanel = new Panel(border, backdrop); cursor = new FocusWidget(brush, backgroundPanel); ... } ...}

Gdybyśmy chcieli zrobić rozpoznanie, posługując się obiektem cursor, mielibyśmyproblem. Obiekt ten jest osadzony w kłębowisku tworzonych obiektów. Moglibyśmy spró-bować przenieść poza klasę cały kod użyty do tworzenia kursora. W dalszej kolejnościklient mógłby utworzyć kursor i przekazać go jako argument. Nie będzie to jednak bez-pieczne, jeśli nie mamy na miejscu testów, poza tym rozwiązanie takie byłoby sporymutrudnieniem dla klientów tej klasy.

Jeśli dysponujemy narzędziem refaktoryzującym, które bezpiecznie wyodrębnia metody,możemy skorzystać z techniki wyodrębniania i przesłaniania metody wytwórczej (351)w odniesieniu do kodu konstruktora, co jednak nie sprawdzi się we wszystkich językach.Możemy tak postąpić w Javie i C#, ale już C++ nie pozwala wywoływać funkcji wirtu-alnych w konstruktorach w celu odwołania się do wirtualnych funkcji zdefiniowanychw klasach pochodnych. Poza tym tak w ogóle to nie jest dobry pomysł. Funkcje w klasachpochodnych często zakładają, że mogą korzystać ze zmiennych w ich klasie bazowej.

Poleć książkęKup książkę

132 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

Dopóki konstruktor klasy bazowej nie zakończy w pełni swojej pracy, istnieje ryzyko, żeprzesłonięta funkcja, która go wywołuje, może uzyskać dostęp do niezainicjalizowanejzmiennej.

Inną opcją jest zastępowanie zmiennej instancji (401). Piszemy setter dla klasy,który umożliwi nam podstawienie innej instancji po skonstruowaniu obiektu.

class WatercolorPane{public: WatercolorPane(Form *border, WashBrush *brush, Pattern *backdrop) { ... anteriorPanel = new Panel(border); anteriorPanel->setBorderColor(brush->getForeColor()); backgroundPanel = new Panel(border, backdrop);

cursor = new FocusWidget(brush, backgroundPanel); ... }

void supersedeCursor(FocusWidget *newCursor) { delete cursor; cursor = newCursor; }}

Musimy być bardzo ostrożni, gdy stosujemy tę technikę refaktoryzacji w C++. Kiedyzastępujemy obiekt, powinniśmy pozbyć się jego starej instancji. Często oznacza to, żemusimy skorzystać z operatora delete w celu wywołania destruktora i zniszczenia pamięciobiektu. Kiedy to robimy, musimy wiedzieć, co robi destruktor i czy niszczy on coś, co zo-stało przekazane konstruktorowi obiektu. Jeśli nie będziemy ostrożni podczas czyszczeniapamięci, możemy spowodować pewne subtelne błędy.

W większości innych języków zastępowanie zmiennej instancji (401) jest dość prostew użyciu. Oto wynik zastosowania tej techniki zapisany w Javie. Nie musimy robić nicszczególnego, aby pozbyć się obiektu, do którego odwołuje się cursor — proces odśmie-cania pamięci i tak w końcu się go pozbędzie. Powinniśmy jednak być szczególnie uważni,aby nie korzystać z tej metody w kodzie produkcyjnym. Jeżeli obiekty, które zastępujemy,zarządzają innymi zasobami, możemy spowodować całkiem poważne problemy związanez dostępem do zasobów.

void supersedeCursor(FocusWidget newCursor) { cursor = newCursor;}

Teraz, gdy mamy już zastępczą metodę, możemy podjąć się próby utworzenia instancjiFocusWidget poza klasą i przekazać go do obiektu po jego skonstruowaniu. Ponieważpotrzebujemy przeprowadzić rozpoznanie, w odniesieniu do klasy FocusWidget możemyskorzystać z techniki wyodrębniania interfejsu (361) albo wyodrębniania implementera

Poleć książkęKup książkę

PRZYPADEK IRYTUJĄCEJ ZALEŻNOŚCI GLOBALNEJ 133

(356) i utworzyć fałszywy obiekt do przekazania. Z pewnością będzie to łatwiejsze niż two-rzenie obiektu FocusWidget w konstruktorze.

TEST(renderBorder, WatercolorPane){ ... TestingFocusWidget *widget = new TestingFocusWidget; WatercolorPane pane(form, border, backdrop);

pane.supersedeCursor(widget);

LONGS_EQUAL(0, pane.getComponentCount());}

Nie lubię korzystać z techniki zastępowania zmiennej instancji (401). Używam jej,kiedy nie mam już innego wyjścia. Prawdopodobieństwo pojawienia się problemów z za-rządzaniem zasobami jest zbyt duże. Mimo to korzystam z niej od czasu do czasu w C++.Często wolałbym zastosować wyodrębnianie i przesłanianie metody wytwórczej (351),co jednak jest niemożliwe w przypadku konstruktorów w języku C++. Z tego też powodurzadko uciekam się do zastępowania zmiennej instancji (401).

Przypadek irytującej zależności globalnej

Od lat ludzie w branży programistycznej narzekają, że nie ma na rynku większej liczbykomponentów wielokrotnego użytku. Wraz z upływem czasu sytuacja polepszyła się— istnieje wiele komercyjnych oraz otwartych platform, ale w zasadzie z wielu z nich taknaprawdę nie korzystamy; to raczej one używają naszego kodu. Platformy te często zarzą-dzają cyklem życia aplikacji, a my piszemy kod wypełniający puste miejsca. Możemy tozaobserwować we wszystkich rodzajach platform, od ASP.NET aż do Java Struts. W takisposób działa nawet xUnit — piszemy klasy testowe, a on je wywołuje i wyświetla wynikiich działania.

Platformy rozwiązują wiele problemów i nadają nam impet, gdy rozpoczynamy noweprojekty, ale to nie takiego rodzaju wielokrotnego wykorzystania oczekiwano we wcze-snych latach rozwoju oprogramowania. Wielokrotne użycie w starym stylu ma miejscewtedy, gdy znajdujemy jakąś klasę lub zbiór klas, których chcemy użyć w naszej aplikacji,i po prostu to robimy. Dodajemy je do naszego projektu i z nich korzystamy. Byłoby miło,gdybyśmy mogli tak robić rutynowo, ale szczerze mówiąc — myślę, że sami siebie oszuku-jemy nawet wtedy, gdy tylko myślimy o takim rodzaju wielokrotnego użycia, skoro niepotrafimy wyciągnąć z pierwszej lepszej aplikacji dowolnej klasy i oddzielnie jej skompi-lować w jarzmie testowym bez konieczności wykonania całej masy pracy (ale zrzędzę).

Wiele różnych rodzajów zależności może utrudniać tworzenie i używanie klas naplatformach testowych, a jedną z najgorszych, z jakimi możemy mieć do czynienia, jestużycie zmiennych globalnych. W prostszych przypadkach w celu ominięcia takich zależ-ności możemy posłużyć się parametryzacją konstruktora (377), parametryzacją metody

Poleć książkęKup książkę

134 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

(381) oraz wyodrębnianiem i przesłanianiem wywołania (349), ale czasami zależnościglobalne są tak wszechobecne, że prościej będzie rozprawić się z nimi u źródła. Z taką sy-tuacją spotkamy się w następnym przykładzie, którym jest aplikacja w Javie, rejestrującapozwolenia na budowę w pewnej agencji rządowej. Oto jedna z jej głównych klas:

public class Facility{private Permit basePermit;

public Facility(int facilityCode, String owner, PermitNotice notice) throws PermitViolation {

Permit associatedPermit = PermitRepository.getInstance().findAssociatedPermit(notice);

if (associatedPermit.isValid() && !notice.isValid()) { basePermit = associatedPermit; } else if (!notice.isValid()) { Permit permit = new Permit(notice); permit.validate(); basePermit = permit; } else throw new PermitViolation(permit); } ...}

Chcielibyśmy utworzyć klasę Facility w jarzmie testowym, w związku z czym za-czynamy od próby utworzenia jej obiektu:

public void testCreate() { PermitNotice notice = new PermitNotice(0, "a"); Facility facility = new Facility(Facility.RESIDENCE, "b", notice);}

Test kompiluje się prawidłowo, ale kiedy zaczynamy pisać kolejne testy, zauważamypewien problem. Konstruktor korzysta z klasy o nazwie PermitRepository i aby nasze testyzostały poprawnie skonfigurowane, musi zostać zainicjalizowany określonym zestawempozwoleń. Przebiegłe, co? Oto ta problematyczna instrukcja w konstruktorze:

Permit associatedPermit = PermitRepository.getInstance().findAssociatedPermit(notice);

Moglibyśmy ominąć tę przeszkodę, parametryzując konstruktor, ale w tej aplikacji niejest to odosobniony przypadek. Istnieje jeszcze 10 innych klas, które zawierają mniej więcejtaki sam wiersz kodu. Tkwi on w konstruktorach, metodach zwykłych i statycznych.Możemy tylko wyobrazić sobie, ile czasu byśmy poświęcili, walcząc z tym problememw bazie kodu.

Jeżeli uczyłeś się kiedyś o wzorcach projektowych, być może rozpoznałeś w tym przy-kładzie wzorzec projektowy singleton (370). Metoda getInstance klasy PermitRepository

Poleć książkęKup książkę

PRZYPADEK IRYTUJĄCEJ ZALEŻNOŚCI GLOBALNEJ 135

jest metodą statyczną, której zadaniem jest zwrócenie jedynej instancji klasy PermitRepository, która może istnieć w naszej aplikacji. Pole przechowujące tę instancję także

jest statyczne i znajduje się w tej klasie.W Javie wzorzec singleton jest jednym z mechanizmów używanych do tworzenia

zmiennych globalnych. Zazwyczaj wykorzystywanie zmiennych globalnych jest złympomysłem z kilku powodów. Jednym z nich jest nieprzejrzystość. Kiedy oglądamy frag-ment kodu, dobrze byłoby wiedzieć, na co może on wpływać. Na przykład w Javie —jeśli chcemy zrozumieć, jaki wpływ wywiera na różne elementy poniższy kod — musimyzajrzeć do kilku zaledwie miejsc.

Account example = new Account();example.deposit(1);int balance = example.getBalance();

Wiemy, że obiekt klasy Account może mieć wpływ na elementy, które przekazujemydo konstruktora Account, ale my niczego nie przekazujemy. Obiekty klasy Account mogąteż wpływać na obiekty, które przekazujemy do metody jako parametry, ale w tym przy-padku nie przekazujemy nic, co mogłoby ulec zmianie, a jedynie liczbę całkowitą. W miej-scu tym przypisujemy zwracaną wartość getBalance zmiennej i tak naprawdę jest to jedynyelement, który może być zmieniany przez powyższe instrukcje.

Kiedy korzystamy ze zmiennych globalnych, sytuacja zostaje postawiona na głowie.Możemy patrzeć na użycie takich klas jak Account i nie mieć pojęcia, czy ma ona dostępi modyfikuje zmienne zadeklarowane w jakimś innym miejscu programu. Nie trzebanadmieniać, że z tego powodu programy mogą być trudniejsze do zrozumienia.

Trudnym zadaniem w naszej sytuacji jest konieczność określenia, które zmienneglobalne zostały użyte w klasie, i nadanie im odpowiednich wartości na potrzeby testu.Do tego musimy to robić przed każdym testem, jeśli konfiguracja testów ma się zmieniać.Zadanie to jest dość żmudne; musiałem je wykonywać w odniesieniu do całej masy syste-mów, aby można było je poddać testom, i wraz z upływem czasu nie stało się ono nawetna jotę bardziej ekscytujące.

Powróćmy jednak do naszego przykładu.PermitRepository jest singletonem. Z tego względu jest on szczególnie trudny do

sfałszowania. Cała koncepcja kryjąca się za wzorcem singletona polega na uniemożli-wieniu tworzenia więcej niż jednej jego instancji w danej aplikacji. Takie rozwiązaniemoże sprawdzać się w kodzie produkcyjnym, ale w przypadku testowania każdy test w ze-stawie powinien być w pewnym sensie miniaplikacją — powinien być całkowicie odizolo-wany od pozostałych testów. Aby zatem uruchomić w jarzmie testowym kod zawierającysingletony, musimy osłabić własność singletona. Oto jak to zrobimy.

Najpierw do klasy singletona dodamy nową metodę statyczną. Pozwoli nam ona za-stąpić statyczną instancję w singletonie. Nazwiemy ją setTestingInstance.

public class PermitRepository{ private static PermitRepository instance = null;

Poleć książkęKup książkę

136 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

private PermitRepository() {}

public static void setTestingInstance(PermitRepository newInstance) { instance = newInstance; }

public static PermitRepository getInstance() { if (instance == null) { instance = new PermitRepository(); } return instance; } public Permit findAssociatedPermit(PermitNotice notice) { ... } ...}

Teraz, gdy mamy już setter, możemy utworzyć testową instancję klasy PermitRepositoryi nadać jej wartość. W naszej testowej metodzie setUp chcielibyśmy dodać następujący kod:

public void setUp() { PermitRepository repository = new PermitRepository(); ... // w tym miejscu dodaj zezwolenia do repozytorium ... PermitRepository.setTestingInstance(repository);}

Wprowadzanie statycznego settera (370) nie jest jedynym sposobem na poradzenie sobiez taką sytuacją. Oto inne podejście. Do metody resetForTesting() możemy dodać singleton,który wygląda następująco:

public class PermitRepository{ ... public void resetForTesting() { instance = null; } ...}

Jeśli wywołamy tę metodę z naszej metody testowej setUp (a dobrym pomysłem będziewywołanie jej także z metody tearDown), będziemy tworzyć świeże singletony w każdym te-ście. Za każdym razem singleton będzie inicjalizował się od nowa. Schemat ten dobrze sięsprawdza, kiedy metody publiczne w singletonie pozwalają konfigurować jego stan w do-wolny sposób, jaki tylko będzie potrzebny podczas testowania. Jeśli singleton nie ma takichpublicznych metod albo korzysta z zewnętrznych zasobów, które wpływają na jego stan,lepszym wyborem będzie wprowadzenie statycznego settera (370). Dzięki temu będzieszmógł tworzyć podklasy singletona, przesłaniać metody, usuwać zależności i dodawać me-tody publiczne do podklas, aby poprawnie konfigurować ich stan.

Poleć książkęKup książkę

PRZYPADEK IRYTUJĄCEJ ZALEŻNOŚCI GLOBALNEJ 137

Czy to zadziała? Jeszcze nie. Kiedy programiści korzystają ze wzorca projektowegosingleton (370), często ich konstruktor klasy singletona jest prywatny, i mają ku temudobry powód. Jest to najbardziej przejrzysty sposób zagwarantowania, że nikt spoza tejklasy nie będzie mógł utworzyć kolejnej instancji singletona.

W tym momencie pojawia się konflikt między dwoma założeniami naszego projektu.Chcemy mieć pewność, że w systemie istnieje tylko jedna instancja klasy PermitRepository,ale chcemy też dysponować systemem, w którym klasy można testować niezależnie od sie-bie. Czy uda nam się osiągnąć jednocześnie oba te cele?

Cofnijmy się na chwilę. Dlaczego chcemy mieć w systemie tylko jedną instancjęklasy? Odpowiedź będzie się różnić w zależności od systemu, ale oto kilka najczęściejspotykanych powodów:

1. Modelujemy rzeczywisty świat, a w rzeczywistym świecie istnieje tylko jednataka rzecz. Właśnie takie są niektóre systemy kontrolujące sprzęt. Programiścitworzą klasę dla każdego urządzenia, które musi być kontrolowane. Wychodząz założenia, że jeśli istnieje tylko po jednym urządzeniu, każde z nich powinno byćsingletonem. Podobnie sprawy się mają w przypadku baz danych. W naszej agencjiistnieje tylko jeden zbiór pozwoleń, a zatem element zapewniający do nich dostęppowinien być singletonem.

2. Jeśli utworzymy dwie takie rzeczy, możemy znaleźć się w poważnych opałach.Sytuacja taka również często ma miejsce w dziedzinie sterowania urządzeniami.Wyobraź sobie przypadkowe utworzenie dwóch kontrolerów prętów uranowychi umożliwienie dwóm różnym częściom programu sterowanie nimi w tym samymczasie bez wzajemnej wiedzy o sobie.

3. Jeśli ktoś utworzy dwie takie rzeczy, będziemy zużywać zbyt wiele zasobów.To zdarza się często. Zasoby mogą być obiektami fizycznymi, takimi jak miejscena dysku albo zużycie pamięci, ale mogą być także abstrakcyjne, jak na przykładliczba licencji na oprogramowanie.

Takie są powody, dla których wymusza się istnienie pojedynczych instancji, ale nie sąto główne powody, dla których singletony są używane. Programiści często tworzą single-tony, ponieważ chcą mieć zmienne globalne. Uważają, że przekazywanie zmiennychdo miejsc, w których będą one potrzebne, jest zbyt kłopotliwe.

Jeśli mamy singletona z tej drugiej przyczyny, to naprawdę nie ma powodu do zacho-wania jego własności. Nasz konstruktor może mieć zakres chroniony, publiczny albopakietu, a przy tym nadal będziemy dysponować przyzwoitym, możliwym do testowaniasystemem. W innym przypadku i tak warto poszukać alternatywy. Jeśli zajdzie taka potrzeba,wprowadzimy inny rodzaj ochrony. Moglibyśmy dodać w naszym systemie kompilującymsprawdzanie we wszystkich plikach źródłowych, czy metoda setTestingInstancenie jest wywoływana przez kod nietestujący. Tak samo możemy postąpić w odniesieniudo kontroli wykonywanej w czasie działania programu. Jeśli metoda setTestingInstancezostanie wywołana podczas działania aplikacji, możemy podnieść alarm albo zawiesićsystem i poczekać na działanie ze strony użytkownika. Faktem jest, że chociaż wymuszenie

Poleć książkęKup książkę

138 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

„singletonowości” nie było możliwe w wielu językach przed pojawieniem się zorientowa-nia obiektowego, to jednak programiści zdołali utworzyć wiele bezpiecznych syste-mów. W końcu wszystko sprowadza się do odpowiedzialnego projektu i kodowania.

Jeżeli naruszenie własności singletona nie stanowi większego problemu, możemy zdaćsię na regułę stosowaną przez zespół. Przykładowo każdy w zespole powinien zrozumieć,że w aplikacji mamy jedną instancję bazy danych i że nie powinniśmy mieć kolejnej.

Aby osłabić właściwość singletona w klasie PermitRepository, możemy przekształcićkonstruktor w publiczny. Takie rozwiązanie będzie nas satysfakcjonować, dopóki publicznemetody tej klasy pozwalają nam robić wszystko, czego potrzebujemy w celu skonfigurowa-nia naszego repozytorium na potrzeby testów. Jeśli na przykład w klasie PermitRepositoryznajduje się metoda o nazwie addPermit, umożliwiająca dodawanie pozwoleń, które będąnam potrzebne w testach, być może wystarczy po prostu, że umożliwimy sobie tworzenierepozytoriów i użyjemy ich w naszych testach. W innym przypadku możemy nie miećpotrzebnego nam dostępu lub — co gorsza — singleton może robić rzeczy, co do którychnie chcielibyśmy, aby się działy w jarzmie testowym, takie jak komunikowanie się w tlez bazą danych. W takich okolicznościach możemy utworzyć podklasę i przesłonić metodę(398), po czym utworzyć klasy pochodne, które ułatwią nam testowanie.

Oto przykład z naszego systemu pozwoleń. Oprócz metod i zmiennych, które sprawiają,że PermitRepository jest singletonem, mamy także następującą metodę:

public class PermitRepository{ ... public Permit findAssociatedPermit(PermitNotice notice) { // otwórz baz danych z pozwoleniami ...

// wybierz na podstawie warto ci w powiadomieniu ...

// sprawd , czy mamy tylko jedno pasuj ce pozwolenie; je li nie, zg o b d ...

// zwró pasuj ce pozwolenie ... }}

Jeśli chcemy uniknąć komunikacji z bazą danych, możemy utworzyć następującąpodklasę klasy PermitRepository:

public class TestingPermitRepository extends PermitRepository{ private Map permits = new HashMap();

public void addAssociatedPermit(PermitNotice notice, permit) { permits.put(notice, permit); }

public Permit findAssociatedPermit(PermitNotice notice) {

Poleć książkęKup książkę

PRZYPADEK IRYTUJĄCEJ ZALEŻNOŚCI GLOBALNEJ 139

return (Permit)permits.get(notice); }}

Kiedy tak zrobimy, będziemy mogli zachować część własności singletona. Ponieważkorzystamy z podklasy klasy PermitRepository, sprawimy, że będzie chroniony raczejnasz konstruktor klasy PermitRepository niż publiczny. Dzięki temu zabezpieczymy sięprzed utworzeniem więcej niż jednej instancji tej klasy, chociaż będziemy mogli tworzyćjej podklasy.

public class PermitRepository{ private static PermitRepository instance = null;

protected PermitRepository() {}

public static void setTestingInstance(PermitRepository newInstance) { instance = newInstance; }

public static PermitRepository getInstance() { if (instance == null) { instance = new PermitRepository(); } return instance; } public Permit findAssociatedPermit(PermitNotice notice)

{ ... } ...}

W wielu przypadkach możemy skorzystać z tworzenia podklasy i przesłaniania me-tody (398) — takiego jak powyżej — aby wstawić na miejsce fałszywy singleton. Innym ra-zem zależności będą do tego stopnia rozbudowane, że łatwiej będzie wyodrębnić interfejs(361) względem singletona i zmienić wszystkie referencje w aplikacji, aby używały nazwyinterfejsu. Może to kosztować sporo pracy, ale w celu dokonania takich zmian mogliby-śmy skorzystać ze wsparcia kompilatora (317). Po wyodrębnieniu klasa PermitRepositorybędzie wyglądać następująco:

public class PermitRepository implements IPermitRepository{ private static IPermitRepository instance = null;

protected PermitRepository() {}

public static void setTestingInstance(IPermitRepository newInstance) { instance = newInstance;

Poleć książkęKup książkę

140 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

}

public static IPermitRepository getInstance() { if (instance == null) { instance = new PermitRepository(); } return instance; }

public Permit findAssociatedPermit(PermitNotice notice) { ... } ...}

Interfejs IPermitRepository będzie mieć sygnatury wszystkich publicznych, nie-statycznych metod klasy PermitRepository.

public interface IPermitRepository{ Permit findAssociatedPermit(PermitNotice notice); ...}

Jeśli używasz języka, który jest wyposażony w narzędzie do refaktoryzacji, mógłbyśwykonać takie wyodrębnienie interfejsu automatycznie. Jeśli Twój język nie ma tejmożliwości, łatwiej będzie skorzystać z techniki wyodrębniania implementera (356).

Cały ten proces refaktoryzacji nosi nazwę wprowadzania statycznego settera (370).Jest to technika, z której możemy skorzystać, aby rozmieścić testy mimo istnienia rozle-głych zależności globalnych. Niestety, niespecjalnie nadaje się ona do obchodzenia global-nych zależności. Jeśli musisz poradzić sobie z tym problemem, posłuż się parametryzacjąmetody (381) i parametryzacją konstruktora (377). Za pomocą tych technik refakto-ryzacji zmienisz referencję globalną na zmienną tymczasową w metodzie lub na polew obiekcie. Wada parametryzacji metody (381) polega na tym, że w wyniku jej zasto-sowania możesz uzyskać wiele dodatkowych metod, które będą rozpraszać osoby pró-bujące zrozumieć klasy. Z kolei wada parametryzacji konstruktora (377) jest taka, żekażdy obiekt korzystający ze zmiennej globalnej otrzymuje w rezultacie dodatkowe pole.Pole to będzie musiało zostać przekazane do konstruktora, przez co klasa tworząca obiekttakże będzie musiała mieć dostęp do instancji. Jeżeli to dodatkowe pole będzie potrzebnew zbyt wielu obiektach, może to znacząco wpłynąć na ilość pamięci używanej przezaplikację, chociaż często wskazuje to na inne problemy w projekcie.

Przyjrzyjmy się najgorszemu przypadkowi. Mamy aplikację z kilkoma setkami klas,które podczas działania programu tworzą tysiące obiektów, a każdy z tych obiektówwymaga dostępu do bazy danych. Pierwsze pytanie, które przychodzi mi na myśl, nawetbez spojrzenia na aplikację, brzmi: dlaczego? Jeśli system robi jeszcze coś innego opróczkomunikowania się z bazą danych, można przeprowadzić jego refaktoryzację, dzięki czemu

Poleć książkęKup książkę

PRZYPADEK STRASZLIWYCH ZALEŻNOŚCI DYREKTYW INCLUDE 141

część klas zajmie się tymi innymi rzeczami, a pozostałe klasy będą zapisywać i pobieraćdane z bazy. Kiedy podejmujemy skoordynowane działania w celu rozdzielenia odpowie-dzialności w aplikacji, zależności stają się lokalne — referencja do bazy danych w każdymobiekcie nie będzie potrzebna. Niektóre obiekty będą zapełniane danymi pochodzącymiz bazy, a inne będą przeprowadzać obliczenia na danych, które otrzymały za pośrednic-twem swoich konstruktorów.

W ramach ćwiczeń wybierz sobie w dużej aplikacji zmienną globalną i poszukaj jej.W większości przypadków zmienne globalne są globalnie dostępne, ale rzadko są globalnieużywane. Korzysta się z nich w relatywnie niewielu miejscach. Wyobraź sobie, jak mogliby-śmy przekazać taki obiekt do obiektów, które go potrzebują, gdyby nie mógł on byćzmienną globalną. W jaki sposób dokonalibyśmy refaktoryzacji tego programu? Czy ist-nieją odpowiedzialności, które moglibyśmy wydzielić ze zbiorów klas, aby ograniczyć ichglobalny zasięg?

Jeśli znajdziesz globalną zmienną, która rzeczywiście jest używana we wszystkichmiejscach, oznacza to, że w Twoim kodzie nie ma żadnego podziału na warstwy. Zajrzyjdo rozdziałów 15., „Cała moja aplikacja to wywołania API”, i 17., „Moja aplikacja nie mastruktury”.

Przypadek straszliwych zależności dyrektyw include

C++ był moim pierwszym językiem zorientowanym obiektowo i muszę przyznać, że czu-łem się bardzo dumny, kiedy nauczyłem się wielu jego szczegółów i zawiłości. Zdomi-nował on branżę, gdyż w swoim czasie stanowił niezwykle praktyczne rozwiązanie wieludokuczliwych problemów. Komputery są zbyt wolne? Proszę bardzo, oto język, w którymwszystko jest opcjonalne. Możesz mieć całą wydajność czystego C, jeśli będziesz używaćtylko jego funkcji. Nie możesz namówić swoich ludzi do korzystania z języka zorientowa-nego obiektowo? Proszę bardzo, oto kompilator C++; w kodzie C możesz dopisać fragmentw C++ i uczyć się zorientowania obiektowego w trakcie programowania.

Chociaż C++ był przez pewien czas bardzo popularny, w końcu ustąpił miejsca narzecz Javy oraz paru innych, nowszych języków. W pewnym stopniu przyczyną była ko-nieczność zachowania wstecznej zgodności z C, ale o wiele większy wpływ wywarł wymóguproszczenia pracy z językami programowania. Zespoły pracujące w C++ regularnieprzekonywały się, że domyślna konfiguracja tego języka nieszczególnie sprawdza się pod-czas konserwacji i że muszą poza nią wykraczać, aby system był elastyczny i podatny nawprowadzanie zmian.

Jeden z aspektów C++, wywodzący się z C, który jest szczególnie kłopotliwy, to sposób,w jaki jedna część programu dowiaduje się o innej części. W Javie i C#, gdy klasa w jednympliku musi skorzystać z klasy w drugim pliku, stosujemy import lub posługujemy się dy-rektywą using, aby definicja klasy stała się dostępna. Kompilator szuka tej klasy i sprawdza,czy była już ona kompilowana. Jeśli nie, kompiluje ją. Jeżeli klasa była już kompilowana,

Poleć książkęKup książkę

142 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

kompilator odczytuje ze skompilowanego pliku niewielki fragment, pobierając tylko tyleinformacji, ile jest potrzebnych do zagwarantowania, że wszystkie metody wymaganeprzez nową klasę znajdą się na miejscu.

Kompilatory C++ zazwyczaj nie stosują tego typu optymalizacji. Jeśli w C++ klasamusi coś wiedzieć o innej klasie, deklaracja tej drugiej klasy (w innym pliku) jest dołączanaw formie tekstowej do pliku, który potrzebuje tych informacji. Taki proces może byćpowolny. Kompilator musi powtórnie przeanalizować deklarację i zbudować jej wewnętrznąreprezentację za każdym razem, kiedy ją napotyka. Co gorsza, mechanizm dołączaniajest podatny na nadużycia. Plik może dołączać plik, który dołącza kolejny plik itd.W przypadku projektów, przy których programiści nie unikali takiego rozwiązania, nie-trudno o znalezienie małych plików, które koniec końców dołączają tysiące wierszy kodu.Programiści zastanawiają się, dlaczego kompilacja trwa tak długo, ale ponieważ instrukcjedołączające są rozsiane po całym systemie, trudno jest wskazać konkretny plik i zrozu-mieć, dlaczego zajmuje to tyle czasu.

Można odnieść wrażenie, że czepiam się C++, ale tak nie jest. To ważny język i stwo-rzono w nim niewiarygodnie dużo kodu, ale poprawna z nim praca wymaga szczególnejstaranności.

Uzyskanie instancji klasy C++ w jarzmie testowym może być trudne w przypadkucudzego kodu. Jeden z problemów, które prawie od razu napotykamy, to zależności na-główkowe. Które pliki nagłówkowe są nam potrzebne, aby w jarzmie testowym utwo-rzyć klasę?

Oto część deklaracji obszernej klasy C++ o nazwie Scheduler. Zawiera ona ponad 200metod, ale tutaj pokazałem jakieś 5 z nich. Nie dość, że klasa ta jest ogromna, to jeszczecharakteryzuje się silnymi i złożonymi zależnościami od wielu innych klas. Jak mogliby-śmy poddać klasę Scheduler testom?

#ifndef SCHEDULER_H#define SCHEDULER_H

#include "Meeting.h"#include "MailDaemon.h"...#include "SchedulerDisplay.h"#include "DayTime.h"

class Scheduler{public: Scheduler(const string& owner); ~Scheduler();

void addEvent(Event *event); bool hasEvents(Date date); bool performConsistencyCheck(string& message); ...};#endif

Poleć książkęKup książkę

PRZYPADEK STRASZLIWYCH ZALEŻNOŚCI DYREKTYW INCLUDE 143

Poza innymi elementami klasa Scheduler korzysta też z plików Meeting, MailDemon,Event, SchedulerDisplay i DayTime. Gdy chcemy utworzyć testy dla obiektów klasyScheduler, najprostsze, co możemy zrobić, to próba utworzenia ich w tym samym katalogu,w nowym pliku o nazwie SchedulerTests. Dlaczego chcemy mieć testy w tym samymkatalogu? Przy obecności preprocesora tak będzie łatwiej. Jeżeli w projekcie nie użyto ście-żek, umożliwiających dołączanie plików w spójny sposób, czekałoby nas sporo pracy,gdybyśmy chcieli umieścić nasze testy w innych katalogach.

#include "TestHarness.h"#include "Scheduler.h"

TEST(create,Scheduler){ Scheduler scheduler("fred");}

Jeśli utworzymy plik i po prostu wpiszemy taką deklarację obiektu do testu, natkniemysię na problem z dołączaniem plików nagłówkowych. Aby skompilować klasę Scheduler,musimy mieć pewność, że kompilator i konsolidator wiedzą wszystko na temat ele-mentów, które są tej klasie potrzebne, a także wszystko na temat elementów potrzebnychtym elementom itd. Na szczęście system przekazuje nam sporą liczbę komunikatów o błę-dach i szczegółowo nas o nich informuje.

W prostszych przypadkach plik Scheduler.h zawiera wszystko, co jest nam potrzebnedo utworzenia klasy Scheduler, ale w niektórych przypadkach plik nagłówkowy nie obej-muje wszystkiego. Aby utworzyć obiekt i z niego korzystać, będziemy musieli dołączyćdodatkowe pliki.

Moglibyśmy po prostu skopiować wszystkie dyrektywy #include z pliku źródłowegoz klasą Scheduler, ale być może nie będziemy potrzebować każdego z tych plików. Najlep-szą taktyką byłoby dodawanie ich po jednym i podjęcie decyzji, czy ta konkretna za-leżność jest nam tak naprawdę niezbędna.

W idealnym świecie najprostsze byłoby dołączanie po kolei wszystkich potrzebnychnam plików, aż przestałyby się pojawiać błędy kompilacji, lecz takie postępowanie mogłobydoprowadzić do zamętu w naszym kodzie. Jeśli istnieje długi ciąg przechodnich zależności,zapewne w rezultacie dołączylibyśmy o wiele więcej, niż naprawdę potrzebujemy. Nawetjeśli ciąg zależności nie jest zbyt długi, mogłoby się okazać, że zależymy od elementów,z którymi praca w jarzmie testowym jest bardzo trudna. W naszym przykładzie klasaSchedulerDisplay jest jedną z takich zależności. Nie pokazuję tego tutaj, ale sięga do niejkonstruktor w klasie Scheduler. Tego rodzaju zależności możemy się pozbyć następująco:

#include "TestHarness.h"#include "Scheduler.h"

void SchedulerDisplay::displayEntry(const string& entyDescription){}

TEST(create,Scheduler)

Poleć książkęKup książkę

144 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

{ Scheduler scheduler("fred");}

Wprowadziliśmy w tym miejscu alternatywną definicję SchedulerDisplay::displayEntry. Niestety, kiedy tak zrobimy, będziemy potrzebować odrębnej kompilacji przy-

padków testowych zawartych w tym pliku. W programie możemy mieć tylko po jednejdefinicji każdej metody w klasie SchedulerDisplay, w związku z czym potrzebny nambędzie oddzielny program dla naszych testów tej klasy.

Na szczęście w pewnym stopniu będziemy mogli wielokrotnie korzystać z fałszy-wek, które utworzyliśmy w ten sposób. Zamiast umieszczać definicje klas — takich jakSchedulerDisplay — bezpośrednio w pliku testowym, możemy zamieścić je w oddziel-nym pliku, z którego będzie można skorzystać w plikach z testami:

#include "TestHarness.h"#include "Scheduler.h"#include "Fakes.h"

TEST(create,Scheduler){ Scheduler scheduler("fred");}

Po kilkakrotnym wykonaniu takiego zabiegu tworzenie instancji klasy C++ w jarzmietestowym stanie się całkiem łatwe i machinalne. Istnieje jednak kilka dość poważnychwad tego rozwiązania. Musimy utworzyć odrębny program i tak naprawdę nie usuwamyżadnych zależności na poziomie języka, w związku z czym kod nie staje się przejrzystszy,gdy się ich pozbywamy. Co gorsza, powielone definicje, które umieszczamy w pliku testo-wym (w naszym przykładzie SchedulerDisplay::displayEntry), muszą być utrzymywanetak długo, jak długo zachowujemy dany zestaw testów na swoim miejscu.

Technikę tę zachowuję dla przypadków, w których mam do czynienia z bardzo dużąklasą, wykazującą poważne problemy z zależnościami. Nie jest to technika, z której możnakorzystać często lub w prosty sposób. Jeśli dana klasa ma zostać rozbita wraz z upływemczasu na dużą liczbę mniejszych klas, korzystne może okazać się utworzenie dla niej od-rębnego programu testowego. Może on odgrywać rolę poligonu doświadczalnego, słu-żącego do rozległej refaktoryzacji. Wraz z upływem czasu, gdy coraz więcej klas zostaniepoddanych testom, będzie można pozbyć się tego programu.

Przypadek cebulowego parametru

Lubię proste konstruktory. Naprawdę. To wspaniałe, kiedy decydujesz się na utworzenieklasy, po czym po prostu wpisujesz wywołanie konstruktora i otrzymujesz sympatyczny,żywy, działający i gotowy do użycia obiekt. W wielu przypadkach tworzenie obiektówmoże być jednak trudne. Każdy obiekt powinien zostać skonfigurowany we właściwymstanie — stanie, który przygotuje go do dodatkowych zadań. W wielu przypadkach ozna-

Poleć książkęKup książkę

PRZYPADEK CEBULOWEGO PARAMETRU 145

cza to, że musimy mu udostępnić inne obiekty, które także muszą być poprawnie skonfi-gurowane. Obiekty te podczas konfiguracji mogą wymagać jeszcze innych obiektówi w rezultacie dochodzimy do tworzenia obiektów potrzebnych do utworzenia obiektówpotrzebnych do utworzenia obiektów potrzebnych do utworzenia parametru dla kon-struktora klasy, którą chcemy poddać testom. Obiekty wewnątrz innych obiektów — wy-gląda to jak jakaś wielka cebula. Oto przykład tego rodzaju problemu.

Mamy klasę wyświetlającą obiekt typu SchedulingTask:

public class SchedulingTaskPane extends SchedulerPane{ public SchedulingTaskPane(SchedulingTask task) { ... }}

Aby ją utworzyć, musimy przekazać jej obiekt SchedulingTask, ale w celu jego utwo-rzenia potrzebujemy skorzystać z jego jedynego konstruktora:

public class SchedulingTask extends SerialTask{ public SchedulingTask(Scheduler scheduler, MeetingResolver resolver) { ... }}

Jeśli odkryjemy, że do utworzenia obiektów Scheduler i MeetingResolver potrze-bujemy kolejnych obiektów, prawdopodobnie zaczniemy rwać sobie włosy z głowy. Jedyne,co nas powstrzymuje od pogrążenia się w skrajnej rozpaczy, to fakt, że musi istnieć przy-najmniej jedna klasa, która nie potrzebuje jako argumentów obiektów innej klasy. W prze-ciwnym razie system nigdy nie dałby się skompilować.

Sposób na poradzenie sobie z taką sytuacją polega na bliższym zastanowieniu się nadtym, co chcemy osiągnąć. Musimy napisać testy, ale czego tak naprawdę potrzebujemy odparametrów przekazywanych do konstruktora? Jeśli na potrzeby naszych testów nie po-trzebujemy niczego, to możemy przekazać wartość pustą (126). Jeżeli potrzebujemy tylkopewnego, elementarnego zachowania, możemy z najbliższej zależności wyodrębnićinterfejs (361) albo wyodrębnić implementer (356) i skorzystać z otrzymanego interfejsuw celu utworzenia fałszywego obiektu. W naszym przypadku najbliższą zależnością klasySchedulingTaskPane jest SchedulingTask. Jeśli uda nam się utworzyć fałszywy obiekt klasySchedulingTask, będziemy w stanie utworzyć instancję klasy SchedulingTaskPane.

Niestety, klasa SchedulingTask dziedziczy po klasie SerialTask, a jedyne, co robi, toprzesłonięcie kilku metod chronionych; wszystkie metody publiczne znajdują się w klasieSerialTask. Czy w odniesieniu do klasy SchedulingTask możemy skorzystać z wyodręb-niania interfejsu (361)? A może powinniśmy zastosować tę technikę także do klasySerialTask? W Javie nie musimy tego robić. Możemy utworzyć interfejs dla klasySchedulingTask, który zawiera również metody klasy SerialTask.

Poleć książkęKup książkę

146 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

Nasza wynikowa hierarchia wygląda jak na rysunku 9.3.

Rysunek 9.3. Interfejs SchedulingTask

W tym przypadku mamy szczęście, że korzystamy z Javy. Niestety, w C++ nie mamymożliwości obsługiwania takich przypadków, gdyż w języku tym nie istnieją samodzielneinterfejsy. Są one zwykle implementowane w klasach zawierających jedynie funkcje czystowirtualne. Gdyby przykład ten został przełożony na C++, interfejs SchedulingTask stałbysię klasą abstrakcyjną, ponieważ dziedziczy funkcje wirtualne po klasie SchedulingTask.Aby utworzyć instancję klasy SchedulingTask, musielibyśmy udostępnić w niej ciało me-tody run(), które odsyłałoby do metody run() w klasie SerialTask. Na szczęście będzieto łatwe do wykonania. Oto jak teraz wygląda kod:

class SerialTask{public: virtual void run(); ...};

class ISchedulingTask{public: virtual void run() = 0; ...};

class SchedulingTask : public SerialTask, public ISchedulingTask{public: virtual void run() { SerialTask::run(); }};

W dowolnym języku, w którym możemy tworzyć interfejsy lub klasy działające jak in-terfejsy, możemy z nich systematycznie korzystać w celu usuwania zależności.

Poleć książkęKup książkę

PRZYPADEK ZALIASOWANEGO PARAMETRU 147

Przypadek zaliasowanego parametru

Często, kiedy do konstruktorów przekazywane są parametry, które wchodzą nam w drogę,możemy ominąć ten problem, stosując wyodrębnianie interfejsu (361) lub wyodrębnianieimplementera (356). Czasami jednak takie rozwiązanie nie jest praktyczne. Spójrzmy nainną klasę z systemu pozwoleń na budowę, z którym mieliśmy do czynienia we wcze-śniejszym podrozdziale:

public class IndustrialFacility extends Facility{ Permit basePermit;

public IndustrialFacility(int facilityCode, String owner, OriginationPermit permit) throws PermitViolation { Permit associatedPermit = PermitRepository.GetInstance() .findAssociatedFromOrigination(permit);

if (associatedPermit.isValid() && !permit.isValid()) { basePermit = associatedPermit; } else if (!permit.isValid()) { permit.validate(); basePermit = permit; } else throw new PermitViolation(permit); } ...}

Chcielibyśmy utworzyć instancję tej klasy w jarzmie, ale na przeszkodzie stoi namkilka problemów. Jeden z nich polega na tym, że znowu mamy do czynienia z singletonem— PermitRepository. Możemy ominąć ten problem, stosując techniki, które poznaliśmywe wcześniejszym podrozdziale „Przypadek irytującej zależności globalnej”. Zanim jednakrozwiążemy ten problem, napotykamy kolejny. Uzyskanie źródłowego pozwolenia, któremusimy przekazać do konstruktora, jest trudne. Obiekty klasy OriginationPermit cechująsię okropnymi zależnościami. Pierwsze, co przychodzi mi na myśl to: „Aha, żeby ominąćtę zależność, zastosuję wobec klasy OriginationPermit wyodrębnianie interfejsu”, ale niejest to takie proste. Na rysunku 9.4 pokazano hierarchię obiektów klasy Permit.

Konstruktor IndustrialFacility przyjmuje obiekt klasy OriginationPermit i przecho-dzi do obiektu PermitRepository, aby zdobyć odpowiednie pozwolenie; w PermitRepositorykorzystamy z metody przyjmującej obiekt klasy OriginationPermit i zwracającej obiekttypu Permit. Jeśli repozytorium odnajdzie odpowiednie pozwolenie, zapisze je w poluPermit. Jeśli nie, zapisze w tym polu obiekt OriginationPermit. Moglibyśmy utworzyć in-terfejs dla klasy OriginationPermit, ale w niczym by nam to nie pomogło. Musielibyśmy

Poleć książkęKup książkę

148 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

Rysunek 9.4. Hierarchia obiektów klasy Permit

polu Permit przypisać interfejs IOriginationPermit, co by nie zadziałało — w Javie interfejsynie mogą dziedziczyć po klasach. Najbardziej oczywistym rozwiązaniem będzie utworzenieinterfejsów od samej góry aż po dół i zamiana pola Permit na IPermit. Rysunek 9.5 poka-zuje, jak to będzie wyglądać.

Rysunek 9.5. Hierarchia obiektów klasy Permit z wyodrębnionymi interfejsami

A fe! To absurdalnie dużo pracy i w ogóle nie podoba mi się kod, jaki byśmy otrzymali.Interfejsy świetnie nadają się do usuwania zależności, ale kiedy zbliżamy się do mo-mentu, gdy między klasami a interfejsami mamy już niemal relację „jeden do jednego”,projekt robi się zagracony. Nie zrozum mnie źle — gdy jesteśmy przyparci do muru,dobrze będzie zmierzać w stronę takiego projektu, ale jeśli istnieją inne możliwości, po-winniśmy je rozpatrzyć. Na szczęście je mamy.

Wyodrębnianie interfejsu (361) jest tylko jednym ze sposobów na usuwanie zależnościw odniesieniu do parametru. Czasami opłaca się zadać pytanie, dlaczego zależność jestniedobra. Czasem tworzenie obiektu jest uciążliwe. Niekiedy parametr wywołuje niepożą-dane efekty uboczne; może komunikuje się z systemem plików albo bazą danych. Innymrazem wykonywanie się kodu może zabierać zbyt dużo czasu. Kiedy stosujemy wyodręb-nianie interfejsu (361), możemy poradzić sobie z tymi wszystkimi problemami, chociażrobimy to, brutalnie odcinając jego połączenie z całą klasą. Jeśli problem kryje się tylkowe fragmentach klasy, możemy przyjąć inne podejście i przeciąć połączenie tylko z kłopo-tliwymi fragmentami.

Poleć książkęKup książkę

PRZYPADEK ZALIASOWANEGO PARAMETRU 149

Przyjrzyjmy się bliżej klasie OriginationPermit. Nie chcemy jej używać w teście, po-nieważ komunikuje się ona po kryjomu z bazą danych, kiedy każemy jej przeprowadzićautoryzację:

{ ... public void validate() { // po cz si z baz danych ... // pobierz informacj o autoryzacji ... // ustaw flag autoryzacji ... // zamknij baz danych ... }}

Nie chcemy tego robić w teście — musielibyśmy wprowadzić do bazy danych jakieśfałszywe wpisy, co zdenerwowałoby jej administratora. Gdyby się o tym dowiedział, przy-szłoby nam postawić mu obiad, ale i tak byłby poirytowany. I bez tego jego praca jestwystarczająco trudna.

Inną strategią, którą moglibyśmy przyjąć, jest utworzenie podklasy i przesłonięciemetody (398). Możemy utworzyć klasę o nazwie FakeOriginationPermit, która udostęp-nia metodę ułatwiającą zmianę flagi uwierzytelnienia. Następnie w podklasach mogli-byśmy przesłonić metodę validate i podczas testowania klasy IndustrialFacility prze-stawić flagę uwierzytelniania w taki sposób, jaki tylko będzie nam potrzebny. Oto całkiemdobry, pierwszy test:

public void testHasPermits() { class AlwaysValidPermit extends FakeOriginationPermit { public void validate() { // ustaw flag uwierzytelnienia becomeValid(); } };

Facility facility = new IndustrialFacility(Facility.HT_1, "b", new AlwaysValidPermit()); assertTrue(facility.hasPermits());}

W wielu językach możemy tworzyć takie klasy „w locie” za pomocą metod. Chociażnie lubię tego często robić w kodzie produkcyjnym, sposób ten jest całkiem wygodny pod-czas testowania. Bardzo prosto możemy tworzyć przypadki specjalne.

Tworzenie podklasy i przesłanianie metody (398) jest pomocne podczas usuwaniazależności dotyczących parametrów, ale czasami faktoryzacja metod w klasie nie jestidealnym rozwiązaniem tego problemu. Mieliśmy szczęście, że zależności, które namprzeszkadzały, były odizolowane w metodzie validate. W najgorszym przypadku są

Poleć książkęKup książkę

150 ROZDZIAŁ 9. NIE MOGĘ UMIEŚCIĆ TEJ KLASY W JARZMIE TESTOWYM

one splecione z potrzebną nam logiką, a my najpierw musimy wyodrębnić metody. Możeto być proste, gdy mamy narzędzie refaktoryzujące. Jeśli nie dysponujemy takim narzę-dziem, przydatne mogą okazać się niektóre z technik opisanych w rozdziale 22., „Muszęzmienić monstrualną metodę, lecz nie mogę napisać do niej testów”.

Poleć książkęKup książkę

Skorowidz

#define, 52#include, 53, 61

Aabstrakcyjna klasa nadrzędna, 386, 389Account, 135, 409AccountDetailFrame, 158, 161, 162, 163

po topornej refaktoryzacji, 163ACMEController, 91adaptacja parametru, 328

czynności, 331pomocne funkcje języka, 156ryzyko, 330

AddEmployeeCmd, 275, 281AddOpportunityFormHandler, 99, 100AddOpportunityXMLGenerator, 99addPermit, 138AddsEmployeeCmd, 279addText, 177adnotowanie listingów, 221

obrysowywanie bloków, 222wyodrębnianie metod, 222wyodrębnianie odpowiedzialności, 221zrozumienie skutków zmiany, 222zrozumienie struktury metody, 221

AGGController, 340algorytm pisania testów charakteryzujących, 196analiza rozmowy, 232

opisywanie projektów, 233rozbieżność między rozmową a kodem, 233

analiza skutków, 179punkty przechwycenia, 184wsparcie zintegrowanego środowiska

programistycznego, 166analizator reguł, 255AnonymousMessageForwarder, 111, 113

aplikacjabez struktury, 225jako wywołania API, 209

odpowiedzialność kodu, 213zidentyfikowanie obliczeniowego rdzenia

kodu, 213architekt, 225architektura systemu, Patrz struktura aplikacjiArithmeticException, 105arkusz z tekstem, 47ArtR56Display, 41asercje, 196aspekty, 177assertEquals, 69AsyncReceptionPort, 405automatyczna refaktoryzacja, 63, 64

bez testów, 65, 297długie metody, 297

BBankingServices, 368bezpieczeństwo, 331bezpieczne zmiany, 239biblioteka, 207

fałszywek, 241graficzna, 57

BillingStatement, 188, 189BindName, 338błędy, 200BondRegistry, 365budowanie systemu, 321buildMartSheet, 59

CC++, 141calculatePay(), 87CAsyncSslRec, 50

Poleć książkęKup książkę

418 SKOROWIDZ

CCAImage, 153cell, 58charakterystyka działania kodu, 213charakteryzowanie klas, 199

heurystyka, 199przekazywane informacje, 200znajdowanie błędów, 200

charakteryzowanie rozgałęzień, 202chwilowe sprzężenie, 85ciąg

skutków, 186zależności, 143

classpath, 55ClassReader, 169CLateBindingDispatchDriver, 338Command, 279, 282

z usuniętą duplikacją, 288, 289command/query separation, 161CommoditySelectionPanel, 297const, 176, 178, 179, 370ConsultantSchedulerDB, 99Coordinate, 177CppClass, 166, 170, 179CppUnitLite, 68CRC, 230createForwardMessage, 398CreditMaster, 123CreditValidator, 122cudzy kod, 10, 11

algorytm dodawania zmian, 36brak warstw abstrakcji, 330dołączanie, 209dylemat, 34identyfikacja miejsca zmian, 36język proceduralny, 239kontakt z większą społecznością, 322kopiowanie kodu, 108miejsca na wstawienie testów, 36motywacja do pracy, 322myślenie o skutkach, 170narzędzia pracy, 63objęcie testami, 103pisanie testów, 37podstawowa poprawność, 179praca nad kodem, 321programowanie sterowane testami, 109refaktoryzacja, 37reguły w bazie kodu, 179skutki zmian, 166

słabe przystosowanie do testowania, 47szukanie błędów, 195umieszczenie klasy w jarzmie testowym, 97usuwanie zależności, 35, 37utworzenie obiektu klasy, 122wprowadzanie zmian, 33wyodrębnianie metody, 414zaśmiecenie interfejsami, 330znajdowanie błędów, 200zniechęcenie, 322

CustomSpreadsheet, 59czas wprowadzenia zmiany, 75

długość, 95kiełkowanie

klasy, 80metody, 77

opakowywanieklasy, 88metody, 85

opóźnienie, 96usuwanie zależności, 97zrozumienie kodu, 95

czysty kod, 12

Ddane statyczne, 347db_update, 52declarations, 167Declarations, 170deklaracja

using, 154deklarowanie typu, 338dekorator, 89delegator instancji, 367delegowanie do klasy, 115delete, 132destruktor, 132

wirtualny, 357detailDisplay, 160diagramy, 230dispatchPayment(), 86, 87Display, 42długie metody, 293, 411

automatyczna refaktoryzacja, 297zmiana kolejności instrukcji, 297

nadawanie nazw wysokopoziomowymfragmentom metod, 298

narzędzia refaktoryzujące, 297

Poleć książkęKup książkę

SKOROWIDZ 419

przeniesienie do nowej klasy, 332przenoszenie metod, 299refaktoryzacja, 296ręczna refaktoryzacja, 300

gromadzenie zależności, 305wprowadzenie zmiennej rozpoznającej, 300wyłonienie obiektu metody, 306wyodrębniaj to, co znasz, 304

rodzaje, 294strategia, 307

bądź gotów na powtórne wyodrębnianie,309

szkieletyzuj metody, 307szukaj sekwencji, 307wyodrębniaj małe fragmenty, 309wyodrębniaj najpierw do bieżącej klasy,

308wyłonienie obiektu metody, 332wyodrębnianie

kodu, 297poleceń, 304

wyodrębnienia o niskiej liczbie powiązań, 304zachowanie głównej logiki, 305złożona logika, 300zmienne rozpoznające, 303

dodawaniefunkcji, 21, 24, 25nowej funkcjonalności, 103

do klasy, 83duplikaty, 103kiełkowanie, 103nowy kod, 77opakowywanie, 103oszacowanie czasu, 77poddawanie kodu testom, 103programowanie sterowane testami, 104umieszczanie w podklasach, 113uproszczenie parametru, 384usuwanie duplikatów, 109w jednym miejscu, 91wiele istniejących obiektów, 91zbiór własności, 114zmiany w wielu miejscach, 183

zachowaniado istniejących metod, 85

dołączanie kodu, 209DOMBuilder, 300dostęp do kodu, 151

dostrzeganie odpowiedzialności, 257double, 203draw(), 333drawPoint, 334drugi moment statystyczny, 107duplikaty, 103

eliminacja, 275usuwanie, 108

duże klasy, 253decyzje, które można zmienić, 259dezorientacja, 253edytuj i módl się, 254główna odpowiedzialność, 266grupowanie metod, 257kiełkowanie

klasy, 254metody, 254

po wyodrębnieniu klasy, 273przenoszenie kodu, 268refaktoryzacja, 254

szybka, 269wyodrębnianie klasy, 271

rozdzielanie interfejsu, 268skupienie na bieżącej pracy, 269strategia, 270taktyka, 270testowanie, 254ukryte metody, 258wewnętrzne relacje, 259wyodrębnianie klas, 259

wybór techniki, 270zasada rozdzielania interfejsów, 268zidentyfikowanie odpowiedzialności, 256

dylematjednorazowości, 208ograniczonego przesłaniania, 208

dynamiczna konsolidacja, 55dyrektywy

#include, 143include, 141kompilacji warunkowej, 52typedef, 406using, 141

dziedziczenie, 110problemy, 113wywołanie błędów, 116

Poleć książkęKup książkę

420 SKOROWIDZ

Eedytowanie kodu, 311

błędy, 314edytowanie jednego elementu naraz, 313programowanie w parach, 318superświadome, 312wsparcie kompilatora, 317zachowania, 312zachowywanie sygnatur, 314zmiany w metodach, 314

edytuj i módl się, 27edytuj-kompiluj-konsoliduj-testuj, 97Element, 174elements, 172elementy globalne, 340

ominięcie zależności, 396wytwórnia, 372

Employee, 88EndPoint, 40expectedMessage, 111ExternalRouter, 371

FFacility, 134FakeConnection, 125FakeDisplay, 42, 44FakeOriginationPermit, 149FakeTransactionLog, 362fałszowanie, 44fałszywa biblioteka, 375fałszywe klasy, 235fałszywe obiekty, 41, 145, 415

dwie strony, 44tworzenie, 124wspomaganie testowania, 43

fałszywki, 44, 129, 192biblioteka, 241korzystanie, 144

fasada, 267final, 156, 158, 170, 370firewall, 177firstMomentAbout, 105, 106FIT, 71fit.Fixture, 55fit.Parse, 55FitFilter, 54Fitnesse, 71

FocusWidget, 132form_command, 245formConnection, 401formStyles, 349Formula, 67FormulaCell, 58FormulaTest, 67forward, 243forwardMessage, 111Frame, 341Framework for Integrated Tests, 71frequently asked questions, 17FuelShare, 201funkcja

niewirtualna, 365dostęp przez interfejs, 366

opakowująca, 246wirtualna

C++, 353wywoływanie przesłoniętych funkcji, 402zastąpienie zmiennej instancji, 401

wolna, 343, 384, 415funkcje

buildMartSheet, 59db_update, 52form_command, 245formStyles, 349GetOption, 343ksr_notify, 241, 247leniwy getter, 354mart_key_send, 244PostReceiveError, 49, 61report_deposit, 409scan_packets, 241, 242send_command, 244SequenceHasGapFor, 384setOption, 343zastępowanie inną, 375

funkcjonalność, 386rozłożenie w klasach, 388

GGDIBrush, 333generate(), 82generateIndex, 171, 172getBodySize(), 284getDeadtime, 387getDeclaration(int index), 168

Poleć książkęKup książkę

SKOROWIDZ 421

getFrom, 113getFromAddress, 110, 115, 117getInstance, 134getInterface, 168, 181, 182getKSRStreams, 156getLastLine, 43getName, 167GetOption, 343getParameterForName, 329getSize, 283

przeniesienie metody, 284getter, 353, 396

czas życia, 355leniwy, 354

getValidationPercent, 122, 125, 126getValue, 184globalna wytwórnia, 372globalResultNotifier, 249główne zadanie, 254grafy, 220granica hermetyzacji, 191gromadzenie

zależności, 305zmian, 96

grupowanie metod, 257ćwiczenie grupowe, 258heurystyka, 257

grupy metod, 386

HhasGapFor, 383hermetyzacja, 182, 347

duże klasy, 254granica, 191referencji do wolnych funkcji, 344referencji globalnej, 247, 249, 251, 340

błędy kompilacji, 342czynności, 345fałszywe obiekty, 342fałszywki, 344funkcje nieskładowe, 343nazywanie klasy, 341nowa klasa, 341odseparowanie, 342refaktoryzacja, 342referencja do elementu składowego klasy,

342

rozpoczynanie, 343źródło opcji, 344

zmiennej globalnej, 317hierarchia obiektów klasy Permit, 148

z wyodrębnionymi interfejsami, 148hierarchia znormalizowana, 118HttpFileCollection, 156, 158HttpPostedFile, 156HttpPostedFileWrapper, 157HttpServletRequest, 328

Iidentyfikowanie

obliczeniowego rdzenia kodu, 213odpowiedzialności, 257

inne techniki, 269IHttpPostedFile, 157imadło programistyczne, 28import, 54index, 171IndustrialFacility, 147informacja zwrotna, 28, 29

algorytm dokonywania zmian w cudzymkodzie, 36

błyskawiczna, 96opóźnienie, 96pokrycie testami, 33przebudowywanie testu, 102szybkie uruchamianie testu, 102testowanie jednostkowe, 30testy wyższego poziomu, 32

initialize, 130InMemoryDirectory, 171instance, 370, 371instancja testowa, 355instrukcja warunkowa, 295int, 203interakcje w systemie, 261interfejs, 148

czysty, 357Display, 42IHttpPostedFile, 157IPermitRepository, 140komunikujący odpowiedzialności, 329MailService, 214MessageProcessor, 214nazywanie, 356, 362

Poleć książkęKup książkę

422 SKOROWIDZ

interfejsParameterSource, 329PointRenderer, 336SchedulingTask, 146

interpretery języków, 256InvalidBasisException, 105Inventory, 396InventoryControl, 189Invoice, 184, 188inwarianty, 199IPermitRepository, 140irytujący parametr, 121, 123Item, 187iteracje, 76

Jjarzmo testowania jednostkowego, 66jarzmo testowe, 30, 126, 415

CppUnitLite, 68duże klasy, 271inne platformy xUnit, 70JUnit, 66, 67kod zawierający singletony, 135NUnit, 70ogólne, 71

Fitnesse, 71Framework for Integrated Tests, 71

problemy z uruchamianiem metody, 151separowanie, 39tworzenie instancji klasy, 97, 121, 122, 334

C++, 144umieszczanie klasy, 183utworzenie obiektu, 122zmiana klasy, 97

jednostki behawioralne systemu, 30języki proceduralne, 239

a zorientowanie obiektowe, 247projektowanie obiektów, 251usuwanie zależności, 393wyodrębnianie zależności, 251zorientowane obiektowo rozszerzenia, 251

JUnit, 67, 122, 227architektura, 227zestaw obiektów, 68

Kkarta CRC, 230kiełkowanie, 103kiełkowanie klasy, 80, 83

czynności, 84długie metody, 293duże klasy, 254uproszczenie parametru, 384zalety i wady, 84

kiełkowanie metody, 77czynności, 79długie metody, 293duże klasy, 254przekazanie wartości pustej, 80przekształcenie w statyczną metodę

publiczną, 80stosowanie, 80zalety i wady, 80zastosowanie, 92

klasaabstrakcyjna, 146, 390biblioteczna, 207charakteryzowanie, 199duża, 253elementy globalne, 340finalna, 207, 215główne zadanie, 254interfejsowa, 344konwencja nazewnicza, 358, 363logiczna, 299nadawanie nazwy, 341nowa funkcjonalność, 84odpowiedzialność, 153, 192

pojedyncza, 254odrębny program testowy, 144odwzorowanie na zbiór koncepcji, 83panelowa, 298pojedyncze instancje, 137pomocnicza, 367produkcyjna, 102przekształcenie w interfejs, 89, 356reguły z rozdzielonymi

odpowiedzialnościami, 256rozbijanie na fragmenty, 191schemat funkcjonalności, 260skróty w nazwach, 289

Poleć książkęKup książkę

SKOROWIDZ 423

szablony, 405testowa, 68, 122

nazewnictwo, 235testowanie niezależnie od siebie, 137ukryta, 191wprowadzanie zmian, 99wzajemne zależności, 265zależność od interfejsu, 102zapieczętowana, 156, 208zbrylenie, 259zgodność z zasadą podstawienia Liskov, 117

klasyAccount, 135, 409AccountDetailFrame, 158, 161, 162, 163AddEmployeeCmd, 275, 281AddOpportunityFormHandler, 99, 100AddOpportunityXMLGenerator, 99AddsEmployeeCmd, 279AGGController, 340AnonymousMessageForwarder, 111ArtR56Display, 41AsyncReceptionPort, 405BankingServices, 368BillingStatement, 188, 189BondRegistry, 365CAsyncSslRec, 50CCAImage, 153ClassReader, 169CLateBindingDispatchDriver, 338Command, 279, 282CommoditySelectionPanel, 297ConsultantSchedulerDB, 99Coordinate, 177CppClass, 166, 170, 179CreditMaster, 123CreditValidator, 122Declarations, 170DOMBuilder, 300Element, 174Employee, 88EndPoint, 40ExternalRouter, 371Facility, 134FakeConnection, 125FakeDisplay, 42FakeOriginationPermit, 149FakeTransactionLog, 362FeeCalculator, 265fit.Fixture, 55

fit.Parse, 55FitFilter, 54FocusWidget, 132FormulaTest, 67Frame, 341FuelShare, 201GDIBrush, 333HttpFileCollection, 156, 158HttpPostedFile, 156HttpServletRequest, 328InMemoryDirectory, 171Inventory, 396InventoryControl, 189Invoice, 184, 188Item, 187LinkageNode, 359ListDriver, 213LoggingEmployee, 89LoginCommand, 279, 281, 283LogonCommand, 275MailForwarder, 110, 213MailingConfiguration, 115, 116MailReceiver, 213MailSender, 213MessageForwarder, 113, 398MessageRouter, 371MimeTesting, 398ModelNode, 357, 359NameObjectCollectionBase, 156NetworkBridge, 40Node, 359OffMarketTradeValidator, 391OpportunityItem, 100OptionSource, 344OriginationPermit, 147, 149OurHttpFileCollection, 157Packet, 346PageGenerator, 196PageLayout, 349Parser, 191PaydayTransaction, 361Permit, 148PermitRepository, 134, 138PointRenderer, 335PremiumRegistry, 365ProductionModelNode, 357QuarterlyReportTableHeaderGenerator, 82QuaterlyReportTableHeaderProducer, 82Renderer, 333

Poleć książkęKup książkę

424 SKOROWIDZ

klasyReservation, 260ResultNotifier, 248RGHConnection, 123RouteFactory, 373RSCWorkflow, 347RuleParser, 256, 258Sale, 41Scanner, 249ScheduledJob, 266Scheduler, 142, 387SchedulerDisplay, 143SchedulingServices, 388SchedulingTask, 145SerialTask, 145Session, 215ShippingPricer, 185StepNotifyController, 90StyleMaster, 349SymbolSource, 164Task, 254TermTokenizer, 258TestCase, 67, 68TestingAsyncSslRec, 50TestingExternalRouter, 372TestingMessageForwarder, 399TestResult, 381ToolController, 90TransactionLog, 361TransactionRecorder, 362WindowsOffMarketTradeValidator, 391

kodasercji, 67bez testów, 12testujący, 30zastany, 11

kod proceduralnya zorientowanie obiektowe, 247dodanie nowego zachowania, 244

funkcje wykonujące zadaniaobliczeniowe, 244

funkcje z wywołaniami zewnętrznymi, 246możliwości, 251pułapki zależności, 244przypadki zmian, 240, 241

kod produkcyjny, 125funkcje, 344zastępowanie zmiennej instancji, 132

kod testowy, 125, 235konwencje nazewnicze klas, 235lokalizacja testu, 236oddzielenie od kodu produkcyjnego, 237

komendy, 161komentarze, 79

kod do wyodrębnienia, 414kompilacja, 54kompilator, 317

C++, 142komponenty wielokrotnego użytku, 133konfiguracja, 116konsolidacja

dynamiczna, 55statyczna, 56

konsolidator, 54konstrukcyjne kłębowisko, 131konstruktor

IndustrialFacility, 147tworzenie obiektu, 377ukryte zależności, 128, 131zależność od obiektu, 129

konwencja kodowania, 208konwencje nazewnicze klas, 235

Fake, 235Test, 235Testing, 236

konwersjaproblemy, 204z liczb podwójnej precyzji na całkowite, 203

koszt rekompilacji, 98koszty metod, 97kryj i modyfikuj, 27ksr_notify, 241, 247

Llegacy code, 10leniwy getter, 354liczba powiązań, 304, 415liczby podwójnej precyzji, 202LinkageNode, 359lista

declarations, 167elements, 172

ListDriver, 213LoggingEmployee, 89LoginCommand, 279, 281, 283

Poleć książkęKup książkę

SKOROWIDZ 425

LogonCommand, 275lokalizacja testu, 236

a wdrażanie aplikacji, 237ograniczenia wdrożeniowe aplikacji, 236rozmiar kodu wdrożeniowego, 237

lokalizowanie zachowań, 118long, 203LONGS_EQUAL, 69

Łłączenie obiektów, 89

MMailForwarder, 213MailingConfiguration, 115, 116MailReceiver, 213MailSender, 213MailService, 214makeLoggedPayment, 86makra, 52

LONGS_EQUAL, 69TEST, 69

mart_key_send, 244martwy kod, 389mechanika zmian, 21

model spoinowy, 47narzędzia, 63praca z informacją zwrotną, 27rozpoznanie i separowanie, 39testy, 28unikanie zmian, 26w dużym systemie, 26w oprogramowaniu, 21w zachowaniu, 22zmiany w systemie, 27

mechanizmdołączania deklaracji klasy do pliku, 142refleksji, 67

menedżerobjaśniający, 353transakcji, 353

MessageForwarder, 113, 398MessageProcessor, 214MessageRouter, 371metaklasa, 347

metodaabstrakcyjna, 118, 357delegowanie do funkcji, 249funkcjonalność, 152klasy testowej, 67komenda a zapytanie, 161monstrualna, 293nazewnictwo, 381odpowiedzialność, 255pomocnicza, 347przesłanianie, 117punktowana, 294

sekcje, 295zmienne tymczasowe, 295

reguła użycia, 199szkieletyzacja, 307statyczna, 80, 346, 347

ograniczenie dostępu, 348zastosowanie, 367

testowalna, 153upublicznianie, 152wirtualna, 208, 357wysunięta, 295wytwórcza, 351zmiana na chronioną, 154

metodyaddPermit, 138addText, 177AnonymousMessageForwarder, 113BindName, 338calculatePay(), 87createForwardMessage, 398dispatchPayment(), 86, 87draw, 333draw(), 333drawPoint, 334firstMomentAbout, 105, 106formConnection, 401forwardMessage, 111generate(), 82generateIndex, 171, 172getBodySize(), 284getDeadtime, 387getDeclaration(int index), 168getFrom, 113getFromAddress, 110, 115, 117getInstance, 134getInterface, 168 181, 182

Poleć książkęKup książkę

426 SKOROWIDZ

metodygetKSRStreams, 156getLastLine, 43getName, 167getParameterForName, 329getSize, 283getValidationPercent, 122, 125, 126getValue, 184hasGapFor, 383initialize, 130instance, 370, 371makeLoggedPayment, 86newFixedYield, 366nthMomentAbout, 109parseExpression, 191pay(), 86, 89performCommand, 159, 161populate, 328QuaterlyReportGenerator, 81readToken, 170Recalculate, 58, 60replaceTrackListing, 23resetForTesting(), 136run(), 146, 337saveTransaction, 364scan(), 41secondMomentAbout, 107setDescription, 163setSnapRegion, 153setTestingInstance, 137setUp, 68showLine, 42, 44snap(), 153someMethod, 402suspend_frame, 340tearDown, 68, 373testEmpty, 67uniqueEntries, 78updateAccountBalance, 368updateBalance, 368validate, 149, 346, 347void testXXX(), 67WorkflowEngine, 351write, 275, 278, 279writeBody, 280, 286writeField, 278

miejscadeklaracji, 304na wstawienie testów, 36, 175zmian, 36

mieszana kompilacja, 395MimeTesting, 398minitesty integracyjne, 192model spoinowy, 47ModelNode, 357, 359modułowość, 48modyfikacje w testach, 184modyfikowanie

danych statycznych lub globalnych, 177obiektów przekazywanych jako parametry, 177

monstrualna metoda, 293mutable, 179

Nnadawanie nazw interfejsom, 362należyta staranność, 27NameObjectCollectionBase, 156narzędzia, 63

do automatycznej refaktoryzacji, 63wybór, 64zachowania, 64

do refaktoryzacjidługie metody, 297osobliwości, 204

do wyszukiwania skutków, 177jarzmo testowania jednostkowego, 66make, 102obiekty pozorowane, 65ogólne jarzmo testowe, 71

nazewnictwo, 356, 362metod, 381, 404

NetworkBridge, 40newFixedYield, 366niepowodzenia testów, 96niewykrywalne skutki uboczne, 158Node, 359notatki, 220nthMomentAbout, 109N-ty moment statystyczny, 107NUnit, 70

Oobiekt

metody, 332nie korzystający z innych obiektów, 158objaśniający, 351opakowujący, 157

Poleć książkęKup książkę

SKOROWIDZ 427

pozorowany, 45, 65, 415biblioteki, 329

wewnątrz innych obiektów, 145wprowadzanie zmian w zachowaniu, 403

obiekty, 144CustomSpreadsheet, 59Formula, 67globalResultNotifier, 249HttpPostedFileWrapper, 157ResultNotifier, 249SchedulingTask, 145testDigit, 67

obrysowywanie bloków, 222obsługiwanie parametrów, 176oddzielenie komendy od zapytania, 161odpowiedzialności, 213, 254

delegowanie, 267dostrzeganie, 257główna, 257, 266grupowanie metod, 257klasy, 199metoda prywatna, 258nazwa klasy, 255nazwy metod, 255schematy funkcjonalności, 265skuteczne rozdzielanie, 257strategia rozbijania klasy, 270wydzielona klasa, 264wyłonienie obiektu metody, 306

odwołania do klas bibliotecznych, 207odwracanie rozkładu, 10odzwierciedlanie i opakowywanie API, 215, 216

odseparowanie od klasy, 157OffMarketTradeValidator, 391ograniczenia w projektach, 208opakowywanie, 103opakowywanie klasy, 88

czynności, 92dodawanie zachowania, 91wzorzec dekoratora, 89zastosowanie, 92

opakowywanie metody, 85czynności, 87dodawanie nowej metody, 86umieszczanie zmienionej metody w starym

kodzie, 86wprowadzanie spoin, 87zalety i wady, 88zastosowanie, 92

operatordelete, 132zasięgu, 50

opowiadanie historii systemu, 226sesja JUnit, 227

opóźnienie, 96OpportunityItem, 100OpportunityProcessing, 100OptionSource, 344optymalizacja, 24

a nowa funkcjonalność, 24zmieniane elementy, 24

OriginationPermit, 147, 149ortogonalność, 290osłabianie ochrony dostępu, 155OurHttpFileCollection, 157

PPacket, 346PageGenerator, 196PageLayout, 349pakiety, 100

refaktoryzacja struktury, 101struktura, 101

papierowy widok, 399ParameterSource, 329parametry, 176

cebulowy, 144zaliasowany, 147

parametryzacja konstruktora, 129, 140, 377czynności, 379dodanie parametru, 378hermetyzacja, 182

referencji globalnej, 342kod, 129kopia konstruktora, 378nowy konstruktor, 379problemy, 130użycie zmiennych globalnych, 133w językach zezwalających na domyślne

argumenty, 379wady, 379zmienna instancji, 378zorientowanie obiektowe, 249

parametryzacja metody, 140, 381czynności, 382hermetyzacja referencji globalnej, 342użycie zmiennych globalnych, 133wada, 140

Poleć książkęKup książkę

428 SKOROWIDZ

parseExpression, 191Parser, 191pay(), 86, 89PaydayTransaction, 361performCommand, 159, 161PermitRepository, 134, 138, 147

testowa instancja klasy, 136pierwszy moment statystyczny, 104pisanie testów, 37, 195

charakteryzujących, 196heurystyka, 205klasy, 199

dla istniejącego kodu, 193dla metody, 199

prywatnej, 152dla rozgałęzienia, 202efekty, 75fałszywa klasa, 124interfejsy użytkownika, 66ogólna liczba, 198pod presją czasu, 76podczas opracowywania projektu, 47podczas wprowadzania zmian, 77programowanie sterowane testami, 109sprawdzających

funkcjonalność, 158klasę, 183

testowanie ukierunkowane, 200w punkcie przechwycenia, 187w punktach zwężenia, 192weryfikujących metody, 151

problemy, 151wyłonienie obiektu metody, 332

Platforma dla Testów Zintegrowanych, 71platforma testowa

CppUnitLite, 68Fitnesse, 71Framework for Integrated Tests, 71JUnit, 67NUnit, 70TestKit, 70xUnit, 66

platformy, 133plik nagłówkowy, 53podklasy

dla dwóch różnych opcji, 112testowe, 236, 348, 388, 415

PointRenderer, 335pojedyncze instancje, 137, 139

pokrycie testami, 33, 182pomocne funkcje języka, 155popagacja skutków, 176poprawianie błędów, 21, 24

a nowa funkcjonalność, 25populate, 328porządkowanie, 23PostReceiveError, 49, 61poszukaj decyzji, które można zmienić, 259poukrywane zależności, 81powielony kod, 275

generalizowanie zmiennej, 285klasa nadrzędna, 279opracowanie testów po refaktoryzacji, 278pierwsze kroki, 278po usunięciu duplikacji, 289

skupione metody, 290utrata elastyczności, 289wyłaniający się projekt, 290

przenoszenie metod, 281, 286refaktoryzacja, 275

rozpoczynanie, 278rozpoczęcie od małych kroków, 279zasada otwarte-zamknięte, 291

powtórne wyodrębnianie, 309pozorowany obiekt, 45pozostawianie zachowania, 25praca inicjalizacyjna konstruktorów, 351praca z informacją zwrotną, 27PremiumRegistry, 365preprocesor, 61, 143, 242

makr, 51przedefiniowanie tekstu, 410

preprocesowanie, 52proces budowania

alokowanie klas w pakietach, 102optymalizacja przeciętnego czasu budowy, 102pakiety, 100przyspieszenie, 100

w odniesieniu do kodu, 101rekompilacja, 98struktura pakietu, 101średni czas budowy, 98usuwanie zależności, 98, 102wyodrębnienie

implementera, 98, 99, 100interfejsu, 98

zmiana fizycznej struktury projektu, 98

Poleć książkęKup książkę

SKOROWIDZ 429

ProductionModelNode, 357programowanie ekstremalne, 14programowanie różnicowe, 110, 415

kluczowe aspekty projektu, 112korzystanie

z dziedziczenia, 113z klas, 115

tworzenie podklas, 112zalety, 118zasada podstawienia Liskov, 116zastosowanie, 116zbiór własności, 113zmiana konstruktora klasy, 113

programowanie sterowane testami, 104, 110, 415algorytm, 104

dla cudzego kodu, 110edytowanie kodu, 312kiełkowanie

klasy, 82metody, 78

kodcudzy, 109odpowiedzi, 105proceduralny, 244

kompilowanie testu, 105, 106, 107powodzenie testu, 105, 106, 108próbowanie, 322przypadek testowy kończący się

niepowodzeniem, 104, 105, 107usuwanie duplikatów, 105, 106, 108

programowanie w parach, 318projekt

przyjazny testowaniu, 47w kategorii obiektów, 230

przedefiniowanieszablonu, 405

czynności, 407udostępnianie alternatywnych definicji

metod, 407w C++, 407

tekstu, 409czynności, 410wady, 409

w locie, 409przeformułowanie kodu, 246przeglądarka refaktoryzująca kod, 63przekazanie parametru, 374

przekazywanie pustej wartości, 126kiełkowanie metody, 80parametr cebulowy, 145w kodzie produkcyjnym, 127

przenoszenie metod, 272, 346do abstrakcyjnej klasy nadrzędnej, 388

przepisanie systemu, 225przesłanianie metod, 117

wywoływanie, 402przesunięcie

funkcjonalności w górę hierarchii, 386czynności, 389

zachowania, 115zależności w dół hierarchii, 390

czynności, 392przewidywanie skutków, 166przywieranie statyczne, 367punkt dostępowy, 53, 54

spoiny konsolidacyjnej, 57spoiny obiektowe, 59

punkt przechwycenia, 184, 416dobieranie, 187ograniczony, 186śledzenie skutków w przód, 185wyższego poziomu, 187

punkt zmiany, 172, 187, 189, 416punkt zwężenia, 98, 184, 189, 190, 416

ocena projektu, 191pułapki, 192schemat funkcjonalności, 265testy, 193w kodzie proceduralnym, 240znajdowanie, 190

puste karty CRC, 230opis systemu do głosowania, 231wskazówki korzystania, 232

pusty obiekt, 127

QQuarterlyReportTableHeaderGenerator, 82QuaterlyReportGenerator, 81

Rrdzenna logika, 214readToken, 170Recalculate, 58, 60

Poleć książkęKup książkę

430 SKOROWIDZ

refaktoryzacja, 23, 35, 64, 411a nowa funkcjonalność, 24automatyczna, 63bez testów, 327charakterystyka elementów, 204długie metody, 296duże klasy, 254klasy, 144metody, 116podatność na błędy, 314powielony kod, 275, 278przygotowanie pola, 183, 188ręczna, 63, 204, 300rozdzielenie interfejsu, 269sparametryzowanie konstruktora, 130sprawdzenie zachowania, 204struktury pakietu, 101szybka, 222techniki usuwania zależności, 327testy, 201

wysokopoziomowe, 184toporna, 164upraszczanie typu parametru, 35w skali mikro, 312wsparcie, 63wspierająca testowanie, 327wydzielanie interfejsu, 35wyodrębnianie metody, 164zasada pojedynczej odpowiedzialności, 254zmieniane elementy, 24zmienne rozpoznające, 303

referencja globalna, 340zastąpienie getterem, 396

refleksje, 155reguły, 179

użycia metody, 199rekompilacja, 98

klas zależnych od klasy produkcyjnej, 102zapobieganie, 100

Renderer, 333replaceTrackListing, 23report_deposit, 409Reservation, 260

schemat funkcjonalności, 262resetForTesting(), 136ResultNotifier, 248ręczna refaktoryzacja, 300RGHConnection, 123

metody, 124

RouteFactory, 373rozgałęzienia

charakteryzowanie, 202rozkład kodu, 10rozmieszczanie testów, 36, 60, 151

rozległe zależności globalne, 140skutki zmian, 165

rozpoznanie, 39fałszywki, 45parametryzacja metody, 381spoina konsolidacyjna, 57warunków w metodzie, 300

RSCWorkflow, 347RuleParser, 256, 258run(), 146, 337rysunki, 220

SSale, 41

z hierarchią wyświetlaczy, 42sanity checks, 64saveTransaction, 364scan(), 41scan_packets, 241, 242Scanner, 249ScheduledJob, 266Scheduler, 142, 387

tworzenie klasy, 143Scheduler.h, 143SchedulerDisplay, 143SchedulingServices, 388SchedulingTask, 145, 146SchedulingTaskPane

tworzenie instancji klasy, 145schemat funkcjonalności, 260, 416

skupiska, 263zastosowanie, 261, 265

schemat skutków, 168, 172, 416a schemat funkcjonalności, 261dla klasy CppClass, 180dla systemu fakturującego, 188punkt zwężenia, 189rysowanie, 174upraszczanie, 169, 180wspólnie używane elementy, 190zastosowanie, 261znajdowanie ukrytych klas, 191

Poleć książkęKup książkę

SKOROWIDZ 431

sealed, 156, 158secondMomentAbout, 107sekwencje, 307send_command, 244separowanie, 39, 56

parametryzacja metody, 381spoina konsolidacyjna, 57uproszczenie parametru, 383

SequenceHasGapFor, 384seria testów, 172SerialTask, 145serwer listy mailingowej, 214Session, 215setDescription, 163setOption, 343setSnapRegion, 153setter, 132

zmieniający bazowe obiekty, 403setTestingInstance, 135, 137setUp, 68ShippingPricer, 185showLine, 42, 44siatka zabezpieczająca, 27singleton, 135, 137, 370, 372

osłabieniewartości, 136własności, 135, 138

powody używania, 137właściwości wspólne, 370zastępowanie, 370

składnia UML, 220skróty, 289skupienie na bieżącej pracy, 269skutki zmian, 165

adnotowanie listingów, 222hermetyzacja, 182lista elementów, 167myślenie o skutkach, 166narzędzia do wyszukiwania, 177ograniczanie, 180określanie miejsca testów, 175po użyciu danych globalnych i statycznych, 176propagacja, 176, 177punkt przechwycenia, 184schemat skutków, 168szukanie, 177śledzenie w przód, 171upraszczanie schematów, 180w języku C++, 178

wyciąganie wniosków z analizy, 179znajomość języka programowania, 177

słowa kluczowe, 176Smalltalk, 63snap(), 153someMethod, 402spoiny, 48, 49, 251, 416

konsolidacyjne, 54, 61, 345, 416kod proceduralny, 241środowisko testowe i produkcyjne, 58

obiektowe, 51, 58, 61, 252, 416właściwości, 247wprowadzenie delegatora instancji, 367

preprocesowe, 51, 61, 345punkt dostępowy, 53, 57rodzaje, 51w języku zorientowanym obiektowo, 58w kodzie proceduralnym, 240właściwy wybór, 61wprowadzanie podczas dodawania

funkcjonalności, 87zastosowania, 51

statyczne części klasy, 347statyczny setter, 370StepNotifyController, 90strategia, 270

strukturalne ustępstwa dla długich metod,307

String, 167struktura aplikacji, 225

analiza rozmowy, 232architekt, 225diagramy, 230historia systemu, 226obraz całości, 226prosty obraz, 227przeszkody poznania, 225puste karty CRC, 230wzrost złożoności, 229zachowanie nienaruszonej architektury, 226znajdowanie nowych abstrakcji, 229

StyleMaster, 349supersede, 404superświadome edytowanie, 312suspend_frame, 340SymbolSource, 164system

bazujący na API, 209dobrze utrzymywany a system obcy, 95konserwowany, 95

Poleć książkęKup książkę

432 SKOROWIDZ

szablonyparametryzacja, 407przedefiniowanie, 405w C++, 407

szkicowanie fragmentów projektu, 221szkieletyzacja metody, 307szukanie

błędów, 195sekwencji, 307

szwy, 297szybka refaktoryzacja, 222, 269

zagrożenia, 223

Śśledzenie skutków, 176śledzenie w przód, 171

seria testów, 172

Ttaktyka, 270Task, 254tearDown, 68, 373TermTokenizer, 258TEST, 69TestCase, 68testDigit, 67testEmpty, 67TESTING, 54, 242TestingAsyncSslRec, 50TestingExternalRouter, 372TestingMessageForwarder, 399TestKit, 70testowanie, 66

alternatywne funkcje, 44automatyczne, 195cudzego kodu, 51fałszywe obiekty, 41, 43fałszywki, 44jednocześnie kilku zmian, 183jednostkowe, 29

grupowanie, 31testowanie w izolacji, 30xUnit, 66

języków .NET, 70klas, 192klasy Scheduler, 142logiki, 245

metodyprywatnej, 152, 258publicznej, 152statycznej, 346

obiektów, 377obiekty pozorowane, 45odwołania do biblioteki graficznej, 56podstawianie innej wersji klasy, 55regresyjne, 28tworzenie odrębnej biblioteki dla klasy, 56ukierunkowane, 200

wyodrębnianie metody, 303uruchamiane edycją, 312uruchamianie metody bez wywoływania

funkcji, 49wiązanie nazw, 338wyodrębnianie klas, 48zmiana metody w chronioną, 60zmienianych metod, 154zmienne globalne, 135

TestResult, 381testy, 28

a automatyczna refaktoryzacja, 64automatyczne, 195czas trwania, 96dla klas, 33dla metod ukrytych, 152dokumentujące, 198dołączenie pliku, 243informacje zwrotne, 29integracyjne, 31konstrukcyjne, 122mieszanie z kodem źródłowym, 242modyfikowalnych fragmentów kodu, 247na wyższym poziomie, 34pisanie, 37poczytalności, 64podejrzane, 198pokrywające, 184pokrywanie kodu, 33praca inicjalizacyjna konstruktorów, 351słonecznego dnia, 204specyfikujące, 195spodziewane wartości, 198umieszczanie, 33usuwanie zależności, 35utrzymujące, 195wykonywane ręcznie, 195wykrywające zmianę, 28

Poleć książkęKup książkę

SKOROWIDZ 433

wysokopoziomowe, 184wyższego poziomu, 32zmiany w kodzie, 34

testy charakteryzujące, 165, 192, 196, 198, 416grupę klas, 188konwersja, 204śledzenie w przód, 171

testy duże, 192czas wykonywania, 31lokalizacja błędów, 30, 31pokrycie, 31problemy, 30

testy jednostkowe, 30, 416cechy, 31, 32wolne, 32zagrożenia, 192

thing, 354ToolController, 90TransactionLog, 361TransactionRecorder, 362, 363tworzenie

indeksu, 172instancji klasy

C++, 144w jarzmie testowym, 121

obiektów, 144w konstruktorach, 351, 401

zmiennych globalnych, 135tworzenie podklasy i przesłanianie metody, 128, 398

automatyczna refaktoryzacja, 299czynności, 400fałszywy singleton, 139niewykrywalne skutki uboczne, 162ostrożność, 399papierowy widok, 399parametr zaliasowany, 149stosowanie, 390

typedef, 406, 408

Uudostępnianie setterów, 403ukryta metoda, 152, 258ukryta zależność, 128ulepszanie projektu, 23umieszczanie klasy w jarzmie testowym, 121

irytująca zależność globalna, 133irytujący parametr, 121parametr cebulowy, 144

parametru zaliasowanego, 147parametryzacja konstruktora, 131ukryta zależność, 128zależności dyrektyw include, 141

UML, 220, 230unikanie zmian, 26uniqueEntries, 78updateAccountBalance, 368updateBalance, 368uproszczenie parametru, 383

czynności, 385typu parametru, 35

upublicznienie metody, 152statycznej, 315, 332, 346, 347

czynności, 348przekształcanie metody oryginalnej

na statyczną, 347dostęp do kodu, 151

uruchamianie metody w jarzmie testowymadaptacja parametru, 156niewykrywalne skutki uboczne, 158osłabianie ochrony dostępu, 155pomocne funkcje języka, 155problemy, 151ukryta metoda, 152upublicznanie metody, 152

using, 141, 154usuwanie

duplikatów, 108, 109, 291generalizowanie zmiennej, 285zasada otwarte-zamknięte, 291

nieużywanego kodu, 223usuwanie zależności, 35, 37, 39, 51

cel, 330czas wprowadzenia zmiany, 97efekty, 75lokalnych, 349na potrzeby testów, 315od elementów globalnych, 340od typów, 353opakowywanie parametru, 329osłabianie ochrony dostępu, 155patrzenie naprzód, 329preprocesor, 242programowanie w parach, 318rozpoznanie, 39separowanie, 39

Poleć książkęKup książkę

434 SKOROWIDZ

usuwanie zależnościtechniki, 325

adaptacja parametru, 328hermetyzacja referencji globalnej, 340parametryzacja konstruktora, 377parametryzacja metody, 381przedefiniowanie szablonu, 405przedefiniowanie tekstu, 409przesunięcie funkcjonalności w górę

hierarchii, 386przesunięcie zależności w dół hierarchii,

390uproszczenie parametru, 383upublicznienie metody statcznej, 346utworzenie podklasy i przesłonięcie

metody, 398uzupełnianie definicji, 338wprowadzenie delegatora instancji, 367wprowadzenie statycznego settera, 370wyłonienie obiektu metody, 332wyodrębnienie i przesłonięcie gettera, 353wyodrębnienie i przesłonięcie metody

wytwórczej, 351wyodrębnienie i przesłonięcie

wywołania, 349wyodrębnienie implementera, 356wyodrębnienie interfejsu, 328, 361zastąpienie funkcji wskaźnikiem do

funkcji, 393zastąpienie referencji globalnej getterem,

396zastąpienie zmiennej instancji, 401zastępowanie biblioteki, 375

tworzenie interfejsu, 146tworzenie podklasy i przesłanianie metody, 149w C++, 407w językach proceduralnych, 393w kodzie proceduralnym, 239wiele zmian w jednym miejscu, 183wprowadzanie więcej interfejsów i klas, 102zachowywanie sygnatur, 315znajdowanie klas, 55związanych z parametrami, 328

utrzymanie zachowania, 25ryzyko, 25

utworzenieabstrakcji, 385podklasy i przesłanianie metody, 138

uzupełnianie definicji, 338czynności, 339osobny plik wykonywalny, 339zestawy definicji, 339

uzyskanie źródłowego pozwolenia, 147

Vvalidate, 149, 346, 347ValueCell, 58void testXXX(), 67

Wwariacje w systemach, 118wartości zwrotne, 177wewnętrzne relacje, 259wiązanie nazw, 338widok papierowy, 399wiele zmian w jedym miejscu, 183

punkty przechwycenia, 184wyższego poziomu, 187

punkty zwężenia, 191pułapki, 192

wielokrotne użycie, 133kodu, 207

WindowsOffMarketTradeValidator, 391wirtualna funkcja, 61własności, 115wnioski z analizy skutków, 179WorkflowEngine, 351wprowadzenie

delegatora instancji, 367czynności, 368

statycznego settera, 136, 140, 370czynności, 374globalna wytwórnia, 372hermetyzacja referencji globalnej, 342wyodrębnienie interfejsu, 372

write, 275, 278, 279writeBody, 280, 286writeField, 278

przeniesienie metody, 283wskaźniki do funkcji, 246, 393

deklaracje, 393usuwanie zależności, 393

wsparcie kompilatora, 156, 317pomocne funkcje języka, 157przenoszenie metod, 272, 334

Poleć książkęKup książkę

SKOROWIDZ 435

wykonywane kroki, 317wyodrębnianie metod, 365, 412zastępowanie referencji, 406zastosowanie, 318zastrzeżenia stosowania, 272zmiana referencji w aplikacji, 139

wsparcie zintegrowanego środowiskaprogramistycznego w analizie skutków, 166

wstępna refaktoryzacja, 314wybór metody do testowania, 165

myślenie o skutkach, 166narzędzia do wyszukiwania skutków, 177propagacja skutków, 176śledzenie w przód, 171upraszczanie schematów skutków, 180wnioski z analizy skutków, 179

wydorębnianie metodyzestaw testów weryfikujących, 411

wydzielenieinterfejsu, 35klas, 327

wyłonienie obiektu metody, 306, 332czynności, 337dostęp do kodu, 151odmiany, 336publiczny konstruktor, 333schemat, 336zachowywanie sygnatur, 316zmienne instancji, 333

wyodrębniaj to, co znasz, 304wyodrębnianie bazujące na

odpowiedzialnościach, 215, 216wyodrębnianie i przesłanianie gettera, 131, 351, 353

czas życia gettera, 355czynności, 355leniwy getter, 354menedżer transakcji, 353wady, 355

wyodrębnianie i przesłanianie metodyfabrycznej, 131

ukryte zależności konstruktora, 131wytwórczej, 351

czynności, 352możliwości zastosowania, 351

wyodrębnianie i przesłanianie wywołania, 349czynności, 350kod po wyodrębnieniu, 349użycie zmiennych globalnych, 134wyodrębnianie metody, 350

wyodrębnianie implementera, 356czynności, 358klasy w hierarchii dziedziczenia, 359opakowywanie klasy, 89parametr

cebulowy, 145zaliasowany, 147

przekazywanie instancji klasy do obiektu, 132w klasie

ConsultantSchedulerDB, 99OpportunityItem, 100

wyodrębnienie interfejsu, 140zależności podczas budowania, 98, 99, 100, 102

wyodrębnianie interfejsu, 98, 129, 328, 361automatyczne wsparcie refaktoryzacji, 361czynności, 365i funkcji niewirtualnych, 365Java, 145klasy w hierarchii dziedziczenia, 360nadawanie nazw interfejsom, 362opakowywanie klasy, 89osłabienie ochrony konstruktora, 372parametr

cebulowy, 145zaliasowany, 147, 148

przekazywanie instancji klasy do obiektu, 132stopniowe wyodrębnianie, 361tworzenie

fałszywego obiektu, 124instancji klasy, 334

wycięcie metod, 361względem singletona, 139zależności podczas budowania, 102

wyodrębnianie klasa dziedziczenie, 272bez przeprowadzania testów, 271, 272duże klasy, 271refaktoryzacja, 259

wyodrębnianie koducele, 297

wyodrębnianie metod, 88, 114, 222, 411automatyczne narzędzia, 299błędy, 315

konwersji typu, 304czynności, 411do bieżącej klasy, 308długie metody, 296gromadzenie zależności, 305liczba powiązań, 304

Poleć książkęKup książkę

436 SKOROWIDZ

wyodrębnianie metodmałe fragmenty kodu, 304, 309możliwe błędy, 300powtórne wyodrębnianie, 309proste, 297przykład w Javie, 412rozdzielanie zadań, 159typ węzła, 301wprowadzenie zmiennej rozpoznającej, 300wyłonienie obiektu metody, 306zachowanie sygnatur, 271zestaw przypadków testowych, 300zmienne instancji, 301

wyodrębnianie odpowiedzialności, 221wyodrębnianie różnic między metodami, 281wysunięcia, 295wzorzec

dekoratora, 89, 91projektowy singleton, 134, 135, 370

konstruktor klasy singletona, 137leniwy getter, 354prywatny konstruktor, 372

pustego obiektu, 127, 330

XxUnit, 66

cechy, 66inne platformy, 70prostota i ukieruknowanie, 66

Zzachowanie, 22

charakterystyka, 196dodawanie, 22

do istniejących metod, 85metody, 23w kodzie proceduralnym, 244

narzędzia refaktoryzujące, 64pozostawienie, 25, 196przesuwanie do klasy, 115systemu, 199testowanie pozostawienia, 113testy

charakteryzujące, 198słonecznego dnia, 204

usuwaniebiblioteka ze szczątkową funkcją, 61

zastąpienie w miejscu spoiny, 51zmiana, 22

zachowanie sygnatur, 87, 130, 215, 248, 271,314, 346adaptacja parametru, 330wyłonienie obiektu metody, 333zastosowanie, 316

zagmatwana logika, 199zależności, 34

biblioteczne, 207dyrektyw include, 141fałszywe obiekty, 41globalne, 133

rozdzielanie odpowiedzialnościw aplikacji, 141

zmiana na pole w obiekcie, 140zmiana na zmienną tymczasową, 140

kodu od interfejsu, 101między klasami, 39nagłówkowe, 142oddzielanie od innych części klasy, 390pisanie testu, 37podczas budowania, 98poukrywane, 81separowanie, 56tworzeniowe, 81usuwanie, 39zasada odwrócenia, 101

zapytania, 161zasada

hermetyzacji, 125odwrócenia zależności, 101otwarte-zamknięte, 291podstawienia Liskov, 116pojedynczej odpowiedzialności, 115, 254, 266

na poziomie implementacji, 270naruszenie, 266

rozdzielania interfejsów, 267, 268zasoby, 137zastępowanie, 272

biblioteki, 375a hermetyzacja referencji globalnej, 343czynności, 376

dyspozytora, 371funkcji wskaźnikiem do funkcji, 393

czynności, 395zalety, 395

Poleć książkęKup książkę

SKOROWIDZ 437

obiektu, 377, 381referencji globalnej getterem, 396

czynności, 397zachowania, 403zmiennej instancji, 131, 351, 401

czynności, 404konstrukcyjne kłębowisko, 132setter, 132w Javie, 132

zbiór własności, 113zbrylenie, 259zezwolenia, 155zmiany

architektury, 225funkcjonalne, 312nazwy klasy, 116wymagań, 9strukturalne, 318typów, 318w klasie, 77, 99, 383w kodzie bez poddawania istniejących klas

testom, 77w metodach, 152, 328

edytowanie kodu, 314zmiany w oprogramowaniu, 21, 73

bezpieczne zmiany, 239czas, 95długie metody, 293dodawanie funkcji, 21instancja klasy w jarzmie testowym, 121irytujący parametr, 121kiełkowanie

klasy, 80metody, 77

opakowywanieklasy, 88metody, 85

optymalizacja, 24pisanie testów, 195poprawianie błędów, 21powielony kod, 275powody wprowadzania, 21problemy z uruchamianiem metody

w jarzmie testowym, 151ryzyko podczas edycji kodu, 311skutki zmian, 165ukryta zależność, 128ulepszanie projektu, 23

utrzymanie zachowania, 25wiele zmian w jedym miejscu, 183wielokrotne odwołania do czyjejś biblioteki, 209wybór metody do testowania, 165wywołania API, 209zależności biblioteczne, 207zrozumienie kodu, 219

zmiany w systemie, 27, 76dobrze utrzymanym, 95edytuj i módl się, 27kryj i modyfikuj, 27należyta staranność, 27siatka zabezpieczająca, 27testowanie regresyjne, 28

zmienneinstancji, 306, 332

wprowadzenie gettera, 353zastępowanie, 401

globalne, 135, 396singletony, 137szukanie, 141

testowe, 300tymczasowe, 78rozpoznające, 300

charakteryzowanie klas, 199konwersja automatyczna, 204sesja refaktoryzacji, 303testy dla rozgałęzienia, 202wyodrębniaj to, co znasz, 305

zorientowanie obiektowe, 230, 247, 250hermetyzacja referencji globalnej, 247programy proceduralne, 250spoiny obiektowe, 247zastępowanie biblioteki, 375

zrozumienie, 95kodu, 219

adnotowanie listingów, 221notatki i rysunki, 220pozyskiwanie wiedzy, 219szybka refaktoryzacja, 222usuwanie nieużywanego kodu, 223

skutków zmiany, 222struktury metody, 221

Źźródłowe pozwolenie, 147

Poleć książkęKup książkę

438 SKOROWIDZ

Poleć książkęKup książkę