Tytuł oryginału: Working Effectively with Legacy Code · Nie bierze jednak żadnej...
-
Upload
truongkien -
Category
Documents
-
view
226 -
download
0
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ę