Inżynieria oprogramowania w ujęciu obiektowym. UML, wzorce projektowe i Java

Sprawdź, jak sprawnie i bezbłędnie projektować systemy informatyczne! Czym jest inżynieria oprogramowania? Jak zapanować

8,899 152 27MB

Polish Pages [877] Year 2011

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

Inżynieria oprogramowania w ujęciu obiektowym. UML, wzorce projektowe i Java

Citation preview

pr

1? 1I

I

Bernd Bruegge, Allen H. Dutoit

Inżynieria oprogramowania w ujęciu obiektowym U ML, wzorce projektowe i Java Sprawdź, jak sprawnie i bezbłędnie projektować systemy informatyczne! Czym jest inżynieria oprogramowania? Jak zapanować nad wszystkimi aspektami procesu projektowania? Jak wygląda cykl życia oprogramowania?

Helion

Dla Goega, Toby'ego i Clary. -B.B

Dla mojej kochanej rodziny: Vicky, Eleni, Anny Marii, Michelle i Chrisa. — A.H.D

Spis treści

Przedmowa

17

Wstęp

19

Podziękowania

31

CZĘŚĆ I

Zaczynamy

33

Rozdział 1.

Wprowadzenie do inżynierii oprogramowania

35

1.1. 1.2.

36 38 38 40 41 42 43 44 46 46 47 48 48 49 50 51 51 53 53 54 54 55 55 56 56 56 57

1.3.

1.4.

1.5.

Wprowadzenie: niepowodzenia w inżynierii oprogramowania Czym jest inżynieria oprogramowania? 1.2.1. Modelowanie 1.2.2. Rozwiązywanie problemów 1.2.3. Pozyskiwanie wiedzy 1.2.4. Racjonalizacja Podstawowe koncepcje inżynierii oprogramowania 1.3.1. Uczestnicy i role 1.3.2. Systemy i modele 1.3.3. Produkty 1.3.4. Aktywności, zadania i zasoby 1.3.5. Wymagania funkcyjne i pozafunkcyjne 1.3.6. Notacje, metody i metodologie Aktywności inżynierii oprogramowania 1.4.1. Zbieranie wymagań 1.4.2. Analiza 1.4.3. Projekt systemu 1.4.4. Projektowanie obiektów 1.4.5. Implementowanie 1.4.6. Testowanie Zarządzanie tworzeniem oprogramowania 1.5.1. Komunikacja 1.5.2. Zarządzanie racjonalizacją 1.5.3. Zarządzanie konfiguracją oprogramowania 1.5.4. Zarządzanie projektem 1.5.5. Cykl życiowy oprogramowania 1.5.6. Podsumowanie

6

Spis treści

1.6. 1.7. 1.8.

Rozdział 2.

Modelowanie w języku UML 2.1. 2.2.

2.3.

2.4.

2.5. 2.6.

Rozdział 3.

Analiza przypadku — system ARENA Literatura uzupełniająca Ćwiczenia

Wprowadzenie Ogólnie o UML 2.2.1. Diagramy przypadków użycia 2.2.2. Diagramy klas 2.2.3. Diagramy interakcji 2.2.4. Diagram stanów 2.2.5. Diagramy aktywności Podstawowe koncepcje modelowania 2.3.1. Systemy, modele i widoki 2.3.2. Typy danych, abstrakcyjne typy danych i instancje 2.3.3. Klasy, klasy abstrakcyjne i obiekty 2.3.4. Klasy zdarzeniowe, zdarzenia i komunikaty 2.3.5. Modelowanie zorientowane obiektowo 2.3.6. Falsyfikacja i prototypowanie UML — głębszy wgląd 2.4.1. Diagramy przypadków użycia 2.4.2. Diagramy klas 2.4.3. Diagramy interakcji 2.4.4. Diagramy stanów 2.4.5. Diagramy aktywności 2.4.6. Organizacja diagramów 2.4.7. Rozszerzenia diagramów Literatura uzupełniająca Ćwiczenia

57 58 59

63 64 65 65 65 67 67 68 69 69 72 73 75 76 77 78 79 86 95 98 101 104 106 107 108

Organizacja projektu i komunikacja

113

3.1. 3.2. 3.3.

114 115 119 119 122 124 126 128 128 135 138 146 146 146

3.4.

3.5.

Wstęp — katastrofa Ariane O projekcie ogólnie Koncepcje organizacyjne projektu 3.3.1. Organizacja projektów 3.3.2. Role w realizacji projektu 3.3.3. Zadania i produkty 3.3.4. Harmonogramy Koncepcje komunikacyjne projektu 3.4.1. Komunikacja planowa 3.4.2. Komunikacja pozaplanowa 3.4.3. Mechanizmy komunikacyjne Aktywności organizacyjne 3.5.1. Dołączanie do zespołu 3.5.2. Dołączanie do infrastruktury komunikacyjnej

7 Spis treści

3.6. 3.7.

CZĘŚĆ II Rozdział 4.

3.5.3. Udział w zebraniach zespołu 3.5.4. Organizacja przeglądów Literatura uzupełniająca Ćwiczenia

147 149 151 152

Zmagania ze złożonością

155

Zbieranie wymagań

157

4.1. 4.2. 4.3.

158 159 161 161 162 164 165

4.4.

4.5.

4.6.

4.7. 4.8.

Wstęp: przykłady problemów z użytecznością O zbieraniu wymagań ogólnie Koncepcje zbierania wymagań 4.3.1. Wymagania funkcyjne 4.3.2. Wymagania pozafunkcyjne 4.3.3. Kompletność, spójność, jednoznaczność i poprawność 4.3.4. Realizm, weryfikowalność i identyfikowalność 4.3.5. Inżynieria pierwotna, inżynieria wtórna i inżynieria interfejsu Aktywności związane ze zbieraniem wymagań 4.4.1. Identyfikacja aktorów 4.4.2. Identyfikacja scenariuszy 4.4.3. Identyfikacja przypadków użycia 4.4.4. Doskonalenie przypadków użycia 4.4.5. Identyfikacja relacji między aktorami a przypadkami użycia 4.4.6. Początkowa identyfikacja obiektów modelu analitycznego 4.4.7. Identyfikacja wymagań pozafunkcyjnych Zarządzanie zbieraniem wymagań 4.5.1. Negocjowanie specyfikacji z klientem: metoda Joint Application Design 4.5.2. Zarządzanie identyfikowalnością 4.5.3. Dokumentowanie zbierania wymagań Analiza przypadku — system ARENA 4.6.1. Wstępna deklaracja problemu 4.6.2. Identyfikacja aktorów i scenariuszy 4.6.3. Identyfikacja przypadków użycia 4.6.4. Doskonalenie przypadków użycia i identyfikacja relacji 4.6.5. Identyfikacja wymagań pozafunkcyjnych 4.6.6. Wnioski Literatura uzupełniająca Ćwiczenia

165 166 167 169 171 173 176 179 182 183 185 187 188 190 190 192 195 198 204 204 205 207

Spis treści

8

Rozdział 5.

Analiza wymagań

211

5.1. 5.2. 5.3.

212 212 214 214 215 216

5.4.

5.5.

5.6.

5.7. 5.8.

Rozdział 6.

Wstęp: złudzenie optyczne O analizie wymagań ogólnie Koncepcje analizy wymagań 5.3.1. Analityczny model obiektowy i modele dynamiczne 5.3.2. Obiekty encji, obiekty brzegowe i obiekty sterujące 5.3.3. Generalizacja i specjalizacja Aktywności analizy wymagań: od przypadków użycia do obiektów 5.4.1. Identyfikacja obiektów encji 5.4.2. Identyfikacja obiektów brzegowych 5.4.3. Identyfikacja obiektów sterujących 5.4.4. Odwzorowywanie przypadków użycia w obiekty za pomocą diagramów sekwencji 5.4.5. Modelowanie interakcji między obiektami za pomocą kart CRC 5.4.6. Identyfikacja skojarzeń 5.4.7. Identyfikacja agregacji 5.4.8. Identyfikacja atrybutów 5.4.9. Modelowanie zachowania poszczególnych obiektów uzależnionego od ich stanu 5.4.10. Modelowanie relacji dziedziczenia między obiektami 5.4.11. Przeglądy modelu analitycznego 5.4.12. Podsumowanie analizy Zarządzanie analizą wymagań 5.5.1. Dokumentowanie analizy wymagań 5.5.2. Przydzielanie odpowiedzialności 5.5.3. Komunikacja w związku z analizą wymagań 5.5.4. Iteracje modelu analitycznego 5.5.5. Uzgodnienie modelu analitycznego z klientem Analiza przypadku — system ARENA 5.6.1. Identyfikacja obiektów encji 5.6.2. Identyfikacja obiektów brzegowych 5.6.3. Identyfikacja obiektów sterujących 5.6.4. Modelowanie interakcji między obiektami 5.6.5. Weryfikacja i konsolidacja modelu analitycznego 5.6.6. Wnioski Literatura uzupełniająca Ćwiczenia

217 218 220 222 224 228 228 231 232 233 234 235 236 237 238 239 240 241 243 245 245 250 251 252 254 256 258 258

Projektowanie systemu — dekompozycja na podsystemy

263

6.1. 6.2. 6.3.

264 266 267

Wstęp: projekt mieszkania O projektowaniu systemu ogólnie Koncepcje projektowania systemu

9 Spis treści

6.4.

6.5. 6.6.

Rozdział 7.

6.3.1. Podsystemy i klasy 6.3.2. Usługi i interfejsy podsystemów 6.3.3. Sprzężenie i spoistość 6.3.4. Warstwy i partycje 6.3.5. Style architektoniczne Aktywności projektowania systemu: od obiektów do podsystemów 6.4.1. Punkt wyjścia: model analityczny systemu planowania podróży 6.4.2. Identyfikowanie celów projektowych 6.4.3. Identyfikowanie podsystemów Literatura uzupełniająca Ćwiczenia

268 270 271 275 279 288 288 290 294 296 297

Projekt systemu: realizacja celów projektowych

301

7.1. 7.2. 7.3. 7.4.

302 303 304 306

7.5.

7.6.

7.7. 7.8.

Wstęp: przykład redundancji O aktywnościach projektowania systemu ogólnie Koncepcje: diagramy wdrażania UML Aktywności realizacji celów projektowych 7.4.1. Odwzorowywanie podsystemów w procesory i komponenty 7.4.2. Identyfikowanie trwałych danych i ich przechowywanie 7.4.3. Definiowanie założeń kontroli dostępu 7.4.4. Projektowanie globalnego przepływu sterowania 7.4.5. Identyfikowanie usług 7.4.6. Identyfikowanie warunków granicznych 7.4.7. Weryfikowanie projektu systemu Zarządzanie projektowaniem systemu 7.5.1. Dokumentowanie projektu systemu 7.5.2. Przydzielanie odpowiedzialności 7.5.3. Komunikacja w projektowaniu systemu 7.5.4. Iteracje projektowania systemu Analiza przypadku — system ARENA 7.6.1. Identyfikowanie celów projektowych 7.6.2. Identyfikowanie podsystemów 7.6.3. Odwzorowanie podsystemów w procesory i komponenty 7.6.4. Identyfikowanie i przechowywanie trwałych danych 7.6.5. Definiowanie założeń kontroli dostępu 7.6.6. Projektowanie globalnego przepływu sterowania 7.6.7. Identyfikowanie usług 7.6.8. Identyfikowanie warunków granicznych 7.6.9. Wnioski Literatura uzupełniająca Ćwiczenia

306 309 312 319 321 323 326 328 328 330 331 333 334 335 336 337 339 340 341 343 345 347 348 348

10

Rozdział 8.

Spis treści

Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych 8.1. 8.2. 8.3.

8.4.

8.5.

8.6.

8.7. 8.8.

Wstęp: wpadki produkcyjne O projektowaniu obiektów ogólnie Koncepcja wielokrotnego wykorzystywania — dziedziczenie, delegowanie i wzorce projektowe 8.3.1. Obiekty aplikacyjne i obiekty realizacyjne 8.3.2. Dziedziczenie implementacyjne i dziedziczenie specyfikacyjne 8.3.3. Delegowanie 8.3.4. Zasada zastępowania Liskov 8.3.5. Delegowanie i dziedziczenie we wzorcach projektowych Wybór wzorców projektowych i gotowych komponentów 8.4.1. Hermetyzacja przechowywania danych za pomocą wzorca projektowego Most 8.4.2. Hermetyzacja niekompatybilnych komponentów za pomocą wzorca projektowego Adapter 8.4.3. Hermetyzacja kontekstu za pomocą wzorca projektowego Strategia 8.4.4. Hermetyzacja platformy za pomocą wzorca projektowego Fabryka abstrakcyjna 8.4.5. Hermetyzacja przepływu sterowania za pomocą wzorca projektowego Polecenie 8.4.6. Hermetyzacja hierarchii za pomocą wzorca projektowego Kompozyt 8.4.7. Heurystyki wyboru wzorców projektowych 8.4.8. Identyfikowanie i przystosowywanie frameworków aplikacyjnych Zarządzanie wykorzystywaniem gotowych rozwiązań 8.5.1. Dokumentowanie wykorzystywania gotowych rozwiązań 8.5.2. Przydzielanie odpowiedzialności Analiza przypadku — system ARENA 8.6.1. Zastosowanie wzorca projektowego Fabryka abstrakcyjna 8.6.2. Zastosowanie wzorca projektowego Polecenie 8.6.3. Zastosowanie wzorca projektowego Obserwator 8.6.4. Wnioski Literatura uzupełniająca Ćwiczenia

353 354 355 359 359 360 363 364 364 367 368 371 373 376 377 378 379 381 386 388 389 390 390 392 393 393 394 395

11 Spis treści

Rozdział 9.

Projektowanie obiektów: specyfikowanie interfejsów

399

9.1. 9.2. 9.3.

400 401 403 403 403

9.4.

9.5.

9.6.

9.7. 9.8.

Wstęp: kolej miejska i tramwaje O specyfikowaniu interfejsów ogólnie Koncepcje specyfikowania interfejsów 9.3.1. Implementator, ekstender i użytkownik klasy 9.3.2. Typy, sygnatury i widzialność 9.3.3. Kontrakty: niezmienniki, warunki wstępne i warunki końcowe 9.3.4. Język OCL (Object Constraint Language) 9.3.5. Kolekcje OCL: zbiory, wielozbiory i ciągi 9.3.6. Kwantyfikatory OCL: forAll() i exists() Aktywności specyfikowania interfejsów 9.4.1. Identyfikowanie brakujących atrybutów i operacji 9.4.2. Specyfikowanie typów, sygnatur i widzialności 9.4.3. Specyfikowanie warunków wstępnych i warunków końcowych 9.4.4. Specyfikowanie niezmienników 9.4.5. Dziedziczenie kontraktów Zarządzanie projektowaniem obiektów 9.5.1. Dokumentowanie projektowania obiektów 9.5.2. Przydzielanie odpowiedzialności 9.5.3. Wykorzystywanie kontraktów w analizie wymagań Analiza przypadku — system ARENA 9.6.1. Identyfikowanie brakujących operacji w klasach TournamentStyle i Round 9.6.2. Specyfikowanie kontraktów dla klas TournamentStyle i Round 9.6.3. Specyfikowanie kontraktów dla klas KnockOutStyle i KnockOutRound 9.6.4. Wnioski Literatura uzupełniająca Ćwiczenia

Rozdział 10. Odwzorowywanie modelu na kod 10.1. 10.2. 10.3.

10.4.

Wstęp: Władca Pierścieni O odwzorowywaniu ogólnie Koncepcje odwzorowywania 10.3.1. Transformowanie modelu 10.3.2. Refaktoryzacja 10.3.3. Inżynieria postępująca 10.3.4. Inżynieria odwracająca 10.3.5. Zasady transformacji Aktywności odwzorowywania 10.4.1. Optymalizowanie modelu obiektowego 10.4.2. Odwzorowywanie skojarzeń na kolekcje

406 407 411 415 416 417 418 419 421 424 425 425 431 432 433 434 435 438 439 440 440

445 446 447 448 449 450 452 452 453 454 455 458

Spis treści

12

10.4.3. 10.4.4.

10.5.

10.6.

10.7. 10.8.

Odwzorowywanie kontraktów w wyjątki Odwzorowywanie modelu obiektowego w schematy bazy danych Zarządzanie transformacjami 10.5.1. Dokumentowanie transformacji 10.5.2. Przydzielanie odpowiedzialności Analiza przypadku — system ARENA 10.6.1. Statystyki systemu ARENA 10.6.2. Odwzorowywanie skojarzeń na kolekcje 10.6.3. Odwzorowywanie kontraktów w wyjątki 10.6.4. Odwzorowywanie modelu obiektowego w schemat bazy danych 10.6.5. Wnioski Literatura uzupełniająca Ćwiczenia

Rozdziałll. Testowanie 11.1. 11.2. 11.3.

11.4.

11.5.

11.6. 11.7.

CZĘŚĆ III

Wstęp: testowanie wahadłowców O testowaniu ogólnie Koncepcje związane z testowaniem 11.3.1. Usterki, błędne stany i awarie 11.3.2. Przypadki testowe 11.3.3. Namiastki testowe i sterowniki testowe 11.3.4. Poprawki Aktywności związane z testowaniem 11.4.1. Inspekcja komponentu 11.4.2. Testowanie użyteczności 11.4.3. Testowanie jednostkowe 11.4.4. Testowanie integracyjne 11.4.5. Testowanie systemu Zarządzanie testowaniem 11.5.1. Planowanie testów 11.5.2. Dokumentowanie testowania 11.5.3. Przydzielanie odpowiedzialności 11.5.4. Testowanie regresyjne 11.5.5. Automatyzacja testowania 11.5.6. Testowanie bazujące na modelach Literatura uzupełniająca Ćwiczenia

Zarządzanie zmianami

Rozdział 12. Zarządzanie racjonalizacją 12.1. 12.2.

Wstęp: przycinanie wędzonej szynki O racjonalizacji ogólnie

465 469 475 475 477 478 478 480 482 484 485 485 486

491 492 494 498 500 503 505 505 506 507 508 510 519 526 531 532 532 536 537 538 539 541 543

547 549 550 551

13 Spis treści

12.3.

12.4.

12.5.

12.6. 12.7.

Koncepcje racjonalizacji 12.3.1. CTC — system centralnego sterowania ruchem 12.3.2. Definiowanie problemów: zagadnienia 12.3.3. Eksploracja przestrzeni rozwiązań: propozycje 12.3.4. Wartościowanie elementów przestrzeni rozwiązań: kryteria i argumenty 12.3.5. Kolapsacja przestrzeni rozwiązań: rozstrzygnięcie 12.3.6. Implementowanie rozstrzygnięć: elementy działania 12.3.7. Przykłady modeli zagadnień i ich realizacje Aktywności racjonalizacji — od zagadnień do decyzji 12.4.1. Projekt systemu CTC 12.4.2. Kolekcjonowanie racjonalizacji w ramach zebrań 12.4.3. Asynchroniczne kolekcjonowanie racjonalizacji 12.4.4. Racjonalizacja dyskutowanych zmian 12.4.5. Rekonstruowanie racjonalizacji Kierownicze aspekty zarządzania racjonalizacją 12.5.1. Dokumentowanie racjonalizacji 12.5.2. Przypisywanie odpowiedzialności 12.5.3. Heurystyki komunikowania racjonalizacji 12.5.4. Modelowanie i negocjowanie zagadnień 12.5.5. Strategie rozwiązywania konfliktów Literatura uzupełniająca Ćwiczenia

Rozdział 13. Zarządzanie konfiguracją 13.1. 13.2. 13.3.

13.4.

Wstęp: samoloty O zarządzaniu konfiguracją ogólnie Koncepcje zarządzania konfiguracją 13.3.1. Elementy konfiguracji i agregaty CM 13.3.2. Wersje i konfiguracje 13.3.3. Żądania zmian 13.3.4. Promocje i emisje 13.3.5. Repozytoria i przestrzenie robocze 13.3.6. Schematy identyfikowania wersji 13.3.7. Zmiany i zbiory zmian 13.3.8. Narzędzia wspomagające zarządzanie konfiguracją Aktywności tworzące zarządzanie konfiguracją 13.4.1. Identyfikowanie elementów konfiguracji i agregatów CM 13.4.2. Zarządzanie promocjami 13.4.3. Zarządzanie emisjami 13.4.4. Zarządzanie gałęziami 13.4.5. Zarządzanie wariantami 13.4.6. Zarządzanie propozycjami zmian i ich implementowaniem

554 555 556 557 559 560 561 562 567 568 569 577 579 582 585 585 587 588 589 590 592 593

597 598 600 602 603 604 604 605 606 606 608 610 611 613 615 616 619 623 626

14

Spis treści

13.5.

13.6. 13.7.

Kierownicze aspekty zarządzania konfiguracją 13.5.1. Dokumentowanie zarządzania konfiguracją 13.5.2. Przypisywanie odpowiedzialności 13.5.3. Planowanie aktywności w ramach zarządzania konfiguracją 13.5.4. Integracja ciągła jako optymalizacja zarządzania promocjami i ich testowaniem Literatura uzupełniająca Ćwiczenia

Rozdział 14. Zarządzanie projektem 14.1. 14.2. 14.3.

14.4.

14.5.

14.6. 14.7.

Wstęp: uruchomienie misji STS-51L O zarządzaniu projektem ogólnie Koncepcje zarządzania projektem 14.3.1. Zadania i aktywności 14.3.2. Produkty, pakiety pracy i role 14.3.3. Struktura podziału pracy 14.3.4. Model zadań 14.3.5. Macierz kwalifikacji 14.3.6. Plan zarządzania projektem Aktywności klasycznego zarządzania projektem 14.4.1. Planowanie projektu 14.4.2. Organizowanie projektu 14.4.3. Kontrolowanie projektu 14.4.4. Kończenie projektu Aktywności „zwinnej" realizacji projektu 14.5.1. Planowanie projektu: wykazy zaległości produktu i przebiegu 14.5.2. Organizowanie projektu 14.5.3. Kontrolowanie projektu: dni robocze i wykresy wygaszania 14.5.4. Kończenie projektu: przeglądy przebiegów Literatura uzupełniająca Ćwiczenia

Rozdział 15. Cykl życiowy oprogramowania 15.1. 15.2.

Wstęp: nawigacja polinezyjska IEEE 1074: standard cykli życiowych 15.2.1. Procesy i aktywności 15.2.2. Modelowanie cyklu życiowego 15.2.3. Zarządzanie projektem 15.2.4. Prerealizacja 15.2.5. Realizacja — tworzenie systemu 15.2.6. Postrealizacja 15.2.7. Procesy integralne (międzyrealizacyjne)

627 627 628 629 630 632 633

637 638 639 646 646 646 648 649 650 651 653 654 659 665 671 673 674 675 675 677 677 679

683 684 688 688 690 690 691 692 693 694

15 Spis treści

15.3. 15.4.

15.5. 15.6.

Charakteryzowanie dojrzałości modeli cyklu życiowego Modele cyklu życiowego 15.4.1. Sekwencyjne modele ukierunkowane na aktywności 15.4.2. Iteracyjne modele ukierunkowane na aktywności 15.4.3. Modele ukierunkowane na encje Literatura uzupełniająca Ćwiczenia

Rozdział 16. Wszystko razem, czyli metodologie 16.1. 16.2. 16.3.

16.4.

16.5.

16.6. 16.7.

Dodatek A

Wstęp: pierwsze zdobycie K2 Środowisko projektu Zagadnienia metodologiczne 16.3.1. Ile planowania? 16.3.2. Ile powtarzalności? 16.3.3. Ile modelowania? 16.3.4. Ile procesów cyklu życiowego? 16.3.5. Ile kontroli i monitorowania? 16.3.6. Kiedy przedefiniować cele projektu? Spektrum metodologii 16.4.1. Metodologia Royce'a 16.4.2. Programowanie ekstremalne (XP) 16.4.3. Metodologie rugby Analizy przypadku 16.5.1. Projekt XP: ATRACT 16.5.2. Lokalny klient: FRIEND 16.5.3. Rozproszony projekt: JAMES 16.5.4. Podsumowanie analiz przypadku Literatura uzupełniająca Ćwiczenia

695 698 699 701 706 709 710

713 714 717 719 719 720 721 723 723 724 724 725 731 737 742 743 746 754 761 766 766

Dodatki

769

Wzorce projektowe

771

A. 1.

Fabryka abstrakcyjna (Abstract Factory) — hermetyzacja platformy A.2. Adapter (Adapter) — otoczka dla starszego kodu A.3. Most (Bridge) — podmiana implementacji A.4. Polecenie (Command) — hermetyzacja przepływu sterowania A.5. Kompozyt (Composite) — rekurencyjna reprezentacja hierarchii A.6. Fasada (Facade) — hermetyzacja podsystemów A.7. Obserwator (Observer) — oddzielenie encji od widoków A.8. Proxy (Proxy) — hermetyzacja kosztownych obiektów A.9. Strategia (Strategy) — hermetyzacja algorytmów A. 10. Heurystyki pomocne w wyborze wzorców projektowych

772 773 774 775 776 777 778 779 780 781

Spis treści

16

Dodatek B

Dodatek C

Objaśnienia haseł

783

B.l. B.2.

783 817

Terminologia Słownik terminów angielskich

Bibliografia

831

Skorowidz

847

Przedmowa

Jakieś dziesięć lat temu dowiedziałem się o kursie z zakresu inżynierii oprogramowania, prowadzonym przez Bernda Bruegge'a na Uniwersytecie Carnegie Mellon. Większość prowadzonych przez inne uniwersytety kursów o tej tematyce realizowana jest zwykle w ten sposób, że każda z niewielkich — trzy- lub czteroosobowych — grup otrzymuje do rozwiązania konkretny problem, w określonym czasie, powiedzmy, jednego miesiąca. I ciąg dalszy zazwyczaj wygląda tak, że jeden „wiodący" programista narzuca swą koncepcję całemu zespołowi — nie ma mowy o praktykowaniu umiejętności w zakresie sprawnej komunikacji, o rozwiązywaniu wątpliwości i niejasności projektowych, niepotrzebne stają się narzędzia do modelowania. Zadanie do rozwiązania jest wszak dość proste — by nie powiedzieć: rozrywkowe — i tego typu quasi-profesjonalne praktyki mają nawet duże szansę powodzenia. W rezultacie jednak studenci kończący semestralny kurs pozostają nieprzygotowani do zmagania się ze złożonością prawdziwych wyzwań, jakie wkrótce postawią przed nimi rzeczywiste problemy projektowe. Na kursie Bruegge'a było jednak inaczej: cała klasa zaangażowana była w realizację wspólnego projektu, jakim było stworzenie opartego na kwerendach systemu nawigacji dla miasta Pittsburgh. W projekcie tym wykorzystywano interaktywny system map, zrealizowany w ramach podobnego kursu w poprzednim semestrze; system ten, opracowany na użytek miejscowego departamentu planowania i służb zarządzających lokalnym portem lotniczym miał jednak tę bolączkę, że dane geograficzne oraz rozkłady jazdy lokalnych autobusów nie dość, że najeżone były błędami ortograficznymi, to na dodatek przechowywane były w kilku niezgodnych ze sobą formatach. Studenci jednak poradzili sobie z tymi niedostatkami, tworząc w ciągu semestru bardzo dobry projekt o rozmiarze kodu źródłowego w granicach 27 000 wierszy — cóż za znakomita różnica w porównaniu ze wspomnianą powyżej rutyną „rozrywkowych" projektów! I choć projektom tej skali daleko jeszcze do wielu olbrzymich przedsięwzięć programistycznych, to skala ta jest całkowicie wystarczająca, by docenić znaczenie strategii, organizacji i narzędzi w trudach zmagania się z zawiłościami i złożonością otaczającego nas świata. Studenci zgłębiali tajniki inżynierii oprogramowania w taki sposób — jedyny skuteczny — w jaki uczy się każdego rzemiosła: przez praktykę na bazie realnego świata. Treść tej książki stanowi odzwierciedlenie owej pragmatycznej filozofii tworzenia oprogramowania, postrzeganego jako dyscyplina inżynierska. Autorzy prezentują tę skomplikowaną tematykę w bardzo przystępnym ujęciu, ułatwiającym zrozumienie nawet najtrudniejszych jej aspektów — w ujęciu zorientowanym obiektowo, z wykorzystaniem UML. Czytelnicy znajdą tu obszerną dyskusję na temat zarówno technik modelowania, jak i rozwijania umiejętności w zakresie komunikacji interpersonalnej, tak istotnej dla osiągnięcia sukcesu. Kilka rozdziałów

18

Przedmowa

poświęconych jest też problematyce zarządzania zmianami — często lekceważonej i niedocenianej, choć w istocie towarzyszącej każdemu niemal niebanalnemu projektowi. Gdy się w to wszystko wczytać, docenia się z należytym respektem i bogactwo, i niesamowicie skomplikowany charakter inżynierii oprogramowania. Osobiście zbudowany jestem błyskotliwymi anegdotami urozmaicającymi treść książki. Dostarczają one zaskakujących nieraz analogii, nawiązujących do wielkich i małych — często subtelnych — problemów, jakim stawiać musi nieustannie czoła profesjonalny programista. Gdy co rusz znajduje się interesujące historie opisujące prymitywne techniki nawigacyjne Polinezyjczyków, perypetie towarzyszące pierwszemu wydaniu Władcy Pierścieni Tolkiena czy też zaskakujące wyjaśnienie przyczyny, dla której przycina się szynkę na końcach przed jej wędzeniem — wszystko to, a jakże, a propos zasadniczej tematyki — książka staje się już nie tylko cenną pozycją w bibliotece profesjonalisty, ale także wciągającą lekturą. Jim Rumbaugh

Wstęp

W zachodnich Himalajach, w paśmie Karakorum, majestatycznie wznosi się na wysokość 8611 m drugi co do wysokości szczyt Ziemi — K2. Przez himalaistów uważany jest za najtrudniejszy do zdobycia ośmiotysięcznik. Wyprawa na K2 trwa kilka miesięcy, zazwyczaj latem, gdy pogoda jest najbardziej sprzyjająca — choć nawet wówczas zamiecie śnieżne nie należą tu do rzadkości. Uczestnicy wyprawy taszczą ze sobą tysiące kilogramów wyposażenia — sprzęt wspinaczkowy, stosowne odzienie, namioty, żywność, aparaturę telekomunikacyjną i setki sztuk innego rozmaitego bagażu. Zaplanowanie takiej wyprawy wymaga sporo czasu i wysiłku, nie może odbyć się bez udziału dziesiątek pomocników. I najstaranniej nawet zaprojektowana ekspedycja musi nieuchronnie zmagać się z nagłymi i nieprzewidywalnymi wypadkami, jak lawiny, awarie sprzętu, zasłabnięcia i inne przypadłości zdrowotne — a to wymaga zdolności do szybkiego przystosowywania się do nowych sytuacji, poszukiwania doraźnych rozwiązań, a w skrajnych wypadkach rezygnacji ze szczytnego (nomen omen) celu. Na podstawie dotychczasowej historii szansę powodzenia takiej wyprawy ocenia się na mniej niż 40%. Ruch lotniczy na terenie USA monitorowany jest i zarządzany poprzez System Krajowej Przestrzeni Powietrznej (NAS — National Airspace System). System ten obejmuje 18 300 portów lotniczych, 21 centrów sterowania ruchem i ponad 460 wież kontrolnych. Do tego należy dodać ponad 34 000 urządzeń pomocniczych: radarów, przełączników telekomunikacyjnych, przekaźników radiowych, systemów komputerowych i wyświetlaczy. Zarządzająca tym wszystkim infrastruktura starzeje się, niestety, w szybkim tempie — dość wspomnieć, iż praca wzmiankowanych centrów sterowania opiera się na komputerach mainframe serii IBM 3083, wywodzących się jeszcze z początku lat 80. ubiegłego wieku. W 1996 roku rząd USA zapoczątkował program modernizacji infrastruktury NAS, obejmujący między innymi zastosowanie nawigacji satelitarnej, cyfrowej komunikacji pilotów z kontrolerami ruchu i generalną automatyzację wspomagania procesów decyzyjnych dotyczących wyboru tras, ustalania kolejności lądowania i tym podobnych. Jest zrozumiałe, iż tak drastyczna modernizacja nie może mieć wymiaru jednoaktowego, lecz dokonywać się musi w sposób przyrostowy. A to oznacza, że wprowadzając do infrastruktury nowe komponenty, należy zapewnić nie tylko ich niezawodne działanie, lecz także niezawodną współpracę z istniejącymi komponentami — i tak na przykład komunikacja między centrami sterowania a pilotami musi jeszcze przez jakiś czas odbywać się za pośrednictwem kanałów dwu generacji: analogowych i cyfrowych. Co więcej, wszystko to odbywać się musi w kontekście gwałtownego wzrostu dynamiki globalnej komunikacji lotniczej, której rozwój na przestrzeni najbliższych 1 0 - 1 5 lat można co najwyżej w przybliżeniu prognozować. Nie można zapominać, że poprzednie wysiłki administracji USA w tym kierunku nie zostały uwieńczone powodzeniem — projekt systemu o nazwie Advanced Automation

20

Wstęp

System (AAS) zawieszony został w roku 1994, czego bezpośrednią przyczyną okazały się problemy z oprogramowaniem, objawiające się przekraczaniem założonych harmonogramów 0 miesiące i lata oraz nadprogramowymi wydatkami budżetowymi sięgającymi miliardów dolarów. Oba przytoczone przykłady ilustrują sytuacje, gdy funkcjonowanie i tak już skomplikowanych systemów zderza się z nieprzewidywalnymi zmianami i powinno wyjść z tej konfrontacji obronną ręką. Wyzwania, jakie kreuje sama złożoność systemów, ewidentnie przekraczają możliwości pojedynczego człowieka, a pojawiające się dodatkowo nowe okoliczności zmuszają do wyjścia poza (niewystarczające już) rutynowe rozwiązania i poszukiwanie ad hoc nowych, adekwatnych do nowej sytuacji. Wymaga to sprawnego współdziałania wszystkich uczestników przedsięwzięcia — porażka w tym względzie przekłada się w prostej konsekwencji na klęskę całego projektu. Celem tej książki jest dostarczenie czytelnikom wiedzy o tym, jak skutecznie radzić sobie z takimi wyzwaniami.

Temat Dziedzina aplikacyjna przedsięwzięcia (planowanie ekspedycji wysokogórskiej, sterowanie ruchem lotniczym, obsługa transakcji finansowych, przetwarzanie tekstów) obejmuje zwykle multum różnych koncepcji, z których większość obca jest projektantom realizującym jego stronę informatyczną. Z kolei dziedzina realizacyjna (a więc całokształt technik programistycznych, narzędzia do projektowania interfejsu użytkownika, środki komunikacji bezprzewodowej, oprogramowanie pośredniczące, systemy zarządzania bazami danych, systemy przetwarzania transakcyjnego, urządzenia mobilne) okazuje się zwykle niedojrzała (lub nie w pełni dojrzała) do należytego odzwierciedlenia dziedziny aplikacyjnej, chociaż daje projektantom i programistom szeroki wachlarz technologii implementacyjnych, często konkurencyjnych względem siebie. W konsekwencji tego stanu rzeczy sam system staje się niezwykle złożony już na etapie wstępnego projektu, co wyraża się między innymi mnogością jego komponentów, bogatym zestawem używanych narzędzi i metod oraz licznością personelu zaangażowanego w jego realizację. W miarę jak projektanci programiści wprowadzani są w arkana dziedziny aplikacyjnej systemu przez przyszłych jego użytkowników, modyfikują sukcesywnie zestaw wymagań związanych z tym systemem. Gdy stają się odbiorcami nowych technologii lub doświadczają rozmaitych ograniczeń ze strony technologii już używanych, modyfikują sam projekt systemu 1 metody jego implementacji. Gdy z kolei użytkownicy żądają nowych cech funkcjonalnych, a kontrola jakości wynajduje wciąż nowe błędy, zmienia się sam system i towarzyszące mu produkty pomocnicze. W tym kontekście proces tworzenia aplikacji jawi się wyraźnie jako pasmo nieustających zmian. Owe zmiany, w połączeniu z olbrzymim stopniem komplikacji przedsięwzięcia, wykluczają a priori możliwość skutecznego stawienia im czoła przez pojedynczego człowieka — ten absolutnie nie jest w stanie zapanować zarówno nad status quo projektu, jak i nad jego nieuchronną ewolucją, nawet jeśli kierunek tej ewolucji jest znany i dobrze zdefiniowany. Zbyt wiele błędów popełnionych na etapie interpretacji dziedziny aplikacyjnej sprawia, że system staje się nieprzydatny dla użytkowników, a w konsekwencji dyskwalifikuje go pod względem

Wstęp

21

marketingowym. Niedojrzałe lub niekompatybilne technologie implementacyjne wydłużają proces tworzenia systemu i odbijają się negatywnie na jego niezawodności. Niezdolność do skutecznego zapanowania nad niezbędnymi zmianami wprowadza do systemu kolejne usterki i zwykle objawia się degradacją jego wydajności oraz użyteczności. Książka ta stanowi owoc doświadczeń ponad dziesięcioletniej praktyki w tworzeniu systemów komputerowych oraz nauczania tej trudnej sztuki na rozmaitych kursach inżynierii oprogramowania. Przyglądając się baczniej wielu takim kursom, skonstatowaliśmy, że uczą one postrzegania rzemiosła programistycznego raczej w oderwaniu od rzeczywistego świata, w kontekście niewielkich, dobrze zdefiniowanych problemów. W rezultacie studenci nabywają umiejętności znakomitego radzenia sobie z takowymi, lecz pierwsze zderzenie z realiami programistycznymi rzeczywistego świata okazuje się dla nich wręcz szokujące — stają w obliczu niełatwych wyborów pomiędzy rozmaitymi narzędziami i technologiami, a także muszą stawić czoła niebanalnemu wyzwaniu, którym jest produktywna współpraca w ramach zespołu. Ta niekorzystna sytuacja zdaje się jednak zmieniać — w programach studiów informatycznych opisane rozdrobnienie na izolowane, odrębne zadania powoli ustępuje tendencji realizowania pojedynczego, niebanalnego projektu jako zadania semestralnego.

Narzędzia: UML, Java i wzorce

projektowe

Co prawda, pomyśleliśmy tę książkę jako podręcznik akademicki, lecz równie dobrze sprawdzi się ona w kontekście nieco bardziej doraźnym, jako literatura pomocnicza dla krótkoterminowych warsztatów czy też projektów badawczo-rozwojowych. Prezentowane przykłady, inspirowane rzeczywistymi projektami z realnego świata, związane są z uznanymi i szeroko wykorzystywanymi technikami w rodzaju UML (Universal Modeling Language — „uniwersalny język modelowania" — technologiami bazującymi na języku Java, wzorcach projektowych, racjonalizacji projektów, zarządzaniu konfiguracją czy kontroli jakości. Technologie te rodzą określone problemy w zakresie zarządzania projektami, odbijające się bezpośrednio zarówno na ich złożoności, jak i na dynamice zmian, którym podlegają.

Zasady Wykładając szeroko rozumianą inżynierię oprogramowania, kierujemy się pięcioma wymienionymi poniżej przesłankami. Praktyka. Wierzymy, że wiedza teoretyczna musi być połączona z doświadczeniami praktycznymi. Studenci mogą zrozumieć pojęcie złożoności nie inaczej, jak tylko pracując nad złożonymi systemami — złożonymi, czyli przekraczającymi możliwości kompletnego zrozumienia przez jedną osobę. Rozwiązywanie problemów. Jesteśmy przekonani, że edukacja informatyczna musi opierać się na rozwiązywaniu problemów. Nie istnieją rozwiązania „dobre" ani „złe", a jedynie „lepsze" lub „gorsze" w świetle przyjętych kryteriów. Doceniając należycie rolę istniejących rozwiązań znanych problemów i zachęcając do ich wykorzystywania, zwracamy jednocześnie uwagę na znaczenie krytycyzmu i tendencji do ulepszania zastanego porządku rzeczy.

Wstęp

22

Ograniczoność zasobów. Gdy dysponuje się wystarczającą ilością czasu i zasobów, można tworzyć systemy niemal idealne. Taka sytuacja jest jednak utopią i to z dwóch zasadniczych powodów. Po pierwsze, czas realizacji projektu zawsze jest ograniczony, a przeznaczone na tę realizację zasoby są mniej lub bardziej limitowane. Po drugie — nawet przy założeniu dostatecznego bogactwa zasobów, gdy szczegóły problemu zmieniają się gwałtownie podczas jego rozwiązywania, może się zdarzyć, że zbudowany właśnie system rozwiązuje nie ten problem, co potrzeba. Konieczne jest więc założenie, że proces tworzenia systemu podlega ograniczeniom w kontekście dostępnych zasobów; założenie takie ma dodatkowo tę cenną zaletę, że dążymy do jak najlepszego ich wykorzystywania, stosując podejście oparte na komponentach oraz wykorzystując wielokrotnie wiedzę, elementy projektu i fragmenty kodu. Innymi słowy — traktujemy tworzenie oprogramowania jako dyscyplinę inżynierską. Interdyscyplinarność. Tworzenie oprogramowania jest działalnością interdyscyplinarną, wymagającą olbrzymiej wiedzy z zakresu elektroniki i architektury komputerów, informatyki, administrowania biznesem, projektowania grafiki, standardów przemysłowych — a często także umiejętności teatralnych i literackich! Jednocześnie budowanie oprogramowania jest dziedziną ściśle ukierunkowaną: próbując zrozumieć i modelując dziedzinę aplikacyjną problemu, programiści zmuszeni są do ciągłej interakcji z innymi programistami, użytkownikami i klientami, którzy o tajnikach tworzenia oprogramowania mają pojęcie raczej znikome. Wymaga to umiejętności spojrzenia na tworzony system z wielu różnych perspektyw. Komunikacja. Nawet wówczas, gdy programiści tworzą oprogramowanie na użytek własny lub innych programistów, i tak muszą się sprawnie komunikować między sobą. Jako programiści, nie możemy pozwolić sobie na luksus ograniczania komunikacji interpersonalnej do zwyczajowego „dzień dobry" dla kolegi z sąsiedniego boksu: sprawne komunikowanie się to proponowanie alternatywnych rozwiązań, negocjowanie kompromisów oraz krytyczne i konstruktywne spojrzenie na cudzą pracę. Nadspodziewanie wiele przypadków niepowodzenia w realizowaniu projektów programistycznych ma swe przyczyny w komunikowaniu niewłaściwej lub nieprawdziwej informacji bądź braku pożądanej informacji w ogóle. Musimy więc nauczyć się sprawnego porozumiewania się z innymi — nie tylko kolegami programistami, lecz (co ważniejsze) z klientami i użytkownikami naszych systemów. Pięć wymienionych przesłanek stanowi osnowę treści tej książki. W naszym zamierzeniu ma to zachęcić czytelnika do postrzegania złożonych i zmieniających się problemów w kategoriach praktycznych środków, dostępnych dla ich rozwiązywania.

Książka Książka ta prezentuje techniki zorientowane obiektowo w zastosowaniu do inżynierii oprogramowania. Nie jest ani podręcznikiem programowania, ani też przewodnikiem po algorytmach i strukturach danych. Skoncentrowaliśmy się raczej na ograniczonym podzbiorze technik, których zastosowanie prezentujemy w odniesieniu do przedsięwzięcia o wyraźnym stopniu komplikacji, którego przykładem może być projekt realizowany przez zespół 20 - 60 osobowy. Ów subiektywny wybór ma swe konsekwencje w subiektywizmie ujęcia opisu — wskazujemy rozwiązania godne, naszym zdaniem, polecenia, nie stroniąc od opisywania

Wstęp

innych, których słabe i mocne strony staramy się wyraźnie eksponować. Mamy zatem nadzieję, że każdy czytelnik tej książki znajdzie w niej dla siebie coś pożytecznego. Treść książki, w naszym zamierzeniu przeznaczona jako materiał dla jednosemestralnego kursu, składa się z 16 rozdziałów, podzielonych na trzy części. I tak w części pierwszej, zatytułowanej „Wprowadzenie" i obejmującej trzy rozdziały, zajmujemy się problematyką podstawowych umiejętności programisty, warunkujących jego zdolność do funkcjonowania w kontekście inżynierii oprogramowania. • W rozdziale 1., „Wprowadzenie do inżynierii oprogramowania", wyjaśniamy różnicę między programowaniem a inżynierią oprogramowania i wyzwania, jakie stawia przed programistami ta ostatnia, definiujemy też podstawowe koncepcje przewijające się przez treść wszystkich rozdziałów. • Rozdział 2., „Modelowanie w języku UML", zawiera opis podstawowych elementów UML (Universal Modeling Language), popularnego języka opisu modelowania, wykorzystującego techniki obiektowe. Prezentujemy w nim modelowanie jako jeden ze sposobów radzenia sobie ze złożonością problemów, pokazujemy, jak czytać i rozumieć diagramy UML (budowaniem takich diagramów, odzwierciedlających różnorodne aspekty systemu, zajmujemy się w następnych rozdziałach). Język UML przewija się przez całość tej książki jako środek opisu rozmaitych artefaktów — od systemów do procesów i produktów. • W rozdziale 3., „Organizacja projektu i komunikacja", wprowadzamy podstawowe koncepcje związane z organizacją projektu i sprawnym komunikowaniem się jego uczestników. Programiści i menedżerowie spędzają ponad połowę swego zawodowego czasu na komunikowaniu się — osobiście lub za pośrednictwem e-maili, grup dyskusyjnych, wideokonferencji czy pisemnej dokumentacji. Podczas gdy modelowanie jest środkiem radzenia sobie ze złożonością, komunikacja umożliwia sprostanie wymaganiom wynikającym z dynamicznych zmian w projekcie. Opisujemy organizację projektów i dyskutujemy czynniki warunkujące sprawne komunikowanie się. W części drugiej — nazwanej „Zmagania ze złożonością" — koncentrujemy się na metodach i technologiach umożliwiających programistom formułowanie specyfikacji, projektowanie i implementowanie skomplikowanych systemów. • W rozdziałach 4., „Zbieranie wymagań", i 5., „Analiza wymagań", przedstawiamy definicję systemu widzianego oczyma jego użytkownika. Na etapie zbierania wymagań programiści określają niezbędne dla użytkowników elementy funkcjonalne i sposób ich dostarczania. W trakcie analizy programiści formalizują tę wiedzę, weryfikując jednocześnie jej kompletność i spójność. Pokażemy tu, w jaki sposób język UML może okazać się pomocny w procesie ogarniania złożoności dziedziny aplikacyjnej problemu. • W rozdziałach 6., „Projektowanie systemu — dekompozycja na podsystemy", i 7., „Projekt systemu: realizacja celów projektowych", definiujemy system z perspektywy programisty. Na tym etapie programiści definiują architekturę systemu w kategoriach celów projektowych i w podziale na podsystemy. Rozwiązywane są podstawowe programy o charakterze globalnym, takie jak odwzorowanie systemu w komponenty sprzętowe, sposoby trwałego przechowywania danych czy ogólna

24

Wstęp

kontrola przepływu sterowania. Skupiamy uwagę czytelników na tym, jak programiści mogą wykorzystywać swoiste style architektoniczne oprogramowania oraz jego komponenty i jak stosować mogą język UML do radzenia sobie ze złożonością dziedziny aplikacyjnej. • W rozdziałach 8., „Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych", 9., „Projektowanie obiektów: specyfikowanie interfejsów", i 10., „Odwzorowywanie modelu na kod", opisujemy szczegółowo związane z modelowaniem czynności odnoszące się do „dziedziny realizacyjnej", czyli ujęcia systemu w kategoriach programistycznych. Na tym etapie programiści identyfikują i adaptują wzorce projektowe oraz środowiska programistyczne niezbędne do realizacji poszczególnych podsystemów. Następuje wówczas precyzyjne określenie interfejsów i klas, z wykorzystaniem obiektowych języków definiowania ograniczeń, do jakich należy między innymi OCL (Object Constraint Language) będący częścią UML. W następnej fazie szczegółowy projekt obiektowy odwzorowywany jest w kod źródłowy i schematy baz danych. • Przedmiotem rozważań rozdziału 11., „Testowanie", jest weryfikacja zachowania systemu pod kątem zgodności z modelami. Zadaniem testowania jest wykrywanie usterek systemu, między innymi wprowadzanych do niego w związku ze zmianami lub zbieraniem wymagań. Opisujemy tu kilka technik testowania, takich jak testy biało- i czarnoskrzynkowe, testowanie ścieżek, testowanie oparte na informacji o stanie, inspekcję zmiennych; dyskutujemy ich zastosowanie do systemów zorientowanych obiektowo. Część trzecia — „Zarządzanie zmianami" — dedykowana jest metodom i technologiom wspierającym kontrolowanie, ocenę i implementowanie zmian w trakcie całego cyklu rozwojowego systemu. • W rozdziale 12., „Zarządzanie racjonalizacją", opisujemy proces rodzenia się decyzji projektowych, ich uzasadniania i moderowania. Modele opracowywanie na etapach zbierania wymagań, analizy i projektowania stanowią silne narzędzie do stawienia czoła złożoności zagadnienia, umożliwiają bowiem spojrzenie z różnych perspektyw na to, co tak naprawdę powinien robić tworzony system i jak powinien to robić. Decyzje projektowe, wraz ze swymi alternatywami, oraz uzasadnienie jednych i drugich składają się na coś, co powszechnie nazywa się racjonalizacją systemu. • Treść rozdziału 13., „Zarządzanie konfiguracją", to opis technik modelowania historii projektu. Zarządzanie konfiguracją dopełnia racjonalizację w dziele radzenia sobie ze zmianami, których doświadcza wciąż przeobrażający się projekt systemu. Zarządzanie wersjami obejmuje chronologiczną dokumentację ewolucji systemu. Zarządzanie edycjami zapewnia spójność i odpowiednią jakość komponentów poszczególnych edycji. Wreszcie — zarządzanie zmianami daje gwarancję, że kolejne modyfikacje systemu pozostaną w spójności z jego celami projektowymi. • Rozdział 14., „Zarządzanie projektem", poświęcony jest technikom inicjowania projektu programistycznego, śledzenia postępu jego realizacji, a także czynnikowi ryzyka i nieprzewidywalnym zdarzeniom. Koncentrujemy się tu na organizacji,

25

Wstęp

rolach i działaniach kierowniczych pozwalających na efektywną współpracę dużej liczby uczestników projektu, zmierzającą do dostarczenia wysokiej jakości produktu w sposób zgodny z przyjętymi ograniczeniami. • W rozdziale 15., „Cykl życiowy oprogramowania", opisujemy koncepcję cyklu życiowego oprogramowania, na przykładzie modelu spiralnego Boehma i modelu jednolitego procesu wytwarzania oprogramowania — oba dostarczają abstrakcje niezbędne do modelowania działań projektantów i programistów. Opisujemy także model dojrzałości organizacyjnej, wykorzystywany do oceny stopnia zorganizowania całego procesu wytwarzania oprogramowania. • Ostatni, 16. rozdział, „Wszystko razem, czyli metodologie", to swoiste podsumowanie, poświęcone metodologiom i heurystykom związanym z treścią rozdziałów poprzednich i użytecznym w konkretnych sytuacjach. Realizacja każdego projektu o realistycznej skali, niezależnie od tego, jak solidnego w fazie zbierania wymagań i jak szczegółowo zaprojektowanego, nieuchronnie wiąże się z nieprzewidywalnymi zdarzeniami i koniecznością wprowadzania wynikających stąd niezaplanowanych wcześniej zmian. Właśnie zmaganie się z czynnikiem niepewności jest tym aspektem inżynierii oprogramowania, który wyraźnie odróżnia rzeczywiste projekty i systemy od opisywanych w podręcznikach. Omawiamy kilka różnych metodologii, zwracamy uwagę na pewne problemy wspólne dla większości typowych projektów programistycznych, a na zakończenie prezentujemy trzy różne studia przypadków oparte (co zrozumiałe) na projektach faktycznie realizowanych. Wymienione powyżej tematy są ze sobą wzajemnie i ściśle powiązane. By to powiązanie wyeksponować, zastosowaliśmy podejście iteracyjne, którego wyrazem jest podział każdego rozdziału na pięć sekcji. W pierwszej z nich wprowadzamy czytelnika w podstawowe problemy, składające się tematycznie na całość rozdziału; wprowadzenie to jest często ilustrowane przykładami zaczerpniętymi z rzeczywistego świata. W sekcji drugiej krótko opisujemy podstawowe czynności i działania, związane z realizacją konkretnego tematu. Trzecia sekcja zawiera wyjaśnienie podstawowych koncepcji, zaś w czwartej analizujemy ich detale techniczne, na przykładzie rzeczywistych systemów. Zamykająca rozdział sekcja piąta poświęcona jest aktywnościom menedżerskim, a także nieuchronnym kompromisom, jakie przychodzi rozstrzygać w związku z określonym aspektem realizacji projektu. Przez treść rozdziałów od 4. do 10. przewija się realne studium przypadku — złożony, wielodostępny system gry zespołowej ARENA. Powracając nieustannie do tych samych koncepcji, ilustrowanych coraz to bardziej złożonymi przykładami, mamy nadzieję dostarczyć użytkownikom sporo praktycznej wiedzy na temat obiektowo zorientowanej inżynierii oprogramowania.

Kurs Tworzenie ogromnego, złożonego systemu można porównać do wspinaczki na wysoki szczyt. Warto wówczas znać trasę do celu, tej jednak nie sposób nigdy dokładnie określić a priori, nie sposób bowiem przewidzieć wszystkich szczelin, które wciąż mogą się niespodziewanie

Wstęp

26

otwierać. Na podobnej zasadzie — mimo iż w tej książce odzwierciedliliśmy rzetelnie naszą wiedzę z zakresu inżynierii oprogramowania — świat idzie naprzód i metody, którym dziś hołdujemy, mogą się wkrótce okazać przestarzałe. Jak nauczyć studentów radzenia sobie z błyskawicznie zmieniającymi się uwarunkowaniami? Naszym zdaniem (jeśli trzymać się wcześniejszej analogii), nie wystarczy do tego znajomość mapy, potrzebna jest jeszcze umiejętność pokonywania przeszkód w terenie. I jakkolwiek znajomość trasy jest niezbędna, nie jest w stanie zastąpić wspinaczkowego doświadczenia. Napisaliśmy tę książkę z przeznaczeniem dla jednosemestralnego kursu z zakresu inżynierii oprogramowania, zarówno dla studiów magisterskich, jak i podyplomowych. Zakładamy, że czytelnicy posiadają doświadczenie w programowaniu w języku wysokiego poziomu, takim jak C, C++, C# lub Java. Zakładamy także elementarne umiejętności w zakresie rozwiązywania problemów technicznych, lecz nie oczekujemy doświadczenia w konfrontowaniu tychże z sytuacjami typowymi dla tworzenia złożonych systemów. Oczywiście, książka ta nadaje się równie dobrze na potrzeby doraźnych kursów, odbywających się poza kontekstem regularnych studiów. Kursy projektowania i kursy dla zaawansowanych. Kurs projektowania powinien obejmować wszystkie rozdziały, zasadniczo w ich naturalnej kolejności. W przypadku kursów o stopniu bardziej zaawansowanym instruktor może wcześniej zająć się tematyką rozdziału 14., „Zarządzanie projektem", by zawczasu zaznajomić słuchaczy z problematyką kontroli i planowania. Kursy wprowadzające. Kurs wprowadzający, nierozłącznie związany z zadaniami domowymi, powinien ograniczać się do trzech pierwszych sekcji każdego rozdziału. Dwie pozostałe sekcje stanowią doskonały materiał na zadania domowe, w ramach których słuchacze doskonalić będą swe umiejętności w zakresie tworzenia „na papierze" minisystemów w postaci diagramów UML, dokumentacji i kodu. Krótkie kursy technologiczne. Książka przydatna będzie także dla krótkich, intensywnych kursów adresowanych do profesjonalistów. Kursy tego rodzaju, koncentrujące się na języku UML i metodach obiektowo zorientowanych, powinny czerpać z rozdziałów 1., 2., 4., 5., 6., 7., 8., 9., 10. i 11. (w tej kolejności), obejmujących wszystkie fazy tworzenia systemu, od zbierania wymagań do testowania. W kursach bardziej zaawansowanych można także wykorzystać rozdziały 12., „Zarządzanie racjonalizacją", i 13., „Zarządzanie konfiguracją". Krótkie kursy dla menedżerów. Książka okaże się również pożyteczna na intensywnych kursach dla menedżerów. Szczególnie przydatne będą rozdziały związane z menedżerskimi aspektami realizacji projektów — komunikacją, zarządzaniem ryzykiem, racjonalizacją, modelami dojrzałości i językiem UML — czyli rozdziały (kolejno) 1., 2., 3., 14., 15., 16., 12. i 13.

Zmiany w stosunku do wydania

drugiego

Redagowanie tego wydania rozpoczęto od przystosowania treści do wersji UML 2 oraz ostatnich osiągnięć z zakresu „zwinnych" metod projektowania. Dodaliśmy także nieco nowego materiału związanego z projektowaniem systemów i ich testowaniem. W związku z tym, dziękujemy za cierpliwość naszej redaktor — Tracy Dunkelberger. Oto szczegółowa lista zmian.

27

Wstęp



Wszechstronne przystosowanie treści do najnowszych standardów UML i OCL. Zrewidowaliśmy pod tym kątem większość diagramów, w szczególności wprowadziliśmy diagramy komponentowe z notacją „kółko-gniazdo" (ball-and-socket).

• Rozszerzenie o zwinne metody projektowania. W wydaniu drugim, w rozdziale 16. znajduje się ogólne omówienie technologii eXtreme Programming. To wydanie rozszerzyliśmy o opis „zwinnych" metod Serum i rubgy i ich konsekwencji pod względem testowania, zarządzania konfiguracją i zarządzania projektami. Czytelnik znajdzie je w rozdziałach 11., 13. i 14. • Nowości na temat ciągłego integrowania. Jedną ze wspomnianych „zwinnych" technik jest ciągłe integrowanie zmian dokonywanych w oprogramowaniu z jego „głównym pniem" {trunk). I jakkolwiek technika ta pozwala na wczesne identyfikowanie i usuwanie problemów ogólnie związanych z integracją zmian, to jej realizacja może w stadium początkowym wiązać się z wieloma wyzwaniami. Piszemy o tym w rozdziale 13., „Zarządzanie konfiguracją". • Nowości na temat specyfikacji U2TP i zautomatyzowanego testowania. Na przykładzie rozszerzeń UML 2 Testing Profile dyskutujemy pewne koncepcje testowania, między innymi różnicę między systemem testującym a systemem podlegającym testowaniu. U2TP posłużyło nam także do wzbogacenia opisu o automatyzację testowania i automatyczne generowanie przypadków testowych. •

Ulepszenia w zakresie studiów przypadku i lepszy dobór przykładów. Od czytelników poprzedniego wydania otrzymaliśmy w tym względzie wiele uwag, za które bardzo dziękujemy — zaimplementowaliśmy większość z nich, nie sposób jednak wymienić ich tutaj szczegółowo.

Konwencje

typograficzne

Aby treść książki była bardziej czytelna, zróżnicowaliśmy styl jej poszczególnych elementów, i tak: • gdy nowy termin pojawia się po raz pierwszy, wyróżniamy go czcionką pogrubioną, • tytuły książek i rozdziałów, wyróżnione terminy oraz adresy URL pisane są kursywą, • nazwy systemów i nazwy związane z modelowanymi elementami (klasami, atrybutami, operacjami, stanami, zmiennymi) pojawiają się w postaci czcionki o s t a ł e j szerokości, • nazwy klas abstrakcyjnych pisane są pochylę

czcionką

o stałej

szerokości,

• nazwy obiektów na rysunkach mają postać podkreślonej czcionki o s t a ł e j szerokości, • przykładowy kod źródłowy pisany jest czcionką o s t a ł e j szerokości, przy czym słowa kluczowe są pogrubione, a komentarze pochylone.

Wstęp

28

Notka

produkcyjna

Treść książki została napisana i złożona za pomocą programu Adobe Framemaker. Ostatecznie przyjęła formę plików PDF sporządzonych przy użyciu programu Adobe Acrobat Distiller.

Autorzy Dr Bernd Bruegge studiował inżynierię oprogramowania, a później nauczał jej przez dwadzieścia lat na Uniwersytecie Carnegie Mellon, gdzie uzyskał najpierw dyplom magistra, a potem doktoryzował się. Uzyskał także dyplom uniwersytetu w Hamburgu. Jest obecnie profesorem informatyki na Uniwersytecie Technicznym w Monachium, gdzie zajmuje się zastosowaniami inżynierii oprogramowania, współpracuje także jako adiunkt na Uniwersytecie Carnegie Mellon. W tej książce zebrał owoc piętnastoletnich doświadczeń z zakresu nauczania obiektowo zorientowanej inżynierii oprogramowania — materiał dostępny w licznych publikacjach drukowanych i na stronach WWW. W 1995 roku Uniwersytet Carnegie Mellon przyznał mu nagrodę Herbert A. Simon Excellence in Teaching Award. Jest także konsultantem o zasięgu międzynarodowym. Opisywane w tej książce techniki wykorzystywał do implementowania wielu złożonych systemów, między innymi systemu zarządzania projektami dla firmy Daimler Chrysler, systemu modelowania środowiska naturalnego na zamówienie U.S. Environmental Protection Agency oraz systemu zarządzania roszczeniami powypadkowymi na potrzeby lokalnego departamentu policji — że ograniczymy się tylko do kilku naj ważniej szych. Dr Allen Dutoit pracuje w przemyśle lotniczym jako specjalista tworzenia oprogramowania poświęconego awionice. Ukończył studia magisterskie, a później doktoryzował się na Uniwersytecie Carnegie Mellon, uzyskał także dyplom inżyniera Szwajcarskiego Federalnego Instytutu Technologicznego w Lozannie. Od 1993 roku współpracuje z prof. Bruegge zarówno na Uniwersytecie Carnegie Mellon, jak i na Uniwersytecie Technicznym w Monachium, gdzie wykorzystuje i doskonali metody opisywane w tej książce. Jego prace badawcze obejmują wiele obszarów inżynierii oprogramowania i systemów zorientowanych obiektowo, między innymi inżynierię zbierania wymagań i zarządzania nimi, zarządzanie racjonalizacją, tworzenie oprogramowania w warunkach rozproszenia oraz systemy bazujące na prototypach. Uprzednio współpracował z instytutem złożonych systemów inżynierskich Uniwersytetu Carnegie Mellon.

Fotografie Zdjęcia widoczne na początku każdego z rozdziałów zostały zrobione podczas wejścia na Denali (6193 m) w stylu alpejskim drogą przez West Rib, dokonanego przez jednego z autorów tej książki, zanim jeszcze rozpoczęły się prace związane z jej powstaniem. Podczas tej ekspedycji analogie między alpinizmem a tworzeniem oprogramowania stały się więcej niż oczywiste. Czytelnicy zobaczyć mogą mała kronikę wyprawy: nasz ekspedycyjny wehikuł na autostradzie łączącej Alaskę z Kanadą (początek części I), Mount Robson, najwyższą górę kanadyjskich Gór Skalistych — 3954 m — ze ścianą Kaina (Kain Face) (rozdział 1.),

Wstęp

29

widok z samolotu na Denali (rozdziały 2. i 4.), początek drogi przez West Rib (rozdział 3.), widok z West Rib 1000 m w dół (zauważalne są nasze ślady na East Kahiltna Glacier (rozdział 5.), Mount Foraker z obozu 5. (rozdział 6.), piękną, lecz trudną do zdobycia grań na wysokości 5000 m (rozdział 7.), obóz bazowy na drodze normalnej, gdzie wykorzystaliśmy resztki igloo (rozdział 8.), lądowisko dla samolotu Douga Geetinga (rozdział 9.), biwak na drodze przez West Rib, zwany „Hotel Crux", bo nie da się tam wykopać platformy wystarczającej do rozbicia namiotu (rozdział 10.), przekraczanie szczeliny brzeżnej(rozdział 11.), świeże lawinisko (rozdział 12.), Denali z drogą Cassina (rozdział 13.), plany różnych dróg na szczyt (rozdział 14.), „horyzontalny" wschód słońca u podnóża drogi Cassina (rozdział 15.) i wierzchołek Denali. A na fotografii z okładki majestatycznie prezentuje się szczyt K2.

Podziękowania

I ^ s i ą ż k a ta w trakcie swego cyklu rozwojowego doznała szeregu złożonych zmian. W 1989 roku jeden z autorów (Bernd Bruegge) rozpoczął serię kursów, których celem było zaznajomienie studentów z realiami typowych projektów informatycznych, realizowanych dla prawdziwych klientów, przy użyciu prawdziwych narzędzi. W pierwszym takim kursie, zarejestrowanym w katalogu Uniwersytetu Carnegie Mellon pod numerem 15-413, uczestniczyło dziewiętnastu studentów, a jego owocem był kod o rozmiarze 4000 wierszy. Zainspirowani książką Jamesa Rumbaugha i jego kolegów, traktującą o obiektowo zorientowanym modelowaniu i projektowaniu, poczęliśmy wdrażać w praktyce opisywane w niej metody. Zorganizowaliśmy kilka rozproszonych geograficznie kursów, obejmujących kilkudziesięciu studentów z Uniwersytetu Carnegie Mellon i Uniwersytetu Technicznego w Monachium, czego owocem była każdorazowo dokumentacja o objętości około 500 stron i kod liczący 50 000 wierszy. Obecnie realizujemy podobny kurs dla studentów Uniwersytetu w Otago w Nowej Zelandii i Uniwersytetu Technicznego w Monachium. Podstawowym mankamentem wielu kursów z zakresu inżynierii oprogramowania jest niezdolność instruktorów do dystansowania się od problemów, jakie napotykają studenci; w rezultacie instruktorzy ci mimowolnie stają się uczestnikami projektów, najczęściej w roli ich menedżerów. Mamy nadzieję, że ta książka okaże się pomocna w dziele przywracania właściwych proporcji w tym względzie. Niezależnie od dużego wysiłku, jaki włożyliśmy w realizację wspomnianych kursów, znaleźliśmy wystarczająco dużo czasu na napisanie tej książki i opracowanie kolejnych jej wydań, co zawdzięczamy głównie pomocy i cierpliwości wielu studentów, klientów, asystentów, pracowników technicznych, korektorów i innych współpracowników, załodze Prentice Hall i — przede wszystkim — naszym rodzinom. Wielu z nich przyczyniło się bezpośrednio do usprawnienia i uatrakcyjnienia kursów, inni dostarczyli nam cenne uwagi na temat szkiców i projektów, inni jeszcze okazywali znaczącą pomoc, gdy sprawy zaczynały się komplikować i właśnie w tym miejscu chcielibyśmy wyrazić wdzięczność, jaką z perspektywy dwudziestu lat całej historii winni jesteśmy wielu ludziom, których postaramy się tu wymienić. Uczestnicy kursów projektowych: Workstation Fax (1989), Interactive Maps (1991), Interactive Pittsburgh (1991), FRIEND (1992, 1993, 1994), JEWEL, GEMS (1991, 1994, 1995), DIAMOND (1995, 1996), OWL (1996, 1997), JAMES (1997, 1998), PAID (1998, 1999), STARS (1999, 2000, 2001), TRAMP (2001, 2002), ARENA (2002, 2003), CampusTV (2004,2005), Virtual Symphony Orchester (2005), WALOS (2006) i DOLLI (2007, 2008).

32

Podziękowania

Pomocnicy i asystenci, którzy okazali nam wiele serdeczności i nieocenioną pomoc w trudnych sytuacjach: Martin Bauer, Ulrich Bauer, Catherine Copetas, Oliver Creighton, Ava Cruse, Barry Eisel, Luca Girardo, Dieter Hege, Mariss Jansons, Joyce Johnstone, Siegfried Kiese, Siegfried Klinkhammer, Rafael Kobyliński, Marc Lindike, Asa MacWilliams, Monika Marki, Key Maerkl i jego kwartet Aritus, Pat Miller, Martin Ott, Ralf Pfleghar, Martin Pittenauer, Harald Ranner, Joachim Reichel, Max Reiss, Barbara Sandling, Christian Sandor, Ralph Schiessl, Arno Schmackpfeffer, Helma Schneider, Stephan Schoenig, Stefien Schwarz, Martin Wagner, Uta Weber, Timo Wolf i Michael Zaddach. Pomocnicy, asystenci i przyjaciele, dostarczający nam wciąż nowych pomysłów i inspiracji: Mario Barbacci, Len Bass, Ben Bennington, Elizabeth Bigelow, Roberto Bisiani, Naoufel Boulila, Harry Q Bovik, Andreas Braun, Manfred Broy, Sharon Burks, Marvin Carr, Mike Collins, Robert Coyne, Douglas Cunningham, Michael Ehrenberger, Kim Faught, Peter Feiler, Allen Fisher, Laura Forsyth, Eric Gardner, Helen Granger, Thomas Gross, Volker Hartkopf, Bruce Horn, David Kauffer, Gudrun Klinker, Kalyka Konda, Suresh Konda, Rich Korf, Birgitte Krogh, Sean Levy, Frank Mang, K C. Marshall, Dick Martin („Tang Soo"), Horst Mauersberg, Roy Maxion, Russ Milliken, Ira Monarch, Rhonda Moyer, Robert Patrick, Brigitte Pihulak, Mark Pollard, Martin Purvis, Raj Reddy, Yoram Reich, James Rumbaugh, Johann Schlichter, Mary Shaw, Jane Siegel, Daniel Siewiorek, Asim Smailagic, Mark Stehlik, Eswaran Subrahmanian, Stephanie Szakal, Tara Taylor, Michael Terk, Gunter Teubner, Marc Thomas, Walter Tichy, Jim Tomayko, Blake Ward, Alex Waibel, Art Westerberg, Jeannette Wing i Tao Zhang. Redaktorzy i korektorzy, którzy wskazali nam nasze uchybienia i udzielali cennych wskazówek: Martin Barrett, Brian Berenbach, Alex Borgida, Ramsey Bualuan, Dave Chesney, Andrea De Lucia, Debora East, Thomas Eichhorn, Henry Etlinger, Ray Ford, Jim Helm, Jonas Helming, Korbinian Herrmann, Allen Holliday, John Keklak, Robert Lechner, Max Koegel, Jonathan Maletic, Jeff McKinstry, Bruce Maxim, Gerhard Mueller, Michael Nagel, Helmut Naughton, Barbara Paech, Dennis Pagano, Daniel Paulish, Joan Peckham, Gary Pollice, David Rine, Florian Schneider, Ingo Schneider, Anthony Sullivan, Damla Turgut i wielu nieznanych nam z imienia i nazwiska. Wszelkie błędy, które mimo to uchowały się w kolejnych wydaniach, zawinione są wyłącznie przez nas. Ekipa z Prentice Hall, która pomogła nam przydać realizmu naszym książkom, szczególnie: Alan Apt — nasz pierwszy wydawca, który nie pozwolił nam stracić wiary we własne siły; Lakshmi Balasubramanian, Toni Holm, Patrick Lindner, Camille Trentacoste, Jake Warde i (przy obecnej edycji) Tracy Dunkelberger, Scott Disanno, a także wielu innych, których ciężka praca umożliwiła doprowadzenie wydania do szczęśliwego końca i którym nie mieliśmy okazji podziękować osobiście. I na koniec — naszym rodzinom, którym tę książkę dedykujemy i których nieskończona miłość i cierpliwość pozwoliła nam często dokonywać rzeczy graniczących z niemożliwością.

w

1.1.

Wprowadzenie: niepowodzenia w inżynierii oprogramowania

36

1.2.

Czym jest inżynieria oprogramowania? 1.2.1. Modelowanie 1.2.2. Rozwiązywanie problemów 1.2.3. Pozyskiwanie wiedzy 1.2.4. Racjonalizacja

38 38 40 41 42

1.3.

Podstawowe koncepcje inżynierii oprogramowania 1.3.1. Uczestnicy i role 1.3.2. Systemy i modele 1.3.3. Produkty 1.3.4. Aktywności, zadania i zasoby 1.3.5. Wymagania funkcyjne i pozafunkcyjne 1.3.6. Notacje, metody i metodologie

43 44 46 46 47 48 48

1.4.

Aktywności inżynierii oprogramowania 1.4.1. Zbieranie wymagań 1.4.2. Analiza 1.4.3. Projekt systemu 1.4.4. Projektowanie obiektów 1.4.5. Implementowanie 1.4.6. Testowanie

49 50 51 51 53 53 54

1.5.

Zarządzanie tworzeniem oprogramowania 1.5.1. Komunikacja 1.5.2. Zarządzanie racjonalizacją 1.5.3. Zarządzanie konfiguracją oprogramowania 1.5.4. Zarządzanie projektem 1.5.5. Cykl życiowy oprogramowania

54 55 55 56 56 56

1.5.6.

57

Podsumowanie

1.6.

ARENA — analiza przypadku

57

1.7.

Literatura uzupełniająca

58

1.8.

Ćwiczenia

59

Bibliografia

61

Wprowadzenie do inżynierii oprogramowania Programista amator ma to do siebie, że wciąż poszukuje magicznych narzędzi, które sztukę tworzenia aplikacji miałyby sprowadzić do banalnego zadania. W chwili gdy uświadamia sobie, że takich narzędzi po prostu nie ma, staje się programistą profesjonalistą. *

— Grady Booch Object-Oriented Analysis and Design

O k r e ś l e n i e „inżynieria oprogramowania" narodziło się w roku 1968 jako reakcja na wyobcowany status trudnej sztuki tworzenia oprogramowania wysokiej jakości, w założonych ramach czasowych i za cenę nieprzekraczającą założonego budżetu. Przejawem wspomnianego „wyobcowania" była niezdolność programistów do określenia konkretnych celów, do oszacowania zasobów niezbędnych do osiągnięcia tych celów oraz do sprostania — wciąż zmieniającym się, bo coraz lepiej uświadamianym — oczekiwaniom klientów. I tworzenie typowego projektu wyglądało mniej więcej tak, że na początku obiecywano Księżyc, potem realizowano coś na kształt łunochodu, a ostatecznie klienci otrzymywali parę kwadratowych kółek. W inżynierii oprogramowania istotne są oba człony składowe — inżynieria i oprogramowanie. Inżynier to człowiek obdarzony umiejętnościami tworzenia wysokiej jakości produktów przy wykorzystywaniu standardowych komponentów „z półki" i integrowaniu ich w ramach ograniczonego czasu i ograniczonego budżetu. Inżynier najczęściej staje w obliczu źle zdefiniowanych problemów i jedynie częściowych rozwiązań, a dokonując rozmaitych wyborów, kierować się musi (z braku lepszego kryterium) rozmaitymi metodami empirycznymi. I jakkolwiek inżynierowie zajmujący się projektowaniem systemów ruchu lotniczego czy budowaniem mostów radzą sobie z takimi wyzwaniami stosunkowo nieźle, to „inżynierowie od programowania" mają w tym względzie zdecydowanie mniej szczęścia. Problematyka tworzenia i dostarczania skomplikowanych systemów była i jest intensywnie badana. Przyczyny wspomnianych niepowodzeń upatrywano dosłownie we wszystkim: niezrozumieniu ze strony klientów („co to znaczy, że nie mogę mieć Księżyca za 50 baksów?"), dosłownym znaczeniu frazy „soft" w rzeczowniku „software" („Gdybym tylko mógł dodać tę jedną, ostatnią cechę...") i wreszcie w niedojrzałości (w sensie młodego wieku) samej dyscypliny. Gdzież więc leży rzeczywisty problem?

Złożoność i zmiany Prawdziwie użyteczny system informatyczny jest z konieczności systemem odpowiednio skomplikowanym. By mógł on zachować swą użyteczność w obliczu zmieniających się potrzeb klientów i ewoluującego środowiska docelowego, sam musi nieustannie doznawać odpowiednich przeobrażeń. Celem tej książki jest zapoznanie czytelnika z metodami, które

36

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

u m o ż l i w i a j ą s k u t e c z n e r a d z e n i e sobie z a r ó w n o ze w s p o m n i a n ą złożonością, jak i z n i e u s t a n n i e d o k o n u j ą c y m i się z m i a n a m i ; w t y m rozdziale p r z e d s t a w i a m y m o t y w a c j ę d o wykorzystywania t e c h n i k z o r i e n t o w a n y c h o b i e k t o w o i d e f i n i u j e m y p o d s t a w o w e k o n c e p c j e p r z e w i j a j ą c e się przez treść tego i następnych rozdziałów.

1.1. Wprowadzenie: niepowodzenia w inżynierii oprogramowania «

S p ó j r z m y n a p o n i ż s z e p r z y k ł a d y , z a c z e r p n i ę t e z k s i ą ż k i P. N e u m a n n a [ N e u m a n n , 1995].

Błąd roku 1 9 0 0 W 1992 roku Mary z miasteczka Winona w stanie Minnesota otrzymała zaproszenie do dziecięcego parku zabaw. Nie byłoby w tym nic dziwnego, gdyby nie fakt, że Mary skończyła właśnie 104 lata. Błąd roku p r z e s t ę p n e g o Pewien supermarket zapłacił 1000 dolarów grzywny za sprzedaż przeterminowanych porcji mięsa mielonego. Ich przydatność do spożycia, zgodnie z nadrukiem na etykiecie, kończyła się 1 marca 1988 roku, podczas gdy faktyczny termin ważności upływał dzień wcześniej. W programie drukującym etykietki przeoczono fakt, że rok 1988 jest rokiem przestępnym. N a d u ż y c i e interfejsu 10 kwietnia 1990 pociąg londyńskiego metra odjechał ze stacji bez maszynisty. Maszynista nacisnął starter, ale przecież wiedział, że pociąg pozostanie unieruchomiony, dopóki nie zamkną się wszystkie drzwi; zablokował w tym celu drzwi najbliższe kabinie i wyszedł z pociągu. Nieświadomy niczego konduktor chwilę później usunął blokadę ze wspomnianych drzwi — te się zamknęły i . . . Bezpieczeństwo CERT (Computer Emergency Response Team)' w Software Engineering Institute jest organizacją rządu USA, zajmującą się bezpieczeństwem informacji — jego naruszeniem, jego zagrożeniami i wiedzą dotyczącą jego zapewnienia. Liczba zgłoszonych przypadków naruszenia owego bezpieczeństwa zwiększyła się z 252 w 1990 roku do 21 756 w roku 2000, by w roku 2001 przekroczyć liczbę 40 000. Za późno i poza budżetem W 1995 roku błąd w zautomatyzowanej przechowalni bagażu w międzynarodowym porcie lotniczym w Denver powodował fizyczne niszczenie walizek. Port otwarto z 16-miesięcznym opóźnieniem, po przekroczeniu zakładanego budżetu o 3,2 mld dolarów, z ręcznie sterowanym system e m przechowywania bagażu. Z a p ó ź n o i p o z a b u d ż e t e m na bis W 2002 roku system kontroli ruchu lotniczego w Swanick zawiadywał wszystkimi trasami lotniczymi przebiegającymi nad Anglią i Walią. Przy jego realizacji znacznie przekroczono budżet — zamiast planowanych 350 min funtów wydano ostatecznie 623 min, a realizacje tę ukończono z 6-letnim opóźnieniem. Dwie główne poprawki wprowadzano do systemu w czasie, gdy trwało już szkolenie personelu kontroli lotów.

1

Zespół ds. reagowania na przypadki naruszenia bezpieczeństwa teleinformatycznego — organizacja utworzona w listopadzie 1988 roku przez DARPA, po znanym incydencie z robakiem Morrisa — przyp. tłum, cytat z Wikipedii, patrz http://pl.wikipedia.org/wiki/CERT i http://pl.wikipedia.org/wiki/Robak_Morrisa.

1.1. Wprowadzenie: niepowodzenia w inżynierii oprogramowania

37

Planowe ukończenie projektu W 1984 roku, po półtorarocznej realizacji i wydaniu 200 min dolarów, oddano do użytku w Wisconsin system ubezpieczeń zdrowotnych. Ukończony terminowo system pracował jednak niepoprawnie: doprowadzenie go do działania zgodnie z oczekiwaniami trwało 3 następne lata i kosztowało dodatkowe 60 min.

Niepotrzebne komplikacje Stworzenie samolotu C-17 firmy McDonnell Douglas pochłonęło 500 min dolarów ponad plan, z powodu licznych błędów w systemie pokładowym. Trudno się temu dziwić, zważywszy na fakt, że wspomniany system składał się z 19 komputerów i 80 mikroprocesorów, oprogramowanych przy użyciu 6 różnych języków. 4

Każde z opisanych powyżej niepowodzeń daje się w prostej linii wywieść od problemów związanych z oprogramowaniem. W niektórych przypadkach programiści nie uwzględnili rzadko zdarzających się sytuacji (ludzie żyjący dłużej niż 100 lat, rok przestępny i okres przydatności do spożycia przecinający granicę lutego i marca), kiedy indziej ich wyobraźnia okazała się niewystarczająca do uwzględnienia nietypowych zachowań ludzkich (opuszczenie kabiny przez maszynistę po uprzednim włączeniu startera, eksploatowanie przez hakerów luk bezpieczeństwa w oprogramowaniu sieciowym). Innym jeszcze razem dało o sobie znać nieudolne zarządzanie realizacją systemu, czego efektem było przekroczenie ram czasowych i budżetowych, bądź też dostarczenie, owszem, na czas i w założonym budżecie, systemu niespełniającego oczekiwań klientów albo systemu nadmiernie skomplikowanego i stąd wysoce podatnego na awarie. Systemy informatyczne są tworami skomplikowanymi. Wykonują zwykle wiele funkcji, tworzone są z myślą o osiągnięciu wielu różnych, często sprzecznych ze sobą, celów. Składają się z wielu komponentów, z których większość to gotowe produkty innych („trzecich") producentów, skomplikowane same w sobie — tworzone przy udziale specjalistów z wielu różnych dziedzin. Realizacja typowego systemu informatycznego i jego cykl życiowy rozciągają się zazwyczaj na wiele lat. To wszystko przesądza o tym, że prawdziwy system informatyczny przekracza możliwości ogarnięcia wszystkich jego szczegółów przez pojedynczą osobę. Niektóre systemy są na tyle trudne do zrozumienia już na etapie opracowywania, że ich realizacja nie zostaje ukończona — oprogramowanie tej fatalnej kategorii zyskało sobie potoczną nazwę vaporware2. Projekty systemów informatycznych są z natury obiektami podatnymi na nieustanne zmiany. Wymagania stawiane złożonemu systemowi same są skomplikowane, a często wymagają też weryfikacji, gdy odkryty zostanie błąd w rozumowaniu bądź też programiści, w miarę postępu w realizacji systemu, zaczynają coraz lepiej rozumieć rozmaite jego aspekty. Jeśli realizacja takiego projektu ciągnie się przez lata, wiąże się z zaangażowaniem licznego personelu, wymagającego niezbędnych szkoleń i treningu. Na przestrzeni owych wielu lat zmieniają się także dostępne technologie — cykl życiowy określonej technologii implementacyjnej jest zwykle krótszy niż cykl życiowy systemu, na potrzeby realizacji którego można tę technologię wykorzystywać. Jeżeli w tych warunkach menedżerowie projektu potraktują wymagania stawiane systemowi jako byt raz określony i niezmienny, możemy być prawie pewni, że owocem owych wielu lat pracy będzie system niespełniający oczekiwań użytkowników. Ang. vapor — opar, mgła, mrzonka, trafna analogia czegoś, co nie miało szczęścia fizycznie zaistnieć, mimo szczytnych koncepcji i celów — przyp.

tłum.

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

38

W następnej sekcji zaprezentujemy wysokopoziomowe spojrzenie na inżynierię oprogramowania — opiszemy tę dyscyplinę z perspektywy nauki, inżynierii, pozyskiwania wiedzy i formalizacji. W sekcji 1.3 opiszemy bardziej szczegółowo poszczególne terminy i koncepcje obecne w treści tej książki. W sekcji 1.4 dokonamy ogólnego przeglądu programistycznych aktywności składających się na inżynierię oprogramowania, zaś sekcja 1.5 to ogólny obraz aktywności na szczeblu zarządzania projektem. 4

1.2. Czym jest inżynieria oprogramowania? Inżynieria oprogramowania jest w swej istocie modelowaniem. To właśnie za pomocą modelowania programiści radzą sobie ze złożonością, skupiając się w danej chwili na aktualnie istotnych szczegółach i ignorując wszelkie inne. Realizacja systemu wiąże się z budowaniem wielu różnych modeli zarówno samego systemu, jak i jego dziedziny aplikacyjnej. Inżynieria oprogramowania to niewątpliwie rozwiązywanie problemów. Modele służą do poszukiwania akceptowalnych rozwiązań, a poszukiwanie to prowadzone jest na drodze eksperymentalnej. Programiści dysponują ograniczonymi zasobami i związani są ograniczeniami wynikającymi z dostępnego budżetu oraz harmonogramu. W obliczu (zazwyczaj) braku fundamentalnych teorii zmuszeni są polegać jedynie na empirycznych metodach dokonywania wyboru spośród wielu możliwych rozwiązań. Inżynieria oprogramowania wiąże się z pozyskiwaniem wiedzy. Podczas modelowania dziedziny aplikacyjnej i dziedziny realizacyjnej programiści kolekcjonują dane, przetwarzając je najpierw w użyteczną informację, a następnie formalizując tę ostatnią w formie wiedzy. Pozyskiwanie wiedzy nie jest bynajmniej procesem sekwencyjnym — poznanie nowego, drobnego nawet szczegółu może sprawić, że dotychczas budowany model w jednej chwili wali się w gruzy. Inżynieria oprogramowania jest procesem sterowanym racjonalizacją. Pozyskując niezbędną wiedzę i podejmując na jej podstawie rozmaite decyzje dotyczące systemu i jego dziedziny aplikacyjnej, programiści uwzględniać muszą kontekst, w którym decyzje te są podejmowane, i każdą z nich odpowiednio uzasadniać. Informacja związana z racjonalizacją reprezentowana w formie odnośnych modeli umożliwia programistom należyte zrozumienie implikacji proponowanych zmian, gdy przychodzi ponownie rozważyć zasadność podjętej decyzji. Dalej opiszemy bardziej szczegółowo inżynierię oprogramowania w kategoriach wymienionych aktywności — modelowania, rozwiązywania problemów, pozyskiwania wiedzy i racjonalizacji. Nieodłącznym aspektem każdej z nich jest czynnik ludzki oraz ograniczenia wynikające z harmonogramu i dostępnego budżetu. No i nie wolno zapomnieć o zmianach, które dokonywać się mogą w każdej niemal chwili.

1.2.1. Modelowanie Zadaniem nauki jest opisywanie i rozumienie złożonych systemów, takich jak atomowa struktura materii, zachowania ludzi i społeczności, czy Układ Słoneczny. Usankcjonowany, zgodnie z tradycją, podział dyscyplin naukowych na nauki przyrodnicze i nauki społeczne wyznacza granicę umożliwiającą odróżnienie dwóch kategorii systemów. Przedmiotem zainteresowania nauk przyrodniczych — biologii, chemii, fizyki czy paleontologii — jest przyroda (natura) i jej podsystemy; dla odmiany, przedmiotem zainteresowania psychologii i socjologii, jako nauk społecznych, jest ludzkie życie oraz interakcje międzyludzkie w ramach społeczności.

1.2. Czym jest inżynieria oprogramowania?

39

Odrębną grupę systemów stanowią systemy sztuczne, których przykładem może być statek kosmiczny, rozproszona aplikacja rezerwacji miejsc w samolotach czy system obsługujący transakcje giełdowe. H. Simon [Simon, 1970] określił nawet dyscypliny naukowe zajmujące się takimi systemami jako sciences of the artificial („nauki o sztuczności"). Podczas gdy nauki przyrodnicze i społeczne mają za sobą kilkusetletnią historię, sciences of the artificial są bytem stosunkowo młodym — na przykład informatyka, rozumiana jako naukowe ujęcie rozmaitych aspektów systemów komputerowych, jest dzieckiem XX wieku. Wiele metod, z powodzeniem stosowanych w ramach nauk przyrodniczych i społecznych, przydaje się także w odniesieniu do sciences of the artificial — jedną z nich jest modelowanie. Model stanowi abstrakcyjną reprezentację systemu, umożliwiającą udzielenie odpowiedzi na wiele pytań formułowanych pod adresem tegoż systemu. Model systemu staje się użyteczny i często niezastąpiony, w sytuacji-gdy oryginalny system byłby zbyt ogromny, zbyt miniaturowy, zbyt skomplikowany czy zbyt drogi w kontekście eksperymentowania. Modele okazują się koniecznym substytutem systemów, które już nie istnieją, oraz tych, których istnienie jest dopiero planowane. Paleontolodzy odnajdują zachowane fragmenty kości i uzębienia dinozaurów, których z oczywistych względów nigdy nie widzieli w naturze. Z tych fragmentów rekonstruują modele organizmów zwierzęcych, opierając się na regułach anatomii. Im więcej dostępnego materiału z wykopalisk, tym klarowniejsza staje się idea, jak poszczególne elementy tego materiału poskładać ze sobą, i tym większe zaufanie do modelu mającego odzwierciedlać rzeczywisty organizm żyjącego w zamierzchłych czasach dinozaura. Dysponując dostateczną ilością kości, zębów i szponów, mogą zyskać niemal całkowitą pewność co do tego, że ich model odzwierciedla rzeczywistość z należytą dokładnością; co więcej — mogą formułować rozmaite hipotezy dotyczące jego brakujących elementów: przykładowo, nogi występują zwykle parami, jeśli zatem dysponują kośćmi jedynie lewej nogi, mogą w sposób niemal pewny odtworzyć postać brakującej prawej nogi oraz jej pozycję w budowanym modelu. Tak z grubsza prezentuje się modelowanie systemów, które już nie istnieją. Podobnie ma się rzecz z modelowaniem systemów nierealistycznych bądź nieogarnionych jak Wszechświat. Fizycy zajmujący się badaniem materii w obliczu wysokich energii znajdują się na pozycji porównywalnej z położeniem paleontologów odnajdujących szczątki prehistorycznych organizmów. Obserwując zachowanie cząstek elementarnych i mierząc ich rozmaite właściwości, próbują stworzyć model materii i energii odzwierciedlający zarówno ich makrostrukturę, jak i strukturę na poziomie subatomowym. Wieloletnie eksperymenty przy użyciu akceleratorów cząstek elementarnych dostarczają fizykom coraz to więcej dowodów na to, że budowane przez z nich modele należycie odzwierciedlają rzeczywistość, i jednocześnie pozwalają im żywić nadzieję na to, że brakujące jeszcze ich elementy, gdy zostaną już zidentyfikowane, również wpiszą się gładko w tzw. model standardowy. W przypadku obu typów modelowania mamy do czynienia z encjami dwóch kategorii: systemem rzeczywistym, manifestującym swą obecność poprzez fakty i zjawiska, oraz modelem jego dziedziny aplikacyjnej, stanowiącej zbiór wzajemnie uzależnionych od siebie koncepcji. Do pierwszej z wymienionych kategorii należą dinozaury i cząstki elementarne, do drugiej — opisy tych ich aspektów, które okazują się istotne (relewantne) dla rozwiązywanego właśnie problemu.

40

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

Inżynieria oprogramowania jawi się w opisywanym kontekście jako coś pośredniego między paleontologią i fizyką wysokich energii. Po pierwsze, programiści muszą zrozumieć środowisko, w którym pracować ma ich system: jeśli jest to system sterowania ruchem kolejowych, muszą dokładnie poznać obowiązujące w tym ruchu reguły sygnalizacji; jeśli jest to system zarządzania giełdą, mujzą znać zasady obrotu akcjami. Programista nie musi jednak być certyfikowanym maszynistą ani doświadczonym brokerem: dla niego istotne są jedynie te koncepcje spośród dziedziny aplikacyjnej, które związane są bezpośrednio z samym systemem — te bowiem są niezbędne do zbudowania modelu tej dziedziny. Po drugie, programiści muszą rozumieć systemy, które będą tworzyć, ponieważ tylko wtedy będą mogli rozstrzygać rozmaite kompromisy i dokonywać trafnych wyborów spośród dostępnych rozwiązań. Większość systemów przekracza swą złożonością możliwość zrozumienia ich przez jedną osobę, realizacja takich systemów jest ponadto kosztownym przedsięwzięciem. By uporać się z jego złożonością i sprostać jego wymogom ekonomicznym, programiści opisują istotne aspekty „swojego" systemu, tworząc w ten sposób model dziedziny realizacyjnej. Istotą podejścia zorientowanego obiektowo jest połączenie modeli obu dziedzin — aplikacyjnej i realizacyjnej. Dziedzina aplikacyjna jest wpierw modelowana w postaci zbioru obiektów powiązanych różnorodnymi relacjami — model ten odzwierciedla obiekty ze świata rzeczywistego i zachodzące między nimi powiązania: w systemie sterowania ruchem pociągów wspomniane obiekty reprezentują pociągi, których ruch jest monitorowany, w systemie zarządzania giełdą obiekty transakcyjne reprezentują klientów kupujących i sprzedających akcje. W podobny sposób modelowane są koncepcje dotyczące dziedziny realizacyjnej: fragment kodu, reprezentujący ruch pociągu czy transakcję finansową, jest obiektem wchodzącym w skład tej dziedziny. Na gruncie podejścia zorientowanego obiektowo dokonuje się zatem transformacja modelu dziedziny aplikacyjnej w model dziedziny realizacyjnej. W tym ujęciu proces tworzenia oprogramowania przekłada się na zespół aktywności niezbędnych do postrzegania i opisywania tworzonego systemu jako zbioru modeli odnoszących się bezpośrednio do problemów, których rozwiązania oczekują użytkownicy tego systemu. Modelowanie oraz koncepcję wspomnianych obiektów opiszemy szczegółowo w rozdziale 2. „Modelowanie w języku UML".

1.2.2. Rozwiązywanie problemów Nieodłącznym aspektem inżynierii jest rozwiązywanie problemów. Inżynierowie poszukują rozwiązań problemów przeważnie metodą prób i błędów, oceniając dostępne warianty tych rozwiązań w sposób empiryczny, bo dysponują ograniczonymi zasobami i niekompletną wiedzą. Każda metoda inżynierska, w najprostszym ujęciu, daje się przedstawić jako kombinacja pięciu kroków: 1. Sformułowanie problemu, 2. Analiza problemu, 3. Poszukiwanie dostępnych rozwiązań, 4. Wybór odpowiedniego rozwiązania, 5. Specyfikacja rozwiązania.

1.2. Czym jest inżynieria oprogramowania?

41

Inżynieria oprogramowania jest procesem inżynierskim, nie algorytmicznym: wymaga eksperymentowania, wielokrotnego wykorzystywania rozwiązań wzorcowych i zapewnienia przyrostowej ewolucji systemu w kierunku postaci akceptowalnej przez klientów. Zorientowane obiektowo tworzenie oprogramowania obejmuje sześć następujących aktywności: zbieranie wymagań, analizę, projektowanie systemu, projektowanie obiektów, ich implementowanie i testowanie. Na etapie zbierania wymagań i analizy programiści, we współpracy z klientami, formułują problem i budują model dziedziny aplikacyjnej; koresponduje to z punktami 1. i 2. przedstawionego scenariusza. W czasie projektowania systemu problem dekomponowany jest na serię mniejszych podproblemów, ustalane są też ogólne strategie projektowe; projektowanie obiektów sprowadza się do rozpoznawania repertuaru szczegółowych rozwiązań każdego podproblemu i wyboru rozwiązania najbardziej odpowiedniego — otrzymujemy w ten sposób model dziedziny realizacyjnej. Ten krok stanowi odpowiednik punktów 3. i 4. wspomnianego scenariusza. Implementacja to proces urealnienia systemu poprzez translację modelu dziedziny realizacyjnej na jego wykonywalną reprezentację, co odpowiada ostatniemu, 5. punktowi. Tym, co wyróżnia inżynierię oprogramowania spośród innych dyscyplin inżynierskich, jest intensywność zmian, jakich doznawać mogą (i faktycznie doznają) obie dziedziny — aplikacyjna i realizacyjna — w trakcie rozwiązywania problemu. Z tworzeniem oprogramowania nieodłącznie związane są czynności zmierzające do oceny adekwatności danego modelu. Na etapie analizy model dziedziny aplikacyjnej porównywany jest z realiami, w jakich działają klienci, co skutkować może jego drastyczną nieraz zmianą; na etapie weryfikowania projektu model dziedziny realizacyjnej konfrontowany jest z celami projektu, zaś w czasie testowania sam system weryfikowany jest w kontekście modelu dziedziny realizacyjnej, który może się zmieniać wskutek pojawiania się nowych technologii. Wreszcie, na szczeblu zarządzania projektem menedżerowie porównują swoje modele — czyli harmonogram i budżet — z realiami, czyli terminowością dostarczania rozwiązań cząstkowych i dynamiką wykorzystywania zasobów.

1.2.3. Pozyskiwanie wiedzy Jednym z błędów często popełnianych przez programistów i menedżerów jest przyjmowanie założenia, iż stopniowe pozyskiwanie wiedzy niezbędnej do stworzenia konkretnego systemu jest procesem linearnym. Błąd ten nie jest jednak ich wynalazkiem ani też nie są oni w swym błędnym przekonaniu odosobnieni: w XVII wieku ukazała się książka3, której autor zaleca utrwalanie wierszy (w języku niemieckim) poprzez sączenie ich tekstu (w sensie dosłownym — przez lejek umieszczony w uchu!) do głów studentów, przez 6 godzin dziennie. Ów niezwykły pomysł zasadzał się na powszechnie panującym przekonaniu, iż umysł ludzki początkowo jałowy może być wypełniany treścią w sposób linearny — za pomocą zmysłów treść ta dociera do mózgu, gdzie jest akumulowana i odpowiednio interpretowana. K. Popper nazywa ów model linearnej akwizycji „teorią umysłu pojemnika" (bucket theory of the mind)-, jednym z wielu błędów tej teorii ([Popper, 1992]) jest założenie, że wiedza pojmowana jest jako istnienie rzeczy, które mogą stopniowo napełniać wspomniany pojemnik — im pełniejszy, tym bogatsza staje się nasza wiedza. 3

Georg Philipp Harsdórffer (1607 - 1658), Poetischer Trichter, die teutsche Dicht- und ohn Behuf der lateinischen Sprache, in 6 Stunden einzugiefien, Nuernberg, 1630.

Reimkunst,

42

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

Pozyskiwanie wiedzy nie jest jednak procesem linearnym: pojawienie się nowego faktu czy nowej informacji może postawić pod znakiem zapytania lub po prostu przekreślić całą dotychczasową wiedzę, jaką nabyliśmy podczas uczenia się rozumienia naszego systemu. I jeśli nawet owo rozumienie zostało już odzwierciedlone w postaci dokumentacji i kodu źródłowego („kod systemu jest gotowy w 90%, resztę napiszemy w przyszłym tygodniu"), musimy być mentalnie przygotowani na to, że pisanie dokumentacji czy (co ważniejsze) tworzenie kodu systemu będziemy musieli rozpocząć od zera. A to w istotny sposób przekłada się na zbiór aktywności i ich wzajemnych interakcji, jakie zdefiniowaliśmy w związku z tworzeniem wspomnianego systemu. Na gruncie inżynierii oprogramowania odpowiednikiem teorii „umysłu pojemnika" jest sekwencyjny model kaskadowy, zgodnie z którym poszczególne kroki związane z tworzeniem systemu realizowane są w sposób sekwencyjny. Istnieje kilka różnych technik i metodologii zmierzających do unikania krępującej sekwencyjności modelu kaskadowego — i tak na przykład istotą programowania sterowanego ryzykiem jest przewidywanie zawczasu problemów, jakie mogą pojawić się w późnych stadiach realizacji projektu; dokonuje się tego poprzez identyfikowanie komponentów obarczonych wysokim ryzykiem. Metodologia programowania sterowanego problemami zmierza natomiast do całkowitego wyeliminowania narzuconej a priori linearności. Każda z poszczególnych aktywności związanych z tworzeniem systemu — analiza, projekt systemu, projekt obiektów, implementowanie, testowanie i wdrażanie — ma mniejszy lub większy wpływ na pozostałe i na przykład na gruncie programowania sterowanego problemami wszystkie one realizowane są równolegle. Takie nielinearne modele, choć bardziej adekwatne do realiów, mają tę niedogodność, że trudno się nimi zarządza.

1.2.4. Racjonalizacja Opisując pozyskiwanie wiedzy i jej ewoluowanie, z konieczności stajemy na pozycji mniej pewnej w porównaniu z opisywaniem rzeczywistego, istniejącego systemu. Podręczniki wszelakich dyscyplin matematycznych pełne są dowodów, rzadko jednak napotkać można w nich wzmiankę na temat ich genezy, na temat inspiracji, jakie skierowały myślenie autora w tę czy inną stronę. Dzieje się tak chyba dlatego, że matematycy nie uważają tego aspektu sprawy za szczególnie istotny: gdy określi się zbiór pojęć pierwotnych, zbiór aksjomatów i reguł dedukcji, sam dowód staje się kwestią wręcz automatycznej konstrukcji. Inżynierowie programiści znajdują się w sytuacji cokolwiek trudniejszej. W przeciwieństwie do matematyków, obracających się w komfortowych środowisku ustalonych aksjomatów, muszą stawiać czoła nieustannym zmianom. I jeżeli nawet będą mieć to szczęście, że modele dziedziny aplikacyjnej w miarę ustabilizują się po pewnych czasie, to na pewno modele dziedziny realizacyjnej gwarantują borykanie się z nieustanną płynnością. Błędy projektowe i implementacyjne, bezlitośnie obnażane w trakcie testowania, oraz problemy związane z użytecznością objawiające się przy pierwszym kontakcie użytkowników z nowymi elementami systemu wymagają często nawet drastycznych zmian w modelu dziedziny realizacyjnej. Zmiany takie mogą być również wymuszane przez postęp technologiczny: nowa kategoria długożyciowych akumulatorów oraz nowe osiągnięcia w zakresie szybkiej komunikacji bezprzewodowej z naturalnych względów wymagają zrewidowania przyjętych wcześniej koncepcji projektowych przenośnego terminala.

1.3. Podstawowe koncepcje inżynierii oprogramowania

43

Nowości technologiczne często stwarzają nowe możliwości w zakresie formułowania wymagań funkcyjnych i pozafunkcyjnych. Jednym z typowych zadań programistów jest właśnie unowocześnianie istniejących systemów stosownie do możliwości stwarzanych przez nowe technologie. By podołać temu zadaniu, nie wystarczy rozumienie jego zachowania i aktualnej struktury komponentów: konieczne jest także poznanie i zrozumienie kontekstu, w jakim podejmowane były decyzje projektowe determinujące taką, a nie inną ich bieżącą postać. Ta dodatkowa wiedza nazywana jest racjonalizacją systemu. Pozyskiwanie tej wiedzy i jej efektywne wykorzystywanie nie są kwestiami banalnymi. Po pierwsze, każda decyzja projektowa to zwykle nic innego, jak dokonanie wyboru spośród wielu konkurencyjnych możliwości: wybór ten musi być dokładnie rozważony, oszacowany i uzasadniony. W konsekwencji racjonalizacja reprezentuje znacznie większy zasób informacji niż modele poszczególnych aspektów dziedziny realizacyjnej. Po drugie, informacja składająca się na racjonalizację zwykle nie jest osiągalna w jawnej postaci. Programiści często podejmują swe decyzje, bazując na intuicji i doświadczeniu, bez formalnego rozważania kryteriów oceny poszczególnych wariantów rozwiązania — pytani o uzasadnienie takiej czy innej decyzji, niekiedy muszą spędzić sporo czasu, by uzasadnienie to wyartykułować w sposób zrozumiały dla innych. W obliczu dynamiki zmieniającego się systemu takie uzasadnienia są jednak koniecznością.

1.3. Podstawowe koncepcje inżynierii oprogramowania Zaprezentowaliśmy właśnie wysokopoziomowe spojrzenie na inżynierię oprogramowania z perspektywy modelowania, rozwiązywania problemów, pozyskiwania wiedzy i racjonalizacji. W tej sekcji wyjaśnimy znaczenie podstawowych terminów oraz opiszemy najważniejsze koncepcje, jakie przewijać się będą przez całą treść książki4. A zatem projekt, którego celem jest stworzenie systemu informatycznego, urzeczywistniany jest poprzez różne aktywności. Każda aktywność obejmuje wykonywanie pewnej liczby zadań. Zadania powodują zużywanie przeznaczonych na nie zasobów, w zamian dostarczając produkty. Produktem może być system, model lub dokument, n a t o m i a s t p r z y k ł a d a m i zasobów są uczestnicy, czas i sprzęt. Zależności

te przedstawiono symbolicznie na rysunku 1.1. Każdy prostokąt reprezentuje tu pewną koncepcję, zaś przebiegające między prostokątami linie odzwierciedlają różne zależności między nimi — i tak na przykład romb symbolizuje agregację: projekt obejmuje pewną liczbę aktywności, z których każda obejmuje pewną liczbę zadań. Trójkąty symbolizują generalizację: uczestnicy, czas i sprzęt to szczególne przykłady zasobów. Nieprzypadkowo ilustracja ta ma formę notacji języka UML (Universal Modeling Language), ponieważ w tej książce używać będziemy tej notacji do reprezentowania licznych modeli — jest ona na tyle intuicyjna, że staje się zrozumiała nawet bez znajomości kompletnej semantyki UML: diagramy UML mogą być wykorzystywane nawet podczas dialogu z klientem, mimo iż ten może nie mieć pojęcia o istnieniu tego języka. A propos wspomnianej semantyki — opisujemy ją ze szczegółami w rozdziale 2. „Modelowanie w języku UML".

4

Staramy się przy tym zachować jak największą zgodność z definicjami standardów IEEE w zakresie inżynierii oprogramowania [IEEE Std. 610.12-1990].

44

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

Rysunek 1.1. Podstawowe koncepcje inżynierii oprogramowania przedstawione w formie diagramów klas UML [OMG, 2009]

1.3.1. Uczestnicy i role Tworzenie oprogramowania odbywa się przy udziale wielu ludzi, obdarzonych różnymi kwalifikacjami i przedstawiających różne kierunki własnych zainteresowań: klienci zamawiają system i za niego płacą, programiści fizycznie tworzą ten system, zaś menedżerowie projektu sprawują kontrolę nad harmonogramem i budżetem oraz koordynują interakcje między programistami a klientem. Użytkownicy systemu wykorzystują go do celów, które w ogóle przesądziły o jego stworzeniu. Każdą osobę zaangażowaną w realizację projektu nazywać będziemy jego uczestnikiem. Zbiór rozmaitych zakresów odpowiedzialności w związku z projektem lub systemem określać będziemy mianem roli. Każda rola związana jest z pewnym zbiorem zadań i przypisana określonemu uczestnikowi. Jednemu uczestnikowi przypisać można wiele ról. By lepiej zrozumieć powyższe pojęcia, przeanalizujmy opis systemu o nazwie Ticket "-•Di s t r i butor, który fizycznie jest komputerowym automatem do sprzedaży biletów. TicketDistributor ma być automatem sprzedającym bilety na pociąg. Podróżny ma mieć możliwość wyboru między biletem na pojedynczą podróż, na wiele podróży i karnetem ważnym jeden dzień lub cały tydzień. TicketDistributor ma wyliczać cenę żądanego biletu, bazując na danych geograficznych określających cel podróży, z uwzględnieniem ewentualnej ulgi wynikającej z młodego wieku podróżnego. TicketDistributor musi radzić sobie z rozlicznymi sytuacjami wyjątkowymi, na przykład niedokończonymi i anulowanymi transakcjami, niemożnością wydania reszty, wyczerpaniem zasobów (taśmy biletowej) czy awariami zasilania.

45

1.3. Podstawowe koncepcje inżynierii oprogramowania

G d y p o t r a k t o w a ć Ti cketDi stri butor j a k o p r o j e k t s y s t e m u i n f o r m a t y c z n e g o , m o ż n a

w jego kontekście zdefiniować przykładowe role, co zrobiliśmy w tabeli 1.1. Tabela 1.1. Przykładowe role w projekcie systemu Ti cketDi s t r i b u t o r

Rola

Zakres odpowiedzialności

Przykłady

Klient

Dostarczenie wysokopoziomowego zestawu wymagań dla systemu, zdefiniowanie w a r u n k ó w brzegowych projektu (daty ukończenia realizacji, budżetu, kryteriów jakościowych).

Kompania kolejowa zamawiająca system T i c k e t D i s t r i b u t o r

Użytkownik

Dostarczenie wiedzy na temat dziedziny aplikacyjnej, czyli zakładanych zachowań podróżnego kupującego bilet w automacie. Zauważmy, że role klienta i użytkownika przypisywane są zwykle różnym osobom.

Podróżny

Menedżer

Organizacja pracy: zatrudnienie personelu, przyporządkowanie zadań poszczególnym pracownikom, monitorowanie postępu pracy każdego z nich, zapewnienie im szkoleń i treningów, ogólna kontrola nad zasobami dostarczanymi przez klienta.

Alicja (szef)

Specjalista od czynnika ludzkiego

Zapewnienie użyteczności systemu.

Zoe (specjalista od interakcji „człowiek-komputer")

Programista

Konstrukcja systemu: specyfikacja, projekt, implementacja i testowanie. W p r z y p a d k u b a r d z o dużych systemów role poszczególnych programistów podlegają bardziej szczegółowemu zróżnicowaniu.

John (analityk), Marc (koder),

Sporządzenie d o k u m e n t a c j i czytelnej i zrozumiałej dla klienta, w drodze negocjacji z menedżerami, programistami i użytkownikami na temat stopnia zrozumiałości systemu.

John

Dokumentalista

5

Zoe (tester) 5

T i c k e t D i s t r i b u t o r jest systemem stosunkowo prostym, dlatego Zoe pełni podwójną rolę: specjalisty ds. czynnika ludzkiego i testera, podobnie podwójna rola — analityka i dokumentalisty — przypisana jest Johnowi.

46

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

1.3.2. Systemy i modele Określenia system używać będziemy w znaczeniu kolekcji połączonych ze sobą części. Modelowanie to sposób radzenia sobie ze złożonością systemów (i wszelkich zagadnień w ogólności) poprzez ignorowanie rzeczy nieistotnych w danym kontekście. Pisząc w tej książce model, mamy na myśli dowolną abstrakcję systemu. Wspomniany wcześniej Ti cketDi s t r i butor jest przykładem systemu, zaś jego modelami są: „niebieski podręcznik" (blueprint), schematy jego połączeń elektrycznych i modele obiektów składających się na jego oprogramowanie. Zauważmy, że projekt systemu sam jest systemem w przyjętym powyżej znaczeniu i jako taki sam stanowić może przedmiot modelowania — harmonogram, budżet i kolejne terminy deadline są właśnie jego modelami.

1.3.3. Produkty Produkt to coś użytecznego, co powstaje w trakcie realizacji systemu, na przykład dokument lub fragment programu, przeznaczony dla klienta lub na wewnętrzne potrzeby projektu — tę drugą kategorię nazywa się często produktem wewnętrznym, należący natomiast do kategorii pierwszej produkt określa się jako produkt docelowy. Te ostatnie definiowane są typowo jeszcze przed rozpoczęciem projektu, na podstawie kontraktu wiążącego wykonawcę z klientem. W tabeli 1.2 przedstawiliśmy krótki opis produktów związanych z systemem Ticket "-•Distributor. Tabela 1.2. Przykładowe produkty projektu Ti cketDi s t r i b u t o r

Produkt

Typ

Opis

Specyfikacja

Produkt docelowy

Specyfikacja opisuje system z perspektywy jego użytkowników. Stanowi swoisty k o n t r a k t między p r o j e k t e m a klientem. Specyfikacja systemu Ti cketDi s t r i butor to szczegółowy opis systemu w taki sposób, w jaki widzieć go będą korzystający z niego podróżni.

Podręcznik serwisowy

Produkt docelowy

Podręcznik serwisowy systemu Ti cketDi s t r i b u t o r przeznaczony jest dla personelu technicznego kompanii kolejowej, odpowiedzialnego za zainstalowanie i konfigurowanie tegoż systemu. Elementem wspomnianego konfigurowania może być zmiana ustawień spowodowana zmianami w zakresie cen biletów czy struktury połączeń kolejowych między poszczególnymi stacjami.

Raport o statusie projektu

Produkt wewnętrzny

Jest to bieżący raport o zadaniach, które już zostały zakończone, i tych właśnie wykonywanych. Taki raport sporządzany jest na użytek szefa (Alicji) i rzadko u d o s t ę p n i a n y klientom (kompanii kolejowej).

Podręcznik testera

Produkt wewnętrzny

Planowanie testów i dokumentowanie wyników testów już przeprowadzonych to domena testera (Zoe). Dokumenty takie zawierają opis znanych defektów, znalezionych w systemie Ti cketDi s t r i butor, i raport na temat stanu ich naprawiania. Tych dokumentów klient zazwyczaj nie ogląda.

1.3. Podstawowe koncepcje inżynierii oprogramowania

47

1.3.4. Aktywności, zadania i zasoby Aktywność jest zbiorem zadań ukierunkowanych na osiągnięcie pewnego celu. Przykładowo zbieranie wymagań jest aktywnością zmierzającą do zdefiniowania przez klienta jego oczekiwań względem powstającego systemu. Dostarczanie to aktywność, której celem jest zainstalowanie systemu w lokalizacji docelowej. Zarządzanie to aktywność polegająca na monitorowaniu i kontrolowaniu projektu pod względem zgodności z założonymi celami (terminy, jakość, budżet). Konkretna aktywność może mieć strukturę złożoną i składać się z kilku prostszych aktywności, na przykład aktywność dostarczania obejmuje dwie prostsze aktywności — zainstalowanie oprogramowania i przeszkolenie personelu. Aktywności nazywane są niekiedy fazami projektu. Zadanie reprezentuje niepodzielną jednostkę pracy, która może być odrębnie zarządzana. Menedżer przydziela zadanie programiście, ten zadanie to wykonuje, a menedżer monitoruje stan zaawansowania wykonania. Zadania powodują konsumowanie zasobów oraz generowanie produktów i zwykle zależne są od produktów generowanych przez inne zadania. Zasobami nazywamy wszelkie dobra wykorzystywane i (lub) zużywane do wykonania pewnego zadania. Zasobami są czas, sprzęt i praca. Podczas planowania projektu menedżer dokonuje rozdziału realizacji projektu na poszczególne zadania i z każdym z nich wiąże określoną porcję zasobów. W tabeli 1.3 opisaliśmy przykładowe aktywności, zadania i zasoby charakterystyczne dla inżynierii oprogramowania. Tabela 1.3. Przykłady aktywności, zadań i zasobów związanych z projektem Ti cketDi s t r i b u t o r

Przykład

Typ

Opis

Zbieranie wymagań

Aktywność

Obejmuje uzyskiwanie, od klientów i użytkowników, i weryfikowanie wymagań oraz wiedzy na temat dziedziny aplikacyjnej. Owocem zbierania wymagań jest specyfikacja (patrz tabela 1.2).

Realizacja przypadku testowego „nie m a m drobnych"

Zadanie

Zadanie to, przypisane testerowi (Zoe), koncentruje się na zweryfikowaniu zachowania systemu Ti cketDi s t r i b u t o r w sytuacji, gdy bieżący stan zasobów nominałów płatniczych w automacie uniemożliwia prawidłowe wydanie reszty podróżnemu. Konstruowanie przez Zoe przypadku testowego (które w opisywanym tu sensie jest aktywnością) obejmuje określenie środowiska testowego, sekwencji danych wprowadzanych do automatu i jego oczekiwanej reakcji.

Ocena adekwatności przypadku testowego „Szybka pomoc" pod względem użyteczności systemu pomocy dla podróżnych

Zadanie

Zadanie to, przydzielone Johnowi (specjaliście od czynnika ludzkiego), koncentruje się na wykrywaniu elementów deprecjonujących wspomnianą użyteczność.

Baza danych taryf i cennika

Zasób

Baza ta zawiera przykładowy plan taryfowy, wraz z przykładową siecią połączeń. Klient będzie ją oceniać pod względem zgodności ze swymi wymaganiami, a następnie wykorzystywać do testowania powstających fragmentów systemu.

48

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

1.3.5. Wymagania funkcyjne i pozafunkcyjne Wymagania to w istocie zestaw cech, jakie musi posiadać tworzony system. Wymagania funkcyjne określają funkcje, które system ten ma realizować. Pod określeniem wymagań pozafunkcyjnych rozumiemy rozmaite ograniczenia narzucone na system, które jednak nie odnoszą się bezpośrednio do realizowanych przez niego funkcji. I tak na przykład wymagania Podróżni muszą być w stanie kupować bilety lub Dla podróżnych musi być dostępna informacja taryfowa należą do grupy wymagań funkcyjnych; do wymagań pozafunkcyjnych można zaliczyć te w rodzaju Czas oczekiwania podróżnego na odpowiedź automatu musi być krótszy niż sekunda czy Kolorystyka interfejsu użytkownika musi być spójna z kolorystyką typową dla kompanii. Wymagania pozafunkcyjne mogą także obejmować różnorodne aspekty realizacyjne systemu, takie jak konkretna platforma sprzętowa, określona infrastruktura bezpieczeństwa, sposób radzenia sobie systemu z awariami i własnymi usterkami czy sposób zapewnienia kompatybilności z systemem wcześniej funkcjonującym.

1.3.6. Notacje, metody i metodologie Notacja to zestaw reguł reprezentowania modelu w sposób graficzny lub tekstowy. Alfabet rzymski jest notacją przeznaczoną do reprezentowania słów języka. UML (Unified Modeling Language [OMG, 2009]) — notacja, której konsekwentnie używamy w tej książce — jest językiem przeznaczonym do reprezentowania modeli zorientowanych obiektowo. Inżynieria oprogramowania od zawsze wykorzystywała rozmaite notacje, na długo przedtem, zanim pojawiły się techniki obiektowe. Diagramy przepływu danych [De Marco, 1978] to notacja reprezentacji systemu w kategoriach źródeł i ujść danych oraz transformacji danych. Notacja Z [Spivey, 1989] opiera się na koncepcji postrzegania systemu w kategoriach teorii mnogości. Metoda to powtarzalna technika określająca ciąg kroków obejmujących rozwiązywanie konkretnego problemu. Konkretny przepis z książki kucharskiej jest metodą przyrządzania określonego specjału (na przykład kremu czekoladowego). Każdy algorytm sortowania jest metodą porządkowania elementów listy. Zarządzanie racjonalizacją jest metodą uzasadniania zmian wprowadzanych do projektu. Zarządzanie konfiguracją to metoda śledzenia zmian. Metodologią nazywamy kolekcję metod związanych z określoną klasą problemów, wraz z określeniem, jak i kiedy należy używać każdej z tych metod. Książka kucharska poświęcona owocom morza jest metodologią przyrządzania potraw tej właśnie kategorii, o ile zawiera wskazówki dotyczące użycia określonych składników i radzenia sobie z sytuacjami, gdy — jak na złość — brakuje konkretnego składnika. Metodologia Royce'a [Royce, 1998], metodologia OMT (Object Modeling Technique OMT [Rumbaugh i in., 1991]), metodologia Boocha [Booch, 1994], Catalysis [D'Souza i Wills, 1999] to zorientowane obiektowo metodologie tworzenia oprogramowania. Metodologie procesu tworzenia oprogramowania dekomponują ten proces na rozmaite aktywności, przykładowo OMT dostarcza metody dla trzech aktywności: analizy, skupiającej się na formalizacji wymagań dla systemu w postaci modelu obiektowego, projektowania systemu, rozumianego jako podejmowanie strategicznych decyzji, oraz projektu obiektów, transformującego model stworzony na etapie analizy w model obiektowy, zdatny do zaimplementowania. Częścią metodologii OMT jest założenie, że wymagania zostały już zdefiniowane, nie

1.4. Aktywności inżynierii oprogramowania

49

dostarcza ona przeto żadnych metod zbierania wymagań. Metodologia jednolitego procesu (Unified Software Development Process)1' także obejmuje aktywność analizy, lecz w przeciwieństwie do OMT aktywności projektowania systemu i projektu obiektów połączone są w pojedynczą aktywność zwaną projektowaniem. Z kolei metodologia Catalysis, stosując tę samą notację, co jednolity proces, skupia się w większym stopniu na wielokrotnym wykorzystywaniu projektów i kodu, dostarczając odpowiednich wzorców i narzędzi do tego celu. Niezależnie jednak od opisanych różnic, wszystkie wymienione metodologie stworzone zostały w tym samym celu — w celu uporania się ze złożonością tworzonych systemów. W książce tej prezentujemy metodologię tworzenia złożonych systemów w obliczu koniecznych, dynamicznych zmian. W czasie prowadzonych przez nas kursów i badań ([Bruegge, 1992], [Bruegge i Coyne, 1993], [Bruegge i Coyne, 1994], [Coyne i in., 1995]) zaadaptowaliśmy i udoskonaliliśmy wiele metod zaczerpniętych z różnych źródeł. W związku z aktywnościami modelowania dziedziny aplikacyjnej, czyli zbieraniem wymagań i analizą, opisujemy metody podobne do przedstawionych w OOSE [Jacobson i in. 1992]. Dla aktywności związanych z modelowaniem dziedziny realizacyjnej, takich jak projektowanie systemu i projekt obiektów, przewidzieliśmy zorientowane obiektowo aktywności podobne do znanych z OMT. Wśród aktywności związanych z zarządzaniem zmianami szczególną uwagę poświęcamy zarządzaniu racjonalizacją, wzorując się na badaniach racjonalizacji projektowej [Moran & Carroll, 1996], oraz zarządzaniu konfiguracją w sposób zbliżony do opisanego [Babich, 1986] w związku z konserwowaniem dużych systemów.

1.4. Aktywności inżynierii oprogramowania W tej sekcji przedstawimy ogólny obraz technicznych aktywności związanych z obiektowym podejściem do tworzenia oprogramowania. Istotą tych aktywności jest radzenie sobie ze złożonością problemów poprzez konstruowanie i weryfikowanie modeli dziedziny aplikacyjnej i samego systemu. Aktywności te obejmują: • zbieranie wymagań (opisywane w sekcji 1.4.1), • analizę (sekcja 1.4.2), • projektowanie systemu (sekcja 1.4.3), • projekt obiektów (sekcja 1.4.4), • implementowanie (sekcja 1.4.5), • testowanie (sekcja 1.4.6). Na rysunku 1.2 przedstawiliśmy ogólny obraz zależności występujących pomiędzy wymienionymi aktywnościami i generowanymi przez nie produktami. W rozdziałach 14. „Zarządzanie projektem" i 15. „Cykl życiowy oprogramowania" dyskutujemy bardziej szczegółowo aktywności związane z modelowaniem i planowaniem.

6

Patrz sekcja 15.4.2 — przyp.

tłum.

50

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

Rysunek 1.2. Ogólny obraz aktywności związanych z obiektowo zorientowanym tworzeniem oprogramowania oraz produktów generowanych przez te aktywności. Powyższy diagram odzwierciedla jedynie logiczne zależności między produktami; obiektowo rozumiana inżynieria oprogramowania jest dyscypliną iteracyjną — niektóre aktywności mogą występować w wielu instancjach i równolegle z innymi

1.4.1. Zbieranie wymagań N a etapie zbierania w y m a g a ń klient i programiści definiują przeznaczenie systemu. Rezultatem tej a k t y w n o ś c i jest opis s y s t e m u w kategoriach a k t o r ó w i p r z y p a d k ó w użycia. A k t o r z y r e p r e z e n t u j ą byty z e w n ę t r z n e uwikłane w interakcję z systemem, m i ę d z y i n n y m i role u ż y t k o w n i k ó w ,

1.4. Aktywności inżynierii oprogramowania

51

zewnętrznych komputerów (serwera sieciowego, centralnego komputera bankowego) czy środowiska (którym może być na przykład proces chemiczny). Przypadkami użycia są ogólne sekwencje zdarzeń odzwierciedlające wszelkie możliwe akcje występujące między aktorami a systemem w kontekście określonego aspektu jego funkcjonalności. W tabeli 1.4 opisaliśmy przypadki użycia naszego przykładowego systemu Ti cketDi s t r i butor. Samo zbieranie wymagań, wraz z przypadkami użycia oraz uwzględnieniem podziału na wymagania funkcyjne i pozafunkcyjne, opiszemy szczegółowo w rozdziale 4. „Zbieranie wymagań". Tabela 1.4. Przykładowy przypadek użycia— PurchaseOneWayTicket Nazwa przypadku Aktor

testowego

uczestniczący

Sekwencja

zdarzeń

PurchaseOneWayTi cket Traveler 1. Travel er wybiera strefę, w której zlokalizowana jest stacja docelowa 2. Ti cketDi s t r i b u t o r wyświetla cenę biletu. 3. Travel e r wprowadza do automatu środki płatnicze w wysokości nie niższej niż cena biletu. 4. Ti cketDi s t r i butor drukuje żądany bilet i wydaje ewentualną resztę.

Warunek

wstępny

Travel e r staje przed automatem zlokalizowanym na stacji początkowej lub dowolnej stacji pośredniej.

Warunek

końcowy

Travel e r jest w posiadaniu żądanego biletu i zwróconej nadpłaty.

Wymagania

jakościowe

Jeżeli transakcja nie została ukończona, a bezczynność Travel era trwa dłużej niż minutę, Ti cketDi s t r i butor zwraca ewentualną gotówkę wprowadzoną już przez Travel e r a w ramach bieżącej transakcji.

1.4.2. Analiza Na etapie analizy programiści dążą do stworzenia modelu systemu — poprawnego, kompletnego, spójnego i wolnego od niejednoznaczności. Dokonują transformacji przypadków użycia, zdefiniowanych podczas zbierania wymagań, w model obiektowy, który jest kompletnym opisem systemu. Ewentualne przejawy niejednoznaczności i niespójności w modelu przypadków użycia wyjaśniane są w drodze negocjacji z klientem. Rezultatem analizy jest model systemu wzbogacony o atrybuty, operacje i skojarzenia. Model ten daje się opisać w kategoriach struktury systemu i jego dynamicznego współdziałania. Na rysunku 1.3 przedstawiamy przykładowy model dynamiczny systemu Ti cketDi s t r i butor, zaś na rysunku 1.4 — jego model obiektowy. Etap analizy, z uwzględnieniem modeli obiektowych, opisujemy z detalami w rozdziale 5. „Analiza wymagań", zaś szczegóły notacji UML wykorzystywanej do reprezentowania obiektów wyjaśniamy w rozdziale 2. „Modelowanie w języku UML".

1.4.3. Projekt systemu W czasie projektowania systemu programiści definiują cele projektu i dekomponują system na mniejsze podsystemy, z których każdy realizowany będzie przez odrębny zespół. Programiści dokonują także wyboru strategii budowania systemu, m.in. platformy sprzętowej i systemu

52

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

Rysunek 1.3. Model dynamiczny systemu Ti cketDi s t r i butor (diagram sekwencyjny UML). Na diagramie tym uwidoczniliśmy obiekty będące elementami przypadku użycia PurchaseOneWayTi cket oraz interakcje zachodzące między systemem a aktorem Travel e r w ramach tego przypadku

Rysunek 1.4. Model obiektowy systemu Ti cketDi s t r i b u t o r (diagram klas UML). W ramach przypadku użycia PurchaseOneWayTi cket aktor Traveler inicjuje transakcję, reprezentowaną przez obiekt Transaction, której rezultatem jest wyprodukowanie obiektu Ticket. Bilet reprezentowany przez ten obiekt ważny jest tylko w strefie reprezentowanej przez obiekt Zone. W ramach wspomnianej transakcji system kontroluje jej bilans (reprezentowany przez obiekt Bal ance), zliczając narastająco wartość wprowadzanych do automatu monet (Coi n) i porównując tę wartość z ceną biletu (Bi 11)

operacyjnego, w środowisku których działać ma docelowo projektowany system, a także technologii trwałego przechowywania danych, globalnej kontroli przepływu sterowania, reguł polityki dostępu i sposobu obsługi warunków granicznych. W rezultacie system jawi się jako klarowny opis każdej z tych strategii, jako zespół podsystemów, a także jako diagram wdrożeniowy w postaci odwzorowania poszczególnych jego aspektów na komponenty sprzętowe i programowe. Mimo iż zarówno analiza, jak i projekt systemu skutkują powstawaniem modeli tworzonego systemu, to jedynie analiza obejmuje encje zrozumiałe dla klienta: projekt systemu to operowanie bardziej zaawansowanymi i ulepszonymi modelami, zawierającymi elementy wykraczające poza możliwość zrozumienia (i zainteresowanie) klienta. Na rysunku 1.5 widzimy przykład dekompozycji systemu Ti cketDi s t r i butor. Projekt systemu i powiązane z nim koncepcje opisujemy szczegółowo w rozdziałach 6. „Projektowanie systemu — dekompozycja na podsystemy" i 7. „Projekt systemu: realizacja celów projektowych".

1.4. Aktywności inżynierii oprogramowania

53

Rysunek 1.5. Przykład dekompozycji systemu Ti cketDi s t r i butor (diagram klas UML — pakiety reprezentują podsystemy, a linie przerywane — zależności między nimi). Podsystem Travel e r l n t e r f a c e odpowiedzialny jest za kolekcjonowanie informacji wejściowych i sprzężenie zwrotne z podróżnym (między innymi wyświetlenie ceny biletu, wydrukowanie biletu, wydanie reszty). Zadaniem podsystemu Local T a r i f f jest obliczenie ceny biletu na podstawie cennika zapisanego w lokalnej bazie danych. Podsystem Central Tari f f, zlokalizowany na centralnym komputerze, ma za zadanie utrzymywanie kopii referencyjnej bazy danych taryfowych; zapewnienie zgodności z tą kopią każdej lokalnej bazy w poszczególnych egzemplarzach systemu T i c k e t D i s t r i b u t o r jest zadaniem podsystemu Updater

1.4.4. Projektowanie obiektów W czasie projektowania obiektów programiści definiują obiekty dziedziny realizacyjnej. Zadaniem tych obiektów jest zniwelowanie luki między modelem zbudowanym na etapie analizy a platformą sprzętową i programową zdefiniowaną na etapie projektu systemu. Sprowadza się to do precyzyjnego opisywania interfejsów poszczególnych obiektów i podsystemów, do wyboru odpowiednich gotowych komponentów („z półki"), do restrukturyzowania modelu obiektowego w celu osiągnięcia takich celów projektowych jak rozszerzalność oraz zrozumiałość i wreszcie do optymalizowania tego modelu pod kątem wydajności tworzonego systemu. Owocem tej aktywności jest szczegółowy model obiektowy opatrzony adnotacjami wyjaśniającymi istniejące ograniczenia oraz dostarczającymi szczegółowe opisy każdego elementu. Opis projektowania obiektów, wraz z koncepcjami pokrewnymi, będzie treścią rozdziałów 8. „Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych" i 9. „Projektowanie obiektów: specyfikowanie interfejsów".

1.4.5. Implementowanie Implementowanie to tłumaczenie dziedziny realizacyjnej na kod źródłowy aplikacji. W praktyce oznacza to kodowanie atrybutów i metod każdego obiektu w języku programowania i integrowanie wszystkich obiektów w funkcjonujący pojedynczy system. Aktywność implementowania wypełnia lukę pomiędzy uszczegółowionym projektem obiektów a kompletnym, kompilowalnym kodem źródłowym. Czynność mapowania modeli UML w kod źródłowy opiszemy szczegółowo w rozdziale 10. „Odwzorowywanie modelu na kod"; zakładamy, że czytelnikom nieobce są podstawowe koncepcje programistyczne oraz umiejętność programowania struktur danych i algorytmów w języku zorientowanym obiektowo, takim jak Java czy C++.

54

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

1.4.6. Testowanie Testowanie to etap wynajdywania różnic między urzeczywistnionymi fragmentami systemu a ich modelami, za pomocą specjalnie przygotowanych w tym celu danych wejściowych. I tak w czasie testowania modułów programiści porównują modele stworzone na etapie projektowania obiektów z każdym zaimplementowanym modelem i podsystemem; testy integracyjne to weryfikacja prawidłowości połączenia podsystemów w całość, w kontekście modelu stworzonego na etapie projektowania systemu. Wreszcie, na etapie testowania systemowego następuje konfrontacja zachowania się systemu w nietypowych i wyjątkowych sytuacjach z modelem stworzonym na etapie zbierania wymagań. Celem testowania jest wykrycie jak największej liczby usterek i poprawienie ich przed dostarczeniem systemu klientowi. Planowanie poszczególnych rodzajów testów odbywa się równolegle z innymi fazami tworzenia systemu — i tak testy systemowe planowane są równolegle ze zbieraniem wymagań i analizą, planowanie testów integracyjnych towarzyszy projektowaniu systemu, zaś projektowanie obiektów połączone jest z planowaniem testów modułowych. Zależności te opiszemy dokładniej w rozdziale 11. „Testowanie".

1.5. Zarządzanie tworzeniem oprogramowania W tej sekcji opiszemy pokrótce aktywności związane z zarządzaniem projektami informatycznymi. Aktywności te koncentrują się na planowaniu projektu, monitorowaniu jego statusu, śledzeniu zmian i koordynowaniu wykorzystania zasobów, z intencją dostarczenia wysokiej jakości produktu na czas i zgodnie z zaplanowanym budżetem. Są one udziałem nie tylko menedżerów, ale także wielu innych uczestników. Obejmują między innymi: • komunikację (patrz sekcja 1.5.1), • zarządzanie racjonalizacją (patrz sekcja 1.5.2), • zarządzanie konfiguracją (patrz sekcja 1.5.3), • zarządzanie projektem (patrz sekcja 1.5.4), • cykl życiowy oprogramowania (patrz sekcja 1.5.5). Gdy gotowy system zostanie przekazany klientowi, rozpoczyna się proces, który nazwać można ogólnie utrzymaniem lub konserwację tegoż systemu. W książce nie będziemy zajmować się tą tematykę, ograniczymy się do kilku uwag w tym miejscu. Tradycyjnie konserwację powierza się zespołowi innemu niż ten, który system wyprodukował; czyni się tak ze względu na fakt, że jest ona procesem wybitnie sterowanym zachodzącymi zmianami i z definicji oddzielonym od tworzenia systemu. Ponieważ jednak współczesne projekty informatyczne także realizowane są w obliczu dynamicznych zmian, granica między jednym a drugim staje się cokolwiek płynna; w rezultacie wiele opisywanych w tej książce aktywności — projektowanie obiektów, implementowanie, testowanie, zarządzanie racjonalizacją i konfiguracją — realizowanych jest także w fazie konserwacji.

1.5. Zarządzanie tworzeniem oprogramowania

55

1.5.1. Komunikacja Komunikacja to najbardziej krytyczna i czasochłonna aktywność inżynierii oprogramowania. Nieporozumienia i przeoczenia często prowadzą do powstawania usterek i opóźnień, kosztownych i trudnych do naprawienia w późniejszych fazach. Komunikacja obejmuje wymianę modeli i dokumentów dotyczących systemu i jego dziedziny aplikacyjnej, raportowanie statusu produktów i realizowanie sprzężenia zwrotnego dotyczącego ich jakości, formułowanie i negocjowanie problemów oraz oznajmianie różnorodnych decyzji. Komunikacja staje się utrudniona ze względu na różnice między kwalifikacjami poszczególnych uczestników, na ich rozproszenie geograficzne oraz ze względu na kompleksowość, rozmiar i ewoluowanie wymienianej informacji. Aby sprawnie radzić sobie z tymi trudnościami, uczestnicy mają do dyspozycji wiele metod i narzędzi, wśród których do najbardziej efektywnych zaliczyć należy wszelkiego rodzaju konwencje: gdy uczestnicy porozumieją się co do notacji reprezentującej informację, co do narzędzi służących do jej przetwarzania oraz procedur zgłaszania i negocjowania problemów, eliminują tym samym dość spory repertuar potencjalnych źródeł rozmaitych nieporozumień. Przykładem takiej uzgodnionej notacji mogą być diagramy UML, szablony różnych dokumentów czy schematy nazewnictwa poszczególnych komponentów aplikacji. Do narzędzi tego rodzaju zaliczyć można te z kategorii CASE (Computer Aided Software Engineering — „wspomagana komputerowo inżynieria oprogramowania") służące do operowania na modelach, edytory tekstów i standardy formatów publikowania informacji. Przykładowymi procedurami komunikacyjnymi mogą być wzorcowe zasady organizowania, prowadzenia i dokumentowania spotkań roboczych, procedury analizowania i oceniania dokumentów czy procedury inspekcyjne zmierzające do wykrywania defektów w modelach i kodzie źródłowym. Uzgodnione konwencje nie muszą być „najlepsze iż najlepszych": ważne jest, by były zrozumiałe dla wszystkich uczestników i przez wszystkich akceptowane. Zagadnienia związane ze sprawnym komunikowaniem się opisujemy w rozdziale 13. „Zarządzanie konfiguracją".

1.5.2. Zarządzanie racjonalizacją Racjonalizacja to szeroko rozumiane usprawiedliwianie każdej podejmowanej decyzji, między innymi w aspekcie zestawu problemów, rozwiązaniu których miała służyć, jej wyboru spośród wielu różnych rozwiązań, kryteriów, jakimi kierowali się programiści przy ocenianiu tych rozwiązań, czy debat i negocjacji, jakie w związku z tym prowadzili. Racjonalizacja to najważniejsza informacja, jaką posiadać muszą programiści przystępujący do dokonywania zmian w systemie: gdy nieoczekiwanie zmienią się jakieś okoliczności czy kryteria, programiści mogą ponownie rozważyć wszystkie decyzje zależne od tych okoliczności lub kryteriów. Gdy pojawi się nowa alternatywa, możliwe będzie jej porównanie ze wszystkimi innymi rozwiązaniami, jakich oceny już wcześniej dokonano. W sytuacji gdy jakaś decyzja zostanie zakwestionowana, stojąca za nią racjonalizacja staje się sposobem jej obrony. Niestety, racjonalizacja należy do najbardziej złożonych rodzajów informacji, z którymi mają do czynienia programiści, jako taka jest więc najtrudniejsza do utrzymywania i aktualizowania. By radzić sobie z tymi trudnościami, programiści dążą do reprezentowania racjonalizacji w formie różnorodnych modeli budowanych w rezultacie spotkań oraz dyskusji online i aktualizowanych stosownie do zachodzących zmian. Zajmiemy się szerzej tą tematyką w rozdziale 12. „Zarządzanie racjonalizacją.

56

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

1.5.3. Zarządzanie konfiguracją oprogramowania Istotą zarządzania konfiguracją jest monitorowanie i kontrolowanie zmian w zakresie produktów. Zmiany są nieodłącznym elementem tworzenia oprogramowania. Gdy klient żąda nowych cech aplikacji lub gdy programiści coraz lepiej rozumieją poszczególne aspekty dziedziny aplikacyjnej, mamy do czynienia ze zmianami w wymaganiach; gdy pojawiają się nowe technologie, zmienia się platforma sprzętowa i (lub) programowa, na której system jest realizowany; wykrywanie i poprawianie błędów podczas testowania oznacza zmiany w samym systemie. Do niedawna jeszcze wymienione zmiany były raczej typowe dla konserwacji eksploatowanego systemu, jednak współczesne projekty programistyczne doznają rozmaitych zmian w stopniu bodaj jeszcze większym, wskutek czego z zarządzaniem konfiguracją mamy do czynienia we wszystkich fazach ich realizacji. Zarządzanie konfiguracją umożliwia programistom śledzenie zmian. Z perspektywy tego zarządzania system reprezentowany jest jako zbiór elementów konfiguracyjnych, z których każdy może być kontrolowany i korygowany niezależnie od pozostałych. Ewolucja każdego z tych elementów oznacza powstawanie kolejnych wersji systemu; utrzymywanie poszczególnych wersji daje możliwość cofnięcia się do dobrze określonego stanu, w sytuacji gdy wprowadzone do systemu zmiany okażą się niewłaściwe. Zarządzanie konfiguracją daje także możliwość kontrolowania zmian. Gdy zdefiniowana zostanie pewna „linia bazowa", wszelkie zmiany muszą zostać przeanalizowane i zaaprobowane przed ich zaimplementowaniem. Menedżerowie zyskują dzięki temu pewność, że system ewoluuje zgodnie z założonymi celami projektowymi i że liczba problemów, jakie mogą się w związku z tym pojawić, pozostaje na niskim poziomie. Powrócimy do tej kwestii w rozdziale 13. „Zarządzanie konfiguracją".

1.5.4. Zarządzanie projektem Zarządzanie projektem ma tę specyfikę, że nie manifestuje się samo przez się w postaci jakiejś specyficznej aktywności. Ma raczej charakter nadzoru zapewniającego, że system ukończony zostanie w terminie i zgodnie z założonym budżetem, bez jakiegokolwiek uszczerbku dla jakości. W praktyce nadzór taki polega na planowaniu harmonogramu i budżetu w połączeniu z negocjacjami z klientem, zatrudnianiu programistów i organizowaniu ich w zespoły, monitorowaniu statusu realizacji projektu i interweniowaniu w sytuacjach wyjątkowych. Opis większości tych aktywności wykracza poza ramy tej książki, ograniczymy się zatem do tych, które bezpośrednio odczuwalne są przez programistów, zwrócimy także uwagę na techniki usprawniające komunikację między programistami a menedżerami. Tematykę tę znajdą czytelnicy w rozdziale 14. „Zarządzanie projektem".

1.5.5. Cykl życiowy oprogramowania W tym rozdziale opisujemy inżynierię oprogramowania w kategoriach aktywności modelowania. Programiści budują modele dziedziny aplikacyjnej oraz dziedziny realizacyjnej, koncentrując się jedynie na szczegółach istotnych w danym kontekście i ignorując pozostałe — dzięki temu mogą efektywniej rozwiązywać konkretne problemy i udzielać wiarygodnych odpowiedzi. Jako że proces tworzenia systemu sam może być traktowany jako swoisty system, ze swymi

1.6. Analiza przypadku — system ARENA

57

specyficznymi danymi wejściowymi i wyjściowymi, aktywnościami i zasobami, przeto nie powinien zaskakiwać fakt, iż może on być modelowany przy użyciu tych samych technik, które charakterystyczne są dla inżynierii oprogramowania. Ogół modeli tego rodzaju, zwany cyklem życiowym oprogramowania, opiszemy z detalami w rozdziale 15. „Cykl życiowy oprogramowania".

1.5.6. Podsumowanie Treść rozdziałów od 1. do 15. tej książki odzwierciedla bieżący stan wiedzy na temat metod obiektowo zorientowanej inżynierii oprogramowania. Czytelnicy mogą potraktować tę treść jako zawartość swoistej książki kucharskiej — problem jednak w tym, że książka kucharska rzadko jest wystarczająca dla nowicjusza sztuki kulinarnej: nie zdoła on przygotować kompletnego posiłku, polegając wyłącznie na książce kucharskiej. Gdy zabraknie mu któregoś składnika i jest zmuszony improwizować, nie zda się ona na nic. Dwa rozdziały: 14. „Zarządzanie konfiguracją", o treści skupiającej się na planowaniu i kontrolowaniu projektów, oraz 15. „Cykl życiowy oprogramowania" poświęcony modelowaniu, usprawnianiu i powtarzalności procesów składających się na cykl życiowy aplikacji, koncentrujące się na technikach i modelach, niosą optymistyczne raczej przesłanie w kwestii realizowania projektów informatycznych. W rozdziale 16. „Wszystko razem, czyli metodologie" zajmujemy się jednak sytuacjami wymagającymi wyjścia poza tradycyjną wiedze podręcznikową: opisujemy metodologie i heurystyki służące realizowaniu koncepcji opisywanych w poprzednich rozdziałach, tyle że w specyficznych warunkach, przedstawiamy kilka „zwinnych" i mniej zwinnych metodologii.

1.6. Analiza przypadku — system ARENA Jednym z naszych założeń dotyczących tej książki jest stopniowanie trudności. W każdym z rozdziałów wprowadzamy rozmaite koncepcje i opisujemy różnorodne aktywności, posiłkując się coraz to bardziej skomplikowanymi przykładami — od banalnych zadań, odpowiednich na klasówkę, do prawdziwie złożonych problemów zaczerpniętych bądź to z różnych kursów, bądź też z faktycznie realizowanych projektów. Ponadto aby umiejscowić opisywane koncepcje w ogólnym kontekście inżynierii oprogramowania, posługujemy się obszerną analizą przypadku, jakim jest system o nazwie ARENA. ARENA to wielodostępny, webowy system służący do organizowania i przeprowadzania rozgrywek. System ten jest niezależny od konkretnej gry w tym sensie, że organizator może zaadaptować nową grę do standardów interfejsu ARENY, wysłać ją na serwer i natychmiast zaanonsować jej obecność graczom i obserwatorom zlokalizowanym gdziekolwiek w internecie. Organizatorzy mają możliwość definiowania nowych stylów rozgrywek, poprzez określenie szczegółowych zasad wygranej oraz przegranej i sporządzania rankingów zgodnie z tymi zasadami. By zrekompensować sobie koszty operacyjne, organizatorzy mają ponadto możliwość umieszczania w scenerii gry banerów zawierających reklamy sponsorów. W kończących każdy z rozdziałów sekcjach zatytułowanych „ARENA — analiza przypadku" dyskutujemy rozmaite kwestie związane z systemem ARENA, rozważamy różnorodne decyzje projektowe i kompromisy wynikające z treści poszczególnych rozdziałów, a także nawiązujemy do końcowych sekcji poprzednich rozdziałów, uwydatniając w ten sposób fakt wzajemnych powiązań między nimi.

58

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

• I tak na przykład w rozdziale 4. „Zbieranie wymagań" pokazujemy zaczątki tworzenia zbioru przypadków użycia na podstawie informacji dostarczanej przez klienta: definiujemy sposób organizowania i rozgrywania turniejów oraz zasady uczestnictwa graczy w tych turniejach. Zadając klientowi coraz więcej pytań, pozbywamy się kolejnych niejasności i odkrywamy luki w koncepcji systemu. • W rozdziale 5. „Analiza wymagań" opisujemy konstruowanie modelu obiektowego i modelu zachowania na podstawie modelu przypadku użycia. Pokazujemy także, jak tworzenie owych modeli prowadzi do ulepszania modelu przypadku użycia i odkrywania dodatkowych wymagań — definiujemy na przykład w sposób bardziej formalny koncepcję wyłącznego sponsoringu i sposób decydowania o sponsorowaniu turnieju; demonstrujemy także konsolidowanie modelu obiektowego. • W rozdziale 7. „Projekt systemu: realizacja celów projektowych" dokonujemy wyboru architektury klient-serwer i frameworku dla realizacji systemu, ustalamy także sposób przechowywania danych i kontroli dostępu. Omawiamy różnorodne mechanizmy uwierzytelniania użytkowników w internecie, identyfikujemy obiekty wymagające przechowywania w sposób trwały (stan gry, wyniki rozgrywek, profile graczy i tym podobne) i wreszcie przeprowadzamy dekompozycję systemu ARENA na prostsze podsystemy, z których każdy staje się możliwy do ogarnięcia przez pojedynczego programistę. • W rozdziale 8. „Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych" rozpoznajemy dodatkowe obiekty dziedziny realizacyjnej, umożliwiające wypełnienie luki między projektem systemu a jego implementacją. Wykorzystujemy wielokrotnie te same rozwiązania szablonowe, poprzez wybór wzorców projektowych stosownych dla specyficznych problemów — przykładowo wzorzec „strategia" wykorzystywany jest do enkapsulacji różnych stylów rozgrywek. • Rozdział 10. „Odwzorowywanie modelu na kod" poświęcony jest translacji zbudowanych modeli UML na kod w języku Java oraz analizowaniu projektu obiektów pod kątem tkwiących w nich możliwości optymalizacyjnych. Tym samym rozdział ten jest ilustracją ścisłego związku iteracyjnego między projektem obiektów a implementacją. Produkty związane z systemem ARENA, wraz z jego wersją demonstracyjną, dostępne są pod adresem http://wwwbruegge.in.tum.de/OOSE/WebHome.

1.7. Literatura uzupełniająca Podstawowe, fundamentalne problemy związane z inżynierią oprogramowania nie są wynalazkiem dnia dzisiejszego — bogate ślady ich obecności znaleźć można z łatwością w literaturze ostatnich dziesięcioleci. I tak w swojej książce [Brooks, 1995], wydanej po raz pierwszy w roku 1975, F. Brooks snuje refleksje na temat własnych doświadczeń związanych z konstruowaniem systemu operacyjnego dla komputera mainframe IBM/360. Projekt wyceniony na miliony dolarów, zaplanowany na kilka lat, nie został zrealizowany w terminie i nie zmieścił się w zakładanym

1.8. Ćwiczenia

59

budżecie. Od tego czasu minęło wiele lat, programiści dostali do swych rąk nowe technologie, narzędzia i metody — po to, by dzielniej zmagać się z kolejnymi problemami, coraz bardziej kosztownymi coraz bardziej spektakularnymi. Wiele podstawowych lekcji wyniesionych z doświadczeń minionych dekad wciąż zachowuje swą aktualność. Książka P. Neumanna [Neumann, 1995] zawiera ciekawy przegląd rozmaitych nieszczęść spowodowanych (ogólnie rzecz ujmując) wadami technologii informatycznych. Autor, uświadamiając czytelnikom powagę konsekwencji, jakie nieść może ze sobą ułomność procesu wytwarzania oprogramowania, rozważa jednocześnie przyczyny opisywanych niepowodzeń i dostarcza wskazówki pomagające unikać takich i podobnych incydentów. Tę naprawdę rzeczową książkę przeczytać powinien każdy programista, któremu choćby tylko marzy się tworzenie skomplikowanych systemów informatycznych. Książka K. Poppera [Popper, 1992] to esej na temat konstruowania wiedzy. Autor zrywa z tradycyjnymi teoriami w tym względzie, datującymi się jeszcze z czasów Arystotelesa, przedstawiając własny punkt widzenia, zgodnie z którym wiedza naukowa, od kiedy tylko zaistniała jako pojęcie, rozwija się jako odrębna całość poprzez selekcję. Jako że tworzenie oprogramowania jest procesem zespołowym, związanym ze zdobywaniem wiedzy i konstrukcją, wspomniana książka może być użyteczna jako zachęcająca do krytycznych przemyśleń i cechująca się niecodziennym spojrzeniem na inżynierię oprogramowania. W tej książce skupiamy się na obiektowo zorientowanej inżynierii oprogramowania i adresujemy ją do zaawansowanych kursów o tej tematyce. W konsekwencji pomijamy więc wiele zagadnień o charakterze historycznym lub czysto menedżerskim, takich jak metryki oprogramowania, szacowanie kosztów czy metody formalne konstruowania programów. Przegląd tych zagadnień zainteresowani czytelnicy znajdą w podręcznikach o bardziej ogólnym charakterze, takich jak na przykład autorstwa I. Sommerville'a [Sommerville, 2006] czy R. S. Pressmana [Pressman, 2009].

1.8. Ćwiczenia 1.1. Co jest celem modelowania? 1.2. Język programowania to notacja przeznaczona do reprezentowania algorytmów i struktur danych. Wymień dwie zalety i dwie wady użytkowania tej notacji jako jedynej w procesie tworzenia aplikacji. 1.3. Wyobraź sobie, że stajesz w obliczu realizacji zadania, na temat którego masz nikłe pojęcie — na przykład przed skonstruowaniem samochodu w ogóle nieprodukującego spalin. Jak zabrałbyś się do takiego zadania? 1.4. Jak rozumiesz stwierdzenie „pozyskiwanie wiedzy nie jest procesem sekwencyjnym"? Podaj konkretny przykład uzasadniający prawdziwość tego stwierdzenia. 1.5. Spróbuj wymyślić hipotetyczną racjonalizację dla następujących decyzji projektowych: • Automat realizujący system Ti cketDi s t r i butor nie może być wyższy niż półtora metra. • System Ti cketDi s t r i butor powinien być zrealizowany w oparciu o dwa dublujące się komputery.

60

Rozdział 1. • Wprowadzenie do inżynierii oprogramowania

• Automat realizujący system Ti cketDi s t r i butor powinien komunikować się z użytkownikiem za pomocą ekranu dotykowego, wyświetlającego informacje i umożliwiającego wprowadzanie poleceń, przy czym w każdej chwili powinno być możliwe anulowanie rozpoczętej transakcji przez naciśnięcie jednego przycisku. 1.6. Które z poniższych wymagań mają charakter funkcyjny, a które pozafunkcyjny? • System TicketDistributor powinien umożliwiać użytkownikowi zakup biletu tygodniowego. • Kod systemu Ti cketDi s t r i butor powinien być napisany w języku Java. • System Ti cketDi s t r i butor musi być łatwy w użytkowaniu. • System T i c k e t D i s t r i b u t o r musi być dostępny bez przerwy. • System Ti cketDi s t r i butor powinien wskazywać użytkownikowi numer telefonu, pod którym może on uzyskać niezbędną pomoc w przypadku awarii i innych nieoczekiwanych zdarzeń. 1.7. Jak myślisz, które z poniższych decyzji podjęte zostały w fazie zbierania wymagań, a które na etapie projektowania systemu: • System TicketDistributor składa się z następujących podsystemów: interfejsu użytkownika, obliczania ceny zgodnie z taryfą i komunikacji z centralnym komputerem. • Platforma sprzętowa systemu T i c k e t D i s t r i b u t o r oparta jest na procesorze PowerPC. • System Ti cketDi s t r i butor oferuje użytkownikowi system pomocy online. 1.8. W których miejscach poniższego opisu termin „konto" odnosi się do dziedziny aplikacyjnej, a w których do dziedziny realizacyjnej? „Opracowywany jest system obsługi kont bankowych dla mobilnych klientów. Jednym z podstawowych problemów tego zadania jest zapewnienie użytkownikowi dostępu do konta nawet wówczas, gdy nie może on nawiązać połączenia z serwerem. Jedna z propozycji zakłada dostęp do konta poprzez mobilny komputer nawet wówczas, gdy serwer jest niedostępny lub nieczynny — w takiej sytuacji lokalne konto we wspomnianym komputerze zawierać będzie informację aktualną w momencie ostatniego połączenia z serwerem". 1.9. Jaka jest różnica między zadaniem a aktywnością? 1.10. Samolot pasażerski składa się z wielu milionów komponentów i produkowany jest z udziałem tysięcy ludzi różnych specjalności. Podobnie złożonym przedsięwzięciem jest budowa mostu, przez który biegnie czteropasmowa autostrada. Pierwsza wersja MS Word dla Windows, udostępniona w 1989 roku — cztery lata po zakładanym terminie — zrealizowana została kosztem 55 roboczolat w postaci aplikacji zawierającej 249 000 wierszy kodu źródłowego. Samoloty i mosty realizowane są zazwyczaj w terminie i zgodnie z budżetem, czego przeważnie nie można powiedzieć o projektach programistycznych. Gdzie Twoim zdaniem leży przyczyna tej różnicy?

61

Bibliografia

Bibliografia [Babich, 1986]

W. Babich Software Configuration Management, Addison-Wesley, Reading, MA, 1986.

[Booch, 1994]

G. Booch Object-Oriented Analysis and Design with Applications, wyd. drugie, Benjamin/Cummings, Redwood City, CA, 1994.

[Brooks, 1995]

F. P. Brooks The Mythical Man Month: Anniversary Edition: Essays on Software Engineering, wyd. Addison-Wesley, Reading, MA, 1995.

[Bruegge, 1992]

B. Bruegge „Teaching an industry-oriented software engineering course", Software Engineering Education, SEI Conference, Lecture Notes in Computer Sciences, t. 640, str. 65 - 87, Springer-Verlag, październik 1992.

[Bruegge i Coyne, 1993]

B. Bruegge, R. Coyne „Model-based software engineering in larger scale project courses", IFIP Transactions on Computer Science and Technology, t. A-40, str. 273 - 287, Elsevier Science, Netherlands, 1993.

[Bruegge i Coyne, 1994]

B. Bruegge, R. Coyne „Teaching iterative object-oriented development: Lessons and directions", w: J. L. Diaz-Herrera (red.), 7h Conference on Software Engineering Education, Lecture Notes in C o m p u t e r Science, t. 750, str. 413 - 427, Springer-Verlag, 1994.

[Coyne i in., 1995]

R. Coyne, B. Bruegge, A. Dutoit, D. Rothenberger „Teaching m o r e c o m p r e h e n s i v e model-based software engineering: Experience with Objectory's use case approach", w: L. Ibraham (red.), 8'k Conference on Software Engineering Education, Lecture Notes in C o m p u t e r Science, str. 339 - 374, Springer-Verlag, kwiecień 1995.

[De Marco, 1978]

T. De M a r c o Structured N e w York, 1978.

[D'Souza i Wills, 1999]

D. F. D'Souza, A. C. Wills Objects, Components, and Frameworks with UML: The Catalysis Approach, Addison-Wesley, Reading, MA, 1999.

[IEEE Std. 610.12-1990]

IEEE Standard Computer Dictionary: A Compilation ComputerGlossaries, NY, 1990.

[Jacobson i in. 1992]

I. Jacobson, M. Christerson, P. Jonsson, G. Overgaard Object-Oriented Software Engineering — A Use Case Driven Approach, Addison-Wesley, Reading, MA, 1992.

[Moran i Carroll, 1996]

T. P. Moran i J. M. Carroll (red.) Design Rationale: Concepts, and Use, Lawrence Erlbaum Associates, Mahwah, NJ, 1996.

[Neumann, 1995]

P. G. Neumann Computer-Related Risks, Addison-Wesley, Reading, MA, 1995.

[OMG, 2009]

Object Management Group, OMG Unified Modeling Language Specification. Version 2.2, 2009. http://www.omg.org.

[Popper, 1992]

K. P o p p e r Objective Knowledge: An Evolutionary O x f o r d , 1992.

[Pressman, 2009]

R S. Pressman Software Engineering: A Practitioner's Approach, wyd. siódme, McGraw-Hill, 2009.

[Royce, 1998]

W . Royce Software Project Management: Addison-Wesley, Reading, MA, 1998.

[Rumbaugh i in., 1991]

J. Rumbaugh, M. Błaha, W. Premerlani, F. Eddy, W. Lorensen Object-OrientedModeling and Design, Prentice Hall, Englewood Cliffs, NJ, 1991.

[Simon, 1970]

H. A. Simon The Sciences of the Artificial, MIT Press, Cambridge, MA, 1970.

[Sommerville, 2006]

I. Sommerville Software Engineering, wyd. ósme Addison-Wesley, Reading, MA, 2006.

[Spivey, 1989]

J. M. Spivey The Z Notation, A Reference Manual, Prentice Hall, Hertfordshire,

Analysis

and System Specification,

of IEEE

Approach,

A Unified

Yourdon,

Standard

Techniques,

Clarendon,

Framework,

2.1.

Wprowadzenie

64

2.2.

Ogólnie 2.2.1. 2.2.2. 2.2.3. 2.2.4. 2.2.5.

65 65 65 67 67 68

2.3.

Podstawowe koncepcje modelowania 2.3.1. Systemy, modele i widoki 2.3.2. Typy danych, abstrakcyjne typy danych i instancje 2.3.3. Klasy, klasy abstrakcyjne i obiekty 2.3.4. Klasy zdarzeniowe, zdarzenia i komunikaty 2.3.5. Modelowanie zorientowane obiektowo 2.3.6. Falsyfikacja i prototypowanie

2.4.

UML — głębszy wgląd 2.4.1. Diagramy przypadków użycia 2.4.2. Diagramy klas 2.4.3. Diagramy interakcji 2.4.4. Diagramy stanów 2.4.5. Diagramy aktywności 2.4.6. Organizacja diagramów

78 79 86 95 98 101 104

2.4.7.

106

o UML Diagramy przypadków użycia Diagramy klas Diagramy interakcji Diagram stanów Diagramy aktywności

Rozszerzenia diagramów

69 69 72 73 75 76 77

2.5.

Literatura uzupełniająca

107

2.6.

Ćwiczenia

108

Bibliografia

109

Modelowanie w języku UML

m

Każdy mechanik to zna: nie możesz kupić osobno jakiejś części, bo producent potraktował ją jako element większej całości. — Robert Pirsig Zen i sztuka oporządzania

motocykla

X - J ż y w a j ą c właściwej notacji, możemy nawet skomplikowane idee wyrażać w sposób zwięzły i precyzyjny. W sytuacji, gdy w realizacji projektu uczestniczy wielu ludzi, o odmiennych profilach kulturowych i różnym profilu wyedukowania technicznego, dokładność i klarowność to cechy niezbędne — bez nich wzajemna komunikacja błyskawicznie pogrąża się w chaosie. Aby notacja mogła czynić komunikację dokładną i czytelną, powinna cechować się przede wszystkim dobrze zdefiniowaną semantyką oraz musi być dobrze przystosowana do reprezentowania określonych aspektów systemu. Musi być ponadto dobrze zrozumiała dla wszystkich uczestników — i tu ujawnia się znaczenie przyjęcia odpowiednich standardów i konwencji: nawet w przypadku licznego zespołu, gdy wszyscy posługują się jednolitymi standardami, wydatnie zmniejsza się okazja do różnych nieporozumień, fałszywych interpretacji czy dwuznaczności. I vice versa: gdy w obiegu funkcjonują różne dialekty albo notacja jest zbyt specjalistyczna, może się zdarzyć, że poszczególni uczestnicy przyjmują własne, różniące się interpretacje tych samych faktów, co porozumienia bynajmniej nie ułatwia. Na potrzeby tej książki wybraliśmy notację w postaci języka UML (Universal Modeling Language — uniwersalny język modelowania), zdefiniowanego w OMG Unified Modeling Language Superstructure. Version 2.2 z 2009 roku [OMG, 2009], ponieważ dostarcza on szerokiego spektrum środków do reprezentowania różnorodnych aspektów systemu i stał się zaakceptowanym standardem notacyjnych w środowisku tworzenia oprogramowania na skalę przemysłową. W tym rozdziale opiszemy ogólne koncepcje modelowania, ze szczególnym uwzględnieniem modelowania zorientowanego obiektowo. Omówimy pięć fundamentalnych rodzajów notacji UML, których używać będziemy w całej książce; są to diagramy przypadków użycia, diagramy klas, diagramy interakcji, diagramy stanów i diagramy aktywności. Dla każdej z tych kategorii wyjaśnimy podstawową semantykę i zilustrujemy ją konkretnymi przykładami. Do każdej z nich będziemy — oczywiście — wielokrotnie powracać w książce przy okazji opisywania różnych rodzajów aktywności. W dwóch rozdziałach czytelnicy znajdą także notację innego typu: w rozdziale 6. „Projektowanie systemu — dekompozycja na podsystemy" wykorzystamy diagramy wdrożenia UML, natomiast w rozdziale 14. „Zarządzanie projektem" posłużymy się notacją PERT.

64

Rozdział 2. • Modelowanie w języku UML

2.1. Wprowadzenie Język UML powstał jako efekt unifikacji kilku innych notacji: OMT (Object Modeling Technique [Rumbaugh i in., 1991]), notacji Boocha [Booch, 1994] oraz OOSE (Object-Oriented Software Engineering [Jacobson i in., 1992]). Pewien wpływ na ostateczną postać języka wywarło także kilka innych standardów, takich jak notacje zaproponowane przez Mellora i Shlaer [Mellor i Shlaer, 1998], Coada i Yourdona [Coad i in., 1995], Wirfs-Brock [Wirfs-Brock i in., 1990] oraz Martina i Odella [Martin & Odell, 1992], Celem języka UML jest dostarczenie standardowej notacji, która z jednej strony byłaby użyteczna dla wszelkich metodologii zorientowanych obiektowo, z drugiej natomiast promowała najlepsze cechy swych poprzedniczek. Istotnie, w języku UML spotkać możemy diagramy przypadków użycia przejęte z OOSE oraz wiele cech wywodzących się z OMT diagramów klas. Jednocześnie UML przynosi wiele nowatorskich, niespotykanych wcześniej koncepcji, takich jak mechanizmy rozszerzające czy język ograniczeń. UML zaprojektowany został z myślą o szerokim zakresie zastosowań, dostarcza zatem konstrukcji przydatnej dla systemów i aktywności różnego rodzaju (na przykład systemów rozproszonych, analizy systemów, projektowania systemu czy wdrażania). Tworzenie systemu ujmowane jest z perspektywy UML w postaci trzech różnych modeli (patrz rysunek 1.2). Oto one. • Model funkcjonalny, reprezentowany na gruncie UML przez diagramy przypadków użycia, postrzegający system z perspektywy jego pojedynczego użytkownika. • Model obiektowy, reprezentowany w postaci diagramów klas i opisujący strukturę systemu w kategoriach obiektów, atrybutów, skojarzeń i operacji. Na etapie zbierania wymagań i analizy model obiektowy przyjmuje swą postać początkową — analitycznego modelu obiektowego — i opisuje różne koncepcje zastosowania systemu. W fazie projektowania systemu model ten rozwijany jest do postaci obiektowego modelu projektu systemu i wzbogacony zostaje między innymi o opis interfejsów poszczególnych podsystemów. Kolejne stadium przeobrażeń modelu obiektowego to model projektu obiektów, zawierający szczegółowy opis poszczególnych obiektów składających się na dziedzinę realizacyjną. • Model dynamiczny, obecny w UML w postaci diagramów interakcji, diagramów stanów i diagramów aktywności, opisujących wewnętrzne aspekty zachowania systemu. Diagramy interakcji opisują zachowanie systemu jako sekwencję komunikatów wymienianych w ramach zbioru obiektów, podczas gdy diagramy stanów koncentrują się na zmianie stanu pojedynczych obiektów i możliwych przejściach między poszczególnymi stanami. Z kolei diagramy aktywności opisują zachowanie systemu w kategoriach przepływu sterowania i przepływu danych. W tym rozdziale opiszemy diagramy UML przeznaczone do reprezentowania każdego z tych modeli. Ich prezentacja wiązać się będzie mimowolnie z pewnym wyzwaniem: otóż zrozumienie przeznaczenia danej notacji wymaga pewnej znajomości aktywności, na potrzeby których została stworzona, a przecież nie sposób jednoznacznie i precyzyjnie opisać tych aktywności bez zrozumienia służącej do tego notacji! Aby poradzić sobie z tą kłopotliwą rekurencją, przedstawiać będziemy poszczególne koncepcje języka UML w sposób iteracyjny — i tak

2.2. Ogólnie o UML

65

w sekcji 2.3 opiszemy fundamentalne koncepcje modelowania, w sekcji 2.4 przyjrzymy się dokładniej pięciu wymienionym rodzajom diagramów w świetle tychże koncepcji, zaś w kolejnych rozdziałach omawiać będziemy owe diagramy na rozmaitym poziomie szczegółowości, przy okazji dyskusji na temat odzwierciedlanych przez nie aktywności.

2.2. Ogólnie o UML W tej sekcji opiszemy krótko pięć podstawowych rodzajów notacji UML: • diagramy przypadków użycia (patrz sekcja 2.2.1), • diagramy klas (patrz sekcja 2.2.2), • diagramy interakcji (patrz sekcja 2.2.3), • diagramy stanów (patrz sekcja 2.2.4), • diagramy aktywności (patrz sekcja 2.2.5).

2.2.1. Diagramy przypadków użycia Diagramy przypadków użycia wykorzystywane są w fazie zbierania wymagań do reprezentowania elementów funkcjonalnych systemu. Koncentrują się na zachowaniu systemu postrzeganym z zewnątrz. Przypadek użycia opisuje funkcję realizowaną przez system jako efekt widoczny dla aktora, z kolei aktor stanowi personifikację encji uwikłanej w interakcję z przedmiotowym systemem — taką encją może być fizyczne otoczenie tego ostatniego, użytkownik lub inny system. Efektem identyfikacji aktora i przypadku życia jest zdefiniowanie warunków brzegowych (granicznych) systemu, czyli wyraźne określenie granicy oddzielającej zadania realizowane przez system od zadań realizowanych przez jego środowisko. Granica ta ma kształt figury zamkniętej: aktorzy znajdują się na zewnątrz niej, wewnątrz rozgrywają się przypadki użycia. Na rysunku 2.1 widoczny jest diagram przypadków użycia związanych z prostym zegarkiem elektronicznym. Aktor WatchUser może używać swojego zegarka do sprawdzenia aktualnego czasu (czemu odpowiada przypadek użycia ReadTime) bądź do skorygowania ustawienia czasu (ta czynności reprezentowana jest przez przypadek testowy SetTime). Wymianę baterii (reprezentowaną przez przypadek użycia ChangeBattery) woli jednak powierzyć zegarmistrzowi, którego na diagramie reprezentuje aktor WatchRepai rPerson.

2.2.2. Diagramy klas Przeznaczeniem diagramów klas jest opisywanie struktury systemu. Klasy są abstrakcjami określającymi wspólną strukturę i wspólne zachowanie określonego zbioru obiektów. Obiekty są instancjami (egzemplarzami) klas; w czasie funkcjonowania systemu instancje te mogą być tworzone, modyfikowane i niszczone. Dla każdego obiektu określony jest stan, na który składają się wartości jego atrybutów i jego połączenia z innymi obiektami. Diagram klas jest opisem systemu w kategoriach obiektów, klas, atrybutów, operacji i skojarzeń między obiektami. Przykładowy diagram z rysunku 2.2 to diagram klas uwidoczniający elementy każdego zegarka reprezentowanego przez klasę Simpl eWatch. Obiekt tej klasy

66

Rozdział 2. • Modelowanie w języku UML

Rysunek 2.1. Diagram przypadków użycia ukazujący funkcjonalność prostego zegarka Simpl eWatch. Aktor WatchUser może bądź to odczytywać wskazanie czasu (przypadek użycia ReadTime), bądź je korygować (przypadek użycia SetTime). Jednak tylko aktor WatchRepai rPerson może wymieniać baterię w zegarku (przypadek użycia ChangeBattery). Aktorzy reprezentowani są na diagramie przez sugestywne figurki, zaś przypadki użycia — przez elipsy. Prostokąt otaczający elipsy jest granicą między aktorami a przypadkami użycia

Rysunek 2.2. Diagram klas przedstawiający elementy prostego zegarka

skojarzony jest z obiektami klas PushButton, Display, Time i Battery reprezentującymi odpowiednio przyciski, wyświetlacze, czas i baterie. Liczby przy końcach linii oznaczających skojarzenia ukazują liczbę skojarzonych obiektów — i tak obiekt klasy Simpl eWatch skojarzony jest z dokładnie dwoma obiektami klasy PushButton (zegarek ma dwa przyciski), jednym obiektem klasy Di spl ay (zegarek jest prosty, ma więc jeden wyświetlacz) i jednym obiektem klasy Time (każdy zegarek, jakkolwiek byłby nastawiony, wskazuje w danej chwili jakiś konkretny czas), i dwoma obiektami klasy Battery (dwie bateryjki służą do zasilania zegarka). Podobnie każdy obiekt klas PushButton, Di spl ay, Time i Battery skojarzony jest z dokładnie jednym obiektem klasy Simpl eWatch (zakładamy, że bateria znajduje się wewnątrz zegarka i wskazuje on jakiś czas). Na etapie analizy skojarzenia z diagramu reprezentują istniejące w rzeczywistości związki między encjami — i tak na przykład zegarek (Simpl eWatch) wyposażony jest w odpowiednią liczbę przycisków (PushButton), posiada wyświetlacz (Di spl ay), wskazuje konkretny czas (Time) i zasilany jest energią pochodzącą z odpowiedniej liczby bateryjek (Battery). W tym przykładzie skojarzenia są symetryczne: zegarek nie mógłby funkcjonować bez przycisków, zaś przycisk nie ma znaczenia samoistnego, a istnieje jedynie jako część zegarka. UML umożliwia także reprezentowanie relacji jednokierunkowych, czego przykłady przedstawimy w sekcji 2.4.2. Na etapie implementacji skojarzenia odzwierciedlane są jako referencje lub wskaźniki (pointers) do obiektów.

2.2. Ogólnie o UML

67

2.2.3. Diagramy interakcji Zadaniem diagramów interakcji jest formalizacja dynamicznego zachowania się systemu i uwidocznienie komunikacji między obiektami. Okazuje się to użyteczne dla identyfikowania dodatkowych obiektów związanych z przypadkami użycia — każdy obiekt mający związek z konkretnym przypadkiem użycia nazywać będziemy obiektem uczestniczącym. Diagram interakcji reprezentuje związki między obiektami uczestniczącymi — na rysunku 2.3 widzimy szczególny przypadek diagramu interakcji, zwany diagramem sekwencji, dla przypadku użycia SetTime naszego zegarka Simpl eWatch. Skrajna lewa kolumna reprezentuje aktora WatchUser inicjującego przypadek użycia — etykietowane strzałki reprezentują wykonywane przez niego czynności w stosunku do zegarka. W tym przykładzie WatchUser naciska dwukrotnie przycisk 1 i jednokrotnie przycisk 2, by skorygować wskazanie czasu o jedną minutę do przodu. Przypadek użycia SetTime kończy się, gdy WatchUser naciśnie równocześnie oba przyciski.

Rysunek 2.3. Diagram sekwencji dla zegarka (: Watch). Skrajna lewa kolumna reprezentuje moment, w którym aktor : WatchUser inicjuje przypadek użycia. Pozostałe kolumny związane są z obiektami uczestniczącymi w przypadku użycia. Podkreślenie nazwy obiektu oznacza, że mamy do czynienia z konkretnym obiektem, nie z całą klasą. Etykietowane strzałki reprezentują sygnały (bodźce), które aktor lub inny obiekt wysyła do innych obiektów

2.2.4. Diagram stanów Diagram stanów opisuje zachowanie się pojedynczego obiektu, rozumiane jako przebywanie w danej chwili w określonym stanie oraz przechodzenie z jednego stanu do innego. Pod pojęciem „stanu" rozumiemy tu zbiór wartości związanych z obiektem, zaś jako „przejście" — zmianę bieżącego stanu na inny, pod wpływem spełnienia określonego warunku. Na rysunku 2.4 przedstawiliśmy diagram stanów dla zegarka Watch. Zaczernione kółeczko reprezentuje wyróżniony stan początkowy — BI i nkHours, zaś takie samo kółeczko otoczone białym pierścieniem — stan końcowy (StopBl inking).

68

Rozdział 2. • Modelowanie w języku UML

Rysunek 2.4. Diagram stanów dla przypadku użycia SetTime zegarka Watch

Zauważmy, że diagram stanów reprezentuje informację różną od wynikającej z diagramu sekwencji na rysunku 2.3: diagram sekwencji uwidocznia komunikaty wymieniane między obiektami jako efekt zewnętrznych zdarzeń wywoływanych przez aktora, zaś diagram stanów koncentruje się na zmianie stanów konkretnego obiektu w rezultacie tychże zdarzeń.

2.2.5. Diagramy aktywności Diagram aktywności odzwierciedla zachowanie systemu w kategoriach aktywności. Aktywności jako elementy modelowania reprezentują wykonywanie zbioru lub ciągu operacji — konkretna aktywność może być zapoczątkowana w konsekwencji zakończenia innej aktywności, udostępnienia jakiegoś obiektu czy też ogólnie wskutek wystąpienia jakiegoś zdarzenia zewnętrznego. Programiści natychmiast rozpoznają tu podobieństwo do schematów blokowych, odzwierciedlających przepływ sterowania (następstwo wykonywanych instrukcji i procedur) lub przepływ danych (czyli wpływ wykonywanych operacji na poszczególne obiekty). W diagramie widocznym na rysunku 2.5 uwidocznione są aktywności związane z zarządzaniem jakaś sytuacją nadzwyczajną, określoną ogólnie jako „Incydent". Zaokrąglone prostokąty reprezentują aktywności, strzałki między nimi — przepływ sterowania; pionowe belki są punktami synchronizacji przepływu zdarzeń: każda z aktywności AliocateResources („przydziel zasoby"), CoordinateResources („koordynuj wykorzystywanie zasobów") i Documentlncident („udokumentuj incydent") może zostać zainicjowana dopiero po tym, jak zakończy się aktywność Open Incident („zareaguj na incydent"). Nie ma natomiast żadnych przeciwwskazań, by aktywności Al 1 ocateResources, CoordinateResources i Documentlncident realizowane były równolegle, niezależnie od siebie. Wszystkie one muszą się jednak zakończyć, by mogła rozpocząć się aktywność Archi velncident („archiwizuj incydent").

2.3. Podstawowe koncepcje modelowania

69

Rysunek 2.5. Przykład diagramu aktywności, reprezentującego zachowanie systemu w kategoriach aktywności i ich warunków wstępnych: zakończenie jednej aktywności może stanowić wyzwalacz inicjujący inną aktywność (Openlncident — reakcja na zdarzenie, „otwarcie" incydentu; Al locate Resources — przydzielenie zasobów niezbędnych do obsługi incydentu; Coordinate Resources — koordynacja wykorzystywania zasobów; Document Incident — dokumentowanie incydentu; Archive Incident — przeniesienie dokumentacji incydentu do archiwum)

Tak z grubsza prezentują się diagramy pięciu kategorii, składające się na podstawową notację UML. W następnych sekcjach przyjrzymy się im bardziej szczegółowo: w sekcji 2.3 omówimy podstawowe koncepcje modelowania — pojęcia systemu, modelu, typu, instancji, abstrakcji i falsyfikacji. W sekcjach 2.4.1 - 2.4.5 opiszemy szczegółowo diagramy przypadków użycia, diagramy klas, diagramy sekwencji, diagramy stanów i diagramy aktywności, ilustrując ich zastosowania prostymi przykładami. Sekcję 2.4.6 poświęcimy różnorodnym konstrukcjom pokrewnym, wykorzystywanym we wszystkich pięciu typach diagramów — między innymi pakiety i notatki. Notację tę stosować będziemy w całej książce w celu opisywania systemów informatycznych, produktów, aktywności i organizacji projektów. Wykorzystując systematycznie, w sposób spójny, niewielkie podzbiory tej notacji, mamy nadzieję dostarczyć czytelnikowi obszerną wiedzę na temat możliwości języka UML.

2.3. Podstawowe koncepcje modelowania W tej sekcji opiszemy podstawowe koncepcje modelowania. Rozpoczniemy od zdefiniowania pojęć system, model i widok oraz omówimy podstawowe zadania modelowania. Wyjaśnimy ich związek z językami programowania, a dokładniej — z takimi ich elementami jak typy danych, klasy, instancje i obiekty. Na koniec pokażemy, jak obiektowo zorientowane modelowanie skupia się na budowaniu abstrakcji środowiska systemu, zmierzając do stworzenia modelu systemu.

2.3.1. Systemy, modele i widoki System to zorganizowany zbiór komunikujących się części. Interesować nas będą systemy inżynieryjne, czyli skonstruowane z określonym przeznaczeniem — w przeciwieństwie do systemów naturalnych, takich jak Układ Słoneczny, których cel istnienia na zawsze pozostanie dla nas tajemnicą. I tak na przykład samochód, składający się z czterech kół, podwozia, silnika i karoserii, zaprojektowany został z myślą o przewożeniu osób. Zegarek, złożony z baterii, obwodu scalonego, przycisków i wyświetlacza, służy do pomiaru upływającego czasu. System

70

Rozdział 2. • Modelowanie w języku UML

płacowy, obejmujący komputer mainframe, drukarki, dyski, oprogramowanie i personel, ma za zadanie obliczanie i wypłatę wynagrodzeń dla pracowników firmy. Poszczególne części systemu mogą być same z siebie rozpatrywane jako prostsze systemy i z tej racji nazywane podsystemami — silnik samochodu, składający się między innymi z cylindrów, zaworów i modułu wtrysku paliwa, jest przykładem takiego podsystemu, podobnie jak układ scalony zegarka czy komputer realizujący obliczenia w ramach systemu płacowego. Dekompozycję tę można — oczywiście — stosować rekurencyjnie, aż do otrzymania części na tyle prostych, że ich zrozumienie nie wymaga już dalszej dekompozycji. Wiele systemów składa się dużej liczby podsystemów połączonych ze sobą skomplikowanymi zależnościami — często na tyle skomplikowanymi, iż niemożliwe jest pełne zrozumienie systemu przez pojedynczego człowieka. Modelowanie stanowi skuteczny sposób radzenia sobie z tą złożonością. Złożony system opisywany jest zwykle przez wiele modeli, z których każdy skupia się na określonym jego aspekcie lub określonym stopniu szczegółowości jego postrzegania. Modelowanie to konstruowanie abstrakcji systemu, skupiającej się jedynie na szczegółach istotnych w danym kontekście i ignorującej kwestie nieistotne z jego perspektywy. Odróżnienie jednych od drugich jest każdorazowo zadaniem specyficznym dla konkretnego zastosowania. Załóżmy, że chcemy zbudować samolot. Nawet przy udziale licznych ekspertów z różnych dziedzin nie jesteśmy w stanie zbudować go od zera i spodziewać się, że z powodzeniem odbędzie swój dziewiczy lot. Musimy w zamian rozpocząć od zbudowania modelu w małej skali i przetestowania jego własności fizycznych w tunelu aerodynamicznym. W czasie tych testów interesująca będzie dla nas jedynie sylwetka samolotu, w odróżnieniu od takich nieistotnych szczegółów jak pulpit w kabinie pilotów czy silniki. Gdy chcemy wyszkolić pilotów na potrzeby tego samolotu, musimy skonstruować odpowiedni symulator — ten z kolei odzwierciedlać będzie rozkład i funkcje poszczególnych narzędzi sterujących samolotu, bez jakiegokolwiek związku z jego zewnętrzną sylwetką. Zarówno małoskalowy model samolotu, jak i symulator lotów są znacznie mniej skomplikowane od całości samolotu, którego reprezentację stanowią. Modelowanie umożliwia zmaganie się ze złożonością na zasadzie „dziel i zwyciężaj": dla danego typu problemu (jak testowanie własności aerodynamicznych lub szkolenie pilotów) budujemy modele skupiające się jedynie na zagadnieniach istotnych dla tego problemu. Generalnie każdy z tych modeli powinien być już na tyle prosty, że możliwy do ogarnięcia przez pojedynczą osobę. Zgodnie ze wskazówką „7 ± 2" podaną przez Millera [Miller, 1956], model taki powinien obejmować od 5 do 9 części. Modele posiadają ponadto istotną zaletę, która dodatkowo zwiększa ich rolę w zmaganiu się ze złożonością problemów: otóż modele, początkowo niedoskonałe, można sukcesywnie ulepszać, co sprawia, że stają się bardziej szczegółowe i bliższe rzeczywistości. W inżynierii oprogramowania — jak zresztą w większości dyscyplin inżynierskich — modele poprzedzają zazwyczaj powstanie rzeczywistego systemu. I tak na etapie analizy budujemy najpierw model otoczenia systemu i jego ogólnej funkcjonalności, jaką ma prezentować — model na poziomie zrozumiałym przez klienta. Model ten następnie rozwijamy, dodając doń więcej szczegółów, takich jak zestaw wyświetlanych formularzy, układ interfejsu użytkownika, zachowanie się w sytuacjach wyjątkowych i tym podobne. Zbiór wszystkich takich modeli, stworzonych w czasie powstawania systemu, składa się na ogólny, całościowy system model. Gdybyśmy nie korzystali z modelowania, lecz od razu przystąpilibyśmy do pisania kodu źródłowego, musielibyśmy ze szczegółami określić wszelkie szczegóły interfejsu użytkownika,

2.3. Podstawowe koncepcje modelowania

71

zanim jeszcze zdążylibyśmy zapytać klienta o cokolwiek. Nieuniknione w tej sytuacji późniejsze korekty i poprawki — jako skutek zmieniających się wymagań klienta — byłyby bardzo pracochłonne i zajmowałyby dużo czasu. Niestety, często same modele bywają bardzo skomplikowane i trudne do zrozumienia. Na szczęście, paradygmat „dziel i zwyciężaj" i tutaj przychodzi z pomocą, dostarczając kolejny środek upraszczający — widoki. Widok to podzbiór wybranych cech modelu, bardziej zrozumiały niż model ujmowany całościowo. Jak pokazaliśmy to na rysunku 2.6, zbiór podręczników i instrukcji serwisowych związanych z samolotem jest właśnie takim podzbiorem, podobnie jak system paliwowy czy system okablowania. Jest zrozumiałe, że widoki nie muszą być rozłączne — na przykład elementem okablowania samolotu jest okablowanie jego systemu paliwowego.

Rysunek 2.6. Model jest abstrakcją opisującą podzbiór systemu, podobnie widok ukazuje tylko wybrany aspekt modelu. Poszczególne widoki tego samego modelu mogą nakładać się na siebie

Notacja to tekstowe lub graficzne reguły reprezentowania widoków. Diagram klas jest graficznym widokiem modelu obiektowego. Na diagramie okablowania samolotu każda linia ciągła reprezentuje przewód lub wiązkę przewodów. Na diagramie klas prostokąt opatrzony tytułem reprezentuje klasę, zaś linia łącząca dwa prostokąty odpowiada relacji istniejącej między odnośnymi klasami. Zauważmy, że ten sam widok może być reprezentowany w formie różnych notacji, czego przykładem jest rysunek 2.7.

Rysunek 2.7. Przykład przedstawienia modelu w formie dwóch różnych notacji. Na wspomniany model składają się dwie klasy: Książka i Rozdział, połączone oczywistą zależnością „Książka składa się z rozdziałów". W notacji UML klasy przedstawione są w formie prostokątów, zaś agregacja symbolizowana jest przez linię zakończoną małym rombem. W notacji Boocha natomiast klasy przedstawiane są w formie chmur, zaś linia reprezentująca agregację zakończona jest zaczernionym kółeczkiem

72

Rozdział 2. • Modelowanie w języku UML

Na gruncie inżynierii oprogramowania obok UML funkcjonuje wiele różnych notacji używanych do modelowania systemów. Podczas gdy UML ujmuje system w kategoriach klas, zdarzeń, stanów, interakcji i aktywności, diagramy De Marco [De Marco, 1978] uwidoczniają procesy wyszukiwania, przetwarzania i magazynowania danych, a Z-Schematy [Spivey, 1992] postrzegają system w charakterze niezmienników (czyli warunków, które permanentnie pozostają niezmienne) oraz warunków pozostających prawdziwymi przed i po wykonaniu danej operacji. Każda z tych (i innych) notacji bywa szczególnie przydatna dla określonej klasy problemów. W kolejnych sekcjach przyjrzymy się bardziej szczegółowo procesowi modelowania.

2.3.2. Typy danych, abstrakcyjne typy danych i instancje Typ danych jest abstrakcją istniejącą w kontekście języka programowania. Opatrzony jest unikalną nazwą, która odróżnia go od innych typów danych i wyznacza zbiór wartości, jakie przyjmować mogą jego instancje, zwane też wystąpieniami lub egzemplarzami. Integralną częścią definicji typu danych jest określenie struktury jego instancji oraz zestawu operacji, jakim instancje te mogą być poddawane. Języki z wbudowaną kontrolą typów danych (zwane typed languages) traktują naruszenie tych reguł jak błędy składniowe. I tak w języku Java nazwa i n t odnosi się do typu obejmującego liczby całkowite z przedziału od -2 3 2 do +2 3 2 -l (włącznie). Dopuszczalnymi operacjami dla tego typu są te, które wynikają z arytmetyki całkowitoliczbowej — czyli między innymi dodawanie, odejmowanie, mnożenie i dzielenie — oraz funkcje i metody posiadające parametr(y) typu i n t (jak na przykład mod zwracające resztę z dzielenia). Biblioteka Javy spowoduje wygenerowanie wyjątku przy próbie wykonania operacji zmiennopozycyjnej (na przykład trunc czy f 1 oor) na liczbie całkowitej. Abstrakcyjny typ danych to typ definiowany w specyfikacji niezależnej od konkretnej implementacji. Niezależność taka umożliwia programistom operowanie na bardziej ogólnym poziomie abstrakcji, bez nawiązywania do szczegółów składniowych czy semantycznych konkretnego języka programowania. Przykładem abstrakcyjnych typów danych są zbiory (w sensie teorii mnogości) i sekwencje — w czysto matematycznym rozumieniu. Konkretny język programowania może dostarczać różne mechanizmy ich implementowania, często także w różnych wariantach optymalizowanych pod kątem określonego kryterium (wydajność wyszukiwania lub wstawiania elementu, zajętość pamięci i tym podobne), jednak w pewnych sytuacjach programista zainteresowany jest tylko semantyką zbioru jako takiego, w oderwaniu od jego konkretnej realizacji. Przykładowo abstrakcyjny typ danych Pracowni k może definiować operacje podaj Nazwisko ()', podaj NrNIP() czy podajAdres(). To, czy numer NIP jest w konkretnej implementacji zapamiętywany jako liczba całkowita czy jako łańcuch, może w danym kontekście nie mieć znaczenia dla reszty systemu. Decyzje o wyborze konkretnej implementacji danego typu stanowią szczególny przypadek ogólnie pojmowanych decyzji implementacyjnych.

1

Odwołania do operacji będą mieć w tej książce postać nazwy, po której następuje lista parametrów zamknięta w nawias; dla operacji bezargumentowych jest to nawias pusty. Operacjami zajmiemy się szczegółowo w następnej sekcji.

73

2.3. Podstawowe koncepcje modelowania

2.3.3. Klasy, klasy abstrakcyjne i obiekty Klasa jest abstrakcją na gruncie zarówno modelowania zorientowanego obiektowo, jak i na gruncie obiektowego języka programowania. Podobnie jak abstrakcyjne typy danych, klasy zamykają w sobie („enkapsulują") i strukturę danych, i ich zachowanie. W przeciwieństwie jednak do „zwykłych" abstrakcyjnych typów danych, klasy mogą być definiowane na bazie innych Idas za pomocą mechanizmu zwanego dziedziczeniem. Załóżmy na przykład, że nasz prosty zegarek z poprzednich przykładów postanowiliśmy rozszerzyć o funkcję kalkulatora. Reprezentująca ów nowy model (nomen omen) zegarka klasa Cal cul atorWatch może być uważana za rozwiniętą wersję ldasy Watch, po której dziedziczy wszystkie własności, dodając własne, specyficzne. Klasę Watch, jako bardziej ogólną, nazywamy w tym przypadku nadklasą łub superklasą2, zaś klasę rozwiniętą (CalculatorWatch) — podklasą lub subklasą3. Zgodnie z diagramem przedstawionym na rysunku 2.8, klasa Cal cul atorWatch definiuje elementy funkcjonalne związane z wykonywaniem prostych działań arytmetycznych, zaś klasa Watch elementów tych — oczywiście — nie posiada. Pojęcia superklasy i subklasy są względne: dana klasa może być subklasą jednej klasy i jednocześnie stanowić superklasę dla innych klas.

Rysunek 2.8. Diagram klas prezentujący dwie powiązane klasy — Watch i CalculatorWatch. Klasa Cal cul atorWatch stanowi rozwinięcie klasy Watch o elementy typowe dla kalkulatora, niespotykane w „normalnych" zegarkach. Na diagramach UML klasy reprezentowane są przez prostokąty podzielone w pionie na trzy części: w najwyższej z nich znajduje się nazwa klasy, w środkowej — jej atrybuty, w najniższej natomiast wyszczególnione są jej operacje. W niektórych przypadkach dwie ostatnie części pomijane są ze względów prostoty lub przejrzystości. Relacja dziedziczenia odzwierciedlona jest przez linię zakończoną małym trójkątem, zlokalizowanym po stronie superklasy

Jeżeli klasa pełni jedynie rolę generalnego wzorca dla swych subklas, dostarczając im wspólnych atrybutów i operacji, a więc gdy nie przewiduje się tworzenia jej instancji (obiektów), nazywamy ją klasą abstrakcyjną. Klasy abstrakcyjne używane są do reprezentowania ogólnych koncepcji dziedziny aplikacyjnej, a ich nazwy wyróżniane są na diagramach pochyloną czcionką. W diagramie na rysunku 2.9 klasa Benzen reprezentująca związek chemiczny o tej samej nazwie i o określonej strukturze molekularnej jest subklasą klasy ZwiqzekOrganiczny, reprezentującej jakąś substancję chemii organicznej w ogólności, bez jakiejś konkretnej struktury molekuł. 2

Często zwana także „klasą bazową" lub „klasą macierzystą" — przyp.

' Często nazywana też „klasą pochodną" — przyp.

tłum.

tłum.

74

Rozdział 2. • Modelowanie w języku UML

Rysunek 2.9. Przykład klasy abstrakcyjnej: klasa ZwiązekOrganiczny nie reprezentuje żadnego konkretnego związku chemicznego, istnieje tylko jako reprezentant wszystkich związków organicznych w ogólności. Jej nazwa, jako nazwa klasy abstrakcyjnej, pisana jest na diagramie klas kursywą

W języku Java klasa abstrakcyjna Col 1 ecti on uosabia wspólne cechy wszystkich kolekcji i pełni rolę superldasy dla klas reprezentujących konkretne rodzaje kolekcji, między innymi Li nkedLi s t , ArrayLi s t i HashMap. W przeciwieństwie do tych ostatnich, klasa Col 1 ecti on jest zbyt ogólna na to, by można było tworzyć jej instancje. Należy w tym miejscu zauważyć, że nie wszystkie klasy generalizujące pewną funkcjonalność są klasami abstrakcyjnymi — nie jest na przykład klasą abstrakcyjną klasa Watch z diagramu na rysunku 2.8. Na gruncie modelowania systemów informatycznych klasy abstrakcyjne niekiedy nie posiadają odpowiedników w postaci koncepcji z dziedziny aplikacyjnej, lecz wprowadzane są do modelu raczej z myślą o zredukowaniu jego złożoności czy też wielokrotnym wykorzystaniu pewnych wzorców. Częścią definicji klasy jest definicja jej operacji, którym poddawane są instancje klasy. Operacja zdefiniowana w superklasie może być dziedziczona przez subklasę i stosowana do jej obiektów. Przykładowo w diagramie na rysunku 2.8 operacja SetDate(d), odpowiadająca ustawieniu konkretnej daty w zegarku reprezentowanym przez klasę Watch, stosuje się także do obiektów klasy Cal cul atorWatch. Dla odróżnienia, definiowana w subklasie operacja EnterCal cMode() nie ma sensu w odniesieniu do klasy Watch. Atrybutem klasy jest jej nazwana własność, określona w odniesieniu do każdego jej obiektu z osobna. Każdy atrybut ma w ramach klasy unikalną nazwę i posiada określony typ. Klasa Watch posiada atrybuty time i date; klasa Cal cul atorWatch, dziedzicząc te atrybuty ze swej superldasy, dodaje do nich atrybut cal cul a t o r S t a t e . Obiekt jest instancją klasy. Każdy obiekt jest jednoznacznie identyfikowany i przechowuje określony zbiór wartości atrybutów swej klasy. Każdy obiekt należy do dokładnie jednej klasy. Na diagramach UML obiekty reprezentowane są w postaci prostokątów, a ich nazwa jest podkreślona. Podkreślenie ma na celu odróżnienie obiektów od Idas4. W diagramie na rysunku 2.10 obiekt simpl eWatchl291 jest instancją klasy Watch, a obiekt calculatorWatchl515 jest instancją klasy Cal cul atorWatch. Warto zwrócić uwagę na pewien ważny fakt: mimo iż obiekt cal cul atorWatchl515 nie jest instancją klasy Watch, stosują się do niego operacje tej klasy. Atrybuty obiektu mogą być widoczne dla innych części systemu, zależnie od konkretnego języka programowania; język Java umożliwia bardzo szczegółowe definiowanie widoczności poszczególnych atrybutów. 4

Podkreślenia używamy także do oznaczenia lokalizatorów URL, zatem w celu polepszenia czytelności nie podkreślamy nazw obiektów w tekście otwartym, przez co stają się one nieodróżnialne od nazw klas. Staramy się jednak przy tym, by zawsze w takich sytuacjach jasno wynikało z kontekstu, czy mamy do czynienia z klasą, czy też z konkretnym obiektem. W diagramach na rysunkach podkreślenia są jednak konsekwentnie stosowane.

2.3. Podstawowe koncepcje modelowania

75

Rysunek 2.10. Dwie klasy i ich instancje na diagramie UML. Mimo iż operacje klasy Watch stosują się także do obiektu cal cul atorWatchl515, ten nie jest instancją klasy Watch

2.3.4. Klasy zdarzeniowe, zdarzenia i komunikaty Klasa zdarzeniowa to abstrakcja reprezentująca zbiór zdarzeń, na które system przejawiać powinien określoną, wspólną reakcję. Zdarzenie jest instancją klasy zdarzeniowej i ma swój odpowiednik w postaci na przykład impulsu ze strony aktora („WatchUser nacisnął lewy przycisk"), upływu czasu („minęły 2 minuty") lub wymiany komunikatów między obiektami. Wysłanie komunikatu oznacza żądanie (ze strony jednego obiektu) wykonania pewnej operacji przez obiekt docelowy. Komunikat składa się z nazwy i pewnego ciągu parametrów; gdy obiekt docelowy odbierze komunikat, kojarzy jego nazwę z jedną ze swych operacji i wykonuje tę operację, przekazując jej otrzymane w komunikacie parametiy. Ewentualny wynik operacji przesyłany jest z powrotem do obiektu nadawcy. W diagramie na rysunku 2.11 obiekt Watch, w celu wyświetlenia bieżącego wskazania czasu, zwraca się najpierw do obiektu Time o udostępnienie aktualnej wartości czasu GMT, a następnie żąda od obiektu TiraeZone dostarczenia różnicy (przesunięcia) między czasem lokalnym a czasem GMT; zsumowanie tych dwóch wartości da w wyniku wartość, która zostanie wyświetlona.

Rysunek 2.11. Przykłady komunikatów przesyłanych między obiektami. Obiekt Watch wysyła do obiektu Time komunikat getTime() oznaczający żądanie udostępnienia bieżącej wartości czasu GMT, po czym wysyłając do obiektu TimeZone komunikat getTimeDeltaQ, żąda wartości przesunięcia czasu lokalnego dla bieżącej strefy czasowej. Otrzymane zwrotnie wyniki (fakt ich przesiania zaznaczony jest linią przerywaną ze strzałką) są sumowane, co daje wartość odpowiednią do wyświetlenia

76

Rozdział 2. • Modelowanie w języku UML

Zdarzenia i komunikaty są instancjami: reprezentują konkretne wydarzenia w ramach systemu. Klasy zdarzeniowe są natomiast abstrakcjami opisującymi grupy zdarzeń wywołujące jednolite relacje ze strony systemu. W praktyce jednak termin „zdarzenie" może odnosić się zarówno do obiektu (instancji), jak i do klasy zdarzeniowej — właściwe znaczenie można jednak odczytać z kontekstu, w którym się pojawia.

2.3.5. Modelowanie zorientowane obiektowo Dziedzina aplikacyjna reprezentuje wszelkie aspekty problemu użytkownika: jego fizyczne środowisko, potencjalnych użytkowników systemu i inne osoby, czynności, które system ma usprawniać i tak dalej. Zrozumienie wszelkich subtelności dziedziny aplikacyjnej przez analityków i programistów jest niezbędne, by tworzone produkty zgodne były z oczekiwaniami użytkowników. Nie zapominajmy przy tym, że dziedzina aplikacyjna zmienia się z biegiem czasu, w miarę jak użytkownicy coraz lepiej uświadamiają sobie swoje potrzeby i zmienia się ich środowisko 3 . Dziedzina realizacyjna to przestrzeń modelowania wszystkich możliwych systemów. Modelowanie w tej przestrzeni obejmuje aktywności typowe dla projektu systemu i projektu obiektów. Model dziedziny realizacyjnej jest znacznie bogatszy od modelu dziedziny aplikacyjnej, ma też zwykle bardziej ulotny charakter. Jest to prosta konsekwencja faktu, że sam system modelowany jest na poziomie znacznie bardziej szczegółowym niż dziedzina aplikacyjna. Pojawiające się nowe technologie (zwane także „nowinkami technologicznymi"), lepsze rozumienie technik implementacyjnych przez programistów, zmiany w wymaganiach — wszystko to jest motorem napędowym zmian w modelach dziedziny realizacyjnej. Zauważmy również, że wdrażanie systemu może mieć wpływ na zmiany w dziedzinie aplikacyjnej, jeśli użytkownicy wypracują nowe sposoby postępowania w związku z otrzymaniem tego systemu. Analiza zorientowana obiektowo powiązana jest z dziedziną aplikacyjną: aktywności modelowania obu tych kategorii reprezentowane są w ten sam sposób — poprzez klasy i obiekty. Na gruncie obiektowo zorientowanej analizy model dziedziny aplikacyjnej staje się częścią modelu systemu. Przykładowo częścią systemu kontroli ruchu lotniczego jest klasa T r a f i c C o n t r o l l e r , reprezentująca poszczególnych użytkowników (kontrolerów) wraz z ich osobistymi ustawieniami i bieżącą informacją na temat ruchu. W systemie tym istnieje także klasa Ai r c r a f t , reprezentującą samoloty objęte kontrolą. Obie wymienione klasy należą do modelu dziedziny aplikacyjnej i kodowane są w ramach systemu (patrz rysunek 2.12). Używanie tej samej notacji dla modelowania zarówno dziedziny aplikacyjnej, jak i dziedziny realizacyjnej ma swe wady i zalety. Jest korzystne, bowiem odpowiedniość koncepcji aplikacyjnych i poszczególnych klas dziedziny realizacyjnej umożliwia mapowanie tych ostatnich z powrotem w dziedzinę aplikacyjną; co więcej, klasy te mogą być hermetyzowane w ramach podsystemów niezależnie od innych koncepcji implementacyjnych (na przykład interfejsu 5

Dziedzina aplikacyjna może podlegać dalszemu podziałowi na dziedzinę użytkownika i dziedzinę klienta. Pierwsza obejmuje wszelkie zagadnienia znaczące dla użytkownika systemu — funkcjonalność, wygodę użytkowania, prostotę obsługi oraz łatwość nauczenia się jej i tym podobne; do dziedziny klienta należą natomiast takie aspekty systemu jak koszt jego wytworzenia, ogólny koszt posiadania i konserwacji czy też wpływ systemu na całokształt organizacji firmy.

2.3. Podstawowe koncepcje modelowania

77

Rysunek 2.12. Model dziedziny aplikacyjnej reprezentuje encje środowiska związane z kontrolą ruchu lotniczego (między innymi samoloty i kontrolerów). Model systemu reprezentuje natomiast encje stanowiące część tego systemu — wyświetlacz map, bazę rozkładu lotów i tym podobne. W ramach obiektowo zorientowanej analizy i projektu model dziedziny aplikacyjnej staje się częścią modelu systemu. Innymi słowy, model systemu stanowi rozwinięcie modelu dziedziny aplikacyjnej o koncepcje pochodzące z dziedziny realizacyjnej, reprezentowane między innymi przez klasy SummaryDisplay, MapDispl ay i FI ightPl anDatabase (do tej kwestii powrócimy w rozdziale 5. „Analiza wymagań")

użytkownika czy technologii bazodanowych), co otwiera drogę do tworzenia zestawów narzędziowych wielokrotnego użytku, powiązanych wprost z określonymi aspektami dziedziny aplikacyjnej. Niestety, wspólna notacja dla modelowania obu dziedzin usuwa granicę między nimi, czyli między światem rzeczywistym a jego modelem. W efekcie utrudnia to upraszczanie dziedziny realizacyjnej i jej polaryzację w kierunku ostatecznego rozwiązania. Świadomi tej niedogodności używamy wspólnej notacji dla obu dziedzin, wprowadzając jednak wyraźne rozróżnienie między nimi, gdy pojawiają się niejednoznaczności. W większości przypadków odwołujemy się do modeli (i na przykład stwierdzenie „Ai r c r a f t jest skojarzone z FI i ghtPl an" odnosi się do klas dziedziny realizacyjnej, a nie do koncepcji dziedziny aplikacyjnej).

2.3.6. Falsyfikacja i prototypowanie Istotą modelowania jako upraszczania rzeczywistości jest ignorowanie nieistotnych aspektów problemu; aspekty istotne w danym kontekście muszą być jednak w modelu reprezentowane. Falsyfikacja [Popper, 1992] jest procesem demonstracji, że pewne istotne szczegóły problemu są w modelu reprezentowane niewłaściwie lub nie są reprezentowane w ogóle — czyli że model jest nieadekwatny do rzeczywistości, którą ma reprezentować.

78

Rozdział 2. • Modelowanie w języku UML

Proces falsyfikacji jest dobrze znanym elementem naukowego opisywania rzeczywistości. Naukowcy proponują różne jej modele, które zdobywają coraz większą wiarygodność, w miarę jak pojawia się coraz więcej danych świadczących na rzecz ich prawdziwości, by odrzucić dany model, gdy pojawi się choć jeden fakt świadczący na jego niekorzyść. I tak na przykład u schyłku XVIII wieku okazało się, że orbita Merkurego ma postać różną od tej, jaką przewiduje prawo grawitacji Izaaka Newtona. Minęło ponad 100 lat i Albert Einstein zaproponował swą ogólną teorię względności, która różnicę tę znakomicie tłumaczyła. Teoria Newtona została więc obalona na rzecz teorii Einsteina. Mimo to, wciąż wykorzystujemy teorię Newtona do praktycznego wyjaśniania wielu zjawisk zachodzących na Ziemi, bowiem stopień jej nieadekwatności do otaczającego świata jest szczegółem nieistotnym w skali, w jakiej zjawiska te zachodzą; jest ona za to znacznie prostsza od ogólnej teorii względności. Falsyfikację możemy z powodzenie stosować także w inżynierii oprogramowania. Jedną z technik tworzenia systemów informatycznych jest prototypowanie: projektując interfejs użytkownika, programiści najpierw budują jego prototyp, symulujący jedynie ostateczną postać. Prototyp ten jest następnie prezentowany potencjalnym użytkownikom w celu oceny — czyli przeważnie falsyfikacji — i odpowiednio modyfikowany. W pierwszej iteracji tego procesu początkowa wersja prototypu zostanie prawdopodobnie porzucona w rezultacie konsultacji z użytkownikami. Innymi słowy, użytkownicy dokonają falsyfikacji prototypu — będącego jednym z modeli systemu — ponieważ nie reprezentuje on właściwie detali, które są dla nich istotne. Niestety, nie istnieje podobna metoda kategorycznego zaakceptowania modelu (podobnie jak nie istnieje metoda udowodnienia, że program jest bezbłędny). I nawet jeśli w niektórych sytuacjach możliwe jest matematyczne wykazanie równoważności dwóch modeli, nie sposób matematycznie udowodnić, że którykolwiek z nich prawidłowo reprezentuje rzeczywistość. Owszem, formalne techniki weryfikacji poprawności programów mogą co najwyżej przekonać programistów, że specyficzna implementacja systemu jest spójna z jego formalną specyfikacją, jednakże tylko testowanie przez specjalistów z dziedziny aplikacyjnej i intensywna eksploatacja systemu mogą skonfrontować jego zgodność z potrzebami klienta. Model systemu może być w każdym czasie zakwestionowany (sfalsyfikowany) w rezultacie zmieniających się wymagań, pojawienia się nowych technologii implementacyjnych czy też zmian w środowisku tego systemu.

2.4. UML — głębszy wgląd Opiszemy teraz bardziej szczegółowo pięć głównych typów diagramów UML, które czytelnicy napotykać będą dalej w tej książce. • Diagramy przypadków użycia — reprezentują funkcjonalność systemu z perspektywyjego użytkowników, ukazują granice systemu; poświęcamy im sekcję 2.4.1. • Diagramy klas — reprezentują statyczną strukturę systemu w kategoriach obiektów, ich atrybutów, operacji i powiązań; opisujemy je w sekcji 2.4.2.

2.4. UML. — głębszy wgląd

79

• Diagramy interakcji — przedstawiają obraz zachowania systemu w kategoriach interakcji między obiektami; wykorzystywane są do identyfikowania obiektów zarówno w ramach dziedziny aplikacyjnej, jak i dziedziny implementacyjnej; są przedmiotem sekcji 2.4.3. • Diagramy stanów — reprezentują zachowanie każdego z istotnych obiektów; przedstawiamy je w sekcji 2.4.4. • Diagramy aktywności — ukazują przepływ sterowania lub danych w systemie; opisujemy je w sekcji 2.4.5.

2.4.1. Diagramy przypadków użycia Przypadki użycia i aktorzy Aktorzy to zewnętrzne encje przejawiające interakcję z systemem. Aktorem może być użytkownik obsadzony w określonej roli (administrator systemu, klient banku, pracownik banku) lub inny system (centralna baza danych, linia produkcyjna). Aktorzy identyfikowani są za pomocą unikalnych nazw i opatrzeni opisami. Przypadki użycia opisują zachowanie systemu z punktu widzenia aktorów. Z tego względu zachowanie systemu opisywane w ramach przypadków użycia nazywane jest zachowaniem zewnętrznym. Każdy przypadek użycia dedykowany jest określonej funkcji systemu, widzianej jako zbiór zdarzeń dających skutki widoczne dla aktorów. Aktorzy inicjują przypadki użycia, korzystając ze wspomnianych funkcji; zainicjowany przypadek użycia może uruchamiać inne przypadki użycia, wymagające od aktorów nowych informacji. Wymianę informacji między aktorami a przypadkami użycia nazywamy komunikacją. W dalszym ciągu pokażemy odwzorowanie tej wymiany w postaci relacji komunikacyjnych między obiektami. I tak na przykład w systemie zarządzania sytuacjami kryzysowymi (wypadkami) funkcjonariusze policji oraz strażacy komunikują się z dyspozytorem za pomocą urządzeń mobilnych. Dyspozytor ma czytelną informację na temat stanu dostępnych zasobów (na przykład wozów strażackich) i dysponuje nimi, wydając polecenia ze swej stacji roboczej. Z perspektywy przypadków użycia związanych z sytuacją kryzysową dyspozytor i funkcjonariusz straży pożarnej (lub policji) mogą być modelowani jako aktorzy. W diagramie przedstawionym na rysunku 2.13 aktor FieldOfficer (funkcjonariusz) inicjuje przypadek użycia ReportEmergency, którego istotą jest powiadomienie aktora Dispatcher (dyspozytora) o zaistniałym zdarzeniu. W odpowiedzi aktor Dispatcher inicjuje przypadek użycia Openlncident, obejmujący sporządzenie raportu o sytuacji kryzysowej i uruchomienie stosownych procedur awaryjnych. Dispatcher wprowadza otrzymane od aktora FieldOfficer informacje do bazy aplikacji zarządzającej (FRIEND — First Responder Interactive Emergency Navigational Database, interaktywna nawigacyjna baza danych dla pierwszego reagowania)6 i wprowadza do akcji niezbędne zasoby w ramach przypadku użycia Al 1 ocateResources.

6

Patrz — przyp.

http://wwwxs.cmu.edu/afs/andrew.cmu.edu/inst/ini/www/WIRELESS/FRIEND/index.html tłum.

80

Rozdział 2. • Modelowanie w języku UML

Rysunek 2.13. Przykład przypadku użycia dla bazy FRIEND systemu zarządzania sytuacjami kryzysowymi. Skojarzenia między aktorami i przypadkami użycia oznaczają przepływ informacji. Skojarzenia te mają charakter dwukierunkowy: mogą oznaczać inicjowanie przypadku użycia przez aktora (na przykład zainicjowanie przypadku ReportEmergency przez aktora F i e l d O f f i c e r ) lub dostarczenie informacji aktorowi ( D i s p a t c h e r jest informowany w ramach przypadku użycia ReportEmergency). Prostokąt zamykający przypadki użycia wyznacza granice systemu

Opisując przypadek użycia w formie tekstowej, wykorzystywać będziemy szablon składający się z sześciu pól, taki jak w tabeli 2.1, zaczerpnięty z książki Constantina i Lockwood [Constantine i Lockwood, 2001]. Pola te określają: • nazwę, unikalną w skali całego systemu, jednoznacznie identyfikującą przypadek użycia dla wszystkich uczestników i programistów, • listę aktorów współdziałających z przypadkiem użycia, • zbiór warunków wstępnych, których spełnienie konieczne jest do zainicjowania przypadku użycia, • przepływ zdarzeń, czyli listę poszczególnych interakcji aktorów z przypadkiem użycia, ponumerowanych dla celów identyfikacji; zdarzenia „normalne" (czyli spodziewane przez użytkownika) i zdarzenia wyjątkowe (błędy i inne nieoczekiwane sytuacje) dla czytelności opisywane są w ramach odrębnych przypadków użycia; opis przepływu zdarzeń ma postać dwukolumnową: w lewej kolumnie reprezentowane są kroki podejmowane przez aktorów, prawa kolumna przeznaczona jest na zdarzenia zachodzące w ramach systemu; każda para takich kroków opisuje pojedynczą interakcję, • zbiór warunków końcowych, czyli takich, których spełnienie jest gwarantowane po zrealizowaniu przypadku użycia, • wymagania jakościowe, niezwiązane bezpośrednio z funkcjonalnością systemu. Należą do nich między innymi wymagania pod względem wydajności systemu, jego implementacji, jego platformy sprzętowej i tym podobne. Wymagania takie opiszemy szczegółowo w rozdziale 4. „Zbieranie wymagań".

2.4. UML. — głębszy wgląd

81

Tabela 2.1. Przykładowy przypadek użycia — ReportEmergency Nazwa przypadku Aktorzy

użycia

uczestniczący

ReportEmergency Fi el dOf f i c e r — inicjuje przypadek użycia Di s p a t c h e r — komunikuje się z przypadkiem użycia

Przepływ

zdarzeń

1. Fi el dOf f i c e r aktywuje funkcję „Raportuj zdarzenie" na swym terminalu 2.System FRIEND wyświetla w odpowiedzi stosowny formularz 3. Fi el dOf f i c e r wypełnia formularz, ustalając typ zdarzenia, stopień zagrożenia, jego lokalizację i krótki opis sytuacji. Fi el dOf f i c e r określa także możliwe sposoby pierwszej reakcji na zagrożenie. Po wypełnieniu formularza Fi el dOf f i c e r wysyła go do systemu. 4. System FRIEND powiadamia dyspozytora ( D i s p a t c h e r ) o zdarzeniu. 5. Di s p a t c h e r analizuje informacje zawarte w formularzu, po czym tworzy w bazie nowy obiekt I n c i d e n t , realizując p r z y p a d e k użycia Openlnci d e n t . Di s p a t c h e r określa sposób reakcji i zatwierdza raport. 6. System FRIEND i n f o r m u j e aktora Fi el dOf f i c e r o zatwierdzeniu raportu i przekazuje m u informację od dyspozytora.

Warunki

wstępne



Fi el dOf f i cer jest zalogowany do systemu FRI END

Warunki

końcowe



Fi el dOf f i c e r otrzymał potwierdzenie przesłania r a p o r t u oraz wiadomość od dyspozytora (Di spatcher)

InH lUD

Wymagania

jakościowe



Fi el dOf f i cer otrzymał od systemu wyjaśnienie, dlaczego transakcja nie może zostać zrealizowana



Raport aktora Fi el dOf f i c e r zostaje potwierdzony w czasie nie dłuższym niż 30 sekund



Fi el dOf f i c e r o t r z y m u j e o d p o w i e d ź nie później niż 30 sekund po wysłaniu jej przez dyspozytora

P r z y p a d k i użycia z a p i s y w a n e są w j ę z y k u n a t u r a l n y m . Jest to n i e z b ę d n e z p e r s p e k t y w y k o m u n i k a c j i p r o g r a m i s t ó w z u ż y t k o w n i k a m i , u k t ó r y c h z reguły n i e m o ż n a zakładać z n a j o m o ś c i t e r m i n o l o g i i i n o t a c j i p r o g r a m i s t y c z n e j . Użycie języka z r o z u m i a ł e g o dla wszystkich m a jeszcze tę zaletę, że u m o ż l i w i a z r o z u m i e n i e w y m a g a ń s y s t e m u p r z e z u c z e s t n i k ó w r e p r e z e n t u j ą c y c h dyscypliny w i e d z y i n n e niż inżynieria o p r o g r a m o w a n i a czy n a w e t dziedzina aplikacyjn a . P o n a d t o w j ę z y k u n a t u r a l n y m często wiele rzeczy d a się w y r a ż a ć z g r a b n i e j i czytelniej n i ż za p o m o c ą d i a g r a m ó w . W d i a g r a m a c h p r z y p a d k ó w użycia w y s t ę p u j ą cztery r o d z a j e zależności: k o m u n i k a c j a , z a w i e r a n i e , r o z s z e r z e n i e i d z i e d z i c z e n i e . P r z y j r z y j m y i m się n i e c o d o k ł a d n i e j .

Rozdział 2. • Modelowanie w języku UML

82

Zależności

komunikacyjne

Aktor i przypadek użycia komunikują się, gdy między nimi wymieniana jest informacja. Relacja komunikacji reprezentowana jest w diagramie za pomocą linii ciągłej łączącej aktora z przypadkiem użycia. W diagramie z rysunku 2.13 aktorzy Fi el dOf f i cer i Di spatcher komunikują się z przypadkiem użycia ReportEmergency. Aktor Dispatcher (i tylko on) komunikuje się ponadto z przypadkami użycia Openlncident i Al 1 ocateResources. Z relacji komunikacyjnych można odczytywać dostęp aktorów do poszczególnych elementów funkcjonalnych systemu: we wspomnianym diagramie obaj aktorzy — Fi el dOf f i cer i Di spatcher posługują się odmiennymi interfejsami i mają dostęp do różnych funkcji systemu.

Relacja

zawierania

W skomplikowanym systemie modele przypadków użycia mogą być również bardzo złożone. Jednym z oczywistych sposobów redukowania złożoności jest eliminowanie redundancji: elementy i konstrukcje występujące w przypadku użycia wielokrotnie są wówczas wyodrębniane w postaci osobnych przypadków użycia. Dyspozytor (Di spatcher) z naszego przykładu ma możliwość wyświetlenia na ekranie planu miasta w wyniku naciśnięcia odpowiedniego klawisza; ponieważ czynność ta będzie prawdopodobnie elementem kilku przypadków użycia (między innymi Openlncident i Al 1 ocateResources), wyodrębnimy ją w postaci przypadku użycia ViewMap, który dołączony zostanie do wyżej wymienionych przypadków (i prawdopodobnie wielu innych), czyli zostanie w nich zawarty. Funkcjonalność reprezentowana przez przypadek użycia ViewMap będzie opisana w modelu jednokrotnie, co zmniejszy jego redundancję i sprawi, że stanie się mniej złożony. Gdy jeden przypadek użycia jest częścią drugiego — czyli gdy przepływ zdarzeń w ramach jednego z nich jest częścią przepływu zdarzeń drugiego — mówimy, że przypadki te połączone są relacją zawierania. Na diagramie relacja ta odzwierciedlona jest w postaci linii przerywanej opatrzonej etykietą «i ncl ude» i zakończonej strzałką skierowaną w stronę przypadku zawieranego — czego przykład widzimy na rysunku 2.14.

Rysunek 2.14. Przykład relacji zawierania w diagramie przypadków użycia

W tekstowym opisie przypadków użycia relację zawierania uwzględniać będziemy na dwa sposoby. Jeśli wyodrębniony przypadek użycia będzie mógł być dołączany w dowolnym punkcie przepływu zdarzeń (jak przypadek Vi ewMap), fakt jego dołączania ujmiemy w wierszu Wymagania jakościowe (tak jak w tabeli 2.2); jeśli przypadek ten wywoływany będzie w konkretnych punktach przepływu zdarzeń, zaznaczać to będziemy w sposób jawny.

2.4. UML. — głębszy wgląd

83

Tabela 2.2. Tekstowa reprezentacja relacji zawierania z rysunku 2.14 (słowo „dołączyć" wyszczególnione jest czcionką pogrubioną) Nazwa przypadku Aktor

użycia

uczestniczący

Al l o c a t e R e s o u r c e s Di s p a t c h e r — inicjuje przypadek użycia

Przepływ zdarzeń

[...]

Warunki

wstępne



Di spatcher otwiera realizację zdarzenia (Inci dent)

Warunki

końcowe



Dla obiektu Inci dent zostały przydzielone dodatkowe zasoby



Obiekty Resource otrzymały informację o nowym przydziale

8

F i e l d O f f i c e r reagujący na Incident otrzymał informację o przydziale zasobów

Wym aga n i a jakościo we

Relacja

W dowolnym punkcie przepływu zdarzeń do bieżącego przypadku użycia można dołączyć przypadek użycia Vi ewMap, który zainicjowany przez dyspozytora (Di s p a t c h e r ) obejmuje wyświetlenie mapy. Mapa jest automatycznie pozycjonowana w taki sposób, by miejsce zdarzenia na niej było widoczne dla dyspozytora.

rozszerzania

Relacja rozszerzania to inny środek redukowania modelu przypadku użycia. Mając konkretny przypadek użycia, możemy rozszerzyć go o dodatkowe zdarzenia, a uzyskamy nowy, bardziej obszerny. Relacja rozszerzenia wskazuje, że instancja pewnego przypadku może zawierać (pod pewnymi warunkami) zachowanie reprezentowane przez przypadek rozszerzający. Typowym zastosowaniem relacji rozszerzania jest specyfikowanie zachowania w sytuacjach wyjątkowych. Jest na przykład zrozumiałe, że połączenie sieciowe między dyspozytorem (Dispatcher) a funkcjonariuszem (Fi el dOf f i cer) może zostać przerwane w dowolnej chwili z różnych przyczyn (przykładowo gdy FieldOfficer wjedzie swym samochodem do tunelu). Przypadek użycia ConnectionDown (patrz rysunek 2.15) opisuje zbiór zdarzeń generowanych przez system i aktorów w sytuacji przerwania połączenia; przypadek ten rozszerza przypadki Openlncident i Al 1 ocateResources. Oddzielenie zachowania wyjątkowego od „normalnego" umożliwia tworzenie krótszych i bardziej ukierunkowanych na meritum przypadków użycia. W tekstowym opisie przypadku użycia relacja rozszerzania specyfikowana jest w warunkach wstępnych przypadku rozszerzającego — w diagramie na rysunku 2.15 jest nim przypadek Connect! onDown (patrz opis w tabeli 2.3).

Rysunek 2.15. Przykład relacji rozszerzania

Rozdział 2. • Modelowanie w języku UML

84

Tabela 2.3. Tekstowa reprezentacja relacji rozszerzania zawierania z rysunku 2.15 (słowo „rozszerza"

wyszczególnione jest czcionką pogrubioną) Nazwa przypadku Aktorzy

użycia

uczestniczący

ConnectionDown Di s p a t c h e r i Fi el dOf f i c e r komunikują się ze sobą

Przepływ zdarzeń

[...]

Warunki

Ten przypadek rozszerza przypadki użycia Openlncident i Al 1 ocateResources. Jest inicjowany przez system w momencie, gdy przerwane zostanie połączenie sieciowe między aktorami Di s p a t c h e r i Fi el dOf f i cer.

wstępne

Warunki końcowe

[...]

Tym, co odróżnia relacje rozszerzania od relacji zawierania, jest lokalizacja zależności. Załóżmy, że dla aktora Dispatcher stworzyliśmy kilka nowych przypadków użycia, na przykład Updatelncident i Real 1 ocateResources. G d y b y ś m y m o d e l o w a l i p r z y p a d e k ConnectionDown

przy użyciu relacji zawierania, autorzy przypadków Updatelncident i Real 1 ocateResources musieliby być świadomi istnienia przypadku ConnectionDown. Używając relacji rozszerzania, eliminujemy taką konieczność, bo przypadek ConnectionDown dokonuje sam z siebie rozszerzania dwóch pozostałych. Generalnie wszelkie sytuacje wyjątkowe (takie jak błędy, pomoc czy inne nieoczekiwane warunki) modelowane są przy użyciu relacji rozszerzania, natomiast przypadki użycia reprezentujące zachowanie współdzielone przez ograniczony zbiór innych przypadków użycia modelowane są za pomocą relacji zawierania.

Relacja

dziedziczenia

Relacja dziedziczenia to trzeci mechanizm redukowania złożoności modelu. W wyniku dziedziczenia jeden przypadek użycia może stanowić konkretyzację innego, bardziej ogólnego, przez wzbogacenie go o dodatkowe szczegóły. I tak na przykład aktor Fi el dOf f i cer musi się zalogować (uwierzytelnić) do systemu FRIEND. We wczesnym stadium zbierania wymagań uwierzytelnianie jest modelowane przez ogólny („wysokopoziomowy") przypadek użycia Authenti cate. Później programiści opisują uwierzytelnianie w sposób bardziej szczegółowy, zależny od konkretnej platformy sprzętowej, przez co powstawać mogą na przykład przypadki użycia Authenti cateWithPassword (który odpowiada typowemu logowaniu aktora FieldOfficer na komputerze pozbawionym specyficznych urządzeń) i AuthenticateWithCard (który reprezentuje logowanie przy użyciu czytnika kart inteligentnych). Te dwa nowe przypadki użycia stanowią konkretyzacje (specjalizacje) przypadku użycia Authenti c a t e (patrz rysunek 2.16). W tekstowym opisie przypadku użycia takie specjalizowane przypadki dziedziczą z przypadku ogólnego aktora inicjującego oraz warunki wstępne i końcowe (patrz tabela 2.4). Zwróćmy uwagę na istotną różnicę między relacją rozszerzania a relacją dziedziczenia. Każdy z dwóch przypadków użycia uwikłanych w relację rozszerzania opisuje inny przepływ zdarzeń, podporządkowany innemu zadaniu. Na rysunku 2.15 przypadek Openlncident opisuje akcje składające się na „pierwszą reakcję" w związku z incydentem, natomiast przypadek użycia ConnectionDown opisuje działania podejmowane przez system i aktorów w sytuacji

85

2.4. UML. — głębszy wgląd

Authenticate WithCard

Rysunek 2.16. Przykład relacji dziedziczenia. Przypadek użycia Authenti c a t e jest wysokopoziomowym przypadkiem opisującym proces uwierzytelniania w kategoriach ogólnych. Przypadki A u t h e n t i c a t e ^-WithPassword i Authenti cateWi thCard są jego specjalizacjami Tabela 2.4. Tekstowa reprezentacja relacji dziedziczenia z rysunku 2.16 Nazwa przypadku

użycia

Aktorzy

uczestniczący

Przepływ

zdarzeń

AuthenticateWithCard Dziedziczeni z przypadku użycia A u t h e n t i c a t i o n 1. Fi el dOff i c e r umieszcza swą kartę w czytniku terminala. 2. Terminal akceptuje kartę i wyświetla k o m u n i k a t z żądaniem wprowadzenia kodu PIN. 3. Fi el dOf f i c e r wprowadza kod PIN z klawiatury numerycznej. 4. Czytnik terminala dokonuje porównania wprowadzonego kodu PIN z kodem PIN zapisanym na karcie. Gdy oba numery są zgodne, Fi el dOf f i cer zostaje uwierzytelniony, w przeciwnym razie próba uwierzytelniania zostaje odrzucona.

Warunki

wstępne

Dziedziczone z przypadku użycia A u t h e n t i c a t i o n

Warunki

końcowe

Dziedziczone z przypadku użycia A u t h e n t i c a t i o n

zerwania połączenia sieciowego. Na rysunku 2.16 natomiast przypadki Authenti cateWi th ^Password i Authenticate opisują ten sam proces — uwierzytelnianie — tyle że na różnych poziomach abstrakcji.

Scenariusze Przypadek użycia jest abstrakcją, która opisuje wszelkie możliwe scenariusze związane z określonym elementem funkcjonalnym systemu. Scenariusz jest instancją przypadku użycia, opisującą konkretny ciąg akcji. Scenariusze używane są jako przykłady ilustrujące konkretne sytuacje — ich treść koncentruje się na zrozumiałości. Przypadki użycia przeznaczone są do opisywania ogółu sytuacji pewnego rodzaju — sens istnienia przypadków użycia zasadza się na kompletności. Scenariusze opisywać będziemy w postaci szablonu składającego się z trzech pól, określających kolejno: • nazwę scenariusza, umożliwiającą jego jednoznaczne identyfikowanie; nazwa ta będzie podkreślona dla zaznaczenia, że mamy do czynienia z konkretną instancją,

Rozdział 2. • Modelowanie w języku UML

86

® instancje aktorów uczestniczących w scenariuszu — ich nazwy również będą podkreślone, ® przepływ zdarzeń, czyli opis, krok po kroku, sekwencji zdarzeń składających się na scenariusz. Zauważmy, że dla scenariusza nie określa się warunków wstępnych ani warunków końcowych. Warunki te są bowiem abstrakcjami umożliwiającymi programistom opisywanie całego spektrum założeń, których spełnienie jest konieczne do wywołania przypadku użycia. Ponieważ scenariusz dedykowany jest konkretnej sytuacji, warunki te są zbędne. W tabeli 2.5 zamieszczamy przykładowy scenariusz odzwierciedlający zdarzenie pożaru w hurtowni. Tabela 2.5. Scenariusz warehouseOnFi re jako instancja przypadku użycia ReportEmergency warehouseOnFi re

Nazwa Instancje aktorów

uczestniczących

bob, alice:FieldOfficer ,iohn:Dispatcher

Przepływ

zdarzeń

1. John, patrolując główną ulicę, zauważa przez o k n o s a m o c h o d u d y m wydobywający się z h u r t o w n i . Jego p a r t n e r k a Alice aktywuje funkcję „Raport Emergency" na laptopie systemu FRIEND. 2. Alice w p r o w a d z a do f o r m u l a r z a adres b u d y n k u , jego orientacyjną lokalizację ( p ó ł n o c n o - z a c h o d n i zakątek) i rozmiar zagrożenia. Dodatkowo żąda kilku jednostek pomocy paramedycznej, gdyż okolica pożaru jest dość gęsto zaludniona. Wysyła formularz i czeka na potwierdzenie. 3. Dyspozytor (Di spatcher) John zostaje zaalarmowany przez sygnał dźwiękowy na swojej stacji roboczej. Odczytuje informację od Alice i wysyła potwierdzenie raportu. Aktywuje jednostkę gaśniczą oraz dwie jednostki paramedyczne i wysyła do Alice informację o szacunkowym czasie oczekiwania na przybycie (ETA — Estimated Arrival Time). 4. Alice odczytuje potwierdzenie i informację o ETA.

2.4.2. Diagramy klas Klasy i obiekty Diagramy klas opisują strukturę systemu w kategoriach klas i obiektów. Klasa jest abstrakcją specyfikującą atrybuty i zachowanie zbioru obiektów — składa się na nią kolekcja wszystkich obiektów współdzielących identyczny zbiór atrybutów, który odróżnia te obiekty od obiektów należących do innych kolekcji. Obiekt jest encją zamykającą (enkapsulujacą) w sobie określony stan i zachowanie. Każdy obiekt ma swoją tożsamość — można się do niego jednoznacznie odwoływać, odróżniając go od innych obiektów.

2.4. UML. — głębszy wgląd

87

W diagramach UML Idasy i obiekty reprezentowane są przez prostokąty, podzielone w pionie na trzy części. Górna część zawiera nazwę klasy (obiektu), środkowa wyszczególnia jego atrybuty, zaś w dolnej części prezentowane są operacje klasy (obiektu). Dwie ostatnie części pomija się niekiedy dla potrzeb czytelności. Nazwy obiektów są podkreślone, co oznacza, że są one instancjami. Tradycyjnie nazwa klasy rozpoczyna się dużą literą. Obiekt może być opatrzony nazwą, po której następuje nazwa jego klasy; w takim przypadku nazwa obiektu rozpoczyna się małą literą. W systemie FRIEND (patrz rysunki 2.17 i 2.18) Bob i Alice są funkcjonariuszami, a na diagramie klas reprezentowani są przez obiekty (odpowiednio) b o b : F i e l d O f f i c e r i al i c e : F i e l d O f f i c e r ; F i e l d O f f i c e r jest klasą opisującą wszystkie obiekty reprezentujące funkcjonariuszy, podczas gdy bob i al i ce są dwoma jej obiektami.

Rysunek 2.17. Przykładowy diagram klas uczestniczących w przypadku użycia ReportEmergency. Szczegółowe informacje na temat typu atrybutów są zwykle pomijane, aż do etapu projektowania obiektów (patrz rozdział 9. „Projektowanie obiektów: specyfikowanie interfejsów")

Rysunek 2.18. Przykładowy diagram klas: obiekty uczestniczące w scenariuszu warehouseOnFi r e

W diagramie na rysunku 2.17 ldasa FieldOfficer posiada dwa atrybuty: name i badge ^Number, odpowiadające imieniu i numerowi odznaki funkcjonariusza; oznacza to, że atrybuty te posiadać będzie każdy obiekt klasy Fi el dOff i cer. W diagramie na rysunku 2.18 w obiektach bob: Fi el dOf f i cer i al i ce: Fi el dOf f i cer atrybuty te mają konkretne wartości, odpowiednio „Bob D." i „132" oraz „Alice W." i „23". Atrybut FieldOfficer.name jest wielkością typu S t r i n g , co oznacza że mogą mu być przypisywane jako wartości tylko instancje klasy String. Generalnie typ atrybutu określa zbiór wartości, jakie atrybut ten może przyjmować.

Rozdział 2. • Modelowanie w języku UML

88

Jeżeli w danym kontekście nie jest istotny typ atrybutu, decyzję o jego ustaleniu odkłada się na późniejsze stadia projektu. Pozwala to programistom skoncentrować się na funkcjonalności systemu i uwalnia od drobiazgowych zmian każdorazowo, gdy zmienia się obraz tej funkcjonalności.

Skojarzenia

i łącza

Łącze reprezentuje połączenie między dwoma obiektami. Skojarzenie jest relacją między klasami i może być uważane za grupę łączy. Każdy obiekt klasy Fi el dOf f i cer powiązany jest z listą raportów (stanowiących obiekty klasy EmergencyReport) sporządzonych przez funkcjonariusza reprezentowanego przez ten obiekt — na rysunku 2.17 linia łącząca klasy Fi el dOffi cer i EmergencyReport jest skojarzeniem. Linia łącząca obiekty al i ce: Fi el dOff i cer i report_129ł: EmergencyReport jest łączem. Łącze to reprezentuje w systemie informację o tym, że funkcjonariusz al i c e : F i e l d O f f i c e r sporządził raport report_1291:Emergency •-•Report. Skojarzenia UML mogą być symetryczne (dwukierunkowe) lub asymetryczne (jednokierunkowe). Na rysunku 2.19 widzimy przykład jednokierunkowego skojarzenia między klasami Wielokąt i Punkt. Strzałka skierowana w stronę pierwszej z tych klas oznacza, że system realizuje nawigację jedynie z klasy Wi el okąt do klasy Punkt. Innymi słowy, mając dany konkretny wielokąt, możemy wyliczyć wszystkie punkty będące jego wierzchołkami; dla konkretnego punktu nie sposób jednak wskazać wielokąta, którego wierzchołkiem jest (ewentualnie) ów punkt.

Wielokąt

Punkt

Rysunek 2.19. Przykład skojarzenia jednokierunkowego. Programiści zwykle pomijają nawigację na etapie analizy i określają ją dopiero na etapie projektowania obiektów (patrz rozdziały 8. „Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych" i 9. „Projektowanie obiektów: specyfikowanie interfejsów")

Analogicznie skojarzenia dwukierunkowe mogą być reprezentowane przez linie zakończone strzałkami z obu stron, choć często pomija się wówczas obie strzałki — linia nieskierowana reprezentuje więc skojarzenie dwukierunkowe.

Klasy

skojarzeniowe

Skojarzenia podobne są do klas w tym sensie, że można przyporządkowywać im atrybuty i operacje. Skojarzenie posiadające własne atrybuty i operacje nazywane jest klasą skojarzeniową. Klasy skojarzeniowe przedstawiane są na diagramach, tak jak „zwykłe" klasy, jednakże prostokąt symbolizujący klasę skojarzeniową połączony jest z odnośnym skojarzeniem linią przerywaną. W diagramie na rysunku 2.20 przydzielenie funkcjonariusza (klasa Fi el dOffi cer) do incydentu (klasa Inci dent) modelowane jest jako klasa skojarzeniowa posiadająca atrybuty role i notificationTime.

2.4. UML. — głębszy wgląd

89

Rysunek 2.20. Przykład klasy skojarzeniowej

Każdą klasę skojarzeniową wraz z odnośnym skojarzeniem można przekształcić w zwykłą klasę. Klasy uprzednio skojarzone ze sobą przestają być skojarzone bezpośrednio i zostają skojarzone z nową klasą, czego przykład widzimy na rysunku 2.21. Mimo iż reprezentacja na tym rysunku podobna jest do tej z rysunku poprzedniego, to jednak wersja z klasą skojarzeniową jest czytelniejsza: tak jak skojarzenie nie może istnieć bez klas, które kojarzy, tak klasa Al 1 ocati on z rysunku 2.21 nie ma samoistnej racji bytu, co jednak z diagramu w żaden sposób nie wynika. I choć oba diagramy (2.20 i 2.21) są sobie równoważne pod względem prezentowanej informacji, drugi z nich wymaga bardziej uważnej analizy krotności skojarzeń. Kompromisy tego rodzaju rozpatrywać będziemy dokładniej w rozdziale 5. „Analiza wymagań".

Rysunek 2.21. „Zwykła" klasa będąca wynikiem transformacji ldasy skojarzeniowej

Role Każdy z końców skojarzenia może być etykietowany przez rolę. Na rysunku 2.17 skojarzenie między klasami F i e l d O f f i c e r i EmergencyReport etykietowane jest rolami author („autor") i reportsGenerated („utworzone raporty"). Etykietowanie skojarzeń za pomocą ról nie tylko ułatwia rozróżnienie wielu skojarzeń wychodzących z tej samej klasy, lecz także sprawia, że znaczenie samych skojarzeń staje się czytelniejsze.

Krotność Każdy koniec skojarzenia może być także etykietowany za pomocą zbioru nieujemnych liczb całkowitych. Liczby te określają dopuszczalną liczbę skojarzeń wychodzących z instancji danej klasy, a zbiór tych liczb nazywany jest krótko krotnością skojarzenia. Na rysunku 2.17

90

Rozdział 2. • Modelowanie w języku UML

skojarzenie po stronie etykietowanej rolą author ma krotność równą 1 — oznacza to, że każdy raport (EmergencyReport) sporządzany jest przez dokładnie jednego autora funkcjonariusza (FieldOfficer); innymi słowy, każdy obiekt klasy EmergencyReport połączony jest łączem z dokładnie jednym obiektem klasy F i e l d O f f i c e r . Dla odmiany, krotność skojarzenia po stronie etykietowanej jako reportsGenerated ma postać „wiele", wyrażaną przez gwiazdkę, która z kolei jest skrótową formą zapisu 0. .n. Ten ostatni zapis wyraża oczywisty fakt, że dany funkcjonariusz teoretycznie sporządzać może wiele raportów lub nie sporządzić żadnego. W języku UML krotność może być dowolnym podzbiorem zbioru nieujemnych liczb całkowitych — na przykład zbiorem liczb pierwszych, zbiorem liczb parzystych i tak dalej. W praktyce jednak większość używanych skojarzeń da się podzielić na trzy typy (patrz rysunek 2.22).

Rysunek 2.22. Przykłady różnych krotności skojarzeń — kolejno od góry „jeden do jednego", „jeden na wiele" i „wiele na wiele"

° Jeden do jednego — takie skojarzenie posiada na obu końcach krotność będącą liczbą 1. Zgodnie z takim skojarzeniem między klasami (na przykład między klasami Pol i c e O f f i c e r i BadgeNumber), istnieje dokładnie jedno łącze między instancjami tych Idas (funkcjonariusz posiada dokładnie jedną odznakę, odznaka identyfikuje dokładnie jednego funkcjonariusza). • Jeden na wiele — jeden z końców takiego skojarzenia ma krotność równą 1, drugi natomiast krotność o postaci 0 . . n lub 1.. n. Skojarzenie takie reprezentuje kompozycję, na przykład oddział straży pożarnej (ldasa Firellnit) może być właścicielem wielu wozów strażackich (Fi reTruck), lecz każdy wóz strażacki jest własnością dokładnie jednego oddziału. • Wiele na wiele — na każdym z końców takiego skojarzenia krotność ma postać 0 . . n lub 1.. n. Skojarzenie takie występuje na przykład między klasami Fi el dOf f i cer i Inci dentReport, reprezentującymi (odpowiednio) funkcjonariusza i raport o incydencie: raport taki może mieć wielu autorów, a każdy funkcjonariusz może być autorem wielu takich raportów. Jest to najbardziej złożony typ skojarzenia. Określenie krotności skojarzeń zwiększa ilość informacji, jaką możemy uzyskać w zakresie dziedziny aplikacyjnej lub dziedziny realizacyjnej. Krotność staje się czynnikiem krytycznym wówczas, gdy określamy liczbę przypadków użycia niezbędnych do manipulowania

2.4. UML. — głębszy wgląd

91

obiektami dziedziny aplikacyjnej. Jakoprzykład rozpatrzmy klasyczny system plików, z katalogami (klasa Di rectory) i plikami (klasa Fi l es), traktowanymi ogólnie jako „elementy systemu plików" (klasa abstrakcyjna FileSystemElement). Każdy katalog zawierać może potencjalnie dowolną liczbę takich „elementów". W przypadku systemu ściśle hierarchicznego (jak stary dobry FAT) każdy „element systemu plików" należy do dokładnie jednego katalogu, co odpowiada relacji „jeden na wiele" (patrz rysunek 2.23). Jeśli jednak „element systemu plików" może należeć równocześnie do wielu katalogów — jak w systemach klasy UNIX — agregacja klasy FileSystemElement z klasą Directories musi być zrealizowana w postaci skojarzenia „wiele na wiele" (patrz rysunek 2.24).

Rysunek 2.23. Przykład hierarchicznego systemu plików. Katalog (Di r e c t o r y ) może zawierać dowolną liczbę elementów (FileSystemElement), z których każdy może być plikiem ( F i l e ) lub katalogiem (Di r e c t o r y ) , lecz każdy taki element jest częścią dokładnie jednego katalogu

Rysunek 2.24. Przykład niehierarchicznego systemu plików. Katalog (Directory) może zawierać dowolną liczbę elementów (Fi 1 eSystemEl ement), z których każdy może być plikiem (Fi 1 e) lub katalogiem (Di r e c t o r y ) , i każdy taki element może być częścią wielu katalogów równocześnie

W tym miejscu czytelnik może odnieść wrażenie, że zagłębiamy się zbytnio w szczegóły, które istotne bywają raczej w dalszych stadiach realizacji projektu. Otóż niezupełnie: różnice między hierarchicznym a niehierarchicznym systemem plików mają także swój funkcjonalny wymiar. Jeśli w systemie możliwe jest, by dany plik był częścią kilku katalogów, musimy opracować przypadki użycia opisujące między innymi dodawanie pliku do istniejącego już katalogu (na przykład za pomocą uniksowego polecenia link czy opcji MakeAl i as z menu kontekstowego pliku w systemie Mac OS X). Ponadto czynność usuwania pliku wymaga opracowania co najmniej dwóch przypadków użycia, obejmujących (odpowiednio) usuwanie pliku z jednego konkretnego katalogu lub permanentne usuwanie pliku ze wszystkich katalogów, w których jest obecny. Nic w tym dziwnego, że skojarzenia „wiele na wiele" skutkować mogą bardziej skomplikowanymi systemami.

92

Rozdział 2. • Modelowanie w języku UML

Agregacja Skojarzenia wykorzystywane są do reprezentowania szerokiego spektrum połączeń i zależności między obiektami. Jednym z bardzo często spotykanych w praktyce typów skojarzenia jest agregacja. Przykładowo w polskim ustroju administracyjnym województwo dzieli się na powiaty, te zaś na gminy, komisariat policji składa się z policjantów, zaś w katalogach znajdują się pliki. Oczywiście, zależności tego typu mogą być modelowane w postaci skojarzeń „jeden na wiele", UML oferuje jednak coś więcej — koncepcję agregacji, symbolizowaną przez zwykłą linię zakończoną rombem po stronie kontenera skojarzenia (czego przykłady widzimy na rysunkach 2.23 i 2.25). Mimo iż agregacja podobna jest do skojarzenia „jeden na wiele", nie może być jego zastępnikiem w każdej sytuacji, gdyż posiada specjalną własność, specyficzną tylko dla niektóiych skojarzeń „jeden na wiele" i „wiele na wiele": reprezentuje hierarchiczny charakter relacji między encjami. Na rysunku 2.17 klasy Fi el dOff i cer i EmergencyReport połączone są relacją „jeden na wiele" — funkcjonariusz może być autorem wielu raportów. Jest jednak oczywiste, że funkcjonariusz nie składa się z raportów, dlatego absolutnie nie mamy tu do czynienia z agregacją i musimy ograniczyć się do zwykłego skojarzenia.

Województwo

O -

Powiat

Komisariat

O -

Policjant

Katalog

O -

P1 i k

O -

Gmina

Rysunek 2.25. Przykład agregacji: Województwo składa się z Powi atów, te zaś — z Gmi n; Komi s a r i a t zatrudnia (czyli składa się z) Pol i cjantów; Katal og może zawierać wiele PI i ków

Kwalifikowanie Kwalifikowanie to technika redukowania krotności skojarzeń za pomocą kluczy. Skojarzenia typu 1 lub 0. .1 są łatwiejsze do zrozumienia niż skojarzenia typu 0 . . n czy 1 . . n . Często w ramach skojarzeń typu „jeden na wiele" końcówki po stronie „wiele" odróżniane są od siebie za pomocą unikalnych nazw. Przykładowo w danym katalogu nie mogą występować dwa pliki o tej samej nazwie (choć nic nie przeszkadza dublowaniu nazw plików w ramach różnych katalogów) — każdy plik jest więc jednoznacznie identyfikowany w ramach katalogu, w którym się znajduje. Nie używając kwalifikowania, wyrażamy ten fakt w postaci skojarzenia „jeden na wiele", jak w górnej części rysunku 2.26. Możemy jednak zredukować krotność po stronie „wiele", używając atrybutu f i 1 ename (reprezentującego nazwę pliku) jako klucza, zwanego także kwalifikatorem — jak w dolnej części rysunku 2.26. Skojarzenie między klasami Di r e c t o r y i Fi 1 e staje się wówczas skojarzeniem kwalifikowanym. Redukowanie krotności zawsze jest zjawiskiem pożądanym, bowiem dzięki niemu model staje się czytelniejszy i zmniejsza się liczba przypadków szczególnych, które trzeba brać pod uwagę. Programiści powinni zatem dokładnie analizować wszystkie skojarzenia „jeden na wiele" i „wiele na wiele" pod kątem tego, czy nie dadzą się one redukować przez użycie kwalifikatorów będących atrybutami klasy docelowej, tak jak na rysunku 2.26.

2.4. UML. — głębszy wgląd

93

Rysunek 2.26. Przykład redukowania krotności skojarzenia za pomocą kwalifikowania. Dodanie kwalifikatora sprawia, że diagram staje się bardziej czytelny, i zwiększa ilość prezentowanej przez niego informacji — w tym przypadku kwalifikowanie odzwierciedla fakt, że nazwa (filename) pliku (Fi 1 e) jest unikalna w ramach katalogu (Di r e c t o r y )

Dziedziczenie Dziedziczenie jest relacją wiążącą klasę ogólną z klasami bardziej szczegółowymi, będącymi jej konkretyzacjami. Dziedziczenie umożliwia opisywanie atrybutów i operacji wspólnych dla całego zbioru klas. Przykładowo zarówno funkcjonariusz (Fi el dOffi cer), jak i dyspozytor (Di spatcher) posiadają nazwisko (atrybut name) i odznakę z unikalnym numerem (atrybut badgeNumber), chociaż funkcjonariusz kojarzony jest z raportem o zagrożeniu (EmergencyReport), zaś dyspozytor kojarzony jest z samym zdarzeniem nadzwyczajnym (Incident). Oba wspólne atrybuty — name i badgeNumber — stają się wobec tego podstawą do zdefiniowania bardziej ogólnej klasy, wspólnej dla Fi el dOffi cer i Di spatcher — klasy PoliceOfficer reprezentującej policjanta w ogóle (patrz rysunek 2.27). Taka ogólna klasa nazywa się superklasą, zaś dziedziczące po niej klasy bardziej konkretne nazywane są subklasami. Subklasy dziedziczą po swej superklasie wszystkie atrybuty i operacje. Superklasa może być klasą abstrakcyjną — wyjaśnialiśmy to pojęcie w sekcji 2.3.3 — jej nazwa pisana jest wówczas kursywą. Taką klasą abstrakcyjną jest na rysunku 2.27 klasa PoliceOfficer. Klasy abstrakcyjne wykorzystywane są w modelowaniu zorientowanym obiektowo do reprezentowania powiązanych ze sobą koncepcji, dzięki czemu zmniejsza się złożoność modeli.

Rysunek 2.27. Przykład dziedziczenia. PoliceOfficer jest klasą abstrakcyjną definiującą wspólne atrybuty i operacje dla swych konkretnych subklas Fi el dOffi cer i Di s p a t c h e r

94

Rozdział 2. • Modelowanie w języku UML

Zachowanie obiektu opisywane jest przez jego operacje. Jeden obiekt żąda wykonania określonej operacji przez drugi, wysyłając mu komunikat. Otrzymany komunikat kojarzony jest z odpowiednią metodą definiowaną w klasie, do której należy obiekt adresat komunikatu, łub w którejś7 z jej superklas. Metoda jest obiektowym mechanizmem implementowania operacji. Rozróżnienie między operacją a metodą pozwala na odróżnienie zachowania obiektu (reprezentowanego przez operację) od jego implementacji (uosabianej przez metodę lub zbiór metod, definiowanych w różnych klasach na zasadzie dziedziczenia). Na rysunku 2.28 dla klasy Incident zdefiniowana jest operacja assignResource(), która zainicjowana przez dyspozytora (Dispatcher) powoduje utworzenie skojarzenia między zdarzeniem (obiektem klasy Incident) a konkretnym zasobem (będącym obiektem klasy Resource). Operacja assignResource() prawdopodobnie przejawia także efekty uboczne, między innymi w postaci „poinformowania" przydzielonego zasobu o fakcie i czasie dokonania przydziału. Operacja close() klasy Incident powoduje zakończenie obsługi wydarzenia reprezentowanego przez obiekt tej klasy. Mimo iż język UML odróżnia operacje od metod, programiści często ignorują tę różnicę, mówiąc po prostu o „metodach".

Rysunek 2.28. Przykład operacji definiowanych w klasie I n c i d e n t

Zastosowania

diagramów

klas

Diagramy Idas używane są do opisywania struktury systemu. Na etapie analizy programiści i projektanci tworzą diagramy klas w celu sformalizowania wiedzy pochodzącej z dziedziny aplikacyjnej. Klasy reprezentują wówczas obiekty uczestniczące w diagramach przypadków użycia oraz diagramach interakcji i opisują atrybuty i operacje tych obiektów. Zadaniem modeli analitycznych jest opisanie zakresu tematycznego systemu i zidentyfikowanie jego granic (warunków brzegowych). W przykładzie z rysunku 2.17 analityk potrafi odpowiednio zinterpretować krotności skojarzenia łączącego klasy Fi el dOffi cer i EmergencyReports („jeden funkcjonariusz może sporządzić zero lub kilka raportów o zagrożeniu, każdy taki raport jest sporządzany przez dokładnie jednego funkcjonariusza") i poddać tę kwestię pod ocenę użytkownikowi — czy raport o zagrożeniu może mieć jednak wielu autorów? Czy dopuszczalne są raporty anonimowe? Zależnie od otrzymanej odpowiedzi analityk być może zmieni model tak, by faktycznie odzwierciedlał dziedzinę aplikacyjną. Tworzenie modeli analitycznych opisujemy w rozdziale 5. „Analiza wymagań". Modele analityczne abstrahują od kwestii implementacyjnych. Zagadnienia, takie jak szczegóły interfejsu użytkownika, komunikacja sieciowa czy sposób magazynowania danych, 7

Dana klasa może mieć kilka superklas nawet w warunkach jednokrotnego dziedziczenia: po prostu relacja „bycia superklasą" jest relacją przechodnią — superklasa superklasy jest superklasą — p r z y p . tłum.

2.4. UML. — głębszy wgląd

95

nie leżą w gestii tych modeli. Dopiero na następnych etapach diagramy klas uzupełniane są o elementy reprezentujące koncepcje pochodzące z obszaru dziedziny realizacyjnej. Wtedy właśnie programiści definiują klasy reprezentujące bazy danych, okna interfejsu użytkownika, otoczki adaptujące gotowy przestarzały kod do nowych potrzeb, optymalizację kodu i tak dalej. Klasy te grupowane są zwykle w podsystemy z dobrze zdefiniowanymi interfejsami. Budowanie takich modeli opisujemy w rozdziałach 6. „Projektowanie systemu — dekompozycja na podsystemy", 8. „Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych", 9. „Projektowanie obiektów: specylikowanie interfejsów" i 10. „Odwzorowywanie modelu na kod".

2.4.3. Diagramy interakcji Diagramy komunikacji przedstawiają wzorce komunikacji pomiędzy obiektami z pewnego zbioru. Obiekty współdziałają ze sobą poprzez wysyłanie komunikatów. Odebranie komunikatu przez obiekt wyzwala wykonywanie określonej jego metody, a skutkiem tego wykonywania może być wysyłanie kolejnych komunikatów do innych obiektów. Częścią komunikatu są zazwyczaj argumenty (parametry), które przekazywane są do wspomnianej metody. W języku UML diagramy interakcji występują w dwóch odmianach: diagramów sekwencji i diagramów komunikacyjnych. Diagram sekwencji ma strukturę dwuwymiarową: kierunek poziomy wypełniany jest przez poszczególne obiekty uczestniczące w interakcji, zaś kierunek pionowy reprezentuje upływający czas. Wyjaśnimy tę zasadę na przykładzie zegarka posiadającego dwa przyciski, reprezentowanego przez klasę 2Bwatch. Aby skorygować wskazanie czasu, posiadacz zegarka, aktor 2Bwatch0wner, naciska równocześnie oba przyciski, przestawiając zegarek w tryb ustawiania czasu. W trybie tym można oddzielnie korygować wskazanie każdej z sekcji — godzin, minut i sekund; aktualnie korygowana sekcja (nazwijmy ją sekcją aktywną) wyróżniona jest przez migotanie. Bezpośrednio po wejściu w tryb ustawiania czasu aktywna jest sekcja godzin, a naciskanie pierwszego przycisku powoduje cykliczne przenoszenie aktywności — z godzin na minuty, z minut na sekundy, z sekund z powrotem na godziny i tak dalej. Naciskanie drugiego przycisku powoduje inkrementowanie wskazania aktywnej sekcji (cyklicznie — po osiągnięciu maksymalnej wartości na przykład 59 dla godzin czy minut następuje wartość 0). Równoczesne naciśnięcie obu przycisków powoduje ponowne przejście zegarka w tiyb odmierzania czasu. Diagram na rysunku 2.29 odpowiada sytuacji, gdy aktor zwiększa o 1 wskazanie sekcji minut. Każda kolumna diagramu odpowiada jednemu obiektowi uczestniczącemu w interakcji; komunikaty reprezentowane są przez linie ciągłe zakończone wypełnionymi strzałkami, etykiety opatrujące te linie są nazwami komunikatów, być może zawierających także parametry. Aktywacja metody wywoływanej wskutek otrzymania przez obiekt komunikatu reprezentowana jest przez pionowe prostokąty. Aktor inicjujący interakcję reprezentowany jest w skrajnej lewej kolumnie — pochodzące od niego komunikaty reprezentują interakcje przedstawiane w diagramach przypadków użycia. Jeśli w przypadek użycia uwikłani są także inni aktorzy, są oni reprezentowani w skrajnych prawych kolumnach i mogą otrzymywać komunikaty. Mimo iż dla prostoty wszelkie interakcje na diagramie reprezentowane są w sposób jednolity, przez komunikaty, należy mieć świadomość, że interakcja między aktorami a systemem ma zupełnie inną naturę niż interakcja między obiektami systemu.

96

Rozdział 2. • Modelowanie w języku UML

Rysunek 2.29. Przykład diagramu sekwencji — korygowanie wskazań zegarka 2Bwatch

Diagramy sekwencyjne mogą być wykorzystywane do opisywania sekwencji abstrakcyjnych (czyli wszelkich możliwych interakcji) lub konkretnych sekwencji zdarzeń, tak jak na rysunku 2.29. W tym pierwszym przypadku w diagramie może być konieczne przedstawienie wyboru jednego spośród dwóch komunikatów, na podstawie pewnego warunku i (lub) iteracji komunikatów — na tę okazję język UML oferuje stosowne elementy notacji (patrz rysunek 2.30). Iteracja reprezentowana jest przez opatrzenie etykietą 1 oop powtarzanego fragmentu, zaś wybór (alternatywa) reprezentowany jest przez etykietę al t: po wyszczególnieniu warunku w nawiasach prostokątnych (tu: i>0) następuje komunikat wysyłany przy spełnieniu tego warunku (tu: op 1 ()), zaś komunikat wysyłany w przeciwnym razie (tu: op2 ()) specyfikowany jest po frazie [el se].

Rysunek 2.30. Przykłady wyboru i iteracji w diagramie sekwencji

97

2.4. UML. — głębszy wgląd

Diagramy komunikacyjne wyrażają tę samą informację, co diagramy sekwencji, lecz sekwencje komunikatów reprezentowane są przez ponumerowanie interakcji. Z jednej strony, znosi to geometryczne wymogi formy, niezbędne dla diagramu sekwencji, i skutkuje diagramem w formie bardziej zwartej, z drugiej jednak, sprawia, iż przesyłane komunikaty staja się nieco trudniejsze do śledzenia. Na rysunku 2.31 przedstawiamy diagram komunikacyjny równoważny diagramowi sekwencji z rysunku 2.29.

Rysunek 2.31. Przykład diagramu komunikacyjnego, przedstawiającego korygowanie wskazań zegarka 2Bwatch. Diagram ten reprezentuje ten sam przypadek użycia, co diagram sekwencji z rysunku 2.29

Zastosowanie

diagramów

interakcji

Diagramy interakcji opisują interakcje między obiektami. Jednym z powodów konstruowania takich diagramów jest identyfikowanie zakresu odpowiedzialności poszczególnych klas w diagramach klas, a być może także odkrywanie potrzeby definiowania nowych klas. Innymi słowy, diagramy interakcji pomagają programiście w dokonaniu przyporządkowania poszczególnych operacji do odpowiednich obiektów. Zwykle dzieje się tak, że dla każdego przypadku użycia konstruowany jest diagram interakcji koncentrujący się na przepływie zdarzeń tego przypadku. Programista identyfikuje obiekty uczestniczące w przypadku użycia i rozdziela między nie poszczególne elementy zachowania opisywanego przez ten przypadek, definiując stosowne operacje. Diagramy interakcji występują zwykle w duecie z odpowiednimi diagramami klas: gdy zdefiniowany zostanie początkowy diagram klas, dla każdej z kolejnych jego mutacji tworzony jest diagram interakcji. Często prowadzi to do ulepszania samego przypadku użycia przez korygowanie niejasnych opisów, uzupełnianie niekompletnych opisów zachowań i tym podobne, a w konsekwencji do definiowania nowych obiektów i usług. Do diagramów interakcji powrócimy w rozdziale 5. „Analiza wymagań".

98

Rozdział 2. • Modelowanie w języku UML

2.4,4. Diagramy stanów Maszyna stanu to element języka UML opisujący sekwencję stanów, w jakich kolejno przebywa dany obiekt w ramach odpowiedzi na zdarzenia zewnętrzne. Model maszyny stanów UML jest rozszerzeniem modelu automatu skończonego. Maszyna stanów dostarcza środki zarówno do zagnieżdżania stanów (stan może być opisywany przez maszynę stanów), jak i wiązania stanu obiektu z komunikatami oraz warunkami. Maszyna stanów UML bazuje w dużym stopniu na mapach stanów (statecharts) D. Harela ([Harel, 1987]) zaadaptowanych do wykorzystywania na gruncie modelu obiektowego [Douglass, 1999], Maszynę stanów UML wykorzystywać można z powodzeniem do reprezentowania dowolnego automatu Mealyego lub dowolnego automatu Moore'a. Poprzez stan obiektu rozumiemy warunek określony przez jego atrybuty. Przykładowo obiekt klasy Incident systemu FRIEND może znajdować się czterech stanach: Active, Inactive, Closed i Archived (patrz rysunek 2.32). Stan Active oznacza rozpoznanie sytuacji wymagającej interwencji (pożar, wypadek drogowy). Skuteczna interwencja prowadzi do stanu Inactive — sytuację opanowano, ale nie sporządzono jeszcze raportu (na przykład ugaszono pożar, lecz nie oszacowano jeszcze strat). Gdy skompletowana zostanie dokumentacja incydentu, będziemy mieli do czynienia ze stanem Cl osed. „Zamknięty" incydent może z czasem zostać przeniesiony z bazy głównej do archiwum i wtedy zaistnieje stan Archi ved. W ogólnym przypadku stan obiektu ustalany jest na podstawie kilku jego atrybutów.

Rysunek 2.32. Diagram stanów dla klasy Inci dent

Przejście między stanami to zmiana stanu wywołana przez zdarzenie, spełnienie warunku lub upływ czasu. Na diagramie z rysunku 2.32 widoczne są trzy przejścia, odpowiednio o d s t a n u Active d o Inactive, o d Inactive d o Cl osed.i o d Closed d o Archived.

Stan reprezentowany jest na diagramie przez zaokrąglony prostokąt, zaś przejście między stanami — przez linię ciągłą, zakończoną otwartą strzałką i łączącą prostokąty reprezentujące owe stany. Każdy stan etykietowany jest przez swą nazwę. Małe czarne kółeczko reprezentuje stan początkowy, zaś czarne kółeczko otoczone białym.pierścieniem — stan końcowy. Na rysunku 2.33 widzimy inny przykład — maszynę stanów dla zegarka 2Bwatch (tego samego, dla którego skonstruowaliśmy diagram sekwencji widoczny na rysunku 2.29). Z perspektywy najwyższego poziomu abstrakcji zegarek ten może znajdować się w dwóch stanach: MeasureTime, który odpowiada pomiarowi czasu, oraz SetTime, który odpowiada korygowaniu wskazania czasu. Naciśnięcie równocześnie obu jego przycisków powoduje zmianę stanu; przy przejściu ze stanu SetTime do stanu MeasureTime zegarek wydaje krótki sygnał

2.4. UML. — głębszy wgląd

99

Rysunek 2.33. Diagram stanów dla funkcji SetTime zegarka 2Bwatch

dźwiękowy, co odpowiada akcji /beep towarzyszącej temu przejściu. Po pierwszym włączeniu zasilania (czyli po wymianie baterii) zegarek znajduje się w stanie SetTime — dlatego na diagramie stan ten jest wyróżniony jako początkowy. Gdy wyczerpie się bateria (zdarzenie batteryEmpty), zegarek staje się bezużyteczny do czasu jej wymiany: stan DeadBattery reprezentujący tę sytuację wyróżniony więc został jako końcowy. W prezentowanym przykładzie przejścia między stanami mogą być wyzwalane przez zdarzenia (pressBothButtons lub batteryEmpty) albo przez upływ czasu (mi nęły 2 mi nuty)8. Na rysunku 2.34 widoczny jest ulepszony diagram stanów dla zegarka 2Bwatch. Powstał on w wyniku wzbogacenia diagramu z rysunku 2.33 o akcje opisujące zachowanie się obiektu w ramach poszczególnych stanów. Akcje są fundamentalnymi jednostkami przetwarzania; mogą czerpać i wykorzystywać informację wejściową oraz produkować informację wynikową, mogą też zmieniać stan systemu. Akcje wykonywane są zwykle dość szybko i nie są przerywalne — akcja musi wykonać się w całości lub nie wykonać w ogóle. W szczególności akcja może być zrealizowana jako wywołanie operacji. W ramach maszyny stanów akcje zachodzić mogą w trzech miejscach: • w czasie przejścia między stanami (jak akcja beep związana z przejściem od stanu SetTime do stanu MeasureTime — przejściem wywołanym przez zdarzenie pressBothButtons),

• w momencie wchodzenia obiektu w określony stan (jak akcja bl ink hours w stanie SetTime na rysunku 2.34), • w momencie wychodzenia obiektu z określonego stanu (jak akcja stop bl i nki ng w stanie SetTime na rysunku 2.34).

8

Gdy zegarek znajduje się w stanie SetTime i przez 2 minuty nie zostanie naciśnięty żaden przycisk, zegarek samoczynnie przełącza się do stanu MeasureTime — przyp. tłum.

100

Rozdział 2. • Modelowanie w języku UML

Rysunek 2.34. Przejścia między stanami obiektu 2Bwatch

Akcje kończące przebywanie obiektu w danym stanie wykonywane są w pierwszej kolejności. Po nich wykonywane są akcje związane z przejściem między stanami, po czym przychodzi kolej na akcje rozpoczynające przebywanie obiektu w nowym stanie. Akcje związane z wchodzeniem w dany stan lub wychodzeniem z niego wykonywane są zawsze, bez względu na przejście odpowiedzialne za zmianę stanu. Przejściem wewnętrznym jest przejście, które nie prowadzi do zmiany stanu. Przejścia wewnętrzne wyzwalane są przez zdarzenia i można przypisywać im akcje; uruchomienie przejścia wewnętrznego nie powoduje natomiast żadnych akcji związanych ze stanem — me jest wykonywana ani początkowa, ani końcowa akcja dla stanu, w którym obiekt wciąż pozostaje. Stan SetTime z diagramu na rysunku 2.34 posiada dwa przejścia wewnętrzne, każde związane z naciśnięciem jednego przycisku. Aktywność to skoordynowany zbiór akcji. Ze stanem może być powiązana aktywność trwająca tak długo, jak długo obiekt pozostaje w tym stanie. Podczas gdy akcje są z założenia krótkie i nieprzerywalne, aktywność może zajmować dużo czasu i jest przerywana w momencie, gdy uruchamiane jest przejście prowadzące do opuszczenia danego stanu. Na diagramie aktywności wiązane są ze stanem za pomocą etykiety do/, po której następuje nazwa odnośnej aktywności. W diagramie na rysunku 2.34 aktywność count t i c k s powiązana jest ze stanem MeasureTime.

Zagnieżdżone maszyny stanów są kolejnym środkiem redukowania złożoności modeli. Można ich używać zamiast przejść wewnętrznych. Na rysunku 2.35 bieżące wskazanie czasu (i daty) traktowane jest jako zagnieżdżony stan, podczas gdy akcje związane z jego modyfikowaniem modelowane są za pomocą przejść wewnętrznych. Zauważmy, że każdy stan może być modelowany jako zagnieżdżony — przykładowo na stan BlinkHours składają się 24 różne podstany odpowiadające różnych wskazaniom godziny; przejścia między tymi podstanami odbywają się wskutek naciskania prawego przycisku.

2.4. UML. — głębszy wgląd

101

Rysunek 2.35. Udoskonalona maszyna stanów skojarzona ze stanem SetTime. Akcje 1B i rB reprezentują naciskanie (odpowiednio) lewego i prawego przycisku

Zastosowania

diagramów

stanów

Diagramy stanów znajdują zastosowanie w reprezentowaniu niebanalnego zachowania podsystemu lub obiektu. W odróżnieniu od diagramów interakcji skupiających się na zdarzeniach wpływających na zachowanie grupy obiektów, diagramy stanów uwidaczniają atrybuty lub grupy atrybutów decydujące o zachowaniu pojedynczego obiektu. Maszyny stanów używane są także do identyfikowania atrybutów obiektów i ulepszania opisu ich zachowania, podczas gdy diagramy interakcji umożliwiają identyfikowanie obiektów uczestniczących w oferowanych usługach. Ponadto diagramy stanów mogą być wykorzystywane na etapie projektowania systemu i projektowania obiektów do opisu obiektów dziedziny realizacyjnej przejawiających interesujące zachowania. Diagramami stanów zajmiemy się dokładniej w rozdziałach 5. „Analiza wymagań" i 6. „Projektowanie systemu — dekompozycja na podsystemy".

2.4.5. Diagramy aktywności Diagramy aktywności reprezentują sekwencjonowanie i koordynowanie zachowania niższego poziomu. Zadaniem diagramu aktywności jest ukazanie, jak zachowanie systemu realizowane jest w postaci jednej lub kilku sekwencji aktywności, przy udziale obiektów koordynujących te sekwencje. Diagramy aktywności mają naturę hierarchiczną: aktywność może być pojedynczą akcją, ale może także stanowić graf prostszych podaktywności wraz z powiązanymi obiektami. Diagram aktywności z rysunku 2.36 jest odpowiednikiem diagramu stanów z rysunku 2.32. Zaokrąglone prostokąty reprezentują akcje i aktywności, zaś linie łączące te prostokąty — przepływ sterowania. Każda aktywność może być rozpoczęta tylko wówczas, gdy zakończy się wykonywanie wszystkich aktywności poprzedzających ją na diagramie. Węzły kontrolne stanowią punkty koordynacji przepływu sterowania na diagramie, dostarczają środki do realizowania współbieżności, synchronizacji i podejmowania decyzji. Decyzje oznaczają rozgałęzienie przepływu sterowania. Reprezentują wybór jednej z kilku ścieżek sterowania na podstawie warunku określonego przez stan obiektu lub grupy obiektów. Na diagramach decyzja oznaczana jest przez romb, do którego prowadzi jedna lub

102

Rozdział 2. • Modelowanie w języku UML

Rysunek 2.36. Diagram aktywności dla klasy Incident. W ramach akcji Handl elnci dent dyspozytor otrzymuje raporty i przydziela zasoby. Gdy obsługa incydentu zostanie zamknięta, sporządzana jest jego dokumentacja — w aktywności Documentlncident uczestniczą funkcjonariusze (FieldOfficer) i dyspozytor (Dispatcher). Gdy incydent przestanie być już tematem nr 1, przenosi się (aktywność Archivelncident) jego dokumentację do archiwum, zrealizowanego w oparciu o nośnik zapewniający bardziej efektywne magazynowanie dużej porcji informacji za cenę mniej sprawnego wyszukiwania (na przykład streamer)

więcej ścieżek wejściowych i z którego wychodzą dwie lub więcej ścieżek wyjściowych. Każda ścieżka wyjściowa etykietowana jest przez warunek decydujący o jej wyborze. Liczba możliwych wartości warunku selekcyjnego determinuje liczbę ścieżek wyjściowych. Na rysunku 2.37 widzimy sytuację, gdy po zakończeniu aktywności Openlncident podejmowana jest decyzja wyboru jednego z trzech wariantów sterowania: jeśli incydent jest pożarem (fi re) i ma wysoki priorytet (hi ghPri ori ty), alarmowany jest komendant straży pożarnej; jeśli incydent ma wysoki priorytet, lecz jest zdarzeniem innym niż pożar (not fi re), alarmowany jest szef lokalnej policji. Gdy incydent jest zdarzeniem o niskim priorytecie (1 owPri ori ty), nie ma potrzeby alarmowania osób na wyższym szczeblu zarządzania, od razu przechodzi się do aktywności przydzielania zasobów.

Rysunek 2.37. Przykład podejmowania decyzji, zależnie od rodzaju incydentu. Rozróżniane są trzy wartości warunku selekcyjnego: pożar o wysokim priorytecie ( f i re & hi ghPri ori ty), zdarzenie o wysokim priorytecie inne niż pożar (not f i r e & hi ghPri ori ty) i zdarzenie o niskim priorytecie (1 owPri ori ty)

Ze współbieżnością wiążą się dwa rodzaje węzłów kontrolnych: rozwidlenia i złączenia. Rozwidlenie oznacza podział sterowania na dwa lub więcej wątków realizowanych równocześnie, zaś złączenie oznacza punkt synchronizacji wątków, czyli ponowne połączenie sterowania w jeden wątek. W diagramie na rysunku 2.38 akcje Ali ocateResources, CoordinateResources i Document Inci dent mogą być wykonywane współbieżnie, jednakże żadna z nich nie może się rozpocząć przed zakończeniem akcji Openlncident i wszystkie muszą się zakończyć, by mogła rozpocząć się akcja Archi vel nci dent.

103

2.4. UML. — głębszy wgląd

Rysunek 2.38. Przykład rozwidlenia i złączenia

Aktywności mogą być grupowane w partycje aktywności, w oryginalnej specyfikacji UML nazywane obrazowo „torami pływackimi" (swimlanes), w celu ukazania obiektu lub podsystemu odpowiedzialnego za ich implementację. Partycje takie reprezentowane są na diagramach aktywności w postaci prostokątów zamykających grupy aktywności; granice tych partycji mogą być przecinane przez linie reprezentujące przejścia między stanami. Na rysunku 2.39 widzimy dwie partycje grupujące aktywności związane z obiektami klasy (odpowiednio) Fi el dOf f i cer i Di spatcher.

Rysunek 2.39. Przykład partycjonowania aktywności

Zastosowania

diagramów

aktywności

Diagramy aktywności dostarczają spojrzenia na grupę obiektów z perspektywy realizowanych przez nie zadań. Ten punkt widzenia może być użyteczny na przykład przy identyfikowaniu sekwencji ograniczających przypadki użycia, sekwencji zachowań przejawianych przez grupy obiektów czy też przy spojrzeniu na projekt pod kątem realizowanych w ramach niego zadań. W tej książce wykorzystywać będziemy diagramy aktywności w rozdziałach 14. „Zarządzanie projektem" i 15. „Cykl życiowy oprogramowania".

104

Rozdział 2. • Modelowanie w języku UML

2.4.6. Organizacja diagramów Modele skomplikowanych systemów rozwijane są sukcesywnie i szybko stają się równie skomplikowane jak modelowana rzeczywistość. Komplikację tę można zmniejszać, organizując w pakiety powiązane ze sobą elementy modelu — przypadki użycia, klasy i tym podobne. Pakiety reprezentowane są na diagramach w postaci prostokątów opatrzonych zakładkami z lewej strony górnej krawędzi. Diagram z rysunku 2.40 przedstawia system FRIEND pogrupowany według powiązanych z nim aktorów — i tak przypadki użycia związane z zarządzaniem incydentem (rozpoczynanie interwencji, przydzielanie zasobów, dokumentowanie) zgrupowane są w pakiecie IncidentManagement, zaś powiązane z archiwizowaniem incydentu (przenoszenie danych na nośnik archiwizacyjny, generowanie raportów na podstawie zarchiwizowanych incydentów) zgrupowane są w pakiecie Inci dentArchi ve; pakiet SysAdmi ni s t r a t i on zawiera natomiast przypadki użycia o charakterze administracyjnym (definiowanie użytkowników czy rejestrowanie zdalnych stacji). Umożliwia to klientowi oraz programistom powiązanie przypadków użycia w grupy i skupienie się na ich podzbiorze interesującym w danym kontekście.

Rysunek 2.40. Przykład grupowania przypadków użycia w pakiety — organizacja systemu FRIEND z perspektywy udziału aktorów

Na rysunkach 2.41 i 2.42 przedstawiamy przykłady użycia pakietów w diagramach klas — klasy związane z przypadkiem użycia ReportEmergency zorganizowane są pod kątem miejsca tworzenia obiektów: klasy Fi el dOf f i cer i EmergencyReport są częścią pakietu Fi el dStati on, zaś klasy Dispatcher i Incident stanowią część pakietu DispatcherStation. Na rysunku 2.40 widoczne są pakiety grupujące elementy modelu, zaś na rysunku 2.41 przedstawiono te same pakiety bez wyszczególniania ich zawartości. Rysunek 2.41 prezentuje więc bardziej

2.4. UML. — głębszy wgląd

105

Rysunek 2.41. Kolejny przykład pakietów — są to pakiety z rysunku 2.40, bez uwidaczniania ich zawartości

Rysunek 2.42. Przykład grupowania klas w pakiety: klasy F i e l d O f f i c e r i EmergencyReport są częścią pakietu F i e l d S t a t i o n , zaś klasy Di s p a t c h e r i I n c i d e n t stanowią część pakietu Di s p a t c h e r S t a t i o n

wysokopoziomowe spojrzenie na system FRIEND, użyteczne przy jego funkcjonalnej analizie, podczas gdy szczegółowy widok z rysunku 2.40 może być bardziej przydatny podczas dyskutowania zawartości poszczególnych pakietów. Pakiety przyczyniają się do upraszczania diagramów UML na tej samej zasadzie, zgodnie z którą system plików staje się prostszy dzięki zorganizowaniu plików w katalogi, z jedną wszakże istotną różnicą: pakiety niekoniecznie muszą mieć strukturę hierarchiczną — ta sama klasa może pojawić się w kilku pakietach. By zminimalizować ryzyko związanej z tym faktem niespójności, każda z klas (i ogólnie — każdy element modelu) jest własnością dokładnie jednego ze wspomnianych pakietów, podczas gdy pozostałe zawierają jedynie odwołania do niej. Zauważmy, że pakiety są jednostkami organizacji, nie obiektami: nie można im przypisać określonego zachowania, nie mogą więc wysyłać i otrzymywać komunikatów. Notatka jest komentarzem załączonym do diagramu. Notatki używane są przez programistów w charakterze informacji uzupełniających model i jego elementy, stanowią więc idealne narzędzie do zapisywania rozmaitych uwag, wyjaśniania różnych skomplikowanych kwestii czy tworzenia list problemów czekających na rozwiązanie. Mimo iż notatki pisane są językiem potocznym i jako takie nie prezentują żadnej określonej semantyki, same z siebie mogą być nieocenione pod względem formułowania treści niedających się w żaden sposób wyrazić za pomocą semantyki UML. Widać to wyraźnie choćby na rysunku 2.43, gdzie notatka w wyraźny sposób artykułuje relacje między klasą EmergencyReport a obydwoma pakietami.

106

Rozdział 2. • Modelowanie w języku UML

Rysunek 2.43. Przykład notatki. Notatka może być związana z konkretnym elementem diagramu

2.4.7. Rozszerzenia diagramów Intencją twórców języka UML było dostarczenie notacji wystarczającej do opisywania jak najszerszej gamy różnych systemów i konstrukcji programistycznych. Nikt nie jest jednak jasnowidzem i autorzy języka świetnie zdawali sobie sprawę z tego, że nawet najdalej posunięta wyobraźnia nie jest w stanie antycypować wszelkich przyszłych potrzeb w zakresie modelowania dziedziny aplikacyjnej i dziedziny realizacyjnej. Wprowadzono więc do języka UML kilka mechanizmów umożliwiających jego rozszerzanie, stosownie do bieżących potrzeb konstruktorów modeli. W tej sekcji opiszemy dwa spośród tych rozszerzeń: stereotypy i ograniczenia. Stereotyp jest mechanizmem umożliwiającym klasyfikowanie elementów modeli. Na diagramie reprezentowany jest przez łańcuch znaków zamknięty w poziome znaki cytowania (guillemets), czyli « oraz » i dołączony do klasyfikowanego elementu. Formalnie dołączenie stereotypu do elementu modelu jest semantycznie równoważne utworzeniu nowej klasy w metamodelu UML (czyli w modelu reprezentującym konstrukcję języka UML). Umożliwia to definiowanie nowych rodzajów bloków konstrukcyjnych, stosownych dla modelu określonej domeny. Przykładowo na etapie analizy obiekty modelu dzieli się na trzy kategorie: obiektów encji {entity), obiektów brzegowych (boundary) i obiektów sterujących (control). Obiekty każdej z tych kategorii posiadają tę samą strukturę — atrybuty, operacje, skojarzenia — lecz służą różnym celom. „Czysty" język UML zna tylko jedną kategorię obiektu; by rozróżniać trzy wymienione kategorie, definiujemy stereotypy «enti ty», «boundary» i «control», których przykłady zastosowań widoczne są na rysunku 2.44. Stereotypy te opisujemy szczegółowo w rozdziale 5. „Analiza wymagań". Innym obszarem zastosowania stereotypów mogą być relacje między obiektami, czego przykład widzieliśmy na rysunkach 2.14 i 2.15 w postaci stereotypów «i ncl ude» i «extent».

Rysunek 2.44. Przykłady zastosowań stereotypów «enti ty», «boundary» i «control»

2.5. Literatura uzupełniająca

107

Ograniczenie to dodatkowa reguła narzucona na semantykę obiektu, czyniąca tę semantykę bardziej restrykcyjną. Restrykcje te mogą być tego rodzaju, że ich wyrażenie za pomocą klasycznej semantyki UML może okazać się wręcz niemożliwe. Powracając do systemu FRIEND: z określonym incydentem (reprezentowanym przez obiekt klasy Incident) może być skojarzonych wiele raportów (reprezentowanych przez obiekty klasy EmergencyReport), co czytelnie daje się wyrazić za pomocą skojarzenia „jeden na wiele". Dyspozytor (Di spatcher) powinien mieć przy tym szybki dostęp do żądanego dokumentu, a to zapewnić może sobie jedynie przez uporządkowanie poszczególnych raportów w sposób chronologiczny, czyli według daty sporządzenia. Język UML nie udostępnia środków umożliwiających określenie kryterium uporządkowania obiektów po stronie „wiele", lecz brak ten można wypełnić, opatrując skojarzenie odpowiednim ograniczeniem, jak na rysunku 2.45. Ograniczenie może być wyartykułowane bądź w formie potocznej (jak na rysunku), bądź też w specjalnym języku służącym do tego celu, na przykład w języku OCL (Object Constraint Language, [OMG, 2009]). Ograniczeniami i językiem OCL zajmiemy się dokładniej w rozdziale 9. „Projektowanie obiektów: specyfikowanie interfejsów".

Rysunek 2.45. Przykład ograniczenia narzuconego na skojarzenie

2.5. Literatura uzupełniająca Geneza notacji wykorzystywanej na potrzeby modelowania sięga czasów analizy strukturalnej opisanej przez T. De Marca [De Marco, 1978] i projektowania strukturalnego omówionego przez E. Yourdona i L. Constantina ([Yourdon i Constantine, 1975]) — obie te koncepcje, bazujące na dekompozycji funkcjonalnej, stały się punktem wyjścia dla diagramów przepływu danych, które opisał T. De Marco [De Marco, 1978], bardzo istotnych dla programistów zajmujących się konserwacją i rozwijaniem starszych aplikacji zaprojektowanych przy udziale modeli analizy strukturalnej. Język UML narodził się jako owoc doświadczeń wielu badaczy i praktyków — nazwiska niektórych z nich wymienialiśmy wcześniej w tym rozdziale. Wysiłek Boocha, Jacobsona i Rumbaugha doprowadził do powstania jednolitej, szeroko akceptowanej notacji; wcześniejsze ich prace, czyli G. Boocha [Booch, 1994], I. Jacobsona, M. Christersona, P. Jonssona i G. Overgaarda [Jacobson i in., 1992] oraz J. Rumbaugha, M. Błahy, W. Premerlaniego, F. Eddy'ego i W. Lorensena [Rumbaugh i in., 1991], zawierają głęboki wgląd w naturę analizy i projektowania zorientowanych obiektowo i wciąż stanowią bogate źródło wiedzy na temat podstawowych koncepcji obiektowo zorientowanego modelowania. Ponieważ język UML został zaprojektowany z myślą o szeroldej ldasie zastosowań, jest standardem dość skomplikowanym. W tym rozdziale skupiliśmy się jedynie na jego podstawowych elementach, wystarczających do rozpoczęcia lektury następnych rozdziałów. Czytelnikom zainteresowanym bardziej szczegółową wiedzą na temat UML polecamy następujące książki.

108

Rozdział 2. • Modelowanie w języku UML

• Książka M. Fiowera [Fowler, 2003] to krótkie wprowadzenie do UML ilustrowane wieloma przykładami. Idealne wprowadzenie dla początkującego czytelnika, nie znającego w ogóle języka UML. • Książka G. Boocha, J. Rumbaugha i I. Jacobsona [Booch i in., 2005] jest wszechstronną prezentacją UML w wykonaniu jego głównych autorów. Bardziej obszerna niż dzieło M. Fiowera [Fowler, 2003], zawierająca mniej przykładów i z tego względu adresowana raczej dla czytelników bardziej zaawansowanych w modelowaniu. • Praca OMG Unified Modeling Language Superstructure. Version 2.2 [OMG, 2009] jest oficjalną specyfikacją UML. Utrzymywana na bieżąco przez gremium rewizyjne, odpowiedzialne za wyjaśnianie niejednoznaczności, poprawianie błędów i eliminowanie niespójności na forum społeczności UML.

2.6. Ćwiczenia 2.1. Traktując bankomat jako system, wymień co najmniej trzech współdziałających z nim aktorów. 2.2. Czy system istniejący jedynie w zamyśle projektantów może być reprezentowany przez aktora? Uzasadnij odpowiedź. 2.3. Jaka jest różnica między scenariuszem a przypadkiem użycia? Kiedy używa się każdej z tych konstrukcji? 2.4. Narysuj diagram przypadków użycia automatu do sprzedaży biletów. System zawiera dwóch aktorów: podróżnego kupującego bilet określonego rodzaju i centralny komputer utrzymujący bazę danych o taryfach i cenniku. Przypadki użycia powinny obejmować zakup zwykłego biletu (BuyOneWayTicket), zakup karnetu tygodniowego lub miesięcznego (BuyWeeklyCard i BuyMonthlyCard) oraz aktualizację informacji taryfowej o cenach (UpdateTari ff). Nie zapomnij o uwzględnieniu przypadków wyjątkowych, takich jak przeterminowanie transakcji (Timeout — podróżny po zainicjowaniu transakcji wykazuje zbyt długą bezczynność), anulowanie transakcji (Transacti onAborted — podróżny zainicjował transakcję, lecz wycofał się z zamiaru kupna biletu, naciskając odpowiedni przycisk), brak nominałów do prawidłowego wydania reszty (Di s t r i butorOutOfChange) i wyczerpanie papieru w drukarce (Di s t r i butorOutOfPaper). 2.5. Opisz przepływa zdarzeń i określ wszystkie elementy przypadku użycia Update "-•Tari f f z ćwiczenia 2.4. Nie zapomnij o relacjach między obiektami. 2.6. Narysuj diagram klas reprezentujący książkę zdefiniowaną następująco: „Książka podzielona jest na kilka części, z których każda składa się z pewnej liczby rozdziałów. Rozdziały podzielone są na sekcje". Zwróć szczególną uwagę na klasy i skojarzenia między nimi. 2.7. Uzupełnij skojarzenia z ćwiczenia 2.6 o krotności. 2.8. Narysuj diagram zawierający obiekty reprezentujące pierwszą część tej książki „Zaczynamy". Upewnij się, że stworzony przez Ciebie diagram jest spójny z diagramem z ćwiczenia 2.6.

Bibliografia

109

2.9. Rozszerz diagram klas utworzony w ćwiczeniu 2.6 o atrybuty określające: • wydawcę, datę wydania i numer ISBN książki, • numer i tytuł każdej części, ® numer, tytuł i streszczenie każdego rozdziału, • numer i tytuł każdej sekcji. 2.10. Zauważ, że klasy diagramu z ćwiczenia 2.9, reprezentujące część, rozdział i sekcję, zawierają wspólne atrybuty — tytuł i numer. Dodaj do diagramu abstrakcyjną superklasę dla tych klas, definiującą wspomniane atrybuty. 2.11. Narysuj diagram klas reprezentujący relację między rodzicami a dziećmi. Pamiętaj, że człowiek może być zarówno dzieckiem, jak i jednym z rodziców. Zaetykietuj skojarzenia ich rolami i krotnościami. 2.12. Narysuj diagram klas reprezentujący bibliografię i odwołania do jej pozycji. Twój diagram powinien być jak najbardziej szczegółowy. Przetestuj go na podstawie dodatku C do tej książki. 2.13. Narysuj diagram sekwencji dla scenariusza warehouseOnFi re z tabeli 2.5, uwzględniając obiekty bob, al ice, john i FRIEND oraz ewentualne instancje innych klas. Ogranicz się do pierwszych pięciu przesyłanych komunikatów. 2.14. Narysuj diagram sekwencji dla przypadku użycia ReportEmergency z tabeli 2.1. Ogranicz się do pierwszych pięciu przesyłanych komunikatów. Upewnij się, że stworzony przez Ciebie diagram jest spójny z diagramem z ćwiczenia 2.13. 2.15. Przeanalizuj proces zamawiania pizzy przez telefon. Narysuj diagram aktywności reprezentujący każdy krok tego procesu, od momentu sięgnięcia po telefon do momentu rozpoczęcia konsumpcji dostarczonej pizzy. Dla uproszczenia pomiń możliwe sytuacje wyjątkowe. Uwzględnij również aktywności innych obiektów uwikłanych w proces. 2.16. Dodaj obsługę możliwych sytuacji wyjątkowych do diagramu utworzonego w ćwiczeniu 2.15 — uwzględnij co najmniej trzy (na przykład dostarczenie pizzy pod niewłaściwy adres, dostarczenie niewłaściwego rodzaju pizzy, dostawca powyjadał po drodze co smakowitsze anchovis i tym podobne). 2.17. Narysuj diagram dla aktywności opisanych w sekcji 1.4 poprzedniego rozdziału, zakładając ich ściśle sekwencyjne wykonanie. Narysuj następny diagram, przy założeniu wykonywania wspomnianych aktywności w sposób przyrostowy (jedna partia systemu jest analizowana, projektowana, implementowana i wyczerpująco testowana przed rozpoczęciem prac nad następną partią). Wreszcie, na trzecim diagramie załóż wykonywanie tychże aktywności w sposób równoległy.

Bibliografia [Booch, 1994]

G. Booch Object-Oriented Analysis and Design with Applications, wyd. drugie, Benjamin/Cummings, Redwood City, CA, 1994.

[Booch i in., 2005]

G. Booch, J. Rumbaugh, I. Jacobson The Unified Modeling Language User Guide, Addison-Wesley, Reading, MA, 2005.

110 [Coad i in., 1995]

Rozdział 2. • Modelowanie w języku UML P. Coad, D. North, M. Mayfield Object Models: Strategies, & Applications,

[Constantine i Lockwood, 2001]

Patterns,

Prentice Hall, Englewood Cliffs, NJ, 1995.

L. L. Constantine & L.A.D. Lockwood „Structure and style in use cases for user interface design," w: M. van Harmelen (red.) ObjectOriented User Interface Design, 2001.

[De Marco, 1978]

T. De Marco Structured Analysis and System Yourdon, New York, 1978.

[Douglass, 1999]

B. P. Douglass Doing Hard Time: Using Object Oriented Programming and Software Patterns in Real Time Applications, Addison-Wesley, Reading, MA, 1999.

[Fowler, 2003]

M. Fowler UML Distilled: A Brief Guide To The Standard Object Modeling Language, wyd. trzecie, Addison-Wesley, Reading, MA, 2003.

[Harel, 1987]

D. Harel Statecharts: A visual formalism for complex systems, „Science of Computer Programming", str. 231 - 274, 1987.

[Jacobson i in., 1992]

I. Jacobson, M. Christerson, P. Jonsson, G. Overgaard Object-Oriented Software Engineering — A Use Case Driven Approach, Addison-Wesley, Reading, MA, 1992.

[Martin i Odell, 1992]

J. Martin, J. J. Odell Object-Oriented Hall, Englewood Cliffs, NJ, 1992.

[Mellor i Shlaer, 1998]

S. Mellor, S. Shlaer Recursive Design Approach, Prentice Hall, Upper Saddle River, NJ, 1998.

[Miller, 1956]

G. A. Miller The magical number seven, plus or minus two: Some limits on our capacity for processing information, „Psychological Review", t. 63, str. 81 - 97, 1956.

[OMG, 2009]

Object M a n a g e m e n t G r o u p OMG Unified Modeling Superstructure. Version 2.2, http://www.omg.org.

[Popper, 1992]

K. Popper Objective Knowledge: An Evolutionary Approach, Clarendon, Oxford, 1992.

[Rumbaugh i in., 1991]

J. R u m b a u g h , M. Błaha, W . Premerlani, F. Eddy, W . Lorensen Object-Oriented Modeling and Design, Prentice Hall, Englewood Cliffs, NJ, 1991.

[Spivey, 1992]

J. M. Spivey The Z Notation, A Reference Manual, wyd. drugie, Prentice Hall International, Hertfordshire, U.K., 1992.

[Wirfs-Brock i in., 1990]

Specification,

Analysis and Design, Prentice

R. Wirfs-Brock, B. Wilkerson, L. Wiener Designing

Language

Object-Oriented

Software, Prentice Hall, Englewood Cliffs, NJ, 1990. [Yourdon i Constantine, 1975]

E. Y o u r d o n , L. C o n s t a n t i n e Structured Englewood Cliffs, NJ, 1975.

Design, Prentice Hall,

3.1.

Wstęp — katastrofa Ariane

114

3.2.

O projekcie ogólnie

115

3.3.

Koncepcje organizacyjne projektu

119

3.3.1. 3.3.2. 3.3.3. 3.3.4.

119 122 124 126

Organizacja projektów Role w realizacji projektu Zadania i produkty Harmonogramy

3.4.

Koncepcje komunikacyjne projektu 3.4.1. Komunikacja planowa 3.4.2. Komunikacja pozaplanowa 3.4.3. Mechanizmy komunikacyjne

128 128 135 138

3.5.

Aktywności organizacyjne 3.5.1. Dołączanie do zespołu 3.5.2. Dołączanie do infrastruktury komunikacyjnej 3.5.3. Udział w zebraniach zespołu 3.5.4. Organizacja przeglądów

146 146 146 147 149

3.6.

Literatura uzupełniająca

151

3.7.

Ćwiczenia

152

Bibliografia

153

Organizacja projektu i komunikacja Dwa podzespoły rakiety, dostarczone przez dwóch różnych podwykonawców, połączone były parą przewodów. Kontrola przedstartowa wykazała, że połączenie jest nieprawidłowe —przewody są skrzyżowane i należy zamienić ich kolejność po jednej ze stron. Śledztwo prowadzone w związku ze spłonięciem rakiety na stanowisku startowym wykazało, że obaj podwykonawcy sumiennie zastosowali się do zalecenia — każdy zamienił końcówki po swojej stronie.

Inżynieria oprogramowania jest działalnością zespołową, obejmującą specjalistów z różnych dziedzin — ekspertów z dziedziny aplikacyjnej, analityków, projektantów, programistów, menedżerów, dokumentalistów technicznych, grafików i użytkowników. Żaden z nich nie jest w stanie ogarnąć w pojedynkę wszystkich aspektów tworzonego systemu, wszyscy są więc nawzajem zależni od siebie w swej pracy. Co więcej, wszelkie zmiany w zakresie dziedziny aplikacyjnej czy dziedziny realizacyjnej wymagają także zmian w rozumieniu systemu przez poszczególnych uczestników. W tych warunkach krytycznym czynnikiem uzależniającym powodzenie całego przedsięwzięcia jest skuteczna wymiana informacji — wymiana dokonywana w sposób dokładny i terminowy. Komunikowanie się uczestników może przyjmować różne formy, zależnie od tego, jakiego rodzaju aktywności jest podporządkowane. Uczestnicy raportują status swej pracy w trakcie regularnych spotkań i utrwalają tę informację w protokołach. Status projektu komunikowany jest regularnie klientowi w ramach przeglądu. Wymagania i zmiany komunikowane są przy wsparciu modeli i towarzyszącej im dokumentacji. Sytuacje kryzysowe i nieporozumienia rozstrzygane są w ramach spontanicznej komunikacji telefonicznej, e-mailowej lub bezpośredniej. W miarę jak projekt staje się coraz obszerniejszy, jego uczestnicy poświęcać muszą więcej czasu na wzajemne komunikowanie się, co może odbywać się kosztem zasadniczych, technicznych aktywności. By poradzić sobie z tymi wyzwaniami, konieczne jest odpowiednie zorganizowanie uczestników w zespoły oraz zapewnienie efektywnego obiegu informacji za pomocą kanałów formalnych i mniej formalnych. Rozpoczniemy ten rozdział od opisania podstawowych koncepcji związanych z organizacją projektu — opiszemy zadania, produkty w ogólności i produkty docelowe. Następnie omówimy mechanizmy komunikacyjne, jakie mają do dyspozycji uczestnicy projektu. Na koniec przejdziemy do aktywności związanych z organizacją projektu i komunikowaniem się jego uczestników. W tym rozdziale prezentujemy punkt widzenia uczestnika projektu (na przykład programisty), który powinien jedynie rozumieć infrastrukturę organizacyjną i komunikacyjną projektu; budowanie tej struktury jest zadaniem menedżerów i będzie przedmiotem rozważań rozdziału 14. „Zarządzanie projektem".

114

Rozdziat 3. • Organizacja projektu i komunikacja

3.1. Wstęp — katastrofa Ariane Realizując system informatyczny, programiści koncentrują się na zgodności jego zachowania ze specyfikacją. Gdy współdziałają z innymi uczestnikami projektu, muszą komunikować się z nimi w sposób precyzyjny i efektywny. I jeśli nawet komunikowanie to nie wydaje się aktywnością kreatywną ani wyzywającą, jest dla powodzenia przedsięwzięcia nie mniej ważne niż dobry projekt czy efektywna implementacja — o czym świadczyć może choćby poniższy przykład [Lions, 1996]. Ariane 501 4 czerwca 1996 roku rakieta Ariane 501 — pierwszy prototyp serii Ariane 5 — eksplodowała pół minuty po starcie. Oprogramowanie głównego komputera nawigacyjnego zakończyło swą pracę awaryjnie z powodu wystąpienia nadmiaru operacji arytmetycznej; główny komputer oddelegował kontrolę do komputera zapasowego i wyłączył się. Niestety, komputer zapasowy wyłączył się ułamek sekundy wcześniej z identycznej przyczyny. W efekcie rakieta, gwałtownie pozbawiona systemu nawigacyjnego, dokonała ostrego skrętu, mającego na celu skorygowanie zboczenia z kursu, które w rzeczywistości nie wystąpiło. Wyjaśnianie łańcucha przyczynowo-skutkowego, prowadzącego od błędu w oprogramowaniu do fatalnego końca rakiety trwało niecałe dwa miesiące. System nawigacyjny serii Ariane 5 budowany był z wielu komponentów serii Ariane 4, które w tejże serii spisywały się bez zarzutu. System nawigacyjny rakiety odpowiedzialny jest za obliczanie korekcji odchyleń kursu od założonej trajektorii na podstawie informacji pochodzącej z systemu referencyjnego, mającego naturę bezwładnościową, złożonego głównie z żyroskopów i mierników przyspieszenia. System ten stanowi jedyny punkt odniesienia dla obliczania aktualnej pozycji rakiety — obliczanie to odbywa się więc w całkowitym oderwaniu od świata zewnętrznego. Niezbędne jest wobec tego odpowiednie zainicjowanie owego systemu referencyjnego, przez skorelowanie jego stanu z tymże światem zewnętrznym oraz z aktualną pozycją rakiety. Inicjowanie to odbywa się bezpośrednio przed startem i trwa w przybliżeniu 45 minut; po starcie rakiety dane z systemu przekazywane są do komputerowego systemu nawigacyjnego, który po uwzględnieniu dodatkowych czynników (głównie ruchu obrotowego Ziemi) oblicza bieżącą pozycję rakiety i konfrontuje ją z pozycją oczekiwaną w danej chwili; obliczenia te trwają ok. 50 sekund i w takim też odstępie odbywa się wspomniane kontrolowanie położenia rakiety. Odliczanie do startu może być wstrzymane na żądanie; jego wznowienie nie wymaga powtórzenia owych 45-minutowych obliczeń inicjacyjnych. Po udanym starcie komputer pokładowy jeszcze przez 40 sekund generuje dane inicjacyjne, które jednak są wtedy całkowicie bezużyteczne. System komputerowy serii Ariane 5 różnił się od tego z serii Ariane 4 między innymi zdublowaniem platformy sprzętowej — dublowany był sam system referencyjny, komputery wykonujące niezbędne obliczenia oraz urządzenia korygujące położenie rakiety; w przypadki awarii któregoś z podzespołów jego funkcje automatycznie przejmowała replika. System inicjacyjny, zaprojektowany wyłącznie do obliczeń naziemnych, wykorzystywał 16-bitowe słowo do reprezentowania poziomej prędkości rakiety — co wydawało się w zupełności wystarczające nawet przy uwzględnieniu czynników zaburzających, głównie wiatru i ruchu wirowego Ziemi. W 30 sekund po starcie rakiety błąd nadmiaru arytmetycznego unieruchomił jednak oba systemy komputerowe, choć ich sprzęt sprawował się nienagannie.

3.2. O projekcie ogólnie

115

Przyczyna. Oprogramowanie komputerów nie zostało wystarczająco przetestowane: mimo iż doświadczyło tysięcy różnych testów, żaden z nich nie był przeprowadzony w warunkach symulujących lot po rzeczywistej orbicie. Równie wyczerpująco przetestowane zostało działanie systemu referencyjnego, jego twórcy nie uwzględnili jednak możliwości kompletnego załamania się pokładowego systemu komputerowego — ten przecież był zdublowany, więc w oczekiwaniu wysoce niezawodny. Pierwotną przyczyną całej katastrofy okazał się zatem brak komunikacji między programistami a zespołem odpowiedzialnym za system referencyjny.

Mimo iż w tym rozdziale dyskutujemy kwestie organizacyjne i komunikacyjne w odniesieniu do projektów programistycznych, prezentowane koncepcje nie są specyficzne dla inżynierii oprogramowania; jednakże w przypadku projektu programistycznego sprawna i adekwatna komunikacja nabiera szczególnego znaczenia, bowiem jej niedociągnięcia skutkować mogą poważnymi, często nawet fatalnymi konsekwencjami dla projektu oraz jakości tworzonego systemu.

3.2. O projekcie ogólnie Przedstawiona w poprzednim rozdziale notacja UML pozwala uczestnikom projektu na tworzenie odpowiednich modeli systemu jako podstawowego środka komunikowania skomplikowanych koncepcji. Modele te nie są jednak jedynym czynnikiem komunikacyjnym, bowiem dla uczestników projektu interesujących jest jeszcze wiele innych kwestii. Oto niektóre z nich. • Kto jest odpowiedzialny za poszczególne części systemu? o Jakie są terminy dostarczenia poszczególnych części systemu? • Z kim należy się kontaktować w przypadku problemów z konkretną wersją konkretnego komponentu? • Jak należy dokumentować napotykane problemy? • Jakie są kryteria jakościowej oceny systemu? • W jakiej formie nowe wymagania powinny być komunikowane programistom? • Kto powinien być informowany o nowych wymaganiach? • Kto jest odpowiedzialny za kontakty z klientem? Mimo iż powyższe kwestie bywają łatwe do rozstrzygnięcia przy popołudniowej kawie, w przypadku realizacji złożonego projektu takie techniki komunikacji ad hoc okazują się znacznie mniej skuteczne. Z punktu widzenia programisty, projekt informatyczny postrzegany jest w kategoriach czterech głównych komponentów, którymi są (patrz rysunek 3.1): • Produkt — pod tym pojęciem rozumiemy jakikolwiek wytwór działań związanych z projektem: fragment kodu źródłowego, model, dokument i tym podobne. Produkt wytworzony dla klienta określa się mianem produktu docelowego. • Harmonogram — określa szczegółowo terminy wykonania poszczególnych prac związanych z projektem.

Rozdziat 3. • Organizacja projektu i komunikacja

116

Rysunek 3.1. Model projektu z perspektywy programisty

• Uczestnik — to każda osoba zaangażowana w realizację projektu; uczestnicy nazywani są także członkami projektu. • Zadanie — realizuje je uczestnik projektu w celu wytworzenia produktu. Projekt może być zdefiniowany w sposób formalny lub nieformalny. Pisemny kontrakt między wykonawcą a klientem, uzgadniający dostarczenie systemu informatycznego w terminie trzech miesięcy za cenę miliona dolarów, to nic innego jak formalne określenie projektu; projektem może być jednak również nieformalna obietnica zainstalowania nowego oprogramowania na komputerze kolegi, w przyszłym tygodniu. Projekty bywają zróżnicowane pod względem typu i rozmiaru. Niekiedy typ projektu definiuje się na podstawie natury jego produktu docelowego — i tak na przykład stworzenie systemu informatycznego dla celów zarządzania księgowością przedsiębiorstwa określa się mianem projektu programistycznego, natomiast budowa promu kosmicznego zaliczana jest do projektów systemowych. Projekty mogą mieć rozmiary banalne albo gigantyczne: budowa promu kosmicznego, wraz z niezbędną infrastrukturą informatyczną, trwająca 10 - 15 lat i wyceniona na 10 miliardów dolarów, zdecydowanie zalicza się do tych drugich, w odróżnieniu od np. przemeblowywania mieszkania, reprezentującego zdecydowanie pierwszą kategorię. Gdy spojrzeć na realizację projektu pod kątem dynamicznym, wyróżnić można kilka jej faz, przedstawionych symbolicznie na rysunku 3.2.

Rysunek 3.2. Stany realizacji projektu

W fazę definiowania projektu zaangażowani są zwykle: menedżer, przedstawiciel klienta, główny architekt oprogramowania i główny analityk. W fazie tej następuje skupienie się na dwóch głównych problemach: wstępnym rozpoznaniu architektury oprogramowania, ze szcze-

3.2. O projekcie ogólnie

117

gólnym uwzględnieniem dekompozycji' systemu na podsystemy, oraz wstępnym sformułowaniu projektu w kategoriach zadań do wykonania, harmonogramu i niezbędnych zasobów. Znajduje to odzwierciedlenie w trzech dokumentach: deklaracji problemu, zarysie architektury oprogramowania i wstępnym planie zarządzania projektem. W fazie startowej menedżer projektu buduje jego infrastrukturę, zatrudnia uczestników, organizuje ich w zespoły, definiuje najważniejsze „kamienie milowe" i doprowadza do rozpoczęcia realizacji projektu. W tej i w poprzedniej fazie większość decyzji podejmowana jest przez menedżera. W fazie ustalonej do działania przystępują programiści i projektanci. Ich prace nadzorowane są przez kierowników zespołów — kierownicy ci odpowiedzialni są za śledzenie postępu prac w swoich zespołach i identyfikowanie problemów, jakie się w związku z tymi pracami pojawiają. Są oni jednocześnie odpowiedzialni za raportowanie sytuacji menedżerowi projektu, który na tej podstawie ocenia status realizacji projektu jako całości, odpowiadają też za zmianę przydziału zadań i realokację zasobów w związku ze zmianami w harmonogramie. Menedżer projektu jest natomiast odpowiedzialny za współdziałanie z klientem i formalizację porozumień, jak również za negocjacje w kwestii niezbędnych zasobów i harmonogramu. W ostatniej fazie — fazie terminalnej — kompletny system dostarczany jest klientowi, jednocześnie kompletowana jest historia projektu. W fazie tej kończy się zaangażowanie większości programistów w projekt, niektórzy z nich, m.in. kierownicy zespołów, przy udziale dokumentalistów, zajmują się instalowaniem systemu i zebraniem historii projektu wykorzystywanej w przyszłości. Komunikacja w ramach projektu związania jest ze zdarzeniami dwóch kategorii: przewidywalnymi i nieprzewidywalnymi. Do kategorii pierwszej zaliczyć można między innymi: • inspekcję problemu, podczas której programiści uzyskują niezbędne dla siebie informacje z zakresu dziedziny aplikacyjnej — na podstawie deklaracji problemu oraz interakcji z klientem i przyszłymi użytkownikami systemu; • zebrania statusowe, w ramach których zespoły analizują postęp swoich prac; • przeglądy partnerskie, obejmujące identyfikowanie usterek we wstępnych wersjach produktów oraz poszukiwanie sposobów niwelowania tych usterek; • przeglądy kliencki i projektowy, w czasie których klient i inni uczestnicy dokonują oceny jakości produktów, szczególnie produktów docelowych; o emisje, polegające na przekazaniu klientowi i użytkownikom kolejnych wersji produktów docelowych wraz z niezbędną dokumentacją. Do zdarzeń nieprzewidywalnych należą natomiast przede wszystkim: • żądanie wyjaśnień, czyli żądanie przez uczestnika dodatkowej informacji dotyczącej systemu, dziedziny aplikacyjnej lub projektu; • żądanie zmian, wskutek problemów napotykanych w związku z tworzonym systemem bądź propozycją nowych jego cech; • rozwiązywanie problemów, które mogą mieć postać konfliktów między uczestnikami bądź charakter czysto techniczny (bądź plasować się na pograniczu jednego i drugiego).

118

Rozdziat 3. • Organizacja projektu i komunikacja

Komunikacja związana ze zdarzeniami przewidywalnymi, zwana krótko komunikacją planową, pomaga w rozpowszechnianiu informacji, którą zainteresowani uczestnicy spodziewają się uzyskiwać. Komunikacja pozaplanowa, czyli związana ze zdarzeniami nieprzewidywalnymi, pomaga w rozwiązywaniu sytuacji kryzysowych. Niezależnie od rodzaju, zarówno potrzeby w zakresie komunikacji, jak i sama komunikacja, powinny mieć charakter precyzyjny i efektywny. Gdy programiści dołączają do projektu w jego fazie startowej, istnieje już deklaracja problemu, menedżer projektu sporządził wstępny plan rozwiązywania głównych projektów, ustanowił organizację projektu, zdefiniował przewidywalne zdarzenia komunikacyjne i dostarczył infrastrukturę niezbędną do komunikacji zarówno planowej, jak i pozaplanowej. Większość wysiłku programistów koncentruje się wówczas na zrozumieniu związanych z nią dokumentów i przystosowaniu się do istniejących struktur organizacyjnych oraz komunikacyjnych. Wiążą się z tym następujące aktywności: » Udział w zebraniach początkujących. W ich ramach programiści zapoznają się z potrzebami klienta oraz zakresem systemu, nad którym będą pracować. Prowadzi to do wysokopoziomowego zrozumienia systemu, co stanowi podstawę dla wszystkich innych aktywności. o Dołączenie do zespołu. Menedżer projektu, dokonując podziału projektu między poszczególne zespoły, ustala jednocześnie skład osobowy poszczególnych zespołów; kieruje się przy tym kwalifikacjami i zainteresowaniami poszczególnych programistów. Może też zdecydować o dodatkowym przeszkoleniu programistów nieposiadających wystarczających kwalifikacji w pewnym zakresie. ® Udział w sesjach treningowych. Ich celem jest zdobycie przez programistów dodatkowych umiejętności, niezbędnych dla niektórych zadań. •

Dołączenie do infrastruktury komunikacyjnej. Na tę infrastrukturę składają się rozmaite mechanizmy w rodzaju narzędzi do komunikacji grupowej, książek adresowych, książek telefonicznych, usług e-mail i wyposażenia do telekonferencji.

• Rozbudowywanie infrastruktury komunikacyjnej. W miarę potrzeb początkowa infrastruktura może być poszerzana o grupy dyskusyjne czy portale tematyczne związane z projektem. •

Udział w pierwszym zebraniu statusowym. W ramach tego zebrania programiści zapoznawani są z procedurami zebrań statusowych oraz procedurami rejestrowania informacji o statusie prac i dostarczania tych informacji innym uczestnikom projektu.

• Zrozumienie harmonogramu przeglądów. Zadaniem harmonogramu przeglądów jest określenie terminów „kamieni milowych", w związku z którymi rezultaty realizacji projektu komunikowane będą menedżerowi i klientowi w formie przeglądu. Celem takich przeglądów jest zarówno informowanie członków innych zespołów o statusie prac nad wybranym obszarem projektu i o ewentualnych problemach dotyczących tego obszaru, jak i prezentowanie klientowi bieżącego statusu projektu oraz uwzględnianie jego uwag w tej kwestii. W kolejnych sekcjach omówimy szczegółowo przedstawione koncepcje i aktywności. W sekcji 3.3 opiszemy zespołową organizację projektu, w sekcji 3.4 dyskutować będziemy

3.3. Koncepcje organizacyjne projektu

119

koncepcje powiązane z komunikacją w ramach projektu; w sekcji 3.5 przedstawimy szczegółowo „startowe" aktywności typowe dla programisty pracującego w zespole. Czytelnikom zainteresowanym dalszymi informacjami dotyczącymi opisywanych zagadnień proponujemy w sekcji 3.6 wybraną literaturę uzupełniającą. W rozdziale tym skupimy się na spojrzeniu na projekt z perspektywy programisty dołączającego do zespołu, nie będziemy zatem poruszać kwestii związanych z budowaniem organizacji projektu i jego infrastruktury komunikacyjnej ani też z zarządzaniem tymi podmiotami: dyskusję tę odkładamy bowiem do rozdziałów 12. „Zarządzanie racjonalizacją", 13. „Zarządzanie konfiguracją" i 14. „Zarządzanie projektem", poświęconych identyfikowaniu, negocjowaniu, rozwiązywaniu i dokumentowaniu problemów, zarządzaniu wersjami, konfiguracją i emisjami oraz spojrzeniu na organizację i komunikację z perspektywy menedżera projektu.

3.3. Koncepcje organizacyjne projektu W tej sekcji przedstawimy definicje takich pojęć jak: • Organizacja projektu (patrz sekcja 3.3.1), • Rola (patrz sekcja 3.3.2), • Zadanie i produkt (patrz sekcja 3.3.3), ® Harmonogram (patrz sekcja 3.3.4).

3.3.1. Organizacja projektów Istotną częścią organizacji projektu jest zdefiniowanie relacji między poszczególnymi uczestnikami, a także ich relacji do poszczególnych zadań, harmonogramu i produktów. W ramach organizacji zespołowej (patrz rysunek 3.3) uczestnicy pogrupowani są w małe zespoły, z których każdy realizuje określone zadanie lub aktywność. Będziemy odróżniać zespoły od innych rodzajów zbiorowisk ludzkich, szczególnie od grup i komisji. Grupa ludzi przypisana jest, co prawda, do realizacji wspólnego zadania, lecz każdy jej członek działa indywidualnie, bez potrzeby komunikowania się z pozostałymi członkami. Zadaniem komisji jest natomiast przegląd i krytyczna ocena rozwiązań oraz formułowanie nowych propozycji.

Rysunek 3.3. Zespołowa organizacja projektu: jednostkę organizacji stanowi zespół, składający się z programistów lub innych zespołów

Na rysunku 3.4 przedstawiono diagram odzwierciedlający organizację prostego projektu, w ramach którego zespołowi zarządzającemu podporządkowane są trzy zespoły programistów.

120

Rozdziat 3. • Organizacja projektu i komunikacja

Rysunek 3.4. Przykład prostej organizacji projektu: raportowanie, decydowanie i komunikowanie realizowane są przez skojarzenie agregacyjne z poziomem organizacyjnym

Wśród różnych interakcji występujących między uczestnikami projektu do trzech najważniejszych należą: • raportowanie — ma na celu przedstawianie informacji o bieżącym statusie projektu; przykładowo programista odpowiedzialny za API modułu raportuje innym programistom szczegóły tego API, natomiast kierownik projektu może informować menedżera projektu o tym, że przypisane zespołowi zadanie nie zostało jeszcze wykonane. • decydowanie — oznacza ogłaszanie podejmowanych decyzji; przykładowo kierownik zespołu decyduje o opublikowaniu API modułu przez programistę odpowiedzialnego za ten moduł, menedżer projektu decyduje o zmianach w harmonogramie itp.; celem decyzji może być także arbitralne rozstrzygnięcie konfliktu lub wątpliwości. • komunikowanie — oznacza wymianę wszelkich innych informacji związanych ze statusem lub podejmowanymi decyzjami; komunikacja może przyjmować różne formy, na przykład wymiany wymagań czy modeli, formułowania propozycji rozwiązań czy dostarczania argumentów do dyskusji; zaproszenie na obiad też jest przejawem komunikowania się. Organizację projektu nazywamy hierarchiczną, jeśli zarówno informacja o statusie, jak i procesy decyzyjne mają charakter jednokierunkowy — decyzje podejmowane są w korzeniu drzewa organizacyjnego i przesyłane w głąb niego, w kierunku liści, natomiast informacja o statusie generowana jest w węzłach wewnętrznych lub liściach i przesyłana w kierunku korzenia. Charakter przebiegu informacji o statusie i informacji decyzyjnych nazywany bywa często strukturą raportowania organizacji. Na rysunku 3.5 widzimy przykład hierarchicznej organizacji zespołowej. W organizacjach typowo hierarchicznych, na przykład w wojsku,, struktura raportowania jest jedyną oficjalną drogą przepływu informacji. W złożonych projektach programistycznych taka organizacja ma swoje wady i może stwarzać problemy: przykładowo wiele decyzji o charakterze technicznym powinno być podejmowane lokalnie, w ramach zespołu, lecz w zależności od informacji pochodzącej od programistów z innych zespołów. Gdyby informację tę uzyskiwać wyłącznie w ramach organizacyjnej struktury raportowania, proces decyzyjny mógłby zostać znacząco spowolniony. Co gorsza, zaistniałoby duże ryzyko zniekształcenia informacji, ze względu na jej złożoność i rozmiar.

3.3. Koncepcje organizacyjne projektu

121

Rysunek 3.5. Przykład hierarchicznej struktury raportowania: informacja o statusie projektu raportowana jest menedżerowi projektu, który następnie przekazuje swe decyzje programistom za pośrednictwem kierowników zespołów; menedżer wraz w kierownikami zespołów tworzą zespół zarządzający

Konieczne jest więc ustanowienie dodatkowej struktury umożliwiającej bezpośrednią wymianę informacji między uczestnikami projektu, z pominięciem hierarchicznej struktury raportowania. Jedną z nich jest struktura oparta na łącznikach: funkcję tę pełni wybrany programista, którego zadaniem jest przekazywanie informacji w obie strony. Na rysunku 3.6 widzimy przykład organizacji, w ramach której wymiana informacji w oparciu o łączników uzupełnia oficjalną strukturę raportowania. Przykładowo zespół dokumentacyjny posiada w swym składzie łącznika kontaktującego się z zespołem interfejsu użytkownika, którego zadaniem jest informowanie na bieżąco o zmianach, jakie dokonywane są w kwestii dialogu użytkownika z systemem. Zespoły, które nie zajmują się bezpośrednio konkretnymi podsystemami, lecz wykonują zadania krzyżujące się z podziałem opartym na podsystemach, nazywane są zespołami międzyfunkcyjnymi. Zespoły takie zajmują się m.in. opracowywaniem architektury systemu, testowaniem i dokumentowaniem.

Rysunek 3.6. Przykład struktury łącznikowej. Zespół składa się z pięciorga programistów: Alice jest kierownikiem, a więc również łącznikiem z zespołem zarządzającym; John jako inżynier API jest łącznikiem z zespołem architektonicznym; Mary jest łącznikiem z zespołem dokumentacyjnym; Chris i Sam, jako implementatorzy, kontaktują się z innymi zespołami jedynie w sposób nieformalny

122

Rozdziat 3. • Organizacja projektu i komunikacja

Strukturę komunikacyjną i organizację bazującą na łącznikach nazywamy (odpowiednio) strukturą łącznikową i organizacją łącznikową. W strukturze tej odpowiedzialność kierowników zespołów rozszerzona zostaje o nową funkcję: oprócz reprezentowania zespołu wobec menedżera projektu, kierownik zespołu pośredniczy także w przekazywaniu informacji między programistami z różnych zespołów. By mógł pełnić tę funkcję efektywnie, konieczne jest zapewnienie sprawnych ścieżek komunikacyjnych. Programiści z różnych zespołów mogą także komunikować się bezpośrednio, z pominięciem swoich kierowników. Ten rodzaj komunikacji nazywamy komunikacją partnerską.

3.3.2. Role w realizacji projektu Pod pojęciem roli rozumiemy zbiór zadań o charakterze technicznym łub menedżerskim, przydzielonych uczestnikowi lub zespołowi. Przykładowo rola testera w zespole zajmującym się konkretnym podsystemem obejmuje opracowywanie zestawów testowych dla powstającego podsystemu, wykonywanie testowania w oparciu o te zestawy oraz raportowanie innym programistom wykrytych różnic i usterek. W typowym projekcie programistycznym rola, w jakiej obsadzony zostaje zespół lub uczestnik, może być rolą jednego z czterech typów: zarządczą, programistyczną, międzyfunkcyjną lub konsultacyjną (patrz rysunek 3.7).

Rysunek 3.7. Typy ról spotykanych w realizacji projektów programistycznych

W roli o charakterze zarządczym obsadzani są między innymi menedżer projektu i kierownicy zespołów. Ten typ ról związany jest z organizacją projektu oraz jego realizacją w ramach istniejących warunków i uzgodnionych ograniczeń. Rolami tego typu zajmiemy się dokładniej w rozdziale 14. „Zarządzanie projektem".

3.3. Koncepcje organizacyjne projektu

123

Role o charakterze programistycznym to role między innymi analityka, architekta systemu, projektanta obiektów, implementatora i testera, czyli role obejmujące zadania ukierunkowane na specyfikowanie, projektowanie, konstruowanie i testowanie podsystemów. Ich omówieniem zajmiemy się w rozdziałach od 5. do 11.; w tabeli 3.1 wymieniamy natomiast kilka przykładów ról tego typu. Tabela 3.1. Przykłady ról programistycznych

Rola

Zakres odpowiedzialności

Architekt systemu

Zadaniem architekta systemu jest zapewnienie spójności rozwiązań z założeniami projektowymi i stylem interfejsów. Spójność z założeniami projektu realizowana jest poprzez sformułowanie wymogów dotyczących zarządzania konfiguracją oraz kryteriów integrowania podsystemów. Rola architekta jest więc rolą typowo integracyjną, opierającą się na informacjach pochodzących od zespołów zajmujących się poszczególnymi podsystemami.

Projektant obiektów

Projektant obiektów odpowiedzialny jest za zdefiniowanie interfejsu podsystemu. Interfejs ten powinien odzwierciedlać zarówno funkcjonalność założoną w projekcie podsystemu, jak również potrzeby komunikujących się z nim innych podsystemów. Gdy w podsystemie dokonywane są zmiany funkcjonalne, podyktowane potrzebami innych podsystemów, projektant obiektów odpowiedzialny jest za przekazanie związanych z tym informacji programistom z zespołu zajmującego się tym podsystemem.

Implementator

Rola implementatora sprowadza się do stworzenia kodu źródłowego klas związanych z danym podsystemem.

Tester

Zadaniem testera jest weryfikowanie zgodności rzeczywistego zachowania podsystemu z założeniami zdefiniowanymi przez projektanta obiektów. Często w projekcie informatycznym tworzy się odrębny zespół zajmujący się jedynie testowaniem. Rozdzielenie ról implementatora i testera skutkuje zwykle bardziej owocnym testowaniem.

Istotą ról iniędzyfunkcyjnych jest koordynowanie działań poszczególnych zespołów. Programiści pełniący role tego rodzaju odpowiedzialni są za wymianę informacji z innymi zespołami i negocjowanie z nimi szczegółów interfejsów. Programista obsadzony w roli międzyfunkcyjnej nazywany jest powszechnie łącznikiem, jako że odpowiada za przekazywanie informacji między zespołami. W niektórych przypadkach łącznik (będący na przykład inżynierem API) reprezentuje zespół w kontaktach z innymi zespołami. Wyróżniamy cztery podstawowe typy łączników: • Inżynier API odpowiedzialny jest za interfejs API konkretnego podsystemu. Interfejs ten reprezentuje funkcjonalność podsystemu wobec innych podsystemów, powinien zatem odzwierciedlać ich potrzeby. Wynikające stąd zmiany powinny być przekazywane programistom podsystemu, za co również odpowiedzialny jest inżynier API. • Redaktor dokumentacji odpowiada za integrowanie dokumentów sporządzanych przez zespól. Z perspektywy innych zespołów, zależnych od podsystemu tworzonego

124

Rozdziat 3. • Organizacja projektu i komunikacja

przez jego macierzysty zespół, redaktor postrzegany jest jako dostawca usługi. Zajmuje się także dokumentowaniem informacji wewnątrzzespołowej, powstającej między innymi w ramach spotkań i narad zespołu. • Menedżer konfiguracji to rola związana z zarządzaniem różnymi wersjami dokumentów, modeli i fragmentów kodu, produkowanych w ramach zespołu. Gdy zasady owego zarządzania nie są skomplikowane, rolę menedżera konfiguracji może pełnić kierownik zespołu. • Tester odpowiada za zgodność rzeczywistego funkcjonowania podsystemu ze specyfikacją jego projektanta. Często testowanie powierza się odrębnemu zespołowi, który nie zajmuje się programowaniem — takie rozdzielenie ról implementatora i testera skutkuje zwykle bardziej efektywnym testowaniem. Konsultanci zapewniają tymczasowe wsparcie w obszarach, względem których uczestnicy projektu posiadają niewystarczającą wiedzę, kwalifikacje lub doświadczenie. W większości przypadków klient i użytkownicy pełnią rolę konsultantów z zakresu dziedziny aplikacyjnej. Konsultant techniczny może dostarczać zaawansowaną wiedzę na temat nowych metod i technologii, zaś konsultanci nietechniczni mogą być pomocni w rozwiązywaniu problemów natury prawnej lub marketingowej. Wśród ról tego typu najczęściej spotyka się następujące: • Klient — jest odpowiedzialny za sformułowanie wymagań i scenariuszy. Pod pojęciem „wymagań" rozumiemy tu wymagania funkcyjne i pozafunkcyjne oraz ograniczenia. Powodzenie projektu wymaga zazwyczaj intensywnych interakcji między klientem a programistami. • Użytkownik — to osoba, która korzystać będzie z tworzonego systemu. Niekiedy realizacja projektu odbywa się w oderwaniu od użytkowników, często też potencjalni użytkownicy tworzonego systemu nie znani są a priori — wówczas użytkownik reprezentowany jest przez klienta, czy nawet przez wyznaczonego programistę. • Specjalista z zakresu dziedziny aplikacyjnej — zwany także „specjalistą od zastosowań" odpowiedzialny jest za dostarczenie wiedzy dotyczącej szczegółów wybranej kategorii funkcjonalności systemu. Podczas gdy klient dysponuje ogólną wiedzą na temat wymaganej funkcjonalności, specjalista od zastosowań dostarcza wiedzę związaną z konkretnym problemem. • Specjalista z zakresu dziedziny realizacyjnej — to dostawca wiedzy na temat dostępnych rozwiązań w zakresie implementacji systemu. Wiedza ta obejmować może szczegóły algorytmów, metod programowania, procesów, technologii implementacyjnych lub środowisk i narzędzi programistycznych.

3.3.3. Zadania i produkty Zadaniem nazywamy dobrze zdefiniowany przydział pracy, wynikający z przydzielonej roli. Grupy powiązanych ze sobą zadań nazywane są aktywnościami. Menedżer projektu lub kierownik zespołu obsadza uczestnika w nowej roli, po czym kontroluje stan wykonywania wynikających z tej roli zadań. Produkt jest uchwytnym efektem wykonania zadania — modelem, diagramem klas, fragmentem kodu źródłowego, dokumentem lub jego częścią albo czymś

125

3.3. Koncepcje organizacyjne projektu

p o d o b n y m . P r o d u k t y s t a n o w i ą rezultaty w y k o n y w a n i a o k r e ś l o n y c h z a d a ń , są p o d s t a w ą f o r m u ł o w a n i a h a r m o n o g r a m ó w i w a r u n k u j ą w y k o n y w a n i e i n n y c h z a d a ń . P r z y k ł a d o w o aktywn o ś ć p l a n o w a n i a t e s t ó w dla p o d s y s t e m u z a r z ą d z a n i a b a z ą d a n y c h s k u t k u j e p r o d u k t e m w p o staci zestawu p r z y p a d k ó w testowych i ich o c z e k i w a n y c h rezultatów, k t ó r e s t a n o w i ą m a t e r i a ł n i e z b ę d n y dla i n n e j a k t y w n o ś c i — t e s t o w a n i a d a n e g o p o d s y s t e m u ( p a t r z r y s u n e k 3.8).

Rysunek 3.8. Produkty podsystemu zarządzania bazą danych. Skojarzenia reprezentują zależności między produktami P r o d u k t p r z e z n a c z o n y dla klienta nazywa się p r o d u k t e m d o c e l o w y m . Działający s y s t e m w r a z z n i e z b ę d n ą d o k u m e n t a c j ą s k ł a d a się zwykle z c a ł e g o z b i o r u p r o d u k t ó w d o c e l o w y c h . P r o d u k t n i e w i d o c z n y dla klienta, p r z e z n a c z o n y n a w e w n ę t r z n y u ż y t e k s y s t e m u , n a z y w a m y p r o d u k t e m w e w n ę t r z n y m . P r z y k ł a d y p r o d u k t ó w w e w n ę t r z n y c h p r e z e n t u j e m y w tabeli 3.2. Tabela 3.2. Opis produktów wewnętrznych przedstawionych na rysunku 3.8 Produkt

Typ

Opis

Obiekty trwałe

Model klas

Ten model klas opisuje wyczerpująco obiekty przeznaczone do trwałego przechowywania w bazie danych stanowiącej przedmiot podsystemu. Dla każdej klasy definiowane są atrybuty, skojarzenia, krotności i role.

Obiekty projektu

Model klas

Ten model opisuje inne obiekty niezbędne do przechowywania danych w bazie, niebędące obiektami trwałymi.

Plan testów

Dokument

Dokument ten opisuje strategie testowanie, kryteria testowania i przypadki testowe dedykowane wykrywaniu usterek tkwiących w implementacji podsystemu zarządzania bazą danych.

Usterki wykryte podczas inspekcji kodu

Dokument

Dokument ten opisuje usterki wykryte podczas inspekcji kodu dokonywanej przez programistów zespołu. Każdej wykrytej usterce towarzyszy propozycja jej naprawienia.

Usterki wykryte podczas testowania

Dokument

W tym dokumencie opisane są usterki podsystemu zarządzania bazą danych, zauważone w trakcie testowania.

126

Specyfikacja prac prowadzących do wykonania zadania lub aktywności ma formę pakietu. Elementami pakietu są: jego nazwa, opis przedmiotowego zadania, zasoby konieczne do jego wykonania, produkty wejściowe (wytworzone przez inne zadania) i wyjściowe (wyprodukowane przez przedmiotowe zadanie) oraz zależności od innych zadań. Na rysunku 3.9 widoczny jest schemat zależności między pakietami, aktywnościami, zadaniami, rolami i produktami.

Rysunek 3.9. Zależności między zadaniami, aktywnościami, rolami, produktami i pakietami

W tabeli 3.3 przedstawiamy przykłady pakietów. Produkty są ważnymi podmiotami w zarządzaniu projektem, ponieważ stanowią podstawę do szacowania terminów rozpoczynania i kończenia zadań oraz dostarczania innych produktów. Przykładowo opóźnienie dostarczenia zestawów testowych skutkuje opóźnieniem rozpoczęcia procesu testowania. Zauważmy jednak, że skupienie się jedynie na czasowych aspektach dostarczania produktów jest niewystarczające: terminowe dostarczenie kiepskiej jakości zestawów testowych może spowodować, że wiele krytycznych usterek podsystemu nie zostanie wykrytych, co ostatecznie opóźni dostarczenie gotowego systemu.

3.3.4. Harmonogramy Harmonogram jest odwzorowaniem zadań na upływający czas: dla każdego zadania określa się moment jego rozpoczęcia i zakończenia. Stanowi to podstawę do planowanie terminów dostarczania poszczególnych produktów docelowych. Dwiema często używanymi notacjami dla harmonogramów są wykresy Gantta [Gantt, 1910] i grafy PERT. Wykres Gantta jest zwartą prezentacją przebiegu realizacji projektu w czasie: równolegle do poziomej osi czasu lysowane są poziome słupki oznaczające poszczególne zadania: początek i koniec słupka odzwierciedla rozpoczęcie i zakończenie konkretnego zadania. Harmonogram w postaci wykresu Gantta dla podsystemu zarządzania bazą danych pokazany jest na rysunku 3.10. Graf PERT jest acyklicznym grafem skierowanym, którego wierzchołki reprezentują zadania, a krawędzie — następstwo powiązanych zadań. Planowane rozpoczęcie i zakończenie każdego zadania staje się podstawą do wyznaczenia tzw. ścieżki krytycznej, będącej najkrótszą ścieżką w grafie. Jej długość stanowi dolne ograniczenie na czas realizacji grupy

3.3. Koncepcje organizacyjne projektu

127

Tabela 3.3. Przykładowe pakiety związane z realizacją podsystemu zarządzania bazą danych Zadanie

W y n i k a z roli

Opis

Wejście

Wyjście

Zbieranie wvmaqan dotvczacvch podsystemu zarządzani a bazą danych

Architekt systemu

Zbieranie od poszczególnych zespołów wymagań dotyczących ich zapotrzebowania p o d względem przechowywanych obiektów trwałych — ich atrybutów i powiązań między nimi

Łącznicy

API podsystemu, diagram klas UML

Pro.iektowanie podsystemu zarzadzani a baza danych

Projektant obiektów

Projektowanie podsystemu z uwzględnieniem wyboru wśród rozwiązań komercyjnych

API

Impl ementac.ia podsystemu zarządzani a bazą danych

Implementator

Implementacja podsystemu

Projekt podsystemu

Kod źródłowy podsystemu

Inspekc.ia kodu podsystemu zarządzania bazą danych

Implementator, tester, projektant obiektów

Przeprowadzenie inspekcji kodu źródłowego, związanego z implementacją podsystemu

Kod źródłowy podsystemu

Lista usterek

Planowanie testów podsystemu zarzadzania bazą danych

Tester

Opracowanie zestawów testowych dla podsystemu

API

Zestawy testowe i plan testowania

Testowanie podsystemu zarzadzania baza danych

Tester

Wykonanie testów podsystemu

przedstawiający model analityczny obiektów trwałych

podsystemu

podsystemu, kod źródłowy podsystemu Plan testowania podsystemu, zestawy testowe dla podsystemu

Projekt podsystemu w postaci diagramu UML

Wyniki testów, lista usterek

zadań, pod w a r u n k i e m dostępności zasobów wystarczających dla pełnego wykorzystania m o ż l i w o ś c i z r ó w n o ł e g l e n i a z a d a ń . Ścieżka k r y t y c z n a P E R T jest c z y n n i k i e m o tyle w a ż n y m w h a r m o n o g r a m i e , że wydłużenie czasu realizacji jakiegokolwiek leżącego n a niej zadania nieu c h r o n n i e p r o w a d z i d o w y d ł u ż e n i a realizacji całego p r o j e k t u . Graf P E R T dla realizacji p o d s y s t e m u z a r z ą d z a n i a b a z ą d a n y c h p r z e d s t a w i o n y jest n a r y s u n k u 3.11; k r a w ę d z i e t w o r z ą c e ścieżkę k r y t y c z n ą w y r ó ż n i o n e są p o g r u b i o n y m i l i n i a m i .

Rozdziat 3. • Organizacja projektu i komunikacja

128

Rysunek 3.10. Przykładowy harmonogram dla realizacji podsystemu zarządzania bazą danych — wykres Gantta

Rysunek 3.11. Przykładowy harmonogram dla realizacji podsystemu zarządzania bazą danych — graf PERT. Pogrubione krawędzie tworzą ścieżkę krytyczną

3.4. Koncepcje komunikacyjne projektu Dotychczas rozważaliśmy głównie organizację projektu, obecnie przyjrzymy się bliżej samej komunikacji jako elementowi tejże organizacji. Omówimy dwa główne typy komunikacji: komunikację planową (sekcja 3.4.1) i komunikację pozaplanową (sekcja 3.4.2). Później zajmiemy się narzędziami usprawniającymi komunikację w ramach projektu (sekcja 3.4.3). Na rysunku 3.12 przedstawiamy zależności występujące pomiędzy koncepcjami organizacyjnymi a koncepcjami komunikacyjnymi.

3.4.1. Komunikacja planowa Przewidywalnymi zdarzeniami komunikacyjnymi są zdarzenia określone przez poszczególne punkty harmonogramu. W ich ramach uczestnicy projektu wymieniają informacje na określony temat, na przykład związane z przeglądem produktu. Zdarzenia te zostają sformalizowane i zorganizowane w odpowiednią strukturę, by zapewnić przepływ jak największej ilości informacji w jednostce czasu, tak cennego dla uczestników. Do typowych przewidywalnych zdarzeń wymagających komunikacji zaliczyć można między innymi: • Prezentowanie problemu, • Przegląd kliencki,

3.3. Koncepcje organizacyjne projektu

129

Rysunek 3.12. Relacje między koncepcjami komunikacyjnymi i organizacyjnymi

• Przegląd projektowy, • Przegląd partnerski, • Przegląd statusowy, • Burzę mózgów, • Emisje, • Przegląd post mortem.

| Przyjrzyjmy się pokrótce poszczególnym z nich.

Deklaracja problem u Centralnym punktem prezentacji problemu jest dokument deklaracji problemu, zawierający sformułowanie problemu, dziedziny aplikacyjnej i pożądanej funkcjonalności systemu. Często deklaracja problemu zawiera także wymagania pozafunkcyjne, na przykład specyfikację platformy sprzętowej lub programowej czy też oczekiwaną szybkość działania systemu. Poniżej zamieszczamy fragment przykładowej deklaracji problemu.

Rozdziat 3. • Organizacja projektu i komunikacja

130

DEKLARACJA PROBLEMU OWL 1. Dziedzina aplikacyjna Obowiązującym dziś t r e n d e m w przemyśle budowniczym jest dostarczanie rozproszonych usług i kontrolowanie środowiska na poziomie poszczególnych użytkowników budynku, co w zamierzeniu łagodzić ma konsekwencje nadmiernego uzależnienia od ogromnych scentralizowanych systemów, charakterystycznych dla budynków biurowych wybudowanych w ostatnich trzydziestu latach. W inteligentnym miejscu pracy pracownicy mają większą kontrolę nad elementami swego środowiska — temperaturą, natężeniem oświetlenia, redukcją światła słonecznego, szybkością i kierunkiem nawiewanego powietrza i tak dalej (we wszystkie te udogodnienia wyposażony jest typowy samochód — czemu więc nie ma być ich w biurze?). Energooszczędna fasada budynku powinna zapewniać napływ świeżego powietrza oraz dostosowywanie natężenia światła wpadającego przez okna, w celu jak najlepszego wykorzystywania światła dziennego na stanowisku pracy, przy jednoczesnej ochronie przed oślepiającym światłem słonecznym. W inteligentnym miejscu pracy pożądane jest zaadaptowanie trzech form sterowania wyżej wymienionymi czynnikami: reaktywnej, planowej i doraźnej. Kontrola reaktywna polega na samoczynnym dostosowywaniu stanu wybranych komponentów do wskazań czujników; kontrola planowa odnosi się do przewidywalnych zdarzeń, zjawisk i czynników, dzięki czemu stan wspomnianych komponentów można bezpośrednio kontrolować i modyfikować, zgodnie ze starannie opracowanym harmonogramem. Przykładem przewidywalnych danych jest pozycja Słońca, dzięki czemu regulowanie dostępności światła dziennego może odbywać się w sposób a priori zaprogramowany. Sterowanie doraźne to nic innego jak możliwość bezpośredniego sterowania wymienionymi czynnikami przez samych pracowników, stosownie do ich aktualnych potrzeb — jeśli będą chcieli ingerować w automatyczną kontrolę swego środowiska pracy, należy im to umożliwić. Przedmiotem tego projektu jest stworzenie systemu OWL (Object-Oriented Workplace jako próby usprawnienia sposobu budowania biurowców. 2.

Laboratory)

Scenariusze 2.1. Sterowanie funkcjami budynku

Pracownicy dokonują dostosowywania swego środowiska pracy do własnych potrzeb za pomocą osobistego modułu środowiskowego (PEM — Personal Environment Module), którego interfejsem jest przeglądarka W W W . Moduł ten sprawuje kontrolę nad aktualnym stanem środowiska, rejestrując w swej bazie akcje poszczególnych pracowników i regulując odpowiednio ogrzewanie i wentylację miejsca pracy. Moduł PEM współpracuje z modułami PEM sąsiadujących stanowisk w celu sprawdzenia, czy chłodzenie danego stanowiska nie wymaga dogrzewania stanowisk sąsiednich.

[...] 2.5. Utrzymanie i konserwacja budynku System monitoruje zachowanie kontrolowanych urządzeń w celu wykrywania ewentualnych usterek. Zdarzenia w rodzaju przepalenia żarówki czy odczytu nienormalnych wartości parametrów raportowane są personelowi zarządzającemu, odpowiedzialnemu za konserwację sprzętu i jego inspekcje. Częstotliwość występowania awarii poszczególnych urządzeń jest analizowana pod kątem regularności, na podstawie której personel może zawczasu podjąć środki uprzedzające kolejne awarie.

[...]

3.3. Koncepcje organizacyjne projektu

131

Dokument deklaracji problemu nie zawiera kompletnej specyfikacji systemu, ogranicza się jedynie do wstępnego określenia aktywności ustanawiającej platformę porozumienia między klientem a zespołem projektu. Aktywnościami związanymi ze zbieraniem wymagań od klienta zajmiemy się w rozdziałach 4. „Zbieranie wymagań" i 5. „Analiza wymagań".

Przegląd kliencki Celem przeglądu klienckiego jest umożliwienie klientowi oceny postępu prac prowadzonych przez programistów, a także dostarczenie programistom potwierdzenia wymagań lub żądania zmian pod adresem tworzonego systemu. Przegląd kliencki stanowi więc okazję do potwierdzenia oczekiwań z obu stron i budowania coraz lepszego porozumienia między uczestnikami projektu. Przedmiotem zainteresowania przeglądu klienckiego są elementy funkcjonalne systemu i ewentualne ograniczenia istotne dla klienta (wydajność, platforma sprzętowa oraz systemowa i tym podobne). W większości przypadków pomijane są kwestie związane z projektowaniem i implementowaniem systemu, chyba że są interesujące (bo nieobojętne) dla ldienta lub użytkownika — wyjątki dotyczą najczęściej kwestii bezpieczeństwa lub ograniczeń o charakterze prawnym. Przegląd kliencki przeprowadzany jest jako formalna prezentacja, w czasie której programiści skupiają się na specyficznym aspekcie funkcjonalności z perspektywy klienta. Przegląd kliencki poprzedzany jest emisją produktu — dokumentu specyfikacji, makiety interfejsu czy ewaluacyjnego prototypu podsystemu. W odpowiedzi na wspomnianą prezentację klient przekazuje swoje uwagi programistom; uwagi te mogą sprowadzać się do generalnej aprobaty zaprezentowanych rozwiązań bądź też mogą zawierać wymogi lub sugestie w zakresie funkcjonalności systemu lub harmonogramu. W tabeli 3.4 widoczny jest przykład agendy przeglądu klienckiego. Tabela 3.4. Przykładowa agenda przeglądu klienckiego

Agenda testów akceptacyjnych klienta OWL Data

5 grudnia

Czas

15:00-16:30

Lokalizacja

Forest Hall

Cel

Ocena systemu przez klienta i identyfikacja niezałatwionych problemów

Program

Deklaracja problemu Cele projektu Architektura systemu Demo nr 1: Interfejs zdalnego użytkownika i zdalna kontrola Demo nr 2: Edytor struktury stanowisk pracy Demo nr 3: Trójwymiarowa wizualizacja i omówienie interfejsu użytkownika Pytania i odpowiedzi Podsumowanie

Rozdziat 3. • Organizacja projektu i komunikacja

132

Przegląd

projektowy

Cele przeglądu projektowego są dwojakie: dla menedżera projektu jest on okazją do oszacowania statusu projektu, dla zespołów programistów to czas na wzajemną prezentację interfejsów poszczególnych podsystemów. W czasie przeglądu projektu programiści mają też sposobność do wymiany opinii na temat wspólnych problemów dotyczących systemu lub używanych narzędzi. Meritum przeglądu projektowego zależne jest od produktu docelowego, który jest podmiotem przeglądu: jeśli produktem tym jest projekt systemu, zainteresowanie skupia się na dekompozycji i interfejsach poszczególnych podsystemów, w przypadku projektu obiektów przedmiotem zainteresowania są interfejsy tychże obiektów, zaś w przypadku testowania i integrowania podsystemów najistotniejsze są same testy i ich rezultaty. Przegląd projektowy ma zwykle formę formalnej prezentacji, w ramach której poszczególne zespoły demonstrują swoje podsystemy menedżerowi i (lub) innym zespołom zależnym od tych podsystemów. Przegląd poprzedzony jest zwykle stworzeniem dokumentu (na przykład dokumentu projektu systemu) opisującego wybrane aspekty przedmiotowego systemu (na przykład interfejsy poszczególnych podsystemów). Przy zakończeniu przeglądu programiści mogą negocjować zmiany w interfejsach i (lub) przesunięcie terminów w harmonogramie.

Przegląd

partnerski

Dwie techniki, które walnie przyczynić się mogą do poprawy jakości systemu, to inspekcja kodu źródłowego i wędrówki po kodzie; techniki te praktykowane są w czasie przeglądu partnerskiego (w odróżnieniu od przeglądu projektowego czy przeglądu klienckiego). W ramach wędrówek po kodzie programista prezentuje swoim kolegom programistom napisany przez siebie kod źródłowy, wiersz po wierszu. Daje to okazję wychwycenia wielu podejrzanych konstrukcji i tym samym wczesnego zidentyfikowania przyczyn rozmaitych błędów. Rolą programisty prezentera jest kierowanie prezentacją i odpowiadanie na pytania kolegów z zespołu. Inspekcja kodu jest natomiast badaniem zgodności kodu z predefiniowaną listą kryteriów (na przykład sprawdzanie, czy dany fragment kodu istotnie jest implementacją konkretnego algorytmu albo czy fragment kodu odwołuje się do innych podsystemów, zgodnie ze specyfikacją ich API). Role uczestników są tu odwrotne w stosunku do wędrówki po kodzie — tutaj zespół zadaje pytania, na które programista musi odpowiadać. Należy jednak pamiętać, że podmiotem inspekcji oraz wędrówki po kodzie jest sam kod, a nie jego autor. W czasie przeglądu partnerskiego wspólnym punktem odniesienia w komunikacji między uczestnikami jest kod źródłowy. Inspekcje i wędrówki mają ten sam cel, co przegląd projektowy — poprawę jakości kodu i rozpowszechnienie niezbędnych informacji operacyjnych — różnią się jednak od niego mniej formalnym charakterem, ograniczonym liczebnie audytorium i dłuższym zazwyczaj czasem trwania. Jak dowodzą tego liczne przykłady (między innymi [Fagan, 1976]), inspekcja kodu i wędrówki po kodzie są technikami powszechnie stosowanymi przez programistów, są bowiem efektywne pod względem wczesnego wykrywania usterek kodu. Wędrówkami po kodzie zajmiemy się dokładniej w rozdziale 11. „Testowanie".

3.3. Koncepcje organizacyjne projektu

Przegląd

133

statusowy

W przeciwieństwie do przeglądów klienckiego i projektowego, które dotyczą samego systemu, przegląd statusowy koncentruje się na zadaniach związanych z systemem. Przeglądy statusowe przeprowadzane są zwykle na forum zespołu (na przykład co tydzień), okazjonalnie też mogą być przeprowadzane na szczeblu całego projektu (przykładowo raz na miesiąc). Celem przeglądu statusowego jest wykrywanie odchyleń od planu realizacji zadań i korygowanie tych odchyleń. Przegląd statusowy stanowi dla programistów dodatkową motywację do szybszego zakończenia zaległych żądań: przegląd statusu zadania zachęca do dyskusji nad otwartymi i nieprzewidzianymi wcześniej problemami, co w naturalny sposób generuje nieformalną komunikację między programistami. Często poszukiwanie rozwiązań wspólnych problemów staje się efektywniejsze, gdy przeprowadzane jest wewnątrz zespołu, nie na forum całego projektu. Przeglądy statusowe reprezentują inwestycję w produktywność programistów: zwiększenie efektywności samych przeglądów ma pozytywny wpływ na globalną efektywność całego zespołu. Przeglądy statusowe powinny odbywać się w oparciu o sporządzoną wcześniej agendę, opisującą zadania i problemy do omówienia. Dzięki niej uczestnicy mogą lepiej przygotowywać się do dyskusji, a także uzupełniać wspomnianą agendę o pilne problemy, które się niespodziewanie pojawią. Protokoły z każdego spotkania, sporządzane przez wyznaczonego uczestnika, powinny uwzględniać jak najwięcej informacji (głównie na temat statusu projektu i podejmowanych decyzji) i stać się jak najszybciej dostępne dla wszystkich uczestników przeglądu. Z jednej strony, motywuje to odpowiedzialnego uczestnika do skrupulatnego notowania, z drugiej, daje możliwość zapoznania się ze szczegółami spotkania tym uczestnikom, którzy byli nieobecni. Protokoły okazują się nieocenione w przypadku dyskusji nad poszczególnymi zadaniami oraz wtedy, kiedy trzeba wyjaśnić jakieś wątpliwości. Protokoły stanowią ponadto fragment historii projektu, którą można przeanalizować po jego zakończeniu.

Burza

mózgów

Celem burzy mózgów jest wygenerowanie jak największej liczby propozycji rozwiązań problemu, niezależnie od ich rzeczywistej wartości merytorycznej, a następnie ocena każdej z tych propozycji. Burzę mózgów urzeczywistnia się zwykle w ramach spotkań osobistych, lecz równie dobrze można ją prowadzić za pomocą e-maili lub grup dyskusyjnych, komunikatorów i tym podobnych form komunikacji. Sens burzy mózgów zasadza się na prostym spostrzeżeniu, iż każda propozycja, choćby nawet najbardziej dziwaczna, może stać się inspiracją dla bardziej konkretnych pomysłów. Co więcej, w przypadku problemów szczególnie skomplikowanych rozwiązanie pojawić się może za pośrednictwem idei, która początkowo wydawała się niedorzeczna. Istotą burzy mózgów jest myślenie poza utartymi schematami. Burza mózgów ma ponadto dwa korzystne efekty uboczne: ocena pomysłów na forum grupy skłania do formułowania bardziej czytelnych kryteriów tej oceny, a poza tym łatwiej osiąga się wówczas konsensus dotyczący wybranego rozwiązania.

134

Rozdziat 3. • Organizacja projektu i komunikacja

Emisje Emisja produktu, często w celu zastąpienia jego starszej wersji, wymaga udostępnienia innym uczestnikom informacji o tym fakcie. Informacja ta może mieć formę tak prostą jak dwulinijkowy komunikat (jak poniżej) bądź też może składać się z wielu części, na przykład opisu nowej wersji produktu, listy zmian od jego poprzedniego wydania, listy nierozwiązanych wciąż problemów i wątpliwości oraz nazwiska autora.

Od: Al Grupy dyskusyjne: Temat: SDD

cs413.f96.architecture.discuss

Data: 2003-11-25 03:39:23 Lines: 6 Message-ID: MimeVersion: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit Nowa wersja dokumentacji API dla podsystemu powiadamiania dostępna jest pod adresem http: //decaf/~al/FRIEND/notifapi.html —Al Kierownik grupy powiadamiania

W ten oto sposób duża porcja informacji może być szybko rozpowszechniona w formie kontrolowanej i spójnej — wiele zmian rozpowszechnionych zostaje łącznie, w jednym miejscu. Przeglądy kliencki i projektowy poprzedzane są zwykle informacją o konkretnym produkcie docelowym lub kilku produktach. W rozdziale 13. „Zarządzanie konfiguracją" opiszemy zarządzanie wersjami dokumentów, modeli i podsystemów.

Przegląd post mortem Celem przeglądu post mortem jest wyciągnięcie wniosków z całego procesu tworzenia systemu, który został właśnie dostarczony klientowi. Im szybciej taki przegląd zostanie przeprowadzony, tym mniejsze ryzyko utraty lub zniekształcenia istotnych informacji. Zakończenie realizacji projektu to dobry moment do oceny technik, metod i narzędzi oraz wskazania tych, które w stopniu krytycznym przyczyniły się do powodzenia (lub klęski) tej realizacji. Przegląd post mortem może być przeprowadzany jako sesja burzy mózgów, jako wywiad poparty strukturalnym kwestionariuszem bądź jako zestaw indywidualnych raportów stworzonych przez uczestników (lub zespoły). Niezależnie od formy, konieczne jest uwzględnienie wszystkich narzędzi, metod, organizacji i procedur wykorzystywanych w związku z zakończonym projektem.

135

3.3. Koncepcje organizacyjne projektu

W tabeli 3.5 widoczne są przykładowe pytania, jakie paść powinny w ramach przeglądu post mortem. Nawet jeśli rezultaty przeglądu post mortem nie zostaną rozpowszechnione oficjalnymi kanałami (na przykład w formie raportów technicznych), mogą być przekazywane pośrednio uczestnikom projektu. Ci bardzo często przydzielani są do kolejnych projektów czy oddelegowani do pełnienia innych funkcji, mają więc okazję upowszechniać wewnątrz firmy wnioski z lekcji wyciągniętych przy realizacji poprzednich projektów. Przegląd post mortem jest zatem idealną okazją do wszelkich podsumowań związanych z ostatnio zakończonymi (lub zaniechanymi) projektami. Tabela 3.5. Przykłady pytań w ramach przeglądu post mortem Napotkane

problemy

Możliwe sposoby problemów

rozwiązywania

Jakie rodzaje k o m u n i k o w a n i a się i negocjowania miały miejsce w związku z tworzeniem systemu? Jaki r o d z a j s t r u k t u r y i n f o r m a c y j n e j byłby o d p o w i e d n i dla pracy zespołowej w połączeniu z obiektową metodologią tworzenia oprogramowania? Czy uważasz, że dostępne formy komunikacji (dokumenty, dyskusje, ogłoszenia) potrafią sprostać temu wyzwaniu? Jakie widzisz problemy w ramach istniejącej struktury informacyjnej i w jaki sposób chciałbyś je rozwiązywać?

Inne aspekty projektu — sprawdzone lub wymagające usprawnienia

Inne

kwestie

Jakie inne wnioski, obserwacje i k o m e n t a r z e dotyczące zakończonego projektu nasuwają ci się w związku z: •

twoimi oczekiwaniami przy r o z p o c z y n a n i u p r o j e k t u i ich ewolucją w trakcie jego realizacji,



celami projektu,



realnością uwzględnionych przypadków użycia,



cyklem życiowym projektu,



zarządzaniem p r o j e k t e m i wynikającymi z niego spotkaniami, k o m u n i k a c j ą i tym p o d o b n y m dokumentowaniem projektu.

Jakie inne p r o b l e m y dostrzegasz w związku z realizacją projektu i jakie z a p r o p o n o w a ł b y ś sposoby ich rozwiązywania?

3.4.2. Komunikacja pozaplanowa W idealnym przypadku komunikacja w ramach projektu odbywa się wyłącznie w związku z przewidywalnymi zdarzeniami. W praktyce jednak trudno przewidywać a priori wszelkie zdarzenia i planować z wyprzedzeniem niezbędną komunikację, o czym niech zaświadczy choćby poniższy przykład.

Rozdziat 3. • Organizacja projektu i komunikacja

136

Niedziela, 29 marca 1998. Uczestnicy projektu JAMES1 gorączkowo przygotowują się do dostarczenia klientowi ukończonego systemu. Rozpoczęcie testów akceptacyjnych uzgodnione zostało z klientem na 31 marca, na godzinę 15:00 środkowoeuropejskiego czasu letniego. Z powodu przejścia na czas letni w nocy z 28 na 29 marca programistom ubyła jedna godzina. Testy akceptacyjne prowadzone będą w formie trójstronnej wideokonferencji między klientem w Stuttgarcie, niemieckimi programistami z Technical University Munich (TUM) w Monachium i amerykańskimi programistami z Carnegie Mellon University (CMU) w Pittsburgu. Amerykanie mają rozpocząć konferencję o godz. 9:00 swojego czasu. Gotowa jest agenda — każdy zespół ma 12 minut na zaprezentowanie funkcjonalności swojego podsystemu. Późnym wieczorem. Niemieccy programiści dowiadują się nagle, niejako przypadkiem, od przebywającego u nich studenta-programisty z CMU, że w USA przejście na czas letni dokonuje się o tydzień później niż w Europie, wobec czego różnica czasu między Monachium a Pittsburgiem wynosi obecnie 7, nie 6 godzin, a zatem Amerykanie powinni rozpocząć wideokonferencję o godzinie 8:00, nie 9:00 swojego czasu. Niecałe 48 godzin przed rozpoczęciem testów akceptacyjnych Amerykanie uświadamiają sobie, że o mały włos nie rozminęli się o godzinę z harmonogramem! Powiadomienie o tym fakcie wszystkich członków amerykańskiego zespołu wymagać będzie być może całego dnia.

Powyższa historyjka jest przykładem niewłaściwego komunikowania w związku z przejściem na czas letni. Z perspektywy czasu można to zakwalifikować jako poważne przeoczenie — tylko przez przypadek nie doszło do katastrofy. Cóż, w ferworze przygotowań każdy z uczestników projektu JAMES myślał tylko o własnych zadaniach, w kontekście własnej lokalizacji, nie biorąc pod uwagę kwestii wynikających z geograficznego rozproszenia. Generalnie, zdarzenia wynikające z (wydawałoby się) odosobnionych faktów są bardzo trudne do przewidywania, jeśli nikt z uczestników nie prezentuje globalnego spojrzenia na ogół tych faktów. W rezultacie należy być przygotowanym na radzenie sobie z nieprzewidywalnymi zdarzeniami, często pod presją czasu. Do takich zdarzeń, wymagających komunikacji pozaplanowej, zaliczamy między innymi: • żądania wyjaśnień, • żądania zmian, • rozwiązywanie problemów. Przyjrzyjmy się szczegółowo każdej z tych kategorii.

Żądanie

wyjaśnień

Żądanie wyjaśnień, choć wiąże się często z intensywną komunikacją między uczestnikami projektu, jest zdarzeniem nieprzewidywalnym. Uczestnicy mogą żądać wyjaśnień dotyczących dowolnego aspektu systemu, niezrozumiałego lub niejasnego dla nich. Forma wymiany związanej z tym informacji bywa rozmaita, zależnie od środków komunikowania dostępnych dla uczestników — mogą to być e-maile, rozmowy telefoniczne, nieformalne spotkania i tym podobne. Oto przykład żądania wyjaśnień za pomocą postu grupy dyskusyjnej:

1

Patrz sekcja 16.5.3 — przyp.

tłum.

3.3. Koncepcje organizacyjne projektu

137

Od: Alice Grupy dyskusyjne: cs413.architecture.discuss Temat: SDD Data: 2010-10-10 23:12:48 Message-ID: MimeVersion: 1.0 Content-Type: text/plain; charset=us-ascii Kiedy dokładnie ma być gotowy dokument projektu systemu? Zgodnie z harmonogramem jest to 22 października, lecz mój szablon zawiera datę 7 listopada. Dziękuję, Alice

W tym miejscu należy zauważyć, że sytuacja, w której większość informacji związanej z projektem pozyskiwana jest w ten właśnie sposób, jest sytuacją patologiczną, świadczącą o wadliwej infrastrukturze komunikacyjnej. W konsekwencji jest to poważne zagrożenie dla projektu, wynikające z nieporozumień lub nawet gubienia istotnych informacji.

Żądanie

zmian

Za pomocą żądania zmian uczestnicy projektu mogą zgłaszać zauważone problemy i, opcjonalnie, proponować sposoby ich rozwiązywania. Problemy te mogą dotyczyć zarówno samego systemu, jak i jego dokumentacji, procesu realizacji czy organizacji projektu. W przypadku dużego projektu i (lub) licznych zespołów żądania zmian często mają postać sformalizowaną, obejmującą zwykle klasyfikację problemu (poważna usterka, żądanie nowego elementu funkcjonalnego, komentarz), opis problemu i kontekstu, w którym wystąpił, oraz wskazanie dodatkowych faktów i materiałów. Przykład sformalizowanego żądania zmian widoczny jest w tabeli 3.6. Formularze podobne do przedstawionego w tabeli 3.6 zyskały sobie dużą popularność za sprawą upowszechnienia aplikacji wspomagających śledzenie usterek w oprogramowaniu, mogą być jednak z powodzeniem wykorzystywane do celów niezwiązanych bezpośrednio z samym tworzeniem kodu, czyli podczas planowania zadań, projektowania systemu czy planowania testów i testowania.

Rozwiązywanie

problemów

Gdy rozpoznany zostanie problem i ocenione proponowane sposoby jego rozwiązania, należy wybrać jedno z rozwiązań, zakomunikować fakt tego wyboru i wybrane rozwiązanie zaimplementować. W małych organizacjach może się to odbywać na przykład w ramach burzy mózgów; w organizacji hierarchicznej, a także w sytuacjach kryzysowych, wspomnianego wyboru dokonuje jedna osoba, narzucając innym uczestnikom swą decyzję. Tak czy inaczej, wybrane rozwiązanie należy udokumentować i uczynić wiadomym dla wszystkich zainteresowanych

138

Rozdziat 3. • Organizacja projektu i komunikacja

Tabela 3.6. Przykład formularza formalizującego żądanie zmiany Nagłówek identyfikujący

żądanie

Raport nr: 1291 Data: 3 maja 2010 Autor: Dave Krótka charakterystyka problemu: Aplikacja kliencka systemu FRIEND załamuje się w przypadku wysłania pustego formularza

Informacja kontekstowa pomocna w zlokalizowaniu problemu

Podsystem: Interfejs użytkownika, wersja 3.4.1 Klasyfikacja: •

brakujący/niepoprawny element funkcjonalny



naruszenie konwencji



błąd programu



błąd w dokumentacji

Istotność błędu: •

Opis problemu

i

uzasadnienie

zastrzeżeń Opis proponowanych

poważny



umiarkowany



irytujący

Opis:.... Uzasadnienie:...

zmian:

uczestników — dokumentacja taka stanowić będzie miarodajny punkt odniesienia w przypadku ewentualnych nieporozumień w późniejszych fazach projektu, poza tym efektywne komunikowanie decyzji pozwala na lepszą synchronizację działań poszczególnych uczestników. Baza, w której przechowywana jest dokumentacja pojawiających się problemów, staje się naturalnym mechanizmem komunikacyjnym, wspomagającym rozwiązywanie problemów. Na rysunku 3.13 widoczny jest ekran ukazujący fragment przykładowej bazy komunikatów. Tytuł każdego komunikatu poprzedzony jest przedrostkiem wskazującym na jego charakter: I: oznacza problem {Issue), P: — propozycję (Proposal), zaś A+ i A- to przedrostki identyfikujące argumenty (Arguments) odpowiednio „za" i „przeciw" prezentowanej propozycji. Gdy dany problem zostaje ostatecznie rozwiązany, fakt ten znajduje odzwierciedlenie w postaci pojedynczego komentarza, opatrywanego przedrostkiem R: (Resolution). Bazom problemów do rozwiązania i modelowaniu samych problemów poświęcimy rozdział 12. „Zarządzanie racjonalizacją".

3.4.3. Mechanizmy komunikacyjne Mianem mechanizmu komunikacyjnego określamy narzędzie łub procedurę, które mogą być użyte do wysyłania i odbierania informacji oraz wspomagania zdarzeń komunikacyjnych. Mechanizmami komunikacyjnymi są więc i faks, i sygnały dymne. Mechanizm komunikacyjny nazywamy synchronicznym, jeśli zarówno nadawca, jak i odbiorca komunikatu muszą być przygotowani na jego transmisję; w przeciwnym razie mechanizm komunikacyjny nazywamy mechanizmem asynchronicznym. Sygnały dymne są przykładem komunikacji synchronicznej, faks jest natomiast narzędziem komunikacji asynchronicznej.

3.3. Koncepcje organizacyjne projektu

139

Rysunek 3.13. Przykład bazy problemów (Domino Lotus Notes)

Oba mechanizmy — synchroniczny i asynchroniczny — mogą być używane do celów komunikacji planowej. W przykładzie z rysunku 3.14 do komunikowania się w ramach przeglądu klienckiego można wykorzystywać zarówno faks, jak i sygnały dymne; komunikację pozaplanową można realizować jednak wyłącznie na bazie mechanizmów synchronicznych — próba raportowania problemu za pomocą sygnałów dymnych mogłaby po prostu zostać niezauważona przez adresata. Warto tu zauważyć, że pojedyncza aktywność komunikacyjna może być realizowana za pomocą kilku różnych mechanizmów komunikacyjnych: dokument z analizy wymagań można przesłać faksem do klienta, który wyrazić może swoje uwagi za pomocą sygnałów dymnych (bo w jego firmie właśnie trwa wymiana sieci elektrycznej). Podobnie pojedynczy mechanizm obsługiwać może wiele zdarzeń komunikacyjnych — przy użyciu faksu może odbierać zarówno powiadomienia o problemach, jak i komentarze z ostatniego przeglądu klienckiego.

Rysunek 3.14. Przykłady mechanizmów komunikacyjnych. Komunikacja planowa może być realizowana zarówno przez mechanizmy synchroniczne, jak i asynchroniczne; komunikacja pozaplanowa może być realizowana jedynie za pomocą środków synchronicznych

W tabeli 3.7 przedstawiamy synchroniczne mechanizmy komunikacyjne opisywane w tym rozdziale; tabela 3.8 zawiera natomiast zestawienie mechanizmów asynchronicznych.

140

Rozdziat

3. • Organizacja projektu i komunikacja

Tabela 3.7. Przykłady synchronicznych mechanizmów komunikacyjnych Mechanizm

Obsługiwane zdarzenia komunikacyjne

Konwersacja okazjonalna

Żądanie wyjaśnień Żądanie zmian

Kwestionariusze i strukturalizowane wywiady

Definiowanie problemu Przegląd post mortem

Spotkania osobiste, rozmowy telefoniczne, wideokonferencje

Definiowanie problemu Przegląd kliencki Przegląd projektowy Przegląd partnerski Przegląd statusowy Przegląd post mortem Burza mózgów Rozwiązywanie problemów

Synchroniczna komunikacja grupowa (komunikatory)

Przegląd kliencki Przegląd projektowy Przegląd partnerski Burza mózgów Rozwiązywanie problemów

Tabela 3.8. Przykłady asynchronicznych mechanizmów komunikacyjnych Mechanizm

Obsługiwane zdarzenia komunikacyjne

E-mail

Żądanie zmian Burza mózgów

Grupy dyskusyjne

Żądanie zmian Burza mózgów

WWW

Informacja o emisji Asynchroniczny przegląd partnerski Żądanie zmian Burza mózgów

Lotus Notes

Informacja o emisji Asynchroniczny przegląd partnerski Żądanie zmian Burza mózgów

Konwersacje

okazjonalne

Konwersacja okazjonalna stanowi nieplanowaną, nieformalną wymianę informacji, b a z u j ą c ą w zasadzie n a p r z y p a d k u — uczestnicy w y k o r z y s t u j ą okazję d o konwersacji. W ram a c h takiej w ł a ś n i e k o n w e r s a c j i w y k r y t y został o p i s a n y w c z e ś n i e j p r o b l e m ze z m i a n ą czasu. O t o kolejny przykład.

141

3.3. Koncepcje organizacyjne projektu

Sally i Bob, uczestniczący w tym samym projekcie, spotkali się przy automacie z kawą. Sally, odpowiedzialna jest za interfejs użytkownika. Wiedząc, że Bob jest członkiem zespołu powiadamiania, odpowiedzialnym za komunikację między podsystemami klienckimi a serwerem, informuje go o nieregularnych kłopotach z pobieraniem pakietów z serwera: nie jest pewna, czy przyczyna tych kłopotów nie leży przypadkiem w jej kodzie. I dowiaduje się od Boba, iż równolegle z jej próbami pobierania pakietów testowana była nowa rewizja systemu komunikacyjnego; Bob, w trosce o oszczędność czasu, pominął obowiązujące w takich przypadkach oficjalne procedury związane z zarządzaniem konfiguracją. Ostatecznie wyjaśnia to całą zagadkę.

Takie okazjonalne rozmowy mają zwykle znaczący udział w całościowej komunikacji związanej z projektem. Nic nie kosztują, a bywają niesamowicie skuteczne podczas rozwiązywania prostych problemów wynikających z braku dostatecznej koordynacji działań między uczestnikami. Przyczyniają się także do wymiany wiedzy operacyjnej na temat powszechnie znanych problemów i wątpliwości, używanych narzędzi i procedur oraz źródeł informacji na temat projektu. Ich słabą stroną jest niewielka zazwyczaj liczba rozmówców i brak dokumentowania historii: wiele ważnych informacji może zostać przez to zagubionych, a informacja rozpowszechniana jedynie w sposób werbalny podatna jest na zniekształcenia. Co więcej, po tym werbalnym przekazie nie pozostaje żaden trwały ślad w postaci dokumentu, bazy danych czy choćby e-maili — ślad, który mógłby stanowić punkt odniesienia do analizy decyzji podejmowanych i oznajmianych w trakcie takich konwersacji. Kwestionariusze

i wywiady

strukturalne

Celem kwestionariusza jest zebranie informacji od jednej lub kilku osób w sposób strukturalny. Kwestionariusze stosowane są zazwyczaj do pozyskiwania wiedzy od użytkowników i ekspertów, w celu lepszego zrozumienia wymagań i priorytetów klienta, mogą być też używane na potrzeby wyciągania wniosków z przeglądu post mortem. Kwestionariusz może zawierać zarówno pytania o charakterze otwartym, jak i opcje wyboru (być może wielokrotnego). Kwestionariusze mają tę zaletę, że pozyskiwanie informacji odbywa się przy minimalnym fatygowaniu użytkownika. Oczywiście, każdy kwestionariusz musi być sporządzony indywidualnie, niezależnie od pozostałych, a wszelkie niejasności lub niekompletne odpowiedzi powinny zostać wyjaśnione w ramach wywiadu strukturalnego. Podstawową wadą kwestionariuszy jest fakt, że trudno je właściwie projektować; wysiłek włożony w ich zaprojektowanie jest jednak opłacalny, jeśli weźmie się pod uwagę potencjalne konsekwencje niedostatecznego zrozumienia potrzeb klienta przez programistów. Projektowanie kwestionariuszy i wywiadów strukturalnych omówione jest między innymi w książce J. T. T. Barone'a i J. Switzera [Barone i Switzer, 1995]. Spotkania Spotkania osobiste umożliwiają niewielkim grupom uczestników wymianą, ocenę i negocjowanie problemów oraz rozwiązań. Dotąd nie wymyślono innego, ale równie skutecznego sposobu znajdowania dobrych rozwiązań i budowania konsensusu. Organizacja zebrania wymaga jednak zainwestowania pewnych kosztów i pozyskania zasobów, ponadto zebraniami trudno zarządzać, gdyż ich przebieg łatwo wymyka się spod kontroli. Aby więc sprawić, by zebrania stały się jak najbardziej treściwe i efektywne pod względem liczby podjętych decyzji, zwykle wybranym uczestnikom przypisuje się trzy poniższe role.

Rozdziat 3. • Organizacja projektu i komunikacja

142

• Facilitator — odpowiedzialny jest za organizację zebrania i jego przebieg. Jego zadaniem jest sporządzenie agendy opisującej cel zebrania i zakres przewidywanych tematów; agenda powinna być gotowa na tyle wcześnie, by uczestnicy zdążyli się z nią zapoznać przed rozpoczęciem zebrania, dzięki czemu będą mogli ocenić, jak ważne jest dla nich samo zebranie, i przygotować ewentualne materiały pomocnicze. • Protokolant — jego zadaniem jest dokumentowanie przebiegu zebrania, w sposób tradycyjny (na papierze) lub za pomocą komputera; po zakończeniu zebrania materiał dokumentujący powinien zostać jak najszybciej opracowany i przekazany poszczególnych uczestnikom, którzy tym samym będą mogli przeanalizować swoje zadania i role w kontekście tematyki poruszanej na zebraniu. Dokumentacja zebrania może też być użyteczna dla tych uczestników, którzy nie byli na nim obecni. • Chronometrażysta — kontroluje zgodność wykorzystywania czasu zebrania z agendą, informując facilitatora o przekroczeniu założonego czasu dyskusji na dany temat. Agenda spotkania składa się z przynajmniej trzech części: nagłówka określającego planowane miejsce i czas spotkania, listy tematów zgłoszonych przez uczestników oraz listy problemów, dla których w trakcie zebrania poszukiwać się będzie rozwiązań. Każdemu elementowi zebrania przydziela się maksymalny czas, dzięki czemu chronometrażysta może zapewnić, że całe zebranie zakończy się w wyznaczonym terminie. W tabeli 3.9 widzimy przykład agendy; agenda przedstawiona w tabeli 3.10, mimo pozorów poprawności, jest w istocie bezwartościowa (załączone komentarze wyjaśniają, dlaczego). Tabela 3.9. Przykładowa agenda zebrania Nagłówek zebranie

Oczekiwane zebrania

identyfikujący

rezultaty

Gdzie i kiedy Data: 30 stycznia 2010 Początek: 16:30

Role Facilitator: Peter C h r o n o m e t r a ż y s t a : Dave

Koniec: 17:30 Pokój: W H , 3420

P r o t o k o l a n t : Ed

1. Cel Rozwiązanie problemów z wymaganiami klienta, uniemożliwiających rozpoczęcie prototypowania.

Akcje podlegające sprawozdaniu

2. Status [Czas: 15 m i n u t ]

Problemy

3. Tematy do dyskusji [Czas: 35 minut]

Dave: Stan kodu parsera poleceń.

przeznaczone

do przedyskutowania na zebraniu

3.1. Jak radzić sobie z arbitralnie s f o r m a t o w a n y m i plikami danych wejściowych? 3.2. W jakiej formie tworzyć wyniki parsingu? 3.3. Szczegóły k o d u parsera (łatwość modyfikacji, kompatybilność wstecz).

Czas podsumowania sam dla wszystkich

jest taki zebrań

4. Podsumowanie [Czas: 5 minut] 4.1. Analiza bieżących akcji i e w e n t u a l n e przydzielenie nowych zadań. 4.2. Komentarze i wnioski.

3.3. Koncepcje organizacyjne projektu

143

Tabela 3.10. Mniej fortunny przykład agendy zebrania Zebrania bez określonego czasu trwania maję tendencję do przeciągania się w nieskończoność

Cel trudny do osiągnięcia i zweryfikowania

Gdzie i kiedy Data: 30 stycznia 2010 Początek: 16:30 Koniec: Nieznany Pokój: W H , 3420

Role Facilitator: Peter Chronometrażysta: Dave Protokolant: Ed

1. Cel Rozwiązanie zaistniałych problemów.

Brak konkretów: jakie zadania przydzielono Dave'owi?

2. Status [Czas: 15 minut]

Brak konkretów: jakie problemy aktualnie istnieją w każdej

3. Tematy do dyskusji [Czas: 35 minut]

z trzech kategorii?

wymienionych

Dave: Zadanie przydzielone Dave'owi.

3.1. Problemy z wymaganiami klienta. 3.2. Problemy z projektowaniem. 3.3. Problemy implementacyjne. 4. Podsumowanie [Czas: 5 minut] 4.1. Analiza bieżących akcji i ewentualne przydzielenie nowych zadań. 4.2. Komentarze i wnioski.

Protokół zebrania składać się powinien z trzech części, odpowiadających poszczególnym częściom agendy. Dodatkowo protokół zawiera część opisującą akcje, które uczestnicy zebrania mają wykonać w rezultacie podjętych na zebraniu decyzji. Nagłówek protokołu określa miejsce, czas i uczestników zebrania; w drugiej części reprezentowana jest informacja przedstawiona w ramach zebrania, zaś część trzecia poświęcona jest decyzjom — tym podjętym i tym, które mimo oczekiwań nie zostały podjęte. Przykładowy protokół zebrania widoczny jest w tabeli 3.11. Mimo iż zebranie odbywające się w jednym miejscu jest najbardziej efektywne, możliwe jest także organizowanie zebrań uczestników rozproszonych geograficznie, za pomocą telekonferencji lub wideokonferencji. Takie rozproszone zebrania bywają bardziej oszczędne, gdy chodzi o koszty i zasoby, lecz także mniej efektywne i mniej niezawodne, z powodu zawodności samych środków (internet) służących do ich realizacji. W warunkach ograniczonej jakości dźwięku i obrazu wyjątkowego znaczenia nabiera dobrze przygotowana agenda; możliwość rozróżniania głosu (i innych unikalnych cech osobowych) poszczególnych uczestników przyczynia się wydatnie do lepszej komunikacji. Facilitator sporządzający agendę powinien być tak konkretny, jak to tylko możliwe, by uniknąć niepotrzebnego jej rozbudowywania. Istnieje pokusa ułatwiania sobie pracy poprzez sporządzanie szablonowych, zbyt ogólnych agend, w rodzaju widocznej w tabeli 3.10, przydatnych dla każdego zebrania bez większych zmian. Agenda przestaje być wówczas czynnikiem wspomagających efektywność zebrania, a staje się niepotrzebną procedurą biurokratyczną, niedającą uczestnikom żadnych korzyści.

Rozdziat 3. • Organizacja projektu i komunikacja

144 Tabela 3.11. Przykładowy protokół z zebrania Nagłówek identyfikujący zebranie i jego audytorium

Gdzie i kiedy Data: 30 stycznia 2010 Początek: 16:30 Koniec: 18:00 Pokój: W H , 3420

Taki sam jak w agendzie

1. Cel

Streszczenie

prezentowanej

Role Facilitator: Peter Chronometrażysta: Dave Protokolant: Ed Obecni: Ed, Dave, Mary, Peter, Alice

Status

informacji Dyskutowane i przedstawione

tematy rozwiązania

3. Dyskusja 3.1. Kod parsera to instrukcja if o rozmiarze 1200 - 1300 wierszy. W y d a j e się wręcz n i e w y k o n a l n e d o d a n i e nowych poleceń lub m o d y f i k o w a n i e znaczenia istniejących bez naruszenia kompatybilności z dotychczasową wersją wykorzystywaną przez klientów. Propozycje rozwiązania: 1) Restrukturyzacja kodu parsera przez przyporządkowanie o d r ę b n e g o obiektu k a ż d e m u rodzajowi poleceń. 2)

Identyfikowanie poszczególnych parametrów polecenia przez nazwę, co z jednej strony, ułatwi w przyszłości zachowanie kompatybilności wstecz, z drugiej jednak, spowoduje zwiększenie rozmiaru polecenia i plików zawierających polecenia.

Przyjęte rozwiązanie: Restrukturyzacja kodu. Kompatybilność wstecz nie jest obecnie poważnym problemem, gdyż kod wywołujący parser i tak musi zostać napisany na nowo. Patrz AI[1].

Uzupełnienie i modyfikacja

4. Podsumowanie [Czas: 5 minut]

istniejącego planu

AI[1] Dla: Dave.

zadań

Przeróbka kodu parsera ze szczególnym uwzględnieniem jego modularności, koordynacja działań z Billem z grupy baz danych (która być może wymagać będzie zachowania kompatybilności wstecz).

Narzędzia

komunikacji

grupowej

O p r o g r a m o w a n i e w s p o m a g a j ą c e dyskusję r o z p r o s z o n y c h u c z e s t n i k ó w w czasie rzec z y w i s t y m p r z e z długi czas b y ł o d o m e n ą o ś r o d k ó w a k a d e m i c k i c h ( [ G r u d i n , 1988], [Borghoff i Schlichter, 2000]), lecz z b i e g i e m czasu zyskało p o p u l a r n o ś ć t a k ż e w k r ę g a c h b i z n e s o w y c h za s p r a w ą czatu i n t e r n e t o w e g o . N a r z ę d z i a w r o d z a j u N e t m e e t i n g [Microsoft] u m o ż l i w i a j ą uczestn i k o m w s p ó ł p r a c ę g r u p o w ą w e w s p ó ł d z i e l o n e j przestrzeni. W s p ó ł p r a c a ta o p i e r a się n a m e taforze rzeczywistych spotkań: użytkownik „wchodzi" do pokoju, w k t ó r y m m o ż e oglądać t e k s t l u b grafikę b ę d ą c ą p r z e d m i o t e m dyskusji. W s z y s c y u ż y t k o w n i c y są r ó w n o u p r a w n i e n i ,

3.3. Koncepcje organizacyjne projektu

145

choć w danej chwili tylko jeden może modyfikować ów przedmiot dyskusji: zabieranie głosu może mieć charakter anarchiczny (wszyscy wypowiadają się w sposób niekontrolowany) bądź sekwencyjny (użytkownik mający głos nie może być z niego wywłaszczony, dopóki sam nie przekaże go kolejnemu dyskutantowi). Słabą stroną takich „synchronicznych" dyskusji jest trudność koordynowania poczynań poszczególnych dyskutantów. Pisanie na klawiaturze jest trudniejsze (wolniejsze) od swobodnego wypowiadania się (dla typowego, niezaprawionego w czatowaniu użytkownika), poza tym zredukowanie formy wymiany informacji do tekstu pisanego stwarza ryzyko zagubienia wielu informacji, które w rozmowie bezpośredniej można wyrazić na przykład przez gestykulację. Co więcej, drobne nawet zakłócenia w działaniu sieci (internetu) mogą powodować interferencję działań grożącą zupełną utratą koordynacji. I mimo iż dostępna przepustowość internetu zwiększa się nieustannie, a na rynku dostępne są znakomite narzędzia do obsługi internetowych wideokonferencji — takie jak Lotus Sametime [Lotus] — narzędziom tym jeszcze daleko do dojrzałości wymaganej w związku z niezawodnością codziennej pracy. W konsekwencji praca grupowa w rozproszeniu wciąż pozostaje dla projektantów i programistów niebagatelnym wyzwaniem, któremu stawić czoła można jedynie poprzez staranne jej planowanie z odpowiednim wyprzedzeniem. Grupowe opracowywanie procedur wspomagających pracę grupową to kolejne wyzwanie w sytuacji, gdy kontakt bezpośredni oraz niewerbalne kanały komunikacji nie są dostępne. Na przestrzeni ostatnich dwudziestu lat coraz większą popularność zdobywa sobie asynchroniczna komunikacja grupowa. W swej najprostszej formie stanowi ona analogię tablicy, na której uczestnicy mogą zapisywać swoje komentarze, w podziale na wątki dedykowane poszczególnym tematom. Sieć W W W zapewnia ponadto szeroki dostęp do ogromnych repozytoriów dokumentów pochodzących z rozmaitych źródeł. Obecnie w każdym projekcie angażującym więcej niż tylko garstkę uczestników w powszechnym użyciu są rozmaite narzędzia i procedury stanowiące kombinacje forów dyskusyjnych, repozytoriów, kalendarzy i książek adresowych, w rozmaitej skali — od darmowych narzędzi dedykowanych małym projektom do komercyjnych rozwiązań wspomagających olbrzymią organizację. I tak na przykład darmowe Yahoo Groups [Yahoo] stwarza zespołowi platformę wymiany plików i fotografii, planowania zdarzeń, umieszczania ogłoszeń i dyskutowania problemów. BSCW (Basic Support for Cooperative Work) [BSCW] także umożliwia współpracę za pośrednictwem WWW, w postaci wysyłania (uploadu) dokumentów, powiadamiania o zdarzeniach i zarządzania pracą zespołu. Wiki [Wiki] to inny przykład prostej, lecz użytecznej platformy współpracy na bazie WWW; interesującą osobliwością Wiki jest możliwość edycji stron W W W przez każdego użytkownika, wspomaganej automatycznym tworzeniem hiperłączy między poszczególnymi stronami. Wśród narzędzi komercyjnych Sharepoint Team Services [Microsoft] zawiera mechanizmy współpracy dla użytkowników pakietu Microsoft Office. Narzędzia wspierające przepływ działań, takie jak Lotus Notes [Lotus], dostarczają mechanizmy replikacji, obsługi repozytoriów i książek adresowych upubliczniających strukturę organizacyjną. Powtarzane regularnie czynności mogą być automatyzowane. Sformalizowane aktywności, jak asynchroniczny przegląd dokumentu przez wielu uczestników, wspomagane są adnotacjami, którymi opatrywać można dokument na jego drodze poprzez potok użytkowników.

146

Rozdziat

3. • Organizacja projektu i komunikacja

3.5. Aktywności organizacyjne Zajmiemy się teraz aktywnościami programisty dołączającego do organizacji projektu i jego infrastruktury komunikacyjnej. Aktywności te obejmują: • Dołączenie do zespołu (patrz sekcja 3.5.1), • Dołączenie do infrastruktury komunikacyjnej (patrz sekcja 3.5.2), • Udział w zebraniu statusowym zespołu (patrz sekcja 3.5.3), • Organizację przeglądów klienckich i projektowych (patrz sekcja 3.5.4). Opiszemy powyższe aktywności na podstawie przykładowego projektu, skupiając się na realizacji nowego systemu przez wiele zespołów.

3.5.1. Dołączanie do zespołu W fazie definiowania projektu menedżer projektu organizuje zespoły programistów dla poszczególnych podsystemów (zwane krótko zespołami podsystemów), wyodrębnionych w ramach początkowej dekompozycji architektury systemu. Dodatkowo organizowane są zespoły międzyfunkcyjne — między innymi architektoniczny i integracyjny — jako uzupełnienie zespołów zajmujących się konkretnymi podsystemami. Dla każdego systemu ustanawiany jest jego kierownik. Kluczową aktywnością w fazie definiowania projektu staje się więc przyporządkowanie programistów do poszczególnych zespołów. Przyporządkowania tego dokonuje menedżer projektu przy udziale kierowników poszczególnych zespołów, kierując się umiejętnościami i zainteresowaniami programistów. Każdy zespół podsystemu powinien też mianować łącznika na potrzeby komunikacji z zespołami międzyfunkcyjnymi. Menedżer projektu i kierownicy zespołów wyznaczają też zakres niezbędnych szkoleń dla uczestników i całych zespołów. W tabeli 3.12 widoczne jest przykładowe przyporządkowanie ról członkom zespołu baz danych w projekcie OWL.

3.5.2. Dołączanie do infrastruktury komunikacyjnej W celu obsługi komunikacji w ramach zespołów i na forum projektu tworzone są dwa rodzaje forów. Członkowie zespołów subskrybują wszystkie fora projektu i fora należące do ich zespołów. W ramach forów projektu wyróżniamy: •

Ogłoszenia. Na tym forum menedżer informuje o ważnych zdarzeniach (agendach, emisjach i tym podobnych). Jedynie menedżer ma prawo umieszczać ogłoszenia, inni uczestnicy mogą je tylko odczytywać (wraz z niezbędnymi dokumentami) i komentować.



Dyskusja. Forum to przeznaczone jest na globalne żądania wyjaśnień i żądania zmian. Dyskusja na temat żądań ma formę argumentów i alternatywnych rozwiązań, przesyłanych jako komentarze do oryginalnych postów. Każdy uczestnik ma nieograniczoną możliwość umieszczania i czytania postów na tym forum.

3.5. Aktywności organizacyjne

147

Tabela 3.12. Przydział ról dla członków zespołu baz danych projektu OWL

Uczestnik

Role

Kwalifikacje

Potrzeby szkoleniowe

Alice

Kierownik zespołu

Zarządzanie: kierownik zespołu Programowanie: C Zarządzanie konfiguracją

UML Umiejętności komunikacyjne

John

Łącznik z zespołem architektonicznym Implementator

Programowanie: C++ Modelowanie: UML

Java

Mary

Menedżer konfiguracji Implementator

Programowanie: C++, Java Modelowanie: relacje między encjami Bazy danych: relacyjne Zarządzanie konfiguracją

Obiektowe bazy danych Modelowanie w UML

Chris

Implementator

Programowanie: C++, Java Modelowanie: relacje między encjami Bazy danych: obiektowe

Modelowanie w UML

Sam

Główny facilitator Łącznik Tester

Programowanie: C++ Testowanie: biatoskrzynkowe, czarnoskrzynkowe

Inspekcja kodu Java

• Problemy. Forum przeznaczone jest dla niezałatwionych problemów i ich bieżącego statusu. Każdy uczestnik ma nieograniczoną możliwość umieszczania i czytania postów na tym forum. • Dokumenty. Aktualne wersje produktów docelowych (dokumenty analizy wymagań, dokumenty projektu systemu i tym podobne) i innych wewnętrznych dokumentów (takich jak plan zarządzania projektem) umieszczane są na tym forum. Prawo wysyłania postów ma tylko zespół dokumentacyjny, inni uczestnicy mogą tylko odczytywać dokumenty i opatrywać je notatkami. •

Wyposażenie. W ramach tego forum menedżer informuje pozostałych uczestników o wyposażeniu w sprzęt i narzędzia do realizacji projektu i o statusie poszczególnych elementów (własne, dzierżawione). Jedynie menedżer wyposażenia ma prawo do umieszczania postów na tym forum.

Podobnie prezentuje się grupa forów zespołowych, z których każde jednak ograniczone jest tylko do konkretnego zespołu. Generalnie programiści mają nieograniczoną możliwość czytania wszystkich forów zespołowych, lecz posty umieszczać mogą tylko na forum własnego zespołu. Kreowanie forów rozpoczyna się w momencie, gdy dekompozycja systemu staje się już względnie stabilna. Oczywiście, wraz z tworzeniem forów definiowane są także konta dla poszczególnych uczestników.

3.5.3. Udział w zebraniach zespołu Ważną częścią projektu programistycznego są cotygodniowe spotkania zespołowe. Dzięki nim członkowie poszczególnych zespołów partycypują w przeglądach statusowych, burzy mózgów i rozwiązywaniu problemów. Organizację i przebieg takich spotkań opisaliśmy w sekcji 3.4.1.

148

Rozdziat 3. • Organizacja projektu i komunikacja

Szczególnie istotne jest pierwsze spotkanie tego typu: poszczególni członkowie zespołu n a ogół nie znają się jeszcze osobiście i tylko nieliczni ś w i a d o m i są f o r m a l n y c h ról i p r o c e d u r obowiązujących n a takich z e b r a n i a c h . K i e r o w n i c y z e s p o ł ó w m a j ą więc o k a z j ę w p r o w a d z e n i a wszystkich c z ł o n k ó w we w s p o m n i a n e p r o c e d u r y , w y j a ś n i e n i a ich istotności i z m o t y w o w a n i a d o ich przestrzegania. P o n i ż e j w i d o c z n a jest a g e n d a s t w o r z o n a p r z e z k i e r o w n i k a z e s p o ł u w związku z pierwszym spotkaniem.

Gdzie i kiedy Data: 9 stycznia 2010 Początek: 16:30 Koniec: 17:30 Budynek: Wean Hall Pokój: 3420

Role Pierwszy facilitator: Alice Chronometrażysta: Dave Protokolant: Ed

1. Cel: Zaznajomienie z rolami w zarządzaniu projektem średniej skali, w hierarchii dwustopniowej, szczególnie: • • • •

wyjaśnienie różnicy między rolą a osobą przyporządkowanie ról poszczególnym członkom podsumowanie zebrania sformułowanie zagadnień na następne zebranie

2. Status i wymiana informacji [Czas: 40 minut] 2.1. Organizacja spotkań Podstawowe reguły: • • • • • • •

Aktywne słuchanie Aktywne uczestnictwo Punktualne przybycie Brak nieformalnych spotkań Respektowanie agendy Zgodność z wyznaczonymi ramami czasowymi Chęć wypracowywania konsensusu

• Swoboda pod względem kontroli procesów i reguł Role przydzielane w związku z zebraniem: • Główny facilitator • Chronometrażysta • Protokolant • Dokumentalista • Respektowanie agendy [...] 3. Tematy do dyskusji [Czas: 15 minut] 3.1. Książka adresowa zespołu 3.2. Przyporządkowanie ról w związku z zebraniem 3.3. Przyporządkowanie ról w ramach zespołu 4. Podsumowanie [Czas: 5 minut] 4.1. Analiza bieżących akcji i ewentualne przydzielenie nowych zadań 4.2. Komentarze i wnioski

3.5. Aktywności organizacyjne

149

Celem pierwszego zebrania zespołu jest przeszkolenie jego członków na konkretnych przykładach. Temu celowi służyć ma dyskusja na temat procedur, wyjaśnianie ról zespołowych oraz związanych z zebraniami i dokonanie przydziału ról w ramach zespołu. Wyeksponowana zostaje rola facilitatora jako odpowiedzialnego za efektywność zebrania, a nie za narzucanie decyzji. Członkowie zespołu zostają poinformowani, iż każdy z nich może wejść w rolę drugiego facilitatora, czyli zainterweniować w dyskusję, gdy wymknie się ona z ram określonych przez agendę. Uzgadniane są wówczas kluczowe zwroty na określenie standardowych sytuacji — i tak na przykład krótkie stwierdzenie „drugi facilitator" oznacza to samo, co „ta dyskusja wymyka się z ram agendy, wróćmy na właściwe tory", a enigmatyczne „poziom wyżej" jest synonimem stwierdzenia „dyskusja zbytnio się rozdrabnia, ten poziom szczegółowości nie jest teraz istotny, bo większość z obecnych nie jest nim zainteresowana". Uczestnikom zebrania uświadamia się także znaczenie oszczędności czasu, którą można osiągnąć przez przeprowadzanie zebrań w sposób efektywny i skoncentrowany, dzięki czemu uczestnicy prędzej mogą wrócić do swych głównych zadań. Kierownicy zespołów czasami dokonują rotacji ról, przez co członkowie zespołu mają okazję sprawdzić się w każdej roli. Sprzyja to rozwijaniu dodatkowych umiejętności i potęguje wymianę informacji; ma jednak tę wadę, że uczestnicy nie mają okazji dojrzeć w ramach poszczególnych ról i wykonywać swych zadań szczególnie efektywnie. Wczesne przypisanie ról, ich rotacja i sztywne procedury prowadzenia zebrań — to wszystko może powodować pokaźne zamieszanie na początku realizacji projektu, lecz w dłuższej perspektywie okazuje się opłacalną inwestycją. Doskonalone codziennie umiejętności komunikacyjne okażą się nieocenione w sytuacjach kryzysowych, jakie zapewne nastąpią na etapie implementowania projektu. Każdy z zespołów odpowiedzialny jest za wyznaczenie ról związanych z zebraniem i umieszczenie związanej z tym informacji na swym forum Ogłoszenia. Dzień przed zebraniem statusowym facilitator zebrania obowiązany jest umieścić na tym forum szkic agendy, na podstawie tematyki ustalonej w podsumowaniu poprzedniego zebrania, a także na podstawie zespołowego forum Problemy. Ów szkic uzupełniony zostanie przez chronometrażystę o harmonogram czasowy, przesłany na forum Ogłoszenia w formie odpowiedzi. Inni uczestnicy mogą komentować agendę — wraz z jej harmonogramem czasowym — w formie odpowiedzi. Przypisywanie ról w związku z zebraniem, procedury jego przeprowadzania — to wszystko może wydawać się zbędną fatygą, przynajmniej dla niewtajemniczonego programisty. Menedżerowie świadomi są tego faktu, na początku projektu inwestują więc wiele czasu, by przekonać uczestników o czymś wręcz przeciwnym. W pierwszych tygodniach realizacji projektu menedżerowie systematycznie przeglądają agendy i protokoły zebrań, sugerując przy okazji rozmaite sposoby bardziej efektywnego gospodarowania czasem, zarówno facilitatorowi (na przykład kopiowanie z dokumentu do agendy aktywnych akcji przez schowek, zamiast ich ręcznego wpisywania), jak i chronometrażyście (który powinien przeznaczyć więcej czasy na nierozwiązane problemy niż na dyskusję).

3.5.4. Organizacja przeglądów Przeglądy klienckie przeprowadzane są najpierw po wyprodukowaniu dokumentu analizy, a potem po dostarczeniu systemu. Przeglądy projektowe przeprowadzane są w związku z dokumentami projektu systemu oraz szczegółowego projektu obiektów i w związku z testowaniem.

Rozdziat 3. • Organizacja projektu i komunikacja

150

Przeglądy projektowe mogą być związane także z uruchamianiem „na sucho"2 systemu przed przekazaniem go klientowi do testów akceptacyjnych. Terminy wszystkich przeglądów ustalane są przez menedżera projektu w fazie planowania. Przykładowy harmonogram przeglądów przedstawiony jest w tabeli 3.13. Tabela 3.13. Przykładowy harmonogram przeglądów

Przegląd

Data

Produkt docelowy (dostarczany z tygodniowym wyprzedzeniem)

Przegląd kliencki

7. tydzień

Dokument analizy wymagań

Przegląd projektu systemu

9. tydzień

D o k u m e n t projektu systemu

Przegląd projektu obiektów

13. tydzień (dwie sesje)

Dokument projektu obiektów

Przegląd wewnętrzny

16. tydzień

Testy modułowe i integracyjne

„Suchy przebieg" przed testami akceptacyjnymi

17. tydzień

Wszystkie produkty docelowe projektu

Testy akceptacyjne

17. tydzień

Wszystkie produkty docelowe projektu

Menedżerowie definiują także procedury organizowania przeglądów: 1. Produkt docelowy będący przedmiotem przeglądu powinien być dostarczony tydzień'' wcześniej. 2. Krótko po dostarczeniu produktu menedżer publikuje szkic agendy zawierającej listę szczegółów prezentacji dla każdego zespołu. Szkic ten umieszczany jest na forum Ogłoszenia. 3. Kandydaci na prezenterów komentują szkic agendy, precyzując szczegóły prezentacji; menedżer koryguje agendę stosownie do tych komentarzy. 4. W odpowiedzi na zaktualizowaną agendę prezenterzy wysyłają na forum slajdy prezentacyjne; menedżer kolekcjonuje slajdy i odpowiednio uaktualnia agendę. Menedżer ustala także obowiązki i zakres odpowiedzialności protokolanta. W czasie przeglądu protokolant skrupulatnie rejestruje wszystkie pytania i odpowiedzi uczestników. Następnie, jeszcze w dniu przeglądu, wraz z menedżerem łączą swe notatki i generują listę akcji przeznaczonych do wykonania w rezultacie przeglądu oraz listę problemów, które w czasie przeglądu nie mogły zostać rozwiązane. Obie listy opublikowane zostają na forum Ogłoszenia. 2

Uruchamianie na sucho, suchy przebieg (ang. dry run) — mentalne „uruchamianie" programu przez programistę, który analizując krok po kroku wykonywanie kolejnych instrukcji, obserwuje skutki tego wykonywania. W odniesieniu do testów akceptacyjnych termin ten ma jednak trochę inne znaczenie — odnosi się do kompletnego przetestowania systemu, zanim przekazany zostanie klientowi do testowania. Interesująca etymologia tego terminu — w kontekście przymiotnika dry — opisana jest pod adresem http://www.worldwidewords.org/qa/qa-dryl.htm —przyp. tłum. Ów tydzień stanowi pewną rezerwę czasową dla opóźnionych dokumentów: zdarza się bowiem, że zostają one dostarczone nawet na dzień przed planowanym przeglądem, co rodzi dwojakiego rodzaju problemy: po pierwsze, dokumenty te muszą zostać udostępnione wszystkim uczestnikom, po drugie, każdy uczestnik musi mieć czas na zapoznanie się z dokumentem.

3.6. Literatura uzupełniająca

151

Wymóg oficjalnego organizowania przeglądu w ramach infrastruktury komunikacyjnej oraz udostępnienia slajdów prezentacyjnych powoduje zwiększenie ilości uwzględnianej informacji i sprawia, że staje się ona dostępna dla wszystkich uczestników w dłuższej perspektywie.

3.6. Literatura uzupełniająca Teoretycy i praktycy programowania znakomicie zdają sobie sprawę ze znaczenia sprawnej komunikacji w realizowaniu projektów informatycznych. Gdy projekt staje się coraz większy i coraz bardziej złożony, komunikacja staje się czynnikiem krytycznym. Książka autorów B. Curtisa, H. Krasnera i N. Iscoa [Curtis i in., 1988] to jedna z najbardziej znanych publikacji omawiających i klasyfikujących problemy komunikacyjne przy tworzeniu oprogramowania. Z przedstawionych analiz przypadków — 17 dużych projektów rządowych — wynika jednoznacznie, że dokumentacja wcale nie redukuje zapotrzebowania na informację, zwłaszcza we wczesnych fazach projektu, kiedy to uczestnicy definiują podstawowe pojęcia, uzgadniają konwencje reprezentacyjne i kreują nieformalne sieci wymiany informacji. Studium to dowodzi również, iż przeszkody w nieformalnym komunikowaniu się (wynikające na przykład z barier organizacyjnych lub rozproszenia geograficznego) mogą prowadzić do nieporozumień w zakresie konwencji projektowych i racjonalizacji. Obserwacje opisane w pracy R. E. Krauta i L. A. Streeter [Kraut i Streeter, 1995] dowodzą, że oficjalne kanały komunikowania się (zebrania, formalne specyfikacje, przeglądy partnerskie) są użyteczne przy rutynowej koordynacji działań, podczas gdy komunikacja nieformalna (przypadkowe spotkania, rozmowy telefoniczne, burza mózgów) jest niezbędna w przypadku niepewności i nieprzewidzianych problemów, czyli w sytuacjach typowych dla tworzenia oprogramowania. Ze studium tego wynika również, że zapotrzebowanie na nieformalną komunikację wzrasta drastycznie wraz ze wzrostem rozmiaru i złożoności oprogramowania. Jako że spotkania osobiste i wideokonferencje wciąż są podstawowym środkiem komunikowania się uczestników projektu — w celu oznajmiania statusu, identyfikowania konfliktów i rozwiązywania problemów — umiejętności w zakresie organizowania i przeprowadzania takich spotkań są w inżynierii oprogramowania niezbędne, by spotkania te były owocne, a istotna informacja nie była gubiona lub zniekształcana. Mimo to, edukacja w tym zakresie rzadko stanowi ważny element ogólnie pojętej edukacji informatycznej. Wiele użytecznych procedur, również heurystycznych, efektywnego prowadzenia zebrań opisanych jest w pracach M. Doyle'a i D. Strausa [Doyle i Straus, 1982] oraz T. A. Kaysera [Kayser, 1990] — z tej ostatniej zaczerpnęliśmy szablony przykładowych agend i protokołów prezentowanych w niniejszym rozdziale. Książka R. Fishera, W. Ury'ego i B. Pattona [Fisher i in., 1991] zawiera wyjaśnienie podstawowych mechanizmów negocjacyjnych oraz propozycje sposobów unikania impasów w negocjacjach. Z książki J. T. T. Barona i J. Switzera [Barone i Switzer, 1995] dowiedzieć się można, jak projektować kwestionariusze i przeprowadzać wywiady. Dobrym wprowadzeniem w tematykę komunikacji grupowej i wspomagających ją narzędzi jest książka U. W. Borghoffa i J. Schlichtera [Borghoff i Schlichter, 2000] zawierająca wiele użytecznych porad dla twórców i użytkowników oprogramowania w zakresie tej tematyki.

152

Rozdziat 3. • Organizacja projektu i komunikacja

3.7. Ćwiczenia 3.1. Jaka jest różnica między rolą a uczestnikiem? 3.2. Czy jedna rola może być współdzielona między kilku uczestników? Dlaczego tak albo dlaczego nie? 3.3. Jaka jest różnica między klientem a użytkownikiem? 3.4. Do jakich ról przypisałbyś poniższe zadania: • Zmiana interfejsu podsystemu w celu uwzględnienia nowych wymagań, • Zakomunikowanie zmiany interfejsu innym zespołom, • Uwzględnienie zmiany interfejsu w dokumentacji, • Zaprojektowanie zestawu testowego w celu wykrycia ewentualnych usterek wynikających ze zmiany, • Upewnienie się, że zmiana została wykonana terminowo. 3.5. Załóżmy, że jesteś odpowiedzialny za koordynację projektu systemu obsługi wniosków kredytowych dla banku: w jakich rolach obsadziłbyś następujących uczestników, by jak najlepiej wykorzystać ich kwalifikacje? • pracownik banku, odpowiedzialny za obsługę wniosków kredytowych, • menedżer IT banku, kontraktujący system, • niezrzeszony programista, który w przeszłości uczestniczył w realizacji podobnych systemów, • dokumentalista techniczny, • Ty. 3.6. Narysuj diagram aktywności UML reprezentujący zebranie jako proces opisany w sekcji 3.4.1. Zwróć szczególną uwagę na produkty generowane przed zebraniem i po nim, takie jak agenda i protokół. Wykorzystaj partycjonowanie aktywności do wyodrębnienia poszczególnych ról. 3.7. Jaka jest różnica między produktem a pakietem? Kiedy definiowany jest pierwszy, a kiedy drugi? Rozpatrz sytuację, gdy dwóch studentów uczestniczy w tworzeniu podsystemu sortowania listy nazwisk, przy czym każdy student stosuje inny algorytm sortowania. Oczekiwanymi produktami docelowymi są: kod źródłowy, dokumentacja podsystemu orz instrukcja dla innych programistów opisująca sposób integrowania nowego algorytmu sortowania z kodem wywołującym. Podaj przykłady pakietów i produktów związanych z tym projektem. 3.8. Jaka jest różnica między zespołem podsystemu a zespołem międzyfunkcyjnym? 3.9. Skoro przewidywanych jest wiele krytycznych zdarzeń komunikacyjnych (przegląd kliencki, przegląd projektowy, przegląd partnerski), dlaczego istnieje ewentualność komunikacji pozaplanowej (w związku z żądaniami wyjaśnień, żądaniami zmian, rozwiązywaniem problemów)?

153

Bibliografia

3.10. Dla dowolnego dnia w Twoim tygodniu roboczym wymień wszystkie aktywności kwalifikujące się jako zdarzenia komunikacyjne (koleżeńskie spotkania przy kawie, rozmowa z zaprzyjaźnionym studentem, negocjowanie, ogłaszanie, przeglądanie stron WWW). Jaki udział aktywności te mają w ogólnym bilansie Twoich dziennych aktywności? 3.11. Załóżmy, że jesteś członkiem zespołu odpowiedzialnego za podsystem interfejsu użytkownika, jesteś odpowiedzialny za zaimplementowanie formularza kolekcjonującego informację o użytkowniku (nazwisko, imię adres, e-mail, stopień doświadczenia). Informacja zbierana za pomocą formularzy kolekcjonowana jest w bazie danych i wykorzystywana przez podsystem raportowania. Nie jesteś pewien, które pola muszą być wypełnione obowiązkowo, a które opcjonalnie — jak rozwiązałbyś ten problem? 3.12. W związku z weryfikacją planów i rotacją personelu zostałeś przeniesiony z zespołu interfejsu użytkownika do zespołu baz danych. Implementacja projektu jest wysoce zaawansowana. W jakiej obecnie roli sprawdziłbyś się najlepiej, jeżeli wziąć pod uwagę Twoje doświadczenia nabyte przy implementowaniu interfejsów użytkownika? 3.13. Załóżmy, że programiści tworzą system na platformie uniksowej, a dokumentaliści tworzą dokumenty na macintoshu. Klient wymaga dokumentacji czytelnej również w systemie Windows. Programiści tworzą dokumentację projektową przy użyciu Adobe FrameMaker. Zespół dokumentacyjny wykorzystuje MS Word do pisania dokumentacji użytkowej. Klient przysyła poprawki, lecz nie żąda ich uwzględnienia w dokumentach już dostarczonych. Jak należy zorganizować przepływ informacji (dotyczący narzędzi, formatów i tym podobnych) między programistami, dokumentalistami i klientem, by zminimalizować duplikowanie plików i jednocześnie respektować wybór preferowanej platformy przez każdego z uczestników? 3.14. Jakie zaproponowałbyś zmiany w organizacji i infrastrukturze komunikacyjnej dla projektu będącego sukcesorem Ariane 5 w obliczu katastrofy rakiety Ariane 501, opisanej na początku tego rozdziału?

Bibliografia [Barone i Switzer, 1995]

J. T. T. Barone, J. Switzer Interviewing: Art and Skill, Allyn & Bacon, 1995.

[Borghoff i Schlichter, 2000]

U. W. Borghoff, J. Schlichter Computer Supported Cooperative Work: An Introduction into Distributed Applications, Springer-Verlag. 2000.

[BSCW]

Basic Support for Cooperative Work,

[Curtis i in. 1988]

B. Curtis, H. Krasner, N. Iscoe A field study of the software design process for large systems, „Communications of the ACM", t. 31, nr 11, 1268 - 87, 1988.

[Doyle i Straus, 1982]

M. Doyle, D. Straus How to make meetings

http://bscw.gmd.de.

work, T h e Berkeley

Publishing Group, NY, 1982. [Fagan, 1976]

M. E. Fagan Design and code inspections to reduce errors in program development, „IBM System Journal", t. 15, nr 3, str. 182 - 211, 1976.

[Fisher i in., 1991]

R. Fisher, W. Ury, B. Patton Getting to Yes: Negotiating Without Giving In, wyd. drugie, Penguin Books, 1991.

Agreement

Rozdziat 3. • Organizacja projektu i komunikacja

154 [Gantt, 1910]

H. L. Gantt, Work, wages, and profits, „The Engineering Magazine", New York, 1910.

[Grudin, 1988]

J. Grudin „Why CSCW applications fail: Problems in design and evaluation of organization interfaces", Proceedings CSCW'88, Portland, OR, 1988.

[Kayser, 1990]

T. A. Kayser Mining Group Gold, Serif, El Segundo, CA, 1990.

[Kraut i Streeter, 1995]

R. E. Kraut, L. A. Streeter Coordination in software development, „Communications of the ACM", t. 38, nr 3, marzec 1995.

[Lions, 1996]

J.-L. Lions ARIANE 5 Flight 501 Failure: Report by the Inquiry http://www.esrin.esa.it/htdocs/tidc/Press/Press96/ariane5rep.htrnl,

[Lotus]

Lotus

[Microsoft]

Microsoft

[OWL, 1996]

OWL Project Documentation, School of Computer Science, Carnegie Mellon University, Pittsburgh, PA, 1996.

[Wiki]

Wiki,

[Yahoo]

Yahoo,

Board, 1996.

http://www.lotus.corn/. http://www.microsoft.com/.

http:llc2.com/cgilwikKWikiWikiWeh. http://groups.yahoo.com/.

4.1.

Wstęp: przykłady problemów z użytecznością

158

4.2.

O zbieraniu wymagań ogólnie

159

4.3.

Koncepcje zbierania wymagań

161

4.3.1. 4.3.2. 4.3.3. 4.3.4. 4.3.5.

161 162 164 165

Wymagania funkcyjne Wymagania pozafunkcyjne Kompletność, spójność, jednoznaczność i poprawność Realizm, weryfikowalność i identyfikowalność Inżynieria pierwotna, inżynieria wtórna i inżynieria interfejsu

165

4.4.

Aktywności związane ze zbieraniem wymagań 4.4.1. Identyfikacja aktorów 4.4.2. Identyfikacja scenariuszy 4.4.3. Identyfikacja przypadków użycia 4.4.4. Doskonalenie przypadków użycia 4.4.5. Identyfikacja relacji między aktorami a przypadkami użycia 4.4.6. Początkowa identyfikacja obiektów modelu analitycznego 4.4.7. Identyfikacja wymagań pozafunkcyjnych

166 167 169 171 173 176 179 182

4.5.

Zarządzanie zbieraniem wymagań 4.5.1. Negocjowanie specyfikacji z klientem: metoda Joint Application Design 4.5.2. Zarządzanie identyfikowalnością 4.5.3. Dokumentowanie zbierania wymagań

183

Analiza 4.6.1. 4.6.2. 4.6.3. 4.6.4. 4.6.5.

przypadku — system ARENA Wstępna deklaracja problemu Identyfikacja aktorów i scenariuszy Identyfikacja przypadków użycia Doskonalenie przypadków użycia i identyfikacja relacji Identyfikacja wymagań pozafunkcyjnych

190 190 192 195 198 204

4.6.6.

Wnioski

204

4.7.

Literatura uzupełniająca

205

4.8.

Ćwiczenia

207

Bibliografia

208

4.6.

185 187 188

4 Zbieranie wymagań

Powszechnym błędem popełnianym przy projektowaniu rzeczy idiotoodpomych jest niedocenianie geniuszu kompletnych idiotów. — Douglas Adams, Mostly Harmless

^ P o d pojęciem wymagania rozumiemy konkretną cechę lub ograniczenie, jakimi musi charakteryzować się system, by mógł być zaakceptowany przez klienta. Definiowanie wymagań pod adresem tworzonego systemu jest przedmiotem inżynierii wymagań. Inżynieria ta obejmuje dwie główne aktywności: zbieranie wymagań, uwieńczone specyfikacją systemu zrozumiałą dla klienta, oraz ich analizę, prowadzącą do stworzenia modelu, jednoznacznie interpretowalnego przez programistów. Pierwsza z wymienionych aktywności wydaje się być większym wyzwaniem, wymaga bowiem współpracy wielu grup uczestników o zróżnicowanych kwalifikacjach: z jednej strony, klienta i użytkowników, wyspecjalizowanych w zakresie dziedziny aplikacyjnej, lecz być może mających nikłe pojęcie o programowaniu, z drugiej natomiast, programistów znakomicie znających swój warsztat, lecz być może kompletnie niezorientowanych w realiach codziennej pracy użytkowników powstającego systemu. Zasypywaniu tej przepaści mają służyć rozmaite scenariusze i przypadki użycia. Scenariusz opisuje konkretny przykład wykorzystania systemu jako serię jego interakcji z użytkownikiem; przypadek użycia jest natomiast abstrakcją reprezentującą określoną klasę scenariuszy. Zarówno scenariusze, jak i przypadki użycia zapisywane są w języku potocznym (naturalnym), czyli w formie zrozumiałej przez użytkowników. W tym rozdziale zajmować się będziemy zbieraniem wymagań w oparciu o scenariusze. Programiści dokonują zbierania wymagań w drodze obserwacji działań użytkowników i uzupełniają tę wiedzę za pomocą wywiadów: naturalistyczne początkowo scenariusze poddawane są pewnym uogólnieniom, odzwierciedlającym pożądane aspekty funkcjonalności powstającego systemu. Klient i użytkownicy dokonują weryfikacji wizji systemu przez analizę uogólnionych scenariuszy, a także na podstawie testowania niewielkich prototypów systemu dostarczanych przez programistów. W miarę jak definicja systemu stabilizuje się i dojrzewa, programiści i klient dokonują formalnego aktu porozumienia w kwestii wymagań dla systemu, w postaci specyfikacji wymagań obejmującej wymagania funkcyjne i pozafunkcyjne, przypadki użycia oraz scenariusze.

158

Rozdział 4. • Zbieranie wymagań

4.1. Wstęp: przykłady problemów z użytecznością Mile czy stopy?1 W pewnym eksperymencie geofizycznym wiązka promienia laserowego miała odbić się od zwierciadła promu kosmicznego tak, by trafić w wierzchołek góry. Personel obsługujący eksperyment wprowadził do systemu wysokość góry jako 10 023, przyjmując, że obowiązującymi w systemie jednostkami są stopy. Jednostkami tymi były jednak mile i wiązka laserowa, zamiast w wierzchołek góry, trafiła w nieboskłon na (prawie 6000 razy większą) wysokość 10 023 mil.

Separator dziesiętny a separator tysięcy W USA część dziesiętna liczby oddzielana jest kropką od części całkowitej, natomiast trzycyfrowe grupy części całkowitej rozdzielane są przecinkami. W Polsce jest dokładnie na odwrót — część dziesiętna oddzielana jest za pomocą przecinka, zaś kropka pełni rolę separatora tysięcy. Załóżmy teraz, iż amerykański oferent zamierza sporządzić ofertę dla polskiego klienta, z cenami wyrażonymi w dolarach: której z opisanych konwencji powinien użyć, by uniknąć nieporozumień?

Standardowe wzorce W edytorze tekstu Emacs sekwencja klawiszy Cłrl+X Ctrl+C powoduje wyjście z programu; jeśli jednak w edytowanym pliku istnieją niezapisane jeszcze zmiany, wyświetlane jest pytanie Save file ...? (y om) („Czy zapisać plik...?"). Naciśnięcie klawisza y spowoduje zapisanie zmian i wyjście z edytora, naciśnięcie klawisza n spowoduje wyjście z edytora bez zapisywania zmian. Wiele edytorów hołduje jednak odmiennej konwencji, wyświetlając w takiej sytuacji zapytanie w rodzaju: „Czy naprawdę chcesz wyjść z programu?", przy zachowaniu znaczenia klawiszy^ i n jako (odpowiednio) akceptacji i sprzeciwu. Dotychczasowy użytkownik Emacsa, przesiadając się na edytor tej drugiej kategorii, może się wiele razy pomylić (z różnym skutkiem), zanim przyzwyczai się do nowej konwencji.

Zbieranie wymagań opiera się na komunikacji między programistami, klientem i użytkownikami, w celu wypracowania spójnej definicji nowego systemu. Załamanie tej komunikacji i niedostateczne zrozumienie czyichś koncepcji prowadzi nieuchronnie w stronę systemu, który w najlepszym razie będzie niewygodny w użytkowaniu, a prawdopodobnie nie będzie w ogóle użyteczny. Błędy popełnione na etapie zbierania wymagań są bardzo kosztowne w eliminowaniu, zwykle bowiem dają znać o sobie bardzo późno, nawet już po dostarczeniu systemu klientowi. Błędy te dotyczyć mogą zarówno braków w funkcjonalności systemu, opacznego zrozumienia wymagań funkcjonalnych, jak i interfejsu użytkownika, który dla osoby pracującej z nim okaże się mylący lub wręcz bezużyteczny. Z tego względu metody zbierania wymagań zmierzają do usprawnienia wspomnianej komunikacji. Programiści, obserwując środowisko pracy użytkowników, konstruują model dziedziny aplikacyjnej, po czym formułują jego reprezentację w postaci scenariuszy i przypadków użycia, czyli w formie zrozumiałej dla klienta i użytkowników. Dodatkowo użytkownicy otrzymują proste prototypy swego interfejsu: potencjalny użytkownik ma do dyspozycji pełną gamę opcji menu, przycisków i innych, jakie mają pojawić się w przyszłym systemie. Choć ich klikanie nie daje na razie żadnego efektu — wszak nie zaimplementowano jeszcze ani kawałka funkcjonalności — ich zestaw, układ, hierarchia i tym podobne stają się podstawą do formułowania pierwszych ocen i spostrzeżeń użytkowników. Zbieranie wymagań w ogólnym ujęciu, relatywnym do innych aktywności, opiszemy w sekcji 4.2. W sekcji 4.3 zdefiniujemy podstawowe pojęcia używane w tym rozdziale. W sekcji ' Przykłady zaczerpnięto z książek J. Nielsena [Nielsen, 1993] i P. G. N e u m a n n a [Neumann, 1995],

4.2. O zbieraniu wymagań ogólnie

159

4.4 zajmiemy się aktywnościami wchodzącymi w skład zbierania wymagań, zaś sekcję 4.5 poświęcimy aktywnościom menedżerskim związanym ze zbieraniem wymagań. Przedmiotem sekcji 4.6 będzie analiza przypadku, jakim jest system ARENA.

4.2. O zbieraniu wymagań ogólnie Zbieranie wymagań koncentruje się na opisywaniu przeznaczenia systemu. Klient, programiści i użytkownicy identyfikują istniejące problemy i definiują system, którego celem jest rozwiązywanie tych problemów. Konkretnym wyrazem tego definiowania jest specyfikacja wymagań, stanowiąca kontrakt między klientem a programistami. Strukturalizacja i formalizacja tej specyfikacji na etapie analizy (którą zajmiemy się w rozdziale 5. „Analiza wymagań") prowadzi do stworzenia modelu analitycznego (którego przykład widzimy na rysunku 4.1). Model analityczny reprezentuje dokładnie tę samą informację, co specyfikacja wymagań, ma jednak inną postać, wynikającą z odmiennego języka i użycia odmiennej notacji: specyfikacja wymagań spisana jest w języku naturalnym, podczas gdy model analityczny wyrażony jest w notacji formalnej (lub półformalnej). Różnica ta wynika z przeznaczenia obu produktów: specyfikacja wymagań jest płaszczyzną komunikacji między programistami z jednej strony a klientem i użytkownikami z drugiej; model analityczny stanowi natomiast wspólny punkt odniesienia dla programistów. Oba produkty są jednak modelem systemu w tym sensie, że stanowią próbę dokładnego reprezentowania jego zewnętrznych aspektów; w obliczu obu tych modeli zbieranie wymagań i ich analiza odbywają się równolegle i iteracyjnie.

Rysunek 4.1. Produkty zbierania wymagań i ich analizy

Zbieranie wymagań i ich analiza koncentrują się jedynie na spojrzeniu na system z perspektywy jego użytkownika. Częścią wymagań są więc takie aspekty systemu jak jego elementy funkcjonalne, jego interakcja z użytkownikami, kategoria błędów, na które działanie systemu powinno być odporne, czy uwarunkowania środowiska, w którym system będzie funkcjonował. Inne jego aspekty, niewidoczne bezpośrednio dla użytkownika — struktura, technologie implementacyjne, projekt systemu, projekt obiektów — nie są przedmiotem zainteresowania w fazie zbierania i analizy wymagań.

Rozdział 4. • Zbieranie wymagań

160

Zbieranie wymagań obejmuje następujące aktywności: • Identyfikowanie aktorów, obejmuje determinowanie przez programistów różnych typów przyszłych użytkowników systemu. • Identyfikowanie scenariuszy, odzwierciedlających typowe elementy funkcjonalności oferowanej przez przyszły system. Programiści obserwują działania potencjalnych użytkowników i na tej podstawie formułują scenariusze, które służą im zarówno do komunikowania się ze wspomnianymi użytkownikami, jak i przyczyniają się do coraz lepszego rozumienia dziedziny aplikacyjnej. • Identyfikowanie przypadków użycia — gdy tylko programiści i użytkownicy uzgodnią pewien zbiór realistycznych scenariuszy, scenariusze te są przez programistów generalizowane w postaci przypadków użycia reprezentujących w sposób kompletny funkcjonalność przyszłego systemu. Podczas gdy scenariusz jest konkretnym przykładem korzystania z systemu, przypadek użycia jest abstrakcją opisującą wszelkie możliwe przykłady tego typu. Dysponując kompletem przypadków użycia, programiści określają zakres funkcjonalny systemu. • Doskonalenie przypadków użycia — ma na celu dopracowywanie specyfikacji wymagali pod względem szczegółowości przypadków użycia, jak też identyfikowanie wszelkich możliwych sytuacji wyjątkowych, jakie mogą się zdarzyć w związku z działaniem systemu. • Identyfikowanie relacji między przypadkami użycia, wiążącymi ich model w jedną całość dzięki wyeksponowaniu wspólnej funkcjonalności. Daje to gwarancję, że specyfikacja wymagań jest spójna. • Identyfikowanie wymagań pozafunkcyjnych, czyli istotnych dla użytkownika, lecz niemanifestujących się bezpośrednio w warstwie funkcjonalnej systemu. Należą do nich między innymi wymagania dotyczące wydajności systemu, jego dokumentacji, zapotrzebowania na zasoby, jakości i bezpieczeństwa. Podczas zbierania wymagań programiści wykorzystują wiele różnych źródeł informacji, między innymi dostarczane przez klienta dokumenty odzwierciedlające fragmenty dziedziny aplikacyjnej, dokumentację techniczną i użytkową systemów, które będą zastąpione przez nowy system oraz — co najważniejsze — informacje na bieżąco przekazywane przez klienta 1 użytkowników, z którymi współpraca w fazie zbierania wymagań i ich analizy jest bardziej intensywna niż w dalszych stadiach projektu. W tym rozdziale skupimy się na dwóch metodach zbierania informacji, podejmowania decyzji wspólnie z klientem i użytkownikami oraz zarządzania zależnościami, jakie występują między wymaganiami a innymi artefaktami. Oto te metody. • Joint Application Design (JAD) („połączony projekt2 aplikacji") — istotą tej metody jest budowanie konsensusu między programistami, klientem i użytkownikami w celu wspólnego wypracowania specyfikacji wymagań. • Identyfikowałność — metoda ta sprowadza się do rejestrowania, strukturalizowania, łączenia i grupowania zarówno zależności między samymi wymaganiami, jak i ich relacji do innych produktów projektu. 2

Słowo „projekt" (design) w wyjaśnieniu akronimu JAD może być tu cokolwiek mylące, występuje bowiem w zupełnie innym znaczeniu niż to, w jakim używane jest w rozdziałach poświęconych projektowi systemu i projektowi obiektów.

4.3. Koncepcje zbierania wymagań

161

4.3. Koncepcje zbierania wymagań W sekcji 4.3 opiszemy podstawowe pojęcia i koncepcje zbierania wymagań, wykorzystywane w tym rozdziale, między innymi: • Wymagania funkcyjne (patrz sekcja 4.3.1), • Wymagania pozafunkcyjne (patrz sekcja 4.3.2), • Kompletność, spójność, jednoznaczność i poprawność (patrz sekcja 4.3.3), • Realizm, weryfikowalność i identyfikowalność (patrz sekcja 4.3.4), • Inżynierię pierwotną, inżynierię wtórną i inżynierię interfejsu (patrz sekcja 4.3.5). Aktywnościami związanymi ze zbieraniem wymagań zajmiemy się w sekcji 4.4.

4.3.1. Wymagania funkcyjne Wymagania funkcyjne odzwierciedlają interakcje między systemem a jego środowiskiem, w oderwaniu od implementacji tegoż systemu. Pod pojęciem „środowiska" rozumiemy tu zarówno użytkowników wspomnianego systemu, jak i inne systemy wchodzące w (potencjalną) interakcję z przedmiotowym systemem. Poniżej przedstawiono przykład wymagań funkcyjnych dotyczących inteligentnego („satelitarnego") zegarka SatWatch, którego funkcjonowanie (a szczególnie — dostosowywanie do bieżącej strefy czasowej) obywa się bez interwencji użytkownika. SatWatch jest naręcznym zegarkiem wyświetlającym aktualny czas, zgodnie ze strefą czasową właściwą dla bieżącej lokalizacji. W celu określenia tej lokalizacji (i uzyskania związanych z nią struktur danych) SatWatch kontaktuje się z satelitami GPS. SatWatch gwarantuje dokładny pomiar czasu bez potrzeby jakichkolwiek zabiegów dostosowujących ze strony użytkownika. Gdy użytkownik przekracza geograficzną lub polityczną strefę czasową, następuje automatyczne uwzględnienie tego faktu, w związku z czym SatWatch nie posiada żadnych przycisków regulacyjnych dostępnych dla użytkownika. ]ako że działanie zegarka SatWatch uzależnione jest od systemu GPS, obarczone jest tymi samymi mankamentami i ograniczeniami, co generalnie wszystkie urządzenia korzystające z tego systemu, między innymi niemożnością określenia lokalizacji w pewnych porach dnia w rejonie górzystym. W czasie, gdy GPS nie jest dostępny, SatWatch przyjmuje, że nie została przekroczona strefa czasowa; gdy tylko uda m u się nawiązać łączność z GPS, wskazanie czasu jest korygowane do właściwej wartości. Wyświetlacz zegarka SatWatch ma strukturę dwuwierszową: w pierwszym wierszu wyświetlany jest czas (godziny, minuty, sekundy i informacja o bieżącej strefie czasowej), w drugim data (dzień tygodnia, dzień miesiąca, miesiąc i rok). Technologia, w jakiej wykonany jest wyświedacz, gwarantuje widoczność wyświetlanych danych nawet w niesprzyjających warunkach oświetleniowych. Na wypadek, gdyby zmieniły się polityczne granice stref czasowych, istnieje możliwość uaktualnienia programowania zegarka, za pomocą urządzenia WebifyWatch (dostarczanego wraz z zegarkiem) i komputera podłączonego do internetu.

Zauważmy, że powyższe wymagania koncentrują się jedynie na możliwych interakcjach zegarka SatWatch ze światem zewnętrznym (właścicielem, systemem GPS i urządzeniem WebifyWatch). Nie ma w nich mowy o jakichkolwiek detalach implementacyjnych (procesorze, języku programowania czy konkretnej technologii wyświetlacza).

162

Rozdział 4. • Zbieranie wymagań

4.3.2. Wymagania pozafunkcyjne Wymagania pozafunkcyjne opisują te aspekty systemu, które nie są bezpośrednio związane z jego funkcjonalnością. Mogą dotyczyć rozmaitych aspektów systemu, od jego użyteczności do wydajności. Model3 FURPS+ wykorzystywany w ramach jednolitego procesu (Unified Process)4, a opisany przez I. Jacobsona, G. Boocha i J. Rumbaugha [Jacobson i in., 1999], wyróżnia następujące kategorie wymagań pozafunkcyjnych, związane kolejno z: • Użytecznością rozumianą jako łatwość uczenia się przez użytkownika obsługi systemu (lub jego komponentu), przygotowywania jego danych wejściowych i interpretowania wyników. Wymagania tej kategorii mogą dotyczyć konwencji związanych z interfejsem użytkownika, zakresu pomocy online, stopnia szczegółowości dokumentacji i tym podobnych. Często zdarza się, że klient, w celu uproszczenia obsługi systemu, żąda od programistów zgodności interfejsu GUI z określoną konwencją kolorystyczną, czcionkami czy firmowym logo. • Niezawodnością oznaczającą zdolność systemu lub jego komponentu do spełniania wymaganych funkcji w określonych warunkach i w określonym przedziale czasu. Elementem niezawodności może być na przykład średni czas pracy między awariami (MTBF — mean time between failures), wykrywanie specyficznych awarii czy skuteczne odpieranie ataków na zabezpieczenia. Ostatnio kategorię tę zastąpiła nowa, zwana pewnością działania (ang. dependability), która oznacza, że usługi świadczone przez system mogą być uważane za godne zaufania; składają się na nią: niezawodność, solidność (rozumiana jako zdolność do poprawnego radzenia sobie z nieprawidłowymi danymi wejściowymi lub uciążliwymi warunkami środowiska, na przykład dużym obciążeniem) i bezpieczeństwo (będące miarą braku katastroficznych konsekwencji dla środowiska). • Wydajnością, czyli mierzalnymi aspektami działania systemu w postaci między innymi czasu reakcji systemu na akcję użytkownika, przepustowości (mierzonej jako ilość pracy wykonywanej w jednostce czasu), stopnia dostępności (operatywności) systemu lub komponentu, gdy zachodzi potrzeba jego użycia oraz dokładności. • Wspieralności wyrażającej się łatwością wprowadzania zmian do systemu po jego wdrożeniu, czyli między innymi w związku z pojawianiem się nowych koncepcji w dziedzinie aplikacyjnej (adaptowalność), usuwaniem usterek, implementowaniem nowych technologii (łatwość utrzymania) czy poszerzaniem o nowe konwencje regionalne (internacjonalizacją) — języki, jednostki miar, formaty liczb i dat i tym podobne. Standard ISO 9126 dotyczący jakości oprogramowania [ISO Std. 9126], podobny do modelu FURPS+, zastępuje tę kategorię dwiema innymi: łatwością utrzymania i przenośnością rozumianą jako łatwość transferowania systemu lub jego komponentu między różnymi platformami sprzętowymi i programowymi.

3

FURPS+ jest akronimem utworzonym z pierwszych liter słów Functionality (funkcjonalność), Usability (użyteczność) Reliability (niezawodność), Performance (wydajność) i Supportability (wspierałność); znak „+" symbolizuje pozostałe kategorie. Model FURPS został zaproponowany po raz pierwszy w pracy Grady'ego [Grady, 1992], Jego definicję na potrzeby tego rozdziału zaczerpnęliśmy z opisu standardu [IEEE Std. 610.12-1990],

4

Patrz sekcja 15.4.2 — przyp.

tłum.

4.3. Koncepcje zbierania wymagań

163

Model FURPS+ definiuje dodatkowe kategorie wymagań, generalnie cytowane jako przykłady wymagań pozafunkcyjnych. Oto one. • Wymagania implementacyjne dotyczące użycia przy implementowaniu systemu określonych narzędzi, języków programowania, platform sprzętowych. • Wymagania dotyczące interfejsu narzucone przez istniejące systemy zewnętrzne lub standardy formatów wymiany danych. • Wymagania operacyjne związane z administrowaniem i zarządzaniem ustawieniami systemu. • Wymagania pakietowe określające formę (nośnik) dostarczenia i instalowania systemu. • Wymagania prawne dotyczące licencjonowania, certyfikatów i generalnie zgodności z obowiązującymi przepisami. Przykładem wymagań tej kategorii może być wymóg zgodności oprogramowania opracowywanego na potrzeby rządu USA z sekcją 508 ustawy Rehabilitation Act z 1973 roku, zobowiązującej instytucje publiczne do zapewnienia dostępności ich serwisów informacyjnych i usług elektronicznych, ze szczególnym uwzględnieniem potrzeb osób niepełnosprawnych. Wymagania pozafunkcyjne, plasujące się w kategoriach URPS, określane są wspólnym mianem wymagań jakościowych. Wymagania związane z implementacją, eksploatacją, pakietowaniem i uwarunkowaniami prawnymi nazywane są ograniczeniami lub pseudowymaganiami. Wymagania dotyczące budżetu i harmonogramu nie są zwykle traktowane jak wymagania pozafunkcyjne, odnoszą się bowiem do wewnętrznych szczegółów projektu (patrz rozdział 14. „Zarządzanie projektem"). Poniżej widoczne są wymagania pozafunkcyjne dla zegarka SatWatch. Wymagania jakościowe dla zegarka SatWatch ® Każdy, kto potrafi odczytywać czas i datę z zegarka cyfrowego oraz interpretować międzynarodowe skróty stref czasowych, powinien poradzić sobie z obsługą zegarka SatWatch bez dodatkowej instrukcji (wymaganie dotyczące użyteczności). •

Jako że SatWatch nie ma zewnętrznych przycisków, nie powinny występować błędy oprogramowania wymagające konieczności jego resetowania (wymaganie dotyczące niezawodności).



W przypadku przerwania łączności z systemem GPS SatWatch powinien powrócić do poprawnego wyświetlania czasu najdalej po upływie 5 minut od wznowienia tej łączności (wymaganie dotyczące wydajności).

® SatWatch powinien odmierzać czas z dokładnością do 1/100 sekundy przez okres co najmniej 5 lat (wymaganie dotyczące wydajności). ® SatWatch powinien poprawnie wyświetlać czas we wszystkich 24 strefach czasowych (wymaganie dotyczące wydajności). «

SatWatch powinien mieć możliwość aktualizowania danych wewnętrznych za pomocą interfejsu USB urządzenia WebifyWatch (wymaganie dotyczące wspieralności).

Ograniczenia dla zegarka SatWatch •

Całe oprogramowanie związane z zegarkiem SatWatch powinno być napisane w języku Java, zgodnie z aktualną polityką firmy (wymaganie implementacyjne).



SatWatch powinien być w pełni zgodny z fizycznym, elektrycznym i programowym interfejsem definiowanym przez WebifyWatch API 2.0 (wymaganie dotyczące interfejsu).

164

Rozdział 4. • Zbieranie wymagań

4.3.3. Kompletność, spójność, jednoznaczność i poprawność Wymagania nie są kwestią jednorazowego sformułowania, są nieustannie modyfikowane, a ich rozumienie przez programistów weryfikowane jest przez klienta i użytkowników. Weryfikacja ta ma na celu zapewnienie, że zarówno klient, jak i programiści działają zgodnie ze specyfikacją wymagań. Przedmiotem tej weryfikacji jest sprawdzanie, czy wspomniana specyfikacja jest kompletna, spójna, jednoznaczna i poprawna. Specyfikacja jest kompletna, jeśli uwzględnia wszystkie możliwe scenariusze, włącznie z sytuacjami wyjątkowymi — czyli gdy w modelu wymagań uwzględnione są wszystkie aspekty systemu. Specyfikacja jest spójna, jeżeli nie zawiera wewnętrznych sprzeczności. Jednoznaczność specyfikacji oznacza, że definiuje ona dokładnie jeden system — czyli że nie jest możliwe jej interpretowanie na kilka różnych sposobów. Wreszcie, specyfikacja jest poprawna, jeśli odzwierciedla dokładnie ten system, którego potrzebuje klient i który programiści zamierzają tworzyć (czyli model wymagań dokładnie reprezentuje wszystkie aspekty systemu w sposób satysfakcjonujący i klienta, i programistów). Znaczenie wymienionych cech zilustrowaliśmy praktycznymi przykładami w tabeli 4.1. Tabela 4.1. Weryfikowalne cechy specyfikacji

Cecha

Przykład niezgodności

Rozwiązanie

Kompletność

Specyfikacja zegarka SatWatch nie określa jego zachowania w sytuacji, gdy przez dłuższy czas znajduje się na pograniczu dwóch stref czasowych.

Dodanie kolejnego wymagania funkcyjnego, zgodnie z którym synchronizacja zegarka SatWatch z bieżącą strefą czasową odbywa się nie częściej niż co 5 minut.

Wymaganiu, by oprogramowanie zegarka nie zawierało żadnych defektów, nie towarzyszy wymaganie związanie z możliwością aktualizowania oprogramowania.

Zrewidowanie jednego z powodujących konflikt wymagań m o d e l u (na przykład inne s f o r m u ł o w a n i e wymagania dotyczącego braku jakichkolwiek usterek w oprogramowaniu, gdyż w obecnej postaci wymaganie to jest i tak nieweryfikowalne).

Jednoznaczność — żadne wymaganie nie może być interpretowane na dwa sprzeczne sposoby.

Specyfikacja zegarka SatWatch określa zasady synchronizacji jego wskazań z bieżącą strefą czasową. Czy SatWatch powinien także uwzględniać automatycznie czas letni?

Sprecyzowanie wieloznacznego w y m a g a n i a przez decyzję, czy SatWatch ma automatycznie uwzględniać czas letni, czy też nie.

Poprawność

Wymaganie, by SatWatch wyświetlał poprawnie czas w każdej z 24 stref, bazuje na błędnym założeniu, że stref czasowych jest właśnie tyle. Jest to nieprawda, gdyż jest ich trochę więcej: na niektórych obszarach (między innymi w Indiach) strefy czasowe wyznaczane są w podziale półgodzinnym, nie godzinnym.

Usunięcie ze specyfikacji błędnego założenia, iż na kuli ziemskiej obowiązują 24 strefy czasowe w podziale godzinnym.

— wszystkie istotne cechy muszą być opisywane przez wymagania.

Spójność — żadne dwa wymagania nie mogą przeczyć sobie nawzajem.

— wymaganie opisuje cechy systemu i środowiska istotne dla klienta i programistów, nie włączając żadnych cech niepożądanych.

4.3. Koncepcje zbierania wymagań

165

Poprawność i kompletność to dwie cechy, którym najłatwiej uchybić przy tworzeniu specyfikacji, szczególnie wtedy, gdy opisuje ona nieistniejący jeszcze system. Z tego względu specyfikacja, stanowiąca kontrakt między klientem a programistami, musi być szczególnie uważnie kontrolowana przez obie strony. Dodatkowo te części systemu, które stwarzają szczególne ryzyko w tym względzie, powinny być odzwierciedlone w formie prototypów lub symulacji, by użytkownicy mogli sobie wyrobić lepsze wyobrażenie na ich temat. W konkretnym przypadku zegarka SatWatch może to być na przykład jego makieta skonstruowana na bazie tradycyjnego zegarka — przy okazji użytkownik może na przykład uświadomić sobie, że potrzebuje wyświetlania daty w obu formatach (do wyboru): europejskim i amerykańskim.

4.3.4. Realizm, weryfikowalność i identyfikowalność Trzema innymi pożądanymi cechami specyfikacji wymagań są: realizm, weryfikowalność i identyfikowalność. Specyfikacja jest realistyczna, jeśli istnieje fizyczna możliwość zaimplementowania opisywanego przez nią systemu, przy uwzględnieniu wszystkich wyartykułowanych ograniczeń. Weryfikowalność specyfikacji to możliwość zaprojektowania testów, które konfrontować będą zawarte w niej wymagania z faktycznymi własnościami zaimplementowanego systemu. I tak na przykład trudno zweryfikować wymaganie (średnio) stuletniego okresu bezawaryjnej pracy zegarka SatWatch (nawet przy założeniu, iż samo wymaganie jest realistyczne samo w sobie). Oto inne przykłady nieweryfikowalnych wymagań. • Produkt musi posiadać dobry interfejs użytkownika — znaczenie słowa „dobry" nie jest precyzyjnie zdefiniowane. • Produkt musi być wolny od wszelkich defektów — zweryfikowanie tego wymagania wymagałoby użycia nadmiernej ilości zasobów. •

W większości przypadków produkt musi reagować na akcję użytkownika 1 sekundy — co to znaczy „w większości przypadków"?

w ciągu

Specyfikacja jest identyfikowalna, jeśli każde z wymagań może być wyraźnie odniesione do określonego podzbioru funkcji systemu, i vice versa — gdy każda funkcja systemu daje się zidentyfikować w aspekcie zbioru wymagań przemawiających za jej istnieniem. Identyfikowalność obejmuje także możliwość śledzenia zależności między poszczególnymi wymaganiami, funkcjami systemu i pośrednimi artefaktami projektowymi — komponentami systemu, klasami, metodami i atrybutami obiektów. Staje się ona cechą krytyczną w przypadku planowania testów i oceniania konsekwencji zmian: dzięki niej tester może określać stopień pokrycia testowego kodu, czyli identyfikować te wymagania, które faktycznie podlegają przetestowaniu, zaś programiści są w stanie określać wpływ dokonywanych zmian na poszczególne komponenty systemu.

4.3.5. Inżynieria pierwotna, inżynieria wtórna i inżynieria interfejsu Aktywności wchodzące w skład zbierania wymagań mogą być sklasyfikowane w trzech następujących kategoriach. Zależnie od relacji tworzonego systemu do rzeczywistości, wysiłki zmierzające do jego zbudowania można zaklasyfikować bądź jako inżynierię pierwotną (w języku angielskim nazywaną obrazowo „dziewiczą" — greenfield engineering), polegającą

Rozdział 4. • Zbieranie wymagań

166

na tworzeniu systemu „od zera", gdyż nie istnieje żaden jego pierwowzór, oraz inżynierię wtórną (nazywaną często niezbyt zgrabnie „reinżynierią", od ang. reingeenering), której istotą jest ponowne zaimplementowanie lub przeprojektowanie istniejącego systemu, w związku z nowymi potrzebami biznesowymi lub pojawieniem się nowych technologii. Piszą o tym M. Hammer i J. Champty [Hammer i Champy, 1993]. Opisywany wcześniej projekt zegarka SatWatch jest przykładem inżynierii pierwotnej. Przedmiotem inżynierii interfejsu jest interfejs użytkownika istniejącego systemu — poza ponownym zaprojektowaniem lub ponownym zaimplementowaniem tego interfejsu, wszystkie inne cechy systemu pozostają bez zmian. Sytuacja taka występuje wówczas, gdy rachunek ekonomiczny przemawia raczej za użytkowaniem dotychczasowego systemu niż za tworzeniem zupełnie nowego. W przypadku dwóch pierwszych kategorii programiści starają się zebrać jak najwięcej informacji z zakresu dziedziny aplikacyjnej, którą to informację znaleźć mogą między innymi w podręcznikach systemu, dokumentacji dla nowych pracowników, słownikach, ściągawkach i innych notatkach użytkowników, no i — oczywiście — w czasie bezpośrednich wywiadów z klientem i użytkownikami. Jakkolwiek wywiady te są z założenia nieocenionym narzędziem, nie pomagają jednak w uzyskiwaniu żądanej informacji, gdy zadaje się niewłaściwe pytania; dlatego też programiści powinni uzyskać najpierw jak najwięcej wiadomości, zanim zastosują owo podejście bezpośrednie. Zajmiemy się teraz szczegółowo aktywnościami związanymi ze zbieraniem wymagań.

4.4. Aktywności związane ze zbieraniem wymagań Aktywności wchodzące w skład procesu zbierania wymagań stanowią odzwierciedlenie deklaracji problemu (patrz rozdział 3. „Organizacja projektu i komunikacja">) w specyfikacji wymagań, w ramach której deklaracja ta reprezentowana jest w postaci zbioru aktorów, scenariuszy i przypadków użycia (patrz rozdział 2. „Modelowanie w języku UML"). W kolejnych sekcjach opiszemy heurystyki i metody zbierania wymagań od użytkowników oraz modelowanie systemu w świetle tych koncepcji, a mianowicie: • Identyfikację aktorów (patrz sekcja 4.4.1), • Identyfikację scenariuszy (patrz sekcja 4.4.2), • Identyfikację przypadków użycia (patrz sekcja 4.4.3), • Doskonalenie przypadków użycia (patrz sekcja 4.4.4), • Identyfikację relacji między aktorami a przypadkami użycia (patrz sekcja 4.4.5), • Identyfikację obiektów modelu analitycznego (patrz sekcja 4.4.6), • Identyfikację wymagań pozafunkcyjnych (patrz sekcja 4.4.7). Opisywane tu metody zaadaptowane zostały na podstawie OOSE [Jacobson i in., 1992], jednolitego procesu wytwarzania oprogramowania (Unified Software Development Process, [Jacobson i in., 1999]) i projektowania sterowanego odpowiedzialnością (responsibility-driven design, [Wirfs-Brock i in., 1990]).

4.4. Aktywności związane ze zbieraniem wymagań

167

4.4.1. Identyfikacja aktorów Aktorzy są reprezentantami zewnętrznych encji, przejawiających interakcje z projektowanym systemem. Aktorem może być człowiek łub system zewnętrzny. W przykładzie z zegarkiem SatWatch aktorami są: jego właściciel, satelity GPS i urządzenie WebifyWatch (patrz rysunek 4.2) — wszystkie te encje wymieniają informację z zegarkiem, przy czym każdy z tych aktów wymiany cechuje się specyficzną interakcją: właściciel zegarka nosi go na przegubie i odczytuje czas oraz datę, satelity GPS podają zegarkowi niezbędne informacje, zaś urządzenie WebifyWatch umożliwia mu załadowanie uaktualnionych danych. Aktorzy uosabiają zatem określone klasy funkcjonalności systemu.

Rysunek 4.2. Aktorzy związani z systemem SatWatch. WatchOwrer wykorzystuje wskazania zegarka, zegarek współdziała z GPS-em w celu ustalenia bieżącej lokalizacji, zaś urządzenie Webi fyWatch umożliwia aktualizację wewnętrznych danych związanych z polityką zmiany czasu (na przykład zmianą początkowej i końcowej daty czasu letniego)

Rozpatrzmy przykład bardziej skomplikowany — cytowany wcześniej system FRIEND, umożliwiający zarządzanie wypadkami i sytuacjami kryzysowymi na podstawie informacji rozproszonej ([Bruegge i in., 1994]). W interakcję z tym systemem uwikłanych jest wielu aktorów: Fi el dOf f i cer, reprezentujący funkcjonariusza policji lub strażaka reagującego na incydent, Dispatcher, reprezentujący dyspozytora odpowiedzialnego za przyjmowanie zgłoszeń pod numerem 911 i przydział zasobów do obsługi incydentu. System FRIEND współdziała z tymi aktorami, realizując śledzenie incydentów, zasobów i planów zadań. System ten zapewnia także dostęp do informacji zawartych w różnych bazach danych, takich jak ewidencja materiałów niebezpiecznych czy listy kontrolne procedur operacyjnych. Współdziałanie systemu z aktorami Fi el dOff i cer i Di spatcher odbywa się w oparciu o różne interfejsy: pierwszy z nich posługuje się przenośnym komputerem, drugi — stacją roboczą (patrz rysunek 4.3).

Rysunek 4.3. Aktorzy współdziałający z systemem FRIEND. Aktorzy nie tylko wykorzystują różne elementy funkcjonalności systemu, lecz także posługują się różnymi komputerami

Rozdział 4. • Zbieranie wymagań

168

Aktorów należy utożsamiać raczej z abstrakcyjnymi rolami niż konkretnymi osobami: ta sama osoba może przecież wcielać się w różnym czasie w role Fi el dOf f i cer i Di spatcher, lecz przypisane do tych ról funkcje są diametralnie odmienne, więc role te modelowane są w postaci dwóch różnych aktorów. Identyfikacja aktorów jest pierwszym krokiem w procesie zbierania wymagań, jest bowiem niezbędna zarówno do wyznaczenia granic tworzonego systemu, jak i do określenia wszystkich perspektyw, z jakich spoglądać powinni na ten system jego programiści. Gdy system tworzony jest dla istniejącej organizacji (przedsiębiorstwa), większość aktorów istnieje już a priori, jeszcze przed rozpoczęciem prac nad systemem — aktorzy ci uosabiają po prostu role pełnione w firmie przez poszczególnych pracowników. W początkowej fazie identyfikowania aktorów zdarza się, że trudno odróżnić aktorów od obiektów systemu, przykładowo podsystem bazy danych może być w pewnych okolicznościach uważany za aktora, w pewnych zaś za obiekt systemu. Gdy jednak wyznaczone zostaną granice systemu, rozróżnienie to jest jednoznaczne: aktorzy zlokalizowani są na zewnętrz tych granic, zaś podsystemy i obiekty systemu wewnątrz nich. Baza danych, utrzymywana w formie odrębnego, niezależnego systemu jest więc aktorem, z perspektywy korzystającego z niej przedmiotowego systemu. W związku z identyfikowaniem aktorów programiści zadawać mogą różne pytania, przykładowe zostały wyszczególnione w ramce. Przykładowe pytania w związku z identyfikowaniem aktorów: •

Jakie grupy użytkowników w swej pracy wykorzystywać będą system?



Które z tych grup korzystać będą z zasadniczych funkcji systemu?



Które z tych grup zajmować się będą funkcjami wtórnymi, takimi jak administrowanie i utrzymanie?



Z jakim zewnętrznym sprzętem lub oprogramowaniem współpracować ma tworzony system?

W opisywanym już systemie FRIEND powyższe pytania prowadzą do powstania listy potencjalnych aktorów, którymi są między innymi strażak, funkcjonariusz policji, dyspozytor, śledczy, komendant, gubernator, ewidencja materiałów niebezpiecznych sporządzona przez EPA\ administrator systemu i tak dalej. Tę kandydacką listę należy jednak skonsolidować do mniejszych rozmiarów, tak by jej pozycje odzwierciedlały różne punkty spojrzenia na system ze strony jego użytkowników. I tak na przykład zarówno strażak, jak i oficer policji w identyczny sposób korzystają z systemu FRIEND, dodatkowo w danej chwili uwikłani są w jeden, konkretny incydent. Dla odróżnienia — dyspozytor odpowiedzialny jest być może za zarządzanie kilkoma incydentami w danej chwili, wymaga więc dostępu do większej ilościowo i jakościowo informacji. Z kolei komendant czy gubernator niekoniecznie muszą współdziałać z systemem bezpośrednio, korzystając w zamian z usług wprawnego operatora. Gdy tylko dokonana zostanie identyfikacja aktorów, kolejnym krokiem w procesie zbierania wymagań jest określenie zakresu funkcjonalności dostępnego dla każdego z aktorów. Tę informację uzyskuje się za pośrednictwem scenariuszy oraz sformalizowanych przypadków użycia. 3

EPA — Environmental USA — przyp, tłum.

Protection Agency, Agencja Ochrony Środowiska, federalna organizacja rządu

4.4. Aktywności związane ze zbieraniem wymagań

169

4.4.2. Identyfikacja scenariuszy Zgodnie z tym, co w swojej książce napisał J. M. Carrol [Carroll, 1995], scenariusz jest „narracyjnym opisem działań i wrażeń ludzi próbujących korzystać z programu lub systemu komputerowego". Scenariusz jest skoncentrowanym, nieformalnym opisem pojedynczej cechy systemu z perspektywy konkretnego aktora. Scenariusze nie są zamiennikami przypadków użycia, ponieważ dotyczą konkretnych zdarzeń spośród całej gamy możliwych zdarzeń danego typu; zwiększają jednak jakość zbierania wymagań jako narzędzie zrozumiałe dla klienta i użytkowników. W tabeli 4.2 widzimy scenariusz, zgodnie z którym funkcjonariusz policji raportuje zaistnienie pożaru, a dyspozytor zapoczątkowuje reakcję na to głoszenie. Zauważmy, że opisywane jest konkretne zdarzenie, a nie ogólna sytuacja zgłaszania zauważonego pożaru. Tabela 4.2. Scenariusz warehouseOnFi re jako instancja przypadku użycia Report Emergency Nazwa

warehouseOnFire

Instancje aktorów uczestniczących

bob, alice:FieldOfficer ,iohn:Di spatcher

Przepływ zdarzeń

1. Bob, patrolując główną ulicę, zauważa przez o k n o s a m o c h o d u dym wydobywający się z hurtowni. Jego partnerka Alice aktywuje funkcję „Raport Emergency" na laptopie systemu FRIEND. 2. Alice wprowadza do formularza adres budynku, jego orientacyjną lokalizację (północno-zachodni zakątek) i rozmiar zagrożenia. Dodatkowo żąda kilku jednostek pomocy paramedycznej, gdyż okolica pożaru jest dość gęsto zaludniona. Wysyła formularz i czeka na potwierdzenie. 3. Dyspozytor (Di s p a t c h e r ) John zostaje z a a l a r m o w a n y przez sygnał dźwiękowy na swojej stacji roboczej. Odczytuje informację od Alice i wysyła potwierdzenie raportu. Aktywuje jednostkę gaśniczą oraz dwie jednostki paramedyczne i wysyła do Alice informację o szacunkowym czasie oczekiwania na przybycie (ETA — Estimated Arrival Time). 4. Alice odczytuje potwierdzenie i informację o ETA.

Scenariusze nie powinny opisywać podejmowania decyzji — do tego potrzebna jest para scenariuszy: jeden odpowiadający decyzji na „tak", drugi odpowiadający decyzji na „nie". W procesie zbierania wymagań — a także w innych stadiach projektu — scenariusze mogą mieć rozmaite zastosowania. W pracy J. M. Carrola [Carroll, 1995] wyróżniono kilka typowych grup scenariuszy. • Scenariusz bieżący opisuje aktualną sytuację. Przykładowo w ramach inżynierii wtórnej programiści obserwują zachowanie użytkowników aktualnie eksploatowanego systemu, opisując ich akcje w formie scenariuszy. Scenariusze tej kategorii są później weryfikowane przez użytkowników pod kątem poprawności i dokładności. • Scenariusz wizjonerski opisuje fragment systemu istniejącego dopiero w zamyśle projektantów. Taki scenariusz może być zarówno punktem w przestrzeni modelowania programistów, którzy w ten sposób doskonalą swe wyobrażenie na temat przyszłego systemu, jak i medium komunikacji z użytkownikami. Scenariusze wizjonerskie mogą być uważane za tanie odmiany prototypów.

Rozdział 4. • Zbieranie wymagań

170

• Scenariusz ewaluacyjny opisuje zadania użytkowników, pod kątem których oceniany jest system. Wspólne wypracowywanie takich scenariuszy przez programistów i użytkowników wpływa także na udoskonalenie definicji funkcjonalności testowanej przez te scenariusze. • Scenariusz treningowy to przewodnik służący przystosowaniu nowych użytkowników do pracy z systemem. Krok po kroku instruuje on użytkownika, jak ma wykonywać poszczególne zadania. Większość scenariuszy początkowo jest niekompletna i ma charakter ogólnikowy, tak jak przykładowy scenariusz warehouseOnFi re. W identyfikowaniu potencjalnych scenariuszy pomocne mogą okazać się następujące pytania. Przykładowe pytania pomocne w identyfikowaniu scenariuszy •

Spełniania jakich zadań oczekuje aktor od systemu?



Dostęp do jakiej informacji jest dla aktora niezbędny? Kto generuje tę informację? Czy może być ona modyfikowana? Jeśli tak, to kiedy i przez kogo?



O jakich zdarzeniach zewnętrznych system powinien być informowany przez aktora? Jak często? Kiedy?



O jakich zdarzeniach aktor powinien być informowany przez system? Z jakim opóźnieniem?

Odpowiedzi na te pytania znajdują programiści w dokumentach związanych z dziedziną aplikacyjną: w podręczniku użytkownika poprzedniego systemu, podręcznikach procedur, standardach organizacji, notatkach i ściągawkach użytkowników, a także w drodze wywiadu z klientem i użytkownikami. Programiści powinni zapisywać scenariusze, używając terminologii z zakresu dziedziny aplikacyjnej zamiast swej własnej terminologii. W miarę jak zyskują oni coraz to lepsze wyobrażenie o dziedzinie aplikacyjnej i możliwościach obecnych technologii, doskonalą scenariusze w sposób iteracyjny i przyrostowy, wyposażając je w coraz większą ilość szczegółów. Sporządzenie makiety interfejsu użytkownika często pomaga odnajdywać luki w specyfikacji i tym samym budować bardziej adekwatny obraz systemu. W przykładowym systemie FRIEND możemy wyróżnić cztery scenariusze odzwierciedlające typy zadań, jakich wspierania oczekuje się od tego systemu: • warehouseOnFi re (patrz tabela 4.2): zauważono pożar hurtowni; dwoje funkcjonariuszy przybywa na miejsce i żąda niezbędnych zasobów; •

fenderBender 6 : na autostradzie zdarzył się wypadek samochodowy, na szczęście bez ofiar, policjanci dokumentują szczegóły wypadku i organizują ruch w jego otoczeniu, podczas gdy uszkodzone samochody usuwane są na pobocze;

• catlnATree: na wysokie drzewo wdrapał się kot i ma kłopoty z zejściem, wezwano straż pożarną, zdarzenie nie należało do kategorii pilnych, zatem wóz strażacki przyjechał dopiero po dłuższej chwili; ponieważ w międzyczasie niecierpliwy (i pewnie nieświadomy meandrów systemu FRI END) kot niefortunnie zeskoczył z drzewa, łamiąc sobie nogę, konieczne jest wezwanie ambulansu weterynaryjnego; 6

Z ang. fender-bender

to „stłuczka" — p>'zyp- tłum.

4.4. Aktywności związane ze zbieraniem wymagań

171

• earthQuake: nieprzewidywańe wstrząsy sejsmiczne poważnie uszkodziły budynki i drogi, wywołując serię incydentów wtórnych, wymagających uruchomienia planu awaryjnego na szczeblu administracji stanowej, powiadomiony został gubernator, uszkodzenie dróg utrudnia właściwe reagowanie na incydenty. W trakcie identyfikowania aktorów i możliwych scenariuszy programiści coraz lepiej rozumieją dziedzinę aplikacyjną i zyskują wyraźniejszą wizję przyszłego systemu. Gdy sporządzony zostanie już stabilny zbiór aktorów i scenariuszy, następuje kolejny krok — formalizacja scenariuszy w postaci przypadków użycia.

4.4.3. Identyfikacja przypadków użycia Scenariusz jest instancją przypadku użycia — przypadek użycia reprezentuje wszystkie możliwe scenariusze związane z określonym aspektem funkcjonalności. Przypadek użycia inicjowany jest przez konkretnego aktora, po czym może oddziaływać także na innych aktorów. Przypadek użycia reprezentuje kompletny przepływ zdarzeń w systemie w tym sensie, że opisuje serię powiązanych interakcji wywołanych wspomnianą inicjacją. W tabeli 4.3 pokazany jest przypadek użycia ReporEmergency, którego instancją jest scenariusz warehouseOnFi re z tabeli 4.2. Aktor Fi el dOf f i cer inicjuje ten przypadek użycia, aktywując funkcję „Report Emergency" systemu FRI END. Przypadek użycia kończy się, gdy aktor Fi el dOf f i cer odbiera potwierdzenie od dyspozytora. Z opisu poszczególnych kroków przepływu zdarzeń wynika jednoznacznie, kto inicjuje każdy krok: kroki 1. i 3. inicjowane są przez aktora, kroki 2. i 4. — przez system. Przypadek użycia ma charakter ogólny i może obejmować cała gamę scenariuszy — przykładowo nie tylko scenariusz warehouseOnFi re, ale także i scenariusz fenderBender może być uważany za instancję przypadku użycia ReportEmergency. Podobnie jak scenariusze, tak i przypadki użycia mogą być sporządzane na różnym stopniu szczegółowości. Wysokopoziomowe przypadki użycia, będące pierwszym efektem generalizowania scenariuszy, pomagają programistom w definiowaniu zakresu systemu. Programiści opatrują przypadki użycia nazwami, przypisują im aktorów inicjujących i sporządzają wysokopoziomowy opis podobny do tego z tabeli 4.3. Nazwa przypadku użycia powinna mieć formę czasownika odzwierciedlającego czynność, którą aktor inicjujący usiłuje wykonać: czasownik ReportEmergency („zgłoś niebezpieczeństwo") oznacza po prostu, że aktor Field "-•Officer przystępuje do zgłoszenia incydentu systemowi FRIEND (i pośrednio aktorowi Di spatcher). Zauważmy, że przypadek użycia nie został nazwany RecordEmergency („zarejestruj niebezpieczeństwo"), gdyż wówczas odzwierciedlałby działanie systemu, nie aktora. Równie nietrafna byłaby w tej roli nazwa Attempt to Report an Emergency („spróbuj wysłać raport o niebezpieczeństwie"), gdyż odzwierciedlałaby konkretną aktywność, a nie cel przypadku użycia. Przypisanie poszczególnym przypadkom użycia aktorów inicjujących pomaga programistom lepiej zrozumieć role poszczególnych użytkowników. Często w ramach poszukiwania aktorów inicjujących programiści identyfikują istnienie nowych aktorów, o których uprzednio zapomnieli. Opis przypadku użycia obejmuje zwykle cztery pola. Opisując warunki wstępne i warunki końcowe, programiści zdają sobie sprawę z tego, co jest konieczne do uruchomienia danego przypadku użycia i jakie konsekwencje uruchomienie to wywołuje w środowisku systemu. Może się zdarzyć, iż analizując warunki wstępne i końcowe poszczególnych przypadków użycia, programiści identyfikują konieczność definiowania nowych przypadków użycia — i tak na

172

Rozdział 4. • Zbieranie wymagań

Tabela 4.3. Przykładowy przypadek użycia — ReportEmergency. W opisie przepływu zdarzeń lewa kolumna identyfikuje akcje podejmowane przez aktorów, prawa — reakcje systemu Nazwa przypadku Aktorzy

użycia

uczestniczący

Przepływ

zdarzeń

ReportEmergency •

F i e l d O f f i c e r — inicjuje przypadek użycia



D i s p a t c h e r — komunikuje się z przypadkiem użycia

1. F i e l d O f f i c e r aktywuje funkcję „Report Emergency" na swym terminalu. 2. System FRIEND wyświetla w odpowiedzi stosowny formula ,rz. 3. Fi el dOffi cer vvypelnia formularz, określając typ zdarzenia, stopień zagrożenia, jeg o lokalizację i krótki opis sytuacji. F i e l d O f f i c e r określa także m ożliwe sposoby pierwszej reakcji na zagrożenie. Po wypełnieniu fiarmularza Fi el dOf f i c e r wysyła go do systemu. 4. System FRIEND powiadamia dyspozytora (Di s p a t c h e r ) o zdarzęiniu. 5. Dispatcher an£dizuje informacje zawarte w formularzu, po czym tworzy nowy obliekt Incident wbazie, realizując przypadek użycia Openlncident.[ )i spatcher określa sposób reakcji i zatwierdza raport. 6. System FRIEND i n f o r m u j e aktora F i e l d O f f i c e r o zatwiierdzeniu r a p o r t u i p r z e k a z u j e m u i n f o r m a c j ę od dysp ozytora.

Warunki

wstępne

F i e l d O f f i c e r jest zalogowany do systemu FRIEND

Warunki

końcowe



F i e l d O f f i c e r otrzymał potwierdzenie przesłania raportu oraz wiadomość od dyspozytora ( D i s p a t c h e r )

lub

Wymagania

jakościowe



F i e l d O f f i c e r otrzymał od systemu wyjaśnienie, dlaczego transakcja nie in o ż e zostać zrealizowana.



Raport aktora 17 i el dOffi c e r zostaje potwierdzony w czasie nie dłuższym n:iż 30 sekund.



F i e l d O f f i c e r otrzymuje odpowiedź nie później niż 30 sekund po wysłaniu jej przez dyspozytora.

p r z y k ł a d g d y p r z y p a d e k użycia o b e j m u j e u r u c h o m i e n i e p l a n u a w a r y j n e g o w związku z trzęs i e n i e m z i e m i , s p e c y f i k a c j a w y m a g a ń p o w i n n a t a k ż e z a w i e r a ć p r z y p a d e k użycia o d z w i e r ciedlający to u r u c h o m i e n i e . Z kolei s p o r z ą d z a n i e opisu przepływu

zdarzeń

umożliwia p r o g r a -

m i s t o m i k l i e n t o m d y s k u t o w a n i e interakcji m i ę d z y a k t o r a m i a s y s t e m e m , co w rezultacie daje p o d j ę c i e r o z m a i t y c h decyzji w kwestii g r a n i c m i ę d z y s y s t e m e m a a k t o r a m i , czyli p o d z i a ł u akcji n a w y k o n y w a n e p r z e z a k t o r ó w i b ę d ą c e o d p o w i e d z i ą s y s t e m u n a p o c z y n a n i a t y c h ż e . W r e s z c i e , towarzyszące p r z y p a d k o w i użycia wymagania

jakościowe

d a j ą p r o g r a m i s t o m okazję

d o lepszego p o z n a n i a w y m a g a ń z w i ą z a n y c h z o k r e ś l o n y m a s p e k t e m f u n k c j o n a l n o ś c i . M i m o iż w p r a k t y c e n i c n i e stoi n a p r z e s z k o d z i e r o z s z e r z a n i u w s p o m n i a n e g o o p i s u o k o l e j n e pola, z w i ą z a n e m i ę d z y i n n y m i ze z d a r z e n i a m i w y j ą t k o w y m i , r o l a m i czy n i e z m i e n n i k a m i , w książce tej o g r a n i c z y m y się tylko d o czterech w y m i e n i o n y c h — w a r u n k ó w w s t ę p n y c h , w a r u n k ó w k o ń c o w y c h , p r z e p ł y w u z d a r z e ń i w y m a g a ń jakościowych — j a k o określających n a j b a r d z i e j istotne a s p e k t y p r z y p a d k u użycia.

4.4. Aktywności związane ze zbieraniem wymagań

173

Pisanie przypadków użycia jest umiejętnością, którą analityk doskonali poprzez praktykę. Doskonalenie to wiąże się z wypracowywaniem własnego stylu, co — niestety — prowadzi do zróżnicowania stylów poszczególnych analityków i trudności w tworzeniu spójnych specyfikacji wymagań. Analitycy mogą uniknąć tego zagrożenia, posiłkując się określonymi wytycznymi dotyczącymi pisania przypadków użycia. Poniżej widzimy przykład takich wytycznych, zaczerpnięty z pracy A. Cockburna [Cockburn, 2001], zaś w tabeli 4.4 widoczny jest przykład przypadku użycia wyraźnie odbiegającego od tych wytycznych. Prosty przewodnik dla autorów przypadków użycia •

Nazwa przypadku użycia powinna być czasownikiem odzwierciedlającym czynność, jaką zamierza wykonać użytkownik (na przykład ReportEmergency, Openlnci dent).



Aktorzy powinni być opatrywani nazwami w f o r m i e rzeczowników (Fi el dOf f i c e r , Dispatcher, Victim).



Granice systemu powinny być wyraźnie zaznaczone, poprzez rozróżnienie kroków wykonywanych przez aktorów i przez system (w tabeli 4.3 poszczególne kroki zostały w tym celu podzielone między dwie kolumny).



Opis poszczególnych kroków powinien być formułowany z użyciem formy czynnej czasownika, co wyraźnie określa aktora odpowiedzialnego za każdy z kroków 7 .



W opisie kolejnych kroków powinny być widoczne związki przyczynowo-skutkowe.



Przypadek użycia powinien opisywać kompletną transakcję użytkownika (przypadek użycia ReportEmergency zawiera opis wszystkich kroków, począwszy od wysłania r a p o r t u , aż do otrzymania potwierdzenia).



Sytuacje wyjątkowe powinny być opisywane oddzielnie.



Przypadek użycia powinien abstrahować od interfejsu użytkownika, który lepiej interpretowany jest w formie makiety, powinien natomiast skupiać się na czynnościach użytkownika. W przykładzie z tabeli 4.3 jest mowa o funkcji „Raportuj zdarzenie" w oderwaniu od menu, przycisków czy poleceń związanych z tą funkcją.



Rozmiar przypadku użycia nie powinien przekraczać dwóch, trzech stron. Obszerniejsze przypadki użycia kwalifikują się do dekompozycji za pomocą relacji rozszerzania i zawierania (którymi zajmiemy się w sekcji 4.4.5).

Przykład z tabeli 4.3 jest wystarczającą ilustracją tego, jak system FRIEND obsługuje powiadamianie o incydentach i jak współpracują z nim poszczególni użytkownicy: jest on jednak zbyt mało szczegółowy, by mógł stanowić element specyfikacji wymagań. Zobaczmy więc, w jaki sposób możliwe jest doskonalenie przypadków użycia i wzbogacanie ich w niezbędne szczegóły.

4.4.4. Doskonalenie przypadków użycia W tabeli 4.5 widoczna jest udoskonalona wersja przypadku użycia ReportEmergency. Zawiera ona dodatkowe szczegóły związane między innymi z rodzajami zdarzeń, jakie rozróżnia system FRIEND, i sposobem potwierdzania zgłoszenia. 7

Także w języku polskim strona czynna („Policjant wzywa karetkę") udostępnia więcej informacji niż strona bierna („Wezwana została karetka"), czy użycie trybu zwrotnego („Zjawia się karetka") — tylko w pierwszym przypadku jest mowa o policjancie — przyp. tłum.

174

Rozdział 4. • Zbieranie wymagań

Tabela 4.4. Przykład nieprawidłowo sporządzonego przypadku użycia (wraz z komentarzem)

Cecha przypadku użycia

Opis

Co jest źle?

Nazwa przypadku

Accident

Nieodpowiednia nazwa: jaki jest cel działań użytkownika?

Aktor

użycia

FieldOfficer

inicjujący

Przepływ

1. F i e l d O f f i c e r raportuje niebezpieczeństwo.

zdarzeń

2. Fi el d O f f i c e r o t r z y m u j e potwierdzenie przyjęcia zgłoszenia.

Brak związku

3. Wezwana zostaje karetka

Strona bierna: kto wezwał karetkę?

skutkowego:

przyczynowojaka i czyja akcja

spowodowała, że Fi el dOf f i cer otrzymał

potwierdzenie?

Niekompletna transakcja: co robi 4. Gdy karetka przybywa na miejsce zdarzenia, Di spatcher Di spatcher po wysłaniu karetki jest informowany o tym fakcie. na miejsce zdarzenia? Tabela 4.5. Uszczegółowiony przypadek użycia Report Emergency; dodane szczegóły wyróżnione są kursywą Nazwa przypadku użycia

ReportEmergency

Aktorzy



Fi el dOf f i cer — inicjuje przypadek użycia.



Di s p a t c h e r — komunikuje się z przypadkiem użycia.

Przepływ

uczestniczący

zdarzeń

1. Fi el dOf f i cer aktywuje funkcję „Raportuj zdarzenie" na swym terminalu. 2. System FRI END wyświetla w odpowiedzi stosowny formularz. Formularz ten zawiera pola określające między innymi rodzaj niebezpieczeństwa (ogólne, pożar, komunikacyjne), lokalizację zdarzenia, jego opis, żądanie zasobów i udział materiałów niebezpiecznych. 3. Fi el dOf f i c e r wypełnia formularz z obowiązkowymi polami reprezentującymi typ zdarzenia i jego krótki opis. Fi el dOf f i cer określa także możliwe sposoby pierwszej reakcji na zagrożenie i wymagane do tej reakcji zasoby. Po wypełnieniu f o r m u l a r z a F i e l d O f f i c e r wysyła go do systemu. 4. System FRI END p o w i a d a m i a dyspozytora (Di s p a t c h e r ) o zdarzeniu poprzez wyświetlenie okna ze stosownym komunikatem i sygnał dźwiękowy. 5. Di spatcher analizuje informacje zawarte w formularzu, po czym tworzy nowy obiekt Inci dent w bazie, realizując przypadek użycia Openlnci dent. Cała informacja zawarta we wspomnianym formularzu automatycznie włączana jest do obiektu Incident. Di spatcher określa sposób reakcji, dokonując przydziału odpowiednich zasobów (reprezentowanego przez przypadek użycia Al T o c a t e R e s o u r c e s j i zatwierdza raport, wysyłając stosowny komunikat do aktora Fi el dOf f i cer. 6. System FRI END informuje aktora Fi el dOf f i cer o zatwierdzeniu raportu i przekazuje m u informację od dyspozytora.

Warunki

wstępne

Warunki

końcowe

Wymagania jakościowe jakościowe

[...] [...] [...] [...]

4.4. Aktywności związane ze zbieraniem wymagań

175

Definiowanie funkcjonalności systemu za pomocą scenariuszy i przypadków użycia ma na celu przede wszystkim określenie wymagań i ich weryfikację przez klienta we wczesnym stadium rozwoju systemu. Gdy zaczyna się projektowanie systemu i jego implementowanie, wtedy modyfikowanie wymagań i dodawanie nowych, nieprzewidzianych wcześniej elementów funkcjonalnych jest znacznie trudniejsze i bardziej kosztowne. I mimo iż nie sposób uchronić się całkowicie przed perspektywą zmiany wymagań w późniejszych fazach projektu, to jednak należy się starać, by załatwić wszelkie sprawy związane z wymaganiami na etapie ich zbierania. Większość przypadków użycia nie jest tworzona w jednorazowym akcie: niektóre są wiele razy przepisywane, niektóre kompletnie zarzucane. Aby zaoszczędzić jak najwięcej czasu, wykorzystuje się scenariusze i makiety interfejsu użytkownika. Podczas pisania scenariuszy i przypadków użycia mogą być pomocne następujące heurystyki. Heurystyki pisania scenariuszy i przypadków użycia ® Wykorzystuj scenariusze do komunikacji z użytkownikami w celu weryfikowania funkcjonalności systemu. •

Najpierw dopracuj pojedynczy scenariusz, by zrozumieć założenia użytkownika pod adresem nowego systemu. Być może użytkownik ma już praktykę w użytkowaniu podobnych systemów, więc przyjęcie specyficznych konwencji w zakresie interfejsu użytkownika może uczynić system bardziej użytecznym.



Następnie zdefiniuj wiele nie za bardzo szczegółowych scenariuszy w celu wypracowania (w drodze negocjacji z użytkownikiem) zakresu funkcjonalnego systemu.



Wykorzystuj makiety jedynie jako wizualne narzędzia pomocnicze; projektowanie interfejsu użytkownika, jako odrębne zadanie, rozpocznie się wówczas, gdy kształt funkcjonalności systemu stanie się wystarczająco stabilny.



Prezentuj użytkownikowi wiele różnych wariantów rozwiązań, nie wymagając wyboru konkretnego. Przyczyni się to do poszerzenia jego horyzontu myślowego w zakresie oczekiwań pod adresem przyszłego systemu, Ciebie natomiast skłoni do myślenia poza utartymi schematami.

® Gdy dany aspekt funkcjonalny, reprezentowany przez konkretny scenariusz, stanie się dla Ciebie już wystarczająco zrozumiały, wzbogacaj go o nowe szczegóły, ale pod kontrolą użytkownika.

Celem przedstawionych aktywności jest zapewnienie kompletności i poprawności specyfikacji wymagań. Programiści identyfikują elementy funkcjonalne, dla których nie opracowano scenariuszy, i dokumentują te elementy, modyfikując istniejące przypadki użycia i definiując nowe. Programiści uwzględniają także sytuacje rzadko występujące i obsługę zdarzeń wyjątkowych z perspektywy aktorów. Gdy wstępna identyfikacja przypadków użycia i aktorów zaowocuje wyznaczeniem granic systemu, przypadki użycia są w dalszym ciągu rozbudowywane o kolejne szczegóły dotyczące cech prezentowanych przez system i jego ograniczeń. Szczególnie następujące aspekty przypadków użycia, początkowo ignorowane, stają się teraz przedmiotem szczegółowych definicji: • elementy podlegające przetwarzaniu przez system — w tabeli 4.5 są to wyróżnione kursywą szczegóły formularza raportowania i typy incydentów,

176

Rozdział 4. • Zbieranie wymagań

• niskopoziomowe elementy interakcji między systemem a aktorem — w tabeli 4.5 jest to opis generowania potwierdzenia i dysponowania zasobami przez aktora Di spatcher, • uprawnienia w zakresie inicjowania poszczególnych przypadków użycia przez poszczególnych aktorów, • sytuacje wyjątkowe i ich obsługiwanie, • elementy funkcjonalności wspólne dla wielu przypadków użycia. W następnej sekcji opiszemy sposoby zarządzania relacjami między przypadkami użycia i aktorami oraz reorganizację przypadków użycia na podstawie tych relacji — przy okazji dostarczymy przykłady dla trzech ostatnich pozycji powyższej listy.

4.4.5. Identyfikacja relacji między aktorami a przypadkami użycia Nawet dla średniej wielkości systemu opracowuje się zwykle dużą liczbę przypadków użycia. Ponieważ są one podporządkowane wspólnemu celowi, nie są od siebie całkowicie niezależne: właśnie odnalezienie elementów wspólnych — a także relacji między nimi a uczestniczącymi aktorami — pozwala na uproszczenie modelu i sprawienie, by był bardziej zrozumiały. Za pomocą relacji rozszerzania można w sposób czytelny wyodrębniać z przypadków użycia sytuacje wyjątkowe i opcjonalne, lecz często spotykane ciągi zdarzeń, zaś relacja zawierania pozwala na redukcję redundancji przypadków użycia.

Relacje komunikacyjne między aktorami a przypadkami

użycia

Relacje komunikacyjne między aktorami a przypadkami użycia reprezentują przepływ informacji w ramach każdego z tych przypadków. Aktor inicjujący przypadek użycia powinien być wyraźnie odróżniony od innych aktorów, z którymi ów przypadek użycia się komunikuje. Określając aktora inicjującego dany przypadek użycia, określamy jednocześnie aktorów, którzy nie mają prawa go inicjować; podobnie określając aktorów komunikujących się z konkretnym przypadkiem użycia, określamy jednocześnie podzbiory funkcjonalności dostępne dla poszczególnych aktorów. W ten oto sposób, opisując relacje inicjacyjne i komunikacyjne, stwarzamy punkt wyjścia do zdefiniowania zasad kontroli dostępu do docelowego systemu. Relacje poszczególnych aktorów z danym przypadkiem użycia określane są zaraz na początku definiowania tego przypadku. Na rysunku 4.4 widzimy przykład takich relacji dla systemu FRIEND. Stereotyp «initiate® oznacza inicjowanie przypadku użycia przez aktora, stereotyp «part i ci pate» — uczestnictwo w przypadku użycia, bez prawa jego inicjowania.

Relacja rozszerzania między przypadkami

użycia

Przypadek użycia może pełnić funkcję rozszerzania innego przypadku użycia, jeśli ten drugi obejmuje zachowanie reprezentowane przez ten pierwszy (przy spełnieniu określonych warunków). Załóżmy, że w momencie, gdy F i e l d O f f i c e r zamierza wysłać formularz zgłoszenia incydentu, urywa się połączenie sieciowe jego laptopa z internetem (na przykład ze względu na zanik sygnału w tunelu): Fi el dOffi cer musi wówczas zostać poinformowany (przez aplikację działającą we wspomnianym laptopie), że formularz zgłoszeniowy nie został

4.4. Aktywności związane ze zbieraniem wymagań

177

Rysunek 4.4. Przykład relacji komunikacyjnych między aktorami a przypadkami użycia w systemie FRIEND. Aktor F i e l d O f f i c e r inicjuje przypadek użycia ReportEmergency, natomiast D i s p a t c h e r inicjuje przypadki użycia Openlncident i A l l o c a t e R e s o u r c e s . Aktor F i e l d O f f i c e r nie ma więc prawa do rejestrowania („otwierania") nowego incydentu ani do decydowania o przydzielaniu zasobów potrzebnych do jego obsługi

wysłany i uruchomiona musi zostać określona procedura dotycząca zarówno działań Fi el d ^ O f f i cera, jak i rzeczonej aplikacji. Ta wyjątkowa sytuacja zerwania połączenia modelowana jest jako odrębny przypadek użycia o nazwie Connecti onDown, stanowiący rozszerzenie przypadku użycia ReportEmergency (patrz rysunek 4.5). Warunki, przy spełnieniu których inicjowany jest przypadek ConnectionDown, są jego własnymi warunkami wstępnymi i nie wchodzą w skład przypadku ReportEmergency.

Rysunek 4.5. Przykład relacji rozszerzania. Przypadek użycia ConnectionDown jest rozszerzeniem przypadku użycia ReportEmergency. Użycie relacji rozszerzania przyczynia się do uproszczenia przypadku ReportEmergency i jego skoncentrowania na czynności zasadniczej — powiadamianiu o niebezpieczeństwie

Oddzielenie sytuacji wyjątkowych i opcjonalnych od zasadniczego przypadku użycia ma co najmniej dwie zalety. Po pierwsze, „bazowy" przypadek użycia staje się krótszy i bardziej czytelny. Po drugie, rozdzielone zostają dwa różne rodzaje zachowań — zachowania „normalne" i wyjątkowe — co pozwala programistom potraktować oba aspekty funkcjonalności osobno, na przykład z punktu widzenia optymalizacji (zachowania „normalne" optymalizowane są pod kątem efektywności, zaś zachowania wyjątkowe — pod kątem niezawodności). Każdy z dwóch przypadków użycia — bazowy i rozszerzający — jest odrębną całością, posiadającą własne warunki początkowe i końcowe.

Relacja zawierania między przypadkami

użycia

Relacja zawierania umożliwia eliminowanie z przypadków użycia elementów redundantnych, czyli odzwierciedlających powtarzającą się funkcjonalność. Załóżmy, że Di spatcher w momencie „otwierania" incydentu posługuje się mapą podległego mu obszaru, na przykład w celu oceny ryzyka stwarzanego przez zaistniały właśnie pożar czy też zidentyfikowania zasobów

Rozdział 4. • Zbieranie wymagań

178

(wozów strażackich) znajdujących się w pobliżu owego pożaru. Szczegóły czynności wykonywanych przez aktora Di spatcher w związku z korzystaniem z mapy wyodrębnione zostają w postaci osobnego przypadku użycia ViewMap, wykorzystywanego przez dwa inne przypadki użycia — Open Incident i AllocateResources, co p o k a z a n o n a r y s u n k u 4.6.

AllocateResources

Rysunek 4.6. Przykład relacji zawierania między przypadkami użycia. Przypadek ViewMap opisuje przepływ zdarzeń związanych z przeglądaniem m a p y (przewijanie, powiększanie, wyszukiwanie obiektów według nazwy i tym podobne) i wykorzystywany jest przez przypadki użycia Openlnci dent i AllocateResources

Wyodrębnianie wspólnych elementów funkcjonalnych z przypadków użycia ma wiele zalet, spośród których najważniejszymi są skrócenie opisu oraz zredukowanie redundancji. Ale uwaga: wyodrębnienie takie ma sens wyłącznie w odniesieniu do elementów powtarzających się — niepotrzebne mnożenie przypadków użycia czyni specyfikację wymagań mniej czytelną dla klienta i użytkowników.

Różnice między zawieraniem a

rozszerzaniem

Relacje zawierania i rozszerzania są podobnymi konstrukcjami i podobieństwo to może sprawiać pewien kłopot programiście, który nie ma jeszcze wprawy w modelowaniu [Jacobson i in., 1992]. Podstawową różnicą między wspomnianymi relacjami jest kierunek każdej z nich: dla relacji zawierania zdarzenie wyzwalające zawierany (docelowy) przypadek użycia opisywane jest w przepływie zdarzeń przypadku nadrzędnego (źródłowego), zaś dla relacji rozszerzania zdarzenie wyzwalające przypadek rozszerzający (źródłowy) jest jego warunkiem początkowym. Innymi słowy, każde włączenie przypadku podrzędnego („zawieranego") do przypadku nadrzędnego („zawierającego") musi być powiązane z określeniem warunku, przy spełnieniu którego włączany przypadek jest aktywowany. Dla odmiany, w przypadku relacji rozszerzania przypadki nadrzędne („rozszerzane") pozostają bez zmian, natomiast w przypadku rozszerzającym specyfikuje się listę przypadków rozszerzanych za jego pomocą. Tak więc jeśli jakiś aspekt zachowania systemu powiązany jest ze zdarzeniem występującym w stosunkowo niewielu przypadkach użycia, kwalifikuje się do wyodrębnienia jako przypadek dołączany („zawierany") do innych przypadków nadrzędnych. Relacja zawierania okazuje się odpowiednia dla zdarzeń powszechnie występujących w przypadkach użycia — takich jak pobieranie nazwy pliku, przeglądanie mapy, wybór opcji i tym podobne. Jeżeli natomiast dane zachowanie powiązane jest ze zdarzeniem mogącym wystąpić w dowolnej chwili bądź wystąpienie tego zdarzenia łatwo zakwalifikować jako warunek wstępny, wspomniane zachowanie nadaje się do modelowania za pomocą przypadku rozszerzającego.

4.4. Aktywności związane ze zbieraniem wymagań

179

Relacja rozszerzania jest bardziej odpowiednia dla zdarzeń wyjątkowych lub zachodzących sporadycznie — na przykład dla zerwania połączenia, anulowania transakcji czy uruchomienia pomocy kontekstowej. Ilustracją porównania obu relacji (zawierania i rozszerzania) jest tabela 4.6, w której widoczne są efekty powiązania ze sobą przypadków użycia ReportEmergency i ConnectionDown. Relacja zawierania (lewa kolumna) wymaga dołączenia w dwóch miejscach nadrzędnego przypadku (ReportEmergency) tekstu oznaczającego dołączenie przypadku ConnectionDown, aktywowanego pod określonym warunkiem. W przypadku relacji rozszerzania przypadek nadrzędny pozostaje nietknięty, natomiast w przypadku rozszerzającym (ConnectionDown) specyfikowany jest zestaw przypadków nadrzędnych podlegających rozszerzaniu („każdy przypadek użycia, w ramach którego może zdarzyć się zerwanie połączenia sieciowego między Fi el dOff i cerem a Di spatcherem"). Możliwość modelowania nowych zachowań bez potrzeby ingerowania w tekst istniejących przypadków użycia — dzięki relacji rozszerzania — jest bardzo cenna, by nie rzec: krytyczna, z punktu widzenia stabilności modelu i ryzyka popełniania błędów. Rozróżnienie między obiema relacjami, jeśli dokonane właściwie, ma pozytywny wpływ na jakość dokumentacji, ponieważ zmniejsza redundancję przypadków użycia, redukuje zależności między przypadkami i zmniejsza ryzyko popełniania błędów w sytuacji zmiany wymagań. Trzeba jednak przyznać, że z perspektywy innych aktywności programistycznych różnica między wspomnianymi relacjami jest kwestią marginalną. We właściwym zastosowaniu jednej albo drugiej relacji mogą okazać się pomocne heurystyki opisane w ramce. Heurystyki wyboru między relacjami zawierania i rozszerzania o

Wykorzystuj relację rozszerzania do modelowania zachowania opcjonalnego, wyjątkowego lub zdarzającego się rzadko — takiego jak na przykład awaria wozu strażackiego (sytuacja wyjątkowa) czy wezwanie dodatkowego wozu strażackiego znajdującego się w pobliżu, a niepowiązanego bezpośrednio z incydentem (zdarzenia opcjonalne).

® Wykorzystuj relację zawierania do modelowania zachowania współdzielonego między wiele przypadków użycia. o Zachowuj jednak umiar w stosowaniu każdej z relacji: nadmierna fragmentacja modelu w postaci rozmnażania przypadków użycia nie jest zjawiskiem pożądanym — łatwiej czyta się i rozumie kilka dwu-, trzystronicowych przypadków użycia niż kilkadziesiąt przypadków kilkuwierszowych.

Niezależnie od opisanych różnic, obie relacje służą jednak temu samemu celowi — redukowaniu lub usuwaniu redundancji z modelu, a w konsekwencji zmniejszaniu ryzyka wystąpienia niespójności.

4.4.6. Początkowa identyfikacja obiektów modelu analitycznego Jedną z pierwszych trudności, jakie pojawiają się na początku współpracy programistów i użytkowników, jest niemożność porozumienia się z powodu używania odmiennej terminologii. Mimo iż programiści z czasem przyzwyczajają się do mówienia językiem użytkowników, problem pojawia się na nowo, w sytuacji gdy do zespołu dołącza nowy programista. Nieporozumienia pojawiają się zarówno podczas nazywania tych samych rzeczy w zróżnicowany sposób, jak również wskutek używania tego samego określenia w różnych znaczeniach.

Rozdział 4. • Zbieranie wymagań

180

Tabela 4.6. Ilustracja różnicy między relacjami zawierania i rozszerzania ReportEmergency (relacja zawierania)

ReportEmergency (relacja rozszerzania)

1. [...] 2. [...]

1. [...] 2. [...]

3. Fi el dOf f i cer wypełnia formularz, określając typ zdarzenia, stopień zagrożenia, jego lokalizację i krótki opis sytuacji. Fi el dOf f i cer określa także możliwe sposoby pierwszej reakcji na zagrożenie. Po wypełnieniu f o r m u l a r z a Fi el dOf f i cer wysyła go do systemu.

3. Fi el dOf f i cer wypełnia formularz, określając typ zdarzenia, stopień zagrożenia, jego lokalizację i krótki opis sytuacji. Fi el dOf f i cer określa także możliwe sposoby pierwszej reakcji na zagrożenie. Po wypełnieniu f o r m u l a r z a Fi el dOf f i c e r wysyła go do systemu.

Gdyby zerwane zostało połączenie wykorzystywany będzie przypadek ConnectionDown.

sieciowe, użycia

4. Jeśli nawiązane jest połączenie sieciowe, system FRIEND powiadamia dyspozytora (Di s p a t c h e r ) o zdarzeniu. Di s p a t c h e r analizuje informacje zawarte w formularzu, p o czym tworzy nowy obiekt I n c i d e n t w bazie, realizując przypadek użycia Openlncident. D i s p a t c h e r określa sposób reakcji i zatwierdza raport Gdyby zerwane zostało połączenie sieciowe, wykorzystywany będzie przypadek użycia ConnectionDown.

4. System FRIEND powiadamia dyspozytora (Di s p a t c h e r ) o zdarzeniu. Di s p a t c h e r analizuje informacje zawarte w formularzu, po czym tworzy nowy obiekt Inci dent w bazie, realizując p r z y p a d e k użycia Openlncident. D i s p a t c h e r określa sposób reakcji i zatwierdza raport.

5. ... ConnectionDown (relacja zawierania)

ConnectionDown (relacja rozszerzania) Przypadek użycia ConnectionDown jest wykorzystywany przypadku

do rozszerzania

każdego

użycia, w ramach którego

zdarzyć się zerwanie połączenia

może

sieciowego

między Fi el dOff i cerem a Di spatcherem. 1. Fi el d O f f i c e r i D i s p a t c h e r otrzymują informację o zerwaniu połączenia, być może połączoną z wyjaśnieniem prawdopodobnej przyczyny zerwania (modem poza zasięgiem sieci, na przykład w tunelu).

1. F i e l d O f f i c e r i D i s p a t c h e r otrzymują informację o zerwaniu połączenia, być może połączoną z wyjaśnieniem prawdopodobnej przyczyny zerwania (modem poza zasięgiem sieci, na przykład w tunelu).

2. Fakt zerwania połączenia rejestrowany jest w systemie; gdy łączność zostanie przywrócona, następuje próba ponowienia przerwanej transmisji danych.

2. Fakt zerwania połączenia rejestrowany jest w systemie; gdy łączność zostanie przywrócona, następuje próba ponowienia przerwanej transmisji danych.

3. Fi el dOf f i cer i Di spatcher nawiązują kontakt za pomocą innych środków, na przykład radiotelefonu; Di s p a t c h e r sam w p r o w a d z a do systemu i n f o r m a c j e przekazywane m u słownie przez Fi el dOf f i c e r a .

3. Fi el d O f f i c e r i Di spatcher nawiązują kontakt za pomocą innych środków, na przykład radiotelefonu; Di s p a t c h e r sam wprowadza do systemu informacje przekazywane m u słownie przez Fi el dOff i cera.

4.4. Aktywności związane ze zbieraniem wymagań

181

By uniknąć tych nieporozumień i- wypracować czytelną terminologię, programiści dla każdego przypadku użycia identyfikują obiekty uczestniczące w tym przypadku, opatrując je znaczącymi nazwami i intuicyjnym opisem oraz katalogując w specjalnym słowniku8. Tworzenie tego słownika jest pierwszym krokiem etapu analizy, którą zajmiemy się szczegółowo w następnym rozdziale. Wspomniany słownik włączany jest do specyfikacji wymagań jako jej integralna część, później zwykle dołączany jest do dokumentacji użytkownika systemu. Jego zawartość wymaga — oczywiście — aktualizacji przy każdej zmianie wymagań. Korzyści z jego stosowania są wielorakie: nowi programiści już na początku otrzymują spójny zestaw definicji, każda koncepcja nazywana jest jednym określeniem, wspólnym dla programistów i użytkowników, a każde używane określenie ma ustalone, precyzyjne, oficjalne znaczenie. Identyfikacja obiektów uczestniczących w przypadkach użycia jest pierwszym krokiem na drodze do tworzenia modelu analitycznego. Kompletny model analityczny nie nadaje się raczej do roli środka komunikacji między programistami a użytkownikami, tym ostatnim mogą być bowiem obce koncepcje obiektowe w ogólności; jednak opisy obiektów modelu (w kategoriach oficjalnej terminologii słownika danych) i ich atrybuty widoczne są dla użytkowników i podlegają ich ocenie. Doskonaleniem modelu analitycznego zajmiemy się w następnym rozdziale. W literaturze przedmiotu proponowanych jest wiele heurystyk mających ułatwić identyfikowanie obiektów — oto kilka z nich. Heurystyki pomocne w identyfikowaniu obiektów Wśród obiektów modelu wyróżnić można między innymi: ® terminy, które programiści muszą uzgodnić z użytkownikami w celu rozumienia przypadków użycia, ® rzeczowniki powtarzające się w przypadkach użycia ( I n c i d e n t ) , ® encje świata rzeczywistego odzwierciedlone w systemie (Fi el dOf f i cer, Resource), ® procesy świata rzeczywistego odzwierciedlone w systemie (EmergencyOperati onsPl an), ® przypadki użycia (ReportEmergency), ® miejsca generowania i konsumowania danych (Pri n t e r ) , ® artefakty wchodzące w interakcje z użytkownikami ( S t a t i on), ® wszystkie opisy, które powinny być sporządzone w kategoriach dziedziny aplikacyjnej.

Jeśli dwa przypadki użycia odnoszą się do tej samej koncepcji, odpowiadające sobie obiekty powinny nazywać się i wyglądać identycznie. Jeśli dwa obiekty o tej samej nazwie nie odzwierciedlają tej samej koncepcji, należy przynajmniej jedną z tych nazw zmienić, tak by uwydatnić różnicę między wspomnianymi koncepcjami; dzięki takiej konsolidacji unikniemy wieloznaczności używanej terminologii. W tabeli 4.7 widoczna jest początkowa postać listy obiektów uczestniczących w przypadku użycia ReportEmergency.

8

Nazywanym powszechnie „słownikiem danych".

182

Rozdział 4. • Zbieranie wymagań

Tabela 4.7. Wybrane obiekty uczestniczące w przypadku użycia ReportEmergency Dispatcher

Dyspozytor, oficer policji zarządzający wypadkami ( I n c i d e n t ) . D i s p a t c h e r d o k o n u j e otwierania, d o k u m e n t o w a n i a i zamykania obsługi wypadków, zgodnie z raportami (EmergencyReport) i inną informacją pochodzącą od funkcjonariusza (Fi el dOf f i cer). Di s p a t c h e r identyfikowany jest na podstawie n u m e r u odznaki.

EmergencyReport

Początkowy raport o wypadku, s p o r z ą d z o n y przez f u n k c j o n a r i u s z a (Fi el dOf f i cer) dla dyspozytora (Di spatcher). Raport taki zwykle powoduje utworzenie nowego obiektu klasy Incident przez dyspozytora. Podstawowymi elementami raportu są zwykle: stopień zagrożenia, typ wypadku (pożar, wypadek drogowy, inne zdarzenie), wskazanie miejsca wypadku, krótki opis sytuacji.

FieldOfficer

Funkcjonariusz na służbie. Konkretny funkcjonariusz może być w danej chwili przydzielony do co najwyżej jednego wypadku. Funkcjonariusze identyfikowani są na podstawie n u m e r u odznaki.

Incident

Wypadek, sytuacja wymagająca interwencji funkcjonariusza (Fi el dOf f i cer). Wypadek może zostać zgłoszony do systemu przez funkcjonariusza lub przez inną osobę pozostającą na zewnątrz tego systemu. Informacja o wypadku obejmuje jego opis, sposób reakcji, status (otwarty, zamknięty, udokumentowany), lokalizację i identyfikację funkcjonariuszy (Fi el dOf f i cer) przydzielonych do obsługi.

G d y o b i e k t y u c z e s t n i c z ą c e w p o s z c z e g ó l n y c h p r z y p a d k a c h użycia z o s t a n ą j u ż s k o n s o l i d o w a n e , p r o g r a m i ś c i m o g ą użyć ich w c h a r a k t e r z e listy k o n t r o l n e j w celu u p e w n i e n i a się, że z b i ó r o p r a c o w a n y c h p r z y p a d k ó w użycia jest k o m p l e t n y .

Heurystyki w e r y f i k o w a n i a kompletności z e s t a w u p r z y p a d k ó w użycia w kontekście o b i e k t ó w u c z e s t n i c z ą c y c h •

W którym z przypadków użycia tworzony jest ten konkretny obiekt — kiedy wartości jego atrybutów wprowadzane są do systemu?



Którzy aktorzy mają dostęp do informacji zawartej w tym obiekcie?



Które przypadki użycia mogą modyfikować i (lub) usuwać ten konkretny obiekt — w których przypadkach użycia następuje edytowanie lub usuwanie informacji zawartej w tym obiekcie?



Który aktor może inicjować ten konkretny przypadek użycia?



Czy ten konkretny obiekt w ogóle jest potrzebny — czy chociaż jeden przypadek użycia zależny jest od zawartej w tym obiekcie informacji?

4.4.7. Identyfikacja wymagań pozafunkcyjnych W y m a g a n i a p o z a f u n k c y j n e o p i s u j ą te a s p e k t y s y s t e m u , k t ó r e nie są b e z p o ś r e d n i o p o w i ą z a n e z przejawianą przez ten system funkcjonalnością. M o g ą dotyczyć różnych aspektów systemu, o d w y g l ą d u interfejsu u ż y t k o w n i k a , p o p r z e z d o p u s z c z a l n y czas reakcji, aż d o z a g a d n i e ń związ a n y c h z b e z p i e c z e ń s t w e m . D e f i n i o w a n e są w r a z z w y m a g a n i a m i f u n k c j o n a l n y m i , j a k o że mają r ó w n i e istotny wpływ na tworzenie systemu i jego koszt.

4.5. Zarządzanie zbieraniem wymagań

183

Weźmy jako przykład wyświetlacz mozaikowy, służący kontrolerowi lotu do śledzenia ruchu samolotów w podległym mu obszarze. Informacja prezentowana na takim wyświetlaczu stanowi kompilację informacji z wielu radarów oraz baz danych (stąd przymiotnik „mozaikowy") i dla każdego samolotu znajdującego się w obserwowanym obszarze wyświetlana jest jego identyfikacja, prędkość i wysokość, na jakiej się znajduje. Maksymalna liczba samolotów, jaką system jest wstanie jednocześnie zarządzać, determinuje zarówno wydajność pracy kontrolera, jak i koszt całego systemu: gdy jest niewielka, system nadaje się jedynie dla obszarów o niezbyt dużym natężeniu ruchu, z kolei system zdolny kontrolować wiele samolotów jednocześnie jest i droższy, i bardziej złożony pod względem tworzenia i testowania. Wymagania pozafiinkcyjne mają to do siebie, że mimo iż dotyczą niekiedy mało uchwytnych (czy sprawiających wrażenie mało istotnych) aspektów systemu, to mogą mieć krytyczny wpływ na pracę użytkownika. W przypadku wspomnianego wyświetlacza mozaikowego maksymalna liczba samolotów, jakie mogą być jednocześnie widoczne, wpływa nie tylko na złożoność i koszt systemu, lecz także na takie (wydawałoby się) drobiazgi jak wielkość ikon reprezentujących poszczególne samoloty i sposób opatrywania ich informacją identyfikacyjną, nie wspominamy tu już o częstotliwości jej odświeżania. Po zebraniu pewnej porcji wymagań pozafunkcyjnych ich zbiór może się okazać wewnętrznie sprzeczny. Od przykładowego zegarka SatWatch wymaga się, by był bardzo dokładny (co wyklucza potrzebę ręcznego resetowania czy korygowania wskazań) i jednocześnie na tyle tani, żeby w przypadku awarii kupno nowego nie było problemem finansowym. Jest zrozumiałe, że większa dokładność zegarków okupiona jest zwykle wyższą ich ceną, klient musi się zatem zdecydować, które ze sprzecznych wymagań (tani czy dokładny) ma dla niego wyższy priorytet. Niestety, istnieje niewiele systematycznych metod zbierania wymagań pozafunkcyjnych. W praktyce analitycy posługują się ich taksonomią (na przykład opisywanym wcześniej schematem FURPS+) w celu stworzenia listy pytań pomagających skupić się klientowi i programistom na pozafunkcyjnych aspektach systemu. Ponieważ na tym etapie zidentyfikowani są już aktorzy, wspomniana lista może być zorganizowana w podziale na role i na tej podstawie rozdzielona między reprezentatywnych użytkowników. Zaletą takiej listy jest wielokrotna przydatność — nie tylko na potrzeby aktualnie tworzonego systemu, lecz także przyszłych systemów związanych z tą samą dziedziną aplikacyjną. Ponadto dzięki takiej liście użytkownik łatwiej może uświadamiać sobie nowe potrzeby (w zakresie wymagań pozafunkcyjnych) — na przykład w zakresie administrowania systemem. Przykłady pytań kwalifikujących się na taką listę, wzorowanych na schemacie FURPS+, przedstawione są w tabeli 4.8. Gdy zbieranie wymagań pozafunkcyjnych zostanie zakończone, następuje ich doskonalenie i identyfikowanie zależności między nimi, a także ewentualnych sprzeczności. Czytelników zainteresowanych szczegółami aktywności tego rodzaju odsyłamy do pracy L. Chunga, B. A. Nixona, E. Yu i J. Mylopoulosa [Chung i in., 1999].

4.5. Zarządzanie zbieraniem wymagań W poprzedniej sekcji opisaliśmy techniczne aspekty modelowania systemu w kategoriach przypadków użycia. Przypadki użycia same z siebie nie załatwiają jednak zbierania wymagań; programiści, nawet wyspecjalizowani w ich tworzeniu, w dalszym ciągu muszą współdziałać z użytkownikami i uzgadniać swe działania z klientem. W tej sekcji opiszemy sposoby zbierania informacji od użytkowników i negocjowania porozumień z klientem, a szczególnie:

184

Rozdział 4. • Zbieranie wymagań

Tabela 4.8. Przykładowe pytania ułatwiające identyfikowanie wymagań pozafunkcyjnych

Kategoria

Przykładowe pytania

Użyteczność

«

Jaki jest stopień doświadczenia użytkownika w zakresie obsługi podobnych



systemów i generalnie w zakresie obsługi komputera?

Jakie standardy interfejsu GUI są znane użytkownikowi? o W jaką dokumentację należy wyposażyć użytkownika? Niezawodność (w tym solidność, bezpieczeństwo i zabezpieczenia)

• •

Jak wysoce niezawodny, dostępny i solidny powinien być tworzony system? Czy dopuszczalny jest restart systemu w przypadku jego awarii?



Jaki stopień utraty danych powinien być tolerowany przez system?



W jaki sposób system powinien reagować na sytuacje wyjątkowe?



Czy od systemu wymaga się szczególnych cech w zakresie bezpieczeństwa



dla użytkowników i środowiska? Czy i jakie mechanizmy zabezpieczeń mają być obecne w systemie?

Wydajność

• • • • •

Jak szybko powinien reagować system na akcje użytkownika? Czy system obejmuje jakieś zadania z krytycznym uwarunkowaniem czasowym? Jak wielu użytkowników będzie mógł jednocześnie obsługiwać system? Jak duże są magazyny danych w porównywalnych systemach? Jak wielkie opóźnienie ze strony systemu będzie akceptowalne dla użytkowników?

Wspieralność

• •

(w tym łatwość utrzymania i przenośność)

0 Czy istnieją plany przenoszenia systemu na inną platformę sprzętową

Implementacja



Czy istnieją konkretne wymagania dotyczące platformy sprzętowej dla systemu? a Czy istnieją wymagania pod adresem zespołu utrzymania systemu? • Czy istnieją wymagania dotyczące testowania systemu?

Interfejs

• • •

Jakie prawdopodobne rozszerzenia systemu przewidywane są w przyszłości? Kto będzie odpowiedzialny za utrzymanie systemu? lub programową?

Czy system ma współpracować z innymi systemami? W jaki sposób dane są eksportowane z systemu i importowane do niego? Jakie standardy zachowań użytkownika powinny być respektowane przez system?

Operatywność



Kto będzie odpowiedzialny za nadzór nad funkcjonowaniem systemu?

Pakietowanie

• • •

Kto będzie instalował system? Jak wiele instalacji przewidziano? Czy istnieją szczególne wymagania lub ograniczenia związane z instalacją?

Prawo

• •

Jakie będą zasady licencjonowania systemu? Czy awarie systemu powodować będą odpowiedzialność cywilną lub karną?



Czy w systemie wykorzystywane będą k o m p o n e n t y i (lub) algorytmy, których używanie wiąże się z opłatami licencyjnymi?

• Negocjowanie specyfikacji z klientem i metodę Joint Application Design (patrz sekcja 4.5.1), « Zarządzanie identyfikowalnością (patrz sekcja 4.5.2), • Dokumentowanie zbierania wymagań (patrz sekcja 4.5.3).

4.5. Zarządzanie zbieraniem wymagań

185

4.5.1. Negocjowanie specyfikacji z klientem: metoda Joint Application Design Joint Application Design (JAD) to metoda opracowana w firmie IBM w latach 70. ubiegłego wieku. Jej efektywność wynika z faktu, że zbieranie wymagań odbywa się w formie jednorazowej sesji warsztatowej, w której uczestniczą wszystkie osoby zaangażowane w realizację projektu. Użytkownicy, klienci, programiści i wyszkolony prowadzący spotykają się w jednym pomieszczeniu w celu zaprezentowania własnych punktów widzenia i poznania innych oraz negocjowania i poszukiwania rozwiązań akceptowanych przez wszystkich uczestników. Rezultat sesji — dokument JAD — zawiera kompletną specyfikację wymagań obejmującą elementy danych, przepływ pracy i przykładowe zrzuty ekranów. Ponieważ dokument końcowy opracowywany jest wspólnie przez wszystkich uczestników (czyli również tych, którzy podejmować mogą znaczące decyzje), reprezentuje on porozumienie między użytkownikami, klientami i programistami, minimalizujące prawdopodobieństwo modyfikowania wymagań w późniejszych stadiach realizacji projektu. Sesja JAD obejmuje pięć głównych aktywności, przedstawionych na rysunku 4.7. ł. Definiowanie projektu. W ramach tej aktywności facilitator JAD, w drodze wywiadu z menedżerem projektu i klientem, określa cele i zakres projektu. Dane zebrane w trakcie wywiadu utrwalane są w słowniku menedżera. 2. Badania i wywiady. W ramach tej aktywności facilitator JAD przeprowadza wywiady z obecnymi i przyszłymi użytkownikami, zbierając informacje na temat dziedziny aplikacyjnej i konstruując pierwszy zestaw wysokopoziomowych przypadków użycia. Rozpoczyna także sporządzanie listy problemów, jakie powinny zostać rozwiązane w trakcie sesji. Rezultatem tej aktywności są agenda sesji oraz wstępna specyfikacja. 3. Przygotowania. Ta aktywność ma na celu właściwe przygotowanie sesji. Facilitator JAD sporządza w tym celu dokument roboczy stanowiący pierwszy szkic dokumentu końcowego, skrypt sesji i ewentualne slajdy bądź wykresy odzwierciedlające wiedzę zebraną w ramach badań i wywiadów. Formuje także zespół, w skład którego wchodzą: klient, menedżer projektu, wybrani użytkownicy i programiści. Reprezentowane są więc wszystkie grupy zaangażowane w projekt, wszyscy obecni mają możliwość podejmowania wiążących decyzji. 4. Sesja. To zasadnicza część JAD. Jest prowadzona przez facilitatora, a jej celem jest wypracowanie specyfikacji wymagań. Typowa sesja JAD trwa od 3 do 5 dni, w czasie których zespoły definiują i uzgadniają scenariusze, przypadki użycia i prototypy oraz makiety interfejsu użytkownika. Wszystkie podejmowane decyzje dokumentowane są przez pisarza. 5. Dokument końcowy. Końcowa część sesji JAD polega na wypracowaniu dokumentu końcowego poprzez zmodyfikowanie dokumentu wstępnego na podstawie decyzji podjętych w trakcie sesji. Dokument końcowy reprezentuje kompletną, uzgodnioną specyfikację systemu i jest przekazywany każdemu z uczestników sesji do oceny. Następnie uczestnicy spotykają się i w ciągu 1 - 2 godzin wypracowują ostateczną wersję dokumentu.

186

Rozdział 4. • Zbieranie wymagań

Rysunek 4.7. Aktywności metodologii JAD. Centralną jej częścią jest Sesja, w ramach której wszyscy uczestnicy wspólnie wypracowują specyfikację wymagań. Aktywności poprzedzające Sesją mają na celu zwiększenie jej efektywności. Dokument końcowy, będący wynikiem S e s j i , jest odzwierciedleniem decyzji podejmowanych na jej forum

Metoda JAD wykorzystywana była przez firmę IBM i kilka innych firm. JAD wykorzystuje zalety dynamiki grupowej w celu usprawnienia komunikacji między uczestnikami i szybszego osiągnięcia konsensusu. Po zakończeniu sesji JAD programiści są bardziej świadomi potrzeb użytkowników, użytkownicy mają natomiast pojęcie o wielu kompromisach, jakie przychodzi programistom rozstrzygać. Przyczynia się to do zmniejszenia prawdopodobieństwa

4.5. Zarządzanie zbieraniem wymagań

187

wnoszenia poprawek do projektu w dalszych jego etapach. Jako że sukces metody JAD zasadza się na dynamice społeczności, uwarunkowany jest w dużej mierze kwalifikacjami facilitatora prowadzącego cały proces. Więcej szczegółowych informacji na temat metody JAD znaleźć mogą czytelnicy w pracy J. Wooda i D. Silvera [Wood i Silver, 1989].

4.5.2. Zarządzanie identyfikowalnością Identyfikowalność to możliwość śledzenia losów każdego z wymagań — genezy (kto wymaganie sformułował, w związku z jakimi potrzebami klienta) oraz związku z funkcjonalnością systemu (które komponenty systemu realizują wymaganie i które przypadki testowe weryfikują tę realizację). Identyfikowalność umożliwia programistom pokazanie, że system jest kompletny; testerom daje pewność, że system zgodny jest ze wszystkimi wymaganiami, projektantom dostarcza racjonalizację, zaś personelowi odpowiedzialnemu za utrzymanie pozwala szacować wpływ wprowadzanych zmian. Powróćmy do naszego przykładowego zegarka SatWatch. Zgodnie z aktualną specyfikacją, wyświetlacz powinien mieć strukturę dwuwierszową, dla wyświetlenia czasu i daty w osobnych wierszach. Klient zauważa jednak, że wyświetlane cyfry są zbyt małe, by ich odczytywanie można było nazwać komfortowym i zmienia wymaganie — wyświetlacz ma być jednowierszowy, a przełączanie między czasem a datą ma odbywać się za pomocą dedykowanego przycisku. Względy identyfikowalności nakazują w tym momencie udzielenie odpowiedzi na następujące pytania: • Kto jest autorem wymagania, że wyświetlacz ma być dwuwierszowy? • Czy jakiekolwiek niejawne ograniczenie wymusza takie wymaganie? • Które komponenty zegarka muszą ulec zmianie wskutek dodania przycisku i zmiany struktury wyświetlacza? • Które przypadki użycia należy w związku z tym zmienić i w jaki sposób? Najprostszym sposobem zapewnienia identyfikowalności jest utrzymywanie odwołań skrośnych między dokumentami, modelami i fragmentami kodu źródłowego. Poszczególne elementy — wymagania, komponenty, klasy, operacje, przypadki użycia i tym podobne — powinny zostać opatrzone unikalnymi numerami, wówczas każde ze wspomnianych odwołań będzie miało postać (uporządkowanej) pary numerów, wiążącej elementy źródłowy i docelowy. Do przetwarzania takich odwołań okazują się wystarczające proste narzędzia w rodzaju arkusza kalkulacyjnego czy nawet edytora tekstu, jednakże przetwarzanie to jest czasochłonne, pracochłonne i podatne na błędy; mimo to, w małych projektach bardzo szybko daje pozytywne wyniki. W dużych projektach pożądane jest jednak używanie specjalizowanych narzędzi bazodanowych, częściowo automatyzujących identyfikowanie, edytowanie i łączenie wspomnianych zależności na bardziej szczegółowym poziomie. Przykładami narzędzi tego typu są DOORS [Telelogic] i RequisitePro [Rational]. Ich stosowanie przyczynia się do redukowania kosztu utrzymywania identyfikowalności, wiąże się jednak z zainwestowaniem środków w ich zakup, koniecznością odpowiedniego przeszkolenia uczestników projektu i ograniczeniami dotyczącymi używania innych narzędzi.

Rozdział 4. • Zbieranie wymagań

188

4.5.3. Dokumentowanie zbierania wymagań Rezultaty zbierania wymagań i ich analizy dokumentowane są w formie dokumentu analizy wymagań (Requirements Analysis Document, w skrócie RAD). Dokument ten zawiera kompletny opis systemu w kategoriach stawianych mu wymagań — funkcyjnych i pozafunkcyjnych. Stanowi owoc współpracy klienta, użytkowników, menedżera projektu, analityków (czyli programistów uczestniczących w zbieraniu wymagań) i projektantów (którymi są programiści pracujący nad projektem systemu). Pierwsza jego część, obejmująca przypadki użycia i wymagania pozafunkcyjne, powstaje w fazie zbierania wymagań; formalizacja tej specyfikacji w kategoriach obiektów modelu dokonywana jest w fazie analizy. Poniżej widoczny jest przykład szablonu RAD używanego w tej książce; elementy wyróżnione kursywą omówione zostaną w następnym rozdziale. Dokument analizy wymagań 1. Wprowadzenie 1.1. Przeznaczenie systemu 1.2. Zakres funkcjonalny systemu 1.3. Cele i warunki powodzenia projektu 1.4. Definicje, akronimy i skróty 1.5. Odwołania 1.6. Podsumowanie 2. System obecnie użytkowany 3. System proponowany 3.1. Streszczenie 3.2. Wymagania funkcyjne 3.3. Wymagania pozafunkcyjne 3.3.1. Użyteczność 3.3.2. Niezawodność 3.3.3. Wydajność 3.3.4. Wspieralność 3.3.5. Implementacja 3.3.6. Interfejs 3.3.7. Pakietowanie 3.3.8. Wymagania prawne 3.4 Modele systemu 3.4.1. Scenariusze 3.4.2. Model przypadków użycia 3.4.3. Model 3.4.4. Model

obiektowy dynamiczny

3.4.5. Interfejs użytkownika — ścieżki nawigacyjne i makiety ekranów 4. Słownik

Pierwsza część dokumentu RAD jest wprowadzeniem. Zawiera uzasadnienie powstania systemu, jego krótką charakterystykę funkcjonalną, opis zakresu funkcjonalnego i wyjaśnienie kontekstu jego tworzenia (między innymi nawiązanie do deklaracji problemu sporządzonej

4.5. Zarządzanie zbieraniem wymagań

189

przez klienta, odniesienie do istniejących' systemów i studium opłacalności). We wprowadzeniu znajduje się także omówienie celów systemu i warunków, od których uzależnione jest powodzenie całego przedsięwzięcia. Druga część — System obecnie użytkowany — opisuje bieżący stan rzeczy: jeśli nowo tworzony system ma być następcą istniejącego systemu, znajduje się tu opis funkcjonalności istniejącego systemu i związanych z nim problemów, w przypadku natomiast tworzenia „od zera" nowego systemu jest to opis istniejącego wsparcia dla problemów, których rozwiązania oczekuje się od tegoż systemu. I tak na przykład użytkownik klasycznego zegarka zmuszony jest do ręcznego korygowania wskazania czasu przy przekraczaniu strefy czasowej, a także w przypadku rozbieżności wskazań z czasem bieżącym (na skutek ograniczonej dokładności pomiaru w dłuższym okresie). Użytkownik jest istotą omylną: może po prostu zapomnieć o przestawieniu czasu, a nawet pamiętając o nim, może się pomylić przy ustawianiu nowej wartości. Zegarek SatWatch rozwiązuje oba te problemy, zapewniając zarówno automatyczne dostosowywanie wskazania do bieżącej strefy czasowej, jak i bardzo dużą dokładność pomiaru w całym okresie eksploatacji. Z kolei system FRIEND ma usprawnić obecną procedurę obsługi incydentów, opartą na papierowych formularzach i łączności radiotelefonicznej między dyspozytorem a funkcjonariuszami. Skomputeryzowanie systemu powinno w zamierzeniu zarówno obniżyć koszty jego funkcjonowania, jak zapewnić bardziej rzetelną dokumentację wypadków. Część trzecia — System proponowany — poświęcona jest zbieraniu wymagań i modelowi analitycznemu nowego systemu. Jest podzielona na cztery sekcje, obejmujące: • streszczenie przedstawiające przegląd funkcjonalny systemu, •

wymagania funkcyjne, czyli wysokopoziomowy opis funkcjonalności systemu,



wymagania pozafunkcyjne, czyli cechy istotne dla użytkownika, acz niezwiązane bezpośrednio z funkcjonalnością: użyteczność, niezawodność, wydajność, wspieralność, implementację, interfejs, operatywność, pakietowanie i wymagania prawne,



modele systemu, czyli scenariusze, przypadki użycia, model obiektowy i modele dynamiczne. W tej sekcji zawarta jest kompletna specyfikacja funkcjonalności systemu, ilustrowana makietami interfejsu użytkownika i ścieżkami nawigacyjnymi określającymi następstwo poszczególnych ekranów w dialogu z użytkownikiem. Opisy modelu obiektowego i modelu dynamicznego sporządzane są dopiero na etapie analizy.

Dokument RAD powinno się tworzyć dopiero po ustabilizowaniu modelu przypadków użycia, czyli wówczas, gdy zminimalizuje się prawdopodobieństwo modyfikowania wymagań. Te jednak i tak z konieczności podlegać będą nieuchronnym zmianom, które muszą być dokumentowane w ramach zarządzania konfiguracją 9 . Dokument RAD w momencie opublikowania staje się dokumentem bazowym i jednocześnie podmiotem zarządzania konfiguracją: każda z wprowadzanych do niego zmian rejestrowana jest z podaniem autora za nią odpowiedzialnego, daty i krótkiego opisu.

9

Generalnie, sprawdzoną i formalnie zaakceptowaną wersję produktu nazywa się linią bazową. Zarządzanie konfiguracją to proces śledzenia i akceptowania zmian wprowadzanych do linii bazowej. Zarządzaniu konfiguracją poświęcony jest rozdział 13.

190

Rozdział 4. • Zbieranie wymagań

4.6. Analiza przypadku — system ARENA W tej sekcji p o k a ż e m y z a s t o s o w a n i e o p i s y w a n y c h k o n c e p c j i n a p r z y k ł a d z i e r z e c z y w i s t e g o s y s t e m u o n a z w i e ARENA. R o z p o c z n i e m y o d w s t ę p n e j deklaracji p r o b l e m u d o s t a r c z o n e j p r z e z klienta, p o c z y m z b u d u j e m y m o d e l p r z y p a d k ó w użycia i zaczątek m o d e l u analitycznego. Przyk ł a d y p r e z e n t o w a n e w p o p r z e d n i c h sekcjach m i a ł y raczej c h a r a k t e r ilustracyjny, w tej sekcji p r z e d m i o t e m naszych r o z w a ż a ń będzie realistyczny p r z y k ł a d rzeczywistego s y s t e m u , k t ó r e g o p o w s t a w a n i e b ę d z i e m y śledzić w p o s t a c i k r e o w a n i a i u d o s k o n a l a n i a p o s z c z e g ó l n y c h artefaktów. Pozwoli t o n a d o s t r z e ż e n i e wielu subtelności, zwykle n i e o b e c n y c h w sztucznie kreo w a n y c h p r z y k ł a d a c h i l u s t r a c y j n y c h . W t y m opisie a k r o n i m ARENA ( p i s a n y w całości d u ż y m i literami) o z n a c z a ć b ę d z i e system w ogólności, p o d c z a s g d y r z e c z o w n i k „ a r e n a " (pisany w całości m a ł y m i l i t e r a m i ) o d n o s i ć się b ę d z i e d o k o n k r e t n e j i n s t a n c j i s y s t e m u .

4.6.1. Wstępna deklaracja problemu E f e k t e m pierwszego s p o t k a n i a z k l i e n t e m jest deklaracja p r o b l e m u . Z w r ó ć m y u w a g ę n a w y s o k o p o z i o m o w y c h a r a k t e r jej treści — n i e jest to jeszcze ten etap, w k t ó r y m u z g a d n i a się b u d ż e t i t e r m i n y . T w o r z e n i e naszego m o d e l u p r z y p a d k ó w użycia r o z p o c z n i e m y o d zdefiniowania aktorów i scenariuszy.

A R E N A — deklaracja problemu 1. P r o b l e m Popularność internetu i W W W wyzwoliła fenomen kreowania społeczności wirtualnych — grup ludzi, którzy powiązani są wspólnymi sprawami, zainteresowaniami i tym podobnymi rzeczami, lecz być może nigdy nie spotkali się i nie spotkają osobiście. Grupy takie mogą mieć charakter doraźny (jak w przypadku czatu czy rozgrywania turnieju) bądź długotrwały (czego przykładem są liczne grupy dyskusyjne). Grupy takie mogą być kilkuosobowe, lecz równie dobrze mogą obejmować tysiące uczestników. Upowszechnienie internetu i społeczności wirtualnych nie ominęło także obszaru gier komputerowych. Wydostały się one z ciasnych ram pojedynczych komputerów i sieci lokalnych na forum „globalnej wioski", w której wspomniane społeczności kreują się także w związku właśnie z rozgrywaniem rozmaitych gier, rozgrywek, pojedynków czy turniejów. Poszczególni gracze informowani są 0 nowościach w zakresie „swojej" gry, mogą organizować, rozgrywać i obserwować rozgrywki, porównywać wyniki i wymieniać doświadczenia. Producenci gier online zyskują dzięki temu pożytek w postaci reklamowania swych produktów i ogólnie generowania dochodów w inny sposób. W tej konstelacji społeczności gier brakuje jednak koordynacji: każda gra (czy też — każdy producent) kreuje społeczność opartą na swych własnych zasadach, odmiennej infrastrukturze, odmiennych koncepcjach, zapewniając o d m i e n n e sposoby wsparcia dla uczestników. Ta redundancja 1 niespójność ma wiele mankamentów, między innymi w postaci trudności w zakresie uczenia się nowych zasad, doświadczanej przez graczy dołączających do kolejnych społeczności. Dla producentów oznacza to konieczność budowania infrastruktury „od zera", dla reklamodawców konieczność kontaktowania się osobno z każdą ze społeczności. Specyfika infrastruktury w poszczególnych społecznościach utrudnia także budowanie wspólnych doświadczeń.

5.6. Analiza przypadku — system AREIMA

191

2. Cele Wobec powyższego, stworzenie systemu ARENA ma w zamierzeniu zrealizować następujące cele: ® dostarczenie powszechnej infrastruktury unifikującej operacje często stosowane na scenie gry: rejestrowanie nowych gier i graczy, organizowanie rozgrywek i śledzenie ich rezultatów; •

dostarczenie kapitanowi ligi środowiska umożliwiającego planowanie liczby i sekwencji rozgrywek oraz gromadzenie punktów graczy w rankingach;



stworzenie środowiska umożliwiającego producentom tworzenie nowych gier lub przystosowywanie istniejących do standardów infrastruktury ARENA;

® dostarczenie infrastruktury dla reklamodawców.

3. Wymagania funkcyjne W systemie ARENA wyróżnia się pięć typów użytkowników: •

operator, definiujący nowe gry, nowe style rozgrywek (wygrana przez nokaut, mistrzostwa, najlepszy w serii i tym podobne) i nowe formuły rankingu oraz zarządzający użytkownikami;

® kapitan ligi, definiujący nową ligę, organizujący i anonsujący nowe rozgrywki w ramach ligi, prowadzący turnieje i ogłaszający zwycięzców; •

gracz, rejestrujący się na scenie, dołączający do ligi, rozgrywający meczę i ewentualnie rezygnujący z rozgrywki;



kibic, mający możliwość monitorowania dowolnego meczu i gromadzenia statystyk związanych z poprzednimi meczami lub poszczególnymi graczami — bez konieczności rejestrowania się na scenie;



reklamodawca, któremu umożliwia się umieszczanie reklam, wybór stylu reklamowania (sponsor turnieju, sponsor ligi), kontrolowanie stanu konta i wycofywanie reklam.

4. Wymagania pozafunkcyjne •

Niski koszt użytkowania. O p e r a t o r musi mieć możliwość instalowania systemu ARENA i administrowania jego instancją bez potrzeby dokupywania dodatkowego oprogramowania i bez pomocy administratora systemu operacyjnego.



Rozszerzalność. Operator musi mieć możliwość dodawania nowych gier, definiowania nowych stylów rozgrywek i nowych f o r m u ł rankingu. Czynności te mogą wymagać chwilowego zamknięcia systemu i dodania do niego nowych modułów (na przykład klas w języku Java), nie mogą jednak powodować konieczności jego modyfikowania.

® Skalowalność. System musi umożliwiać równoległe prowadzenie wielu rozgrywek (powiedzmy 10), z których każda obejmuje do 64 graczy i kilkuset kibiców. ® Male wymagania dotyczące przepustowości. Gracze muszą mieć możliwość rozgrywania meczów, dysponując połączeniem analogowym o szybkości 56 Kb/s lub szybszym.

5. Platforma docelowa ® Gracz musi mieć możliwość dostępu do wybranej instancji systemu za pośrednictwem dowolnej przeglądarki W W W akceptującej cookies. Funkcje administracyjne (dodawanie nowych gier, definiowanie nowych stylów, definiowanie użytkowników) nie powinny być dostępne za pośrednictwem przeglądarki W W W . •

System musi działać niezawodnie na dowolnej platformie uniksowej (na przykład MacOS X, Linux czy Solaris).

Rozdział 4. • Zbieranie wymagań

192

4.6.2. Identyfikacja aktorów i scenariuszy Stosownie do zdefiniowanych pięciu typów użytkowników, definiujemy pięciu aktorów. Oto oni: Operator, LeagueOwner (kapitan ligi), Player (gracz), Spectator (kibic) i Advertiser (reklamodawca). Jako że zasadniczą funkcją systemu ma być organizowanie i przeprowadzanie rozgrywek, zdefiniujemy na początek przykładowy scenariusz organizeTicTacToe ^Tournament 10 (patrz tabela 4.9), który pomoże nam zgłębić tajniki opisanej na razie ogólnie funkcjonalności. Przez skupienie się na wąskim obszarze systemu lepiej zrozumiemy oczekiwania klienta pokładane w tym systemie i łatwiej wyznaczymy jego granice oraz rodzaje interakcji między nim a użytkownikami. Istotnie, w związku ze wspomnianym scenariuszem nasuwa się cały szereg pytań pod adresem klienta — niektóre z nich prezentujemy poniżej. Bazując na udzielonych przez klienta odpowiedziach, możemy przystąpić do doskonalenia rzeczonego scenariusza. Kroki 2. i 7. W systemie rejestrują się różni aktorzy: w pierwszym przypadku administrator rejestruje Joego jako kapitana ligi, w drugim gracz sam rejestruje się w systemie. • Rejestracja wszystkich użytkowników powinna wzorować się na tym samym paradygmacie. Kto dostarcza informacji rejestracyjnej i jak jest ona przeglądana, weryfikowana i akceptowana? • Klient: W krokach 2. i 7. mowa jest o dwóch różnych procesach: rejestrowaniu, w ramach którego nowy użytkownik (gracz lub kapitan ligi) ustanawiają swoją tożsamość, oraz aplikowaniu, za pomocą którego zarejestrowany gracz zgłasza chęć udziału w danym turnieju. W procesie rejestracji użytkownik wprowadza swoje dane osobowe (imię i nazwisko, nick, adres e-mail) oraz informacje o swych preferencjach (między innymi o typach gier i turniejów, o których chce być informowany). Wprowadzona informacja jest weryfikowana przez operatora. W procesie aplikowania użytkownik wskazuje, w którym turnieju ma ochotę uczestniczyć; informacja ta jest uwzględniania przez kapitana ligi przy planowaniu meczów. • Gdy operator zweryfikuje i zaakceptuje informację rejestracyjną użytkownika, czy zarządzanie jego aplikacjami ma się odbywać całkowicie automatycznie? • Klient: Tak, oczywiście. Krok 5. Joe wysyła pocztę elektroniczną do różnych społeczności. • Czy ARENA ma umożliwiać użytkownikom selektywne subskrybowania wybranych list mailingowych? • Klient: Tak. Przewidywane są listy związane z ogłaszaniem nowych meczów, nowej ligi, nowych turniejów i tym podobne. • Czy ARENA przechowywać będzie profile poszczególnych użytkowników (historię rozgrywanych i oglądanych meczów, zainteresowania) w celach ogłoszeniowych i reklamowych?

10

Pod nazwą Tic-Tac-Toe ukrywa się popularna gra w kółko i krzyżyk — przyp.

tłum.

5.6. Analiza przypadku — system AREIMA

193

Tabela 4.9. Scenariusz organi zeTi cTacToeTournament dla systemu ARENA Nazwa

scenariusza

oraanizeTicTacToeTournament

Aktorzy

uczestniczący

al i c e : O p e a t o r , .ioe:LeaqueOwner, b i l l : S p e c t a t o r , marv:Plaver

Przepływ

zdarzeń

1. Joe, kolega Alice jest entuzjastą gry w kółko i krzyżyk, i zgłasza chęć zorganizowania rozgrywek w tej grze. 2. Alice rejestruje Joego na scenie jako kapitana ligi. 3. Joe definiuje ligę dla początkujących, do której dołączać mogą wszyscy gracze. Regulamin tej ligi, dedykowanej grze w kółko i krzyżyk informuje, że rozgrywki dokonywane będą w stylu „wygrana przez nokaut", czyli zgodnie z formułą „zwycięzca bierze wszystko". 4. Joe organizuje pierwszy turniej, planowany na następny dzień, dla 16 graczy. 5. Joe anonsuje swój zamiar na kilku forach W W W , wysyła także e-mail do członków innych społeczności zainteresowanych grą w kółko i krzyżyk. 6. Bill i Mary odczytują e-mail otrzymany od Joego. 7. Mary rejestruje się jako zainteresowana grą, również 19 innych graczy aplikuje swój udział. 8. Joe akceptuje aplikację 16 graczy, odrzucając aplikacje tych, którzy zgłosili się najpóźniej. 9. 16 wspomnianych graczy (w tym Mary) otrzymuje e-mailem żetony umożliwiające wejście do gry oraz informację o czasie jej rozpoczęcia. 10. Pozostali zainteresowani z listy mailingowej gry w kółko i krzyżyk (w tym Bill) otrzymują powiadomienie o turnieju, wraz z nazwiskami graczy i h a r m o n o g r a m e m meczów. 11. O d m o m e n t u rozpoczęcia turnieju (zgodnie z terminem ogłoszonym przez Joego) gracze mają określony czas, by rozpocząć swój mecz; gracz, który nie zgłosi się w wyznaczonym limicie czasowym, przegrywa walkowerem. 12. Mary rozgrywa swój pierwszy mecz i wygrywa. Awansuje w turnieju do następnego etapu, w k t ó r y m rozgrywać będzie mecz z i n n y m zwycięzcą pierwszej rundy. 13. Bill, oglądając stronę główną gry w kółko i krzyżyk, d o w i a d u j e się 0 zwycięstwie Mary i postanawia obserwować jej n a s t ę p n y mecz. Wybiera więc ów mecz i obserwuje r u c h y obu graczy. Na ekranie widzi także ogłoszenia o n a s t ę p n y m meczach turnieju oraz r e k l a m y producentów gry. 14. Turniej kontynuowany jest aż do wyłonienia ostatecznego zwycięzcy, którego dorobek zostaje powiększony o łączną liczbę punktów zdobytych we wszystkich meczach. 15. Wspomniany zwycięzca gromadzi także punkty w rankingu. 16. Joe postanawia urządzić kolejne turnieje w swojej lidze; znani gracze otrzymają wówczas powiadomienia o terminie rozpoczęcia turnieju 1 o pierwszeństwie przed nowymi graczami.

194

Rozdział 4. • Zbieranie wymagań

• Klient: Tak, lecz powinno się akceptować również niekompletne informacje rejestracyjne. Powinno się zachęcać rejestrującego się użytkownika do wypełnienia ankiety o jego zainteresowaniach i tym podobnych, lecz nie można go do tego zmuszać pod rygorem nieprzyjęcia rejestracji. Niezależnie od kompletności zgłoszenia, użytkownik, rejestrując się, wyraża domniemaną zgodę na otrzymywanie informacji reklamowych. • Czy profile użytkowników powinny być automatycznie przydzielane do list mailingowych? • Klient: Nie, zakładamy bowiem, że w naszej społeczności użytkownik sprawować będzie pełną kontrolę nad swymi subskrypcjami. Ukryte subskrypcje z pewnością nie wywołają u niego wrażenia, iż kontroluje sytuację. Krok 13. Bill ogląda statystyki meczów i następny mecz chciałby obejrzeć „na żywo". • W jaki sposób poszczególni gracze identyfikowani są przez kibiców? Za pomocą imienia i nazwiska, nicka czy adresu e-mail? • Klient: Użytkownik w procesie rejestracji decyduje o sposobie swego identyfikowania dla innych aktorów. • Czy kibic ma mieć możliwość oglądania byłych meczów? • Klient: Generalnie powinna istnieć taka możliwość, lecz w stosunku do niektórych gier (na przykład rozgrywanych w czasie rzeczywistym czy prezentujących bogatą scenerię 3D) można z niej zrezygnować ze względu na duże zapotrzebowanie na zasoby systemu. • Czy system ARENA powinien zapewniać obsługę gier w czasie rzeczywistym? • Klient: Tak, ponieważ takie gry stanowią pokaźny udział w naszym rynku. Generalnie system ARENA powinien zapewniać obsługę gier w najszerszym zakresie, jaki tylko jest możliwy do zrealizowania. '

[...]

Zauważmy, że głównym celem zadawania pytań klientowi jest zrozumienie jego potrzeb oraz pozyskanie wiedzy z zakresu dziedziny aplikacyjnej. Gdy tę wiedzę zdobędziemy i sporządzimy pierwszą wersję specyfikacji wymagań, możemy przystąpić do negocjowania z klientem kompromisów między sprzecznymi wymaganiami, na przykład między zakresem funkcjonalnym systemu a jego kosztem. Nie spieszmy się jednak — zbyt wczesne przeplatanie zbierania wymagań z ich negocjowaniem na ogół nie bywa zbyt produktywne. Gdy pierwszy scenariusz zostanie dopracowany do tego stopnia, że uzgodnione zostaną z klientem granice systemu (dla tego jednego scenariusza), możemy zająć się ogólnie rozumianym obrazem funkcjonalnym tego systemu. Dokonywać będziemy tego sukcesywnie, identyfikując wiele krótszych scenariuszy, oddzielnie dla poszczególnych aktorów; nie będą one jeszcze zbyt szczegółowe, pokryją jednak znaczącą część wspomnianej funkcjonalności (patrz rysunek 4.8). Gdy napotkamy nieporozumienia i wieloznaczności, konieczne stanie się udoskonalanie wspomnianych scenariuszy. W naszym przykładzie scenariusze defineKnockOut *-*-Styl e i i nstal 1 Ti cTacToeGame powinny być uszczegółowione na poziomie porównywalnym ze scenariuszem organizeTicTacToeTournament z tabeli 4.9.

5.6. Analiza przypadku — system AREIMA

195

Rysunek 4.8. Wysokopoziomowe scenariusze dla systemu ARENA. Kwalifikują się do uszczegółowienia w celu wyjaśnienia niejednoznaczności lub wykrycia nieporozumień

Typowy uszczegółowiony scenariusz zajmuje kilka stron tekstu, dla uniknięcia nieporozumień konieczne jest więc utrzymywanie oficjalnego słownika terminologii, który z jednej strony zapewnia spójność specyfikacji, z drugiej natomiast wymusza komunikację językiem użytkownika. Przeglądając scenariusz z tabeli 4.9, łatwo zauważyć, że wyrazy „mecz", „gra", „turniej" i „liga", choć oznaczają istotne koncepcje dziedziny aplikacyjnej, używane są bez wyjaśnienia ich znaczenia. W świecie gier stosowane są w różnych kontekstach, aby więc uniknąć późniejszych komplikacji związanych z tym faktem, konieczny jest precyzyjny opis ich znaczenia — i od niego właśnie zaczniemy budowanie wspomnianego słownika, którego zaczątek widoczny jest w tabeli 4.10. Gdy tylko programiści porozumieją się z klientem co do zakresu funkcjonalnego systemu, formalizują zdobytą wiedzę w postaci wysokopoziomowych przypadków użycia.

4.6.3. Identyfikacja przypadków użycia Generalizacja scenariuszy do postaci przypadków użycia stanowi przejście od konkretnych sytuacji do bardziej ogólnego przypadku. Programiści łączą wspólne elementy funkcjonalne w pojedyncze przypadki użycia i jednocześnie rozdzielają niepowiązane ze sobą elementy funkcjonalne między różne przypadki użycia. Czytając uważnie scenariusz organ i zeTi cTacToeTournament, zauważymy, że opisuje on szeroki wachlarz zachowań prezentowanych przez różnych aktorów. Przewidując, że generalizacja (do postaci przypadku użycia OrganizeTournament) w takiej postaci mogłaby zająć nawet kilkadziesiąt stron, decydujemy się na jego podział między przypadki użycia obejrtiujące pojedynczych aktorów i wzajemnie niezależne od siebie. Rozpoczniemy od dwóch przypadków użycia, związanych z zarządzaniem kontami użytkowników: ManageUserAccounts inicjowanego przez aktora Operator, oraz Regi s t e r inicjowanego przez potencjalnego gracza (Player) lub kapitana ligi (LeagueOwner). Przy okazji

Rozdział 4. • Zbieranie wymagań

196

Tabela 4.10. Początkowa postać słownika roboczego dla systemu ARENA. Sprawowanie kontroli nad kluczową terminologią i jej definicjami gwarantuje spójność specyfikacji oraz daje pewność, że programiści będą porozumiewać się z klientem w jego języku T e r m i n polski

Termin systemu ARENA

Znaczenie

Gra

Game

Współzawodnictwo między określoną liczbą graczy (gracz reprezentowany jest przez aktora Player), p r o w a d z o n e zgodnie z określonymi regułami. W odniesieniu do systemu ARENA termin g r a odnosi się do fragmentu kodu odpowiedzialnego za w y m u s z e n i e tychże reguł, śledzenie postępów każdego gracza i określenie zwycięzcy. Przykładami gry są szachy oraz gra w kółko i krzyżyk.

Mecz

Match

Starcie między dwoma (ogólnie — kilkoma) graczami (PI ayer), zgodnie z regułami gry. W wyniku meczu wyłoniony zostaje zwycięzca (pozostali uczestnicy meczów uważani są wtedy za przegranych) bądź orzeczony remis. Niektóre gry wykluczają orzekanie remisu.

Turniej

Tournament

Seria meczów rozgrywanych między zespołami graczy (PI ayer). Turniej kończy się wyłonieniem jednego zwycięzcy. Formuła gromadzenia punktów przez graczy oraz planowanie meczów w turni eju określane są przez reguły l i g i organizującej ten turniej.

Liga

League

Społeczność, k t ó r e j celem jest organizowanie t u r n i e j ów. Regulamin 1 i gi określa grę, w zamiarze rozgrywania której została założona, oraz obowiązujący s t y l t u r n i e j u . Gracze ( P l a y e r ) zarejestrowani w danej 1 i dze gromadzą punkty, zgodnie z f o r m u ł ą E x p e r t R a t i n g zdefiniowaną w regulaminie l i g i — i tak na przykład członka 1 i g i nowicjuszy szachowych obowiązuje inna f o r m u ł a E x p e r t R a t i n g niż członka l i g i doświadczonych szachistów.

TournamentStyle

Liczba rozgrywanych meczów i ich sekwencja dla danego zbioru graczy (PI ayer). Przykładem może być styl „każdy z każdym", zgodnie z którym każdy gracz spotyka się d o k ł a d n i e jeden raz z każdym z pozostałych.

Styl

turnieju

i d e n t y f i k u j e m y k o l e j n e g o a k t o r a — Anonymous, r e p r e z e n t u j ą c e g o p o t e n c j a l n e g o u ż y t k o w n i k a , k t ó r y n i e p o s i a d a jeszcze k o n t a w systemie ARENA. P o d o b n i e r o z d z i e l a m y f u n k c j o n a l n o ś ć , związ a n ą z o g l ą d a n i e m byłych m e c z ó w o r a z z z a r z ą d z a n i e m p r o f i l a m i u ż y t k o w n i k ó w , m i ę d z y d w a k o l e j n e p r z y p a d k i użycia: B r o w s e T o u r n a m e n t H i s t o r y i M a n a g e O w n P r o f i l e , i n i c j o w a n e p r z e z a k t o r ó w ( o d p o w i e d n i o ) S p e c t a t o r i PI a y e r . W r e s z c i e , w celu dalszego s k r ó c e n i a p r z y p a d k u użycia Organ i z e T o u r n a m e n t , w y d z i e l a m y p r z y p a d e k użycia D e f i neLeague, w r a m a c h k t ó r e g o a k t o r i n i c j u j ą c y LeagueOwner m o ż e o r g a n i z o w a ć wiele t u r n i e j ó w , z g o d n i e z r e g u ł a m i swej ligi. P r z e w i d u j ą c , że instalacja n o w y c h gier i d e f i n i o w a n i e n o w y c h stylów t u r n i e j u w y m a g a ć będzie

5.6. Analiza przypadku — system AREIMA

197

podobnych kroków ze strony aktora Operator, konsolidujemy całą funkcjonalność związaną z instalowaniem nowych komponentów w przypadku użycia ManageComponents inicjowanym przez tegoż aktora. Opisaną konfigurację przypadków użycia i aktorów przedstawiono na rysunku 4.9 i w tabeli 4.11. Warto zauważyć, że sam diagram byłby mało czytelnym zapisem funkcjonalności systemu. Pełni on więc raczej funkcję indeksu do zamieszczonych poniżej opisów.

Rysunek 4.9. Diagram wysokopoziomowych przypadków użycia dla systemu ARENA

Kolejny krok to — oczywiście — wypełnienie treścią poszczególnych pól każdego z wymienionych przypadków użycia: listy aktorów uczestniczących, warunków wstępnych i końcowych oraz przepływu zdarzeń. Wysokopoziomowa postać przypadku użycia Organize ^Tournament widoczna jest w tabeli 4.12. Nieprzypadkowo wszystkie kroki przepływu zdarzeń odzwierciedlają akcje wykonywane przez aktorów, wysokopoziomowe przypadki użycia opierają się bowiem głównie na zadaniach aktorów. Szczegółowa interakcja aktorów z systemem i decyzje na temat jego granic zostają odłożone do fazy precyzowania przypadków użycia. Umożliwia to opisanie najpierw dziedziny aplikacyjnej między innymi w kategoriach tego, jak poszczególni aktorzy współdziałają, by realizować swoje cele. W przypadku użycia przedstawionym w tabeli 4.12 uczestniczy czworo aktorów: League "-•Owner odpowiedzialny za całokształt turniejów w swojej lidze, Adverti ser sponsorujący ligę lub konkretny turniej, PI ayer rozgrywający mecz i S p e c t a t o r kibicujący rozgrywkom. Najbardziej zagadkowa wydaje się rola sponsora reklamodawcy (nie jest ona wystarczająco jasno reprezentowana na diagramie z rysunku 4.8), od niej więc zaczniemy szczegółowe rozważania, niejako odzwierciedlając przy okazji fakt, że wszelkie kwestie związane ze sponsoringiem muszą być załatwione, zanim potencjalni gracze będą mogli zgłaszać swój udział w turnieju. W trakcie dyskusji z klientem decydujemy się także na obsługę sponsoringu konkretnego turnieju (obok dotychczasowego sponsorowania ligi), uzgadnianego na początku turnieju. Z jednej strony, wymaga to dodawania nowych sponsorów do systemu, z drugiej, umożliwia sponsorowi wykorzystywanie swych własnych zasobów, ponadto pozwala na lepszy wybór banerów reklamowych do prezentacji.

Rozdział 4. • Zbieranie wymagań

198 Tabela 4.11. Opis przypadków użycia z rysunku 4.9 Przypadek użycia

Opis

Register

Aktor Anonymous inicjuje ten przypadek użycia jako anonimowy użytkownik w celu zarejestrowania własnego konta w charakterze gracza ( P l a y e r ) lub kapitana ligi (LeagueOwner). Rejestracja taka jest niezbędna do wzięcia udziału w turnieju lub założenia ligi. Kibice ( S p e c t a t o r s ) nie muszą rejestrować się w systemie.

ManageUserAccounts

O p e r a t o r akceptuje rejestrację gracza (PI a y e r ) lub kapitana ligi (LeagueOwner), usuwa wybrane konta i współdziała z użytkownikami w celu uzupełnienia ich informacji rejestracyjnych.

ManageComponents

O p e r a t o r instaluje nowe gry i definiuje nowe style turniejów (TournamentStyl e). Ten przypadek użycia jest generalizacją scenariuszy d e f i neKnockOutSty1e i i n s t a l 1 TicTacToeGame.

DefineLeague

LeagueOwner zakłada nową ligę. Ten przypadek użycia generalizuje początkowe kroki scenariusza organi zeTi cTacToeTournament.

OrganizeTournament

LeagueOwner organizuje i ogłasza nowy turniej, akceptuje aplikacje graczy, zarządza harmonogramem meczów i decyduje o rozpoczynaniu poszczególnych turniejów. W ramach turnieju gracze (PI ayer) rozgrywają mecze, obserwowane przez kibiców ( S p e c t a t o r ) . Po zakończeniu każdego z t u r n i e j ó w n a s t ę p u j e aktualizacja punktacji na k o n t a c h graczy. Ten p r z y p a d e k użycia stanowi generalizację scenariusza organi zeTi cTacToeTournament.

ManageAdverti sements

Reklamodawca (Adverti ser) dokonuje upłoadu banerów i wykonuje funkcje sponsora ligi lub turnieju. Ten przypadek użycia to generalizacja scenariusza sponsorTi cTacToeBegi nnersLeague.

ManageOwnProfile

Gracze (PI ayer) zarządzają swymi subskrypcjami do list mailingowych oraz odpowiadają na oferty i ankiety reklamodawców.

BrowseTournamentHi s t o r y

Kibice ( S p e c t a t o r ) przeglądają statystyki t u r n i e j ó w i statystyki poszczególnych graczy, odtwarzając zakończone mecze. Ten przypadek jest generalizacją scenariusza analyzeTi cTacToeTournament.

P r z y p a d e k użycia z tabeli 4.12 jest w y n i k i e m p o d z i a ł u s c e n a r i u s z a o r g a n i zeTi cTacToe ^ T o u r n a m e n t n a sześć k r o k ó w , k t ó r e tak n a p r a w d ę s t a n o w i ą o d w o ł a n i a (na zasadzie relacji zawierania) d o szczegółowych p r z y p a d k ó w użycia. Stosując k o n s e k w e n t n i e to p o s t ę p o w a n i e d o w s z y s t k i c h w y s o k o p o z i o m o w y c h p r z y p a d k ó w u ż y c i a , z i d e n t y f i k u j e m y w s z y s t k i e relacje m i ę d z y a k t o r a m i , k t ó r e m u s z ą zostać o d z w i e r c i e d l o n e w systemie. E f e k t e m tych działań będzie s u m a r y c z n y opis s y s t e m u z r o z u m i a ł y n a w e t dla k a ż d e g o n o w e g o uczestnika dołączającego d o projektu.

4.6.4. Doskonalenie przypadków użycia i identyfikacja relacji D o s k o n a l e n i e p r z y p a d k ó w użycia p o p r z e z w z b o g a c a n i e ich w szczegóły u m o ż l i w i a p r o g r a m i s t o m precyzyjne zdefiniowanie informacji w y m i e n i a n e j z a r ó w n o m i ę d z y aktorami, jak i między a k t o r a m i a s y s t e m e m . Szczegółowy p r z y p a d e k użycia m o ż e przyczynić się także d o o d krycia a l t e r n a t y w n y c h d r ó g p r z e p ł y w u z d a r z e ń o r a z z i d e n t y f i k o w a n i a n o w y c h wyjątków, k t ó r e muszą być obsługiwane przez system.

5.6. Analiza przypadku — system AREIMA

199

Tabela 4.12. Przykład wysokopoziomowego przypadku użycia Nazwa przypadku Aktorzy

użycia

uczestniczący

OrganizeTournament LeagueOwner — inicjuje przypadek użycia. Adverti ser, PI ayer i Spectator — komunikują się z przypadkiem użycia.

Przepływ

zdarzeń

1. LeagueOwner organizuje turniej (Tournament), zabiega o sponsoring u reklamodawców (Adverti sers) i ogłasza rozpoczynanie turniejów. Dołączony przypadek użycia AnnounceTournament. 2. Gracze (PI ayer) aplikują do udziału w turnieju (Tournament). Dołączony przypadek użycia ApplyForTournament. 3. LeagueOwner przetwarza aplikację potencjalnych graczy (PI a y e r ) i przyporządkowuje ich do odpowiednich meczów. Dołączony przypadek użycia ProcessAppl i c a t i o n s . 4. LeagueOwner rozpoczyna turniej (Tournament). Dołączony przypadek użycia Ki ckoffTournament. 5. Gracze (PI ayer) rozgrywają mecze zgodnie z harmonogramem, kibice ( S p e c t a t o r ) obserwują te mecze. Dołączony przypadek użycia PI ayMatch. 6. LeagueOwner ogłasza zwycięzcę i archiwizuje dane turnieju (Tournament). Dołączony przypadek użycia Archi veTournament.

Warunki

wstępne



LeagueOwner jest zalogowany do systemu ARENA.

Warunki

końcowe



LeagueOwner zarchiwizował dane turnieju w archiwum systemu ARENA, zwycięzca zyskał nowe punkty na swym koncie

lub •

LeagueOwner anulował turniej, status poszczególnych graczy w lidze pozostał niezmieniony.

Aby zbytnio nie komplikować naszego opisu, nie przedstawimy tu kompletnego procesu uszczegółowiania; ograniczymy się do jednego szczególowego przypadku użycia dla każdego z kroków wysokopoziomowego przypadku użycia Organi zeTournament i w rezultacie otrzymamy diagram przedstawiony na rysunku 4.10. Następnie zajmiemy się przypadkiem użycia AnnounceTournament, przedstawionym w tabeli 4.13 oraz związanymi z nim sytuacjami wyjątkowymi, zobrazowanymi schematycznie na rysunku 4.11. Doskonalenie pozostałych przypadków użycia odbywać się będzie w podobny sposób. Na rysunku 4.10 wszystkie przypadki użycia inicjowane są przez aktora LeagueOwner, z wyjątkiem przypadków ApplyForTournament i PI ayMatch inicjowanych przez aktora gracza (PI ayer). Reklamodawca (Adverti ser) uczestniczy w przypadku użycia AnnounceTournament,

zaś kibic (Spectator) bierze udział w przypadkach AnnounceTournament i PI ayMatch. Gracz (Player) uczestniczy we wszystkich przypadkach użycia stanowiących udoskonalenie przypadku Organi zeTournament. By nie zaciemniać czytelności i tak już skomplikowanego

diagramu, zrezygnowaliśmy z jawnego przedstawienia relacji « i n i t i a t e s wiążącej aktora LeagueOwner z uszczegółowionymi przypadkami użycia; używając narzędzi UML, musimy jednak pamiętać o ich uwzględnieniu. W tabeli 4.13 widoczny jest kolejny szczegółowy przypadek użycia — AnnounceTournament.

200

Rozdział 4. • Zbieranie wymagań

Rysunek 4.10. Szczegółowy przypadek użycia jako efekt udoskonalenia przypadku Organi zeTournament

Poszczególne kroki przypadku użycia w tabeli 4.13 odzwierciedlają szczegółowo komunikację między aktorami a systemem. Zauważmy, że nie ma w nich mowy o jakichkolwiek szczegółach interfejsu użytkownika (układzie formularzy, przyciskach, kompozycji okien na stronie W W W i tak dalej). Znacznie łatwiej będzie bowiem zaprojektować interfejs użytkownika później, gdy znane już będą intencje i zakres odpowiedzialności poszczególnych aktorów — właśnie przypisywanie (lub odkrywanie) owych intencji i odpowiedzialności stanowi sedno fazy doskonalenia przypadków użycia. Opisując szczegółowo kroki przypadku użycia AnnounceTournament, programiści wraz z klientem podejmują dalsze decyzje dotyczące granicy systemu i tak: • wprowadzone zostają daty graniczne zgłaszania chęci udziału w turnieju i przeprowadzania wchodzących w jego skład meczów (krok 3. w tabeli 4.13), umożliwia to zamknięcie każdego turnieju w rozsądnych granicach czasowych; • reklamodawcy określają w swych aplikacjach, czy są zainteresowani wyłącznym sponsoringiem, czy też nie, umożliwia to kapitanowi ligi bardziej precyzyjny wybór sponsorów (krok 4.); • komunikacja z potencjalnymi sponsorami zarówno w zakresie aplikowania, jak i naliczania opłat jest wysoce zautomatyzowana, co rodzi określone wymagania prawne pod adresem systemu, opisane w polu Wymagania jakościowe przypadku użycia.

5.6. Analiza przypadku — system AREIMA

201

Tabela 4.13. Przykład szczegółowego przypadku użycia Nazwa przypadku

użycia

AnnounceTournament

Aktorzy

uczestniczący

LeagueOwner — inicjuje przypadek użycia. PI ayer, Adverti ser i Spectator — komunikują się z przypadkiem użycia.

Przepływ

zdarzeń

I. LeagueOwner wysyła żądanie zorganizowania nowego turnieju. 2. System sprawdza, czy LeagueOwner nie wykorzystał już przysługującego m u limitu liczby turniejów w lidze lub na arenie; jeśli mieści się w limicie, system wysyła mu formularz do wypełnienia. 3. LeagueOwner wpisuje w formularzu swą nazwę, początkową i końcową datę zgłaszania graczy do turnieju, początkową i końcową datę rozgrywania meczów w ramach turnieju i maksymalną liczbę graczy biorących udział w turnieju. 4. System wysyła do kapitana ligi (LeagueOwner) zapytanie o ewentualny sponsoring wyłączny turnieju i w przypadku decyzji o takim sponsoringu wyświetla mu listę reklamodawców (Adverti s e r ) wyrażających chęć takiego sponsoringu. 5. Jeśli LeagueOwner zdecyduje o poszukiwaniu konkretnego sponsora wyłącznego, wybiera kilka pozycji z proponowanej listy. 6. System powiadamia wybranych sponsorów o nadchodzącym turnieju i o wysokości ryczałtowej opłaty (flatfee) wyłącznego sponsoringu. 7. System przekazuje kapitanowi ligi odpowiedzi otrzymane od zainteresowanych sponsorów. 8. Jeżeli istnieją zainteresowani potencjalni sponsorzy, LeagueOwner wybiera jednego z nich. 9. System rejestruje dokonany wybór nazwy wyłącznego sponsora i obciąża k o n t o wskazanego reklamodawcy opłatą sponsoringową. O d tej chwili w reklamach podczas turnieju pojawiać się będą banery tylko tego jednego sponsora. 10. Jeżeli nie wyłoniono wyłącznego sponsora — z braku chętnych lub rezygnacji kapitana ligi z wyboru — w czasie turnieju wyświetlane będą banery wybierane losowo spośród materiałów wszystkich zarejestrowanych sponsorów, których konta obciążone zostaną wynikającymi stąd opłatami proporcjonalnymi. 11. Gdy załatwione zostaną kwestie sponsoringu, system wyświetla listę graczy, kibiców i reklamodawców zainteresowanych nowym turniejem. 12. LeagueOwner wybiera osoby, które zostaną powiadomione o nowym turnieju. 13. System tworzy stronę główną nowego turnieju stanowiącą punkt startowy dla graczy zamierzających zgłosić swój udział i kibiców zainteresowanych oglądaniem meczów. 14. Gdy nadejdzie data rozpoczęcia aplikacji, system powiadamia o tym wszystkich zainteresowanych użytkowników, przesyłając im link do strony głównej turnieju. Gracze mogą zgłaszać swój udział aż do ustalonego m o m e n t u (przypadek użycia Apply ForTournament).

Rozdział 4. • Zbieranie wymagań

202

Tabela 4.13. Przykład szczegółowego przypadku użycia — ciąg dalszy Warunki

wstępne

0

LeagueOwner jest zalogowany do systemu ARENA.

Warunki

końcowe

«

Wybrano styl sponsorowania turnieju: albo wyświetlanie banerów pochodzących od wyłącznego sponsora, albo losowe wyświetlanie banerów zarejestrowanych w bazie systemu.



Potencjalni gracze zostali powiadomieni o nadchodzącym turnieju i mogą składać zgłoszenia uczestnictwa.

o

Potencjalni kibice otrzymali powiadomienie o nowym turnieju i czasie jego rozpoczęcia.



Strona główna turnieju jest publicznie dostępna, dzięki czemu może być zlokalizowana przez każdego i n t e r n a u t ę bądź to za p o m o c ą wyszukiwarek, bądź też za pośrednictwem strony głównej systemu ARENA.



Komunikacja z reklamodawcami — przyjmowanie ofert i odpowiedzi na oferty — wymaga bezpiecznego uwierzytelniania, stanowi bowiem podstawę do naliczania opłat.



Reklamodawcy powinni mieć możliwość wycofania się z zawartej umowy sponsoringowej, w okresie wyznaczonym przez lokalne uregulowania prawne w stosunku do zdalnych transakcji.

Wymagania

jakościowe

Powyższe decyzje stanowią efekty uzgodnień z klientem, co jest o tyle istotne, że różni klienci, funkcjonujący w różnych realiach, mogą odmiennie rozstrzygać rozmaite kompromisy między sprzecznymi wymaganiami. Przykładowo pełna automatyzacja obsługi sponsoringu, wraz z bezpiecznym uwierzytelnianiem, skutkuje systemem bardziej złożonym, więc droższym. Tańszą alternatywą może być wybór sponsorów za pomocą e-maili, lecz odbieranie ich zobowiązań drogą telefoniczną: system będzie wówczas prostszy, bardziej złożone stają się natomiast działania kapitana ligi. Przy rozstrzyganiu takich dylematów decydujący głos ma klient, zdający sobie — oczywiście — sprawę z wpływu podjętej decyzji na koszt realizacji i termin dostarczenia systemu. Z każdym przypadkiem użycia wiążą się określone sytuacje wyjątkowe, które można zidentyfikować, analizując każdy krok z właściwą podejrzliwością. Opiszemy krótko sytuacje wyjątkowe, jakie mogą wystąpić w związku z przypadkiem użycia AnnounceTournament, który tym samym rozszerzony zostaje (w sensie relacji «extend») o przypadki użycia reprezentujące owe sytuacje (patrz rysunek 4.11 i tabela 4.14). Zwróćmy uwagę, że nie wszystkie z opisanych sytuacji są do siebie podobne — ich zróżnicowane typy predestynują je do rozwiązywania w różnych fazach realizacji projektu. Są zatem wśród nich wyjątki spowodowane wyczerpaniem zasobów (MaxNumberOfTournaments ^•Exceeded), błędnymi danymi wejściowymi (Inval idDate, NamelnUse) i ograniczeniami z zakresu dziedziny aplikacyjnej (Adverti serCredi tExceeded, NoMatchi ngSponsorFound). Obsługę wyjątków związanych z zasobami najlepiej załatwia się na etapie projektu systemu, dopiero wówczas bowiem wiadome jest, które z zasobów podlegają jakim ograniczeniom i jak najefektywniej rozdzielać je między użytkowników. Poprawność wprowadzanych danych to z kolei kwestia interfejsu użytkownika — programiści decydują o tym, w którym momencie przeprowadzać kontrolę wprowadzonych danych, w jakiej postaci wyświetlać związane z tym

5.6. Analiza przypadku — system AREIMA

203

Rysunek 4.11. Sytuacje wyjątkowe związane z przypadkiem użycia AnnounceTournament. Diagram przypadków użycia jest tu taki sam jak na rysunku 4.10 Tabela 4.14. Opis sytuacji wyjątkowych z rysunku 4.11 AdvertiserCreditExceed

InvalidDate

Reklamodawca (Adverti s e r ) wykorzystał swe środki na koncie; system usuwa go z listy potencjalnych sponsorów. LeagueOwner wprowadził niepoprawną datę, system żąda jej ponownego wprowadzenia.

MaxNumberOfTournamentsExceed

LeagueOwner wykorzystał limit przysługującej m u liczby turniejów; przypadek użycia AnnounceTournament zostaje zakończony.

NamelnUse

LeagueOwner wprowadził nazwę turnieju istniejącą już w systemie; system żąda p o n o w n e g o wprowadzenia unikalnej nazwy.

NoMatchi ngSponsorFound

Brak chętnych do wyłącznego sponsoringu; system pomija k r o k wyboru wyłącznego sponsora, w trakcie t u r n i e j u wyświetlane będą banery wybierane losowo z bazy systemu.

komunikaty i jak minimalizować a priori prawdopodobieństwo pomyłek ze strony użytkowników. Trzecia kategoria wyjątków — związanych bezpośrednio z dziedziną aplikacyjną — powinna jak najwcześniej zaprzątać uwagę klienta i programistów, dla tych ostatnich wspomniane wyjątki często nie są oczywiste, a ich przeoczenie skutkuje później kosztownym rewidowaniem pracy już wykonanej. Identyfikację wyjątków tej kategorii należy przeprowadzić przez szczególnie wnikliwą analizę poszczególnych kroków przypadku użycia, przy udziale klienta lub eksperta z dziedziny aplikacyjnej. Wiele sytuacji wyjątkowych może być modelowanych zarówno jako przypadek użycia (Adverti serCreditExceeded), jak i w postaci wymagania pozafunkcyjnego („Reklamodawca może zadłużyć się tylko do ustalonego limitu, uzgodnionego z operatorem podczas rejestracji"). Druga z tych reprezentacji jest bardziej odpowiednia, gdy jako globalne ograniczenie stosuje się do wielu przypadków użycia; pierwsza bardziej nadaje się do zdarzeń mających związek tylko z pojedynczymi przypadkami użycia (NoMatchi ngSponsorFound). Tworzenie szczegółowych przypadków użycia, z uwzględnieniem wszystkich sytuacji wyjątkowych, to zasadnicza część wysiłku związanego ze zbieraniem wymagań. W idealnym przypadku programiści powinni zamknąć zbiór wszystkich przypadków użycia i wyjaśnić

Rozdział 4. • Zbieranie wymagań

204

wszelkie wątpliwości związane z dziedziną aplikacyjną przed uzgodnieniem szczegółów projektu i przystąpieniem do realizacji systemu. W praktyce prawie nigdy nie bywa tak różowo: w przypadku dużych systemów programiści tworzą ogrom dokumentacji, dla której zapewnienie bezwzględnej spójności jest niesamowicie trudne, jeżeli nie niemożliwe. Co gorsza, zbieranie wymagań w związku z dużymi projektami wymaga bieżącego finansowania, pochłania bowiem duże zasoby zarówno po stronie klienta, jak i po stronie firmy programistycznej. Co więcej, przedwczesne zakończenie zbierania wymagań nie wróży nic dobrego na przyszłość — oznaczać może zmaganie się ze zmianami w ramach licznych przypadków użycia wskutek odkrywania nowych faktów z dziedziny aplikacyjnej. Liczba i zakres szczegółowości przypadków użycia to kwestie zaufania i ekonomii: klient i programiści powinni wypracować wspólnie na tyle dobre rozumienie systemu, by odpowiedzialnie określić budżet projektu i harmonogram jego realizacji, a także strategię rozwiązywania przyszłych, nieprzewidzianych trudności związanych ze zmianami wymagań, budżetem czy harmonogramem. Omawiając wymagania systemu ARENA, skupiliśmy się na szczegółach interakcji angażującej graczy (PI ayer) i reklamodawców (Adverti ser), ta interakcja pełni bowiem krytyczną rolę z perspektywy generowania zysków. Przypadki użycia związane z administrowaniem systemu, instalowaniem nowych gier i definiowaniem nowych stylów rozgrywek kwalifikują się do późniejszego omówienia, bo obejmują więcej szczegółów technicznych bliższych dziedzinie realizacyjnej.

4.6.5. Identyfikacja wymagań pozafunkcyjnych Wymagania pozafunkcyjne pochodzą z rozmaitych źródeł. W deklaracji problemu systemu ARENA określone zostały wymagania z zakresu wydajności i implementacji. Podczas uszczegółowiania przypadku użycia AnnounceTournament zidentyfikowaliśmy kolejne, tym razem o charakterze prawnym, związane ze sposobem rozliczania z reklamodawcami. Z kolei przy analizie sytuacji wyjątkowych natrafiliśmy na kolejne wymaganie związane ze zdolnością kredytową reklamodawcy. I choć w ten sposób uwzględniliśmy pewną liczbę ograniczeń pozafunkcyjnych, nie możemy mieć pewności, że wzięliśmy pod uwagę wszystkie istotne dla systemu. By taką pewność uzyskać, odwołamy się do modelu FURPS+, opisanego w sekcji 4.3.2 (każda inna systematyczna taksonomia byłaby zresztą równie dobra w tej roli). W tabeli 4.15 widoczna jest lista kontrolna wymagań pozafunkcyjnych, jakie udało nam się zidentyfikować w związku z uszczegółowianiem przypadku użycia AnnounceTournament.

4.6.6. Wnioski W tej sekcji opracowaliśmy kilka przypadków użycia i stworzyliśmy zaczątki obiektowego modelu analitycznego, bazowaliśmy przy tym na deklaracji problemu dostarczonej przez klienta. Scenariusze i pytania zadawane klientowi posłużyły jako narzędzia do wyjaśniania wątpliwości i niejednoznaczności oraz wykrywania brakującej informacji. Zebraliśmy także kilka wymagań pozafunkcyjnych. Na tej podstawie możemy już wyciągnąć kilka ważnych wniosków. Oto one. • Zbieranie wymagań wymaga nieustannego patrzenia na problem z różnych perspektyw (modelu nisko- i wysokopoziomowego, interesów klienta i interesów programisty, encji i aktywności i tym podobnych).

4.7. Literatura uzupełniająca

205

Tabela 4.15. Skonsolidowana lista wymagań pozafunkcyjnych dla systemu ARENA, jako pochodna pierwszej szczegółowej wersji przypadku użycia AnnounceTournament Kategoria

Wymagania pozafunkcyjne

Użyteczność



Kibice muszą mieć możliwość obserwowania postępu meczu, bez konieczności uprzedniej rejestracji i bez uprzedniej znajomości rozgrywanych turniejów.

Niezawodność



Awaria systemu wskutek błędu w oprogramowaniu może doprowadzić do przerwania co najwyżej jednego turnieju, przy niezakłóconym przebiegu pozostałych.

e

LeagueOwner powinien mieć możliwość wznowienia przerwanego turnieju, przy czym dopuszcza się utratę co najwyżej ostatniego posunięcia gracza w każdym przerwanym meczu. Wydajność

• •

System musi zapewniać jednoczesne prowadzenie wielu równoległych turniejów (na przykład 10), z których każdy obejmuje do 64 graczy i kilkuset kibiców. Do korzystania z systemu musi być wystarczająca łączność za pośrednictwem m o d e m u analogowego.

Wspieralność

o Operator musi mieć możliwość rejestrowania nowych gier i definiowania n o w y c h stylów rozgrywek. Czynności te mogą wymagać chwilowego zamknięcia systemu i dodania do niego nowych modułów (na przykład klas w języku Java), nie mogą jednak powodować konieczności jego modyfikowania.

Implementacja

• •

Użytkownicy powinni mieć dostęp do systemu za pomocą przeglądarki W W W , akceptującej cookies, JavaScript i aplety Javy. Funkcje administracyjne nie powinny być jednak dostępne za pośrednictwem przeglądarki W W W . System powinien działać niezawodnie na dowolnej platformie uniksowej (na przykład MacOS X, Linux czy Solaris).

Operatywność



Reklamodawca może zadłużyć się tylko do ustalonego limitu, uzgodnionego z operatorem podczas rejestracji.

Prawo

o

Komunikacja z reklamodawcami — przyjmowanie ofert i odpowiedzi na oferty — powinna się odbywać w oparciu o bezpieczne uwierzytelnianie, stanowi bowiem podstawę do naliczania opłat.



Reklamodawcy powinni mieć możliwość wycofania się z zawartej umowy sponsoringowej, w okresie w y z n a c z o n y m przez lokalne uregulowania prawne w stosunku do zdalnych transakcji. •

Z b i e r a n i e w y m a g a ń o d b y w a się p r z y z n a c z ą c y m z a a n g a ż o w a n i u k l i e n t a .



P r o g r a m i ś c i n i e p o w i n n i n i g d y z a k ł a d a ć , że d o k ł a d n i e z n a j ą p o t r z e b y k l i e n t a .



Zbieranie wymagań pozafunkcyjnych wymaga d o k u m e n t o w a n i a i rozstrzygania k o n f l i k t ó w ze s t r o n y w s z y s t k i c h u c z e s t n i k ó w .

4.7. Literatura uzupełniająca K o n c e p c j a p r z y p a d k ó w użycia z o s t a ł a s p o p u l a r y z o w a n a p r z e z I. J a c o b s o n a w j e g o s ł y n n e j książce [Jacobson i in., 1992]. Z kolei książka J. M . Carolla [Carroll, 1995], s t a n o w i ą c a rezultat wczesnych b a d a ń n a d w y m a g a n i a m i o p a r t y m i n a scenariuszach i, ogólnie, n a koncepcji projektu

206

Rozdział 4. • Zbieranie wymagań

uczestników, zawiera wiele publikacji czołowych badaczy tematyki scenariuszy i przypadków użycia. W książce tej opisano jednocześnie ograniczenia i pułapki tej metodologii, aktualne po dziś dzień. Spośród przewodników poświęconych konkretnym metodologiom, książka L. L. Constantine'a i L. A. D. Lockwooda [Constantine i Lockwood, 1999] zawiera dużo materiału na temat systemów opisywanych za pomocą przypadków użycia, ze szczególnym uwzględnieniem pozyskiwania mało precyzyjnej wiedzy od użytkowników i klientów — temat ten pomijany jest zwykle milczeniem w podręcznikach z zakresu inżynierii oprogramowania. W książce A. Cockburna [Cockburn, 2001] i na towarzyszącej jej stronie http://www.usecases.org) znaleźć można wiele praktycznych heurystyk tworzenia przypadków użycia w postaci tekstowej (w odróżnieniu od ich rysowania). W procesie zbierania wymagań użytkownicy pełnią rolę krytyczną. D. A. Norman w swej książce [Norman, 2002] ilustruje tę tezę na podstawie zwyczajnych obiektów spotykanych w życiu codziennym — drzwi, pieców i kurków przy kranach. Jest on rzecznikiem tezy, że nie można wymagać od ludzi czytania podręczników ani uczenia się nowych technologii w związku z każdą rzeczą, jaką przychodzi im używać: zamiast tego, odpowiednia wiedza na temat produktu — jak ta, w którą stronę otwiera się drzwi — powinna być organicznie wbudowana w projekt tego produktu. Norman posługuje się powszechnymi codziennymi obiektami, lecz tę samą zasadę należy zastosować do systemów komputerowych i interfejsów użytkownika. Literatura z zakresu inżynierii wymagań okazuje się — niestety — wyjątkowo uboga, gdy chodzi o wymagania pozałunkcyjne. Środowisko NFR, opisane w pracy L. Chunga, B. A. Nixona, E. Yu i J. Mylopoulosa [Chung i in., 1999], jest jedną z niewielu systematycznych metod tej inżynierii. Szablon dokumentu RAD, cytowany w tym rozdziale, jest tylko jednym z przykładów organizowania dokumentów poświęconych wymaganiom. W publikacji [IEEE Std. 830-1998] opisany jest standard specyfikacji wymagań dla systemu informatycznego, zaś w załączniku znaleźć można kilka prostych szkiców opisu konkretnych wymagań. Prezentowane w tym rozdziale przykłady stanowią wyraz dialektycznego podejścia do zbierania wymagań jako procesu dyskusji i negocjacji między programistami, klientem i użytkownikami. Filozofia ta sprawdza się znakomicie, gdy klient jest jednocześnie użytkownikiem lub ma dostatecznie szczegółową wiedzę z zakresu dziedziny aplikacyjnej. W ogromnych systemach, takich jak system kontroli ruchu lotniczego, żaden z użytkowników ani nikt z przedstawicieli klienta nie ma jednak pełnego wyobrażenia o systemie. W tej sytuacji metoda dialektyczna zawodzi, bowiem wiele niejawnych aspektów zachowania użytkowników wychodzi na jaw zbyt późno. W ostatnim dziesięcioleciu w sukurs inżynierii wymagań nieoczekiwanie przyszła gałąź antropologii — etnografia. Zgodnie z tym podejściem, analitycy wtapiają się w społeczność użytkowników, obserwują uważnie ich codzienną prace, uczestniczą w ich zebraniach i tak dalej, zapisując swe obserwacje bez emocjonalnego zaangażowania. Celem tej metody ma być zdobycie wiedzy, którą nawet użytkownicy rzadko sobie uświadamiają. Metoda spójności, opisana w pracy S. Villera i I. Sommerville'a [Viller i Sommerville, 1999], jest praktycznym przykładem zastosowania etnografii w zbieraniu wymagań. Zarządzanie identyfikowalnością wymagań jest wciąż przedmiotem badań; zainteresowanych czytelników odsyłamy do specjalistycznej pracy M. Jarkego [Jarkę, 1998],

4.8. Ćwiczenia

207

Na koniec polecamy książkę M. Jacksona [Jackson, 1995] — zwięzłą, wciągającą, pisaną ciętym językiem lekturę zawierającą wiele interesujących refleksji z zakresu zasad i metod inżynierii wymagań.

4.8. Ćwiczenia 4.1. Potraktuj swój zegarek jako system i skoryguj wyświedany czas o 2 minuty w przód. Opisz każdą swoją interakcję z zegarkiem w postaci scenariusza. Uwzględnij wszystkie interakcje, również te w postaci informacji zwrotnej przekazywanej przez zegarek. 4.2. W scenariuszu stworzonym w zadaniu 4.1 zidentyfikuj aktorów. Następnie utwórz odpowiedni przypadek użycia SetTime. Uwzględnij wszystkie możliwe sytuacje: korygowanie czasu w przód i w tył, korygowanie godzin, minut i sekund. 4.3. Załóżmy, że zegarek, o którym mowa w ćwiczeniach 4.1 i 4.2, wyposażony jest w budzik. Opisz ustawianie alarmu jako niezależny przypadek użycia SetAl armTime. 4.4. Przeanalizuj dokładnie przypadki użycia SetTime i SetAl armTime z ćwiczeń 4.2 i 4.3. Wyeliminuj z nich ewentualną redundancję za pomocą relacji zawierania. Wyjaśnij, dlaczego relacja zawierania jest w tym przypadku bardziej odpowiednia niż relacja rozszerzania. 4.5. Załóżmy, że Fi el dOf f i cer ma do dyspozycji system pomocy online, który dostarcza szczegółowy opis każdego pola oraz wykaz pól obowiązkowo wypełnianych na formularzu raportu o wypadku (EmergencyReport). Stwórz przypadek użycia Hel pReport ^-Emergency reprezentujący tę funkcjonalność, a następnie zmodyfikuj odpowiednio przypadek użycia ReportEmergency (opisany w tabeli 4.5). Jakiej relacji użyjesz do powiązania obu przypadków? 4.6. Które z poniższych wymagań pozafunkcyjnych są weryfikowalne, a które nie? • „System musi być użyteczny", o „System musi w sposób widoczny zareagować na akcję użytkownika w ciągu jednej sekundy od wydania polecenia", « „Dostępność systemu musi kształtować się na poziomie ponad 95%", ® „Interfejs użytkownika w nowym systemie musi być na tyle podobny do interfejsu starego systemu, by użytkownik znający stary system mógł łatwo nauczyć się obsługi nowego". 4.7. Potrzeba stworzenia kompletnej specyfikacji może zachęcać analityków do budowania obszernych i szczegółowych dokumentów. Które z cech wymienionych w tabeli 4.1 mogą skłaniać analityków do tworzenia krótszych specyfikacji? 4.8. Utrzymywanie identyfikowalności w czasie zbierania wymagań i następnych aktywności jest kosztowną procedurą, bowiem konieczne jest zbieranie i przetwarzanie dodatkowych informacji. Jakie korzyści płynące z identyfikowalności usprawiedliwiają ten dodatkowy nakład pracy? Które z tych korzyści są bezpośrednio widoczne dla analityka?

208

Rozdział 4. • Zbieranie wymagań

4.9. Wyjaśnij, dlaczego kwestionariusze z przeważającą ilością pól wielokrotnego wyboru nie są efektywne jako podstawowy środek pozyskiwania informacji od użytkowników w procesie zbierania wymagań. 4.10. Bazując na swoim subiektywnym odczuciu, opisz słabe i mocne strony użytkowników z perspektywy aktywności zbierania wymagań. W podobny sposób opisz słabe i mocne strony programistów w tym procesie. 4.11. Zdefiniuj krótko pojęcie „menu". Porównaj swoją definicję z definicjami wyartykułowanymi przez czterech wybranych studentów. Wyjaśnij, skąd biorą się istotne różnice między wszystkimi pięcioma definicjami. 4.12. Stwórz wysokopoziomowy przypadek użycia ManageAdverti sement inicjowany przez aktora Adverti ser, następnie poddaj go uszczegółowieniu, tworząc serię kolejnych przypadków. Uwzględnij te cechy systemu, które umożliwiają reklamodawcy rejestrowanie w systemie własnych banerów, wiązanie z nimi słów kluczowych, subskrybowanie informacji o wybranych turniejach i ligach oraz monitorowanie stanu obciążeń swojego konta. Upewnij się, że Twoje przypadki użycia są spójne z deklaracją problemu systemu ARENA. 4.13. W związku z przypadkiem użycia AnnounceTournament, przedstawionym w tabeli 4.13, opisz przepływ zdarzeń oraz warunki wstępne i końcowe przypadku użycia ApplyForTournament, inicjowanego przez gracza (Player) zamierzającego uczestniczyć w nowo zorganizowanym turnieju. Nie zapomnij o deklaracji problemu. Napisz listę pytań do klienta w związku z każdą napotkaną alternatywą. 4.14. Opisz przepływ zdarzeń oraz warunki wstępne i końcowe dla „wyjątkowych" przypadków użycia rozszerzających przypadek AnnounceTournament, wymienionych na rysunku 4.11. Tam, gdzie to możliwe, użyj relacji zawierania, by wyeliminować redundancję.

Bibliografia [Bruegge i in., 1994]

B. Bruegge, K. O'Toole, D. Rothenberger „Design considerations for an accident management system," w: M. Brodie, M. Jarke, M. Papazoglou (red.), Proceedings of the Second Conference on Cooperative Information

International

Systems, str. 90 - 100,

University of Toronto Press, Toronto, Canada, m a j 1994. [Carroll, 1995]

J. M. Carroll (red.) Scenario-Based

Design: Envisioning

and Technology in System Development, [Chung i in., 1999]

Work

Wiley, New York, 1995.

L. Chung, B. A. Nixon, E. Yu, J. Mylopoulos

Non-Functional

Requirements in Software Engineering, Kluwer Academic, Boston, 1999. [Cockburn, 2001]

A. Cockburn Writing Effective Use Cases, Addison-Wesley,

Reading,

MA, 2001. [Constantine i Lockwood, 1999]

L. L. Constantine, L. A. D. Lockwood Software for Use, Addison-Wesley, Reading, MA, 1999.

[Grady, 1992]

R. Grady Practical Software Metrics for Project Management and Process Improvement, Prentice Hall, Englewood Cliffs, NJ, 1992.

209

Bibliografia [Hammer i Champy, 1993]

M. Hammer; J. Champy Reengineering The Corporation: a Manifesto

[IEEE Std. 610.12-1990]

IEEE IEEE Standard Computer Dictionary: A Compilation

For Business Revolution, Harper Business, New York, 1993. of IEEE

Standard Computer Glossaries, NY, 1990. [IEEE Std. 830-1998]

IEEE Standard for Software Requirements

Specification, IEEE

Standards Board, 1998. [ISO Std. 9126]

International Standards Organization. Software engineering — Product quality. ISO/IEC-9126, Geneva, Switzerland, 2001.

[Jackson, 1995]

M. Jackson Software Requirements & Specifications: A Lexicon of Practice, Principles and Prejudices, Addison-Wesley, Reading, MA, 1995.

[Jacobson i in., 1992]

I. Jacobson, M. Christerson, P. Jonsson, G. Overgaard Object-Oriented Software Engineering — A Use Case Driven Approach, Addison-Wesley, Reading, MA, 1992.

[Jacobson i in., 1999]

I. Jacobson, G. Booch, J. Rumbaugh The Unified Software Process, Addison-Wesley,

[Jarkę, 1998]

Development

Reading, MA, 1999.

M. Jarke Requirements tracing, „Communications of the ACM", t. 41, nr 12, grudzień 1998.

[Neumann, 1995]

P. G. N e u m a n n Computer-Related Reading, MA, 1995.

[Nielsen, 1993]

J. Nielsen Usability Engineering, Academic, New York, 1993.

[Norman, 2002]

Risks, Addison-Wesley,

D. A. N o r m a n The Design of Everyday

Things, Basic Books,

New York, 2002. [Rational]

Rationale,

http://www.rational.com.

J. Rumbaugh, M. Błaha, W. Premerlani, F. Eddy, W. Lorensen Object[Rumbaugh i in., 1991]

-Oriented Modeling and Design, Prentice Hall, Englewood Cliffs, NJ, 1991. Telelogic,

[Telelogic] [Viller i Sommerville, 1999]

http://www.telelogic.se.

S. Viller, I. Sommerville „Social analysis in the requirements engineering process: from ethnography to method", International Symposium on Requirements Engineering (ISRE'99), Limerick, Ireland, czerwiec 1999.

[Wirfs-Brock i in., 1990]

R. Wirfs-Brock, B. Wilkerson, L. Wiener Designing

Object-Oriented

Software, Prentice Hall, Englewood Cliffs, NJ, 1990. [Wood i Silver, 1989]

J. Wood, D. Silver Joint Application

Design, Wiley, New York, 1989.

5.1.

Wstęp: złudzenie optyczne

212

5.2.

O analizie wymagań ogólnie

212

5.3.

Koncepcje analizy wymagań

214

5.3.1. 5.3.2. 5.3.3.

214 215 216

5.4.

Analityczny model obiektowy i modele dynamiczne Obiekty encji, obiekty brzegowe i obiekty sterujące Generalizacja i specjalizacja

Aktywności analizy wymagań: od przypadków użycia do obiektów 5.4.1. Identyfikacja obiektów encji 5.4.2. Identyfikacja obiektów brzegowych 5.4.3. Identyfikacja obiektów sterujących 5.4.4. Odwzorowywanie przypadków użycia w obiekty za pomocą diagramów sekwencji 5.4.5. Modelowanie interakcji między obiektami za pomocą kart CRC 5.4.6. Identyfikacja skojarzeń 5.4.7. Identyfikacja agregacji 5.4.8. Identyfikacja atrybutów 5.4.9. Modelowanie zachowania poszczególnych obiektów uzależnionego od ich stanu 5.4.10. Modelowanie relacji dziedziczenia między obiektami 5.4.11. Przeglądy modelu analitycznego 5.4.12. Podsumowanie analizy

217 218 220 222

5.5.

Zarządzanie analizą wymagań 5.5.1. Dokumentowanie analizy wymagań 5.5.2. Przydzielanie odpowiedzialności 5.5.3. Komunikacja w związku z analizą wymagań 5.5.4. Iteracje modelu analitycznego 5.5.5. Uzgodnienie modelu analitycznego z klientem

237 238 239 240 241 243

5.6.

Analiza 5.6.1. 5.6.2. 5.6.3. 5.6.4. 5.6.5.

przypadku — system ARENA Identyfikacja obiektów encji Identyfikacja obiektów brzegowych Identyfikacja obiektów sterujących Modelowanie interakcji między obiektami Weryfikacja i konsolidacja modelu analitycznego

245 245 250 251 252 254

5.6.6.

Wnioski

256

5.7.

Literatura uzupełniająca

258

5.8.

Ćwiczenia

258

Bibliografia

260

224 228 228 231 232 233 234 235 236

Analiza wymagań

Mam na imię Foo, na nazwisko... gdybym tylko mógł sobie przypomnieć... — programista o bardzo małym rozumku

ezułtatem analizy jest model wymagań, w intencji twórców poprawny, kompletny, spójny i jednoznaczny. Programiści formalizują specyfikację wymagań, która powstała podczas ich zbierania, i bardziej szczegółowo przyglądają się warunkom granicznym oraz sytuacjom wyjątkowym. Weryfikują wymagania i korygują, gdy znajdą błędy lub niejednoznaczności. Wszelkie zmiany i uzupełnienia odbywają się zwykle w porozumieniu z klientem, który ewentualnie dostarcza dodatkowych informacji. W trakcie analizy zorientowanej obiektowo programiści budują model opisujący dziedzinę aplikacyjną. Przykładowo model analityczny zegarka szczegółowo opisuje sposób wyświetlania przezeń czasu i daty: czy pomiar czasu ma uwzględniać lata przestępne? Czy ma być wyświetlany dzień tygodnia? Fazy Księżyca? Następnie model analityczny rozszerzany jest o opis interakcji między aktorami a systemem, polegających na manipulowaniu obiektami dziedziny aplikacyjnej: w jaki sposób użytkownik zegarka resetuje wskazanie czasu? Jak może resetować wskazanie dnia tygodnia? 1 Programiści wykorzystują model analityczny, wraz z wymaganiami pozafunkcyjnymi, do przygotowania architektury systemu, opracowywanej na etapie projektu wysokiego poziomu (patrz rozdział 6. „Projektowanie systemu — dekompozycja na podsystemy"). W tym rozdziale omówimy szczegółowo aktywności związane z analizą wymagań. Skupimy się na identyfikacji obiektów, ich zachowaniu, klasyfikacji i organizacji. Opiszemy także menedżerskie aspekty związane z analizą w kontekście wielozespołowej realizacji projektu. Na zakończenie zaprezentujemy zastosowanie omówionych koncepcji na konkretnym przykładzie, jakim jest system ARENA, poznany w poprzednim rozdziale.

' Oczywiście, dzień tygodnia da się obliczyć na podstawie aktualnej daty za pomocą prostego algorytmu (jeden z licznych przykładów: http://www.mimuw.edu.pl/delta/artykuly/delta2010-03/2010-03-tydzien.pdf), który — włączony do oprogramowania zegarka — całkowicie oddziela użytkownika od bezpośredniego manipulowania tą częścią wyświetlanej daty. Teoretycznie można sobie jednak wyobrazić prymitywny zegarek, w którym początkowe ustawienie, dokonane przez użytkownika, inkrementowane jest (cyklicznie) z upływem każdej doby — przyp. tłum.

212

Rozdział 4. •Zbieraniewymagań

5.1. Wstęp: złudzenie optyczne W 1915 roku duński psycholog E. Rubin opublikował słynną rycinę ilustrującą koncepcję percepcji dwustabilnej. Co widzimy na rysunku 5.1: dwie spoglądające na siebie twarze czy biały wazon na czarnym tle? Zależnie od własnej interpretacji możemy widzieć raz jedno, raz drugie.

Rysunek 5.1. Przykład niejednoznaczności — co przedstawia ten rysunek?

A gdyby rysunek 5.1 był specyfikacją wymagań, jaki model — wazonu czy konwersacji tete a tete — należałoby zbudować na jej podstawie? Typowa specyfikacja wymagań początkowo ma w sobie coś na kształt takiego właśnie rysunku, co jest konsekwencją zarówno nieprecyzyjnej natury języka naturalnego (w nim formułowane są wymagania), jak i ukrytych założeń, niejawnie (i czasami nieświadomie) przyjmowanych przez autorów tejże specyfikacji. Kilka tego przykładów opisaliśmy już na początku poprzedniego rozdziału, sami też często popełniamy błędy tego rodzaju, na przykład podając znajomym z odległego kraju nasz numer telefonu bez prefiksu +48. Proces formalizowania wymagań ma na celu wykrycie takich właśnie przeoczeń, wieloznaczności i innych uchybień w specyfikacjach, które to uchybienia rozstrzygane są następnie w drodze konsultacji z klientem dostarczającym dodatkowych informacji. W taki oto sposób aktywności zbierania wymagań i ich analizowania sukcesywnie przeplatają się ze sobą.

5.2. O analizie wymagań ogólnie Celem analizy jest stworzenie modelu systemu, zwanego modelem analitycznym — modelu poprawnego, kompletnego, spójnego i weryfikowalnego. Analiza wymagań tym różni się od ich zbierania, że wysiłek programistów skupia się na strukturalizowaniu i formalizowaniu tych wymagań (patrz rysunek 5.2), co prowadzi do spojrzenia na ich zestaw z nowej perspektywy i ułatwia zauważenie zawartych w tym zestawie błędów. Ponieważ model analityczny może nie być dostatecznie zrozumiały dla klienta i użytkowników, na programistach spoczywa

5.2. O analizie wymagań ogólnie

213

Rysunek 5.2. Produkty aktywności zbierania i analizowania wymagań

odpowiedzialność za uaktualnianie specyfikacji wymagań zgodnie z wnioskami wyciągniętymi w trakcie ich analizy, bo dopiero uaktualniona specyfikacja przedstawiana jest klientowi i użytkownikom do weryfikacji. Tak to już w życiu bywa (także w życiu użytkowników i programistów), że trudne decyzje zwykło się odkładać „na potem". Trudności w podejmowaniu decyzji mogą wynikać z niedostatecznej wiedzy z zakresu dziedziny aplikacyjnej, niedostatecznej znajomości poszczególnych technologii lub ze zwykłych nieporozumień między programistami a użytkownikami. Unikanie konfrontacji z realiami przyczynia się, owszem, do szybszego postępu w realizacji projektu — ale tylko do momentu, gdy rzeczone decyzje muszą zostać podjęte, i to znacznie wyższym kosztem niż pierwotnie, bowiem skutki niefrasobliwości dają o sobie znać na etapie testowania lub, co gorsza, już w trakcie eksploatacji systemu. Tłumaczenie specyfikacji wymagań na formalny lub półformalny model zmusza wręcz programistów do rozwiązywania wielu problemów jak najwcześniej. Na model analityczny składają się trzy modele szczegółowe: model funkcjonalny reprezentowany przez przypadki użycia i scenariusze, analityczny model obiektowy w postaci diagramów klas i obiektów oraz model dynamiczny odzwierciedlany jako zbiór diagramów stanów i diagramów sekwencji (patrz rysunek 5.3). W poprzednim rozdziale opisywaliśmy zbieranie wymagań od użytkowników i opisywanie ich w postaci przypadków użycia i scenariuszy; w tym rozdziale pokażemy, jak ulepszać model funkcjonalny i na jego podstawie budować modele obiektowy i dynamiczny — napiszemy, w jaki sposób prowadzi to ku bardziej precyzyjnej i kompletnej specyfikacji, w miarę dodawania nowych szczegółów do modelu analitycznego. Rozdział zakończymy opisem aktywności menedżera, związanych z analizą wymagań. Rozpoczniemy od zdefiniowania podstawowych koncepcji.

Rozdział 4. •Zbieraniewymagań

214

Rysunek 5.3. Model analityczny jako złożenie modelu funkcjonalnego, analitycznego modelu obiektowego i modelu dynamicznego. W języku UML model funkcjonalny reprezentowany jest w postaci diagramów przypadków użycia, model obiektowy — w postaci diagramów klas, a model dynamiczny — w postaci diagramów sekwencji i diagramów stanów

5.3. Koncepcje analizy wymagań W tej sekcji opiszemy podstawowe koncepcje i pojęcia analizy, wykorzystywane w niniejszym rozdziale, czyli: • Analityczny model obiektowy i modele dynamiczne (patrz sekcja 5.3.1), • Obiekty encji, obiekty brzegowe i obiekty sterujące (patrz sekcja 5.3.2), • Generalizację i specjalizację (patrz sekcja 5.3.3).

5.3.1. Analityczny model obiektowy i modele dynamiczne Model analityczny reprezentuje tworzony system z perspektywy jego użytkownika. Analityczny model obiektowy, jako część modelu analitycznego, odzwierciedla indywidualne koncepcje korzystania z systemu, ich właściwości i relacje między nimi. Model ten reprezentowany jest w formie diagramu klas, z uwzględnieniem ich atrybutów i operacji. Stanowi wizualny słownik podstawowych koncepcji użytkownika. Model dynamiczny koncentruje się na zachowaniu systemu. Opisywany jest przez diagramy sekwencji i diagramy stanów. Diagramy sekwencji reprezentują interakcje między obiektami uczestniczącymi w pojedynczym przypadku użycia, zaś każdy diagram stanów opisuje zachowanie pojedynczego obiektu (lub grupy obiektów ściśle ze sobą powiązanych). W ramach modelu dynamicznego widoczne staje się przyporządkowanie zakresów odpowiedzialności poszczególnym klasom, a zwykle także identyfikowanie nowych klas, skojarzeń i atrybutów, o jakie należy wzbogacić analityczny model obiektowy. Należy pamiętać, że zarówno analityczny model obiektowy, jak i model dynamiczny reprezentują koncepcje użytkownika, a nie klasy czy komponenty języka programowania. Klasy w rodzaju Database (baza danych), Subsystem (podsystem), SessionManager (menedżer sesji)

5.3. Koncepcje analizy wymagań

215

czy Network (sieć) nie powinny w ogóle pojawić się w modelu analitycznym z tego prostego względu, że użytkownikowi mogą być zupełnie obce koncepcje reprezentowane przez te klasy. Notabene nie zmienia to w niczym faktu, że większość klas występujących w modelu analitycznym ma swe odzwierciedlenie w postaci klas języka programowania używanego do implementacji systemu, jednak klasy tej drugiej grupy posiadają zazwyczaj więcej atrybutów i uwikłane są w bogatszy układ interakcji niż ich pierwowzory. W konsekwencji klasy modelu analitycznego mogą być uważane za wysokopoziomowe abstrakcje, których ukonkretnienie odbywać się będzie w dalszych stadiach projektu. Na rysunku 5.4 widzimy przykład dobrego i złego wyboru klas dla modelu analitycznego reprezentującego przykładowy zegarek SatWatch.

Rysunek 5.4. Przykłady i kontrprzykłady wyboru klas dla modelu analitycznego zegarka SatWatch

5.3.2. Obiekty encji, obiekty brzegowe i obiekty sterujące I. Jacobson, G. Booch, J. Rumbaugh w swojej książce dzielą obiekty tworzące analityczny model obiektowy na obiekty encji, obiekty brzegowe i obiekty sterujące [Jacobson i in., 1999]. Obiekty encji reprezentują trwałą informację przetwarzaną przez system. Obiekty brzegowe odzwierciedlają interakcje między aktorami a systemem. Za realizację przypadków użycia odpowiedzialne są obiekty sterujące. W przypadku zegarka 2Bwatch, opisywanego w rozdziale 2., obiekty Year, Month i Day są obiektami encji, Button i LCDDi spl ay to obiekty brzegowe, zaś obiektem sterującym jest ChangeDateControl reprezentujący aktywność zmieniania daty, rozpoczynającą się od naciśnięcia odpowiedniej kombinacji przycisków. Podział obiektów modelu na trzy wymienione kategorie dostarcza programistom prostą heurystykę pomagającą w rozróżnieniu odmiennych, choć powiązanych ze sobą koncepcji — i tak na przykład czas odmierzany za pomocą zegarka ma zupełnie inne właściwości niż wyświetlacz służący do jego pokazywania. Różnicę tę uwydatnia odróżnienie obiektów brzegowych od obiektów encji: czas jako taki reprezentowany jest przez obiekt encji Time, zaś reprezentantem wyświetlacza jest obiekt brzegowy LCDDi spl ay. Rozdział między wspomnianymi trzema kategoriami sprawia ponadto, że projekt staje się bardziej elastyczny: interfejs systemu (odzwierciedlany przez obiekty brzegowe) jest bardziej podatny na zmiany niż podstawowa funkcjonalność tego systemu (reprezentowana przez obiekty encji), zatem oddzielając jedno od drugiego, powodujemy, że jedna część modelu staje się niewrażliwa na zmiany dokonywane w ramach drugiej.

216

Rozdział 4. •Zbieraniewymagań

Podczas rozróżniania różnych typów obiektów modelu analitycznego wykorzystywany jest mechanizm stereotypów UML, za pomocą którego programiści dołączają metainformację do modelowanych elementów. Przykład tego widzimy na rysunku 5.5, gdzie obiekt Change ^•DateControl opatrzony został etykietą stereotypu «control». Dodatkowo we wspomnianym rozróżnieniu dopomóc mogą odpowiednie konwencje nazewnicze: obiektom sterującym można w tym celu nadawać nazwy kończące się przyrostkiem Control, obiektom brzegowym — nazwy kończące się przyrostkiem odzwierciedlającym ich naturę, na przykład Form (formularz), Button (przycisk), Di spl ay (wyświetlacz) czy po prostu Boundary; obiekty encji nie wymagają w tym kontekście żadnego specjalnego wyróżniania swoich nazw. Opisana konwencja okazuje się pożyteczna również tam, gdzie nie istnieje odpowiednik stereotypów UML, na przykład w kodzie źródłowym programu.

Rysunek 5.5. Klasy analitycznego modelu obiektowego zegarka 2BWatch. Stereotypy « e n t i t y » , "Control» i «boundary» odpowiadają obiektom (kolejno) encji, sterującym i brzegowym

5.3.3. Generalizacja i specjalizacja Jak już pisaliśmy w rozdziale 2. „Modelowanie w języku UML", relacja dziedziczenia umożliwia hierarchiczne organizowanie koncepcji. Na szczycie („korzeniu") takiej hierarchii znajduje się koncepcja najbardziej ogólna, taka jak Inci dent (wypadek) na rysunku 5.6, na samym dole (w „liściach") koncepcje najbardziej szczegółowe, stanowiące wynik ostatecznej konkretyzacji: CatlnTree (kot na drzewie), TrafficAcccident (wypadek drogowy), BuildingFire (pożar budynku), EarthQuake (trzęsienie ziemi) czy Chemical Leak (skażenie chemiczne). Węzły pośrednie w drzewie hierarchii stanowią wynik mniej lub bardziej zaawansowanej konkretyzacji — LowPriori ty Inci dent (wypadek niewymagający natychmiastowej interwencji), Emergency (niebezpieczeństwo) i Disaster (kataklizm). Dzięki zróżnicowaniu stopnia konkretyzacji, możemy odwoływać się do określonych koncepcji w sposób precyzyjny: używając słowa „wypadek" (lub klasy Inci dent), mamy na myśli wszelkie rodzaje wypadków, zaś słowo „niebezpieczeństwo" (i klasa Emergency) odnosi się tylko do określonej ich kategorii (wypadków wymagających pilnej interwencji). Generalizowaniem nazywamy w modelowaniu aktywność identyfikowania abstrakcyjnych koncepcji na podstawie przykładów ich konkretyzacji. Załóżmy na przykład, że próbujemy opisywać działanie istniejącego systemu zarządzania wypadkami: właśnie przeanalizowaliśmy dialog i ekrany związane z wypadkiem drogowym, potem dialog i ekrany związane z pożarem, a teraz na podstawie elementów wspólnych dla obu kategorii zdarzeń definiujemy abstrakcyjną kategorię „niebezpieczeństwo", której wypadek drogowy i pożar są konkretyzacjami.

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

217

Rysunek 5.6. Przykład generalizowania koncepcji: w korzeniu drzewa reprezentowana jest koncepcja najbardziej ogólna, w jego liściach koncepcje najbardziej szczegółowe

Specjalizowanie to aktywność odwrotna, czyli identyfikowanie koncepcji bardziej specyficznych na podstawie koncepcji wysokopoziomowej. Załóżmy mianowicie, że tworzymy „od zera" system zarządzania wypadkami i właśnie dyskutujemy z klientem elementy funkcjonalne tego systemu. Klient najpierw wprowadza nas w ogólną koncepcję wypadku (Inci dent), po czym wyróżnia trzy kategorie wypadków: kataklizm (Di saster) wymagający współdziałania wielu agencji, niebezpieczeństwo (Emergency) jako wypadek wymagający pilnej interwencji, lecz ze strony pojedynczej agencji, oraz wypadek mało istotny (LowPri ori t y l n c i dents), w sprawie którego interwencję można odłożyć na później, jeżeli zasoby potrzebne są aktualnie do obsługi wypadków bardziej pilnych. W obu przypadkach — generalizowania i specjalizowania — mamy do czynienia z relacją dziedziczenia między koncepcjami, dlatego programiści często nazywają relacje dziedziczenia relacjami „ogół-szczegóły". W tej książce używać będziemy terminu „dziedziczenie" na określenie relacji między klasami, natomiast określenia „generalizowanie" i „specjalizowanie" (lub krótko: „generalizacja" i „specjalizacja") stosować będziemy na oznaczenie aktywności modelowania wynikających z relacji dziedziczenia.

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów W tej sekcji opiszemy aktywności związane z transformowaniem przypadków użycia i scenariuszy (utworzonych w trakcie zbierania wymagań) w model analityczny. Wspomniane aktywności obejmują: • Identyfikację obiektów encji (patrz sekcja 5.4.1), ® Identyfikację obiektów brzegowych (patrz sekcja 5.4.2), • Identyfikację obiektów sterujących (patrz sekcja 5.4.3), » Odwzorowywanie przypadków użycia w obiekty za pomocą diagramów sekwencji (patrz sekcja 5.4.4),

218

Rozdział 4. •Zbieraniewymagań

• Modelowanie interakcji między obiektami za pomocą kart CRC (patrz sekcja 5.4.5), • Identyfikację skojarzeń (patrz sekcja 5.4.6), • Identyfikację agregacji (patrz sekcja 5.4.7), • Identyfikację atrybutów (patrz sekcja 5.4.8), • Modelowanie zachowania poszczególnych obiektów uzależnionego od ich stanu (patrz sekcja 5.4.9), • Modelowanie relacji dziedziczenia między obiektami (patrz sekcja 5.4.10), • Przeglądy modelu analitycznego (patrz sekcja 5.4.11). Każdą z wymienionych aktywności zilustrujemy przykładem przypadku użycia Report ^•Emergency systemu FRIEND opisywanego w rozdziale 4. „Zbieranie wymagań" . Aktywności te sterowane są w większości przez heurystyki, a jakość powstających w ich wyniku produktów uzależniona jest w dużej mierze od doświadczenia programisty stosującego owe heurystyki. Prezentowane w tej sekcji metody i heurystyki zaczerpnęliśmy z pracy T. De Marco [De Marco, 1978] i prac zbiorowych [Jacobson i in., 1999], [Rumbaugh i in., 1991] i [Wirfs-Brock i in., 1990].

5.4.1. Identyfikacja obiektów encji Obiekty uczestniczące w przypadku użycia (patrz sekcja 4.4.6) formują podstawę modelu analitycznego. Obiekty te, jak pisaliśmy w rozdziale 4. „Zbieranie wymagań", odnajduje się drogą uważnej analizy każdego przypadku użycia. Gdy przypadek ten opisywany jest w języku naturalnym, kilka prostych heurystyk, zwanych heurystykami Abbotta od nazwiska ich autora [Abbott, 1983], w sposób intuicyjny może dopomóc w zidentyfikowaniu obiektów, atrybutów i skojarzeń między nimi na podstawie specyfikacji wymagań. Heurystyki Abbotta dokonują wprost odwzorowania części mowy języka angielskiego (rzeczowników, czasowników, przymiotników) na potencjalne komponenty modelu (obiekty, operacje, relacje dziedziczenia i klasy). W tabeli 5.1 prezentujemy przykład takiego odwzorowania dla przypadku użycia ReportEmergency, przedstawionego w tabeli 5.2. Tabela 5.1. Heurystyki Abbotta, odwzorowujące części mowy w modele k o m p o n e n t u

Część mowy lub zdania

Komponent modelu

Przykład z systemu FRIEND

Rzeczownik własny

Obiekt

Alice

Rzeczownik pospolity

Klasa

FieldOfficer

Czasownik — czynność

Operacja

c r e a t e s („tworzy"), submits („wysyła"), s e l e c t s („wybiera")

Czasownik — bycie

Dziedziczenie

I s a kind of ... („jest r o d z a j e m . . . " ) , i s one of e i t h e r . . . („jest jednym z ...")

Czasownik — posiadanie

Agregacja

Has („posiada"),consists of ... („składa się i . . . " ) , i ncl udes ... („zawiera")

Czasownik modalny — imperatyw

Ograniczenie

Must be ... („musi być ...")

Przymiotnik, przydawka

Atrybut

Incident d e s c r i p t i o n („opis wypadku")

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

219

Tabela 5.2. Przykładowy przypadek użycia ReportEmergency (w wariancie jednokolumnowym) Nazwa przypadku

użycia

ReportEmergency

Warunek

wstępny

1. F i e l d O f f i c e r aktywuje f u n k c j ę „Report Emergency" na swym terminalu.

Przepływ

zdarzeń

2. System FRIEND wyświetla w odpowiedzi stosowny formularz. Formularz ten zawiera pola określające między innymi rodzaj niebezpieczeństwa (ogólne, pożar, komunikacyjne), lokalizację zdarzenia, jego opis, żądanie zasobów i udział materiałów niebezpiecznych. 3. Fi el dOf f i cer wypełnia f o r m u l a r z z obowiązkowymi p o l a m i reprezentującymi typ zdarzenia i jego krótki opis. Fi el dOf f i cer określa także możliwe sposoby pierwszej reakcji na zagrożenie i w y m a g a n e do tej reakcji zasoby. Po wypełnieniu f o r m u l a r z a Fi el dOf f i cer wysyła go do systemu, klikając przycisk Send report. W rezultacie system FRIEND powiadamia dyspozytora (Di spatcher) 0 zdarzeniu. 4. Di s p a t c h e r analizuje o t r z y m a n e od Fi el dOf f i c e r a i n f o r m a c j e zawarte w formularzu, po czym tworzy nowy obiekt Inci dent w bazie, realizując przypadek użycia Open Inci dent. Di spatcher określa sposób reakcji, przyporządkowuje niezbędne zasoby (zgodnie z przypadkiem użycia Al 1 ocateResources) i zatwierdza raport, wskutek czego system FRIEND i n f o r m u j e aktora F i e l d O f f i c e r o zatwierdzeniu r a p o r t u 1 przekazuje mu informację od dyspozytora.

Warunki

końcowe

5. Fi el dOf f i cer otrzyma! potwierdzenie od dyspozytora.

Analiza opisu przypadków użycia w języku naturalnym ma tę zaletę, iż język ten odzwierciedla świat widziany z perspektywy użytkownika. Ma ona jednak wiele ograniczeń, z których najważniejsze są dwa. Po pierwsze, jakość wynikowego modelu w dużej mierze zależy od stylu pisarskiego analityka (spójności używanej terminologii, transformacji między częściami mowy i tym podobnych). Języka naturalny jest bardzo nieprecyzyjnym narzędziem i model tworzenie modelu obiektowego literalnie na bazie nieprecyzyjnego opisu niesie ze sobą ryzyko, iż model ten będzie równie nieprecyzyjny. Programiści starają się unikać tego ryzyka poprzez odpowiednie przeredagowywanie specyfikacji wymagań w taki sposób, by używana w niej terminologia była w jak najwyższym stopniu zestandaryzowana. Drugi mankament wynika z bogactwa i redundancji samego języka naturalnego: liczba rzeczowników jest znacznie większa od liczby rzeczy identyfikowanych przez te rzeczowniki. Na określenie danego przedmiotu, koncepcji, zjawiska i tak dalej istnieje zwykle wiele synonimów; zidentyfikowanie, sortowanie i utożsamianie owych synonimów w obszernej specyfikacji wymagań jest procesem niezwykle pracochłonnym. Dlatego heurystyki Abbotta nadają się raczej do wstępnego określenia obiektów kandydatów na rzeczywiste obiekty modelu na podstawie krótkich opisów, na przykład opisów przepływu zdarzeń w scenariuszach i przypadkach użycia. W uzupełnieniu do heurystyk Abbotta można polecić kilka następujących:

Rozdział 4. •Zbieraniewymagań

220

Heurystyki pomocne w identyfikowaniu obiektów encji W ś r ó d obiektów modelu wyróżnić można między innymi: •

terminy, które programiści muszą uzgodnić z u ż y t k o w n i k a m i w celu r o z u m i e n i a p r z y p a d k ó w użycia,



rzeczowniki powtarzające się w przypadkach użycia ( I n c i d e n t ) ,



encje świata rzeczywistego odzwierciedlone w systemie ( F i e l d O f f i c e r , Di spatcher, Resource),

® procesy świata rzeczywistego odzwierciedlone w systemie (EmergencyOperationsPlan), •

miejsca generowania i powstawania danych (Pri n t e r ) .

Programiści przyporządkowują obiektom nazwy i krótkie opisy, określają też ich atrybuty i znaczenie każdego z nich. Używanie unikalnych nazw dla obiektów przyczynia się do promowania standardowej terminologii. Dla obiektów encji zalecamy bezwzględnie wykorzystywanie nazw, którymi posługują się użytkownicy i specjaliści z dziedziny aplikacyjnej; opisy obiektów, nawet krótkie, pozwalają programistom na wyjaśnienie koncepcji reprezentowanej przez te obiekty i unikanie nieporozumień (wynikających na przykład z używania tego samego obiektu do reprezentowania koncepcji powiązanych ze sobą, choć całkowicie różnych). Programiści nie powinni jednak ustalać nadmiernej szczegółowości obiektów zbyt wcześnie, gdy proces analizy jest jeszcze tak naprawdę w stadium nieustalonym: powinni oni dokumentować te atrybuty i koncepcje, które nie są oczywiste; dla pozostałych obiektów wystarczające okażą się nazwy (być może tymczasowe) i treściwe opisy. Potrzebne będzie jeszcze wiele iteracji, w ramach których zmieniać się będą zarówno funkcje realizowane przez poszczególne obiekty, jak i prawdopodobnie nazwy tychże obiektów. Gdy jednak model analityczny stanie się już stabilny, opis każdego obiektu powinien być tak szczegółowy, jak tylko jest to konieczne (powrócimy do tej kwestii w sekcji 5.4.11). I tak na przykład w przypadku użycia prezentowanym w tabeli 5.2 wykorzystaliśmy wiedzę z zakresu dziedziny aplikacyjnej i wywiady z użytkownikami do identyfikacji klas Dispatcher, EmergencyReport, F i e l d O f f i c e r i Incident. Zauważmy przy tym, że obiekt klasy EmergencyReport nie jest nigdzie przywoływany explicite przez nazwę, a jedynie w formie opisowej („otrzymane od Fi el dOf f i cera informacje zawarte w formularzu"); po konsultacjach z klientem dowiadujemy się, że taka informacja nazywana jest potocznie „raportem o niebezpieczeństwie" (emergency report) i decydujemy się na reprezentowanie jej w postaci stosownego obiektu, którego klasę nazywamy EmergencyReport. Zebrana w ten sposób wiedza prowadzi do początkowej postaci modelu analitycznego, którego obiekty encji przedstawiamy w tabeli 5.3 Zauważmy, że modelowi temu bardzo daleko do kompletnego opisu systemu implementującego przypadek użycia ReportEmergency. W następnej sekcji zajmiemy się obiektami brzegowymi przypadku użycia Report ^-Emergency.

5.4.2. Identyfikacja obiektów brzegowych Obiekty brzegowe stanowią reprezentację interfejsu systemu dla współdziałających z nim aktorów. W każdym przypadku użycia każdy aktor przejawia interakcję z przynajmniej jednym obiektem brzegowym. Obiekty brzegowe kolekcjonują informację otrzymywaną od aktorów i przekształcają ją na formę wykorzystywaną przez obiekty encji i obiekty sterujące. Zbiór

Tabela 5.3. Obiekty encji uczestniczące w przypadku użycia Report Emergency Dispatcher

Dyspozytor, oficer policji zarządzający wypadkami (Inci dent). Di spatcher dokonuje otwierania, dokumentowania i zamykania obsługi wypadków, zgodnie z raportami (EmergencyReport) i inną informacją pochodzącą od funkcjonariusza (Fi el dOffi cer). Dispatcher identyfikowany jest na podstawie numeru odznaki.

EmergencyReport

Początkowy raport o wypadku, sporządzony przez funkcjonariusza (Fi el dOffi cer) dla dyspozytora (Di spatcher). Raport taki zwykle powoduje utworzenie nowego obiektu klasy Incident przez dyspozytora. Podstawowymi elementami raportu są zwykle: stopień zagrożenia, typ wypadku (pożar, wypadek drogowy, inne zdarzenie), wskazanie miejsca wypadku, krótki opis sytuacji.

Fi el dOffi cer

Funkcjonariusz na służbie. Dany funkcjonariusz może być w danej chwili przydzielony do co najwyżej jednego wypadku (Inci dent). Funkcjonariusze (Fi el dOffi cer) identyfikowani są na podstawie numeru odznaki.

Incident

Wypadek, sytuacja wymagająca interwencji funkcjonariusza (Fi el dOffi cer). Wypadek może zostać zgłoszony do systemu przez funkcjonariusza lub przez inną osobę pozostającą na zewnątrz tego systemu. Informacja o wypadku (Incident) obejmuje jego opis, sposób reakcji, status (otwarty, zamknięty, udokumentowany), lokalizację i identyfikację funkcjonariuszy (Fi el dOffi cer) przydzielonych do obsługi.

o b i e k t ó w b r z e g o w y c h m o d e l u m o ż e być u w a ż a n y za przybliżenie interfejsu u ż y t k o w n i k a — „przybliżenie", b o w i e m obiekty brzegowe p o w i n n y egzystować w o d e r w a n i u od wizualnych aspektów tego interfejsu: opcja m e n u czy pasek przewijania nie są d o b r y m i k a n d y d a t a m i n a obiekty brzegowe, b o w i e m n a w i ą z u j ą już d o szczegółów wizualnych w s p o m n i a n e g o interfejsu. T e j e d n a k ustalane są w s t ę p n i e z k l i e n t e m za p o m o c ą szkiców, makiet, prototyp ó w i t y m p o d o b n y c h , i m o g ą się znacząco zmieniać w konsekwencji testów użyteczności nawet wtedy, gdy specyfikacja f u n k c j o n a l n a systemu będzie już stabilna. Zbyt wczesne uzależnienie m o d e l u o d szczegółów p o d a t n y c h n a i n t e n s y w n e z m i a n y p o w o d o w a ł o b y w k o n sekwencji k o n i e c z n o ś ć j e g o rewizji p o k a ż d e j takiej zmianie, co — m i m o z n a c z n e g o z a a n g a ż o w a n i a czasu i wysiłku — nie p r z y n o s i ł o b y p r a k t y c z n i e ż a d n y c h korzyści. Heurystyki p o m o c n e w identyfikowaniu o b i e k t ó w b r z e g o w y c h Prawdopodobnymi kandydatami na obiekty brzegowe modelu są między innymi: •

kontrolki interfejsu użytkownika, za pomocą których aktorzy inicjują przypadki użycia (na przykład ReportEmergencyButton — przycisk, którego kliknięcie powoduje wysłanie raportu EmergencyReport);



formularze, za pomocą których użytkownicy wprowadzają dane do systemu (na przykład EmergencyReportForm — formularz, w którym Fi el dOffi cer umieszcza podstawowe informacje o zaistniałym wypadku);



powiadomienia i komunikaty wysyłane przez system do użytkowników (na przykład Acknowl edgmentNotice — potwierdzenie, jakie Di s p a t c h e r wysyła do Fi el dOffi cera po przyjęciu raportu o niebezpieczeństwie);



terminale wykorzystywane przez aktorów uczestniczących w przypadkach użycia do kontaktowania się z interfejsem systemu (na przykład Di s p a t c h e r S t a t i on — stacja robocza dyspozytora).

Należy unikać wiązania obiektów brzegowych z wizualnymi aspektami interfejsu użytkownika. W celu opisywania obiektów brzegowych należy bezwzględnie posługiwać się terminologią użytkownika, unikając terminów pochodzących z dziedziny realizacyjnej i dziedziny implementacyjnej.

W tabeli 5.4 przedstawiamy obiekty brzegowe zidentyfikowane na podstawie przypadku użycia ReportEmergency. Tabela 5.4. Obiekty brzegowe przypadku użycia ReportEmergency AcknowledgmentNoti ce

Powiadomienie, którego efektem jest wyświetlenie na laptopie Fi el dOffi cera potwierdzenia wysłanego przez dyspozytora (Di spatcher) z jego stacji roboczej (Di s p a t c h e r S t a t i on).

Di s p a t c h e r S t a t i o n

Komputer (stacja robocza) używana przez dyspozytora (Di spatcher).

ReportEmergencyButton

Przycisk, którego kliknięcie przez Fi el dOffi cera inicjuje przypadek użycia ReportEmergency.

EmergencyReportForm

Formularz służący dostarczeniu informacji dla raportu o niebezpieczeństwie (EmergencyReport). Formularz ten wyświetlony zostaje na laptopie Fi el dOffi cera, gdy ten aktywuje funkcję „Report Emergency". Formularz EmergencyReportForm zawiera pola umożliwiające określenie wartości wszystkich atrybutów raportu EmergencyReport oraz przycisk (lub inną kontrolkę) przeznaczony do wysłania formularza po jego wypełnieniu.

FieldOfficerStation

Laptop używany przez Fi el dOffi cera.

IncidentForm

Formularz wykorzystywany do utworzenia nowego obiektu Inci dent reprezentującego wypadek. Formularz ten wyświetlany jest na stacji roboczej dyspozytora (Di s p a t c h e r S t a t i o n ) po dotarciu do tej stacji raportu EmergencyReport. Dyspozytor ( D i s p a t c h e r ) używa tego formularza także w celu dokonania przydziału zasobów oraz w celu wysłania potwierdzenia do Fi el dOffi cera.

Zwróćmy uwagę, że obiekt Inci dentForm nie występuje jawnie w przypadku użycia ReportEmergency. Zidentyfikowaliśmy jego istnienie, uświadamiając sobie, że Dispatcher potrzebuje odpowiedniego interfejsu do przeglądania raportu przysłanego przez Fi el dOffi cera i do odesłania mu potwierdzenia. Zauważmy także, iż nazwa tego obiektu odzwierciedla wyłącznie jego funkcję, abstrahując od konkretnej realizacji — ta stanowi bowiem część dziedziny realizacyjnej, o której nie da się po prostu rozmawiać wyłącznie językiem użytkownika. Identyfikując obiekty encji i obiekty brzegowe, dokonaliśmy pewnego postępu w opisywaniu przyszłego systemu — dysponujemy już bowiem zdefiniowanym interfejsem między aktorami a systemem. Nie dysponujemy jednak jeszcze innymi istotnymi informacjami, takimi jak na przykład kolejność, w jakiej następują poszczególne interakcje aktorów z systemem. Zajmijmy się więc trzecią kategorią obiektów odpowiedzialnych za ten i wiele innych aspektów systemu — obiektami sterującymi.

5.4.3. Identyfikacja obiektów sterujących Obiekty sterujące odpowiedzialne są za koordynowanie obiektów brzegowych z obiektami encji. Nie posiadają widocznych odpowiedników w świecie rzeczywistym, istnieją raczej w świecie przypadków użycia. Najczęściej obiekt sterujący tworzony jest podczas inicjowania przypadku użycia i przestaje istnieć dopiero w momencie, gdy sam przypadek użycia kończy się. Obiekt sterujący odpowiedzialny jest za kolekcjonowanie informacji pochodzącej od obiektów brzegowych i przekazywanie tej informacji odpowiednim obiektom encji.

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

223

W naszym przykładowym przypadku użycia Report Emergency utworzymy początkowo osobne obiekty sterujące dla poszczególnych aktorów, odpowiednio ReportEmergencyControl dla aktora Fi el dOffi cer i ManageEmergencyControl dla aktora Di spatcher (patrz tabela 5.5). Tabela 5.5. Obiekty sterujące dla przypadku użycia ReportEmergency ReportEmergencyControl

Zarządza f u n k c j a m i r a p o r t u j ą c y m i na laptopie f u n k c j o n a r i u s z a (Fi el dOffi c e r S t a t i on). Jest tworzony w momencie, gdy Fi el dOffi cer kliknie przycisk „Report Emergency". Następnie powoduje utworzenie obiektu formularza RemergencyReportForin i wyświetlenia go na ekranie wspomnianego laptopa. Po zatwierdzeniu wspomnianego formularza przez Fi el dOffi cera niniejszy obiekt kolekcjonuje informację pochodzącą z tego formularza i przekazuje ją dyspozytorowi (Di spatcher), po czym oczekuje na otrzymanie potwierdzenia. Gdy potwierdzenie to nadejdzie, niniejszy obiekt tworzy obiekt A c k n o w l e d g m e n t s i c e i wyświetla go na ekranie laptopa Fi el dOffi cera.

ManageEmergencyControl

Zarządza f u n k c j a m i r a p o r t u j ą c y m i na stacji roboczej dyspozytora (Di s p a t c h e r S t a t i on). Obiekt ten t w o r z o n y jest w m o m e n c i e , gdy do stacji roboczej dyspozytora dotrze raport EmergencyReport. Niniejszy obiekt tworzy następnie obiekt Inci dentForm i wyświetla go na ekranie stacji dyspozytora. Gdy Di s p a t c h e r utworzy nowy obiekt Inci dent, przydzieli odpowiednie zasoby ( R e s o u r c e s ) i wyśle potwierdzenie, niniejszy obiekt przekazuje to potwierdzenie do laptopa funkcjonariusza (Fi el dOffi c e r S t a t i on).

Decyzja o modelowaniu przepływu sterowania w ramach przypadku użycia Report ^•Emergency za pomocą dwóch obiektów sterujących wynika z faktu, że Fi el dOffi cerStati on i Di spatcherStati on to dwa podsystemy komunikujące się ze sobą poprzez asynchroniczne łącze. Tę decyzję można było — co prawda — odłożyć do etapu projektowania systemu, jednak uwidocznienie już na etapie analizy koncepcji komunikowania się przez asynchroniczne łącze pozwala uwzględnić w modelu analitycznym istotną sytuację wyjątkową, jaką jest utrata połączenia nawiązanego między obiema stacjami. Heurystyki pomocne w identyfikowaniu obiektów sterujących •

Zidentyfikuj jeden obiekt sterujący dla każdego przypadku użycia.



W każdym z przypadków użycia zidentyfikuj po jednym obiekcie sterującym dla każdego aktora biorącego udział w tym przypadku.



Czas życia obiektu sterującego powinien rozciągać się na cały przypadek użycia albo na sesję pojedynczego użytkownika. Jeśli trudno zidentyfikować momenty tworzenia i likwidowania obiektu sterującego, prawdopodobnie niezbyt precyzyjnie zdefiniowano warunki początkowe i końcowe dla przypadku użycia.

Tak oto, modelując przypadek użycia ReportEmergency w kategoriach obiektów encji, brzegowych i sterujących, i spoglądając na ów przypadek z innej perspektywy — strukturalizacji, a nie przepływu zdarzeń — zwiększyliśmy poziom jego szczegółowości i ustanowiliśmy standardową terminologię ułatwiającą odwoływanie się do podstawowych encji dziedziny aplikacyjnej i systemu. A przecież wciąż modelujemy tę samą funkcjonalność.

224

Rozdział 4. •Zbieraniewymagań

5.4.4. Odwzorowywanie przypadków użycia w obiekty za pomocą diagramów sekwencji Istotą diagramu sekwencji jest powiązanie przypadków użycia z obiektami. Diagram taki pokazuje, jak zachowanie przypadku użycia (lub scenariusza) rozkłada się na poszczególne obiekty. Diagramy sekwencji nie są raczej tak dobrym medium komunikacji z użytkownikami jak przypadki użycia, stanowią bowiem egzemplifikację pewnej formalnej notacji, której zrozumienie wymaga dodatkowej wiedzy; dla użytkowników posiadających taką wiedzę mogą być jednak bardziej precyzyjnym opisem niż przypadki użycia. Tak czy inaczej, diagramy sekwencji reprezentują kolejny punkt spojrzenia na funkcjonalność systemu i pomagają programistom odnajdywać przeoczone dotychczas obiekty lub niejasności w specyfikacji wymagań. W tej sekcji zbudujemy model ukazujący sekwencje interakcji niezbędnych do realizacji przypadku użycia. Na rysunkach 5.7, 5.8 i 5.9 widoczne są fragmenty diagramu sekwencji dla przypadku użycia ReportEmergency. Poszczególne kolumny diagramu sekwencji reprezentują poszczególne obiekty uczestniczące w przypadku użycia; skrajna lewa kolumna dedykowana jest aktorowi inicjującemu ów przypadek. Poziome strzałki reprezentują bodźce i komunikaty przesyłane między obiektami. Kierunek pionowy — z góry na dół — odzwierciedla upływ czasu. Przykładowo pierwsza strzałka na rysunku 5.7 reprezentuje komunikat press wysłany przez aktora F i e l d O f f i c e r do obiektu ReportEmergencyButton. Odebranie komunikatu przez obiekt ReportEmergencyButton powoduje zainicjowanie (aktywowanie) przezeń odpowiedniej operacji; operacja ta reprezentowana jest przez pionowy prostokąt, z którego wychodzić mogą kolejne strzałki odpowiadające kolejnym komunikatom. Wysokość prostokąta odpowiada czasowi trwania rzeczonej operacji. Na rysunku 5.7 operacja zainicjowana przez komunikat press powoduje wysłanie komunikatu create do klasy ReportEmergencyControl. Operację tę można uważać za rodzaj usługi, jaką obiekt świadczyć może na rzecz innych obiektów. Na diagramach sekwencji uwidoczniony jest także czas życia obiektów. Obiekty, które istnieją już przed zainicjowaniem przypadku użycia przez aktora, rysowane są u samej góry diagramu. Obiekty tworzone w wyniku interakcji wskazywane są przez strzałki odnośnych komunikatów, opatrzone etykietą «creates. Unicestwienie instancji obiektu symbolizowane jest przez znak przekreślenia, umieszczony na odpowiedniej wysokości, odpowiadającej momentowi, w którym unicestwienie to następuje. Między prostokątem reprezentującym obiekt a znakiem przekreślenia oznaczającym jego unicestwienie (lub dolną linią przypadku użycia, gdy obiekt nie jest niszczony w trakcie jego trwania) rozciąga się pionowa linia przerywana, reprezentująca przedział czasu, w którym dany obiekt może przyjmować komunikaty; oczywiście, linia taka nie może wystąpić poniżej znaku przekreślenia. Na rysunku 5.7 obiekt klasy ReportEmergency "-^Form tworzony jest w wyniku odebrania przez tę klasę komunikatu «create» wysłanego przez obiekt ReportEmergencyControl. Obiekt ReportEmergencyForm przestaje istnieć zaraz po tym, jak formularz ReportEmergencyForm zostaje wysłany (niszczy go ten sam obiekt, który go utworzył). Generalnie druga kolumna diagramu sekwencji reprezentuje obiekt brzegowy, z którym współdziała aktor inicjujący przypadek użycia; na rysunku 5.7 jest to obiekt ReportEmergency ^•Button. Trzecia kolumna przeznaczona jest na obiekt sterujący, zarządzający całą resztą przypadku (ReportEmergencyControl na rysunku 5.7). Ów obiekt sterujący tworzy inne obiekty brzegowe i może współdziałać z innymi obiektami sterującymi (na rysunku 5.7 z obiektem ManageEmergencyControl).

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

225

Rysunek 5.7. Diagram sekwencji dla przypadku użycia ReportEmergency

Rysunek 5.8. Diagram sekwencji dla przypadku użycia ReportEmergency (kontynuacja rysunku 5.7)

W diagramie na rysunku 5.8widoczny jest obiekt encji Acknowl edgment, o którym pierwotnie zapomnieliśmy (patrz tabela 5.3). Obiekt Acknowledgment różni się od obiektu AcknowledgmentNotice: ten pierwszy reprezentuje informację związaną z potwierdzeniem raportu i tworzony jest wcześniej od tego drugiego. Przy okazji obiektu Acknowledgment uświadamiamy sobie ponadto, że opis przypadku użycia ReportEmergency w tabeli 5.2, przedstawiony na rysunku 5.6, jest niekompletny: wspomina się w nim jedynie o istnieniu potwierdzenia raportu, nie precyzując, jaka informacja ma być w nim zawarta. Po konsultacjach z klientem w tej kwestii dodajemy obiekt Acknowledgment do modelu analitycznego (patrz tabela 5.6), a przypadek użycia ReportEmergency rozszerzamy o kolejny szczegół (patrz tabela 5.7)

226

Rozdział 4. •Zbieraniewymagań

Rysunek 5.9. Diagram sekwencji dla przypadku użycia ReportEmergency (kontynuacja rysunku 5.8) Tabela 5.6. Obiekt Acknowl edgment przypadku użycia ReportEmergency Acknowl edgment

Potwierdzenie, odpowiedź dyspozytora (Di spatcher) na raport EmergencyReport wysłany przez Fi el dOffi cera. Za pomocą tego potwierdzenia Di spatcher oznajmia Fi el dOffi cerowi, że otrzymał raport, utworzył w bazie reprezentujący go obiekt i przydzielił zasoby do jego obsługi. Potwierdzenie zawiera też wyszczególnienie przydzielonych zasobów oraz oszacowanie czasu oczekiwania na ich przybycie.

Konstruując diagramy sekwencji, modelujemy nie tylko kolejność interakcji między obiektami, lecz także podział zachowania systemu między przypadki użycia. W ten sposób przypisujemy każdemu obiektowi swoisty zakres odpowiedzialności, w postaci określonego zbioru operacji. Operacje te mogą być współdzielone między wszystkie przypadki użycia, w których dany obiekt uczestniczy; stąd bardzo ważne zastrzeżenie: definicja obiektu uczestniczącego w kilku przypadkach użycia powinna być identyczna we wszystkich, innymi słowy, jeśli dana operacja danego obiektu pojawia się w kilku przypadkach użycia, jej zachowanie powinno być w każdym z nich takie samo. Współdzielenie operacji między kilka przypadków użycia umożliwia programistom redukowanie redundancji w specyfikacji wymagań, przyczynia się więc do zachowania jej spójności. Należy jednak pamiętać, że klarowność i czytelność jest ważniejsza od eliminowania redundancji: zbyt duża fragmentacja zachowania na wiele operacji jedynie komplikuje specyfikację i sprawia, że staje się mniej zrozumiała. Na etapie analizy diagramy sekwencji pomagają identyfikować nowe obiekty uczestniczące oraz przeoczone aspekty zachowań. Jako że diagramy sekwencji opisują zachowanie systemu w sposób wysokopoziomowy, wszelkie kwestie implementacyjne, z wydajnością systemu włącznie, nie powinny być w ogóle rozważane na tym etapie. Ponieważ konstruowanie diagramów interakcji jest czasochłonne samo z siebie, programiści powinni w pierwszym rzędzie koncentrować się na problematycznych i nieprecyzyjnie sformułowanych elementach funkcjonalności systemu. Rysowanie diagramów interakcji dla tych części systemu, które są bardzo proste lub dostatecznie jasno zdefiniowane, chociaż nie wydaje się dobrą inwestycją czasu i wysiłku, także powinno być wykonywane, może bowiem uwidocznić konieczność podjęcia pewnych kluczowych decyzji, których dotąd nie dostrzegano.

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

227

Tabela 5.7. Dalsze uszczegółowienie przypadku użycia ReportEmergency. Zidentyfikowanie istnienia obiektu Acknowledgment umożliwiło wykrycie luki w opisie — brak szczegółów na temat informacji zawartej w potwierdzeniu reprezentowanym przez ten obiekt. Uzupełnienie wyróżniono kursywę Nazwa przypadku użycia

ReportEmergency

Warunek wstępny

1. Fi el dOf f i cer aktywuje funkcję „Report Emergency" na swym terminalu.

Przepływ zdarzeń

2. System FRI END wyświetla w odpowiedzi stosowny formularz. Formularz ten zawiera pola określające między innymi rodzaj niebezpieczeństwa (ogólne, pożar, komunikacyjne), lokalizację zdarzenia, jego opis, żądanie zasobów i udział materiałów niebezpiecznych. 3. Fi el dOf f i c e r wypełnia f o r m u l a r z z obowiązkowymi p o l a m i reprezentującymi typ zdarzenia i jego krótki opis. Fi el dOf f i cer określa także możliwe sposoby pierwszej reakcji na zagrożenie i wymagane do tej reakcji zasoby. Po wypełnieniu formularza Fi el dOf f i cer wysyła go do systemu, klikając przycisk Send report. W rezultacie system FRIEND powiadamia dyspozytora (Dispatcher) o zdarzeniu. 4. Di s p a t c h e r analizuje otrzymane od Fi el dOf f i cera informacje zawarte w formularzu, po czym tworzy nowy obiekt I nci dent w bazie, realizując przypadek użycia Openlncident. Di s p a t c h e r określa sposób reakcji, przyporządkowuje niezbędne zasoby (zgodnie z przypadkiem użycia ATlocateResources) i zatwierdza raport, wskutek czego system FRIEND informuje aktora Fi el dOf f i c e r o zatwierdzeniu raportu i przekazuje m u i n f o r m a c j ę od dyspozytora. Potwierdzenie (Acknowl edgment,) otrzymane od dyspozytora informuje Fi el dOf f i cera, że dyspozytor otrzymał raport EmergencyReport, utworzył w bazie obiekt reprezentujący wypadek (Inci d e n t ) i przydzielił zasoby (na przykład wóz strażacki) niezbędne do jego obsługi; w potwierdzeniu zawarta jest też informacja 0 szacunkowym czasie oczekiwania na przybycie grupy 1 wyszczególnienie przydzielonych zasobów.

Warunki koricowe

interwencyjnej

5. Fi el dOff i c e r otrzymał potwierdzenie od dyspozytora.

Heurystyki wspomagające rysowanie diagramów sekwencji ® Pierwsza kolumna diagramu powinna reprezentować aktora inicjującego odnośny przypadek użycia. •

Drugą kolumnę należy poświęcić obiektowi brzegowemu, za pośrednictwem którego wspomniany aktor inicjuje przypadek użycia.



Trzecia kolumna powinna być przeznaczona dla obiektu sterującego, zarządzającego pozostałą częścią przypadku użycia.

® Obiekty sterujące tworzone są przez obiekty brzegowe, za pośrednictwem których aktorzy inicjują przypadki użycia. •

Wewnątrz przypadku użycia obiekty brzegowe tworzone są przez obiekty sterujące.



Obiekty encji wykorzystywane są przez obiekty brzegowe i sterujące.



Obiekty encji nigdy nie korzystają z obiektów brzegowych i sterujących; ułatwia to współdzielenie obiektów encji przez przypadki użycia.

228

Rozdział 4. •Zbieraniewymagań

5.4.5. Modelowanie interakcji między obiektami za pomocą kart CRC Alternatywnym narzędziem identyfikowania interakcji między obiektami są karty CRC, opisane przez K. Becka i W. Cunninghama [Beck i Cunningham, 1989]; akronim CRC pochodzi od słów Class, Responsibilities i Collaborators („klasa, odpowiedzialność i [klasy] współdziałające"). Zaprojektowane zostały jako narzędzie ułatwiające studiowanie technik obiektowych zarówno nowicjuszom, jak i projektantom doświadczonym, lecz nieobeznanym z technikami obiektowymi. Każda klasa modelu reprezentowana jest przez kartę indeksową (zwaną kartą CRC). U góry karty widnieje nazwa klasy, w lewej kolumnie zakres odpowiedzialności klasy, w prawej zaś znajduje się lista klas, za pomocą których dana klasa spełnia swe funkcje wymienione w lewej. Na rysunku 5.10 widoczne są dwie karty indeksowe reprezentujące klasy ReportEmergencyControl i Incident.

Rysunek 5.10. Przykładowe karty CRC

Karty indeksowe przydają się w zespołowych sesjach modelowania. W trakcie takiej sesji, w której uczestniczą zwykle programiści i eksperci z dziedziny aplikacyjnej, analizowany jest scenariusz i identyfikowane są kolejne klasy biorące w nim udział; dla każdej nowo zidentyfikowanej klasy na tablicy pojawia się nowa karta indeksowa. Dla każdej klasy negocjowany jest zakres odpowiedzialności, identyfikowane są też zależności od pozostałych klas. Karty mogą być modyfikowane lub nawet usuwane na bok tablicy jako nieaktualne; nie są jednak z tablicy usuwane, mogą bowiem za chwilę znowu okazać się przydatne, gdy pojawią się nowe pomysły. Karty CRC i diagramy sekwencji to dwie różne metody opisywania tych samych aktywności. Diagramy bardziej nadają się do modelowania w pojedynkę oraz dla celów dokumentacyjnych, gdyż są bardziej precyzyjne i bardziej zwarte; karty CRC sprawdzają się natomiast lepiej w grupowym („burza mózgów") budowaniu i ulepszaniu struktury obiektowej przypadków użycia, ponieważ łatwiej je tworzyć i modyfikować.

5.4.6. Identyfikacja skojarzeń Podczas gdy diagramy sekwencji umożliwiają programistom reprezentowanie interakcji między obiektami w układzie chronologicznym, diagramy klas ukazują zależności między obiektami. W rozdziale 2. „Modelowanie w języku UML" zdefiniowaliśmy notację UML używaną w tej książce na oznaczenie różnych artefaktów produktu (aktywności, produktów docelowych

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

229

i tym podobnych), teraz omówimy zastosowanie diagramów klas do reprezentowania skojarzeń między obiektami. W sekcji 5.4.8 zajmiemy się zastosowaniem diagramów klas do reprezentowania atrybutów obiektów. Skojarzenie przedstawia relację między dwiema (lub kilkoma) klasami, przykładowo klasa FieldOfficer skojarzona jest z klasą EmergencyReport (patrz rysunek 5.11), ponieważ funkcjonariusz (FieldOfficer) sporządza raporty o niebezpieczeństwie (EmergencyReport). Identyfikowanie skojarzeń między klasami służy dwóm celom. Po pierwsze, czyni model bardziej czytelnym, poprzez uwidocznienie istniejących między klasami zależności (na przykład raporty EmergencyReport mogą być sporządzane przez funkcjonariuszy [Fi el dOffi cer] lub dyspozytorów [Dispatcher]). Po drugie, umożliwia programistom wykrywanie przypadków granicznych związanych z tymi zależnościami. Przypadki graniczne wiążą się najczęściej z wyjątkami, które muszą być jednoznacznie opisane w modelu; jest na przykład intuicyjnie jasne, że dany funkcjonariusz (FieldOfficer) może sporządzać wiele raportów (Emergency ^Report), mniej oczywisty jest natomiast charakter zależności odwrotnych: czy dany raport może mieć kilku autorów, czy też musi być sporządzony przez dokładnie jednego funkcjonariusza. Czy dopuszczalne są raporty anonimowe? Tego rodzaju kwestie powinny być na etapie analizy szczegółowo przedyskutowane z klientem i użytkownikami.

Rysunek 5.11. Przykład skojarzeń między klasami

Każde skojarzenie posiada następujące atrybuty: • nazwę związaną z jego charakterem (sporządza na rysunku 5.11); jest ona opcjonalna i nie musi być globalnie unikalna; • role identyfikujące funkcje każdego z uczestników skojarzenia (autor i dokument na rysunku 5.11); • krotność precyzującą dopuszczalną liczbę instancji klas po każdej ze stron skojarzenia (gwiazdka na rysunku 5.11 oznacza, że funkcjonariusz ( F i e l d O f f i c e r ) może sporządzić dowolną liczbę raportów (EmergencyReport) lub nie sporządzić żadnego; liczba 1 oznacza natomiast, że każdy raport ma dokładnie jednego autora). W początkowej fazie analizy skojarzenia między klasami są niezwykle ważne, odkrywają bowiem nowe fakty z zakresu dziedziny aplikacyjnej. Zgodnie z tabelą 5.1, skojarzeń między obiektami należy poszukiwać w opisie słownym wśród czasowników i fraz czasownikowych określających stan rzeczy („posiada", „jest częścią...", „zarządza", „raportuje do...", „jest wyzwalany przez...", „komunikuje się z . . „ z a w i e r a " ) . Konkretny czasownik determinuje zwykle nazwę skojarzenia i role po obu jego stronach.

230

Rozdział 4. •Zbieraniewymagań

Heurystyki pomocne przy identyfikowaniu skojarzeń •

Analizuj frazy czasownikowe.



Używaj precyzyjnych nazw i precyzyjnie określaj role skojarzeń.



Wykorzystuj kwalifikowanie 2 skojarzeń, wszędzie gdzie to tylko możliwe, do identyfikowania przestrzeni nazw i atrybutów kluczowych.



Nie uwzględniaj skojarzeń, które mogą być wyprowadzone na podstawie innych skojarzeń.



Do momentu ustabilizowania zbioru skojarzeń nie przywiązuj zbyt dużej wagi do ich krotności.



Zbyt wielka liczba skojarzeń sprawia, że model staje się nieczytelny.

Początkowo model może zawierać nadmiar skojarzeń, jeśli programiści zastosują się do heurystyk opisywanych w tabeli 5.1; przykładowo w diagramie na rysunku 5.12 klasa reprezentująca wypadek (Incident) uwikłana jest w dwa skojarzenia: z raportem (EmergrncyReport)

— ponieważ zaistnienie wypadku skutkuje utworzeniem raportu — i z funkcjonariuszem (Fi el dOffi cer), który wypadek raportuje. Drugie z tych skojarzeń jest jednak redundantne, wynika bowiem z dwóch pozostałych — nie ma potrzeby jego reprezentowania explicite. Utrzymywanie zbędnych skojarzeń ma niekorzystny wpływa na model, bo wprowadza do niego redundancję i utrudnia jego zrozumienie.

Rysunek 5.12. Przykład redundantnych skojarzeń: otrzymanie raportu o wypadku (EmergencyReport) powoduje utworzenie przez dyspozytora obiektu reprezentującego ten wypadek ( I n c i d e n t ) . Ponieważ wspomniany raport skojarzony jest z funkcjonariuszem (Fi el dOffi c e r ) będącym jego autorem, skojarzenie między funkcjonariuszem ( F i e l d O f f i c e r ) a wypadkiem ( I n c i d e n t ) jest samoistnym efektem wtórnym obu wymienionych skojarzeń — jest więc od nich zależne i jego umieszczenie na diagramie wprowadza do modelu redundancję

Większość obiektów encji posiada unikalną charakterystykę, wykorzystywaną do ich identyfikowania przez aktorów. Funkcjonariusz (FieldOfficer) i dyspozytorzy (Dispatcher) identyfikowani są na podstawie n u m e r u odznaki, wypadki (Incident) i raporty (Report)

opatrywane są unikalnymi numerami, a przy archiwizowaniu dodatkowo datą. Gdy model analityczny obrośnie już w sporą liczbę klas, programiści powinni zastanowić się nad tym, jak i w jakim kontekście instancje każdej z tych klas identyfikowane są przez aktorów. Czy na przykład numer odznaki policjanta jest niepowtarzalny na całym świecie? A może tylko w obrębie jednego miasta? Czy tylko jednego komisariatu? Tego typu wątpliwości dla danej klasy łatwiej się rozstrzyga, przechodząc ciąg skojarzeń konieczny do uzyskania dostępu do konkretnej instancji tej klasy. 2

Kwalifikowanie skojarzeń autorzy opisują w sekcji 2.4.2 — pTzyp.

tłum.

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

231

5.4.7. Identyfikacja agregacji Agregacja jest specjalnym rodzajem skojarzenia, reprezentującym relację typu „całość-część". Przykładowo oddział straży pożarnej (FireStation) tworzy pewna liczba strażaków (Fire ^•Fighter), oddział ten dysponuje pewną liczbą wozów bojowych (Ambulance), urządzeń gaśniczych (Fi reEngi ne) i samochodem komendanta (LeadCar). Z kolei województwo składa się z pewnej liczby powiatów, te zaś z pewnej liczby gmin (patrz rysunek 5.13). Na diagramie klas agregacja wyróżniana jest za pomocą rombu po stronie „całość" skojarzenia.

Rysunek 5.13. Przykłady agregacji

Istnieją dwa typy agregacji — kompozycyjna i współdzielona. W agregacji kompozycyjnej istnienie części uwarunkowane jest istnieniem całości — powiat jest zawsze częścią województwa, gmina jest częścią powiatu; nie jest możliwe współdzielenie gminy między dwa powiaty (przynajmniej w obecnym podziale administracyjnym). Agregacja kompozycyjna oznaczana jest wypełnionym rombem. Romb pusty oznacza agregację współdzieloną, zgodnie z którą część, choć powiązana z całością, może istnieć niezależnie od niej: choć wóz strażacki jest w danej chwili na wyposażeniu konkretnego oddziału straży, może być w razie potrzeby przekazywany innym oddziałom i generalnie może też istnieć niezależnie, bez związku z konkretnym oddziałem. Agregacje wprowadzają do modelu informację na temat koncepcji „część-całość", które dzięki temu mogą być modelowane w postaci struktur drzewiastych lub innych paradygmatów hierarchicznych. Agregacje stosowane są często w modelowaniu interfejsu użytkownika, by łatwiej było nawigować wśród złożonej struktury obiektów: przykładowo dialog wyszukiwania miejscowości na mapie może odbywać się w trzech etapach, obejmujących określenie (kolejno) województwa, powiatu i gminy. Tak jak w przypadku wielu innych koncepcji modelowania, również i w przypadku identyfikowania agregacji należy zachować umiar: jeżeli nie jesteśmy pewni, czy dana relacja między klasami jest na pewno relacją „część-całość", bezpieczniej modelować tę relację w postaci skojarzenia „jeden na wiele", które w przyszłości, po lepszym zrozumieniu dziedziny aplikacyjnej, być może zamienione zostanie na agregację3.

3

Różnicę między agregacją a skojarzeniem „jeden na wiele" w ogólności autorzy wyjaśniają w sekcji 2.4.2 — przyp. tłum.

Rozdział 4. •Zbieraniewymagań

232

5.4.8. Identyfikacja atrybutów Atrybuty to właściwości poszczególnych obiektów danej klasy. Przykładowo każdy raport EmergencyReport, zgodnie z opisem w tabeli 5.3, określa typ wypadku, jego lokalizację i krótki opis — i takie też są atrybuty obiektów klasy EmergencyReport (patrz rysunek 5.14). Atrybutom tym nadawane są wartości w momencie, gdy Fi el dOf f i cer tworzy formularz raportu; wartości te są następnie przetwarzane przez system. Spośród zidentyfikowanych właściwości obiektów danej klasy na atrybuty kwalifikują się tylko te, które mają znaczenie dla systemu: przykładowo funkcjonariusz ( F i e l d O f f i c e r ) posiada numer ubezpieczenia, który jednak pozostaje bez związku z systemem FRIEND i jako taki nie znajduje się na liście atrybutów obiektu Fi el dOf f i cer. Istotny jest za to numer odznaki funkcjonariusza, który reprezentowany jest w postaci atrybutu badgeNumber klasy Fi el dOf f i cer.

Rysunek 5.14. Atrybuty klasy EmergencyReport

Nie są atrybutami właściwości reprezentowane przez obiekty — nie ma więc wśród atrybutów klasy EmergencyReport atrybutu określającego autora raportu, ten bowiem reprezentowany jest w postaci skojarzenia z klasą Fi el dOff i cer. Programiści powinni więc zidentyfikować jak najwięcej skojarzeń między klasami, zanim przystąpią do identyfikowania atrybutów poszczególnych klas. Każdy atrybut posiada trzy następujące cechy. • Nazwę identyfikującą go w obrębie klasy; przykładowo typ raportu reprezentowany jest w klasie EmergencyReport przez atrybut o nazwie reportType, zaś atrybut o nazwie emergency Type reprezentuje typ wypadku (pożar, wypadek drogowy, inny wypadek). By uniknąć wieloznaczności, żadnemu z tych atrybutów nie należy nadawać ogólnej nazwy type. • Krótki opis. • Typ oznaczający zbiór wartości, jakie może przyjmować dany atrybut; przykładowy atrybut description klasy EmergencyReport jest łańcuchem znaków (String), zaś atrybut emergency Type jest atrybutem typu wyliczeniowego, czyli może przyjmować wartości z predefiniowanego zbioru f i re (pożar), t r a f f i c (wypadek drogowy) i other (inny wypadek). W języku UML typy atrybutów bazują na predefiniowanych typach podstawowych tego języka. Atrybuty identyfikować można w nieformalnym opisie za pomocą cytowanych już heurystyk Abbotta z tabeli 5.1. W szczególności rzeczownik w dopełniaczu („opis wypadku") wskazywać może prawdopodobnego kandydata na atrybut („opis"). W przypadku obiektu encji każda jego właściwość, która zapamiętywana jest w systemie w sposób trwały, jest prawdopodobnym atrybutem.

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

233

Atrybuty należą do najmniej stabilnych części modelu. Bywa, że są identyfikowane i dodawane do modelu nawet w fazie implementacji, gdy użytkownicy wyciągną wnioski z przedstawionej wersji testowej. Jeżeli atrybuty te są związane z dodatkową funkcjonalnością systemu, konieczna jest rewizja znacznej ilości wykonanej już pracy. Z tego względu nie ma sensu trwonić czasu i innych zasobów na wyczerpujące identyfikowanie atrybutów dotyczących mniej istotnych aspektów systemu — mogą one zostać dodane później, po ocenie przez użytkowników modelu analitycznego lub szkiców interfejsu. Heurystyki pomocne w identyfikowaniu atrybutów4 •

Szukaj rzeczowników w dopełniaczu.



Dla obiektów encji każda elementarna informacja o stanie jest prawdopodobnym atrybutem.



Opatruj każdy atrybut treściwym opisem.



Właściwości będące obiektami nie nadają się na atrybuty — są reprezentowane przez skojarzenia (patrz sekcja 5.4.6).



Nie zagłębiaj się zbytnio w subtelności obiektu, jeśli jego struktura nie jest jeszcze ustabilizowana.

5.4.9. Modelowanie zachowania poszczególnych obiektów uzależnionego od ich stanu Diagramy sekwencji uwidaczniają podział funkcjonalności między obiekty i operacje realizujące tę funkcjonalność; reprezentują one zachowanie systemu z perspektywy konkretnego przypadku użycia. Diagramy stanów reprezentują natomiast zachowanie systemu z perspektywy konkretnego obiektu; ten punkt widzenia pozwala programistom na tworzenie bardziej sformalizowanych opisów obiektów i w konsekwencji identyfikowanie przeoczonych przypadków użycia. Skupiając się na poszczególnych stanach obiektu, programiści mogą odkrywać nowe elementy jego zachowania. Przykładowo, analizując każde przejście między stanami, identyfikują akcję aktora wyzwalającą to przejście, a następnie odnajdują w przypadku użycia krok opisujący tę akcję. Nie jest konieczne sporządzanie diagramu stanów dla każdej klasy; warte takiej analizy są jedynie obiekty o długim czasie życia oraz te o zachowaniu silnie determinowanym przez stan wewnętrzny. Dotyczy to najczęściej obiektów sterujących, rzadziej obiektów encji i nigdy obiektów brzegowych. Na rysunku 5.15 widnieje diagram stanów dla klasy Incident. Analiza tego diagramu nakazuje programistom sprawdzenie, czy istnieją przypadki użycia odzwierciedlające dokumentowanie, zamykanie i archiwizowanie obsługi wypadków. Ulepszając definicję każdego stanu, programiści mogą dodawać szczegóły do różnych akcji użytkowników wpływających na ten stan; przykładowo, gdy obsługa wypadku (Inci dent) znajduje się w stanie Acti ve, funkcjonariusz (FieldOfficer) powinien mieć możliwość żądania dodatkowych zasobów, a dyspozytor (Di spatcher) musi mieć możliwość ich przydzielania do „aktywnych" wypadków.

4

Na podstawie pracy ]. Rumbaugha, M. Błahy, W. Premerlaniego, F. Eddyego, W. Lorensena [Rumbaugh i in., 1991].

234

Rozdział 4. •Zbieraniewymagań

Rysunek5.15. Diagram stanów dla klasy I n c i d e n t

5.4.10. Modelowanie relacji dziedziczenia między obiektami Generalizacja wykorzystywana jest do eliminowania redundancji z modelu analitycznego. Jeśli dwie klasy (lub więcej) współdzielą te same atrybuty lub to samo zachowanie, podobieństwo to kwalifikuje się do konsolidacji w ramach superklasy. Przykładowo zarówno funkcjonariusz (FieldOfficer), jak i dyspozytor (Dispatcher) identyfikowani są na podstawie numeru odznaki (unikalnego w całym mieście). Identyfikacja na podstawie odznaki jest cechą charakterystyczną dla oficera policji, niezależnie od tego, do jakich funkcji ów oficer jest przydzielony: by uwidocznić ten fakt w modelu, definiujemy abstrakcyjną superldasę PoliceOfficer, dla której klasy F i e l d O f f i c e r i Dispatcher są subklasami (patrz rysunek 5.16).

Rysunek 5.16. Przykład relacji dziedziczenia

5.4. Aktywności analizy wymagań: od przypadków użycia do obiektów

235

5.4.11. Przeglądy modelu analitycznego Model analityczny budowany jest przyrostowo i iteracyjnie, rzadko więc jest poprawny czy nawet kompletny za pierwszym razem. Zwykle potrzeba wielu iteracji zanim model ten stanie się bliski poprawnej specyfikacji, użytecznej dla programistów projektujących i implementujących system. I tak na przykład przeoczenia wykryte w trakcie analizy prowadzą do rozszerzania istniejących przypadków użycia i definiowania nowych, co wiąże się z pozyskiwaniem dodatkowej wiedzy i zbieraniem dodatkowych wymagań od klienta i użytkowników. Gdy intensywność zmian w modelu stanie się minimalna, a zakres tych zmian zlokalizowany, można uznać, że model analityczny jest stabilny. Model ten jest wówczas przeglądany, najpierw przez programistów (w ramach przeglądu wewnętrznego), następnie wspólnie przez programistów i klienta. Celem tego przeglądu jest upewnienie się, że specyfikacja wymagań jest poprawna, kompletna, spójna i jednoznaczna. Programiści i klient oceniają także poszczególne wymagania pod kątem ich realności i weryfikowalności. Oczywiście, programiści powinni być przygotowani na konieczność zmian w specyfikacji wskutek wykrytych błędów i zmotywowani do wykrywania tych błędów jak najwcześniej. Wspomniany przegląd powinien być wspomagany kontrolną listą pytań — oto przykład takiej listy, zaczerpnięty z prac zbiorowych [Jacobson i in., 1999] i [Rumbaugh i in., 1991]. 1. Pytania dotyczące spójności modelu: • Czy słownik obiektów encji jest zrozumiały dla użytkownika? • Czy zdefiniowane klasy abstrakcyjne odpowiadają koncepcjom użytkowników? • Czy wszystkie opisy sporządzone są w zgodzie z definicjami użytkownika? • Czy wszystkie obiekty encji i obiekty brzegowe posiadają znaczące nazwy w formie rzeczowników? ® Czy wszystkie obiekty sterujące posiadają znaczące nazwy w formie czasowników? ® Czy przewidziano obsługę dla wszystkich opisanych błędów i sytuacji wyjątkowych? 2. Pytania dotyczące kompletności modelu: ® Czy każdy z obiektów wykorzystywany jest przez chociaż jeden przypadek użycia? W którym przypadku użycia dany obiekt jest tworzony? Modyfikowany? Niszczony? Czy jest dostępny z poziomu któregoś z obiektów brzegowych? ® Czy każdemu z atrybutów jest nadawana wartość? Jakie są typy poszczególnych atrybutów? Które z atrybutów powinny być kwalifikatorami skojarzeń? ® Czy każde ze skojarzeń jest potrzebne? Kiedy jest wykorzystywane dane skojarzenie? Dlaczego wybrano dla niego takie, a nie inne krotności? Które ze skojarzeń „jeden na wiele" i „wiele na wiele" powinny być kwalifikowane? • Czy każdy obiekt sterujący opatrzony jest wystarczającym zestawem skojarzeń do osiągnięcia wszystkich obiektów uczestniczących w danym przypadku użycia? 3. Pytania dotyczące spójności modelu: ® Czy każda klasa i każdy przypadek użycia posiadają unikalną nazwę? ® Czy encje (przypadki użycia, klasy, atrybuty) o podobnych nazwach reprezentują podobne koncepcje?

Rozdział 4. •Zbieraniewymagań

236

® Czy istnieją dwa obiekty o podobnych atrybutach i podobnych skojarzeniach, należące do różnych hierarchii generalizacji? 4. Pytania dotyczące realistyczności modelu: • Czy w systemie przewidziana jest jakaś niezwykła cecha lub funkcjonalność? Jeśli tak, to czy weryfikowano rację jej bytu za pomocą prototypów lub analizy opłacalności? • Czy możliwe jest zrealizowanie wymagań dotyczących wydajności i niezawodności? Czy realność tych wymagań została potwierdzona testami prototypów na docelowej platformie sprzętowej?

5.4.12. Podsumowanie analizy Zbieranie wymagań jest procesem wybitnie iteracyjnym i przyrostowym. Elementy funkcjonalności uzgadniane są (w postaci szkiców) między programistami a klientem. Klient formułuje nowe wymagania, poddaje krytycznej ocenie obecny kształt opisu funkcjonalności i modyfikuje istniejące wymagania. Programiści weryfikują swe pojęcie o wymaganiach pozafunkcyjnych za pomocą prototypów i analizy technologicznej. Początkowo zbieranie wymagań przypomina burze mózgów: w miarę jak opis systemu rośnie objętościowo, a wymagania stają się coraz bardziej konkretne, programiści zmuszeni są do rozszerzania i modyfikowania modelu analitycznego w kierunku jego lepszej organizacji, ułatwiającej zapanowanie nad złożonością informacji. Na rysunku 5.17 przedstawiono typową sekwencję aktywności etapu analizy. Użytkownicy, klient i programiści opracowują wspólnie początkowy model przypadków użycia. Identyfikują rozmaite koncepcje i tworzą słownik związanych z tymi koncepcjami klas i obiektów. Obie te aktywności omawialiśmy w poprzednim rozdziale, ten rozdział poświęcony jest pozostałym. Programiści klasyfikują obiekty uczestniczące w przypadkach użycia w trzech kategoriach: obiektów encji, brzegowych i sterujących (o czym pisaliśmy kolejno w sekcjach 5.4.1, 5.4.2 i 5.4.3). Tworzone dalej diagramy sekwencji pomagają zauważyć przypadki ewentualnego przeoczenia klas czy obiektów (diagramom sekwencji poświęciliśmy sekcję 5.4.4). Gdy wszystkie obiekty encji zostaną opatrzone znaczącymi nazwami i treściwie opisane, model analityczny powinien pozostać stosunkowo stabilny podczas kolejnych kroków jego ulepszania. Definiowanie skojarzeń (patrz sekcja 5.4.6), definiowanie atrybutów (patrz sekcja 5.4.8) i definiowanie zachowania zależnego od stanu (patrz sekcja 5.4.9) to aktywności składające się na ulepszanie modelu. Wszystkie one wykonywane są w ścisłym związku: stany i skojarzenia poszczególnych obiektów identyfikowane są na podstawie diagramów sekwencji, po czym ewentualne zmiany funkcjonalne odzwierciedlane są w przypadkach użycia (poprzez modyfikowanie istniejących lub tworzenie nowych), dzięki czemu obraz funkcjonalny systemu staje się coraz bardziej kompletny. Konsolidacja modelu (patrz sekcja 5.4.10) to wprowadzanie doń kwalifikacji skojarzeń, generalizowanie koncepcji (i definiowanie abstrakcyjnych superklas) oraz eliminowanie redundancji. Wreszcie, podczas przeglądu modelu (patrz sekcja 5.4.11) klient, użytkownicy i programiści weryfikują ów model pod kątem poprawności, spójności, kompletności i realistyczności. W harmonogramie projektu należy zaplanować wiele takich przeglądów, z intencją należytego sprecyzowania zebranych wymagań. Kiedy jednak model osiągnie stadium,

4.5. Zarządzaniez b i e r a n i e mwymagań

237

Rysunek 5.17. Aktywności etapu analizy

w którym większość zmian będzie mieć charakter kosmetyczny, należy przystąpić do następnego etapu — projektowania systemu. W procesie zbierania wymagań przychodzi taki moment, że nie sposób już przewidzieć kolejnych problemów bez prototypowania, analizy użyteczności, przeglądu dostępnych technologii czy (właśnie) projektowania systemu. Dążenie do nadmiernej szczegółowości może stać się syzyfową pracą, bowiem niektóre ze szczegółów przy następnej zmianie mogą okazać się nieaktualne. Menedżer projektu powinien rozpoznać ten moment i zarządzić przejście do następnej fazy realizacji.

5.5. Zarządzanie analizą wymagań W tej sekcji przedyskutujemy problematykę zarządzania analizą wymagań w projekcie realizowanym przez wiele zespołów. W tej sytuacji podstawowym wyzwaniem jest utrzymanie spójności między dużą liczbą wykorzystywanych zasobów — po zakończeniu analizy wieńczący ją dokument powinien opisywać jeden spójny system, w sposób zrozumiały dla pojedynczego człowieka.

Rozdział 4. •Zbieraniewymagań

238

Rozpoczniemy od przedstawienia szablonu, który może być użyty do dokumentowania wyników analizy (patrz sekcja 5.5.1), następnie omówimy przypisywanie właściwych ról poszczególnym uczestnikom procesu analizy (patrz sekcja 5.5.2), po czym zajmiemy się problemem komunikacji w tym procesie (patrz sekcja 5.5.3). Na zakończenie przedstawimy zadania dla menedżera projektu, wynikające z iteratywnej i przyrostowej natury wymagań (patrz sekcja 5.5.4).

5.5.1. Dokumentowanie analizy wymagań Jak już pisaliśmy w poprzednim rozdziale, zbieranie wymagań i ich analiza dokumentowane są w formie dokumentu RAD (Requirements Analysis Document), którego szkic struktury widzimy poniżej (szczegóły tej struktury opisane są w sekcji 4.5.3). Początek dokumentu, aż do punktu 3.4.2, sporządzany jest na etapie zbierania wymagań. Na etapie analizy jest zwykle przeredagowywany wskutek odkrycia niejasności lub nowych elementów funkcjonalnych, główny jednak wysiłek skoncentrowany jest wówczas na punktach 3.4.3 i 3.4.4 opisujących analityczny model obiektowy. Dokument analizy wymagań 1. Wprowadzenie 2. System obecnie użytkowany 3. System proponowany 3.1. Streszczenie 3.2. Wymagania funkcyjne 3.3. Wymagania pozafunkcyjne 3.4. Modele systemu 3.4.1. Scenariusze 3.4.2. Model przypadków użycia 3.4.3. Model obiektowy 3.4.3.1. Słownik danych 3.4.3.2. Diagramy klas 3.4.4. Model dynamiczny 3.4.5. Interfejs użytkownika — ścieżki nawigacyjne i makiety ekranów 4. Słownik

W punkcie 3.4.3 dokumentu RAD opisane są szczegółowo wszystkie obiekty, ich atrybuty i operacje (na diagramach sekwencji). Każdy obiekt opisywany jest w postaci tekstowej, natomiast relacje między obiektami uwidaczniane są na diagramach klas. W punkcie 3.4.4 udokumentowane jest zachowanie obiektów modelu w kategoriach diagramów stanów i diagramów sekwencji. Jakkolwiek informacja ta jest redundantna z modelem przypadków użycia, modele dynamiczne umożliwiają bardziej precyzyjne opisywanie skomplikowanych zachowań, między innymi przypadków użycia angażujących wielu aktorów.

4.5. Zarządzaniez b i e r a n i e mwymagań

239

Dokument RAD w momencie opublikowania otrzymuje status dokumentu bazowego („linii bazowej") i odtąd podlega kontrolowaniu w ramach zarządzania konfiguracją. Historia zmian w dokumencie powinna dla każdej zmiany określać jej opis, datę oraz autora odpowiedzialnego za jej wprowadzenie.

5.5.2. Przydzielanie odpowiedzialności Analiza wymagań angażuje być może znaczną liczbę uczestników. Użytkownicy dostarczają wiedzę z zakresu dziedziny aplikacyjnej, klient zapewnia fundusze na realizację projektu i koordynuje poczynania użytkowników, analitycy formalizują zebraną wiedzę, menedżer projektu koordynuje działania po stronie programistów. W realizację złożonego systemu uwikłanych jest zwykle wielu użytkowników, analityków i programistów, co stwarza niebagatelne wyzwania dotyczące integrowania jednostkowych wysiłków i zapewnienia sprawnej komunikacji między uczestnikami. Wyzwaniom tym można skutecznie stawiać czoła, przypisując poszczególnym uczestnikom dobrze zdefiniowane role i zakresy odpowiedzialności. Istnieją trzy główne typy wspomnianych ról: generacyjna, integracyjna i weryfikacyjna. • Użytkownik jest ekspertem z dziedziny aplikacyjnej, generującym informację na temat dotychczasowego systemu, środowiska nowego systemu i zadań, jakie przyszły system ma spełniać. Każdy użytkownik odpowiada jednemu aktorowi lub kilku aktorom (w scenariuszach i przypadkach użycia). • Klient to rola integracyjna, definiująca zakres funkcjonalny systemu w oparciu o potrzeby użytkowników. Różni użytkownicy mogą prezentować odmienne spojrzenie na przyszły system, jako że korzystać będą z różnych jego części (jak Fi el dOffi cer i Dispatcher), bądź też mają inne opinie i oczekiwania dotyczące systemu. Klient służy jako integrator informacji z dziedziny aplikacyjnej i rozwiązuje niespójności w oczekiwaniach poszczególnych użytkowników. • Analityk to ekspert z dziedziny aplikacyjnej, tworzący modele aktualnego systemu i generujący informację na temat przyszłego systemu. Każdy analityk jest początkowo odpowiedzialny za uszczegółowienie jednego lub wielu przypadków użycia; dysponując kompletem szczegółowych przypadków użycia, analityk identyfikuje poszczególne obiekty, ich atrybuty i skojarzenia między nimi; stosuje przy tym techniki opisane w sekcji 5.4. Analitykiem jest zwykle programista dysponujący rozległą wiedzą z zakresu dziedziny aplikacyjnej. • Architekt, jako przedstawiciel roli integracyjnej, dokonuje unifikacji modelu przypadków użycia i modelu obiektowego, z perspektywy przyszłego systemu. Poszczególni analitycy mogą prezentować różne style modelowania i różne punkty spojrzenia na te części systemu, za które sami nie są odpowiedzialni. Mimo iż analitycy często pracują razem i wynikające z powyższego problemy rozwiązują we własnym zakresie, rola architekta jest niezbędna w celu zachowania jednolitej filozofii systemu i zidentyfikowania ewentualnych braków w specyfikacji wymagań. • Redaktor dokumentacji odpowiedzialny jest za niskopoziomową integrację dokumentu, a także za jego ogólny format i użyteczny indeks.

240

Rozdział 4. •Zbieraniewymagań

• Menedżer konfiguracji odpowiada za utrzymywanie historii poprawek wprowadzanych do dokumentu oraz za informacje wiążące dokument RAD z innymi dokumentami (takimi jak dokument projektu systemu opisywany w rozdziale 6. „Projektowanie systemu — dekompozycja na podsystemy"). • Weryfikator dokonuje przeglądu dokumentu RAD pod kątem jego poprawności, kompletności, spójności i klarowności. Użytkownicy, klient, programiści i inni uczestnicy projektu mogą wcielać się w rolę weryfikatorów — ci, którzy jeszcze nie są czynnie zaangażowani w projekt, doskonale nadają się na weryfikatorów, prezentują bowiem świeże spojrzenie zapewniające lepszą identyfikację niejasności i wieloznaczności. Rozmiar przyszłego systemu determinuje liczbę różnych użytkowników i analityków uczestniczących w zbieraniu wymagań i ich modelowaniu. Konieczne jest w związku z tym istnienie roli integracyjnej zarówno po stronie klienta, jak i po stronie programistów. Ostatecznie bowiem wymagania dotyczące przyszłego systemu — jak wielkim by nie był — muszą być zrozumiałe dla pojedynczej osoby posiadającej niezbędną do tego wiedzę z dziedziny aplikacyjnej.

5.5.3. Komunikacja w związku z analizą wymagań Zadanie sprawnego komunikowania informacji jest jednym z najtrudniejszych wyzwań w procesie zbierania wymagań i ich analizy. Wyzwanie to kreowane jest przez rozmaite czynniki. Oto niektóre z nich. • Zróżnicowanie uczestników pod względem posiadanej wiedzy i doświadczenia. Użytkownicy, klient i programiści dysponują wiedzą i doświadczeniem z różnych dziedzin i przy opisywaniu tych samych koncepcji posługują się odmienną terminologią. • Zróżnicowane oczekiwania uczestników. Cele, jakie stawiają sobie użytkownicy, klient i menedżer projektu są zróżnicowane: użytkownicy oczekują systemu, który usprawni ich pracę, bez wpływu na bieżącą pozycję (wdrożenie nowego systemu może się wiązać ze zmianami kadrowymi); klient chciałby uzyskać jak najwięcej z zainwestowanych zasobów, menedżer dąży do dostarczenia systemu w terminie. Zróżnicowanie interesów (również osobistych) może prowadzić do zniechęcenia w kwestii dzielenia się informacjami i raportowania problemów niezwłocznie, gdy tylko się pojawią. • Nowo utworzone zespoły. Zbieranie wymagań jest pierwszą fazą nowego projektu, który przekłada się często na nowych uczestników, nowe zespoły i przypisywanie nowych ról, a w konsekwencji — na pewien okres niestabilności, w czasie którego członkowie każdego zespołu muszą nauczyć się pracować razem. • Ewoluujący system. Gdy nowy system tworzony jest „od zera", związane z nim koncepcje i terminologia są czynnikami płynnymi w trakcie zbierania i analizowania wymagań, a często także w fazie projektowania systemu. Używany dziś termin może jutro mieć inne znaczenie.

4.5. Zarządzaniez b i e r a n i e mwymagań

241

Żadna metodologia i żadne mechanizmy nie są zdolne do rozwiązywania problemów wynikających z wewnętrznej polityki firmy oraz z ukrywania informacji; sprzeczne cele i współzawodnictwo są nieodłącznymi elementami każdego projektu. Kilka prostych wskazówek może jednak przyczynić się przynajmniej do złagodzenia konfliktów w kwestii spojrzenia na nowy system. •

Wyraźne określenie granic. Definiowanie ról, opisane w sekcji 5.5.2, jest częścią tego procesu, który obejmuje także zdefiniowanie granic prywatności informacji: przykładowo każdy zespół ma do dyspozycji własne, wewnętrzne forum dyskusyjne, niewidoczne dla klienta, z którym dyskusja odbywa się w ramach zupełnie innego forum (opisywaliśmy to w rozdziale 3. „Organizacja projektu i komunikacja"). Na zasadzie wzajemności — programiści nie powinni ingerować w wewnętrzną politykę klienta i użytkowników.



Wyraźne zdefiniowanie celów oraz kryteriów powodzenia. Wspólne definiowanie czytelnych, mierzalnych i weryfikowalnych celów oraz kryteriów powodzenia przyczynia się do łagodzenia konfliktów między programistami a klientem. Czytelne zdefiniowanie weryfikowalnego celu nie jest zadaniem banalnym, gdy weźmie się pod uwagę tendencję do pozostawiania definicji celów w stanie mało precyzyjnym. Cele i kryteria powodzenia powinny być opisane w punkcie 1.3 dokumentu RAD.

• Burza mózgów. Szybkie proponowanie rozmaitych rozwiązań podczas zebrania wszystkich uczestników projektu w jednym pomieszczeniu sprzyja przełamywaniu barier komunikacyjnych. Podobny efekt dać mogą przeglądy produktów docelowych dokonywane wspólnie przez klienta i programistów w czasie tej samej sesji. Burza mózgów — i generalnie każde kooperatywne opracowywanie wymagań — może prowadzić do definiowania ad hoc rozmaitych konwencji komunikacyjnych i notacyjnych: tablice, szkice interfejsu użytkownika, wysokopoziomowe diagramy przepływu informacji i tak dalej często pojawiają się spontanicznie. W miarę jak objętość informacji dotyczącej dziedziny aplikacyjnej i nowego systemu staje się coraz większa, krytycznego znaczenia nabiera stosowanie precyzyjnej i strukturalnej notacji. Stosując język UML, programiści wykorzystują przypadki użycia i scenariusze, do komunikowania się z klientem i użytkownikami, oraz diagramy klas, diagramy sekwencji i diagramy stanów do komunikowania się między sobą (patrz sekcje 4.4 i 5.4). Ponadto najnowsza wersja wymagań powinna być dostępna dla wszystkich uczestników — utrzymywanie aktualnej wersji online dokumentu RAD wraz z historią jego rewizji ułatwia terminowe dostarczanie informacji w ramach projektu.

5.5.4. Iteracje modelu analitycznego Analizowanie wymagań to proces iteratywny i przyrostowy, prowadzony często równolegle z innymi aktywnościami, między innymi projektowaniem systemu i jego implementacją. Zwróćmy uwagę, że niekontrolowane modyfikowanie i poszerzanie modelu analitycznego rychło prowadzi do chaosu, szczególnie wtedy, gdy ingeruje w ów model duża liczba uczestników. Od momentu zatem, gdy dokument RAD uzyska status „linii bazowej", kolejne iteracje modelu powinny być starannie dokumentowane. Aktywności związane ze zbieraniem i analizowaniem wymagań mogą być postrzegane jako sekwencje kroków zmierzających do wypracowania stabilnego modelu analitycznego.

Rozdziat 5. • Analiza wymagań

242

Burza mózgów Burza mózgów, towarzysząca zbieraniu wymagań, jest najczęściej pierwszą aktywnością w projekcie. W trakcie burzy mózgów wszelkie koncepcje, a także terminologia służąca do ich formułowania, ulegają nieustannym zmianom. Celem tego procesu jest wygenerowanie jak największej liczby koncepcji bez potrzeby jakiegokolwiek ich organizowania. Iteracje są tu bardzo krótkie i mocno zróżnicowane.

Ustalanie Gdy tylko klient i programiści porozumieją się co do zasadniczych koncepcji, zdefiniują granice systemu i uzgodnią standardową terminologię, zaczyna się proces ustalania: funkcjonalność organizowana jest w grupy przypadków użycia wraz z odpowiadającymi im elementami interfejsu. Poszczególne grupy elementów funkcjonalnych przyporządkowywane są różnym zespołom, które odpowiedzialne są za uszczegółowienie poszczególnych przypadków użycia. Na tym etapie iteracje także są krótkie, lecz już raczej zlokalizowane.

Dojrzewanie Zmiany wysokopoziomowe wciąż są możliwe, lecz już trudniejsze do wprowadzania, powinny więc być dokonywane w sposób szczególnie przemyślany. Każdy zespół odpowiedzialny jest za przypadki użycia i modele obiektowe związane z funkcjonalnością, która została mu powierzona. Zespół architektoniczny, jako zespół międzyfunkcyjny, złożony z reprezentantów poszczególnych zespołów funkcyjnych, odpowiedzialny jest za integrowanie wymagań (między innymi za przyporządkowywanie unikalnych nazw). Gdy specyfikacja wymagań zostanie przyjęta (podpisana) przez klienta, modyfikacje modelu analitycznego powinny ograniczać się tylko do usuwania błędów i uzupełniania przeoczeń. Programiści, szczególnie ci z zespołu architektonicznego, powinni dbać o to, by nie została naruszona spójność modelu. Model wymagań powinien być poddany zarządzaniu konfiguracją: wszelkie zmiany w jego obrębie powinny być natychmiast odzwierciedlane w istniejących modelach projektowych. Iteracje na tym etapie są dłuższe i zwykle zlokalizowane. Zestaw cech i funkcji przyszłego systemu rozrasta się z czasem, każda zmiana w tym zestawie stanowi zagrożenie dla integralności systemu. Wprowadzanie zmian w późnych etapach projektu dokonywane jest zwykle w warunkach niepełnej informacji: nie wszystkie zależności między poszczególnymi elementami funkcjonalnymi są widoczne, wiele założeń przyjmowanych jest domyślnie i zapominanych w trakcie urzeczywistniania zmian. Często zmiany takie związane są z problemem, co do którego istnieje silna presja na implementację: badanie ich konsekwencji jest wówczas tylko powierzchowne. Zawsze w talach wypadkach należy zadać sobie następujące pytania: • Czy wprowadzenia zmian żąda klient? • Czy są niezbędne dla systemu, czy też mają jedynie charakter ozdobników? « Czy może powinny być zrealizowane raczej w postaci odrębnego programu niż jako element systemu bazowego?

4.5. Zarządzaniez b i e r a n i e mwymagań

243

• Czy dotyczą zasadniczej funkcjonalności systemu, czy też jego opcjonalnych funkcji? • Jakie są ich konsekwencje dla spójności i niezawodności systemu? Dla interfejsu systemu? Gdy wspomniane zmiany okazują się uzasadnione, klient i programiści definiują ich zakres, ich oczekiwany efekt i przystępują do modyfikowania modelu analitycznego. Gdy model ten jest kompletny, wprowadzanie do niego nowych elementów funkcjonalnych jest łatwiejsze, choć trudniejsza staje się wtedy ich implementacja.

5.5.5. Uzgodnienie modelu analitycznego z klientem Podpis złożony przez klienta pod dokumentem RAD oznacza akceptację modelu analitycznego, czyli porozumienie między klientem a programistami co do funkcji i cech przyszłego systemu. Dodatkowo klient i programiści uzgadniają między sobą następujące szczegóły: • listę priorytetów, • proces rewizji dokumentu, • listę kryteriów zaakceptowania lub odrzucenia gotowego systemu, • harmonogram i budżet. Zróżnicowanie funkcji systemu pod względem ważności (priorytetów) pozwala programistom lepiej rozumieć oczekiwania klienta, czyli między innymi odróżniać kluczowe cechy systemu od przysłowiowych wodotrysków. Umożliwia ono również programistom lepsze zaplanowanie dostarczania poszczególnych elementów systemu: najpierw zrealizowane będą jego kluczowe funkcje, a dopiero po ich zweryfikowaniu pozostałe. A jeśli nawet system dostarczony ma być jednorazowo w postaci kompletnego, monolitycznego pakietu, zróżnicowanie priorytetów jego funkcji umożliwia programistom lepsze zrozumienie wyobrażeń klienta o produkcie. Poniżej przedstawiamy przykładowy schemat priorytetów tego rodzaju. Każda funkcja systemu powinna być zakwalifikowana w kategoriach następujących priorytetów: ® wysoki priorytet — funkcje tej grupy muszą być z powodzeniem zaprezentowane podczas testów akceptacyjnych u klienta, o

pośredni priorytet — funkcje tej grupy muszą zostać wzięte pod uwagę w czasie projektowania systemu i projektowania obiektów, ich prezentacja przewidywana jest w trakcie drugiej iteracji tworzenia systemu,

°

niski priorytet — funkcje tej grupy stanowią ilustrację możliwości zarządzania systemu w dłuższej perspektywie.

Podpisane przez klienta wymagania uzyskują status linii bazowej i mogą zostać wykorzystane do dokładniejszego oszacowania kosztów projektu. Oczywiście, nie oznacza to końca zmian w samych wymaganiach, lecz odtąd każda z tych zmian dokonywana będzie w ramach formalnego procesu rewizji. Nieuchronność tych zmian wynika z błędów, przeoczeń, zmian

244

Rozdział 4. •Zbieraniewymagań

w środowisku operacyjnym, zmian w zakresie dziedziny aplikacyjnej lub zmian w dostępnych technologiach. Zdefiniowanie a priori szczegółów wspomnianego procesu rewizji ma na celu usprawnienie komunikacji w projekcie i zapobieżenie przykrym niespodziankom w późniejszych jego etapach. Ów proces nie musi mieć wcale charakteru biurokratycznego, niekiedy wystarczy wyznaczenie jednej osoby odpowiedzialnej za odebranie żądania od klienta, zaprojektowanie stosownej zmiany i nadzór nad jej implementacją. Na rysunku 5.18 przedstawiony jest jednak przypadek bardziej skomplikowany, kiedy to projekt zmian musi zostać zatwierdzony przez klienta przed ich zaimplementowaniem. Tak czy inaczej żądanie, by aktualna postać wymagań nie została zamrożona, lecz tylko opatrzona statusem linii bazowej, podyktowane jest realiami realizacji projektu.

Rysunek 5.18. Przykład procesu rewizyjnego

5.6. Analiza przypadku — system AREIMA

245

Lista kryteriów akceptacyjnych weryfikowana jest jeszcze przed podpisaniem dokumentu. Zbieranie wymagań i ich analiza wyjaśniają wiele aspektów systemu, włącznie z wymaganiami pozafunkcyjnymi i względną ważnością poszczególnych jego cech. Formułując ponownie kryteria akceptacyjne, klient upewnia się, że programiści świadomi są wszystkich zmian, jakie dokonały się w jego oczekiwaniach. Budżet i harmonogram podlegają ponownej analizie po tym, jak model analityczny okaże się stabilny. W rozdziale 14. „Zarządzanie projektem" opiszemy zagadnienia związane z szacowaniem kosztów realizacji systemu. Niezależnie od zewnętrznej formy, czyli od tego, czy opisane uzgodnienia mają postać formalnego dokumentu, czy też postać procedury wynikającej z podpisanego wcześniej kontraktu, stanowią one kamień milowy w realizacji projektu, reprezentują bowiem zbieżność klienta i programistów w kwestii jednolitego zbioru definicji funkcjonalnych systemu i jednolitego zbioru oczekiwań klienta. Akceptacja dokumentu RAD przez klienta ma znaczenie większe niż akceptacja pozostałych dokumentów, bo to właśnie model analityczny determinuje znakomitą większość aktywności w realizacji projektu.

5.6. Analiza przypadku — system ARENA W tej sekcji pokażemy, jak opisywane w tym rozdziale koncepcje i metody zastosować można w systemie ARENA. Rozpoczniemy od modelu przypadków użycia i słownika, opracowanych w poprzednim rozdziale. Zidentyfikujemy uczestniczące w tych przypadkach użycia obiekty encji, brzegowe i sterujące, po czym wzbogacimy model analityczny, definiując atrybuty tych obiektów i skojarzenia między nimi. Na zakończenie skonsolidujemy model obiektowy, wykorzystując relację dziedziczenia. W sekcji tej opierać się będziemy głównie na przypadku użycia AnnounceTournament.

5.6.1. Identyfikacja obiektów encji Obiekty encji reprezentują koncepcje z dziedziny aplikacyjnej zarządzane przez system. Jako punktu wyjścia do identyfikowania obiektów tej kategorii użyjemy słownika, który powstał podczas zbierania wymagań, po czym odwołamy się do heurystyk Abbotta (przedstawionych w tabeli 5.1). Początkowo skupimy się tylko na rzeczownikach odzwierciedlających koncepcje dziedziny aplikacyjnej. W tabeli 5.8 widoczny jest opis przypadku użycia AnnounceTournament, w którym to opisie wyróżniono czcionką pogrubioną pierwsze wystąpienie każdego ze wspomnianych rzeczowników. Zwróćmy uwagę, że zidentyfikowaliśmy obiekty odpowiadające aktorom w modelu przypadków użycia. Aktor to koncepcja z zakresu dziedziny aplikacyjnej, mająca związek z systemem (w sensie na przykład kontroli dostępu, autorstwa czy zakresu odpowiedzialności). W systemie ARENA każdy zarejestrowany kapitan ligi (LeagueOwner) reprezentowany jest przez obiekt przechowujący specyficzne dla tego kapitana informacje: adres e-maił, listę posiadanych lig i tak dalej. Zauważmy także, iż nie wszystkie frazy rzeczowników odnoszą się bezpośrednio do klas. Przykładowo „nazwa turnieju" identyfikuje atrybut name, reprezentujący nazwę turnieju (który z kolei reprezentowany jest przez obiekt klasy Tournament). Podobnie „lista reklamodawców"

Rozdziat 5. • Analiza wymagań

246

Tabela 5.8. Zastosowanie heurystyk A b b o t t a do identyfikacji obiektów encji w przypadku użycia AnnounceTournaraent. Pierwsze wystąpienie każdego z rzeczowników wyróżnione jest pogrubioną czcionką AnnounceTournament

Nazwa Przepływ

zdarzeń

1. LeagueOwner wysyła żądanie zorganizowania nowego turnieju. 2. System sprawdza, czy LeagueOwner nie wykorzystywał już przysługującego mu limitu liczby turniejów w lidze lub na arenie; jeśli mieści się w limicie, system wysyła mu formularz do wypełnienia. 3. LeagueOwner wpisuje w formularzu swą nazwę, początkową i końcową datę zgłaszania graczy do turnieju, początkową i końcową datę rozgrywania meczów w ramach turnieju i maksymalną liczbę graczy biorących udział w turnieju. 4. System wysyła d o kapitana ligi (LeagueOwner) zapytanie o ewentualny sponsoring wyłączny turnieju i w przypadku decyzji o takim sponsoringu wyświetla mu listę reklamodawców (Adverti s e r ) wyrażających chęć takiego sponsoringu. 5. Jeśli LeagueOwner zdecyduje o poszukiwaniu konkretnego sponsora wyłącznego, wybiera kilka pozycji z proponowanej listy. 6. System powiadamia wybranych sponsorów o nadchodzącym turnieju i wysokości ryczałtowej opłaty {flatfee) wyłącznego sponsoringu. 7. System przekazuje kapitanowi ligi odpowiedzi otrzymane od zainteresowanych sponsorów. 8. Jeżeli istnieją zainteresowani potencjalni sponsorzy, LeagueOwner wybiera jednego z nich. 9. System rejestruje dokonany wybór nazwy wyłącznego sponsora i obciąża jego k o n t o opłatą sponsoringową. Od tej chwili w reklamach podczas turnieju pojawiać się będą banery tylko tego jednego sponsora. 10. Jeżeli nie wyłoniono wyłącznego sponsora — z braku chętnych lub rezygnacji kapitana ligi z wyboru — w czasie turnieju wyświetlane będą banery wybierane losowo spośród materiałów wszystkich zarejestrowanych sponsorów, których konta obciążone zostaną wynikającymi stąd opłatami proporcjonalnymi. 11. Gdy załatwione zostaną kwestie sponsoringu, system wyświetla listę graczy, kibiców i reklamodawców zainteresowanych nowym turniejem. 12. LeagueOwner wybiera osoby, które zostaną powiadomione o nowym turnieju. 13. System tworzy stronę główną nowego turnieju stanowiącą punkt startowy dla graczy zamierzających zgłosić swój udział i kibiców zainteresowanych oglądaniem meczów. 14. Gdy nadejdzie data, od której przyjmowane są zgłoszenia, system powiadamia o tym wszystkich zainteresowanych użytkowników, przesyłając im link do strony głównej turnieju. Aż do ustalonego m o m e n t u gracze mogą zgłaszać swój udział (przypadek użycia Apply ForTournament).

5.6. Analiza przypadku — system AREIMA

247

to identyfikacja skojarzenia między klasami League (obiekt tej klasy reprezentuje ligę) i Adverti ser (na tę ldasę składają się obiekty reprezentujące reklamodawców). Aby ułatwić rozróżnienie między frazami identyfikującymi (odpowiednio) obiekty, atrybuty i skojarzenia, posłużymy się kilkoma dodatkowymi heurystykami: • Atrybuty są właściwościami. Atrybut reprezentuje pojedynczą właściwość obiektu, wycinek jego informacji i jako taki jest z natury niekompletny: nazwa reklamodawcy to tylko jeden z opisujących reklamodawcę elementów, oprócz między innymi identyfikacji jego konta, typu prezentowanych banerów i tym podobnych, które reprezentowane są przez inne atrybuty i skojarzenia. • Atrybuty są wielkościami prostych typów. Atrybut może mieć postać liczby (będącej na przykład maksymalną dopuszczalną liczbą turniejów), łańcucha znaków (przedstawiającego przykładowo nazwę reklamodawcy) lub daty (na przykład daty rozpoczęcia i zakończenia turnieju). Właściwości, takie jak adres, numer ubezpieczenia czy numer rejestracyjny samochodu, też są zwykle uważane za typy proste (kwalifikują się więc na atrybuty), ponieważ odnoszą się do prostych, niepodzielnych koncepcji. Koncepcje bardziej złożone to kandydatury na reprezentowanie w postaci odrębnych obiektów, powiązanych z obiektem macierzystym za pomocą skojarzeń: przykładowo konto (Account) danego reklamodawcy (Adverti ser) to obiekt zawierający między innymi takie informacje jak aktualny bilans, historia transakcji czy limit kredytowy. • Rzeczowniki odnoszące się do kolekcji reprezentują skojarzenia, często z automatycznie określonymi krotnościami. Listy, grupy, tabele czy zbiory reprezentowane są właśnie przez skojarzenia; i tak na przykład system ARENA wyświetla na żądanie kapitana ligi listę reklamodawców, potencjalnie zainteresowanych wyłącznym sponsoringiem; koncepcja ta reprezentowana jest przez skojarzenie między klasami Arena i Adverti ser. Często klasa uwikłana w skojarzenie uczestniczy w nim na zasadzie implikacji. Przykładowo, gdy rozwiązane zostaną kwestie sponsoringu, system ARENA prezentuje kapitanowi ligi, organizującemu turniej, listy przedstawiające grupy graczy (Player), kibiców (Spectator) i reklamodawców (Advertiser). Identyfikujemy w związku z tym nową klasę InterestGroup, reprezentującą grupę użytkowników zainteresowanych nowych zdarzeniem dotyczącym ligi lub gry. Stosownie do słowa „lista", obiekt klasy Arena skojarzony jest z obiektami klasy InterestGroup reprezentującymi wszystkie zdefiniowane grupy zainteresowań. Z kolei każda grupa zainteresowań jest grupą użytkowników określonej kategorii — graczy, kibiców i reklamodawców — stąd skojarzenie klasy InterestGroup z klasami Player, Spectator i Advertiser. Każda grupa zainteresowań powiązana jest również odpowiednim skojarzeniem z przedmiotem swego zainteresowania, stąd kolejne skojarzenie między klasą InterestGroup a k l a s a m i League i Game.

W tabeli 5.9 prezentujemy listę obiektów encji, ich atrybutów oraz skojarzeń między ich klasami zidentyfikowanych dotychczas na podstawie opisu przypadku użycia Announce "-•Tournament. Dokonaliśmy przyporządkowania rozpoznanych atrybutów i skojarzeń do odpowiednich klas, zdefiniowaliśmy też nowe klasy. Każdą klasę opatrzyliśmy także opisem słownym, głównie z dwóch powodów. Po pierwsze, sama nazwa klasy nie jest na tyle specyficzna, by jednoznacznie wskazywać wszystkim uczestnikom koncepcję, którą klasa ta reprezentuje

248

Rozdział 4. •Zbieraniewymagań

Tabela 5.9. Obiekty encji uczestniczące w przypadku użycia AnnounceTournament zidentyfikowane na podstawie fraz rzeczownikowych. Znaki zapytania (?) oznaczają wątpliwości prowadzące do pytań przedstawionych w ramce poniżej

Obiekt encji

Atrybuty i skojarzenia

Definicja

Account

• •

Konto reklamodawcy (Adverti s e r ) zawierające informację o bieżącym bilansie reklamodawcy, historii zmian i płatnościach.

bilans historia transakcji



Adverti s e r

historia płatności



nazwa



ligi zainteresowane



wyłącznym s p o n s o r i n g i e m

Reklamodawca, aktor zainteresowany wyświetlaniem swych banerów reklamowych w trakcie trwania meczów (Match).

tego reklamodawcy (?) •

sponsorowane turnieje konto

Advertisement



skojarzona gra (?)

Obrazy d o s t a r c z o n e przez reklamodawcę (Adverti s e r ) wyświetlane w czasie trwania meczów.

Arena



maksymalna liczba turniejów

Instancja systemu ARENA.



zryczałtowana opłata



za wyłączny sponsoring (?)



ligi (implikowany) grupy zainteresowań (implikowany)

Game

InterestGroup

Gra, współzawodnictwo między graczami, prowadzone w oparciu o określony zestaw reguł. W systemie ARENA klasa Game odpowiedzialna jest za w y m u s z a n i e w s p o m n i a n y c h reguł (w kodzie programu), śledzenie postępów poszczególnych graczy (PI a y e r ) i wybór zwycięzcy. •

lista graczy (PI ayer), kibiców ( S p e c t a t o r )

0

i reklamodawców (Adverti s e r ) gry i ligi będące przedmiotem zainteresowania grupy

Lista u ż y t k o w n i k ó w systemu ARENA o określonym zainteresowaniu (którego p r z e d m i o t e m jest gra lub liga). Klasa I n t e r e s t G r o u p wykorzystywana jest także do reprezentowania list mailingowych, służących powiadamianiu potencjalnych aktorów o nowych zdarzeniach.

(implikowany) League

o

maksymalna liczba turniejów

0

gra

Liga, wspólnota, której celem jest rozgrywanie t u r n i e j ó w (Tournament). Liga skojarzona jest z a r ó w n o z określoną grą (Game), jak i p r e d e f i n i o w a n y m stylem t u r n i e j u (TournamentStyl e). Gracze zarejestrowani w lidze gromadzą punkty zgodnie z regułami ExpertRating określonymi dla tej ligi.

249

5.6. Analiza przypadku — system AREIMA

Tabela 5.9. Obiekty encji uczestniczące w przypadku użycia AnnounceTournament zidentyfikowane na podstawie fraz rzeczownikowych. Znaki zapytania (?) oznaczają wątpliwości prowadzące do pytań przedstawionych w ramce poniżej — ciąg dalszy

Obiekt encji

Atrybuty i skojarzenia

Definicja

LeagueOwner



nazwa (implikowany)

Aktor zakładający ligę (League) i odpowiedzialny za organizowanie turniejów (Tournament) w j e j ramach.

Match

• •

turnieje

Mecz, konkurs między dwoma (lub większą iiczbą) graczami ( P l a y e r ) p r o w a d z o n y w oparciu o reguły danej gry (Game). W wyniku meczu bądź to wyłoniony zostaje jeden zwycięzca (pozostali gracze uczestniczący w meczu uważani są za przegranych), bądź też u s t a n o w i o n y remis (nie ma wówczas zwycięzcy ani przegranych). Niektóre style turnieju (TournamentStyl e) mogą wykluczać remis.

gracze

Player



nazwa (implikowany)

Gracz.

Tournament



nazwa

Turniej, seria meczów rozgrywanych między graczami (PI ayer) z określonej grupy. Turniej kończy się wyłonieniem jednego zwycięzcy. Gracze uczestniczący w turnieju gromadzą punkty. H a r m o n o g r a m meczów w turnieju ustalany jest przez kapitana ligi (LeagueOwner) organizującego ten turniej.

• •

data rozpoczęcia zgłaszania data zakończenia zgłaszania

• •

data rozpoczęcia rozgrywek data zakończenia rozgrywek

a •

maksymalna liczba graczy wyłączny sponsor

— wyrazy „mecz" (Match) czy „gra" (Game) używane są w różnych kontekstach w znaczeniu potocznym; w systemie ARENA reprezentują one jednak ściśle zdeterminowane koncepcje: i tak „gra" to zestaw reguł, których zachowanie wymuszane jest przez odpowiedni fragment oprogramowania, a „mecz" to rozgrywka między graczami (PI ayer). Po drugie, obiekty zidentyfikowane na etapie analizy odpowiadają także terminom zawartym w słowniku, którego tworzenie rozpoczęliśmy na etapie zbierania wymagań; uczestnicy wykorzystują ów słownik w celu rozwiązywania niejasności i ustanowienia standardowej terminologii, zatem krótka definicja znaczenia każdej z klas jest znakomitym środkiem do zapobiegania wieloznacznościom i nieporozumieniom. Odłożenie „na później" tych definicji stwarzałoby ryzyko utraty informacji, prowadzącej w konsekwencji do ich niekompletności. Identyfikowanie obiektów encji i ich atrybutów rzadko jest oczywiste i zwykle rodzi serię dodatkowych pytań pod adresem klienta. Przykładowo, gdy rozpoznajemy implikowane atrybuty i skojarzenia, powinniśmy się solidnie zastanowić wraz z klientem, czy intuicja nie zwiodła nas na manowce — może się bowiem okazać, że strony skojarzenia nie są określone jednoznacznie. Poniżej widzimy przykładowy zestaw pytań, związanych z obiektami encji uczestniczącymi w przypadku użycia AnnounceTournament.

250

Rozdział 4. •Zbieraniewymagań

Pytania dla klienta systemu ARENA ® Jaka informacja powinna być przechowywana w koncie reklamodawcy? Czy na przykład powinna ona obejmować kompletną kronikę wszystkich wyświetlonych banerów? •

Czy reklamodawcy mogą wyrażać chęć wyłącznego sponsorowania wybranych lig, czy jedynie chęć uczestniczenia w sponsoringu na arenie?





Czy reklamodawcy p o w i n n i przyporządkowywać swe b a n e r y do określonych gier, w celu ich trafniejszego losowego wyboru przez system, w sytuacji gdy nie ma wyłącznego sponsora? Czy zryczałtowana opłata za wyłączny sponsoring powinna być stała w danej instancji systemu, czy też zróżnicowana dla poszczególnych lig i (lub) turniejów?

5.6.2. Identyfikacja obiektów brzegowych Obiekty brzegowe reprezentują interfejs między systemem a aktorami; identyfikowane są na podstawie przypadków użycia, a ich komplet może być uważany za dobre przybliżenie interfejsu użytkownika przyszłego systemu. Nie powinny się jednak odnosić do jakichkolwiek wizualnych aspektów owego interfejsu, bo ten rodzaj informacji lepiej reprezentowany jest przez rozmaite makiety; mogą za to reprezentować koncepcje kompozycyjne interfejsu — okna, formularze czy nawet sprzęt, na którym użytkownik będzie się komunikował z funkcjami systemu za pośrednictwem tego właśnie interfejsu. Stosując heurystyki Abbotta, nie rozpoznamy — niestety — zbyt wielu obiektów brzegowych, gdyż w początkowych wersjach przypadków użycia rzadko są one wymienione explicite. Zamiast tego przeanalizujemy przypadek użycia AnnounceTournament z tabeli 5.8 i zidentyfikujemy te jego miejsca, w których następuje wymiana informacji między aktorami a systemem. Uwzględnimy zarówno formularze, za pomocą których aktorzy dostarczają informacje systemowi (na przykład formularz, przy użyciu którego kapitan ligi (LeagueOwner) organizuje nowy turniej (Tournament)), jak i powiadomienia, jakie system przesyła aktorom (na przykład informację, jaką otrzymuje reklamodawca (Advertiser) zainteresowany sponsorowaniem turnieju). W tabeli 5.10 widzimy definicje obiektów brzegowych przypadku użycia Announce '-•Tournament, zaś w ramce poniżej widoczna jest kolejna porcja pytań dla klienta, związanych z tymi obiektami. Kolejne pytania dla klienta systemu ARENA ® Jak należy traktować sponsorów, którzy nie odpowiadają na powiadomienia? •

Jak można się zareklamować przy okazji nowego turnieju, jeśli brakuje grup zainteresowanych tym turniejem?

® W jaki sposób użytkownicy powinni być powiadamiani o zdarzeniach (przez e-mail, telefon komórkowy czy dedykowaną skrzynkę osobistą w systemie ARENA)?

5.6. Analiza przypadku — system AREIMA

251

Tabela 5.10. Obiekty brzegowe uczestniczące w przypadku użycia AnnounceTournament

Obiekt brzegowy

Definicja

TournamentForm

Formularz wykorzystywany przez kapitana ligi (LeagueOwner) do określenia właściwości turnieju (Tournament), z związku z organizowaniem nowych turniejów lub modyfikowaniem istniejących.

RequestSponsorshipForm

Formularz wykorzystywany przez kapitana ligi (LeagueOwner) do poszukiwania sponsora wśród zainteresowanych reklamodawców (Adverti ser).

SponsorshipRequest

Powiadomienie otrzymane przez reklamodawcę (Adverti s e r ) zgłaszającego chęć sponsorowania turnieju lub ligi.

SponsorshipReply

Powiadomienie otrzymane przez kapitana ligi (LeagueOwner), informujące, czy dany reklamodawca (Adverti s e r ) zainteresowany jest wyłącznym sponsorowaniem turnieju.

S e l e c t E x c l u s i veSponsorForm

Formularz wykorzystywany przez kapitana ligi (LeagueOwner) do wybrania wyłącznego sponsora turnieju i zamknięcia tym samym kwestii sponsoringu dla tego turnieju.

NotifylnterestGroupsForm

Formularz wykorzystywany przez kapitana ligi (LeagueOwner) do wysyłania powiadomień zainteresowanym użytkownikom.

InterestGroupNotice

Powiadomienie o zorganizowaniu nowego turnieju (Tournament) otrzymane przez zainteresowanych użytkowników.

Zauważmy, że AnnounceTournament jest przypadkiem użycia stosunkowo złożonym, angażującym wielu aktorów; skutkuje to stosunkowo dużą liczbą obiektów brzegowych. W praktyce większość spotykanych przypadków użycia zadowala się jednym obiektem brzegowym, za pomocą którego aktor inicjuje dany przypadek. Jednak każdy przypadek użycia musi posiadać przynajmniej jeden obiekt brzegowy, nawet jeśli współdzielony jest on z innymi przypadkami.

5.6.3. Identyfikacja obiektów sterujących Obiekty sterujące reprezentują koordynację między obiektami brzegowymi a obiektami encji. W typowym przypadku użycia pojedynczy obiekt sterujący, tworzony zaraz na początku, przez cały czas trwania przypadku gromadzi informację niezbędną do jego zamknięcia; po zakończeniu przypadku użycia wspomniany obiekt sterujący jest niszczony. W przypadku użycia AnnounceTournament zidentyfikowaliśmy pojedynczy obiekt sterujący — obiekt klasy AnnounceTournamentControl, odpowiedzialny za wysyłanie powiadomień do reklamodawców i kolekcjonowanie tych powiadomień, kontrolowanie dostępności zasobów oraz powiadamianie zainteresowanych użytkowników o wybranych zdarzeniach. Zauważmy przy tym, że generalnie w tym samym przypadku użycia uczestniczyć może kilka obiektów sterujących, jeśli na przykład trzeba koordynować alternatywny przepływ zdarzeń czy współpracę kilku asynchronicznych stacji roboczych lub gdy zakończenie przypadku użycia może być konsekwencją różnych informacji pochodzących z odmiennych źródeł.

252

Rozdział 4. •Zbieraniewymagań

5.6.4. Modelowanie interakcji między obiektami Zidentyfikowaliśmy już pewną liczbę obiektów encji, brzegowych i sterujących uczestniczących w przypadku użycia AnnounceTournament, zdefiniowaliśmy także ich atrybuty oraz skojarzenia między nimi. Teraz przedstawimy wspomniane obiekty na diagramie sekwencji, by poprzez uwidocznienie interakcji zachodzących w ramach wspomnianego przypadku użycia zidentyfikować dodatkowe atrybuty i skojarzenia. W diagramie sekwencji poszczególne kolumny odpowiadają poszczególnym obiektem, identyfikowanym w skrajnie górnym wierszu. Począwszy od skrajnej lewej kolumny, są to: aktor inicjujący (LeagueOwner), obiekt brzegowy warunkujący inicjację (TournamentForm), główny obiekt sterujący (AnnounceTournamentControl) i obiekty encji (Arena, League i Tournament). Kolejne kolumny przeznaczone są dla pozostałych aktorów uczestniczących i odpowiednich dla tych aktorów obiektów brzegowych. Ze względu na rozmiar wspomnianego diagramu podzielimy go na trzy fragmenty widoczne na trzech kolejnych rysunkach: i tak rysunek 5.19 przedstawiać będzie interakcje prowadzące do zorganizowania nowego turnieju, rysunek 5.20 związany będzie z przepływem sterowania w związku z poszukiwaniem i wyborem wyłącznego sponsora, zaś na rysunku 5.21 widoczne będą powiadamiania zainteresowanych grup o wybranych zdarzeniach. Diagram na rysunku 5.19 nie jest skomplikowany: LeagueOwner żąda utworzenia nowego turnieju i określa jego parametry początkowe (nazwę i maksymalną liczbę graczy). Tworzony jest obiekt AnnounceTournamentControl i — o ile pozwala na to stan dostępnych zasobów — obiekt encji Tournament reprezentujący nowy turniej.

Rysunek 5.19. Diagram sekwencji UML dla przypadku użycia AnnounceTournament, część odpowiedzialna za organizację nowego turnieju

5.6. Analiza przypadku — system AREIMA

253

Diagram z rysunku 5.20 jest o tyle bardziej interesujący, że prowadzi do zidentyfikowania nowych skojarzeń i atrybutów. W związku ze sponsoringiem obiekt sterujący musi wpierw uzyskać listę zainteresowanych sponsorów; listę taką utrzymuje obiekt klasy Arena — dokładniej: przez cały czas utrzymuje on listę wszystkich zarejestrowanych reklamodawców (Advertiser) — może ją więc po prostu udostępnić wspomnianemu obiektowi sterującemu (i ewentualnie innym obiektom sterującym w innych przypadkach użycia). Aby wysłać reklamodawcy powiadomienie, potrzebujemy odpowiedniej informacji kontaktowej, na przykład adresu e-mail, bądź też możemy utworzyć w systemie ARENA prywatne skrzynki powiadomień dla poszczególnych reklamodawców. W związku z tym dodajemy do klasy Adverti ser nowy atrybut — informacje kontaktowe — na którego wartość składać się będzie początkowo adres e-mail reprezentowanego reklamodawcy, a w przyszłości prawdopodobnie także dane dotyczące innych kanałów komunikacji. Przewidując analogiczną potrzebę w stosunku do innych aktorów, dodajemy atrybut kontaktowy także do klas League "-•Owner i Player.

Rysunek 5.20. Diagram sekwencji UML dla przypadku użycia AnnounceTournament, część odpowiedzialna za sponsoring

Konstruując diagram z rysunku 5.21, uświadomiliśmy sobie, że przypadek użycia nie precyzuje sposobu powiadamiania sponsorów. W konsekwencji dodaliśmy do niego nowy krok, obejmujący powiadamiania wszystkich sponsorów, którzy odpowiedzieli na ogłoszenie kapitana ligi, o jego decyzji wyboru wyłącznego sponsora. Krok ten wymaga kolejnego obiektu brzegowego — SponsorNoti ce. Pozostała część interakcji nie wnosi nic nowego, poza potwierdzeniem słuszności naszych przewidywań, prowadzących do uprzedniego zdefiniowania klas InterestGroup i InterestGroupNotice.

254

Rozdział 4. •Zbieraniewymagań

Rysunek 5.21. Diagram sekwencji UML dla przypadku użycia AnnounceTournament, część odpowiedzialna za powiadamianie zainteresowanych grup

5.6.5. Weryfikacja i konsolidacja modelu analitycznego Skoro zdefiniowaliśmy już większość obiektów uczestniczących w przypadku użycia Announce ^Tournament, wraz z ich atrybutami i skojarzeniami, udokumentujemy rezultat naszej analizy w postaci diagramu klas, a dokładniej — w postaci kilku diagramów, zidentyfikowaliśmy bowiem dość znaczącą liczbę obiektów. Diagramy te można wykorzystać jako indeks do opracowanego wcześniej słownika — jakkolwiek trudno oczekiwać, by wspomniane diagramy mogły być interesujące same z siebie dla klienta i użytkowników, mogą się jednak okazać przydatne dla generowania dodatkowych pytań przy okazji kolejnych wywiadów z klientem. Skupimy się najpierw na obiektach encji, te bowiem, jako reprezentujące koncepcje dziedziny aplikacyjnej, wymagają szczególnie starannej weryfikacji ze strony klienta (patrz rysunek 5.22). Zwróćmy uwagę na naczelną rolę obiektu klasy Arena: reprezentuje on konkretną instancję systemu ARENA i udostępnia wszelkie informacje o charakterze globalnym dla tej instancji — listy grup zainteresowań (InterestGroup), reklamodawców (Adverti ser), kapitanów lig (LeagueOwner), gier (Game) i stylów rozgrywek (TournamentStyl e). Zauważmy także, iż obiekt Arena wyznacza pewną granicę: żaden z obiektów systemu nie jest współdzielony między dwie lub więcej instancji — przykładowo każdy kapitan ligi (LeagueOwner) należy do dokładnie jednej instancji. Jeżeli dana osoba fizyczna zabawia się wieloma instancjami systemu ARENA, w każdej z tych instancji ma osobne konto i reprezentowana jest przez osobny obiekt klasy LeagueOwner. Jest to wynik decyzji, jakie podjęliśmy na etapie analizy systemu, bazując na naszej interpretacji deklaracji problemu, naszym doświadczeniu i naszej ocenie zasobów dostępnych dla zbudowania systemu. Oczywiście, wszystkie decyzje tej rangi bezwzględnie muszą być zweryfikowane i zatwierdzone przez klienta.

5.6. Analiza przypadku — system AREIMA

255

Rysunek 5.22. Obiekty encji zidentyfikowane na podstawie analizy przypadku użycia Announce ^Tournament

Kolejny nasz diagram klas (ten z rysunku 5.23) uwidacznia relacje dziedziczenia między poszczególnymi klasami. Jakkolwiek język UML umożliwia reprezentowanie skojarzeń i relacji dziedziczenia na tym samym diagramie klas, zalecaną praktyką jest rysowanie dwóch odrębnych diagramów. Powody tego są dwa: po pierwsze, skojarzenia i dziedziczenie reprezentowane są za pomocą podobnych symboli, które łatwo pomylić ze sobą, po drugie — skojarzenia między klasami i hierarchia klas to zagadnienia, którymi analitycy zajmują się w różnym czasie; jak jednak zobaczymy w następnych czterech rozdziałach, nie jest to prawda na etapie projektowania systemu i projektowania obiektów, kiedy to często konieczne jest uwzględnianie obu relacji dla lepszego zrozumienia powiązań między klasami. Na rysunku 5.30 widoczne są trzy hierarchie dziedziczenia. Pierwsza z nich rozpoczyna się od abstrakcyjnej klasy User, zdefiniowanej w wyniku generalizacji i reprezentującej (w postaci odpowiednich atrybutów) własności wspólne dla wszystkich użytkowników, takie jak informacje kontaktowe i procedury rejestracyjne. Zauważmy przy tym, że terminu „użytkownik" (user) używaliśmy już wielokrotnie w deklaracji problemu i w przypadkach użycia, tak więc dokonujemy teraz formalizacji terminu stosowanego dotąd w znaczeniu intuicyjnym. Dwie pozostałe hierarchie są wynikiem specjalizacji Idas Game i TournamentStyle, reprezentujących koncepcje (odpowiednio) gry i stylu rozgrywek. Klasy Ti cTacToe i Chess, stanowiące wynik specjalizowania klasy Game, odpowiadają (kolejno) grze w kółko i krzyżyk oraz szachom.

256

Rozdział 4. •Zbieraniewymagań

Rysunek 5.23. Hierarchia dziedziczenia między klasami obiektów encji przypadku użycia Announce ^Tournament

Klasy KnockOutStyle i RoundRobinStyle, jako specjalizacje klasy TournamentStyle, odpowiadają stylowi „przegranej przez nokaut", zgodnie z którym gracz po pierwszej przegranej odpada z turnieju, i stylowi „karuzelowemu", zgodnie z którym każdy gracz pojedynkuje się dokładnie jeden raz z każdym z pozostałych graczy. Wreszcie na kolejnym diagramie klas (patrz rysunek 5.24) uwidoczniliśmy skojarzenia między obiektami brzegowymi, obiektem sterującym i wybranymi obiektami encji. Diagram ten stworzyliśmy pośrednio, najpierw generując diagram komunikacyjny z diagramu sekwencji i umieszczając obiekt sterujący z lewej strony, obiekty brzegowe pośrodku, z prawej zaś obiekty encji; następnie tam, gdzie to potrzebne, zastąpiliśmy interakcje przez skojarzenia, które dodatkowo opatrzyliśmy „nawigacjami" w celu ukazania kierunku zależności: obiekty sterujące i brzegowe zawierają informacje o wszystkich innych obiektach, podczas gdy obiekty encji niezależne są od obiektów dwóch pozostałych kategorii. Podczas gdy diagram klas z rysunku 5.22 koncentruje się głównie na relacjach między koncepcjami dziedziny aplikacyjnej, diagram widoczny na rysunku 5.24 uwidacznia (w przybliżeniu) koncepcje związane z przepływem sterowania w ramach przypadku użycia. Obiekt sterujący służy tu jako spoiwo między obiektami brzegowymi i obiektami encji, reprezentuje bowiem koordynację i chronologię między formularzami a powiadomieniami. Na diagramach z rysunków 5.19, 5.20 i 5.21 obiekt sterujący odpowiedzialny jest za tworzenie niektórych obiektów brzegowych. Diagram widoczny na rysunku 5.24 jest swego rodzaju sumarycznym zestawienie obiektów biorących udział w przypadku użycia, z uwzględnieniem skojarzeń wykorzystywanych do realizacji tego przypadku. Jednakże to diagram sekwencji dostarcza pełną informację na temat sekwencjonowania operacji i przepływu sterowania.

5.6.6. Wnioski W tej sekcji stworzyliśmy część analitycznego modelu obiektowego, odpowiadającą przypadkowi użycia AnnounceTournament w systemie ARENA. Rozpoczęliśmy od zidentyfikowania obiektów encji, wykorzystując heurystyki Abbotta, po czym zidentyfikowaliśmy obiekty brzegowe i obiekt sterujący, by następnie za pomocą diagramów sekwencji odnaleźć dodatkowe

5.6. Analiza przypadku — system AREIMA

257

Rysunek 5.24. Skojarzenia między obiektami brzegowymi, obiektem sterującym i wybranymi obiektami encji przypadku użycia AnnounceTournament

skojarzenia, atrybuty i obiekty. Na zakończenie skonsolidowaliśmy model obiektowy i ukazaliśmy go w formie serii diagramów klas. Na podstawie tych czynności czytelnicy mogli nauczyć się, że: • identyfikowanie obiektów oraz ich atrybutów i skojarzeń wymaga wielu iteracji, często z udziałem klienta, • identyfikowanie obiektów wymaga skorzystania z wielu źródeł, między innymi deklaracji problemu, modelu przypadków użycia, słownika i opisu przepływu zdarzeń w przypadkach użycia, • każdy niebanalny przypadek użycia wymaga wielu diagramów sekwencyjnych i pewnej liczby diagramów klas; niewykonalne jest przedstawienie wszystkich rozpoznanych obiektów na pojedynczym diagramie, zamiast tego wykorzystuje się więc kilka rodzajów diagramów, służących różnym celom — przykładowo ukazaniu skojarzeń między obiektami encji czy między wszystkimi obiektami uczestniczącymi w pojedynczym przypadku użycia, • produkty docelowe, istotne dla całej realizacji projektu, powinny być na bieżąco aktualizowane w miarę wprowadzania zmian do modelu analitycznego; inne produkty, takie jak diagramy sekwencji, mogą być uaktualniane rzadziej lub z opóźnieniem — utrzymanie bezwzględnej spójności modelu jest bowiem zadaniem nierealnym, • istnieje wiele sposobów modelowania tej samej dziedziny aplikacyjnej lub tego samego systemu, zależnie od osobistego stylu i doświadczenia analityka; wymaga to przyjęcia pewnego standardu dotyczącego wspomnianego stylu i konwencji w projekcie, tak by poszczególni analitycy mogli się ze sobą efektywnie komunikować.

258

Rozdział 4. •Zbieraniewymagań

5.7. Literatura uzupełniająca Podział obiektów modelu analitycznego na trzy kategorie encji, brzegowe i sterujące — stał się popularny dzięki książce I. Jacobsona, M. Christersona, P. Jonssona i G. Overgaarda [Jacobson i in., 1992], Ów podział ma swój pierwowzór w paradygmacie „model-widok-kontroler" (MVC — Model-View-Controller) zastosowanym praktycznie po raz pierwszy w środowisku Smalltalk-80; jego odmiana znalazła swe miejsce w środowisku Swing języka Java [JFC, 2009]. Karty CRC zaproponowane zostały przez K. Becka i W. Cunninghama jako środek pomocny w przyswajaniu myślenia zorientowanego obiektowo.. Autorzy opisali je w materiałach konferencji OOPSLA [Beck i Cunningham, 1989]. Karty CRC wykorzystywane są także w metodologii projektowania sterowanego odpowiedzialnością opisanej przez R. WirfsBrock, B. Wilkersona i L. Wiener [Wirfs-Brock i in., 1990], Analiza i projektowanie zorientowane obiektowo to wynik ewolucji wielu zbiorów rozmaitych heurystyk i terminologii. Modelowanie, podobnie jak programowanie, jest rzemiosłem wymagającym doświadczenia z jednej strony, a świadomości nieuchronnego popełniania błędów z drugiej. Wynika stąd krytyczna rola sprawnej komunikacji między programistami a klientem i użytkownikami. Książka J. Rumbaugha, M. Błahy, W. Premerlaniego, F. Eddy'ego, i W. Lorensena [Rumbaugh i in., 1991] jest doskonałym przewodnikiem dla nowicjuszy w modelowaniu opartym na klasach. Obszerne studium analizy i projektowania zorientowanych obiektowo, w tym modelowania w oparciu o przypadki użycia i wielokrotne wykorzystywanie wzorców projektowych, znajdą czytelnicy w nowszej książce C. Larmana [Larman, 2005]. Z kolei książka B. P. Dougłassa [Douglass, 1999] dostarcza szczegółowe informacje na temat modelowania dynamicznego w oparciu o diagramy stanów, wraz z interesującymi.heurystykami w tym zakresie.

5.8. Ćwiczenia 5.1. Rozpatrzmy system plików z graficznym systemem użytkownika, na przykład Windows Explorer, Finder Macintosha czy linuksowy KDE. W przypadku użycia opisującym kopiowanie pliku z dyskietki na dysk twardy zidentyfikowano następujące obiekty: Fi 1 e (plik), Icon (ikona), TrashCan (kosz), Fol der, Di sk, Poi nter (wskaźnik). Które z nich są obiektami encji, które brzegowymi, a które sterującymi? 5.2. W systemie wspomnianym w ćwiczeniu 5.1 rozpatrzmy scenariusz obejmujący wybór pliku (File) na dyskietce, przeciągnięcie myszą wybranej pozycji do folderu (Folder) i zwolnienie lewego przycisku myszy. Zidentyfikuj i zdefiniuj choć jeden obiekt sterujący uczestniczący w tym scenariuszu. 5.3. Zorganizuj obiekty, o których mowa w ćwiczeniach 5.1 i 5.2, w poziomą strukturę typową dla diagramu sekwencji: począwszy od lewej, najpierw obiekty brzegowe, potem sterujące i na końcu obiekty encji. Narysuj sekwencję interakcji prowadzącą do upuszczenia wybranego pliku w folderze docelowym. Zignoruj możliwe sytuacje wyjątkowe. 5.4. Przeanalizuj diagram sekwencji utworzony w ćwiczeniu 5.3 i zidentyfikuj skojarzenia między obiektami.

5.8. Ćwiczenia

259

5.5. Zidentyfikuj atrybuty każdego' obiektu mające związek ze scenariuszem opisanym w ćwiczeniu 5.2. Uwzględnij sytuacje wyjątkowe polegające na: a) braku wolnego miejsca na dysku docelowym, b) istnieniu w folderze docelowym pliku o takiej samej nazwie jak plik właśnie kopiowany. 5.6. Na rysunku 5.25 widoczny jest model obiektowy (zaczerpnięty z książki M. Jacksona [Jackson, 1995]). Opierając się na powszechnie znanych właściwościach kalendarza gregoriańskiego, wymień problemy, jakie stwarzać może ów model i zaproponuj jego modyfikację pod kątem wyeliminowania tych problemów.

Rysunek 5.25. Naiwny model kalendarza gregoriańskiego

5.7. Czy operując jedynie krotnościami skojarzeń, możesz tak zmodyfikować model widoczny na rysunku 5.25, by programista nieobeznany z kalendarzem gregoriańskim potrafił wydedukować liczbę dni w każdym miesiącu? Zidentyfikuj ewentualne dodatkowe klasy potrzebne do wykonania tego zadania. 5.8. Rozpatrz system sygnalizacji świetinej na skrzyżowaniu dwóch dróg dwukierunkowych. Zakładając najprostszy algorytm sterowania ruchem (dopuszczenie do ruchu na jednej z dróg, przy jednoczesnym zablokowaniu ruchu w kierunku prostopadłym, z cyldiczną zmianą), zidentyfikuj stany tego systemu i narysuj diagram stanów ilustrujący jego funkcjonowanie. Dla każdego sygnalizatora zakładamy trzy stany, odpowiadające światłom: zielonemu, żółtemu i czerwonemu. 5.9. Narysuj diagram Was odpowiadający diagramowi sekwencji z rysunku 2.29. Wskazówka: Rozpocznij od zidentyfikowania obiektów na wspomnianym diagramie. 5.10. Rozpatrz dodanie wymagania pozafunkcyjnego stanowiącego, że działania zmierzające do wyłącznego sponsorowania turnieju wymagać mają jak najmniejszego wysiłku ze strony reklamodawcy. Zmodyfikuj w związku z tym przypadki użycia AnnounceTournament (patrz tabela 5.8) i ManageAdvertisements (rozwiązanie ćwiczenia 4.12), tak by reklamodawca mógł zaznaczyć w swym profilu opcję powodującą, iż na otrzymane od kapitana ligi zapytanie o chęć wyłącznego sponsorowania automatycznie udzielona zostanie odpowiedź twierdząca.

260

Rozdział 4. •Zbieraniewymagań 5.11. Z i d e n t y f i k u j i z d e f i n i u j d o d a t k o w e o b i e k t y encji, b r z e g o w e i sterujące, jakie t r z e b a b y w p r o w a d z i ć d o p r z y p a d k u użycia AnnounceTournament w celu z r e a l i z o w a n i a z m i a n y o p i s a n e j w ć w i c z e n i u 5.10. 5.12. U a k t u a l n i j d i a g r a m y klas z r y s u n k ó w 5.22 i 5.24 tak, b y u w z g l ę d n i a ł y n o w e obiekty, o k t ó r y c h m o w a w ć w i c z e n i u 5.11. 5.13. Bazując n a d i a g r a m a c h sekwencji w i d o c z n y c h n a r y s u n k a c h 5.19, 5.20 i 5.21, narysuj d i a g r a m s t a n ó w o p i s u j ą c y z a c h o w a n i e o b i e k t u AnnounceTournamentControl. Pot r a k t u j k a ż d e wysłanie p o w i a d o m i e n i a i k a ż d e o t r z y m a n i e p o w i a d o m i e n i a j a k o z d a rzenie w y z w a l a j ą c e z m i a n ę s t a n u .

Bibliografia [Abbott, 1983]

R. Abbott „Program design by informal English descriptions", Communications of the ACM, t. 26, nr 11, 1983.

[Beck i Cunningham, 1989]

K. Beck, W. C u n n i n g h a m „A laboratory for teaching object-oriented thinking" OOPSLA'89 Conference Proceedings, New Orleans, LA, 1 - 6 października 1989.

[De Marco, 1978]

T. De Marco Structured Analysis and System Specification, New York, 1978.

[Douglass, 1999]

B. P. Douglass Doing Hard Time: Using Object Oriented Programming and Software Patterns in Real Time Applications, Addison-Wesley, Reading, MA, 1999.

[Jackson, 1995]

M. Jackson Software Requirements i Specifications: A Lexicon of Practice, Principles and Prejudices, Addison-Wesley, Reading, MA, 1995.

[Jacobson i in., 1992]

I. Jacobson, M. Christerson, P. Jonsson, G. Overgaard Object-Oriented Software Engineering — A Use Case Driven Approach, Addison-Wesley, Reading, MA, 1992.

[Jacobson i in., 1999]

I. Jacobson, G. Booch, J. Rumbaugh The Unified Software Process, Addison-Wesley, Reading, MA, 1999.

[JFC, 2009]

Java Foundation

[Larman, 2005]

C. Larman Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design, wyd. trzecie, Prentice Hall, Upper Saddle River, NJ, 2005.

[Rumbaugh i in., 1991]

J. R u m b a u g h , M. Błaha, W. Premerlani, F. Eddy, W . Lorensen Object-Oriented Modeling and Design, Prentice Hall, Englewood Cliffs, NJ, 1991.

[Wirfs-Brock i in., 1990]

R. Wirfs-Brock, B. Wilkerson, L. Wiener Designing Object-Oriented Software, Prentice Hall, Englewood Cliffs, NJ, 1990.

Yourdon,

Development

Classes, JDK Documentation, Javasoft, 2009.

6.1.

Wstęp: projekt mieszkania

264

6.2.

O projektowaniu systemu ogólnie

266

6.3.

Koncepcje projektowania systemu

267

6.3.1. 6.3.2. 6.3.3. 6.3.4. 6.3.5.

268 270 271 275 279

6.4.

Podsystemy i klasy Usługi i interfejsy podsystemów Sprzężenie i spoistość Warstwy i partycje Style architektoniczne

Aktywności projektowania systemu: od obiektów do podsystemów 6.4.1. Punkt wyjścia: model analityczny systemu planowania podróży 6.4.2. Identyfikowanie celów projektowych 6.4.3.

Identyfikowanie podsystemów

288 288 290 294

6.5.

Literatura uzupełniająca

296

6.6.

Ćwiczenia

297

Bibliografia

298

6 Projektowanie systemu — dekompozycja na podsystemy Oprogramowanie można tworzyć na dwa sposoby: tak prosto, że w oczywisty sposób będzie wolne od błędów, albo tak skomplikowanie, że nie będzie w nim oczywistych błędów. C. A. R. Hoare The Emperor's Old Clothes

^IProjektowanie systemu to transformowanie modelu analitycznego w model projektu systemu. W trakcie projektowania systemu programiści definiują cele projektowe i dokonują dekompozycji systemu na prostsze podsystemy, z których każdy może być realizowany przez pojedynczy zespół. Programiści wybierają także strategie budowania systemu — strategie sprzętowe i programowe, strategie trwałego przechowywania danych, globalne sterowanie przepływem informacji, politykę kontroli dostępu i obsługę sytuacji granicznych. Wynikiem tych działań jest model odzwierciedlający wymienione strategie i wspomniany podział systemu na podsystemy. Projektowanie systemu nie jest działalnością algorytmiczną: programiści zmuszeni są kompromisów w związku z licznymi celami projektowymi, często sprzecznymi ze sobą. Nie potrafią ponadto przewidywać a priori wszystkich przyszłych problemów, nie mają bowiem dostatecznie jasnego obrazu dziedziny realizacyjnej. Samo projektowanie systemu też da się przedstawić w postaci dekompozycji na poszczególne aktywności, z których każda związana jest z określoną częścią ogólnie pojmowanej dekompozycji na podsystemy. Oto one. • Rozpoznawanie celów projektowych. Programiści identyfikują zakładane cechy systemu i określają priorytety ich optymalizacji. • Projektowanie wstępnej dekompozycji. Programiści dekomponują system na prostsze części, bazując na modelu przypadków użycia i modelu analitycznym. Jako punktu wyjścia wykorzystują w tym celu standardowe style architektoniczne. • Doskonalenie dekompozycji stosownie do celów projektowych. Początkowy wynik dekompozycji systemu rzadko kiedy czyni zadość celom projektowym, wymaga więc optymalizacji pod tym właśnie kątem. W tym rozdziale skupimy się na dwóch pierwszych z wymienionych aktywności, w następnym natomiast udoskonalimy dekompozycję i obszernie omówimy jej szczegóły na przykładzie zastosowania do systemu ARENA.

264

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

6.1. Wstęp: projekt mieszkania Na konstruowanie systemu składają się: jego projektowanie, projektowanie jego obiektów i implementowanie. W ramach każdej z tych aktywności zadanie programistów postrzegać można jako wypełnianie luki między specyfikacją wymagań, wieńczącą zbieranie wymagań od klienta, a gotowym systemem, którego dostarczenia oczekują klient i użytkownicy. Projektowanie systemu jest pierwszym krokiem na tej drodze i skupia się na jego podziale na mniejsze części, łatwiejsze do ogarnięcia. Na etapie zbierania wymagań i ich analizy wysiłek programistów koncentruje się na zrozumieniu przeznaczenia systemu i szczegółów jego funkcjonalności; w czasie projektowania systemu kluczowymi problemami są procesy, struktury danych oraz komponenty sprzętowe i programowe niezbędne do jego implementacji. Pierwszym znaczącym wyzwaniem jest wówczas pogodzenie sprzecznych kryteriów i ograniczeń wynikających z dekompozycji systemu. Jako przykład procesu generującego analogiczne wyzwania rozpatrzmy zadanie zaprojektowania układu nowego mieszkania. Gdy już porozumiemy się z klientem co do lokalizacji domu, liczby pomieszczeń, powierzchni salonu i tak dalej, zadaniem architekta jest podział powierzchni przyszłego mieszkania na poszczególne pomieszczenia, czyli zaprojektowanie rozmieszczenia ścian, drzwi i okien. Zadanie to musi zostać wykonane z uwzględnieniem wielu wymagań funkcyjnych: przykładowo kuchnia powinna być zlokalizowana jak najbliżej jadalni i jednocześnie jak najbliżej garażu, łazienka powinna znajdować się jak najbliżej sypialni i tak dalej. Architekt musi także liczyć się z kilkoma ograniczeniami: wyposażenie kuchni składać się będzie ze standardowych modułów (mebli, kuchenek, zmywarki), zaś w sypialni znajdą się łóżka czy leżanki, których wymiary też są zestandaryzowane — wszystko to ma wpływ na (rozsądne) rozmieszczenie okien i drzwi. Architekta nie interesują już jednak takie aspekty urządzenia mieszkania jak umeblowanie poszczególnych pomieszczeń czy kolor parkietu, te bowiem zostawiane są do wyboru klientowi. Na rysunku 6.1 widoczna jest krótka historia ewoluowania opisanej koncepcji architektonicznej; jej autorowi postawiono wymóg stosowania się do następujących ograniczeń. 1. W mieszkaniu znajdować się powinny dwie sypialnie, studio, kuchnia i salon. 2. Droga, jaką prawdopodobnie pokonywali będą mieszkańcy w trakcie codziennych czynności domowych, powinna być sumarycznie jak najmniejsza. 3. Należy maksymalnie wykorzystywać światło dzienne. W związku z treścią powyższych wymagań warto zauważyć, że najbardziej intensywna wędrówka mieszkańców dokonuje się w trzech obszarach: między drzwiami wejściowymi a kuchnią (gdy żywność i produkty towarzyszące wyładowywane są z samochodu), między kuchnią a jadalnią (gdy posiłki podawane są na stół) oraz między sypialniami a łazienką (łazienkami). Zakładamy przy tym, że mieszkańcy większą część czasu spędzają w salonie lub jadalni oraz głównej sypialni. W górnej części rysunku 6.1 widoczna jest wersja nr 1 projektu, w której rychło spostrzec można poważny mankament: jadalnia oddalona jest za bardzo od kuchni. By naprawić to niedomaganie, zamieniamy kuchnię z drugą sypialnią (co symbolizują szare strzałki), dzięki czemu dodatkowo salon przesuwa się ku południowej ścianie domu. Teraz jednak — w wersji

6.1. Wstęp: projekt mieszkania

265

Rysunek 6.1. Przykład projektu układu pomieszczeń w mieszkaniu. Kolejne wersje coraz lepiej realizują wymaganie zmniejszenia łącznej drogi pokonywanej przez mieszkańców oraz jak najpełniejsze wykorzystywanie światła dziennego

nr 2 — kuchnia i schody znajdują się zbyt daleko od drzwi wejściowych. W wersji nr 3 przesunięcie drzwi wejściowych na ścianę północną usuwa ten problem, dodatkowo pozwalając na zbliżenie drugiej sypialni do sypialni głównej i przesunięcie łazienki bliżej obu sypialni. Efektem ubocznym jest powiększenie powierzchni salonu. Wszystko to odbywa się w zgodzie z wymienionymi na wstępie ograniczeniami.

266

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Mając już ustalony rozkład poszczególnych pomieszczeń, możemy zająć się lokalizacją okien i drzwi tak, by jak najlepiej spełnione zostały wymienione wymagania. To zadanie kończy projekt, bez określenia szczegółowego umeblowania poszczególnych pomieszczeń. Można teraz przystąpić do projektowania sieci elektrycznej, wodociągowej, kanalizacyjnej i grzewczej budynku. Nieprzypadkowo sięgnęliśmy po analogię architektoniczną, projektowanie domów i mieszkań ma bowiem wiele wspólnego z inżynierią oprogramowania, co pokrótce uzasadniamy w tabeli 6.1. W obu przypadkach zadanie projektowania podzielone jest na prostsze komponenty i interfejsy, w obu mamy do czynienia z wymaganiami funkcyjnymi i pozafunkcyjnymi, oba też niosą ze sobą jednakowo poważne konsekwencje ewentualnych pomyłek i niedopatrzeń. Oba wreszcie przypadki abstrahują od szczegółowego projektu poszczególnych komponentów. Tabela 6.1. Dwie siostry — architektura i inżynieria oprogramowania

Koncepcja architektoniczna

Koncepcja inżynierii oprogramowania

Komponenty

Pomieszczenia

Podsystemy

Interfejsy

Drzwi

Usługi

Wymagania pozafunkcyjne

Maksymalizacja powierzchni mieszkalnej

Minimalizacja czasu reakcji

Wymagania funkcyjne

D o m mieszkalny

Przypadki użycia

Realia przeróbek

Przestawianie ścian

Zmiana interfejsów podsystemów

W sekcji 6.2 przedstawiamy widok „z lotu ptaka" na projektowanie systemu i związek tego projektowania z analizą wymagań. W sekcji 6.3 przedstawiamy koncepcję podsystemów i dekompozycji, zaś sekcję 6.4 poświęcamy aktywnościom związanym z projektowaniem systemu i pokazujemy na przykładzie, jak poszczególne podsystemy powinny ze sobą współpracować.

6.2. O projektowaniu systemu ogólnie Rezultatem analizy wymagań jest model, który tworzą następujące produkty: • zbiór wymagań pozafunkcyjnych i ograniczeń, takich jak maksymalny czas reakcji, minimalna przepustowość, niezawodność czy platforma systemu operacyjnego, •

model przypadków użycia, opisujący zachowanie systemu z perspektywy współdziałających z nim aktorów,



model obiektowy, opisujący encje będące przedmiotem przetwarzania systemu,

• diagram sekwencji dla każdego przypadku użycia, ukazujący sekwencję interakcji między obiektami uczestniczącymi w danym przypadku użycia. Model analityczny jest opisem systemu widzianego oczami aktorów i stanowi podstawę komunikacji między klientem a programistami. Model analityczny nie zawiera jednak informacji na temat wewnętrznej struktury systemu, jego konfiguracji sprzętowej i, ogólnie rzecz ujmując,

6.3. Koncepcje projektowania systemu

267

sposobów jego realizacji. Pierwszym krokiem w tym kierunku jest bowiem projektowanie systemu, którego rezultat stanowią następujące produkty: • cele projektowe, opisujące cechy systemu stanowiące dla programistów przedmiot optymalizacji, • architektura programowa, ujmująca dekompozycję na podsystemy w kategoriach odpowiedzialności każdego z nich, zależności między nimi, ich odwzorowania w komponenty sprzętowe i ogólne założenia dotyczące między innymi kontroli dostępu, przepływu sterowania i strategii trwałego przechowywania danych, • graniczne przypadki użycia, opisujące sytuacje brzegowe, ekstremalne lub wyjątkowe związane z systemem: konfigurowanie, instalowanie, uruchamianie, zamykanie i reagowanie na występujące błędy. Cele projektowe formułowane są na podstawie wymagań pozafunkcyjnych. Stanowią one dla programistów swoisty przewodnik, w sytuacji gdy trzeba dokonywać wyborów, czy decydować o priorytetach w kręgu sprzecznych przesłanek i wymagań. Lwią część projektowania systemu stanowi jego dekompozycja: każdy podsystem powstały w wyniku tej dekompozycji przeznaczony jest do opracowania niezależnie przez osobny zespół — w ten oto sposób programiści radzą sobie z ogólną złożonością, sprowadzając pojedynczy złożony problem do kilku prostszych podproblemów, z których każdy możliwy jest do ogarnięcia przez jedną osobę lub przynajmniej pojedynczy zespół. Ponieważ poszczególne podzespoły realizują swoje podsystemy niezależnie, konieczne jest uprzednie rozwiązanie problemów i wątpliwości dotyczących systemu jako całości. W tym rozdziale opiszemy koncepcje dekompozycji systemu na podsystemy i zaprezentujemy przykłady konkretnych wzorców dekompozycyjnych, w inżynierii oprogramowania zwanych popularnie „stylami architektonicznymi". W następnym rozdziale omówimy doskonalenie dekompozycji, mające na celu osiągnięcie specyficznych celów projektowych. Na rysunku 6.2 przedstawiliśmy poglądowo usytuowanie projektowania systemu w kontekście innych aktywności inżynierii oprogramowania.

6.3. Koncepcje projektowania systemu W tej sekcji opiszemy szczegółowo dekompozycję systemu i jej właściwości. Rozpoczniemy od zdefiniowania koncepcji podsystemu i jego związku z klasami (patrz sekcja 6.3.1), po czym zajmiemy się interfejsem podsystemu (patrz sekcja 6.3.2): podsystemy dostarczają usług dla innych podsystemów — usługa jest zbiorem powiązanych operacji podporządkowanych wspólnemu celowi. W trakcie projektowania systemu poszczególne podsystemy rozważane są najpierw głównie w kontekście oferowanych przez siebie usług; dla programisty zewnętrzny obraz podsystemu wyraża się w postaci jego interfejsu, czyli zestawu udostępnianych operacji. W sekcji 6.3.3 zajmiemy się dwiema ważnymi właściwościami podsystemów: sprzężeniem i spoistością. Sprzężenie między dwoma podsystemami jest miarą ich wzajemnej zależności, podczas gdy spoistość charakteryzuje zależności pomiędzy klasami pojedynczego podsystemu. Ideałem dekompozycji jest uzyskanie minimalnego sprzężenia między poszczególnymi podsystemami i maksymalnej spoistości każdego z nich. Podsystemy mogą być powiązane na dwa

268

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Rysunek 6.2. Aktywności związane z projektowaniem systemu

różne sposoby, którymi zajmiemy się w sekcji 6.3.4: architektura warstwowaoznacza hierarchiczną organizację podsystemów, w ramach której każda warstwa hierarchii dostarcza usługi na potrzeby warstw wyższych, jednocześnie korzystając z usług oferowanych przez warstwy niższe, natomiast partycjonowanie oznacza organizację podsystemów jako wzajemnych partnerów, nawzajem świadczących sobie usługi. W sekcji 6.3.5 przedstawimy natomiast kilka typowych, najczęściej spotykanych w praktyce przykładów stylów architektonicznych dekompozycji.

6.3.1. Podsystemy i klasy W rozdziale 2. „Modelowanie w języku UML" wprowadziliśmy rozróżnienie między dziedziną aplikacyjną a dziedziną realizacyjną. W celu zredukowania złożoności dziedziny aplikacyjnej wprowadziliśmy jej podział na części zwane „klasami", które następnie zorganizowaliśmy w pakiety. Podobnie w celu redukcji złożoności dziedziny realizacyjnej podzieliliśmy system na prostsze części, zwane podsystemami; każda z tych części składa się z klas dziedziny realizacyjnej. Podsystem jest wymienną częścią systemu, posiadającą dobrze zdefiniowane interfejsy i hermetyzującą stan oraz zachowanie składających się na nią klas. Pracochłonność stworzenia podsystemu nie przekracza zwykle możliwości pojedynczego zespołu lub nawet pojedynczego programisty, choć w przypadku systemów bardzo złożonych można stosować dekompozycję w sposób rekursywny (co pokazano schematycznie na rysunku 6.3), dzieląc poszczególne podsystemy na części jeszcze prostsze. Ponieważ poszczególne podsystemy z założenia uzależnione są od siebie w jak najmniejszym stopniu, konkretne zespoły mogą pracować nad nimi względnie niezależnie, z niewielkim narzutem na komunikację z innymi zespołami.

6.3. Koncepcje projektowania systemu

269

Rysunek 6.3. Dekompozycja systemu

I tak na przykład wielokrotnie już cytowany system FRI END można w wyniku dekompozycji podzielić na kilka prostszych podsystemów: • Di s p a t c h e r l n t e r f a c e — realizujący interfejs użytkownika dla dyspozytora (Di s p a t c h e r ) , • FieldOfficerlnterface — realizujący interfejs użytkownika dla funkcjonariusza (FieldOfficer),

• IncidentManagement — odpowiedzialny za tworzenie, modyfikowanie i trwałe przechowywanie obiektów Incident reprezentujących poszczególne wypadki, •

ResourceManagement — zarządzający dostępnymi zasobami (między innymi wozami strażackimi i karetkami pogotowia),

• MapManagement — utrzymujący informację geograficzną w postaci map i słownych opisów lokalizacji, • Noti f i cati on — implementujący komunikację między laptopem funkcjonariusza a stacją roboczą dyspozytora. Dekompozycję tę przedstawiliśmy schematycznie na diagramie komponentów UML, widocznym na rysunku 6.4. Poszczególne komponenty przedstawione są w postaci prostokątów, opatrzonych specjalną ikoną S , oznaczającą w języku UML komponent większej całości. Zależności między komponetami reprezentowane są przez skierowane linie przerywane. Język UML rozróżnia dwa rodzaje komponentów: komponent logiczny odpowiada podsystemowi niemającemu bezpośredniego fizycznego odpowiednika w świecie rzeczywistym — komponentem takim może być warstwa logiczna realizująca pewien zakres reguł biznesowych; natomiast komponent fizyczny to podsystem będący bezpośrednim odzwierciedleniem realnego bytu, na przykład serwera bazy danych. Niektóre języki programowania (między innymi Java i Modula 2) dostarczają programiście konstrukcje do modelowania podsystemów (w Javie są to pakiety, w Moduli — moduły). Inne języki1, takie jak C i C++, nie dostarczają bezpośredniego wsparcia dla podsystemów, przez co programiści muszą zorganizować owo wsparcie we własnym zakresie, co zwykle czynią

1

We współczesnych implementacjach języka Pascal (Delphi, FreePascal, Lazarus i tym podobnych) celowi temu służyć mogą moduły (units) i pakiety (packages). Większość współczesnych środowisk programistycznych umożliwia także tworzenie bibliotek DLL, będących „cegiełkami", z jakich buduje się złożony system — czego koronny przykład zaobserwować możemy w systemach linii Windows — przyp. tłum.

270

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Rysunek 6.4. Diagram komponentowy UML przedstawiający dekompozycję systemu FRIEND; komponentami są podsystemy stanowiące rezultat tej dekompozycji. Skierowane linie przerywane oznaczają zależności między poszczególnymi podsystemami

przez odpowiedni podział plików źródłowych systemu na katalogi i podkatalogi. Niezależnie jednak od zakresu wsparcia, jakie w zakresie dekompozycji oferują programistom różne języki programowania, muszą oni dekompozycję udokumentować szczególnie starannie, bowiem oznaczać ona będzie również dekompozycję realizacji projektu na poszczególne zespoły programistyczne.

6.3.2. Usługi i interfejsy podsystemów Z perspektywy użyteczności podsystem scharakteryzować można przez usługi, jakie oferuje on innym podsystemom. Usługa jest zbiorem powiązanych operacji podporządkowanych realizacji wspólnego celu. Przykładowo podsystem oferujący usługę powiadamiania definiuje operacje wykonujące wysyłanie powiadomień, nasłuchiwanie w kanałach komunikacyjnych, subskrybowanie do tych kanałów i anulowanie subskrypcji i tym podobne. Zbiór operacji, jakie podsystem udostępnia innym podsystemom, nazywamy jego interfejsem. Każda operacja wchodząca w skład interfejsu identyfikowana jest przez nazwę oraz scharakteryzowana pod względem zestawu (liczby i typów) parametrów i (ewentualnie) typu zwracanego wyniku. Projektowanie systemu skupia swe aktywności na definiowaniu usług świadczonych przez każdy podsystem, czyli na wymienieniu (enumeracji) poszczególnych operacji, ich parametrów oraz wysokopoziomowego zachowania. Doskonalenie interfejsów podsystemów, jakie dokonuje się na etapie projektowania obiektów, daje w wyniku interfejs programisty, popularnie określany skrótem API (Application Programmer Interface). Powiązanie danego podsystemu z innymi podsystemami poprzez jego interfejs odzwierciedlone jest w języku UML za pomocą specjalnej notacji złączy, zwanej także potocznie notacją „kółko-gniazdo" (ball-and-socket). Ikona € > będąca sednem tej notacji składa się z dwóch części: zewnętrzny łuk, zwany popularnie gniazdem (socket), reprezentuje korzystanie z operacji,

6.3. Koncepcje projektowania systemu

271

pozostała część ikony, popularnie nazywana piłką (bali) lub lizakiem (lollipop), odzwierciedla udostępnianie operacji. Na rysunku 6.5 widoczne jest zastosowanie tej notacji do przedstawienia powiązań podsystemów F i e l d O f f i c e r l n t e r f a c e i Di s p a t c h t e r l n t e r f a c e z podsystemem ResourceManagement. Podsystem Fi eldOff i c e r l n t e r f a c e wykorzystuje usługę ResourceUpdateServi ce do aktualizacji informacji o statusie i lokalizacji funkcjonariusza, zaś podsystem D i s p a t c h e r l n t e r f a c e korzysta z usługi R e s o u r c e A l l o c a t i o n S e r v i c e w celu zidentyfikowania dostępnych aktualnie zasobów i przydzielenia ich do obsługi wypadku. Obie wymienione usługi udostępniane są przez podsystem ResourceManagement.

Rysunek 6.5. Udostępnianie usług przez podsystem ResourceManagement (przykład notacji „kółko-gniazdo")

Notacja „kółko-gniazdo" staje się użyteczna dopiero wówczas, gdy proces dekompozycji przybiera zdecydowanie stabilny kształt i gdy po dobrym określeniu poszczególnych podsystemów przychodzi kolej na definiowanie oferowanych przez nie usług. Gdy brakuje jeszcze wyraźnego określenia funkcjonalności poszczególnych podsystemów, wystarczająca okazuje się notacja odzwierciedlająca jedynie powiązania między podsystemami, widoczna na rysunku 6.4. Definiowanie podsystemu w kategoriach oferowanych przez niego usług pomaga skupić się na jego interfejsie, w oderwaniu od konkretnej jego implementacji. Należy przy tym unikać uzależniania konkretnych elementów interfejsu od szczegółów implementacyjnych, przykładowo definicja operacji przetwarzającej kolekcję danych nie powinna w żaden sposób nawiązywać do jakiejkolwiek implementacji tej kolekcji (listy wiązanej, tablicy haszowanej, drzewa binarnego i tym podobnych); w ten sposób sprawiamy, że wspomniane szczegóły implementacyjne staja się niewidoczne dla innych podsystemów, a więc nie wywołują żadnych zewnętrznych konsekwencji w przypadku zmiany implementacji.

6.3.3. Sprzężenie i spoistość Sprzężeniem w zbiorze podsystemów nazywamy stopień ich wzajemnego uzależnienia, mierzony liczbą skojarzeń między nimi. W przypadku małego („luźnego") sprzężenia systemy są względnie niezależne i zmiana w zakresie jednego z nich ma niewielki wpływa na funkcjonowanie pozostałych — i vice versa: duże („ścisłe") sprzężenie wielu podsystemów powoduje, że stają się one wzajemnie wrażliwe na dokonywane zmiany. Pożądaną cechą dekompozycji jest tak małe sprzężenie między wynikowymi podsystemami, jakie tylko jest możliwe do osiągnięcia.

272

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Powróćmy jeszcze do przykładu z rysunku 6.4: projektując przedmiotowy system, zdecydowaliśmy o przechowywaniu wszystkich danych nieulotnych (czyli przekazywanych między kolejnymi uruchomieniami systemu) w relacyjnej bazie danych. Prowadzi to do zdefiniowania nowego podsystemu Database (patrz rysunek 6.6). Początkowo zaprojektowaliśmy jego interfejs w ten sposób, że systemy żądające przechowywania i udostępniania wspomnianych danych komunikować się z nim będą za pośrednictwem poleceń w powszechnie używanym, natywnym języku w rodzaju SQL — i tak na przykład podsystem IncidentManagement, zapisujący w rzeczonej bazie tworzone obiekty Incident i pobierający je stamtąd, będzie to czynił poprzez kierowanie do podsystemu Database poleceń w rodzaju CREATE i SELECT. Oznacza to bardzo silne sprzężenie między podsystemem Database a podsystemami będącymi jego klientami (IncidentManagement, ResourceManagement i MapManagement); jeśli zdecydujemy się na wymianę systemu zarządzania bazą danych na produkt używający innego dialektu języka zapytań, będziemy musieli zmienić także sposób komunikowania się trzech wymienionych podsystemów z podsystemem Database. Aby zredukować opisane uzależnienie, stworzyliśmy nowy podsystem Storage, pośredniczący w komunikacji między podsystemem Database a jego dotychczasowymi podsystemami klienckimi. Te ostatnie korzystają teraz z usług oferowanych przez podsystem Storage, którego interfejs niewrażliwy jest na konkretną implementację podsystemu Database. Gdy zmieni się serwer bazy danych, a w konsekwencji interfejs podsystemu Database, konieczne będą jedynie zmiany w zakresie implementacji podsystemu Storage, bez wpływu na inne podsystemy korzystające z jego usług. W ten oto sposób zmniejszyliśmy znacząco ogólne sprzężenie między podsystemami. Należy jednak wykazać się ostrożnością: redukowanie sprzężenia podsystemów nie może odbywać się w oderwaniu od konsekwencji, jakie niesie ze sobą. W przykładzie na rysunku 6.6 redukcja sprzężenia dokonała się za cenę zwiększenia ogólnej komplikacji systemu. Generalnie, redukowanie sprzężenia z dziką determinacją, za wszelką cenę, objawia się tworzeniem wielu dodatkowych, niepotrzebnych warstw abstrakcji, nie tylko pochłaniających cenny czas programistów, lecz zwykle także odbijających się negatywnie na ogólnej wydajności docelowego systemu. Duże sprzężenie oznacza wzajemną wrażliwość podsystemów na dokonywane zmiany, a więc jest problemem tylko wtedy, jeśli zmiany takie rzeczywiście są wysoce prawdopodobne. Spoistość podsystemu jest miarą uzależnienia jego własnych klas. System złożony z wielu obiektów, powiązanych wzajemnie i realizujących podobne zadania, jest systemem wysoce spoistym; system złożony z niewielu obiektów luźno powiązanych (lub niepowiązanych w ogóle) jest systemem o niskiej spoistości. Pożądaną cechą dekompozycji jest uzyskanie jak najbardziej spoistych podsystemów. Znaczenie spoistości zilustrujemy na przykładzie systemu śledzenia decyzji, wyposażonego w funkcje rejestrowania problemów projektowych, dokumentowania dyskusji, alternatywnych rozwiązań, decyzji i ich implementowania (patrz rysunek 6.7). Komponenty Desi gnProbl em i Opti on reprezentują eksplorację przestrzeni projektów: formułujemy system w kategoriach zbioru problemów projektowych (Desi gnProbl em) i dokumentujemy każdą opcję (Option) rozwiązywania poszczególnych problemów. Klasa C r i t e r i o n reprezentuje kryteria różnicowania istotności cech systemu: gdy tylko ocenimy uwzględniane opcje według zdefiniowanych kryteriów, dokonujemy implementowania podjętych decyzji (Decision) w formie zadań (Task). Zadania są rekurencyjnie dekomponowane na podzadania (Subtask) dostatecznie proste, by powierzyć ich realizację pojedynczym programistom; zadanie niepodlegające dekompozycji („atomowe") reprezentowane jest przez obiekt klasy Acti onltem.

6.3. Koncepcje projektowania systemu

273

Wariant 1: bezpośredni dostęp do podsystemu Database

Wariant 2: podsystem Storage pośredniczy w dostępie do podsystemu Database

Rysunek 6.6. Przykład redukowania sprzężenia w zbiorze podsystemów (dla przejrzystości pominęliśmy podsystem N o t i f i c a t i o n ) . W wariancie 1. wszystkie podsystemy korzystające z usług podsystemu Database czynią to bezpośrednio, co uwrażliwia je na zmiany w zakresie interfejsu tegoż podsystemu. W wariancie 2. podsystem S t o r a g e pełni rolę bariery ochronnej oddzielającej wspomniane systemy klienckie od interfejsu p o d s y s t e m u Database. U podstaw takiego rozwiązania legło założenie, że podsystem S t o r a g e cechować się będzie interfejsem daleko stabilniejszym niż podsystem Database

Reprezentowany na rysunku 6.7 system śledzenia decyzji jest na tyle mały, że nie było niczym nadzwyczajnym zamknięcie wszystkich jego klas w formie pojedynczego podsystemu DecisionSubsystem. Bliższe przyjrzenie się strukturze powiązań między wspomnianymi klasami skłania jednak do wniosku, iż graf tych powiązań da się naturalnie podzielić na dwa podgrafy. Jeden z nich, odpowiadający podsystemowi RationaleSubsystem, wiąże klasy DesignProblem, Option, Criterion i Decision; drugi, reprezentujący podsystem Planning '-•Subsystem, zawiera klasy Task, SubTask i Actionltem (patrz rysunek 6.8).

274

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

DecisionSubsystem

Rysunek 6.7. Przykład podsystemu o niskiej spoistości: klasy C r i t e r i o n , Option i DesignProblem nie mają powiązań z klasami S u b T a s k , Acti onltem i T a s k

Rysunek 6.8. Dekompozycja podsystemu z rysunku 6.7 na dwa odrębne podsystemy. Spoistość wynikowych podsystemów Rati onal eSubsystem i PI anni ngSubsystem jest znacznie większa niż spoistość pierwotnego podsystemu Deci si onSubsystem. Oba podsystemy są ponadto prostsze, jednak konieczne jest zdefiniowanie interfejsu między nimi, w celu zrealizowania powiązań między klasami Task i Deci si on

6.3. Koncepcje projektowania systemu

275

Każdy z podsystemów Rati onal eSubsystem i PlanningSubsystem cechuje się znacznie większą spoistością niż pierwotny podsystem DecisionSubsystem. Umożliwia to niezależne wykorzystywanie każdego z nich przez inne podsystemy; co więcej, każdy nich może być budowany niezależnie przez innego programistę. Są one ze sobą — co prawda — sprzężone, lecz w minimalnym stopniu, poprzez jedno skojarzenie. Maksymalna spoistość i minimalne sprzężenie generalnie okazują się celami przeciwstawnymi. Tworzenie nowych, coraz mniejszych i bardziej spoistych podsystemów powoduje zwykle generowanie nowych powiązań między nimi, a więc przyczynia się do zwiększenia sprzężenia. Jedną ze znanych heurystyk pomagających rozwiązywać opisany konflikt interesów jest heurystyka „7 ± 2" zakładająca średnią liczbę siedmiu koncepcji na danym szczeblu abstrakcji: większa niż dziewięć liczba podsystemów na tym szczeblu bądź większa niż dziewięć liczba usług świadczonych przez pojedynczy podsystem jest wskazówką w stronę zrewidowania dokonanej dekompozycji. Zgodnie z tą samą heurystyką, liczba warstw funkcjonalnych systemu nie powinna przekraczać dziewięciu; w praktyce dobre projekty systemu ograniczają się do nie więcej niż trzech warstw.

6.3.4. Warstwy i partycje Efektem dekompozycji hierarchicznej jest uporządkowany zbiór warstw. Warstwa stanowi zgrupowanie podsystemów oferujących powiązane usługi i prawdopodobnie korzystających z usług oferowanych przez inne warstwy. Warstwy uporządkowane są w ten sposób, że każda z nich zależna jest co najwyżej od warstw niższych i nie ma żadnej wiedzy na temat tego, co dzieje się w wyższych warstwach. Najniższa w hierarchii warstwa, z konieczności niezależna od innych, nosi nazwę warstwy dolnej, analogicznie warstwa najwyższa w hierarchii zwana jest warstwą górną lub szczytową (patrz rysunek 6.9)

Rysunek 6.9. Dekompozycja systemu na trzy warstwy (przedstawione jako pakiety UML). Podzbiór podsystemów, zawierający co najmniej jeden podsystem z każdej warstw)', nazywamy pionowym wycinkiem; takim wycinkiem jest podzbiór {A, B, E}, nie jest nim natomiast podzbiór {D, G}

Charakter zależności między warstwami stanowi kryterium podziału architektury dekompozycyjnej na dwa rodzaje. Jeżeli dana warstwa korzysta z usług wyłącznie warstwy bezpośrednio niższej, mamy do czynienia z architekturą zamkniętą; w ramach architektury otwartej2 każda z warstw może w dowolny sposób wykorzystywać usługi oferowane przez wszystkie niższe warstwy. 2

W społeczności programistów termin „architektura otwarta" używany jest także w zupełnie innym znaczeniu — na określenie architektury sprzętowej lub programowej, niewykazującej cech uzależnienia od technologii konkretnego dostawcy. W tym miejscu używam tego terminu w znaczeniu identycznym ze znaczeniem w książce J. Rumbaugha, M. Błahy, W. Premerlaniego, F. Eddy'ego i W. Lorensena [Rumbaugh i in., 1991],

276

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Klasycznym przykładem architektury zamkniętej jest znany model odniesienia OSI-ISO, obrazujący koncepcję komunikacji sieciowej między dwiema aplikacjami (patrz rysunek 6.10) [Day i Zimmermann, 1983]. Każda warstwa odpowiedzialna jest za świadczenie ściśle określonych funkcji, każda też (oczywiście, z wyjątkiem najniższej) wykorzystuje usługi warstwy położonej bezpośrednio poniżej. I tak, idąc od dołu, warstwa f i zyczna reprezentuje sprzętowy interfejs sieci, odpowiedzialny za transmitowanie pojedynczych bitów przez kanał komunikacyjny. Położona bezpośrednio wyżej warstwa łącza danych odpowiedzialna jest za bezbłędne transmitowanie całych ramek; funkcja transmitowania ramki realizowana jest jako złożenie prostszych funkcji transmitowania pojedynczych bitów, co „załatwia" nam warstwa fizyczna. Z kolei funkcja niezawodnego transmitowania ramek stanowi budulec dla zadania bardziej zaawansowanego — transmitowania i trasowania pakietów sieciowych, za które to zadanie odpowiedzialna jest warstwa sieciowa. Zadaniem warstwy transportowej jest niezawodne transmitowanie danych między dwoma połączonymi punktami; programowanie przesyłania informacji między gniazdami TCP/IP opiera się na usługach tej właśnie warstwy. Zadaniem warstwy s e s j i jest nawiązywanie i uwierzytelnianie połączeń. Warstwa prezentacji odpowiedzialna jest za niezbędne konwersje przesyłanych danych, polegając między innymi na szyfrowaniu i odwracaniu kolejności bajtów 3 . Najwyższa warstwa — warstwa apl i kacji — reprezentuje kompletną aplikację, czyli tworzony system (z wyjątkiem przypadków, gdy system ten jest na przykład systemem operacyjnym lub implementacją stosu protokołów). Warstwa aplikacji ma swą wewnętrzną strukturę — realizowana jest przez podsystemy zorganizowane w architekturę warstwową. Co ciekawe, jeszcze do niedawna tylko cztery najniższe warstwy modelu OSI-ISO były dobrze zestandaryzowane. System UNIX, podobnie jak większość „desktopowych" systemów operacyjnych, dostarcza dla protokołu TCP/IP interfejs obejmujący warstwy transportową, sieciową i łącza danych; implementacja warstw sesji i prezentacji spoczywa całkowicie na barkach programistów tworzących aplikację. W miarę wzrostu popularności aplikacji rozproszonych ten stan rzeczy stał się przesłanką do powstania kilku znaczących rozwiązań z kategorii middleware, takich jak CORBA [OMG, 2008] czy Java RMI [RMI, 2009] — obie te technologie znakomicie ułatwiają pracę programistom, pozwalając na wysyłanie komunikatów do zdalnych obiektów w sposób „przezroczysty", czyli tak samo jak wysyła się komunikaty do lokalnych obiektów. Z perspektywy modelu OSI-ISO oznacza to efektywną implementację warstwy sesji i prezentacji (patrz rysunek 6.11). Przykładem architektury otwartej jest znana biblioteka graficzna Swi ng języka Java [JFC, 2009], a raczej jej umiejscowienie w ogólnym schemacie aplikacji (patrz rysunek 6.12). Najniższa warstwa tej architektury realizowana jest bądź to przez system operacyjny, bądź przez podsystem okienkowy w rodzaju Xli, a jej zadaniem jest podstawowe zarządzanie oknami interfejsu użytkownika. Aplikacja w języku Java oddzielona jest od konkretnego systemu okienkowego barierą, jaką stanowi warstwa AWT (Abstract Window Toolkit). Kolejna warstwa — biblioteka Swi ng — dostarcza bogaty zestaw obiektów tworzących interfejs użytkownika i realizujących szeroki wachlarz funkcjonalności, od prostych przycisków do zarządzania geometrią interfejsu. Aplikacja kliencka może — oczywiście — korzystać z tego bogactwa, równie dobrze może jednak ignorować bibliotekę Swi ng i odwoływać się bezpośrednio do warstwy AWT. 3

Odwracanie kolejności bajtów w strukturach wielobajtowych ma na celu zniwelowanie różnic między architekturą little endian a architekturą bigendian (patrz http://pl.wikipedia.org/wiki/Kolejność_bajtów) — przyp. tłum.

6.3. Koncepcje projektowania systemu

277

Rysunek 6.10. Przykład zamkniętej architektury warstwowej — sieciowy model odniesienia OSI-ISO

Rysunek 6.11. Odwzorowanie warstw modelu OSI-ISO w rzeczywiste komponenty sprzętowe i programowe. Mechanizm CORBA, stanowiący efektywną implementację warstw sesji i prezentacji, pozwala na komunikowanie się obiektów rezydujących na różnych komputerach, zaimplementowanych być może z użyciem odmiennych języków programowania

278

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Rysunek 6.12. Przykład otwartej architektury warstwowej: aplikacja wykonywana w środowisku zawierającym bibliotekę Swing. X l i to podsystem odpowiedzialny za niskopoziomowe operacje graficzne; AWT jest abstrakcją reprezentującą mechanizmy okienkowe i jednocześnie izolującą warstwy wyższe od konkretnej implementacji tych mechanizmów. Biblioteka Swing udostępnia aplikacji bogaty repertuar wykoncypowanych obiektów typowych dla interfejsu użytkownika, mimo to, niektóre aplikacje pomijają tę biblioteką, odwołując się bezpośrednio do usług oferowanych przez warstwę A W T

Podstawową przyczyną, dla której niektóre warstwy architektury otwartej sięgają niżej niż tyłko do warstwy sąsiedniej, jest niwelowanie wąskich gardeł wydajnościowych. Architektura zamknięta okazuje się nader korzystna, gdy spojrzeć na nią z perspektywy programistycznej: ponieważ podsystemy należące do różnych warstw są ze sobą bardzo luźno powiązane (lub niepowiązane w ogóle), czyli ponieważ minimalny jest stopień sprzężenia podsystemów, mogą być one integrowane i testowane w sposób przyrostowy. Ta wygoda ma jednak swoją cenę: każdy nowy poziom (warstwa) wnosi do ogólnej architektury zarówno pewne spowolnienie wykonywania systemu, jak i dodatkowe zapotrzebowanie na pamięć. Zsumowanie się tych narzutów może spowodować, że niemożliwe stanie się spełnienie wymagań pozafunkcyjnych. Ponadto dodawanie nowych elementów funkcjonalnych — zwłaszcza nieprzewidzianych zawczasu — może okazać się trudne w przypadku „głębokiej' architektury. W praktyce, typowy system składa się z najwyżej trzech — wyjątkowo czterech lub pięciu — warstw. Odmiennym sposobem dekompozycji systemu jest jego partycjonowanie na równorzędne podsystemy, z których każdy odpowiedzialny jest za inną klasę usług. Przykładowo system pokładowy samochodu może być zdekomponowany na podsystemy zarządzające (odpowiednio) nawigacją i trasą (w postaci informowania kierowcy na bieżąco o lokalizacji i wymaganym kierunku jazdy), personalizacją środowiska (pozycją fotela, ulubioną stacją radiową), informacją o zużyciu paliwa oraz harmonogramem przeglądów i napraw. Każdy z tych podsystemów jest luźno powiązany z pozostałymi, lecz równie dobrze może funkcjonować w oderwaniu od nich. Dekompozycja przeprowadzana w praktyce ma zwykle charakter pośredni między opisanymi skrajnościami. Dekomponowanie systemu rozpoczyna się najczęściej od jego partycjonowania na wysokopoziomowe podsystemy, z których każdy odpowiedzialny jest za specyficzny aspekt funkcjonalności bądź też przeznaczony do pracy na konkretnym węźle sprzętowym. Każdy z tych podsystemów może być (o ile uzasadnia to jego złożoność) sukcesywnie dekomponowany w sposób hierarchiczny, aż poszczególne warstwy staną się na tyle proste, że możliwe

6.3. Koncepcje projektowania systemu

279

do zrealizowania przez pojedynczych programistów. Nie zapominajmy jednak o (wspomnianych przed chwilą) narzutach wynikających z przesadnego partycjonowania i o wynikającym stąd ogólnym wzroście złożoności systemu.

6.3.5. Style architektoniczne Znaczenie specyfikacji dekompozycyjnej staje się tym bardziej krytyczne, im bardziej skomplikowany jest przedmiotowy system. Nietrafną kompozycję koryguje się bardzo trudno, gdy już rozpocznie się projektowanie i implementowanie systemu, bowiem większość opracowywanych właśnie podsystemów zmieni prawdopodobnie swe oblicze (interfejs) i charakter. Na kanwie niezwykłej istotności tego problemu zrodziła się koncepcja architektury oprogramowania — pod tym pojęciem rozumiemy dekompozycję systemu, globalną kontrolę przepływu sterowania, zarządzanie sytuacjami granicznymi oraz protokoły komunikacji między podsystemami [Shaw i Garlan, 1996]. W tej sekcji opiszemy kilka stylów architektonicznych, które można wykorzystywać jako podstawę do projektowania architektury konkretnych systemów. Ów opis ma charakter wybiórczy i nie pretenduje do miana systematycznego czy wyczerpującego: chcieliśmy raczej przedstawić czytelnikom kilka reprezentatywnych przykładów, których uzupełnieniem może być zalecana literatura.

Repozytorium W architekturze repozytoryjnej (patrz rysunek 6.13) podsystemy operują na pojedynczej strukturze danych zwanej repozytorium. Repozytorium stanowi jedyne medium komunikacji między podsystemami, które poza tym są od siebie niezależne. Przepływ sterowania może być determinowany bądź przez wspomniane repozytorium (na przykład w formie wywołań poszczególnych podsystemów, wyzwalanych zmianami w danych), bądź też przez same podsystemy (których nienależne poczynania synchronizowane są za pomocą blokad nakładanych na poszczególne elementy repozytorium).

Rysunek 6.13. Przykład architektury repozytoryjnej. Każdy podsystem zależny jest jedynie od centralnego repozytorium, które nie posiada jednak żadnej wiedzy na temat poszczególnych podsystemów

Architektura repozytoryjna używana jest zwykle w systemach skoncentrowanych na zarządzaniu bazami danych — systemach bankowości, systemach ewidencji pracowników i tym podobnych. Centralny charakter danych ułatwia radzenie sobie z problemami wynikającymi ze współbieżności i konieczności zachowania integralności danych między podsystemami.

280

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Innym obszarem popularności architektury repozytoryjnej są współczesne kompilatory wbudowane w zintegrowane środowiska programistyczne (patrz rysunek 6.14): poszczególne podsystemy — debugger, kompletowanie składni, wizualne rozróżnianie elementów składniowych — korzystają z drzewa rozbioru syntaktycznego i tablicy symboli, produkowanych przez kompilator.

Rysunek 6.14. Przykład architektury repozytoryjnej — zintegrowane środowisko programistyczne. Kompilator (Compiler), złożony z analizatora składni ( S y n t a c t i c A n a l y z e r ) , analizatora semantyki (SemanticAnalyzer), analizatora leksykalnego (LexicalAnalyzer), optymalizatora (Optimizer) i generatora kodu wynikowego (CodeGenerator) produkuje drzewo rozbioru syntaktycznego (ParseTree) i tablicę symboli (SymbolTable) zapamiętywane w centralnym repozytorium (Repository). Z repozytorium tego korzysta zarówno zintegrowany debugger (SourceLevel Debugger), jak i edytor programistyczny, świadom elementów składniowych edytowanego tekstu ( S y n t a c t i cEdi t o r )

W przykładzie z rysunku 6.14 poszczególne narzędzia (kompilator, debugger, edytor) wywoływane są przez programistę, rola repozytorium ogranicza się jedynie do synchronizowania dostępu do wspólnych danych. Repozytorium może jednak pełnić także rolę czynną, wywołując poszczególne operacje podsystemów wskutek zmian dokonywanych w centralnej strukturze danych. Takie systemy nazywane są potocznie „systemami tablicowymi"4 (blackboard systems). Jednym z pierwszych systemów tej kategorii był system rozpoznawania mowy HEARSAY II, opisany w pracy L. D. Ermana, F. Hayesa-Rotha i innych [Erman i in., 1980]. Architektura repozytoryjna idealnie nadaje się dla aplikacji dokonujących intensywnego przetwarzania dynamicznie zmieniających się danych. Gdy tylko zdefiniowana zostanie właściwie struktura i działanie centralnego repozytorium, można łatwo definiować nowe usługi pod postacią nowych podsystemów. Podstawowa wada tej architektury wynika z faktu, że 4

Patrz na przykład http://pl.wikipedia.org/wiki/Architektura_tablicowa

— przyp.

tłum.

6.3. Koncepcje projektowania systemu

281

centralne repozytorium rychło stać się'może wąskim gardłem i to zarówno pod względem wydajności, jak i modyfikowalności — poszczególne podsystemy są przecież z tym repozytorium wysoce sprzężone, zatem wszelkie zmiany dokonywane w jego obrębie przekładają się na konieczność być może daleko posuniętych zmian w samych podsystemach.

Model-widok-kontroler

(MVC)

Istotą architektury model-widok-kontroler (w skrócie MVC, od Model-View-Controller), przedstawionej schematycznie na rysunku 6.15, jest podział podsystemów na trzy grupy, podsystemy modelu reprezentują wiedzę z dziedziny aplikacyjnej, podsystemy widoku odpowiedzialne są za prezentowanie tej informacji użytkownikowi, zaś zadaniem podsystemów kontrolera jest zarządzanie sekwencją interakcji z użytkownikiem. Podsystemy modelu nie są przy tym w żaden sposób uzależnione od podsystemów dwóch pozostałych grup, zmiany w obrębie modelu propagowane są do podsystemów widoku za pomocą mechanizmów powiadamiania i subskrypcji. Architektura MVC może być uważana za szczególny przypadek architektury repozytoryjnej, model jest tu bowiem odpowiednikiem centralnego repozytorium implementującego centralne struktury danych, zaś obiekty kontrolera determinują przepływ sterowania.

Rysunek 6.15. Architektura model-widok-kontroler. Kontroler odpowiedzialny jest za interakcje z użytkownikiem i wysyłanie komunikatów do modelu. Model utrzymuje centralną strukturę danych. Widok realizuje wyświetlanie informacji utrzymywanej przez model — aktualność wyświetlanej informacji zapewniania jest przez mechanizmy powiadamiania, realizowane przez model w drodze subskrypcji

Na rysunku 6.16 widoczny jest efekt zewnętrzny funkcjonowania architektury MVC. Okno „w tle" prezentuje listę plików zawartych w folderze Comp-Based Software Engineering — widzimy tam między innymi plik 9DesignPatterns2.ppt. W oknie pierwszoplanowym widzimy natomiast szczegółowe informacje na temat tego pliku. Nazwa 9DesignPatterns2.ppt pojawia się w trzech miejscach: w „spisie treści" folderu w oknie drugoplanowym oraz na pasku tytułowym i w nagłówku okna pierwszoplanowego. Zobaczmy teraz, co wydarzy się (powinno się wydarzyć?) w tym systemie, gdy zmienimy nazwę rzeczonego pliku — diagram na rysunku 6.17 przedstawia sekwencję związanych z tym zdarzeń: 1. Obiekty InfoView i FolderView subskrybują powiadamianie o zmianach zachodzących w Model u5. 2. Użytkownik wprowadza nową nazwę pliku.

3

Ten krok wykonywany jest — oczywiście — jednorazowo, prawdopodobnie w fazie inicjacji systemu — przyp.

tłum.

282

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Rysunek 6.16. Przykład zastosowania architektury MVC. „Modelem" jest tu nazwa pliku 9DesignPAtterns2.ppt, powiązana z dwoma „widokami": oknem zatytułowanym CBSE, wyświedającym zawartość folderu, i oknem pierwszoplanowym, wyświetlającym właściwości pliku. Gdy zmieni się nazwa pliku, oba widoki zostaną uaktualnione na skutek interwencji „kontrolera"

Rysunek 6.17. Diagram komunikacyjny ukazujący przepływ informacji w związku ze zmianą nazwy pliku w systemie pokazanym na rysunku 6.16

3. Obiekt Kontroler, odbierając od użytkownika wprowadzoną nazwę, przekazuje do Model u żądanie zrealizowania zmiany nazwy pliku. 4. Obiekty modelu dokonują zmiany nazwy pliku i powiadamiają o tym swych subskrybentów — obiekty InfoView i FolderView. 5. Obiekty InfoView i FolderView uaktualniają wyświetianą informację, użytkownik widzi więc aktualny stan rzeczy.

6.3. Koncepcje projektowania systemu

283

Występujące w sekwencji na rysunku 6.17 mechanizmy subskrypcji i powiadamiania realizowane są zwykle w oparciu o wzorzec projektowy Obserwator (patrz sekcja A.7), co przyczynia się do ogólnego obniżenia stopnia sprzężenia podsystemów, brakuje bowiem jakichkolwiek bezpośrednich powiązań modelu z widokami. Czytelnikom zainteresowanych tematyką wzorców projektowych, a szczególnie wzorca Obserwator, możemy polecić książkę E. Gammy, R. Heima, R. Johnsona i J. Vlissidesa [Gamma i in., 1994]6. Jedną z istotnych przesłanek uzasadniających rozseparowanie obiektów modelu, widoku i kontrolera jest fakt, iż interfejs użytkownika (realizowany przez obiekty widoku i kontrolera) jest z natury bardziej podatny na zmiany niż (reprezentowana przez model) dziedzina aplikacyjna. Ponadto izolując model od widoku, zapewniamy sobie swobodę manipulowania podsystemami widoku bez jakiegokolwiek wpływu na podsystemy modelu — i tak na przykład możemy w systemie operacyjnym uruchamiać rozmaite przeglądarki ukazujące żądane szczegóły systemu plików, bez konieczności jakiejkolwiek ingerencji w ów system plików7. Czytelnicy przypominają sobie zapewne analogiczną separację obiektów encji, brzegowych i sterujących, którą opisywaliśmy w rozdziale 5. „Analiza wymagań" — jest ona motywowana dokładnie tymi samymi przesłankami. Architektura MVC jest znakomicie przystosowana do systemów interaktywnych, szczególnie takich, w których informacje zawarte w pojedynczym modelu wyświetlane są w wielu widokach. Pomaga ona w zapewnieniu spójności rozproszonych danych, dotknięta jest jednak tym samym syndromem „wąskiego gardła", co architektura repozytoryjna.

Klient-server W ramach tej architektury (patrz rysunek 6.18) podsystem zwany serwerem jest dostawcą usług dla (jednej lub wielu) instancji innych podsystemów, zwanych klientami i odpowiedzialnych za interakcję z użytkownikiem (użytkownikami). Żądanie usługi odbywa się zwykle za pośrednictwem zdalnego wywoływania procedur (RPC — Remote Procedure Calif bądź za pośrednictwem obiektów brokerów (jak w przypadku technologii CORBA, Java RMI czy protokołu HTTP). Podsystemy klienckie funkcjonują niezależnie od podsystemu serwera, z wyjątkiem synchronizacji niezbędnej do zarządzania żądaniami klientów i odpowiedziami serwera. Przykładem zastosowania architektury klient-serwer jest system informacyjny oparty na centralnej bazie danych. Każdy podsystem klienta odpowiedzialny jest za obsługę dialogu z użytkownikiem, walidację wprowadzanych przez niego danych i inicjowanie transakcji bazodanowych, gdy wynik tej walidacji okaże się pozytywny. Serwer jest natomiast odpowiedzialny za wykonywanie żądanych transakcji i gwarantowanie integralności danych. W tym kontekście 6

A także książkę Head First Design Patterns. Edycja polska (Rusz głową!), wyd. Helion 2005, (http://helion.pl/ksiazki/head_Jirst_design _patterns_edycja _polska_rusz_glowa_eric_jreeman_elisabeth_ freeman_kathy_sierra_bert_bates,hfdepa.htm) —przyp. tłum.

1

Mechanizmy powiadamiania o zmianach zachodzących w systemie plików udostępniane są przez systemy operacyjne na zasadzie subskrypcji za pomocą odpowiednich funkcji API, przykładowo w systemie Windows temu celowi służą między innymi funkcje Win32 API Fi ndFi rstChangeNoti f i c a t i on i FindNextChangeNotifi c a t i o n — p r z y p . tłum.

8

Lub zaawansowanych mechanizmów zbudowanych na bazie RPC, takich jak COM, D C O M czy C O M + firmy Microsoft — przyp. tłum.

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

284

Rysunek 6.18. Architektura klient-serwer. Klienci żądają usług od jednego lub większej liczby serwerów. Serwery nie dysponują żadną informacją na temat klientów. Architektura klient-serwer może być uważana za specjalizację architektury repozytoryjnej

serwer może być uważany za szczególny przypadek repozytorium (realizowanego jako proces zarządzający centralnymi danymi), zaś architektura klient-serwer — za szczególny przypadek (specjalizację) architektury repozytoryjnej. Systemy klient-serwer mogą jednak wykorzystywać wiele serwerów — czego ewidentnym przykładem jest choćby sieć WWW, w której każdy klient może z łatwością pobierać dane z wielu tysięcy serwerów (patrz rysunek 6.19).

Rysunek 6.19. Sieć W W W jako instancja architektury klient-serwer

Architektura klient-serwer jest doskonale przystosowana do systemów rozproszonych zarządzających olbrzymimi zasobami danych. Peer-to-peer Styl architektoniczny peer-to-peer 9 (patrz rysunek 6.20), w skrócie P2P, może być uważany za uogólnienie (generalizację) architektury klient-serwer, bowiem każdy z podsystemów spełniać może obie funkcje: klienta i serwera — w tym sensie, że może zarówno żądać usług, jak i sam je udostępniać. Funkcjonowanie poszczególnych podsystemów jest niezależne, z wyjątkiem synchronizacji koniecznej dla obsługi żądań. Przykładem architektury P2P jest system wykorzystujący bazę danych, która zarówno realizować może żądania związane z odczytem, wyszukiwaniem i modyfikowaniem danych, jak i powiadamiać aplikację o dokonywaniu zmian w pewnych obszarach danych (patrz rysunek 6.21). Systemy P2P są trudniejsze do projektowania niż systemy utrzymane w architekturze klient-serwer, bo cechują się bardziej skomplikowanym przepływem sterowania i stwarzają zagrożenie wystąpienia niepożądanych zjawisk w rodzaju zastoju (deadlock). ' Od ang. „równy z równym" — przyp.

tłum.

285

6.3. Koncepcje projektowania systemu

Peer

konsument *

*

usługalO usługa2() usługaNO

Rysunek 6.20. Architektura peer-to-peer

dostawca

(P2P). Każdy podsystem może spełniać funkcje zarówno

klienta, jak i serwera

Rysunek 6.21. Przykład architektury P2P. Serwer zarządzania bazą danych (DBMS — Database Management System) może zarówno realizować żądania otrzymywane od klienta, jak i powiadamiać go o zaistniałych zdarzeniach

Z systemami P2P wiąże się mechanizm odwołania zwrotnego (callback). Klient może mianowicie wskazać serwerowi jedną ze swych operacji i zażądać, by serwer wywołał tę operację w przypadku wystąpienia określonego zdarzenia. W przykładzie pokazanym na rysunku 6.21 klient DBUser wykorzystuje tę możliwość w stosunku do serwera bazy danych DBMS. Systemy P2P, w których „serwery" odwołują się do „klientów" jedynie za pośrednictwem odwołań zwrotnych, utożsamiane są z systemami klient-serwer — co jest o tyle nieadekwatne, że w tym przypadku połączenie między klientem a serwerem może być nawiązywane także z inicjatywy serwera. Architektura

trójwarstwowa

Architektura trójwarstwowa jest wynikiem organizacji systemu w postaci trzech warstw (patrz rysunek 6.22): •

interfejsu — warstwa ta grupuje wszystkie obiekty brzegowe uczestniczące w interakcji z użytkownikiem: okna, formularze, strony W W W i tym podobne,



logiki aplikacji — w tej warstwie zlokalizowane są wszystkie obiekty encji i obiekty sterujące, realizujące przetwarzanie danych, wymuszanie reguł tego przetwarzania i powiadamianie aplikacji o wybranych zdarzeniach,



magazynowania danych — ta warstwa odpowiedzialna jest za przechowywanie, wyszukiwanie i modyfikowanie obiektów trwałych.

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

286

Rysunek 6.22. Architektura trójwarstwowa: obiekty zorganizowane są w trzy warstwy realizujące interfejs, przetwarzanie danych i ich magazynowanie

Architektura trójwarstwowa wynaleziona została w latach 70. ubiegłego wieku na potrzeby systemów informacyjnych. Warstwa magazynowania danych, jako analogia repozytorium, może być współdzielona między wiele aplikacji operujących na tych samych danych. Z kolei odseparowanie warstwy interfejsu od warstwy logiki biznesowej umożliwia wykorzystywanie różnych interfejsów w oparciu o tę samą logikę aplikacji. Architektura

czterowarstwowa

Architektura czterowarstwowa jest wynikiem modyfikacji architektury trójwarstwowej polegającej na podziale warstwy interfejsu na dwie nowe: serwer prezentacji i klienta prezentacji (patrz rysunek 6.23). Warstwa klienta prezentacji realizowana jest na maszynach klienckich, podczas gdy warstwa serwera prezentacji zlokalizowana jest na jednym lub kilku serwerach. Przez podział warstwy interfejsu na dwie warstwy prezentacyjne stwarza się możliwość zastosowania różnych prezentacji interfejsu wykorzystujących te same obiekty. Przykładowo system bankowości używany jest przez wielu klientów, posługujących się różnymi interfejsami: przeglądarką WWW, jaką wykorzystuje klient realizujący przelew z domowego komputera, pulpitem bankomatu czy dedykowaną aplikacją, za pomocą której uprawnieni pracownicy banku zarządzają opisywanym systemem. Współdzielone przez każdy z tych interfejsów obiekty (na przykład formularze) mogą być zlokalizowane we wspólnej warstwie serwera prezentacji, co przyczynia się do zmniejszenia redundancji systemu.

Filtry i potoki W opisywanej architekturze (patrz rysunek 6.24) każdy z podsystemów realizuje przetwarzanie danych otrzymanych od innych podsystemów na swym wejściu i wysyła wyjście wyniki tego przetwarzania przeznaczone dla innych podsystemów. Każdy z podsystemów nazywany jest w tej strukturze filtrem, zaś połączenia między podsystemami to potoki. Dla każdego z filtrów jedynym źródłem informacji są dane obecne w jego potoku wejściowym — nie wie on nic na temat podsystemów produkujących te dane. Poszczególne filtry działają niezależnie, z wyjątkiem synchronizacji związanej z przekazywaniem danych przez potoki.

6.3. Koncepcje projektowania systemu

287

Rysunek 6.23. Architektura czterowarstwowa: warstwa interfejsu z architektury trójwarstwowej podzielona została na dwie, co stworzyło większe możliwości różnicowania stylów interfejsu użytkownika

wyjście

Rysunek 6.24. Architektura filtr-potok. Każdy filtr może posiadać wiele wejść i wyjść, każdy potok natomiast łączy dokładnie dwa filtry

Architektura filtr-potok jest bardzo elastyczna — poszczególne filtry można łatwo wymieniać i modyfikować, stosownie do bieżących potrzeb. Najbardziej spektakularnym przykładem obecności tej architektury jest powłoka systemu UNIX opisana przez D. M. Ritchie i K. Thompsona [Ritchie i Thompson, 1974]l0. Większość filtrów tworzona jest w ten sposób, że dane pobierane są ze standardowych potoków wejściowych i wysyłane na standardowe potoki wyjściowe, co umożliwia łatwe organizowanie zaawansowanego przetwarzania. Na rysunku 6.25 widzimy uniksową sekwencję filtrów prowadzącą do czytelnego wyświetlenia listy procesów należących do użytkownika d u t o i t . Najpierw za pomocą polecenia ps uzyskujemy listę wszystkich procesów; program grep eliminuje z tej listy pozycje związane z innymi użytkownikami, program s o r t sortuje wynik tej eliminacji w żądanej kolejności, zaś program more organizuje wyświetlanie posortowanej listy w porcjach dostosowanych do wysokości ekranu terminala. Cechą charakterystyczną (i podstawową zaletą) architektury filtr-potok jest jej automatyzm — skomplikowane przetwarzanie informacji może odbywać się bez ingerencji użytkownika. Architektura ta nie nadaje się jednak do zastosowań wymagających bardziej zaawansowanych interakcji między komponentami — systemów informacyjnych czy systemów interaktywnych. 10

Analogiczny mechanizm istnieje także w systemie Windows. Jego pierwowzór istniał już nawet w systemie MS DOS, z naturalnym dla tego systemu ograniczeniem — poszczególne filtry uruchamiane były sekwencyjnie. W danej chwili aktywny był więc dokładnie jeden filtr, wykorzystujący dwa potoki — standardowe wejście i standardowe wyjście, oba zrealizowane jako pliki tymczasowe tworzone i usuwane przez system — przyp. tłum.

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

288

X ps auxwww | grep dutoit | sort | more dutoit dutoit dutoit

19737 19858 19859

0.2 0.2 0.2

1.6 1908 1500 pts/6 0.7 816 580 pts/6 0.6 812 540 pts/6

0 15:24:36 S 15:38:46 0 15:38:47

0:00 -tcsh 0:00 grep dutoit 0:00 sort

Rysunek 6.25. Sekwencja poleceń uniksowych zrealizowana jako kombinacja filtrów i potoków

6.4. Aktywności projektowania systemu: od obiektów do podsystemów Projektowanie systemu to transformowanie modelu analitycznego w model projektu systemu, z uwzględnieniem wymagań pozafunkcyjnych opisanych w dokumencie RAD. Zilustrujemy związane z tym aktywności na przykładzie systemu MyTri p, służącego kierowcom samochodów do planowania podróży. Rozpoczniemy od modelu analitycznego (patrz sekcja 6.4.1), na podstawie którego określimy cele projektowe (patrz sekcja 6.4.2), po czym przystąpimy do wstępnej dekompozycji systemu (patrz sekcja 6.4.3).

6.4.1. Punkt wyjścia: model analityczny systemu planowania podróży Wykorzystując system MyTri p, kierowca może zaplanować trasę podróży za pomocą domowego komputera, połączonego z odpowiednią usługą internetową (w tabeli 6.2 nazwaliśmy ją Plan ^ T r i p). Wynik planowania przechowywany jest na serwerze do późniejszego wykorzystywania. System MyTri p musi być zdolny do obsługi wielu klientów równocześnie. Tabela 6.2. Przypadek użycia PI anTri p systemu MyTri p Nazwa przypadku Przepływ zdarzeń

użycia

PI anTri p 1. Kierowca włącza domowy komputer i loguje się do internetowej usługi planowania podróży. 2. Kierowca wprowadza ograniczenia w postaci docelowego miejsca podróży oraz miejsc, które chce odwiedzić po drodze. 3. Bazując na dostępnych mapach, usługa planowania dokonuje ustalenia najkrótszej drogi spełniającej podane ograniczenia, czyli zawierającej wszystkie wymienione lokalizacje w ustalonej kolejności. 4. Kierowca może zmodyfikować z a p r o p o n o w a n ą trasę, d o d a j ą c do niej lub usuwając z niej żądane lokalizacje. 5. Kierowca zapamiętuje w bazie danych usługi zaplanowaną trasę w celu późniejszego jej wykorzystania.

W dogodnym dla siebie czasie kierowca wsiada do samochodu i rozpoczyna podróż, podczas której komputer pokładowy wskazuje mu kierunek jazdy, bazując na zaplanowanej trasie, pobranej z bazy danych usługi oraz na bieżącym położeniu wskazywanym przez pokładowy system GPS (przypadek użycia ExecuteTri p — patrz tabela 6.3).

6.4. Aktywności projektowania systemu: od obiektów do podsystemów

289

Tabela 6.3. Przypadek użycia ExecuteTri p systemu MyTri p Nazwa przypadku Przepływ

zdarzeń

użycia

ExecuteTri p 1. Kierowca uruchamia swój samochód i loguje się do pokładowego asystenta kierowcy. 2. Po pomyślnym zalogowaniu kierowca wybiera żądaną usługę i nazwę zapamiętanej trasy. 3. Pokładowy asystent kierowcy wczytuje listę lokalizacji, kierunków, segmentów i skrzyżowań znajdujących się na wspomnianej trasie. 4. Bazując na bieżącej lokalizacji, pokładowy asystent wskazuje kierowcy ciąg kolejnych kierunków jazdy. 5. Kierowca przybywa do celu podróży i wyłącza pokładowego asystenta.

Dokonując analizy systemu MyTrip za pomocą technik opisanych w rozdziale 5. „Analiza wymagań", otrzymujemy model analityczny widoczny na rysunku 6.26. Znaczenie jego poszczególnych klas opisane jest w tabeli 6.4.

Rysunek 6.26. Model analityczny systemu planowania i realizowania podróży MyTri p Tabela 6.4. Znaczenie klas modelu analitycznego systemu MyTri p, przedstawionych na rysunku 6.26

Klasa

Reprezentowana encja

Crossing

Skrzyżowanie, punkt geograficzny, w którym spotyka się kilka segmentów (Segment).

Destination

Cel podróży, lokalizacja końca podróży.

Direction

Dla danego skrzyżowania (Crossi ng) i przylegającego doń segmentu (Segment) stanowi opis słowny sposobu skierowania samochodu w ten właśnie segment.

Location

Lokalizacja samochodu określona na podstawie wskazań pokładowego systemu i odległości (mierzonej liczbą obrotów kół) od punktu wzorcowego.

PlanningService

Planista, serwer W W W , ustalający trasę podróży przez konwersję ciągu lokalizacji do postaci ciągu skrzyżowań (Crossi ng) i segmentów (Segment).

RouteAssi stant

Asystent kierowcy, urządzenie wskazujące kierowcy właściwy kierunek jazdy na podstawie bieżącej lokalizacji (Location) i następnego skrzyżowania (Crossi ng).

Segment

Droga łącząca dwa skrzyżowania (Crossi ng).

Trip

Trasa, ciąg kierunków ( D i r e c t i o n ) między dwiema lokalizacjami (Location).

290

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Ponadto na etapie zbierania wymagań ustaliliśmy z klientem następujące wymagania pozafunkcyjne. Wymagania pozafunkcyjne dla systemu MyTrip 1. Kontakt z serwerem systemu (PI anni ngServi ce) powinien odbywać się przez m o d e m bezprzewodowy. Zakładamy, że jakość łączności jest zadowalająca w początkowej lokalizacji podróży. 2. Gdy podróż zostanie rozpoczęta, MyTri p powinien wskazywać właściwe kierunki nawet w przypadku braku połączenia z planistą (PI anni ngServi ce). 3. Łączny czas połączenia z planistą powinien być jak najmniejszy, ze względu na koszty. 4. Zmiana planu podróży powinna być możliwa w warunkach połączenia z planistą. 5. Planista powinien być zdolny do równoczesnej obsługi przynajmniej 50 klientów i 1000 tras.

6.4.2. Identyfikowanie celów projektowych Definiowanie celów projektowych jest pierwszym krokiem projektowania systemu. Obejmuje ono określenie najważniejszych cech systemu. Wiele celów projektowych można wyprowadzić wprost z wymagań pozafunkcyjnych lub z dziedziny aplikacyjnej, inne ustalane są w drodze negocjacji z klientem. Niezależnie jednak od genealogii, wszystkie cele projektowe powinny być wyraźnie udokumentowane, tak by wszystkie istotne decyzje podejmowane były w sposób spójny, czyli w oparciu o ten sam zestaw kryteriów. I tak na przykład w świetle wymagań pozafunkcyjnych, opisanych w sekcji 6.4.1, możemy zidentyfikować dwie cechy systemu MyTri p o randze celów projektowych — niezawodność oraz zdolność funkcjonowania w warunkach utraty łączności. Jako że z systemu korzystać może równocześnie wielu klientów, identyfikujemy następny cel — bezpieczeństwo, jako ochronę danych klienta przed ingerencją (zamierzoną łub nie) ze strony innych klientów. Klient (kierowca) powinien mieć także możliwość wyboru konkretnej usługi planowania, co identyfikujemy jako kolejny cel — modyfikowalność. W poniższej ramce przedstawiamy wyraźne sformułowanie wymienionych celów. Cele projektowe systemu MyTri p ® Niezawodność: MyTri p powinien funkcjonować niezawodnie [generalizacja wymagania pozafunkcyjnego nr 2]. ® Odporność na awarie: MyTri p powinien tolerować utratę połączenia z serwerem [inna postać wymagania pozafunkcyjnego nr 2]. •

Bezpieczeństwo: MyTri p powinien być bezpieczny, czyli nie powinien udostępniać danych użytkownika innym użytkownikom, nie powinien też zezwalać na nieautoryzowany dostęp [wynik dedukcji z dziedziny aplikacyjnej],



Modyfikowalność: MyTri p powinien być modyfikowalny, czyli musi umożliwiać wybór między różnymi usługami planowania podróży [wynik przewidywania przyszłych potrzeb klienta].

Ogólnie cele projektowe wybierane są z długiej listy najbardziej pożądanych cech systemu. W tabelach od 6.5 do 6.9 przedstawiamy listy grupujące możliwe kryteria projektowania. Kryteria te zorganizowaliśmy w pięć grup, związanych z (kolejno) wydajnością, pewnością

6.4. Aktywności projektowania systemu: od obiektów do podsystemów

291

działania, kosztami, pielęgnowalnością i kryteriami użytkownika. Wydajność, niezawodność i kryteria użytkownika określane są zwykle w specyfikacji wymagań bądź dają się wydedukować z dziedziny aplikacyjnej. Kryteria kosztowe i pielęgnacyjne określane są w drodze negocjacji klienta z dostawcą systemu. Kryteria wydajności (tabela 6.5) obejmują wymagania dotyczące szybkości działania i zapotrzebowania na pamięć. Czy system powinien być raczej wysoce reaktywny, czy może ważniejsze jest, by realizował równocześnie jak najwięcej zadań? Czy możliwe jest zwiększone wykorzystanie pamięci w celu zwiększenia szybkości działania, czy też pamięć powinna być raczej oszczędnie używana? Tabela 6.5. Kryteria dotyczące wydajności

Kryterium projektowe Czas reakcji

Definicja Jak długo użytkownik musi oczekiwać na odpowiedź systemu po wysłaniu żądania?

Przepustowość

Ile zadań system zdolny jest wykonać w ustalonym przedziale czasu?

Pamięć

Jak dużo pamięci potrzebuje system do działania?

Kryteria pewności działania (tabela 6.6) związane są z wysiłkiem, jaki należy zainwestować w minimalizowanie zarówno prawdopodobieństwa awarii systemu, jak i konsekwencji ewentualnych awarii. Jak często system może się załamywać? W jakim stopniu powinien być dostępny dla użytkowników? W jakim zakresie powinien tolerować awarie i konsekwencje swych własnych błędów? Czy użytkowanie systemu pociąga za sobą zagrożenia dla środowiska? Jeśli tak, to jakie ? Czy awaria systemu może zagrażać bezpieczeństwu jego użytkowników? Tabela 6.6. Kryteria dotyczące pewności działania

Kryterium projektowe

Definicja

Solidność

Zdolność obsługi nieprawidłowych danych wejściowych.

Niezawodność

Różnica między specyfikowanym a rzeczywistym zachowaniem.

Dostępność

W jakim procentowo czasie system może być używany do spełniania swych normalnych zadań?

Odporność

Zdolność do działania w nienormalnych warunkach.

Zabezpieczenia

Zdolność powstrzymywania ataków z zewnątrz.

Bezpieczeństwo

Brak zagrożenia dla ludzkiego życia i zdrowia, nawet w warunkach błędów i usterek.

Kryteria kosztowe (tabela 6.7) związane są z kosztami opracowania, wdrożenia i administracji systemu. Zauważmy, że są wśród tych kryteriów takie, które nie mają bezpośredniego związku z projektowaniem systemu, lecz dotyczą raczej menedżerskich aspektów całego projektu. Gdy nowy system ma stać się następcą istniejącego systemu, powstaje problem obsługi istniejących danych w nowym środowisku — problem, który rozwiązać można dwojako: poprzez zapewnienie kompatybilności wstecz w nowym systemie albo przez konwersję

292

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Tabela 6.7. Kryteria dotyczące kosztów

Kryterium projektowe

Definicja

Koszty projektowania

Koszty zaprojektowania wstępnej wersji systemu.

Koszty wdrożenia

Koszt zainstalowania systemu i przeszkolenia użytkowników.

Koszty aktualizacji

Koszt konwersji danych, zależny od wymagań dotyczących zachowania kompatybilności wstecz.

Koszty pielęgnacji

Koszty usuwania błędów i rozbudowywania systemu.

Koszty administracyjne

Koszty administrowania systemem.

danych do nowej postaci, wymaganej przez nowy system (bądź za pomocą innej procedury o charakterze pośrednim między wymienionymi skrajnościami). Nie można także zapominać 0 kosztach szkolenia użytkowników nowego systemu i kosztach jego pielęgnowania. Kryteria pielęgnowalności (tabela 6.8) determinują stopień trudności utrzymywania 1 rozwijania systemu po wdrożeniu. Jak łatwo można poszerzać system o nowe elementy funkcjonalne? Jak łatwo można modyfikować jego obecne funkcje? Czy możliwe jest łatwe zaadaptowanie systemu do innej dziedziny aplikacyjnej? Jak skomplikowane może być przenoszenie systemu na inną platformę sprzętową lub programową? Kryteria tej grupy bardzo trudno planować i optymalizować, bowiem trudno zawczasu przewidzieć zarówno przebieg realizacji projektu, jak i okres eksploatowania nowego systemu. Tabela 6.8. Kryteria dotyczące pielęgnowalności

Kryterium projektowe

Definicja

Rozszerzalność

Jak łatwo można dodawać nowe klasy i nowe elementy funkcjonalne do systemu?

Modyfikowalność

Jak łatwo można zmieniać funkcje aktualnie realizowane przez system?

Adaptowalność

Jak łatwo można przystosować system do innej dziedziny aplikacyjnej?

Przenośność

Jak łatwo można przystosować system do działania na innej platformie sprzętowej lub programowej?

Czytelność

Jak zrozumiały okazuje się system na podstawie studiowania jego kodu źródłowego?

Identyfikowalność wymagań

Jak wyraźne jest odwzorowanie poszczególnych fragmentów kodu źródłowego systemu w poszczególne wymagania zapisane w specyfikacji wymagań?

Kryteria użytkownika (tabela 6.9) obejmują te cechy systemu, które istotne są z perspektywy jego użytkownika, lecz nie zostały ujęte w kategoriach wydajności ani pewności działania. Jak trudna jest nauka obsługi systemu? Czy typowy użytkownik zdolny będzie do bezbłędnego wykonywania powierzonych mu zadań? Często zdarza się, że pytań tego rodzaju nie traktuje się z należytą uwagą — zwłaszcza wówczas, gdy klient kontraktujący system nie jest jego bezpośrednim użytkownikiem.

6.4. Aktywności projektowania systemu: od obiektów do podsystemów

293

Tabela 6.9. Kryteria specyficzne dla użytkownika systemu Kryterium projektowe

Definicja

Przydatność

W jakim stopniu system zdolny jest wspomagać użytkownika w jego codziennej pracy?

Użyteczność

Jak łatwe jest dla użytkownika korzystanie z systemu?

D e f i n i u j ą c cele p r o j e k t o w e , r o z p a t r u j e się j e d n o r a z o w o niewielkie g r u p y w y m i e n i o n y c h k r y t e r i ó w , rozstrzygając n i e u c h r o n n e k o m p r o m i s y m i ę d z y n i m i : nie jest n a p r z y k ł a d r e a l n e stworzenie o p r o g r a m o w a n i a n i e z a w o d n e g o , bezpiecznego i j e d n o c z e ś n i e taniego. Programiści, d e c y d u j ą c się n a o k r e ś l o n y w y b ó r celów p r o j e k t o w y c h , p o w i n n i k o n f r o n t o w a ć je także z czynnikami o charakterze menedżerskim, takimi jak ograniczenia budżetowe i wynikające z harm o n o g r a m u . W tabeli 6.10 p r z e d s t a w i a m y kilka w y b r a n y c h p r z y k ł a d ó w w s p o m n i a n y c h kompromisów.

Kompromis

Przesłanki r o z s t r z y g a n i a

Szybkość kontra zapotrzebowanie na pamięć

Jeśli aplikacja nie spełnia wymagań pod względem maksymalnego czasu reakcji czy minimalnej przepustowości, można przyspieszyć jej działanie, stosując na przykład cache'owanie, co wymaga dodatkowej pamięci. Jeśli aplikacja jest zbyt pamięciożerna, można zmniejszyć zapotrzebowanie na pamięć, przykładowo przez kompresję danych czy wielokrotne obliczanie tych samych wielkości zamiast ich zapamiętywania — co oczywiście wymaga dodatkowego czasu procesora.

Czas dostarczenia kontra zakres funkcjonalny

Gdy dotrzymanie terminów określonych w harmonogramie staje się zagrożone, menedżer może zdecydować o okrojeniu zakresu fiinkcjonałnego systemu w celu przyspieszenia realizacji projektu bądź też o zachowaniu zakładanej funkcjonalności systemu kosztem jego spóźnionego dostarczenia. Dla systemów kontraktowanych funkcjonalność systemu staje się w takiej sytuacji czynnikiem ważniejszym niż jego t e r m i n o w e ukończenie, dla oprogramowania „z półki" ważniejsza jest zwykle terminowość dostarczenia.

Czas dostarczenia kontra jakość

Jeśli terminowe dostarczenie systemu zagrożone jest z powodu trwających testów systemu, m e n e d ż e r może zdecydować o dostarczeniu systemu n i e k o m p l e t n i e przetestowanego lub zawierającego r o z p o z n a n e błędy (dła których w terminie późniejszym przewidziane są łaty łub poprawki) albo dostarczyć z opóźnieniem system lepiej przetestowany, po usunięciu znanych błędów.

Czas dostarczenia kontra siły wytwórcze

Gdy terminowość realizacji poszczególnych części systemu staje się zagrożona, menedżer projektu może zdecydować o zaangażowaniu w projekt kolejnych uczestników. Zwiększenie liczebności personelu ma jednak pozytywne skutki raczej we wczesnych stadiach realizacji projektu, bowiem w późniejszych fazach nowi uczestnicy wymagają przeszkolenia wiążącego się z czasowo obniżoną produktywnością. Niezależnie od pozytywnych efektów, przydział dodatkowych zasobów do projektu zawsze wiąże się z dodatkowymi kosztami.

294

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Jak widać, cele menedżerskie mogą kolidować z celami technicznymi — przykładowo bezwzględnie terminowe dostarczenie systemu może odbyć się kosztem jego funkcjonalności. Mając już jasno zdefiniowane cele projektowe, możemy przystąpić do wstępnego dekomponowania systemu.

6.4.3. Identyfikowanie podsystemów Identyfikowanie podsystemów na etapie projektowania systemu jest podobne do identyfikowania obiektów modelu analitycznego — można w tym celu wykorzystywać niektóre techniki opisane w rozdziale 5. „Analiza wymagań", na przykład heurystyki Abbotta. Identyfikowanie podsystemów jest procesem ewoluującym w miarę rozpoznawania nowych okoliczności: niektóre podsystemy zostają łączone w większe całości, złożone podsystemy dzielone są na prostsze części, pojawiają się także nowe podsystemy odzwierciedlające nowo zidentyfikowaną funkcjonalność. Pierwsze iteracje dekomponowania systemu, przeprowadzane zwykle w formie „burzy mózgów", objawiają się drastycznymi zmianami w jego modelu. Początkowo dekompozycja systemu powinna odbywać się w oparciu o jego wymagania funkcjonalne. Przykładowo w systemie MyTri p możemy wyróżnić dwie grupy obiektów: te, które uczestniczą w przypadku użycia PI anTri p, i te uwikłane w przypadek użycia ExecuteTri p. Obiekty Tri p, Di r e c t i on, Crossi ng, Segment i Desti nati on wspólne są dla obu przypadków; są one ściśle powiązane i jako całość mogą być uważane za reprezentację podróży. Dodając im do towarzystwa obiekt PI anni ngServi ce, zamykamy całość w odrębny podsystem służący planowaniu podróży, nazwany PI anni ngSubsystem; pozostałe obiekty — Location i Route ^•Assi s t a n t — zamykamy w drugi podsystem Routi ngSubsystem, służący do wskazywania kierowcy trasy podróży. Między wyodrębnionymi podsystemami występuje tylko jedno skojarzenie. Wynik tej dekompozycji widoczny jest na rysunku 6.27, funkcje obu podsystemów opisane zostały w tabeli 6.11. Rozpoznawalna jest analogia repozytorium, jaką jest podsystem PI anni ngSubsystem zarządzający centralną strukturą danych.

Rysunek 6.27. Wynik wstępnej dekompozycji systemu MyTri p

6.4. Aktywności projektowania systemu: od obiektów do podsystemów

295

Tabela 6.11. Funkcje podsystemów systemu MyTri p, wyodrębnionych w ramach jego wstępnej dekompozycji

Podsystem

Funkcja spełniana w systemie

PlanningSubsystem

Podsystem odpowiedzialny za skonstruowanie trasy (Tri p) łączącej zadany zbiór lokalizacji ( L o c a t i on), a także za aktualizację trasy inicjowaną przez podsystem Routi ngSubsystera.

RoutingSubsystem

Podsystem odpowiedzialny za pobranie trasy (Tri p) z serwera systemu (PI anni ngServi ce) i realizację tej trasy poprzez wskazywanie kierowcy właściwych kierunków jazdy (Di r e c t i on) na poszczególnych skrzyżowaniach (Crossing), stosownie do ich usytuowania względem celu podróży ( D e s t i n a t i o n ) .

Inną heurystyką pomocną w dekomponowaniu systemu jest grupowanie obiektów powiązanych funkcjonalnie. Bezpośrednie zastosowanie tej reguły, polegające na łączeniu w osobne podsystemy obiektów uczestniczących w poszczególnych przypadkach użycia, jest dobrym punktem wyjścia dla dekompozycji. Obiekty współdzielone między przypadki użycia — jak obiekty Crossi ng, Desti nati on, Di r e c t i on, PI anni ngServi ce, Segment i Tri p systemu MyTri p — odpowiedzialne za komunikowanie się ze sobą potencjalnych podsystemów, można bądź to wydzielić w postaci osobnego podsystemu, bądź też przyporządkować je do podsystemu odpowiadającemu temu przypadkowi użycia, w którym obiekty te są tworzone. Heurystyki pomocne w grupowaniu obiektów w podsystemy •

Przyporządkowuj do tego samego podsystemu obiekty uczestniczące w tym samym przypadku użycia



Wyodrębniaj w postaci oddzielnych podsystemów obiekty wykorzystywane do przesyłania danych między podsystemami.



Minimalizuj liczbę skojarzeń przecinających granice podsystemów.



Wszystkie obiekty danego podsystemu powinny być funkcjonalnie powiązane.

Hermetyzacja podsystemów

za pomocą wzorca projektowego

Fasada

Dekompozycja systemu zmniejsza ogólną złożoność dziedziny realizacyjnej poprzez redukowanie sprzężenia między podsystemami. Wzorzec projektowy Fasada (patrz sekcja A.6 i książka E. Gammy, R. Heima, R. Johnsona i J. Vlissidesa [Gamma i in., 1994])" umożliwia dalszą redukcję zależności między klasami dzięki zamknięciu podsystemu w ramy prostego, zunifikowanego interfejsu. Widoczna na rysunku 6.28 klasa Compiler stanowi fasadę skrywającą klasy CodeGenerator, Optimi zer, ParseNode, Parser i Lexer. Fasada zapewnia dostęp jedynie do usług publicznie oferowanych przez podsystem, skrywając wszelkie inne szczegóły obiektów tego podsystemu, co w efekcie redukuje sprzężenie między systemami.

11

Oraz cytowana już książka Head First Design Patterns. Edycja polska (Rusz głowę!), wyd. Helion, 2005 — przyp. tłum.

296

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

Rysunek 6.28. Przykład zastosowania wzorca projektowego Fasada do ukrywania prywatnych szczegółów podsystemu

Podsystemy powstające w wyniku wstępnej dekompozycji często okazują się grupami funkcjonalnie powiązanych klas, jako takie stanowią więc znakomite kandydatury na hermetyzację w formie pojedynczych klas pełniących funkcje fasady.

6.5. Literatura uzupełniająca Koncepcja architektury oprogramowania po raz pierwszy pojawiła się w pracach E. W. Dijkstry i D. Parnasa, którzy argumentowali, że struktura fragmentu oprogramowania ma znaczenie nie mniej krytyczne niż jego zdolność do produkowania poprawnych wyników. Dijkstra jest autorem koncepcji architektury warstwowej i opisuje jej zastosowanie do przykładowego systemu T.H.E. w swojej książce [Dijkstra, 1968]. Parnas w swej pracy [Parnas, 1972] opisuje natomiast koncepcję ukrywania informacji i dyskutuje kryteria uwzględniane przy dekompozycji systemu. Pojęcia sprzężenia i spoistości jako metryk związanych z architekturą oprogramowania omówione są w pracy E. Yourdona i L. L. Constantina [Yourdon i Constantine, 1979]. Mimo iż nikt nie kwestionuje zalet architektury oprogramowania i jej znaczenia w inżynierii oprogramowania, literatura o tej tematyce wciąż, od dziesięcioleci, zdaje się ewoluować w różnych kierunkach. Prawdopodobną tego przyczyną jest brak jednolitego języka do opisywania różnych architektur, jak również brak analitycznych metod porównywania ich ze sobą oraz weryfikowania ich adekwatności do specyfikacji wymagań. Prace M. Shawa i D. Garlana [Shaw i Garlan, 1996] oraz F. Buschmanna, R. Meuniera, H. Rohnerta, P. Sommerlanda i M. Stała [Buschmann i in., 1996] wydają się być pierwszą udaną próbą uporządkowania tego stanu rzeczy w formie katalogu najczęściej używanych architektur. Shaw i Garlan wprowadzają pojęcie stylu architektonicznego, Buschmann ze współautorami prezentują natomiast wykorzystywanie wzorców architektonicznych jako języka opisowego.

5.8. Ćwiczenia

297

Wiele analiz przypadku, jakie pojawiły się w ostatniej dekadzie ubiegłego wieku, podaje ewidentne przykłady korzyści wynikających ze stosowania wzorcowych architektur oprogramowania, nie tylko z technicznego punktu widzenia (czyli decyzji projektowych dotyczących struktur i wielokrotnego wykorzystywania komponentów), lecz także z perspektywy zarządzania projektami (powtarzalnych metod organizowania zespołów i ich komunikowania się ze sobą). W Instytucie Inżynierii Oprogramowania uniwersytetu Carnegie Mellon zgromadzono i opracowano wiele materiałów na temat architektury oprogramowania i jej konkretnych zastosowań. Prace L. Bassa, P. Clementsa i R. Kazmana [Bass i in., 2003] oraz P. Clementsa, R. Kazmana i M. Kleina [Clements i in., 2002] zawierają opis metod i analizy przypadków związanych z wyborem i oceną konkretnej architektury oprogramowania. Przykłady praktycznych zastosowań architektury oprogramowania w rozwiązaniach przemysłowych znajdą natomiast czytelnicy w książce C. Hofmeister, R. Norda i D. Soniego [Hofmeister, 2000],

6.6. Ćwiczenia 6.1. Dekomponowanie systemu na podsystemy z jednej strony zmniejsza złożoność, z którą muszą borykać się programiści, poprzez redukowanie sprzężenia między podsystemami i zwiększanie spoistości każdego z nich; z drugiej jednak strony przyczynia się do wzrostu złożoności, poprzez zwiększanie rozdrobienia systemu i liczby interfejsów. Jeśli maksymalna spoistość ma być przesłanką przemawiającą za dzieleniem podsystemów na coraz mniejsze części, to jaka konkurencyjna przesłanka przemawia za utrzymywaniem liczby podsystemów na możliwie niskim poziomie? 6.2. W sekcji 6.4.2 zaklasyfikowaliśmy cele projektowe w pięciu kategoriach: wydajności, pewności działania, kosztów, pielęgnowalności i kryteriów użytkownika. Przyporządkuj każdy z poniższych celów do jednej lub kilku z wymienionych kategorii: • użytkownik musi otrzymać od systemu odpowiedź najdalej po sekundzie od wysłania żądania, • Ti cketDi s t r i butor musi być zdolny do wydrukowania biletu, nawet w przypadku awarii sieci, • sprzęt hostujący system Ti cketDi s t r i butor musi umożliwiać instalowanie nowych przycisków, jeżeli wymagać tego będzie większe zróżnicowanie typów biletów, • bankomat musi powstrzymywać ataki polegające na próbie odgadnięcia numeru PIN za pomocą systematycznych prób. 6.3. Opracowując system, przechowujący swe dane w uniksowym systemie plików, przewidujesz, że w przyszłości tworzona będzie wersja Twojego systemu dla środowiska przechowującego pliki w odmienny sposób. Zaprojektuj dekompozycję Twojego systemu uwzględniającą tę perspektywę. 6.4. Starsze kompilatory projektowane były zgodnie z architekturą filtr-potok, w której każdy z filtrów przekształcał otrzymane dane do pewnej postaci pośredniej i przekazywał je następnemu filtrowi. Współczesne środowiska programistyczne, obejmujące zintegrowany kompilator, edytor świadomy składni języka i debugger na poziomie kodu źródłowego wykorzystują architekturę repozytoryjną. Spróbuj określić cele projektowe, które prawdopodobnie stały się przyczyną tej zmiany.

298

Rozdział 6. • Projektowanie systemu — dekompozycja na podsystemy

6.5. Dla przykładu architektury model-widok-kontroler (MVC) z rysunków 6.16 i 6.17: a) narysuj diagram sekwencji odpowiadający diagramowi komunikacyjnemu z rysunku 6.17, b) zastanów się, w jaki sposób architektura MVC ułatwia lub utrudnia realizowanie następujących celów projektowych: • rozszerzalności (polegającej na dodawaniu nowych typów widoków); • reaktywności (objawiającej się małym opóźnieniem między wprowadzeniem zmiany w modelu a uwzględnieniem jej na widoku); • modyfikowalności (czyli definiowania nowych atrybutów plików i katalogów); • kontrolowalności dostępu (czyli pewności, że tylko upoważnieni użytkownicy będą mieć dostęp do poszczególnych części modelu). 6.6. Wymień przykładowe cele projektowe, które mogą być trudne do zrealizowania w ramach zamkniętej architektury warstwowej w rodzaju modelu odniesienia OSI-ISO, widocznego na rysunku 6.10. 6.7. W ramach wielu architektur — między innymi architektury trój- i czterowarstwowej (rysunki 6.22 i 6.23) — przechowywaniem obiektów trwałych zarządza dedykowana warstwa. Jak myślisz, które cele projektowe spowodowały podjęcie takiej właśnie decyzji?

Bibliografia [Bass i in., 2003]

[Buschmann i in., 1996]

L. Bass, P. Clements, R. Kazman Software Architecture wyd. drugie, Addison-Wesley, Reading, MA, 2003.

in Practice,

F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, M. Stal Pattern-Oriented Software Architecture, John Wiley & Sons, Chichester, 1996.

[Clements i in., 2002]

P. Clements, R. Kazman, M. Klein Evaluating Software Architectures: Methods and Case Studies, SEI Series in Software Engineering, Addison-Wesley, 2002.

[Day i Z i m m e r m a n n , 1983]

J. D. Day, H. Z i m m e r m a n n „The OSI Reference Model", Proceedings of the IEEE, t. 71, str. 1334 - 1340, grudzień 1983.

[Dijkstra, 1968]

E. W. Dijkstra The Structure of the „T.H.E" Multiprogramming System, „Communication of the ACM" 18 (8), str. 453 - 457, 1968.

[Erman i in., 1980]

L. D. Erman, F. Hayes-Roth i in. The Hearsay-IISpeech-Understanding System: Integrating knowledge to resolve uncertainty, „ACM Computing Surveys", t. 12, nr 2, str. 213 - 253,1980.

[Gamma i in., 1994]

[Hofmeister, 2000]

E. G a m m a , R. H e l m , R. Johnson, J. Vlissides Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1994. Wydanie polskie Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku, Helion 2010. C. Hofmeister, R. Nord, D. Soni Applied Software Architecture, Object Technology Series, Addison-Wesley, 2000.

299

Bibliografia [JFC, 2009]

Java Foundation

Classes, JDK Documentation, Javasoft, 2009.

[OMG, 2008]

Object Management Group Common Object Request Broker Architecture (CORBA) Specification: Version 3.1, http://www.omg.org 2008.

[Parnas, 1972]

D. Parnas On the Criteria to Be Used in Decomposing Systems into Modules, „Communications of the ACM", 15 (12), str. 1053 - 1058,1972.

[Ritchie i Thompson, 1974]

D. M. Ritchie, K. T h o m p s o n The Unix Time-sharing System, „Communications of the ACM", 1.17, nr 7, str. 365 - 377, lipiec 1974.

[RMI, 2009]

Java Remote Method Invocation, JDK Documentation, Javasoft, 2009.

[Rumbaugh i in., 1991]

J. R u m b a u g h , M. Błaha, W . Premerlani, F. Eddy, W . Lorensen Object-Oriented Modeling and Design, Prentice Hall, Englewood Cliffs, NJ, 1991.

[Shaw i Garlan, 1996]

M. Shaw, D. Garlan Software Architecture: Perspectives on an Emerging Discipline, Prentice Hall, Upper Saddle River, NJ, 1996.

[Yourdon i Constantine, 1979]

E. Yourdon, L. L. Constantine Structured Design, Prentice-Hall, Englewood Cliffs, NJ, 1979.

7.1.

Wstęp: przykład redundancji

302

7.2.

O aktywnościach projektowania systemu ogólnie

303

7.3.

Koncepcje: diagramy wdrażania UML

304

7.4.

Aktywności realizacji celów projektowych

306

7.4.1. 7.4.2. 7.4.3. 7.4.4. 7.4.5. 7.4.6. 7.4.7.

Odwzorowywanie podsystemów w procesory i komponenty Identyfikowanie trwałych danych i ich przechowywanie Definiowanie założeń kontroli dostępu Projektowanie globalnego przepływu sterowania Identyfikowanie usług Identyfikowanie warunków granicznych Weryfikowanie projektu systemu

306 309 312 319 321 323 326

7.5.

Zarządzanie projektowaniem systemu 7.5.1. Dokumentowanie projektu systemu 7.5.2. Przydzielanie odpowiedzialności 7.5.3. Komunikacja w projektowaniu systemu 7.5.4. Iteracje projektowania systemu

328 328 330 331 333

7.6.

Analiza 7.6.1. 7.6.2. 7.6.3. 7.6.4. 7.6.5. 7.6.6. 7.6.7. 7.6.8.

przypadku — system ARENA Identyfikowanie cełów projektowych Identyfikowanie podsystemów Odwzorowanie podsystemów w procesory i komponenty Identyfikowanie i przechowywanie trwałych danych Definiowanie założeń kontroli dostępu Projektowanie globalnego przepływu sterowania Identyfikowanie usług Identyfikowanie warunków granicznych

334 335 336 337 339 340 341 343 345

7.6.9.

Wnioski

347

7.7.

Literatura uzupełniająca

348

7.8.

Ćwiczenia

348

Bibliografia

349

Projekt systemu: realizacja celów projektowych Dobre, szybkie, tanie. Wybierz sobie dwa. — stary aforyzm programistyczny

W trakcie projektowania systemu identyfikujemy cele projektowe, dokonujemy dekompozycji systemu na podsystemy, a następnie doskonalimy tę dekompozycję tak długo, aż zgodna będzie ze wszystkim i zakładanymi celami. Samą dekompozycję i koncepcję celów projektowych omawialiśmy w poprzednim rozdziale, ten poświęcimy natomiast aktywnościom zmierzających do zrealizowania wspomnianych celów, między innymi: •

wybieraniu komponentów do realizacji systemu, ze szczególnym uwzględnieniem komponentów „zpółki" i komponentów tradycyjnych — za pomocą takich właśnie komponentów poszczególne systemy realizowane są bardziej oszczędnie, należy więc mieć ten fakt na uwadze przy wstępnej dekompozycji systemu,

• odwzorowywaniu podsystemów w komponenty sprzętowe — gdy system rozproszony jest między kilka węzłów sprzętowych, potrzebne są zwykle dodatkowe podsystemy wynikające z wymagań dotyczących niezawodności i wydajności, • projektowaniu infrastruktury służącej zarządzaniu trwałymi danymi — pod pojęciem „trwałych" danych rozumiemy te, które zachowują swe istnienie po zakończeniu działania systemu; konkretny wybór wspomnianej infrastruktury ma ogólny wpływ na wydajność systemu i prowadzi zwykle do pojawienia się nowych podsystemów, • specyfikowaniu polityki kontroli dostępu — dostęp do współdzielonych obiektów systemu musi odbywać się w sposób kontrolowany; polityka kontroli dostępu określa sposób rozproszenia obiektów między poszczególne podsystemy, •

definiowaniu globalnego przepływu sterowania — sekwencja wykonywanych operacji decyduje o postaci interfejsu poszczególnych podsystemów,

• projektowaniu obsługi warunków granicznych — gdy zidentyfikowane zostaną wszystkie podsystemy, należy określić kolejność uruchamiania i kończenia pracy poszczególnych komponentów. Następnie opiszemy zagadnienia menedżerskie związane z projektowaniem systemu, czyli dokumentowanie, przydzielanie odpowiedzialności i komunikowanie. Rozdział zakończymy szczegółową dyskusją na temat rozstrzygania różnych kompromisów na przykładzie systemu ARENA.

302

Rozdział 7. • Projekt systemu: realizacja celów projektowych

7.1. Wstęp: przykład redundancji Redundancja w systemie komputerowym promu kosmicznego W przeciwieństwie do poprzednich statków kosmicznych, prom kosmiczny zaprojektowany został jako układ autonomiczny. Misje p r o m ó w kosmicznych są dłuższe niż misje Apollo czy Gemini, liczniejsza jest też załoga. Należy też liczyć się z tym, że w danym czasie może być realizowanych kilka misji. Prom kosmiczny musi więc być zdolny do tolerowania kilku awarii, zanim zapadnie decyzja o przedwczesnym zakończeniu misji, co wiąże się z koniecznością redundancji (dublowania) niektórych jego elementów, między innymi systemu komputerowego odpowiedzialnego za kierowanie, nawigację i kontrolę wysokości. Dublowanie systemu komputerowego statku kosmicznego nie jest pomysłem nowym. Rakieta Saturn, która wyniosła statek Apollo, wykorzystywała trzy identyczne kopie systemu — każdy jego komponent występował w trzech egzemplarzach. Gdy wynik produkowany przez jeden egzemplarz danego komponentu różnił się od wyniku produkowanego przez dwa pozostałe egzemplarze, uznawane to było za awarię: wadliwy komponent uznany zostawał za przegłosowany, przyjmowany był wynik produkowany przez pozostałe egzemplarze — tak oto maskowana była pojedyncza awaria. Takie rozwiązanie było po pierwsze trudne do zrealizowania, po drugie zdolne było sobie radzić z incydentalnymi awariami, nie z masową katastrofą, jaką było na przykład spłonięcie Apollo 13. W stacji orbitalnej Skylab zastosowano inne rozwiązanie. Dwa bliźniacze egzemplarze systemu komputerowego umieszczone były na dwóch różnych krańcach statku. Gdyby zawiódł jeden z tych systemów, drugi powinien przejąć jego funkcje. Podczas gdy stosunkowo długi czas przełączenia między systemami jest całkowicie akceptowalny dla stacji orbitalnej (która do pewnego stopnia może tracić wysokość bez kłopotliwych konsekwencji), jest on nie do przyjęcia w przypadku promu kosmicznego, którego komputery reagować muszą błyskawicznie na szybko następujące po sobie zdarzenia związane ze zboczeniem z kursu lub przebiegiem lądowania. Z tego względu system komputerowy promu kosmicznego powinien być zarówno zdublowany (jak w stacji Skylab), jak i szybko przełączalny (podobnie jak w rakiecie Saturn). Zgodnie z początkowymi wymaganiami ze strony NASA, prom kosmiczny powinien umieć poradzić sobie z dwiema awariami pod rząd, bez konieczności przerywania misji: prowadziło to do systemu zawierającego pięć identycznych komputerów z pięcioma uruchomionymi egzemplarzami tego samego oprogramowania. Gdyby zawiodły dwa spośród wspomnianych komputerów, trzy pozostałe powinny zapewnić kontynuowanie misji; gdyby zawiódł kolejny, dwa pozostałe powinny zapewnić bezpieczne wylądowanie. Ze względu na wysokie koszty takiego rozwiązania, NASA zdecydowała się obniżyć swe wymagania do jednej tolerowanej awarii — dwie następujące bezpośrednio po sobie miały powodować przerwanie misji. Ponieważ jednak zaawansowany już projekt zależny był od koncepcji pięciu komputerów (które zostały już dostarczone), piątemu komputerowi powierzono funkcję komputera zapasowego. Zauważmy, że wszystkie cztery pozostałe komputery wykonywać miały to samo oprogramowanie, zatem powielenie systemu nie zapewniało żadnego zabezpieczenia przed konsekwencjami błędów programistycznych. Na wspomnianym komputerze zapasowym wykonywana miała być uproszczona wersja systemu, zawierająca jedynie funkcje niezbędne do prawidłowej obsługi startu i lądowania.

Przedstawione przykłady stanowią doskonałą ilustrację tego, w jaki sposób podejmowane są decyzje związane z wyborem architektury skomplikowanego systemu informatycznego. Mimo iż opisywane historie mają już raczej znaczenie historyczne, większość podejmowanych decyzji dyktowana była celami projektowymi i wymaganiami pozafunkcyjnymi. Oczywiście,

7.2. O aktywnościach projektowania systemu ogólnie

303

gdy decyzje projektowe koncentrują się głównie wokół systemu programistycznego, wymagają nieco innego podejścia niż w przypadku promu kosmicznego, gdzie równie istotne były kwestie sprzętowe; mimo to, niezmienna pozostaje zasada, że cele projektowe rozpatrywane są pojedynczo, za każdym razem wpływając na kształt dekompozycji systemu i interfejsy poszczególnych podsystemów. Projektowanie systemu obejmuje przeanalizowanie kolejno wszystkich celów projektowych. W sekcji 7.2 opisujemy ogólnie związane z tym aktywności, w sekcji 7.3 prezentujemy nowy typ diagramów UML — diagramy wdrażania. Sekcję 7.4 poświęcamy szczegółowemu opisowi aktywności związanych z projektowaniem systemu i ich powiązaniom. Sekcja 7.5 zawiera omówienie zagadnień menedżerskich związanych z projektowaniem systemu. Opisane koncepcje ilustrujemy w sekcji 7.6 w ramach analizy przypadku, jakim jest system ARENA.

7.2. O aktywnościach projektowania systemu ogólnie Cele projektowe stają się drogowskazem do podejmowania rozmaitych decyzji, zwłaszcza wtedy, gdy konieczne jest rozstrzyganie rozmaitych kompromisów. Programiści dokonują podziału systemów na prostsze podsystemy, co stanowi naturalny sposób radzenia sobie ze złożonością: realizacja każdego podsystemu powierzona zostaje oddzielnemu zespołowi i odbywa się niezależnie od realizacji innych podsystemów. Aby jednak stało się to możliwe, programiści, dokonując dekompozycji, rozstrzygać muszą problemy dotyczące systemu jako całości. Oto one. •

Odwzorowanie oprogramowania w konkretny sprzęt. Jaka jest sprzętowa konfiguracja systemu? Które węzły odpowiedzialne są za poszczególne elementy funkcjonalne? Jak realizowana jest komunikacja między węzłami? Które usługi realizowane są z użyciem istniejących komponentów? Jak hermetyzowane są te komponenty? Rozważanie tych i podobnych kwestii powoduje pojawienie się dodatkowych podsystemów dedykowanych przesyłaniu danych między węzłami, zarządzaniu współbieżnością i zapewnianiu niezawodności. Wykorzystywanie komponentów „z półki" umożliwia bardziej ekonomiczną realizację skomplikowanych usług; pakiety dedykowane realizacji interfejsu użytkownika oraz systemy zarządzania bazami danych są najbardziej spektakularnymi przykładami takich właśnie komponentów. Każdy z tych komponentów powinien być jednak hermetyzowany w celu wyeliminowania uzależnień od niego: gdy w przyszłości konkurencyjny dostawca zaoferuje bardziej atrakcyjny system bazy danych, zmiana systemu pod tym kątem nie będzie wymagać znaczących ingerencji w jego kod 1 .

• Zarządzanie danymi. Które dane mają charakter trwały? W jaki sposób powinny być przechowywane? Jak odbywać się będzie dostęp do nich? Dane trwałe okazują się często wąskimi gardłami systemu, stanowią bowiem główny obiekt przetwarzania przez system. Z tego powodu dostęp do trwałych danych powinien być szybki i niezawodny. Jeśli odczytywanie i wyszukiwanie danych jest wolne, wolny jest cały system; jeśli prawdopodobne jest uszkodzenie danych, równie prawdopodobne jest kompletne 1

Przykład hermetyzowania systemu bazy danych widzieliśmy już na rysunku 6.6 w poprzednim rozdziale — przyp. tłum.

304

Rozdział 7. • Projekt systemu: realizacja celów projektowych

załamanie całego systemu. Tego typu zagadnienia muszą być rozpatrywane w sposób spójny na poziomie całego systemu — zwykle wtedy odbywa się wybór konkretnego komponentu bazodanowego i definiowanie towarzyszących mu podsystemów realizujących zarządzanie trwałymi danymi. •

Kontrola dostępu. Kto posiada dostęp do poszczególnych części danych? Czy reguły kontroli tego dostępu mogą być dynamicznie zmieniane? W jaki sposób reguły te są definiowane i realizowane? Kontrola dostępu do danych i ich bezpieczeństwo także są zagadnieniami ogólnosystemowymi — innymi słowy, polityka dostępu do określonych partii danych musi być identyczna dla wszystkich podsystemów.

• Przepływ sterowania. Jaka jest sekwencja poszczególnych operacji w systemie? Czy system sterowany jest zdarzeniami? Czy jest zdolny do obsługi kilku równoczesnych interakcji? Wybór konkretnego schematu przepływu sterowania ma swe konsekwencje dla interfejsów poszczególnych podsystemów: w systemie sterowanym zdarzeniami poszczególne podsystemy muszą zapewniać obsługę tych zdarzeń, w systemie wielowątkowym muszą być zdefiniowane mechanizmy szeregowania dostępu do wspólnych danych, w postaci wzajemnych wykluczeń i sekcji krytycznych. •

Warunki graniczne. W jaki sposób system rozpoczyna i kończy swą pracę? Jak obsługiwane są sytuacje wyjątkowe? Realizacja inicjowania i kończenia pracy systemu skupia większą część jego złożoności, szczególnie w środowisku rozproszonym. Inicjowanie, kończenie pracy i obsługa sytuacji wyjątkowych mają wpływ na postać interfejsów potencjalnie wszystkich podsystemów.

Na rysunku 7.1 widoczny jest diagram przedstawiający aktywności etapu projektowania systemu. Każda z aktywności dedykowana jest jednemu z problemów, które wyżej wymieniliśmy. Efektem każdej ze wspomnianych aktywności jest zwykle zmiana w dekompozycji systemu, generująca kolejne problemy. W ten oto sposób projektowanie systemu staje się procesem wysoce iteratywnym, a rezultatem każdej iteracji jest zidentyfikowanie nowych systemów lub modyfikacja istniejących i często ogólna zmiana w sposobie dekompozycji odbijająca się na wszystkich podsystemach, co postaramy się pokazać w tym rozdziale.

7.3. Koncepcje: diagramy wdrażania UML Diagramy wdrażania służą w języku UML do ukazywania relacji między komponentami wykonawczymi i węzłami infrastruktury. Komponentami są samowystarczalne encje, świadczące usługi na rzecz innych komponentów lub aktorów; przykładem komponentu w tym znaczeniu jest serwer WWW, świadczący usługi na rzecz przeglądarek WWW, jak również przeglądarka W W W (Firefox, Opera, Safari) świadcząca usługi na rzecz użytkownika. Węzeł to fizyczne urządzenie lub środowisko wykonawcze, na którym lub w którym komponent realizuje swe funkcje. System składa się ze współdziałających ze sobą komponentów czasu wykonywania, które mogą być rozproszone między wiele węzłów. Co więcej, węzeł jest pojęciem rekurencyjnym — może zawierać inne węzły: przykładowo na fizycznym urządzeniu może być zainstalowane środowisko wykonawcze. Na diagramach wdrażania węzły reprezentowane są przez pudełka, zawierające ikony komponentów ( 3 ) . By oznaczyć fizyczne urządzenia i środowiska wykonawcze, węzły mogą być etykietowane stereotypami. Ścieżki komunikacyjne

7.3. Koncepcje: diagramy wdrażania UML

305

Rysunek 7.1. Aktywności etapu projektowania systemu

między węzłami reprezentowane są przez linie ciągłe; linie te mogą być etykietowane stereotypami oznaczającymi protokoły komunikacji między parami węzłów. Na diagramie z rysunku 7.2 widoczne są dwie przeglądarki WWW, wyświetlające informację otrzymywaną z serwera WWW; serwer ten z kolei komunikuje się z serwerem bazy danych. Zwróćmy uwagę, że żadna z przeglądarek nigdy nie uzyskuje bezpośredniego dostępu do serwera bazy danych.

Rysunek 7.2. Diagram wdrażania reprezentujący przydział k o m p o n e n t ó w do różnych węzłów. Przeglądarki W W W uruchomione na pececie i macintoshu korzystają z serwera W W W (Webserver), który z kolei komunikuje się z bazą danych (Database)

Rozdział 7. • Projekt systemu: realizacja celów projektowych

306

Diagram wdrażania widoczny na rysunku 7.2 koncentruje się na przydziale komponentów od węzłów i dostarcza wysokopoziomową informację o każdym komponencie. Informację tę można uszczegółowić, określając interfejsy komponentów i zawarte w nich klasy. Na rysunku 7.3 widoczna jest taka informacja na temat komponentu Webserver z rysunku 7.2.

Rysunek 7.3. Szczegółowa informacja na temat k o m p o n e n t u Webserver. Komponent ten dostarcza interfejs h t t p , wymagając jednocześnie interfejsu jdbc. Interfejs h t t p realizowany jest przez klasę HttpService

7.4. Aktywności realizacji celów projektowych W tej sekcji opiszemy aktywności niezbędne do upewnienia się, że dekompozycja systemu zgodna jest ze wszystkimi celami projektowymi i uwzględnia wszelkie ograniczenia fazy implementacji. W sekcji 6.4 sformułowaliśmy cele projektowe systemu MyTri p i dokonaliśmy wstępnej dekompozycji. Teraz przystąpimy do udoskonalenia tej dekompozycji poprzez: • odwzorowywanie podsystemów w procesory i komponenty (patrz sekcja 7.4.1), • identyfikowanie trwałych danych i ich przechowywanie (patrz sekcja 7.4.2), • definiowanie założeń kontroli dostępu (patrz sekcja 7.4.3), • projektowanie globalnego przepływu sterowania (patrz sekcja 7.4.4), • identyfikowanie usług (patrz sekcja 7.4.5), • identyfikowanie warunków granicznych (patrz sekcja 7.4.6), • weryfikowanie projektu systemu (patrz sekcja 7.4.7).

7.4.1. Odwzorowywanie podsystemów w procesory i komponenty Wybór platformy i konfiguracji

sprzętowej

Wiele systemów funkcjonuje na więcej niż jednym komputerze i często zależy od dostępu do internetu lub intranetu. Użycie wielu komputerów rodzi wymagania pod adresem zarówno wydajności, jak i połączeń między wieloma rozproszonymi użytkownikami. W związku z tym musimy starannie przeanalizować przyporządkowanie podsystemów do poszczególnych komputerów i infrastrukturę realizującą komunikację między tymi podsystemami. Komputery te modelowane są jako węzły na diagramach wdrażania. Ponieważ odwzorowywanie podsystemów w węzły sprzętowe ma znaczący wpływ na złożoność i wydajność systemu, dokonywane jest w procesie projektowania systemu możliwie jak najwcześniej. Wybór konfi-

7.4. Aktywności realizacji celów projektowych

307

guracji sprzętowej wiąże się także z wyborem docelowej maszyny wirtualnej dla systemu. Taka maszyna wirtualna obejmuje system operacyjny i wszelkie niezbędne komponenty, takie jak system(y) zarządzania bazą danych czy pakiety komunikacyjne. Właściwy wybór maszyny wirtualnej redukuje dystans między systemem a platformą sprzętową, na której ma być eksploatowany. Im bardziej funkcjonalne wspomniane komponenty, tym mniej pracochłonne będzie opracowywanie samego systemu. Wybór maszyny wirtualnej może być jednak poważnie ograniczony, na przykład przez fakt, że klient kontraktujący system dysponuje już odpowiednią bazą sprzętową. Nie bez znaczenia są także kryteria kosztowe: być może samodzielne zaimplementowanie jakiegoś komponentu jest bardziej opłacalne niż zakup gotowej wersji — jednak tego typu dylematy rzadko są łatwe do rozstrzygania. Z wymagań dotyczących systemu MyTrip wnioskujemy, że jego podsystemy Planning ^Subsystem i Routi ngSubsystem funkcjonować mają na dwóch różnych węzłach: pierwszy na serwerze WWW realizującym odpowiednie usługi, drugi na pokładowym komputerze samochodu. Ten fakt odzwierciedlony jest na rysunku 7.4, na którym widoczne są dwa urządzenia: :OnBoardComputer (komputer pokładowy) i :WebHost (komputer serwera WWW); na tym ostatnim funkcjonuje środowisko wykonawcze : Apache. Jako maszynę wirtualną dla serwera W W W wybraliśmy odmianę UNIX-a, rolę maszyn wirtualnych w komputerze pokładowym spełniają przeglądarki Internet Explorer i Safari.

Rysunek 7.4. Przyporządkowanie podsystemów systemu MyTri p do urządzeń i środowisk wykonawczych. Routi ngSubsystem funkcjonuje na komputerze pokładowym :OnBoardComputer, Planning ^•Subsystem na serwerze Apache

Przydzielanie obiektów i podsystemów

do węzłów

Po zdefiniowaniu konfiguracji sprzętowej i wybraniu maszyn wirtualnych następuje przyporządkowanie obiektów i podsystemów do poszczególnych węzłów tej konfiguracji. Wywołuje to często konieczność definiowania nowych obiektów i podsystemów odpowiedzialnych za transfer danych między węzłami. W systemie MyTrip klasy Trip, Destination, Crossing, Segment i Direction współdzielone są przez oba podsystemy, czyli przez PI anni ngSubsystem i Routi ngSubsystem. Instancje tych klas komunikują się za pomocą łączności bezprzewodowej, zgodnie z ustalonym protokołem komunikacyjnym. Na potrzeby tej komunikacji definiujemy nowy podsystem — Communi cati onSubsystem — przydzielony do obu węzłów i zarządzający wymianą danych między nimi.

308

Rozdział 7. • Projekt systemu: realizacja celów projektowych

W podsystemie Routi ngSubsystem przechowywane są jedynie te segmenty, które składają się na zaplanowaną trasę podróży; segmenty przylegające do nich zapamiętane są jedynie w podsystemie PI anni ngSubsystem. W podsystemie Routi ngSubsystem potrzebne są więc obiekty służące jako surogaty obiektów Segment i Tri p, przechowywanych w systemie PI anni ngSubsystem. Obiekt funkcjonujący w imieniu innego obiektu określany bywa powszechnie mianem proxy1. Definiujemy zatem dwie kolejne klasy — SegmentProxy i TripProxy — jako część podsystemu Routi ngSubsystem. Klasy te są przykładem zastosowania wzorca projektowego Proxy — patrz sekcja A.8 i książka E. Gammy, R. Heima, R. Johnsona i J. Vlissidesa [Gamma i in., 1994]3. Gdy kierowca zdecyduje się na zmianę zaplanowanej trasy, obiekty wymienionych klas w sposób transparentny żądają od podsystemu Communi c a t i onSubsystem pobrania z podsystemu PI anni ngSubsystem informacji związanej z segmentami zapamiętanymi w podsystemie Routi ngSubsystem. W odpowiedzi podsystem Communi cati onSubsystem powoduje przesłanie do obiektu RouteAssistant kompletnej informacji o aktualnej trasie podróży. Zrewidowany model projektu systemu MyTri p wraz z definicjami nowych klas przedstawiamy na rysunku 7.5 i w tabeli 7.1.

Rysunek 7.5. Zrewidowany model projektu systemu MyTri p

Generalnie przydzielanie podsystemów do węzłów konfiguracji umożliwia rozproszenie funkcjonalności i rozdział mocy obliczeniowej stosownie do zapotrzebowania na nią. Jednocześnie stwarza kolejne problemy związane z przechowywaniem, przesyłaniem, replikowaniem i synchronizowaniem danych pomiędzy podsystemami.

2

W języku angielskim słowo to oznacza pełnomocnika — przyp.

3

Oraz cytowana już w poprzednim rozdziale książka Head First Design Patterns. (Rusz głową!), wyd. Helion, 2005 — przyp. tłum.

tłum. Edycja

polska

7.4. Aktywności realizacji celów projektowych

309

Tabela 7.1. Nowe klasy i podsystemy zrewidowanego modelu projektu systemu MyTri p

Element modelu

Definicja

Communi c a t i onSubsystem

Podsystem odpowiedzialny za transfer obiektów z podsystemu PI anni ngSubsystem do podsystemu Routi ngSubsystem.

Connection

Klasa reprezentująca aktywne połączenie między podsystemami PI anni ngSubsystem i Routi ngSubsystem, odpowiedzialna także za obsługę sytuacji wyjątkowej, jaką jest zerwanie przedmiotowego połączenia.

Message

Klasa reprezentująca komunikat niosący kompletną, zakodowaną informację o trasie podróży i związanych z nią obiektach Tri p, D e s t i n a t i o n , S e g m e n t , C r o s s i n g i Di r e c t i on.

SegmentProxy

Klasa podsystemu Routi ngSubsystem spełniająca rolę proxy w stosunku do klasy Segment z podsystemu PI anni ngSubsystem.

TripProxy

Klasa podsystemu Routi ngSubsystem spełniająca rolę proxy w stosunku do klasy Tri p z podsystemu PI anni ngSubsystem.

7.4.2. Identyfikowanie trwałych danych i ich przechowywanie Danymi trwałymi nazywamy te dane, które nie giną wraz zakończeniem pracy systemu lub programu. Przykładowo, pisząc książkę za pomocą edytora tekstu, możemy zapisać w pliku edytowany tekst i zamknąć edytor, by po jakimś czasie otworzyć wspomniany plik ponownie, otrzymując tekst w dokładnie takim stanie, w jakim go pozostawiliśmy. Uruchomiony edytor nie jest warunkiem istnienia pliku przechowującego ów tekst. Podobnie ewidencja danych pracowników i ich wynagrodzeń przechowywana jest zwykle w formie systemu zarządzania bazą danych. Dzięki temu wszystkie programy operujące na tej informacji czynią to w sposób spójny i skoordynowany, ponadto możliwe jest szybkie wyszukiwanie żądanej informacji wśród wielu tysięcy rekordów. Miejsce i sposób przechowywania danych w systemie jest jednym z najważniejszych czynników wpływających na proces dekompozycji tego systemu: przykładowo, co widzieliśmy w sekcji 6.3.5, w ramach architektury repozytoryjnej zarządzanie danymi może być dedykowane odrębnemu podsystemowi. Wybór konkretnego systemu zarządzania bazą danych ma także swe implikacje w kwestii ogólnej strategii przypływu sterowania i zarządzania współbieżnością: i tak w podsystemie Routi ngSubsystem zdecydowaliśmy się przechowywać aktualnie wyznaczoną trasę (Tri p) w pliku umieszczonym na wymiennym dysku, co daje kierowcy możliwość zamknięcia systemu w trakcie podróży i odtworzenie trasy po jego ponownym uruchomieniu, bez konieczności łączenia się z podsystemem PI anni ngSubsystem. To proste rozwiązanie wystarczające jest w zupełności dla podsystemu Routi ngSubsystem jedynie zapamiętującego wyznaczone trasy; w podsystemie PI anni ngSubsystem, od którego wymaga się konstruowania optymalnych tras i to dla wielu użytkowników równocześnie, poszczególne trasy, wraz z mapami i innymi informacjami niezbędnymi do ich konstruowania, przechowywane są w zaawansowanej bazie danych. W związku z tymi faktami dodaliśmy do systemu MyTri p dwa kolejne podsystemy, co pokazujemy na rysunku 7.6 i w tabeli 7.2.

310

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Rysunek 7.6. Dekompozycja systemu MyTrip uwarunkowana konkretnym wyborem strategii przechowywania danych Tabela 7.2. Nowe elementy dekompozycji systemu MyTrip, związane z wyborem strategii przechowywania danych

Element modelu

Definicja

Tri pFi1eStoreSubsystem

Podsystem odpowiedzialny za przechowywanie w plikach dyskowych tras zapamiętywanych w systemie pokładowym samochodu. Ponieważ przechowywanie tras na zewnętrznym nośniku przydatne jest głównie w przypadku zamykania komputera pokładowego, zdecydowaliśmy się na zapisywanie i odczytywanie jedynie k o m p l e t n y c h tras, bez jakiejkolwiek dalszej szczegółowości.

MapDBStoreSubsystem

Podsystem odpowiedzialny za przechowywanie map i tras w bazie p o d s y s t e m u PI anni ngSubsystem. Podsystem ten zdolny jest do równoczesnej obsługi wielu kierowców i agentów planowania.

Identyfikowanie

obiektów

trwałych

Gdy mowa o przechowywaniu danych, musimy najpierw zidentyfikować obiekty trwałe podlegające temu przechowywaniu. Oczywistymi kandydatami do tej grupy wydają się być obiekty encji zidentyfikowane na etapie analizy. W systemie MyTri p niewątpliwie przechowywane muszą być obiekty klasy Trip i klas jej towarzyszących (Crossing, Destination, Planning Servi ce i Segment), nie zawsze konieczne jest przechowywanie wszystkich obiektów encji — w systemie MyTri p nie jest na przykład sensowne przechowywanie obiektów Location i Direction, bowiem ich atrybuty wyliczane są automatycznie wraz z przemieszczaniem się samochodu. I vice versa: obiekty encji nie wyczerpują listy obiektów trwałych: w systemie obsługującym wielu użytkowników (kierowców) konieczne jest przechowywanie informacji spersonalizowanych, prywatnych dla poszczególnych użytkowników, jak również wielu obiektów brzegowych, reprezentujących między innymi pozycje okien, preferencje związane z interfejsem użytkownika czy stany „długożyciowych" obiektów sterujących. Generalnie identyfikowanie obiektów trwałych sprowadza się do wyszukiwania tych klas, których instancje muszą przetrwać zakończenie pracy systemu — zarówno wskutek jego zamierzonego zamknięcia, jak i z powodu niespodziewanej awarii. Po wznowieniu pracy system będzie mógł odtworzyć wartości atrybutów tych instancji w sposób automatyczny lub na żądanie, gdy instancje te okażą się potrzebne.

7.4. Aktywności realizacji celów projektowych

Wybór strategii przechowywania

311

danych

Po zidentyfikowaniu obiektów trwałych podlegających przechowywaniu między kolejnymi uruchomieniami systemu kolejnym istotnym zagadnieniem staje się strategia ich przechowywania. Decyzja wyboru tej strategii jest sprawą złożoną i dyktowaną głównie przez wymagania pozafunkcyjne. Jak szybkie ma być zapisywanie i odczytywanie obiektów? Czy wyszukiwanie obiektów ma się odbywać w oparciu o zaawansowane zapytania? Czy obrazy obiektów konsumują dużo pamięci i (lub) przestrzeni dyskowej? Zależnie od odpowiedzi na te i podobne pytania, dokonywany jest wybór spośród trzech prezentowanych poniżej rozwiązań. • Niezależne pliki. Pliki to abstrakcje magazynowania danych udostępniane przez system operacyjny. Plik jest strukturą niskopoziomową — ciągiem bajtów opatrzonych nazwą i prostymi atrybutami oraz elementarnymi regułami dostępu. Wszelkie problemy wynikające z wielodostępu oraz ryzyka utraty danych w przypadku awarii muszą być rozwiązywane w ramach samej aplikacji; aplikacja ta ma jednak szeroki wybór technik optymalizujących zarówno rozmiary plików, jak i szybkość ich przetwarzania. • Relacyjna baza danych. Relacyjna baza danych jest zbiorem powiązanych tabel, z których każda zgodna jest z pewnym predefiniowanym typem zwanym schematem. Tabela może być utożsamiana z macierzą, której kolumny reprezentują poszczególne atrybuty danych, zaś wiersze odpowiadają poszczególnym obiektom, określając wartości ich atrybutów w postaci krotek 4 . Odwzorowywanie skomplikowanych obiektów w schematy relacyjnej bazy danych jest prawdziwym wyzwaniem, któremu stawienie czoła ułatwiać ma kilka specjalizowanych metod, między innymi opisywane w książce M. Błahy i W. Premerlaniego [Błaha i Premerlani, 1998], Systemy zarządzania relacyjnymi bazami danych udostępniają metody zarządzania współbieżnym dostępem do danych i uprawnieniami tego dostępu, dostarczają także mechanizmy umożliwiające odzyskiwanie danych w przypadku awarii. Relacyjne bazy danych są wykorzystywane i ulepszane od dziesięcioleci, są więc technologią zdecydowanie dojrzałą. Choć z reguły są wysoce skalowalne i znakomicie nadają się do zarządzania ogromnymi porcjami danych, okazują się mało efektywne dla przechowywania małych porcji danych oraz danych bez określonej struktury (obrazów, tekstów w języku naturalnym i tym podobnych). •

4

Obiektowa baza danych. Usługi udostępniane przez obiektowo zorientowane bazy danych podobne są do usług oferowanych przez bazy relacyjne. W przeciwieństwie jednak do tych ostatnich, obiektowa baza danych przechowuje dane w formie obiektów i skojarzeń między nimi. Uwalnia to użytkownika od kłopotliwych odwzorowań między obiektami aplikacji a encjami (krotkami) przechowującymi obrazy tych obiektów, daje także programistom dodatkowe możliwości w postaci dziedziczenia klas i abstrakcyjnych typów danych. Przyczynia się to do znacznej redukcji pracochłonności projektowania systemu. Obiektowe bazy danych są jednak zwykle wolniejsze niż bazy relacyjne, bardziej skomplikowane jest też ich strojenie.

Krotka (ang. tupie), zwana także „n-tką" (czyt. „entką"), to zbiór wartości przypisywanych atrybutom z predefiniowanego zbioru — przyp. tłum.

312

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Poniżej przedstawiamy uproszczoną listę kryteriów wyboru konkretnego systemu przechowywania danych. Zwróćmy uwagę, że w przypadku złożonego systemu stosować można rozwiązania hybrydowe, na przykład niezależne pliki w połączeniu z relacyjnymi bazami danych. Odwzorowywaniem złożonych obiektów w tabele baz relacyjnych i niezależne pliki zajmiemy się szczegółowo w rozdziale 10. „Odwzorowywanie modelu na kod". Kryteria wyboru między niezależnymi plikami, bazami relacyjnymi i bazami obiektowymi Niezależne pliki — wystarczające dla danych: •

niestrukturalnych o dużej objętości (obrazy, animacje),



tymczasowych (plik wymiany),



o małym zagęszczeniu informacji (archiwa, dzienniki).

Bazy danych — relacyjne i obiektowe — zapewniają: •

synchronizację współbieżnego dostępu do danych,



efektywny dostęp do drobnych szczegółów informacji,



wielokrotne wykorzystywanie tych samych danych przez różne aplikacje, pracujące być może na różnych platformach.

Relacyjne bazy danych najlepiej nadają się do obsługi: •

dużych zbiorów danych,



efektywnego wyszukiwania obiektów na podstawie ich atrybutów, uwikłanych w skomplikowane zapytania.

Obiektowe bazy danych okazują się korzystne dla: •

średniej wielkości zbiorów danych,

® wyszukiwania danych silnie bazującego na skojarzeniach między obiektami, ® obiektów powiązanych za pomocą nieregularnych skojarzeń.

7.4.3. Definiowanie założeń kontroli dostępu W systemie obsługującym wielu użytkowników różni aktorzy posiadają zwykle odmienne uprawnienia dostępu do poszczególnych fragmentów danych i konkretnych elementów funkcjonalności. „Zwykli" aktorzy mają zazwyczaj dostęp do danych, które sami tworzą, lecz już administrator systemu ma nieograniczony dostęp do danych każdego użytkownika oraz danych systemowych. Na etapie analizy wymagań rozróżnienie to modelowane jest przez przyporządkowywanie różnych aktorów do różnych przypadków użycia. Na etapie projektowania systemu uprawnienia dostępu modelowane są przez odpowiednie współdzielenie obiektów między aktorów oraz kontrolowanie reguł dostępu poszczególnych aktorów do konkretnych obiektów. Zależnie od wymagań dotyczących zabezpieczeń i bezpieczeństwa, modelowanie to musi uwzględniać także dodatkowe czynniki, takie jak sposób uwierzytelniania aktorów czy techniki szyfrowania danych.

7.4. Aktywności realizacji celów projektowych

313

I tak na przykład w systemie MyTrip równoczesna obsługa wielu klientów stwarza odpowiednie wymagania pod względem bezpieczeństwa: trasy podróży (Tri p) przechowywane w systemie mogą być udostępniane tylko tym klientom, którzy sami je stworzyli, a nieuwierzytelnieni użytkownicy w ogóle nie powinni mieć dostępu do systemu. Wymaganie to, spójne z celami projektowymi sformułowanymi w sekcji 6.4.2, ma swe odzwierciedlenie w zdefiniowaniu klasy Driver, reprezentującej klientów (kierowców) i skojarzonej z klasą Trip. Podsystem PlanningSubsystem odpowiedzialny jest za uwierzytelnienie użytkownika przed przyjęciem od niego żądania dotyczącego określonych tras. Dla większego bezpieczeństwa dane przesyłane między podsystemami PI anni ngSubsystem i Routi ngSubsystem są szyfrowane — funkcję tę wykonuje podsystem CommunicationSubsystem. Definicję klasy Driver oraz zmodyfikowane definicje podsystemów PI anni ngSubsystem i Communi cati onSubsystem przedstawione są w tabeli 7.3; nowe elementy wyróżnione są kursywą. Tabela 7.3. Modyfikacja modelu projektu systemu uwzględniająca uwierzytelnianie użytkowników i szyfrowanie informacji przesyłanej między podsystemami. Nowe elementy wyróżnione są kursywę Communi c a t i onSubsystem

Podsystem odpowiedzialny za transfer obiektów z p o d s y s t e m u PI anni ngSubsystem do podsystemu Routi ngSubsystem. Podsystem Communi c a t i onSubsystem odpowiedzialny jest za szyfrowanie i odszyfrowanie przesyłanej informacji; klucz szyfrowania konstruowany jest na podstawie profilu zalogowanego użytkownika (Dri ver).

PlanningSubsystem

Podsystem odpowiedzialny za skonstruowanie trasy Podróży łączącej zadany zbiór Lokal i zac j i , a także za aktualizację trasy inicjowaną przez podsystem Routi ngSubsystem. Przed przyjęciem żądania podsystem PI anni ngSubsystem dokonuje uwierzytelnienia kierowcy (Dri ver) logującego się za pośrednictwem podsystemu Routi ngSubsystem. Po pomyślnym uwierzytelnieniu kierowca (Driver) otrzymuje listę tras (Tri pj, które sam utworzył i do pobrania których jest uprawniony.

Driver

Kierowca, zalogowany użytkownik. Klasa wykorzystywana przez podsystem Routi ngSubsystem do zapamiętywania klucza szyfrującego skojarzonego z użytkownikiem, zaś przez podsystem PI anni ngSubsystem do reprezentowania skojarzeń między zarejestrowanymi użytkownikami a należącymi do nich trasami (Trip).

Definiowanie kontroli dostępu dla typowego systemu jest zwykle bardziej skomplikowane niż w systemie MyTri p, dla każdego aktora konieczne jest bowiem precyzyjne określenie, jakie operacje ma prawo wykonywać na poszczególnych obiektach. Przykładowo kasjer w banku ma prawo realizować uznania i obciążenia na koncie klienta, ale tylko do pewnego predefiniowanego limitu kwotowego; transakcja wykraczająca poza ten limit musi być zatwierdzona przez menedżera. Podobnie każdy z menedżerów może analizować statystyki ze swego oddziału, nie ma jednak dostępu do statystyk z innych oddziałów. Analityk ma dostęp do wszystkich statystyk, nie może jednak modyfikować żadnego z kont. Reguły dostępu poszczególnych aktorów do poszczególnych obiektów najłatwiej modelować za pomocą macierzy, której wiersze odpowiadają aktorom, a kolumny — klasom podlegającym kontroli. Każda komórka tej macierzy, odpowiadająca parze (aktor-klasa) zawiera listę operacji (zwaną prawami dostępu), jakie dany aktor ma prawo wykonywać na instancjach danej klasy. Tabela 7.4 jest przykładem takiej właśnie macierzy.

314

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Tabela 7.4. Macierz dostępu w systemie bankowym. Kasjer m a prawo dokonywania drobnych transakcji (postSmallDebit() i p o s t S m a l l C r e d i t ( ) ) i przeglądania stanów kont (examineBalance()). Menedżer ma prawo dokonywania wszelkich transakcji na kontach (postSmal 1 Debi t (), postSmal 1 Credi t (), p o s t L a r g e D e b i t ( ) i p o s t L a r g e C r e d i t Q ) , przeglądania stanów i historii kont (examineBalanceQ i examineHistory()) i analizowania statystyk dotyczących własnego oddziału (examineBranchStats()). Analitycy mają dostęp do wszystkich statystyk (examineGlobalStatsQ i examineBranchStatsQ), nie mogą jednak wykonywać żadnych transakcji na kontach

Aktor/Klasa

Corporation (bank)

Local Branch (oddział)

postSmallDebitO postSmallCredit() examineBalanceQ

Teł 1 er (kasjer)

examineBranchStatsQ

Manager (menedżer)

Analyst (analityk)

Account (konto)

xamineGlobalStats()

postSmallDebitO postSmallCredit() postLargeDebit() postLargeCreditQ examineBalance() examineHistory()

examineBranchStats()

Macierz kontroli dostępu jest pewnym abstrakcyjnym elementem modelu, wymagającym konkretnej fizycznej reprezentacji w systemie. Spośród wielu możliwych reprezentacji najczęściej wykorzystywane są trzy. Oto one. • Globalna tabela dostępu, w której każda niepusta komórka macierzy dostępu reprezentowana jest w postaci jednej lub kilku krotek postaci (aktor, klasa, operacja). Rozstrzyganie, czy określony aktor ma prawo wykonywać określoną operację na obiektach określonej kl asy, sprowadza się do poszukiwania odpowiedniej krotki — gdy taka nie istnieje, aktor nie uzyska zezwolenia. • Listy kontroli dostępu, po jednej liście dla każdej klasy; każda z tych list składa się z par postaci (aktor, operacja). Przy każdej próbie wykonania przez określonego aktora określonej operacji na obiekcie danej klasy, w liście związanej z tą klasą poszukiwana jest odpowiednia para — gdy nie zostanie znaleziona, aktor nie uzyska zezwolenia. Przykładem takiej listy jest lista gości zaproszonych na imprezę: każdy przybyły gość konfrontowany jest przez organizatora z listą zaproszonych osób, na której obecność jest warunkiem koniecznym i wystarczającym do udziału w imprezie. • listy uprawnień, po jednej liście dla każdego aktora; każda z tych list składa się z par postaci (ki asa, operacja). Przy każdej próbie wykonania przez określonego aktora określonej operacji na obiekcie danej klasy, w liście związanej z tym aktorem poszukiwana jest odpowiednia para — gdy nie zostanie znaleziona, aktor nie uzyska zezwolenia. Analogią takiej listy może być zbiór zaproszeń, jakimi aktualnie dysponuje bywalec różnych imprez — w danej imprezie może uczestniczyć wyłącznie na podstawie odpowiedniego zaproszenia.. Wszystkie z wymienione reprezentacje niosą ze sobą identyczną informację, lecz różnią się między sobą pod względem konsekwencji dla wydajności systemu. Globalne tabele dostępu

7.4. Aktywności realizacji celów projektowych

315

cechują się dużym zapotrzebowaniem na pamięć; listy kontroli dostępu są bardziej efektywne przy rozstrzyganiu uprawnień dostępu z perspektywy konkretnej klasy, zaś listy uprawnień — bardziej efektywne przy rozstrzyganiu uprawnień dostępu z perspektywy konkretnego aktora. Każdy wiersz macierzy dostępu reprezentuje konkretny „widok dostępowy" do instancji klas ze zbioru określonego przez zbiór kolumn tej macierzy. Owe „widoki" powinny być spójne w obrębie całej macierzy, mimo to, są one zwykle implementowane poprzez definiowanie osobnych subklas dla różnych typów krotek (aktor, operacja). Przykładowo w omawianym przed chwilą systemie bankowości moglibyśmy w tym celu zaimplementować dwie subklasy klasy Account — AccountVi ewedByTel 1 er i AccountVi ewedByManager, reprezentujące (odpowiednio) konto w postaci dostępnej dla kasjera (Tel 1 er) i konto w postaci dostępnej dla menedżera (Manager). Tylko wybrane klasy będą wówczas dostępne dla określonego aktora — i tak na przykład nie istnieje podklasa klasy Account odpowiednia dla analityka (Analyst), ten bowiem nie ma prawa wykonywać żadnych operacji na kontach. W oprogramowaniu przeznaczonym dla analityka nie będzie więc zaimplementowana żadna klasa, która takie operacje mogłaby umożliwiać — zmniejsza to ryzyko uzyskania przez niego nieautoryzowanego dostępu do kont w rezultacie ewentualnych błędów w oprogramowaniu. Często liczba aktorów i (lub) liczba chronionych klas jest na tyle duża, że implementacja uprawnień w postaci zarówno list kontroli dostępu, jak i list uprawnień stanowiłaby zbyt duże obciążenie pamięciowe. W takich przypadkach implementuje się macierz dostępu w postaci bardziej zwartej — w formie zbioru reguł. Przykładem takiej reprezentacji są zapory sieciowe {firewalls) chroniące usługi zlokalizowane w intranecie przed dostępem ze strony komputerów podłączonych do internetu. Bazując na adresie hosta źródłowego, numerze portu źródłowego, adresie hosta docelowego, numerze portu docelowego i rozmiarze pakietu zapora sieciowa decyduje o dopuszczeniu albo niedopuszczeniu tego pakietu do komputera docelowego. Ponieważ liczba potencjalnych kombinacji opisanej postaci jest praktycznie nieskończona, są one uogólniane w postaci ciągu3 reguł: dla każdego pakietu docierającego do zapory analizowane są kolejne reguły w celu znalezienia reguły „pasującej" do tegoż pakietu; gdy taka zostanie znaleziona, stosowana jest akcja określona przez tę regułę (czyli zaakceptowanie albo odrzucenie pakietu). Dla kompletności, ostatnia reguła na liście powinna pasować do dowolnego pakietu — będzie on wówczas określać akcję domyślną dla pakietów, dla których nie stosuje się żadna z pozostałych reguł. W tabeli 7.5 widzimy ciąg reguł dla zapory widocznej na rysunku 7.7. Dwie pierwsze reguły zezwalają dowolnemu hostowi (zlokalizowanemu w internecie lub w intranecie) na dostęp do usług http oferowanych przez serwer W W W oraz na dostarczanie poczty do serwera pocztowego (protokół smtp). Dwie kolejne reguły pozwalają dowolnemu komputerowi znajdującemu się w intranecie na modyfikowanie stron zapamiętanych na serwerze WWW (rsync) i odczytywanie poczty z serwera pocztowego (pop). Ze względy na owe dwie reguły, żaden z hostów zlokalizowanych w internecie nie ma prawa modyfikowania treści w ramach serwera W W W ani odczytywania poczty z serwera pocztowego — i w ogóle korzystania z usług obu tych serwerów (oczywiście, z wyjątkiem usług będących przedmiotem dwóch pierwszych reguł). Dla czytelności i dla celów niezawodności fakt ten został wyartykułowany w postaci trzech następnych reguł blokujących pakiety (wiersze 5 - 7 w tabeli 7.3).

3

„Ciągu", a nie „zbiom", ponieważ istotna jest kolejność stosowania poszczególnych reguł — przyp. tłum.

316

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Rysunek 7.7. Zapora sieciowa filtrująca pakiety: filtr zlokalizowany w routerze decyduje o akceptowaniu lub odrzucaniu poszczególnych pakietów na podstawie zawartej w nich informacji Tabela 7.5. Uproszczony przykład ciągu reguł filtrowania pakietów dla zapory widocznej na rysunku 7.7

Host źródłowy

Host docelowy

Port docelowy

Akcja

serwer W W W

http

zezwól

dowolny

serwer W W W

smtp

zezwól

host w intranecie

serwer W W W

rsync

zezwól

host w intranecie

serwer pocztowy

pop

zezwól

host w internecie

serwer W W W

rsync

zablokuj

host w internecie

serwer pocztowy

pop

zablokuj

host w internecie

host w intranecie

dowolny

zablokuj

dowolny

dowolny

dowolny

zablokuj

dowolny

6

Gdy liczba aktorów jest duża, ciąg reguł filtrujących jest znacznie bardziej zwarty od list kontroli dostępu i list uprawnień; co więcej, niewielki stosunkowo ciąg reguł jest bardziej czytelny i łatwiejszy do zrozumienia (oraz udowodnienia poprawności) przez człowieka, co w przypadku systemu o wysokich wymaganiach w kwestii bezpieczeństwa jest czynnikiem krytycznym. Niezależnie od konkretnej implementacji, macierz dostępu reprezentuje statyczną politykę dostępu. Oznacza to, że reguły dostępu mogą być modelowane w postaci atrybutów obiektów systemu. W naszym przykładowym systemie bankowym przyjrzyjmy się brokerowi, któremu przydzielono zbiór portfolio. Z założenia żaden broker nie ma dostępu do portfolio zarządzanych przez innych brokerów. W tej sytuacji uprawnienia dostępu brokerów do poszczególnych portfolio mają charakter dynamiczny i jako takie muszą być modelowane. Na 6

„Dowolny" oznacza jakikolwiek komputer w internecie lub intranecie, również wspomniany serwer W W W i serwer pocztowy.

7.4. Aktywności realizacji celów projektowych

317

rysunku 7.8 widzimy przykład realizacji' takiego modelu przy użyciu wzorca projektowego Proxy (patrz sekcja A.8 i wspominana już książka E. Gammy i innych [Gamma i in., 1994]). Dla każdego portfolio definiujemy mianowicie chroniący je przed nieuprawnionym dostępem obiekt Portfol ioProxy. Skojarzenie Access między uprawnionym brokerem a obiektem Portfol i oProxy reprezentuje próbę dostępu brokera do tegoż obiektu. Broker w celu uzyskania dostępu do odpowiedniego portfolio wysyła odpowiedni komunikat do proxy chroniącego to portfolio; proxy sprawdza najpierw istnienie skojarzenia ze wspomnianym brokerem i jeśli skojarzenie takie istnieje, komunikat delegowany jest do odpowiedniego portfolio (przy braku skojarzenia komunikat jest blokowany i próba dostępu kończy się niepowodzeniem).

Rysunek 7.8. Dynamiczna kontrola dostępu implementowana za pomocą ochronnego proxy. Klasa skojarzeniowa Access zawiera zbiór operacji wykorzystywanych przez brokera do uzyskania dostępu do konkretnego portfolio. Każda z operacji wywołuje operację i sAccessi bl e ( ) w celu sprawdzenia, czy żądanie dostępu jest legalne, i zależnie od wyniku tego sprawdzenia komunikat wysłany przez brokera jest delegowany do docelowego obiektu Portfol i o lub blokowany. Pojedyncze skojarzenie Access może być używane do kontrolowania dostępu do wielu portfolio (stąd skojarzenie „jeden na wiele")

W obu przypadkach — statycznej i dynamicznej kontroli dostępu — zakładamy, że aktor jest znany, bo jest nim konkretny użytkownik przy klawiaturze komputera lub konkretny podsystem. Proces weryfikacji istnienia skojarzenia między tożsamością użytkownika a systemem nosi nazwę uwierzytelniania. Powszechnie używany mechanizm uwierzytelniania opiera się na podaniu (ogólnie znanej) nazwy użytkownika oraz hasła (znanego tylko użytkownikowi i systemowi, przechowującemu je w listach kontroli dostępu). Zapamiętane w systemie hasła użytkowników zapamiętywane są w postaci zaszyfrowanej i w takiej też postaci konfrontowane są z nimi hasła podawane przez użytkowników przy logowaniu. Jeśli nikt z pozostałych użytkowników nie zna hasła użytkownika logującego się, możemy być pewni autentyczności tego ostatniego. Mimo iż uwierzytelnianie za pomocą haseł można uczynić bezpiecznym przy użyciu współczesnych technologii, jest ono obarczone kilkoma oczywistymi mankamentami. Jeżeli hasło łatwe jest do zapamiętania, może być też łatwe do odgadnięcia przez intruza; hasła skomplikowane, trudne do zapamiętania (i odgadnięcia) użytkownicy zwykli zapisywać i przechowywać w pobliżu komputera (na przykład na kartkach przyczepionych do monitora), co sprawia, że stają się łatwe do przechwycenia przez każdego chętnego. Na szczęście, istnieje wiele innych, bardziej niezawodnych mechanizmów uwierzytelniania, na przykład połączenie tradycyjnych haseł z kartami inteligentnymi — nie wystarczy

318

Rozdział 7. • Projekt systemu: realizacja celów projektowych

znać hasła, trzeba jeszcze posiadać odpowiednią kartę. Kolejnymi zabezpieczeniami mogą być detektory cech biometrycznych: czytniki linii papilarnych czy analizatory gałek ocznych — utożsamienie się pod względem tych wzorców z legalnym użytkownikiem jest dla intruza znacznie trudniejsze niż kradzież karty chipowej. W środowisku, w którym zasoby współdzielone są przez wielu użytkowników, uwierzytelnianie nie zawsze okazuje się wystarczającym zabezpieczeniem. W środowisku sieciowym na przykład łatwo intruzowi podsłuchiwać (za pomocą odpowiednich narzędzi) pakiety generowane przez innych użytkowników (patrz rysunek 7.9). Tak się niedobrze składa, że powszechnie dziś używane protokoły sieciowe, takie jak TCP/IP, nie był)' projektowane ze szczególną troską o bezpieczeństwo: intruz może fabrykować pakietv tak, by wyglądały jak generowane przez legalnych użytkowników.

Rysunek 7.9. Atak bierny: wykorzystując obecną technologię, pasywny intruz może podsłuchiwać cały ruch sieciowy. Aby utrudnić m u działania destruktywne, szyfruje się przesyłaną informację, co sprawia, że staje się trudna do zrozumienia

Żeby utrudnić (uniemożliwić?) intruzowi niecne wykorzystywanie przechwytywanych pakietów, stosuje się szyfrowanie. Algorytm szyfrowania przekształca komunikat jawny („tekst otwarty") w postać zaszyfrowaną („szyfrogram"), która bezużyteczna jest dla intruza nieznającego klucza wykorzystywanego przez ów algorytm (klucz ten jest — oczywiście — znany adresatowi, który odtwarza pierwotną wersję komunikatu). Gdyby intruzowi udało się poznać ów klucz, można go w każdej chwili łatwo zmienić. Bezpieczne uwierzytelnianie i szyfrowanie jest ogólnie trudnym problemem, z którym skutecznie można sobie poradzić, wykorzystując gotowe algorytmy i pakiety zamiast projektowania własnych (chyba że sam tworzony system jest takim właśnie pakietem). Wiele z tych pakietów bazuje na publicznie znanych standardach, zweryfikowanych zarówno w środowiskach akademickich, jak i w zastosowaniach przemysłowych, zapewniających zatem relatywnie wysoki poziom niezawodności i bezpieczeństwa. Wykorzystywanie gotowych i sprawdzonych rozwiązań upraszcza implementowanie specyficznej dla aplikacji polityki bezpieczeństwa, niemniej jednak opracowanie tej polityki i tak jest dość trudnym zadaniem. Pomocnymi w jego rozwiązywaniu mogą okazać się przypadła testowe i scenariusze uwzględniające przynajmniej najbardziej typowe przejawy hakerskiej inwencji intruza. Powrócimy do tej kwestii w następnym rozdziale, przy okazji omawiania systematycznego modelowania podobnych problemów.

7.4. Aktywności realizacji celów projektowych

319

7.4.4. Projektowanie globalnego przepływu sterowania Pod pojęciem przepływu sterowania rozumiemy określoną sekwencję akcji wykonywanych przez system. W systemie obiektowo zorientowanym projektowanie przepływu sterowania polega na określeniu zbioru i kolejności wykonywanych akcji. Decyzje w tym względzie bazują na zdarzeniach zewnętrznych inicjowanych przez aktorów albo na upływie czasu. Przepływ sterowania jest zagadnieniem charakterystycznym dla etapu projektowania; nie istnieje ono na etapie analizy, kiedy to milcząco zakłada się, że każdy obiekt może wykonywać swe operacje w dowolnym czasie, niezależnie od pozostałych obiektów; na etapie projektowania systemu musimy wziąć jednak pod uwagę oczywistą prawdę, że nie każdy obiekt może pozwolić sobie na luksus posiadania własnego procesora. Istnieją trzy główne schematy organizacji przepływu sterowania w systemie. Oto one. • Sterowanie proceduralne. Gdy procedury realizujące operacje potrzebują danych od aktora, zawieszają swe wykonywanie, oczekując na wprowadzenie tych danych. Schemat ten charakterystyczny jest dla większości tradycyjnych systemów, szczególnie stworzonych przy użyciu języków proceduralnych. W przypadku języków obiektowo zorientowanych staje się on istotnym problemem: analizując sekwencje operacji rozproszone między dużą liczbę obiektów, trudno określić sekwencję żądania poszczególnych porcji danych. Przykładowy kod prezentujący sterowanie proceduralne przedstawiamy na listingu 7.1. Listing 7.1. Przykład sterowania proceduralnego (w języku Java): widoczny kod wypisuje kolejne komunikaty i po każdym z nich oczekuje na wprowadzenie danych Stream in, out; String userid, passwd; /* pomijamy czynności inicjacyjne */ out.print1n("Użytkowni k:"); in.readln(userid); out.println("Has?o:"); in.read!n(passwd); i f (!security.check(userid, passw)) { out.println("Nieprawid?owa nazwa użytkownika lub hasło."); system.exit(-l);

} / * •••*/

• Sterowanie zdarzeniowe. W ramach tego schematu (patrz listing 7.2) w głównej pętli powtarzany jest cykl obejmujący oczekiwanie na (dowolne) zdarzenie i po jego wystąpieniu przekazanie go do obsługi przez odpowiednie obiekty, określone na podstawie zawartości komunikatu informującego o wspomnianym wystąpieniu. Zaletą takiego rozwiązania jest scentralizowanie wprowadzania informacji wejściowej w pojedynczej, głównej pętli, co prowadzi do prostszej struktury programu. Bardziej skomplikowane staje się natomiast implementowanie wielokrokowych sekwencji.

320

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Listing 7.2. Przykład sterowania zdarzeniowego (w języku Java). W każdym obrocie głównej pętli ze strumienia eventStream pobierany jest komunikat związany z kolejnym zdarzeniem i przekazywany do obiektów zainteresowanych wystąpieniem tego zdarzenia Iterator subscribers, eventStream; Subscriber subscriber; Event event; EventStream eventStream; /* ... */ w h i l e (eventStream.hasNext()) { event = eventStream.next(); subscribers = dispatchlnfo.getSubscribers(event); w h i l e (subscribers.hasNext()) { subscriber = subscribers.next()) { subscriber.process(event);

} } /* -

V

• Sterowanie wielowątkowe. Ten schemat jest współbieżną odmianą sterowania proceduralnego. System może utworzyć dowolną liczbę wątków, z których każdy związany będzie z określonym zdarzeniem. Gdy wątek potrzebuje określonych danych, zatrzymuje się w oczekiwaniu na ich dostarczenie przez konkretnego aktora (patrz listing 7.3). Schemat wielowątkowy jest najbardziej intuicyjny spośród opisywanych tutaj, wymaga jednak specjalizowanych narzędzi do debugowania; ponadto niedeterminizm wprowadzany przez wywłaszczanie wątków znacznie utrudnia projektowanie i przeprowadzanie testów. Listing 7.3. Przykład wielowątkowej obsługi zdarzeń (w języku Java). Obiekt eventHandl er, dedykowany obsłudze zdarzeń, posiada metodę run (), wywoływaną w rezultacie uruchomienia nowego wątku Thread thread; Event event; EventHandler eventHandl er; boolean done;

/ * •••*/ while ('.done) { event = eventStream.getNextEvent(); eventHandler = new EventHandler(event) thread = new Thread(eventHandler); thread.start();

} * j

Sterowanie proceduralne jest bardzo wygodne z punktu widzenia testowania podsystemów, łatwe staje się bowiem wywoływanie poszczególnych metod oferowanych przez podsystem. Docelowo jednak należy unikać jego stosowania.

7.4. Aktywności realizacji celów projektowych

321

Wybór między sterowaniem zdarzeńiowym a sterowaniem wielowątkowym jest już bardziej problematyczny. Sterowanie zdarzeniowe jest mechanizmem bardziej dojrzałym niż wielowątkowość: nowoczesne języki programowania dopiero od niedawna oferują wsparcie dla programowania wielowątkowości, a narzędzia służące do debugowania aplikacji wielowątkowych pozostawiają wciąż jeszcze wiele do życzenia. Ponadto wiele pakietów dedykowanych interfejsowi użytkownika zrealizowano w technice sterowania zdarzeniami i taki też schemat sterowania wymuszają one na całym podsystemie. Mimo iż sterowanie wielowątkowe jest bardziej intuicyjne, związane z nim wciąż problemy dotyczące głównie debugowania i testowania sprawiają, że jest ono raczej melodią przyszłości i obecnie preferowane jest sterowanie zdarzeniowe. Po wyborze odpowiedniego schematu sterowania przychodzi kolej na jego realizację za pomocą jednego lub kilku obiektów sterujących. Ich zadaniem jest rejestrowanie zdarzeń zewnętrznych, tymczasowe przechowywanie informacji o ich stanie oraz inicjowanie właściwej sekwencji operacji wykonywanych przez obiekty brzegowe i obiekty encji związane z poszczególnymi zdarzeniami. Zlokalizowanie w pojedynczym obiekcie decyzji o wyborze schematu sterowania dla przypadku użycia nie tylko daje w wyniku bardziej zrozumiały kod, lecz także czyni sam system bardziej elastycznym z perspektywy przyszłych zmian w implementacji przepływu sterowania.

7.4.5. Identyfikowanie usług Omówiliśmy już kluczowe decyzje wpływające na kształt dekompozycji systemu: zidentyfikowaliśmy główne podsystemy i 2yskaliśmy ogólne wyobrażenie o odpowiedzialności każdego z nich. Obecnie dokonamy kolejnego udoskonalenia wspomnianej dekompozycji, identyfikując usługi świadczone przez każdy z podsystemów. Rozpatrzymy każdą zależność między podsystemami i dla każdej zidentyfikowanej usługi zdefiniujemy odpowiedni interfejs (reprezentowany przez „lizak" notacji UML)7. W następnym etapie — etapie projektowania obiektów — określimy precyzyjnie każdą usługę w kategoriach operacji, parametrów i ograniczeń (patrz rozdział 9. „Projektowanie obiektów: specyfikowanie interfejsów"). Skupiając się na zależnościach między podsystemami, sprecyzujemy odpowiedzialność każdego z podsystemów, odnajdziemy przeoczenia w aktualnej postaci dekompozycji i zweryfikujemy obecną architekturę aplikacji. Skupiając się na usługach (a nie na atrybutach czy operacjach), pozostaniemy na architektonicznym szczeblu abstrakcji, co pozwoli uniknąć większych zmian w modelu w razie zmian w przyporządkowywaniu odpowiedzialności poszczególnym podsystemom. Zaczniemy od interfejsu dla podsystemu Communi c a t i onSubsystem systemu MyTri p. Zadaniem tego podsystemu jest transmitowanie kompletnych tras z podsystemu Planning •-•Subsystem do podsystemu Routi ngSubsystem. Transmitowanie to następuje z inicjatywy podsystemu Routi ngSubsystem, który uruchamiany jest i zamykany selektywnie, w przeciwieństwie do aktywnego cały czas podsystemu PI anni ngSubsystem; asymetria ta prowadzi do zdefiniowania trzech następujących interfejsów (patrz rysunek 7.10):

7

Patrz sekcja 6.3.2 — przyp.

tłum.

322

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Rysunek 7.10. Efekt kolejnego udoskonalenia dekompozycji systemu w wyniku zidentyfikowania usług oferowanych przez poszczególne podsystemy. Podsystem Communi c a t i onSubsystem dostarcza trzy usługi związane z zarządzaniem połączeniem oraz przesyłaniem tras (w obu kierunkach)

• Connecti onManager — umożliwia podsystemom rejestrowanie się w podsystemie Communi cati onSubsystem w powiązaniu z uwierzytelnianiem, odnajdywanie innych podsystemów oraz nawiązywanie i zamykanie połączeń, • TripRequester — udostępnia listę dostępnych tras i umożliwia pobieranie tras wybieranych z tej listy, • Tri pProvi der — dostarcza listę tras dostępnych dla zalogowanego użytkownika i realizuje żądania udostępnienia wybranej trasy. Mimo iż nie zdefiniowaliśmy jeszcze żadnej operacji podsystemu Communi c a t i on •-•Subsystem, określenie wymienionych usług dostarcza nowe szczegóły umożliwiające zidentyfikowanie przeoczonych dotąd aspektów funkcjonalnych, między innymi: • Czy podsystem Routi ngSubsystem powinien mieć możliwość dostarczania podsystemowi PI anni ngSubsystem informacji statystycznych na temat realizowanych tras (średnia prędkość poruszania się, lokalizacje częstych „korków" i tym podobne) do przyszłego użytku? • Czy podsystem Communi cati onSubsystem powinien przesyłać wyłącznie kompletne trasy, czy też powinien zapewniać możliwość przesyłania także pojedynczych segmentów? Powyższe pytania prowadzą do kolejnych decyzji projektowych. Do podsystemu Communi c a t i onSubsystem dołączamy usługę wysyłania do systemu PI anni ngSubsystem danych zbieranych w czasie rzeczywistym.. Interfejs podsystemu Communi cati onSubsystem jest wystarczająco elastyczny, by poprzez odpowiednią strukturalizację zapewnić dowolną politykę pobierania tras.

7.4. Aktywności realizacji celów projektowych

323

Zwróćmy uwagę na konwencję nazewnictwa usług — wybraliśmy do tej roli rzeczowniki (na przykład Tri pRequester) zgodne z interfejsem obejmującym zarówno atrybuty, jak i operacje. Operacje opatrywane są nazwami w postaci czasowników, rozpoczynającymi się od małej litery (na przykład r e q u e s t T r i p ( ) ) . Nazwy atrybutów są rzeczownikami i rozpoczynają się z małej litery (na przykład connecti onStatus). Rozpoznanie zależności między podsystemami i zidentyfikowanie odpowiadających im usług daje wystarczające zrozumienie pracy systemu w stanie ustalonym, zajmijmy się zatem jego stanami granicznymi, między innymi uruchamianiem i zamykaniem.

7.4.6. Identyfikowanie warunków granicznych W poprzednich sekcjach zajmowaliśmy się projektowaniem i doskonaleniem dekompozycji systemu. Określiliśmy kształt tej dekompozycji, rozdział przypadków użycia pomiędzy poszczególne podsystemy, strategię przechowywania danych oraz mechanizmy kontroli dostępu i zabezpieczeń. Brakuje wciąż rozpoznania warunków granicznych pracy systemu, czyli sposobu jego uruchamiania, inicjowania jego pracy i zamykania, a także radzenia sobie z różnymi sytuacjami wyjątkowymi w rodzaju uszkodzenia danych czy przerwania połączenia sieciowego, niezależnie od przyczyny, którą może być błąd w oprogramowaniu lub zwykłe wyłączenie zasilania. Przypadki użycia odzwierciedlające takie warunki nazywać będziemy granicznymi przypadkami użycia. Powróćmy do systemu MyTri p: mamy już znakomity obraz jego funkcjonowania, nie powiedzieliśmy jednak ani słowa na temat tego, jak rozpoczyna swą pracę. Mówiliśmy o mapach przechowywanych w bazie usługi PI anni ngServi ce — jak znalazły się w tej bazie? Jak system instalowany jest w komputerze pokładowym samochodu? W jaki sposób identyfikuje usługę PI anni ngServi ce, z którą ma się połączyć? W jaki sposób nowi kierowcy rejestrują się w jego bazie? W taki oto sposób odkrywamy kolejne braki w zestawie przypadków użycia. Jest powszechną praktyką, że graniczne przypadki użycia nie są w ogóle rozpatrywane na etapie analizy, a w najlepszym razie traktowane są oddzielnie od „normalnych" przypadków użycia. Przykładowo wiele funkcji z zakresu administrowania systemem — definiowanie nowych użytkowników, definiowanie reguł kontroli dostępu — można zidentyfikować na podstawie codziennych wymagań użytkowników, podczas gdy wiele innych funkcji to konsekwencje decyzji projektowych (rozmiary cache, lokalizacja serwera bazy danych i serwera zapasowego) niemających bezpośredniego związku z wymaganiami użytkowników. Generalnie identyfikację granicznych przypadków użycia rozpoczyna się od przeanalizowania wszystkich obiektów trwałych, w poszczególnych podsystemach, pod kątem wymienionych niżej własności. • Konfigurowanie. Dla każdego obiektu trwałego identyfikujemy przypadki użycia, w których jest on tworzony, niszczony lub archiwizowany. Dla obiektów, które nie są tworzone ani niszczone w ramach „normalnych" przypadków użycia, takich jak mapy (Map) w systemie MyTri p, definiujemy nowe przypadki użycia inicjowane przez administratora systemu (czego przykładem może być przypadek ManageMaps w systemie MyTri p).

324

Rozdział 7. • Projekt systemu: realizacja celów projektowych

o Uruchamianie i zamykanie. Dla każdego komponentu (na przykład Webserver) definiujemy trzy przypadki użycia odzwierciedlające jego uruchamianie, zamykanie i konfigurowanie. Jak pamiętamy, w ramach jednego przypadku użycia można modelować zachowanie się całej grupy ściśle powiązanych komponentów. • Obsługa sytuacji wyjątkowych. Dla każdego rodzaju możliwych awarii komponentu (na przykład zerwania połączenia sieciowego) decydujemy, jak system ma na tę awarię reagować (na przykład — powiadamiać użytkownika o zaistniałej sytuacji). Każdą z tych decyzji dokumentujemy w postaci przypadku użycia, wzbogacającego zestaw „normalnych" przypadków zidentyfikowanych na etapie zbierania wymagań. Zauważmy jednak, że wymaganie odporności na awarie może skutkować zmianą w projekcie systemu zamiast definiowania kolejnego przypadku użycia — przykładowo podsystem Routi ngSubsystem stanie się odporny na zanik połączenia sieciowego, jeśli kompletna trasa pobranie zostanie z podsystemu PI anni ngSubsystem przed rozpoczęciem podróży. Ogólnie rzecz biorąc, zdarzeniem wyjątkowym jest błąd zaistniały w czasie wykonywania systemu. Zdarzenia takie mogą być powodowane trzema podstawowymi przyczynami. Oto one. • Awaria sprzętu. Sprzęt starzeje się i od pewnego momentu zaczyna być coraz bardziej zawodny; awaria dysku twardego może prowadzić do nieodwracalnej utraty danych, awaria routera może spowodować nagłe zerwanie połączenia. • Zmiany w środowisku operacyjnym. Praca systemu uzależniona jest od wielu elementów jego środowiska. System mobilny może utracić połączenie z serwerem, gdy antena modemu znajdzie się poza zasięgiem nadajnika; awaria zasilania może momentalnie unieruchomić system, jeśli komputer pozbawiony jest możliwości natychmiastowego, automatycznego przełączenia się na zasilanie bateryjne. • Błędy w oprogramowaniu. Taki błąd, nawet gdy występuje tylko w obrębie jednego komponentu, może potencjalnie załamać cały system. Mimo iż całkowite wyeliminowanie błędów z oprogramowania jest niedoścignionym ideałem, można jednak uodpornić każdy podsystem na przewidywane błędy, które potencjalnie mogłyby wystąpić w nim i w pozostałych podsystemach. Obsługa sytuacji wyjątkowych to sposób traktowania takich sytuacji przez system i mechanizm realizujący owo traktowanie. I tak w przypadku wprowadzenia przez użytkownika błędnych danych najbardziej rozsądną reakcją wydaje się wyświetienie komunikatu wyjaśniającego użytkownikowi przyczynę błędu i zachęcającego do ponownego wprowadzenia danych, tym razem poprawnych. W przypadku awarii sieci system może zachować informację o swym bieżącym w stanie, by wznowić pracę, gdy sieć ponownie zacznie prawidłowo funkcjonować. Rozpatrzmy jako przykład system nawigacyjny samochodu, pobierający na żądanie informacje z centralnego komputera; gdy samochód wjedzie do tunelu, transfer informacji może zostać przerwany, w wyniku czego warstwa sieciowa8 nie będzie mogła sformować kolejnego pakietu, co zasygnalizuje przez zgłoszenie wyjątku („niespodziewane zamknięcie gniazda"). 8

Poszczególne warstwy sieciowego modelu odniesienia OSI-ISO przedstawione są na rysunku 6.10 — przyp. tłum.

7.4. Aktywności realizacji celów projektowych

325

Warstwa transportowa (korzystająca z usług warstwy sieciowej) może wówczas propagować ów wyjątek do wyższych warstw łub próbować poradzić sobie z zaistniałą sytuacją (bez wiedzy warstw wyższych), na przykład podejmując kilka prób ponowienia transmisji w pewnych odstępach czasu; w międzyczasie operować będzie na poprzednio otrzymanych danych. Programiści, identyfikując warunki graniczne, analizują możliwe awarie każdego komponentu i określają sposób ich obsługi: mogą zaprojektować komponent tak, by samodzielnie radził sobie ze „swymi" awariami albo uwzględnić możliwość sygnalizowania przezeń awarii korzystającym z niego komponentom — na tę drugą okoliczność należy skonstruować odpowiedni graniczny przypadek użycia. Zauważmy, że na etapie projektowania systemu rozważamy awarie jedynie na poziomie komponentów; w rozdziale 9. „Projektowanie obiektów: specyfikowanie interfejsów" zajmiemy się obsługą sytuacji wyjątkowych na poziomie poszczególnych obiektów. Projektowanie niezawodnych systemów jest trudną sztuką, którą można jednak do pewnego stopnia upraszczać, ograniczając nieco funkcjonalność docelowego systemu. Przykładowo w systemie MyTri p sformułowaliśmy wymagania, by połączenie komputera pokładowego z serwerem było zawsze możliwe przy rozpoczynaniu podróży oraz by zmiana planowanej trasy możliwa była tylko w warunkach istnienia tego połączenia. Wprowadzenie granicznych przypadków użycia (patrz rysunek 7.11) do modelu systemu MyTri p stanowi kolejną modyfikację tego systemu. Dodaliśmy trzy przypadki użycia: Manage "-•Drivers obejmujący dodawanie, modyfikowanie i usuwanie profili kierowców, ManageMaps związany z dodawaniem, modyfikowaniem i usuwaniem map używanych do konstruowania tras i ManageServer ilustrujący rutynowe konfigurowanie, uruchamianie i zamykanie systemu. W tabeli 7.6 przedstawiamy przypadek użycia StartServer, dołączany jako część przypadku ManageServer.

Rysunek 7.11. Administracyjne przypadki użycia dla systemu MyTri p. Przypadek użycia ManageDrivers wywoływany jest w celu dodawania, usuwania lub modyfikowania danych dotyczących kierowców (nazw, haseł, historii transakcji, generowanych kluczy szyfrowania), przypadek ManageMaps — w celu dodawania, usuwania i modyfikowania m a p służących do generowania tras, natomiast przypadek ManageServer obejmuje wszelkie funkcje związane z uruchamianiem i zamykaniem serwera

326

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Tabela 7.6. Przypadek użycia StartServer systemu MyTri p Nazwa przypadku

użycia

StartServer

Warunek

wstępny

1. Aktor PI anni ngServi ceAdmi ni s t r a t o r zalogowany jest do serwera systemu.

Przepływ

zdarzeń

2. Aktor PI anni ngServi ceAdmi ni s t r a t o r wydaje polecenie s t a r t P l a n n i ngServi ce. 3. Jeśli poprzednie wykonywanie usługi PI anni ngServi ce zakończyło się normalnie, serwer odczytuje listę zarejestrowanych kierowców (Dri v e r ) oraz indeks m a p (Map) i aktywnych tras (Tri p). Jeśli p o p r z e d n i e wykonywanie usługi PI anni ngServi ce zakończyło się awaryjnie, system i n f o r m u j e o tym fakcie a d m i n i s t r a t o r a (PI anni ngServi ceAdmi ni s t r a t o r ) i rozpoczyna sprawdzanie spójności danych w bazie MapDBStore.

Warunek

końcowy

4. Usługa PI anni ngServi ce jest aktywna i oczekuje na połączenia ze strony instancji podsystemu Routi ngSubsystem.

Rewizja modelu przypadków użycia, w postaci dodania trzech przypadków, nie wpłynęła na dekompozycję systemu, dodaliśmy jednak nowe przypadki użycia do istniejących podsystemów: podsystem MapDBStoreSubsystem obarczony został obowiązkiem sprawdzania, czy jego poprzednie użytkowanie zakończyło się poprawnym zamknięciem — jeżeli nie, musi on wykonać kolejne zadanie: sprawdzić spójność danych i w razie potrzeby przywrócić ich poprawną postać. Oczywiście, fakt ten znajduje swe odzwierciedlenie w opisie podsystemu (patrz tabela 7.7). Tabela 7.7. Zrewidowany opis podsystemu MapDBStoreSubsystem, stosownie do nowego przypadku użycia S t a r t S e r v e r (patrz tabela 7.6). Zmiany wyróżniono kursywą MapDBStoreSubsystem

Podsystem odpowiedzialny za przechowywanie map i tras w bazie p o d s y s t e m u PI anni ngSubsystem. Podsystem ten zdolny jest do równoczesnej obsługi wielu kierowców i agentów planowania. Podczas rozpoczynania pracy podsystem sprawdza, czyjego poprzednie użycie zakończyło się poprawnym zamknięciem —jeśli nie, dokonuje sprawdzenia spójności map (MapJ i tras (Tri p) i w razie potrzeby podejmuje działania naprawcze.

7.4.7. Weryfikowanie projektu systemu Podobnie jak analiza wymagań, tak i projektowanie systemu jest aktywnością ewolucyjną i iteratywną. W przeciwieństwie jednak do analizy wymagań, w projektowaniu systemu nie uczestniczy żaden zewnętrzny agent, weryfikujący kolejne iteracje i przyczyniający się do ciągłej poprawy jakości produktów. Sukcesywna poprawa jakości jest jednak z oczywistych powodów jak najbardziej pożądana — można ją uzyskiwać poprzez okresową weryfikację projektu, dokonywaną przez programistów przy udziale menedżerów. Istnieje wiele możliwości wyboru substytutu wspomnianego agenta zewnętrznego: rolę tę mogą spełniać programiści niezaangażowani w projektowanie bądź (na zasadzie wzajemności) programiści uczestniczący w innym projekcie. Przedsięwzięcie takie ma jednak szansę powodzenia tylko wówczas, gdy weryfikatorzy faktycznie działają z intencją wykrywania i raportowania problemów.

7.4. Aktywności realizacji celów projektowych

327

W uzupełnieniu do spełnienia zidentyfikowanych celów projektowych musimy także upewnić się, że model projektu systemu jest poprawny, kompletny, spójny, realistyczny i czytelny. Model projektu systemu jest poprawny, jeśli jest odwzorowaniem modelu analitycznego, o czym możemy przekonać się, zadając następujące pytania: • Czy istnienie każdego podsystemu da się uzasadnić przez przypadek użycia lub wymaganie pozafunkcyjne? • Czy każdy przypadek użycia może być odwzorowany w podzbiór zdefiniowanych podsystemów? • Czy każdy cel projektowy można wywieść z wymagania pozafunkcyjnego? • Czy każde wymaganie pozafunkcyjne zostało uwzględnione w modelu projektu systemu? • Czy dla każdego aktora zdefiniowano zasady kontroli dostępu? • Czy wszystkie zasady kontroli dostępu spójne są z wymaganiami pozafunkcyjnymi dotyczącymi bezpieczeństwa i zabezpieczeń? Model jest kompletny, jeśli uwzględnia wszystkie wymagania funkcyjne i pozafunkcyjne oraz wszelkie problemy zauważone w trakcie projektowania. Następujące pytania pomogą nam ocenić kompletność modelu: • Czy obsługiwane są wszystkie warunki graniczne? • Czy dokonany został przegląd („wędrówka") wszystkich przypadków użycia w celu zidentyfikowania braków funkcjonalnych w modelu projektu systemu? • Czy każdy przypadek użycia został dostatecznie zweryfikowany i czy w każdym z nich występuje przynajmniej jeden obiekt sterujący? • Czy w modelu uwzględniono wszystkie aspekty projektowania systemu (odwzorowanie komponentów w węzły sprzętowe, przechowywanie obiektów trwałych, kontrolę dostępu, wykorzystanie istniejącego kodu, warunki graniczne)? • Czy wszystkie podsystemy zostały jasno zdefiniowane? Model jest spójny, jeśli jest niesprzeczny, co sprowadza się do następujących kwestii: • Czy konfliktujące cele projektowe zostały opatrzone różnymi priorytetami? • Czy żaden z celów projektowych nie jest sprzeczny z wymaganiami pozafunkcyjnymi? • Czy każdy podsystem i każda klasa opatrzone są unikalną nazwą? • Czy kolekcje obiektów wymieniane są między podsystemami w sposób spójny? Model jest realistyczny, jeśli opisywany przez niego system jest możliwy do zaimplementowania. O możliwości tej decydują między innymi następujące kryteria: • Czy w systemie przewidywane jest użycie nowych technologii i (lub) komponentów? Jeśli tak, to czy oceniona została ich adekwatność i solidność? W jaki sposób?

328

Rozdział 7. • Projekt systemu: realizacja celów projektowych

• Czy wymagania dotyczące wydajności i niezawodności zostały przeanalizowane w kontekście dekompozycji systemu? • Czy uwzględnione zostały potencjalne konsekwencje współbieżności (przeciążenia, zastój)? Wreszcie, model jest czytelny, jeśli zrozumiały jest dla programistów nieuczestniczących w jego tworzeniu. Czytelność modelu przekłada się na następujące pytania: • Czy nazwy podsystemów są zrozumiałe? • Czy encje (podsystemy, klasy) o podobnych nazwach reprezentują podobne koncepcje? • Czy wszystkie encje opisane zostały na tym samym stopniu szczegółowości? W przypadku wielu projektów daje się zauważyć częściowe nakładanie się projektowania systemu i jego implementowania; przykładowo w celu oceny i weryfikacji nowych technologii tworzy się prototypy wybranych podsystemów, zanim jeszcze ustabilizuje się architektura systemu. Konsekwencją tego jest wiele przeglądów cząstkowych zamiast pojedynczego przeglądu klienckiego związanego z akceptacją modelu analitycznego. Mimo iż takie podejście sprawia, że projekt staje się bardziej elastyczny, wymaga od programistów bardziej uważnego traktowania otwartych problemów. Rozwiązywanie wielu trudnych problemów odkłada się na później nie dlatego, że są trudne, lecz ze względu na to, iż zdają się burzyć względny ład istniejący w procesie.

7.5. Zarządzanie projektowaniem systemu W tej sekcji przedyskutujemy zagadnienia związane z zarządzaniem aktywnościami etapu projektowania systemu. Podobnie jak w analizie wymagań, podstawowym wyzwaniem tego zarządzania jest utrzymywanie szeroko rozumianej spójności, niezależnie od tego, jak wiele zasobów znajduje się w użyciu. Dzięki owej spójności efektem projektu będzie architektura i zbiór interfejsów jednoznacznie opisujące spójny system, w sposób zrozumiały dla pojedynczej osoby. Rozpoczniemy od opisania szablonu dokumentu przedstawiającego wyniki projektowania (patrz sekcja 7.5.1), po czym zajmiemy się rolami przypisywanymi w związku z projektowaniem systemu (patrz sekcja 7.5.2) i problemami komunikacyjnymi w projektowaniu systemu (patrz sekcja 7.5.3). Następnie omówimy rolę menedżera projektu w kontekście iteracyjnej natury projektowania systemu (patrz sekcja 7.5.4).

7.5.1. Dokumentowanie projektu systemu Rezultaty projektowania systemu udokumentowane zostają w publikacji zwanej dokumentem projektu systemu, w skrócie SDD (System Design Document). Dokument SDD opisuje cele projektowe, dekompozycję systemu (w formie diagramów klas UML), odwzorowanie sprzętowe i programowe komponentów systemu (w postaci diagramów wdrażania UML), zarządzanie danymi, kontrolę dostępu, mechanizmy przepływu sterowania i warunki graniczne. SDD stanowi podstawę do definiowania interfejsów między zespołami programistów i służy jako punkt odniesienia przy wszelkich decyzjach dotyczących poziomu architektury systemu.

7.5. Zarządzanie projektowaniem systemu

329

SDD adresowany jest do menedżerów projektu, architektów systemu (czyli programistów uczestniczących w jego projektowaniu) i programistów implementujących poszczególne podsystemy. Przykładowy szablon SDD przedstawiamy w poniższej ramce. Dokument projektu systemu 1. Wstęp 1.1. Przeznaczenie systemu 1.2. Cele projektowe 1.3. Definicje, akronimy i skróty 1.4. Odwołania 1.5. Podsumowanie 2. Architektura obecnie używanego oprogramowania 3. Architektura proponowana dla nowego oprogramowania 3.1. Streszczenie 3.2. Dekompozycja systemu 3.3. Odwzorowanie sprzętowe i programowe k o m p o n e n t ó w systemu 3.4. Zarządzanie danymi trwałymi 3.5. Kontrola dostępu i bezpieczeństwo 3.6. Globalny przepływ sterowania 3.7. Warunki graniczne 4. Usługi świadczone przez podsystemy 5. Słownik

Pierwsza sekcja dokumentu SDD jest wprowadzeniem. Jej przeznaczeniem jest dostarczenie krótkiego opisu architektury oprogramowania i celów projektowych. Zawiera także odwołania do innych dokumentów (między innymi powiązanego dokumentu RAD) i informację o identyfikowalności (powołanie się na istniejące systemy czy ograniczenia dotyczące architektury). Sekcja druga opisuje architekturę istniejącego systemu, który ma zostać zastąpiony przez tworzony system. Jeśli nowy system tworzony jest od zera, można w tym miejscu zawrzeć przegląd architektur stosowanych w podobnych systemach. Celem tej części jest jawne wyartykułowanie informacji, na jakich opierają się architekci systemu, przyjmowanych przez nich założeń oraz problemów, jakie nowy system ma rozwiązywać. Trzecia sekcja, poświęcona modelowi projektu nowego systemu, podzielona jest na siedem następujących części: • Streszczenie — prezentuje architekturę systemu „z lotu ptaka" i zawiera krótki opis elementów funkcjonalnych każdego podsystemu. • Dekompozycja systemu — tu znajduje się opis podziału systemu na podsystemy i przypisanie zakresu odpowiedzialności do każdego podsystemu; jest to główny produkt etapu projektowania systemu.

330

Rozdział 7. • Projekt systemu: realizacja celów projektowych



Odwzorowanie sprzętowe i programowe komponentów systemu — zawiera opis odwzorowania podsystemów w komponenty sprzętowe i komponenty „z półki", wylicza także problemy związane z rozproszeniem systemu między wiele węzłów i wynikającymi stąd problemami dotyczącymi współbieżności i synchronizacji.

• Zarządzanie danymi trwałymi — opisuje dane zapamiętywane między kolejnymi uruchomieniami systemu i infrastrukturę realizującą zarządzanie tymi danymi — wybór systemu bazy danych, schematy danych i hermetyzację bazy danych wobec innych podsystemów. • Kontrola dostępu i bezpieczeństwo — tu znajduje się opis modelu dostępu do systemu w formie macierzy dostępu, jak również szczegóły systemu zabezpieczeń: mechanizm uwierzytelniania, algorytm szyfrowania, zarządzanie kluczami szyfrującymi i tym podobne. •

Globalny przepływ sterowania — w tej części opisywana jest implementacja globalnej strategii przepływu sterowania, jak też inicjowanie żądań i synchronizowanie działań poszczególnych podsystemów. Ponadto powinien się tu znaleźć również opis problemów związanych ze współbieżnością.



Warunki graniczne — tu opisane są procedury uruchamiania i zamykania systemu oraz jego zachowanie się w sytuacjach wyjątkowych. (Jeśli zidentyfikowane zostały nowe przypadki użycia związane z administrowaniem systemem, powinny zostać dołączone do dokumentu RAD, nie powinno się ich tutaj opisywać).

W sekcji czwartej opisywane są usługi oferowane przez każdy z podsystemów. Mimo iż w pierwszych wersjach dokumentu SDD sekcja ta pozostaje pusta albo niekompletna, stanowi ona punkt odniesienia dla konkretnych zespołów przy ustalaniu granic poszczególnych podsystemów. Na podstawie tej sekcji definiowane są interfejsy podsystemów, opisywane później w dokumencie projektu obiektów. Dokument SDD sporządzany jest po dokonaniu wstępnej dekompozycji systemu — architekci systemu nie powinni czekać z jego opublikowaniem, aż do sformułowania wszystkich decyzji projektowych. Podobnie jak dokument RAD, dokument SDD z chwilą opublikowania zyskuje status „linii bazowej", lecz podlegać będzie rozmaitym zmianom, w miarę odkrywania nowych problemów i podejmowania nowych decyzji — zmiany te podlegają formalnemu procesowi zarządzania konfiguracją, każda wprowadzona zmiana powinna być opatrzona datą i przypisana konkretnemu autorowi.

7.5.2. Przydzielanie odpowiedzialności W przeciwieństwie do analizy wymagań, projektowanie systemu odbywa się w kręgu programistów — klient i użytkownicy schodzą na plan dalszy. Wiele aktywności związanych z projektowaniem systemu powodować może jednak konieczność wprowadzania poprawek do modelu analitycznego, co nie powinno się odbywać bez konsultacji z klientem i użytkownikami. W przypadku typowych, złożonych systemów projektowanie koncentruje się wokół zespołu architektonicznego — jest to zespół międzyfunkcyjny, złożony z architektów definiujących dekompozycję systemu oraz niektórych programistów implementujących ten system.

7.5. Zarządzanie projektowaniem systemu

331

Ważne jest, by projektowaniem systemu zajmowali się ludzie, którzy w naturalny sposób ponosić będą konsekwencje podejmowanych przez siebie decyzji. Zespół architektoniczny rozpoczyna swą pracę, gdy tylko ustabilizuje się model analityczny, i funkcjonuje, aż do fazy integrowania systemu; stanowi to zachętę dla jego członków do przewidywania potencjalnych problemów, jakie pojawić się mogą w związku z tym integrowaniem. Oto kilka najważniejszych ról, jakie przypisane zostają członkom zespołu architektonicznego: • architekt — jest kierownikiem zespołu architektonicznego i pełni główną rolę w projektowaniu systemu, nadzorując zachowanie spójności w obszarze decyzji projektowych i w stylach interfejsów podsystemów. Jest także odpowiedzialny za zachowanie spójnego charakteru projektu pod względem zarządzania konfiguracją oraz zdefiniowanie strategii integrowania systemu. Rola architekta jest rolą typowo integracyjną, opierającą się na informacjach otrzymywanych od zespołów funkcyjnych. • łącznik architektoniczny — tę rolę pełni każdy z członków zespołu architektonicznego, będący reprezentantem zespołu funkcyjnego. Łącznik odpowiedzialny jest za przepływ informacji między zespołem architektonicznym a „swoim" zespołem oraz za negocjowanie zmian w ramach interfejsu podsystemu opracowywanego przez ten zespół. W fazie projektowania systemu łącznicy skupiają się na usługach oferowanych przez podsystemy, w fazie implementowania interesować ich będzie natomiast spójność API. • role redaktora dokumentacji, menedżera konfiguracji i weryfikatora są w fazie projektowania systemu identyczne z rolami na etapie analizy wymagań (patrz sekcja 5.5.2). Liczba podsystemów powstających w wyniku dekompozycji determinuje liczebność zespołu architektonicznego. Dla systemów szczególnie skomplikowanych tworzonych jest kilka zespołów architektonicznych, po jednym dla każdego poziomu abstrakcji. W tym ostatnim przypadku każdy zespół powinien posiadać własnego architekta, niezależnie od tego powinna być wyznaczona rola głównego architekta na szczeblu projektu, by zdefiniowana architektura systemu była spójna i zrozumiała dla pojedynczego człowieka.

7.5.3. Komunikacja w projektowaniu systemu Komunikacja między uczestnikami projektowania systemu nie stanowi już wyzwania tak wielkiego, jak na etapie analizy wymagań: jasny jest już obraz funkcjonalności systemu, uczestnicy mają już podobne wyobrażenie o systemie, no i zdążyli poznać się lepiej. Mimo to, komunikowanie się między nimi wciąż nie jest łatwe za sprawą nowych źródeł komplikacji. Oto one. • Rozmiar. Liczba problemów, z jakimi przychodzi się zmagać, rośnie w miarę postępu w projektowaniu. Zwiększa się liczba elementów, jakimi manipulować muszą programiści: każdy aspekt funkcjonalny wymaga uwzględnienia wielu operacji na wielu obiektach. Dodatkową komplikację stanowi analizowanie wielu projektów i rozpoznawanie szczegółów nowych technologii.

332

Rozdział 7. • Projekt systemu: realizacja celów projektowych

• Zmiany. Dekompozycja systemu i interfejsy poszczególnych podsystemów wciąż intensywnie ewoluują, podobnie jak terminologia używana przez programistów do określenia różnych elementów systemu. Bywa, że wskutek dynamiki tych zmian programiści nie zdążą uzgodnić ze sobą nowych koncepcji, co prowadzi do wielu nieporozumień i kolejnych komplikacji. • Poziom abstrakcji. Dyskusję na temat wymagań można było uczynić wysoce konkretną, dzięki używaniu makiet, prototypów i analogii z istniejącymi systemami. Podobnie konkretna staje się dyskusja na temat implementacji, gdy znane są wyniki integracji i testowania. Dyskutowanie na temat projektowania systemu rzadko bywa tak konkretne z tej prostej przyczyny, że większość konsekwencji podejmowanych decyzji projektowych odczuwana jest dopiero na etapie implementacji i testowania. • Niechęć do konfrontacji z problemami. Wysoki poziom abstrakcji większości dyskusji stwarza okazję do odkładania „na później" rozstrzygania wielu trudnych, konkretnych kwestii — typowym rozwiązaniem problemu bywa wówczas stwierdzenie w rodzaju „wrócimy do tego przy implementowaniu". Mimo iż pożądane jest opóźnianie niektórych decyzji, na przykład dotyczących wyboru konkretnej struktury danych czy konkretnego algorytmu, decyzje mające wpływ na kształt dekompozycji systemu i interfejsy podsystemów nie powinny być odkładane „na później". • Sprzeczne cele i kryteria. Często zdarza się tak, że różni programiści przyjmują odmienne kryteria optymalizacji. Programista doświadczony w pracy nad interfejsami użytkownika postrzegać będzie czas reakcji systemu jako główny cel optymalizacji, podczas gdy dla programisty parającego się tematyką bazodanową najbardziej istotna będzie przepustowość transmisji danych. Tego typu konfliktowe cele, zwłaszcza gdy nie są wyartykułowane jawnie, mogą kierować dekompozycję na różne tory i prowadzić do kłopotliwych niespójności w jej zakresie. Techniki radzenia sobie z opisanymi komplikacjami podobne są do tych, jakie rozważaliśmy w związku z analizą wymagań (patrz sekcja 5.5.3): •

Określenie priorytetów poszczególnych celów projektowych i uczynienie ich publicznie wiadomymi (patrz sekcja 6.4.2). Dzięki temu programiści zaangażowani w projektowanie łatwiej będą mogli zaplanować czas na realizację poszczególnych celów. Zestaw celów projektowych pełni także rolę obiektywnego punktu odniesienia, względem którego oceniane są wszelkie podejmowane decyzje.



Udostępnienie bieżącego kształtu dekompozycji wszystkim zainteresowanym. Jednym ze sposobów udostępnienia tej informacji jest rozpowszechnianie „żywych" dokumentów w internecie lub intranecie. Programiści mogą także kontrolować na bieżąco dokonywane zmiany za pomocą różnych narzędzi dedykowanych zarządzaniu konfiguracją.



Utrzymywanie aktualnego słownika. Podobnie jak w przypadku analizy, oficjalne, publiczne źródło terminologii zmniejsza ryzyko nieporozumień. Każdy zidentyfikowany podsystem powinien, oprócz sugestywnej nazwy, posiadać także jasną i zwięzłą definicję — diagramy UML, które z konieczności ograniczają się tylko do nazw

7.5. Zarządzanie projektowaniem systemu

333

podsystemów, nie są wystarczającym źródłem efektywnej komunikacji. Podobnymi definicjami powinny być opatrywane wszystkie klasy wchodzące w skład poszczególnych podsystemów. • Konfrontacja z problemami projektowymi. Odkładanie decyzji projektowych „na później" może być korzystne w sytuacji, gdy do podjęcia konkretnej decyzji potrzebne jest uzyskanie dodatkowych informacji. Niestety, ta zasada często wykorzystywana bywa jako pretekst do ucieczki przed trudnymi problemami projektowymi. Zatem przed decyzją o odłożeniu konkretnego problemu na później, warto przeanalizować znane warianty jego rozwiązania oraz wpływ na dekompozycję. • Iterowanie. Wybiórcze „wybieganie w przód", w fazę implementacji, może przyczynić się do usprawnienia projektu systemu. Przykładowo nowe funkcje, jakie pojawiły się w nowej wersji komponentu „z półki", mogą zostać ocenione przez zaimplementowanie „pionowego prototypu" (patrz sekcja 7.5.4) pod kątem ich przydatności w obecnym projekcie. Niezależnie od tego, jak starannie i jakim kosztem wykonany został projekt systemu, kształt dekompozycji systemu i interfejsy podsystemów prawie na pewno zmieniać się będą w fazie implementacji. Przyczyną tego będzie nie tylko coraz lepsze rozumienie systemu przez programistów i odkrywane w związku z tym możliwości ulepszenia projektu, lecz także pojawienie się nowych technologii i nowych pakietów zawierających gotowe rozwiązania. Programiści powinni być świadomi tego faktu i muszą zarezerwować sobie trochę czasu na uaktualnienie dokumentu SDD przed przystąpieniem do integrowania systemu.

7.5.4. Iteracje projektowania systemu Podobnie jak wymagania, także projekt systemu doznaje kolejnych przeobrażeń, iteracji i zmian. Owe zmiany powinny być jednak kontrolowane, w celu uniknięcia chaosu, tym bardziej prawdopodobnego, im bardziej złożony jest projekt i im większą angażuje liczbę uczestników. W fazie projektowania systemu wyróżnić możemy trzy rodzaje iteracji. Pierwszy z nich odnosi się do iteracji związanych z ogólnymi decyzjami na samym początku procesu. Drugi charakteryzuje iteracje związane z interfejsami podsystemów, wynikające z nowej wiedzy zdobywanej w związku z oceną prototypów, iteracje trzeciego rodzaju wynikają natomiast z błędów i przeoczeń odkrywanych w późniejszych fazach i wymagających powrotu do definiowania interfejsów, a często i nawet samej dekompozycji. Iteracje pierwszego rodzaju najefektywniej realizowane są w formie „burzy mózgów" prowadzonej bądź to w formie osobistego spotkania uczestników, bądź też w formie konferencji internetowej. Definicje związane z systemem są wówczas wysoce płynne, programiści nie mają jeszcze dobrego wyobrażenia o systemie jako całości, zatem sprawna komunikacja staje się najważniejsza, ważniejsza niż formalności i procedury. W zespołowej organizacji projektu wstępna dekompozycja systemu dokonywana jest dość wcześnie, zanim jeszcze zakończy się budowanie modelu analitycznego. Kształt tej dekompozycji jest wówczas o tyle istotny, iż staje się podstawą do przydzielania zakresu odpowiedzialności poszczególnym zespołom. Wszechstronna eksploracja problemów i niezbędne zmiany są wtedy jak najbardziej pożądane, choćby tylko dla lepszego zrozumienia kolejnych szczegółów systemu i bieżącej postaci projektu. Spontaniczny charakter tych iteracji czyni zbędną jakąkolwiek biurokrację.

334

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Iteracje drugiego rodzaju zmierzają do rozwiązywania trudnych i dobrze zdefiniowanych problemów, takich jak na przykład wybór odpowiedniej technologii czy konkretnego dostawcy. Dekompozycja systemu jest już raczej ukształtowana — w idealnym przypadku niezależna jest od konkretnego dostawcy o konkretnej technologii — i większość wątpliwości związana jest wówczas z przydatnością określonych pakietów czy komponentów dla tworzonego systemu. W tym okresie programiści często tworzą „pionowe prototypy" 9 w celu zweryfikowania owej przydatności. Umożliwia to wczesne wykrywanie problemów związanych z przepływem sterowania. Ponownie zbytnia formalizacja procesu nie jest tu wskazana, wystarczająca okazuje się lista otwartych problemów wraz z informacjami o statusie każdego z nich. Iteracje trzeciego typu stanowią remedium na problemy projektowe wykrywane w późnej fazie projektu. Mimo iż programiści woleliby takich iteracji raczej unikać, jako że są kosztowne i stanowią ryzyko wprowadzenia wielu błędów do systemu, to jednak muszą być na nie przygotowani. Przygotowanie to wyraża się między innymi w postaci starannego dokumentowania zależności między podsystemami, racjonalności kryjącej się za ich interfejsami oraz ewentualnych rozwiązań „na skróty", które prawdopodobnie okażą się niepoprawne po wprowadzeniu zmian. Wszelkie zmiany powinny być kategorycznie zarządzane, w formie procesu podobnego do śledzenia zmian w specyfikacji wymagań. Postępującą stabilizację dekompozycji systemu łatwiej osiągnąć, wprowadzając koncepcję „okna projektowego". „Okno" to oznacza dopuszczalny przedział czasu, przez który kluczowe problemy mogą pozostawać nierozwiązane. Przykładowo wybór platformy sprzętowej i programowej dla nowego systemu powinien być dokonany jak najwcześniej, tak aby decyzje dotyczące zakupu sprzętu i oprogramowania mogły zostać podjęte i wykonane w czasie realizacji projektu. Problemy dotyczące wyboru określonego algorytmu czy konkretnej struktury danych można jednak pozostawić otwarte, aż do czasu integrowania podsystemów, kiedy to programiści będą mogli je skutecznie rozwiązać, opierając się na wynikach testowania wydajności. Zanim „okno" dla konkretnego problemu zostanie zamknięte, problem ten musi zostać rozwiązany, a powrót do niego nastąpić może dopiero w następnej iteracji. W warunkach szybkich innowacji technologicznych wiele związanych z nimi zmian można lepiej przewidywać, gdy w firmie istnieje dział dedykowany zarządzaniu technologiami. Menedżerowie technologiczni śledzą wówczas zmiany na rynku technologii, oceniają je i gromadzą wiedzę, która okaże się użyteczna, gdy przyjdzie wyboru komponentów. Często zmiany technologiczne następują tak szybko, iż firmy nie są ich do końca świadome.

7.6. Analiza przypadku — system ARENA Pokażemy teraz, jak opisywane w tym rozdziale metody i koncepcje zastosować można do systemu ARENA. Rozpoczniemy od zidentyfikowania celów projektowych dla tego systemu i dokonamy jego wstępnej dekompozycji. Potem dokonamy wyboru sprzętu i oprogramowania oraz zdefiniujemy strategię przechowywania trwałych danych, założenia kontroli dostępu i globalną strukturę sterowania. Na końcu przyjrzymy się warunkom granicznym systemu ARENA. 9

Prototyp pionowy (wertykalny) to kompletna implementacja wąskiego wycinka funkcjonalności, zwykle związanej z określonym przypadkiem użycia i jego obiektami brzegowymi, encji i sterującymi. Prototyp poziomy (horyzontalny) jest natomiast częściową implementacją szerokiego zakresu funkcjonalności, na przykład implementacją jedyne obiektów brzegowych obszernego zbioru przypadków użycia.

7.6. Analiza przypadku — system ARENA

335

7.6.1. Identyfikowanie celów projektowych Cele projektowe określają zróżnicowanie priorytetów poszczególnych cech systemu. Wywodzą się z wymagań pozafunkcyjnych, zdefiniowanych w czasie zbierania wymagań oraz z celów technicznych i menedżerskich określonych przez projekt. Głównym klientem systemu ARENA jest ArenaOperator, dostarczający środki dla udostępnienia tego systemu określonej społeczności. Jest on graczem (PI ayer) posiadającym kwalifikacje administrowania systemem czy nawet kwalifikacje programisty. Możliwość reklamowania własnych produktów daje mu częściową rekompensatę kosztów związanych z systemem. Przewidujemy ponadto, że poszczególni aktorzy ArenaOperator będą tworzyć własne społeczności i integrować z systemem ARENA nowe gry, jak również zgłaszać zapotrzebowanie na sukcesywne ulepszenia systemu. Reklamy i ogłoszenia nie są jednak zasadniczym przeznaczeniem systemu ARENA. Na podstawie powyższych obserwacji oraz na podstawie deklaracji problemu dla systemu ARENA identyfikujemy następujące cele projektowe. • Niskie koszty operacyjne. Aby zmniejszyć zapotrzebowanie na reklamy ze strony klienta (ArenaOperator) jako środek rekompensowania jego kosztów operacyjnych (związanych z infrastrukturą sprzętową, siecią, administrowaniem i tym podobnymi), należy dążyć do tego, by koszty te były jak najmniejsze. Prowadzi to do wyboru komponentów darmowych lub kategorii open source. Ten cel projektowy stanowi rozwinięcie wymagania pozafunkcyjnego „Niski koszt użytkowania". • Duża dostępność. Wartość systemu ARENA mierzona jest liczbą graczy chętnych do uczestniczenia w turniejach. Nieoczekiwane awarie systemu czy niespodziewane przerwanie pasjonującego pojedynku — zdecydowanie nie są to przyjemne dla gracza doświadczenia, zachęcające do udziału w kolejnych turniejach. Ten cel projektowy nie znajduje swej genealogii w deldaracji problemu, lecz jest niezbędny i oczywisty, jeśli system ARENA zachowywać ma swą atrakcyjność i zdobywać nowe rzesze graczy. • Skalowalność pojmowana w kategoriach liczby graczy i równoczesnego prowadzenia wielu turniejów. Reaktywność systemu ARENA nie może pogarszać się w zauważalnym stopniu wraz z rosnącą liczbą równocześnie połączonych graczy (Player). Arena "-•Operator powinien mieć w związku z tym możliwość zwiększenia mocy systemu poprzez dodanie nowych węzłów sprzętowych, gdy zdarzy się taka potrzeba. Ten cel projektowy stanowi rozwinięcie wymagania pozafunkcyjnego „Skalowalność" w deklaracji problemu. • Łatwość dodawania nowych gier. Niektóre gry, takie jak szachy, są rozrywką ponadczasową. Tradycyjne rozrywki uzupełniane są rosnącą dynamicznie ofertą rozmaitych gier komputerowych, ewoluujących w stronę różnych stylów i różnych platform sprzętowych. By system ARENA mógł dotrzymać kroku tej tendencji, musi być adaptowalny do nowych warunków i powinien umożliwiać łatwe instalowanie nowych gier. Ten cel projektowy stanowi rozwinięcie wymagania pozafunkcyjnego „Rozszerzalność" w deklaracji problemu. •

Udokumentowanie systemu ARENA dla programistów społeczności open source. Sporządzenie dokumentacji systemu ARENA ułatwi niezależnym programistom wnoszenie nowych cech do kodu i ulepszanie go. Wspomniana dokumentacja powinna

336

Rozdział 7. • Projekt systemu: realizacja celów projektowych

obejmować zarówno kod źródłowy systemu (w celu umożliwienia zmian i usprawnień o charakterze niskopoziomowym), jak również jego architekturę (co ułatwi dodawanie nowych cech). Ten cel projektowy postulowany jest przez programistów i menedżerów projektu ARENA, nie przez klienta; zauważmy jednak, że tego rodzaju cele projektowe wymagają uzgodnień z klientem, mogą bowiem kolidować z domyślnymi, choć jeszcze wyraźnie niewyartykułowanymi celami klienta.

7.6.2. Identyfikowanie podsystemów Identyfikując podsystemy systemu ARENA, oprzemy się najpierw na wymaganiach funkcyjnych dla tego systemu i na jego modelu analitycznym. Celem tej aktywności jest podział systemu na samookreślone komponenty, których opracowywaniem mogą zająć się poszczególni programiści. Gdy przyjdzie do uwzględnienia kolejnych celów projektowych, związanych między innymi z kontrolą dostępu czy zarządzaniem danymi trwałymi, będziemy doskonalić i modyfikować efekt tej wstępnej dekompozycji. Rozpoczniemy od rozróżnienia dwóch części systemu ARENA: pierwsza z nich, poświęcona organizacji gier, odpowiedzialna będzie za koordynowaniu użytkowników organizujących instancje systemu, ligę (League) lub turniej (Tournament), druga dedykowana będzie rozgrywaniu gier i koordynowaniu graczy (PI ayer) rozgrywających poszczególne mecze (Match) w ramach turniejów (Tournament). Dla pierwszej, organizacyjnej części, wybierzemy architekturę trójwarstwową (patrz rysunek 7.12), w ramach której podsystem ArenaCI i e n t dostarcza interfejs czołowy dla użytkowników aktorów inicjujących przypadki użycia związane z organizacją gier (między innymi AnnounceTournament, ApplyForTournament i RegisterPlayer). Podsystem ArenaSever odpowiedzialny jest za kontrolę dostępu i sterowanie współbieżnością oraz delegowanie do zagnieżdżonych podsystemów zdarzeń odnoszących się do logiki aplikacji — podsystemy te dedykowane są zarządzaniu użytkownikami, reklamom, turniejom i grom. Najniższa warstwa, realizowana przez podsystem ArenaStorage, odpowiedzialna jest za przechowywanie wszystkich trwałych obiektów, z wyjątkiem reprezentujących stany meczów (Match). Dla części drugiej, w której akcja jednego gracza wyzwalać ma akcję drugiego w relatywnie krótkim czasie, architektura klient-serwer okazuje się niewystarczająca. Synchroniczne zachowanie mogłoby być symulowane przez przepytywanie (polling), ze względów skalowalności i reaktywności systemu wybraliśmy inne rozwiązanie — architekturę peer-to-peer, w ramach której instancje podsystemu MatchFrontEndPeer dostarczają interfejs użytkownika, zaś instancje podsystemu GamePeer przechowują informację o stanie rozgrywanych meczów, wymuszając jednocześnie zachowywanie reguł gry. Dla gier rozgrywanych w czasie rzeczywistym poszczególne instancje podsystemu MatchFrontEndPeer mogą komunikować się bezpośrednio. Zgodnie z postulatem niezależności systemu ARENA od konkretnej gry (który to postulat jest jednym z celów projektowych), ARENA dostarcza uniwersalne środowisko dla podsystemów Match Front ^-EndPeer i GamePeer, podczas gdy elementy specyficzne dla konkretnej gry zgrupowane są w odrębnych, specjalizowanych komponentach realizujących logikę owej gry. Przystosowywanie istniejących gier do systemu ARENA sprowadza się do tworzenia odpowiednich komponentów adapterów; nowe gry, przeznaczone do umieszczenia w systemie ARENA, można od początku tworzyć w sposób zgodny ze standardami tego systemu.

337

7.6. Analiza przypadku — system ARENA

Rysunek 7.12. Pierwszy fragment wyniku dekompozycji systemu ARENA — podsystemy odpowiedzialne za część organizacyjną

Instancje podsystemu TournamentManagement odwołują się do instancji podsystemu GameManagement w celu inicjowania instancji podsystemu GamePeer i kolekcjonowania wyników poszczególnych meczów. Instancje podsystemu Match Front EndPeer odwołują się do instancji podsystemu Adverti sementManagement w celu pobierania i wyświetlania banerów reklamowych (patrz rysunek 7.13). Zauważmy, że dla gier turowych 10 architektura klientserwer byłaby wystarczająca, jako że czas reakcji gracza nie jest w tych grach czynnikiem krytycznym. Architektura peer-to-peer nie stanowi jednak żadnej przeszkody dla gier tworzonych w stylu architektonicznym klient-serwer.

7.6.3. Odwzorowanie podsystemów w procesory i komponenty Odwzorowywanie podsystemów w procesory i komponenty umożliwi zidentyfikowanie przejawów współbieżnej pracy podsystemów, a także stanowić będzie krok w kierunku realizacji celów projektowych dotyczących wydajności i niezawodności. ARENA jest z założenia systemem rozproszonym — jego użytkownicy używają różnych komputerów, często oddalonych od serwera o wiele stref czasowych. Mimo to, wyróżnimy jedynie dwa typy węzłów: UserMachi ne, realizujący interfejs użytkownika, oraz ServerMachi ne,

10

Patrz http://pl.wikipedia.org/wiki/Strategiczna_gra_turowa

— przyp.

tłum.

338

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Rysunek 7.13. Drugi fragment wyniku dekompozycji systemu ARENA — podsystemy odpowiedzialne za rozgrywanie gier

realizujący logikę aplikacji, przechowywanie danych i tym podobne, czyli ogólnie udostępniający usługi systemu ARENA. Podsystemy ArenaCl ient i MatchFrontEndPeer zlokalizowane będą bezdyskusyjnie na węźle UserMachi ne. Co się tyczy pozostałych podsystemów, to przy założeniu niewielkiego obciążenia ze strony użytkowników mogłyby one być zlokalizowane na pojedynczym węźle ServerMachine; mając jednak na względzie postulat skalowalności systemu, definiujemy kolejny podsystem Adverti sementServer dedykowany przesyłaniu banerów reklamowych do przeglądarki i przypisujemy podsystemy Adverti sementServer, GamePeer, ArenaStorage i ArenaServer do odrębnych procesów, z których każdy może być realizowany na oddzielnym węźle ServerMachine. W komponencie ArenaServer zagnieżdżamy podsystemy TournamentManagement, UserManagement i GameManagement (patrz rysunek 7.14).

Rysunek 7.14. Odwzorowanie systemu ARENA w węzły sprzętowo-programowe; zauważmy, że każdy k o m p o n e n t wykonawczy może zawierać kilka podsystemów

7.6. Analiza przypadku — system ARENA

339

Dla realizacji części organizacyjnej' systemu ARENA wybraliśmy środowisko Java EE. Stanowi ono kolekcję interfejsów i standardów opracowanych przez firmę Sun Microsystems oraz siłami wspólnoty entuzjastów, a umożliwia tworzenie wieloplatformowych systemów w języku Java. Zaletą języka Java jest jego implementacja w szeregu produktów, zarówno komercyjnych, jak i klasy open source, co ułatwia pogodzenie dwóch sprzecznych wielkości: skali (mierzonej liczbą graczy, lig i turniejów) oraz kosztów (związanych między innymi z licencjonowaniem komponentów). Ponadto komponenty klasy open source mają zwykle to do siebie, że łatwo się je instaluje, a administrowanie nimi nie wymaga zaawansowanej wiedzy. Podsystem ArenaCl ient fizycznie realizowany jest w postaci przeglądarki WWW, natomiast ArenaServer i powiązane z nim podsystemy dostępne są za pośrednictwem serwera WWW. Do realizacji tych podsystemów użyliśmy dwóch składników środowiska Java EE: Java Servlets i Java Server Pages (JSP) jako głównej technologii implementowania obiektów brzegowych. Klasy Java Servlets, zlokalizowane w węźle ServerMachi ne, przetwarzają żądania otrzymywane z serwera WWW, produkując w odpowiedzi strony w języku HTML. JSP jest natomiast zwięzłym narzędziem do programowania serwletów w języku zbliżonym do HTML, który następnie przetwarzany jest przez preprocesor; wykorzystamy JSP do realizacji obiektów brzegowych systemu ARENA. Obiekty brzegowe odwoływać się będą do metod obiektów encji i obiektów sterujących, zrealizowanych także z użyciem klas Java Foundation. Po zidentyfikowaniu podsystemów, współbieżności i odwzorowania sprzętowoprogramowego zajmijmy się teraz zarządzaniem obiektami trwałymi.

7.6.4. Identyfikowanie i przechowywanie trwałych danych Identyfikowanie

obiektów

trwałych

W systemie ARENA występują dwa rodzaje obiektów o charakterze trwałym. Pierwszy z nich obejmuje obiekty tworzone i wykorzystywane przez podsystemy organizacyjne — graczy, gry, mecze i turnieje; obiekty te muszą być przechowywane również między kolejnymi uruchomieniami systemu, by umożliwić śledzenie historii lig, meczów, turniejów i poszczególnych graczy. Obiekty drugiego rodzaju tworzone i wykorzystywane są przez podsystemy GamePeer i MatchFrontEndPeer w związku z koniecznością odtwarzania zakończonych meczów na żądanie kibiców, a także wznawiania meczów przerwanych w wyniku awarii systemu. Pierwsza z wymienionych grup obiektów jest dobrze zdefiniowana i przypuszczalnie nie będą się one znacząco zmieniać w okresie istnienia systemu. Z drugą grupą rzecz ma się zgoła odmiennie: obiekty w niej są specyficzne dla poszczególnych gier i ich specyfika spoczywa całkowicie w gestii twórców tych gier. Zdecydowaliśmy zatem o zarządzaniu obiektami pierwszej grupy w ramach podsystemu ArenaStorage, twórcom gier pozostawiając całkowicie kwestię zarządzania obiektami specyficznymi dla tych gier — obiekty te wykorzystywane są jedynie przez interfejs konkretnej gry i nie mają bezpośredniego kontaktu z resztą systemu.

340

Rozdział 7. • Projekt systemu: realizacja celów projektowych

Wybór strategii przechowywania

obiektów

trwałych

Zidentyfikowanie obiektów trwałych systemu umożliwia określenie strategii ich przechowywania, z uwzględnieniem istotnych czynników tej strategii, czyli obsługi współbieżności i odtwarzania danych po awarii systemu. Wiele systemów zarządzania danymi umożliwia obsługę współbieżnych żądań, udostępniając system transakcji pozwalający na zachowywanie integralności danych. Jako że jednym z kluczowych celów projektowych systemu ARENA jest minimalizacja kosztów, naturalne staje się rozważenie przechowywania wszelkich obiektów trwałych w formie „płaskich" plików. Rozwiązanie takie nie wymaga żadnych zabiegów instalacyjnych czy konfiguracyjnych i dostępne jest bez żadnych dodatkowych kosztów. System oparty całkowicie na „płaskich" plikach nie jest jednak skalowalny, nie zdaje więc egzaminu w środowisku obsługującym równocześnie dziesiątki gier i tysiące graczy. Aby zatem pogodzić niskie koszty ze skalowalnością, wybieramy strategię mieszaną, opartą na połączeniu „płaskich" plików z relacyjną bazą danych. Podsystem ArenaStorage udostępniać będzie abstrakcyjny interfejs uwzględniający oba te rodzaje implementacji; przy instalowaniu systemu ARENA jego operator (ArenaOperator) będzie miał możliwość wyboru strategii najbardziej odpowiadającej jego potrzebom. Co prawda, operator ten nie będzie miał bezpośredniej możliwości samodzielnej zmiany dokonanego wtedy wyboru, lecz zmianę taką umożliwią mu narzędzia rekonfiguracji, dokonujące konwersji danych z „płaskich" plików do postaci relacyjnej bazy danych i odwrotnie. Rozwiązanie takie zwiększy koszty wytworzenia systemu ARENA, lecz zapewni operatorowi większą elastyczność. Aby zredukować stopień ryzyka, pierwszy prototyp systemu oparty będzie w całości na „płaskich" plikach, w drugim prototypie użyjemy natomiast interfejsu API relacyjnej bazy danych, niezależnego jednak od konkretnej bazy, na przykład JDBC [JDBC, 2009], co pozwoli na swobodny wybór tejże konkretnej bazy przez operatora. Problem trwałego przechowywania obiektów drugiej grupy — obiektów specyficznych dla poszczególnych gier — pozostawiamy całkowicie w gestii twórców gier. Biorąc pod uwagę fakt, że gra jest czasową sekwencją zdarzeń, a więc dane odzwierciedlające jej historię będą miały charakter sekwencyjny, przewidujemy wykorzystywanie w tej roli wyłącznie „płaskich" plików.

7.6.5. Definiowanie założeń kontroli dostępu ARENA jest systemem wielodostępnym, różni aktorzy uprawnieni są do wykonywania różnych operacji na różnych obiektach. Dla zwięzłego udokumentowania związanych z tym praw dostępu narysowaliśmy macierz dostępu, widoczną w tabeli 7.8, obrazującą uprawnienia dostępu poszczególnych aktorów do konkretnych obiektów encji. W skrócie — operator Arena "-•Operator może tworzyć obiekty reprezentujące użytkowników (User) i ligi (League), założyciel ligi (LeagueOwner) może tworzyć obiekty reprezentujące turnieje (Tournament) i mecze (Match), reklamodawcy (Adverti ser) mogą wysyłać i kasować reklamy, a także zgłaszać się do sponsorowania lig i turniejów. Zauważmy, że założyciel ligi (LeagueOwner) podejmuje ostateczną decyzję na temat sponsoringu, zgodnie z zapisem w dokumencie RAD. Gracz (Player) może subskrybować konkretną ligę (w celu otrzymywania ogłoszeń), zgłaszać się do udziału w turniejach (Tournament) i rozgrywać mecze (Match) zaplanowane dla tej ligi. Wreszcie kibice (Spectators) mogą oglądać statystyki graczy (Player), harmonogramy lig (League) i turniejów (Tournament), subskrybować powiadomienia o meczach (Match) i je oglądać.

341

7.6. Analiza przypadku — system ARENA Tabela 7.8. Macierz dostępu do obiektów systemu ARENA

Aktor/Obiekt ArenaOperator

Arena

User

League

Tournament

Match

«create»

«create»

"createn

createUser

deactivate

archive

getStats

archive

«create»

«create»

getlnfo

setSponsor

archive

kończenie

zgłoszenie do sponsorowania

zgłoszenie do sponsorowania

LeagueOwner

setSponsor

Advertiser

uploadAds removeAds

PI ayer

zgłoszenie na założyciel a ligi

setlnfo

oglądanie, subskrybowanie

zgłoszenie udziału, oglądanie, subskrybowanie

rozgrywanie, kończenie

Spectator

zgłoszenie gracza, zgłoszenie reklamodawcy

getStats

oglądanie, subskrybowanie

oglądanie, subskrybowanie

subskrybowanie, oglądanie, odtwarzanie

Co prawda, większość informacji związanej z prawami dostępu wynika wprost z modelu przypadków użycia, jednakże powyższa macierz stanowi ujęcie ich w zwięzłej i bardziej szczegółowej formie. Ułatwia to klientowi jej weryfikację, a programistom poprawne implementowanie. Kibice (Spectator) nie wymagają logowania do systemu, wszyscy inni aktorzy muszą zostać uwierzytelnieni, nim uzyskają możliwość modyfikowania obiektów systemu. Uwierzytelnienie to zrealizowaliśmy w formie standardowego dialogu „login/hasło", same zaś uprawnienia zaimplementowaliśmy w postaci list kontroli dostępu związanych z każdą z klas League, Tournament i Match. Zalogowani aktualnie użytkownicy kontrolowani są poprzez dedykowane im instancje klasy Session.

7.6.6. Projektowanie globalnego przepływu sterowania Jak pisaliśmy w sekcji 7.4.4, programista ma do wyboru trzy paradygmaty przepływu sterowania: proceduralny, zdarzeniowy i wielowątkowy. Wybór jednego z nich uzależniony jest z jednej strony, wymaganiami dotyczącymi czasu reakcji i przepustowości, z drugiej zaś, przewidywaną złożonością programowania. Co więcej, w systemie złożonym z wielu komponentów poszczególne komponenty wykorzystywać mogą różne paradygmaty sterowania. Wybierając jednak w sekcjach 7.6.3 i 7.6.4 komponenty dla interfejsu użytkownika, ograniczyliśmy sobie możliwości wyboru paradygmatu sterowania dla części organizacyjnej systemu ARENA. Serwer W W W (Webserver) odbiera żądania z przeglądarki (WebBrowser) i przetwarza je, kierując do odpowiedniego serwletu lub JSP — mamy więc do czynienia z paradygmatem zdarzeniowym. Dla obsługi każdego zdarzenia Webserver tworzy nowy wątek; niezależne realizowanie poszczególnych żądań zmniejsza czas reakcji systemu (serwer może przyjmować nowe żądania, zanim jeszcze zakończy się realizacja uprzednio przyjętych)

342

Rozdział 7. • Projekt systemu: realizacja celów projektowych

oraz jego przepustowość (gdy jedno żądanie oczekuje na odpowiedź od podsystemu bazy danych, inne mogą być przetwarzane przez procesor). Wybór paradygmatu wielowątkowego skutkuje jednak większą złożonością systemu, wynikającą z konieczności synchronizowania współbieżnych wątków. W systemie ARENA solidność rozwiązania w zakresie synchronizacji przy dostępie do wspólnych danych zapewnić mają następujące założenia. •

Obiekty brzegowe nie powinny definiować żadnych pól. Obiekty brzegowe powinny utrzymywać informację o stanie realizowanych żądań w lokalnych zmiennych, nie w swych polach. Pozwoli to na uniknięcie zjawiska wyścigu („hazardu") w sytuacji, gdy obiekt brzegowy współdzielony jest przez kilka wątków".



Obiekty sterujące nie powinny być współdzielone przez wątki. Dla każdej sesji powinien istnieć co najwyżej jeden obiekt sterujący, a użytkownicy nie powinni mieć możliwości wysyłania równoległych żądań w tej samej sesji12. Reguła ta staje się szczególnie istotna w odniesieniu do obiektów sterujących nieznikających po zakończeniu obsługi pojedynczego żądania.



Obiekty encji nie powinny udostępniać bezpośrednio swoich pól. Wszelki dostęp do tych pól musi odbywać się wyłącznie za pośrednictwem dedykowanych metod. Ponadto metody te powinny odwoływać się wyłącznie do pól tego obiektu, który zainicjował ich wywołanie (identyfikowanego przez zmienną thi s), nie do pól w innych instancjach tej samej klasy. W klasach zrealizowanych jako abstrakcyjne typy danych (patrz sekcja 2.3.2) wszystkie pola powinny już być polami prywatnymi.

• Metody odczytujące lub modyfikujące stan obiektu powinny być synchronizowane. Mechanizm synchronizacji dostarczany przez język Java zapewnia wówczas, iż dana metoda wykonywana jest w danej chwili w ramach co najwyżej jednego wątku. • Należy unikać zagnieżdżonych wywołań synchronizowanych metod, czyli wywoływania jednej synchronizowanej metody w ciele innej synchronizowanej metody. Takie zagnieżdżone wywołania stwarzają ryzyko wystąpienia zastoju (deadlock). Gdy wywołań talach nie sposób uniknąć w obecnej postaci metod, programiści powinni dokonać tego poprzez ich rozdrobnienie albo przynajmniej wymusić określoną kolejność wywoływania uzależnionych metod synchronizowanych 13 . • Redundantne informacje o stanie obiektu powinny być opatrywane znacznikami czasowymi. Gdy dwóch użytkowników przystępuje równocześnie do modyfikowania stanu tego samego obiektu trwałego, informacja o tym stanie znajduje się w trzech różnych miejscach: w bazie przechowującej ów obiekt i w formularzach obu użytkowników. Te dwie ostatnie instancje informacji powinny być opatrzone znacznikiem wskazującym chwilę ich wysłania, co ułatwi rozstrzygnięcie konfliktu między nimi.

11

Każdy wątek posiada wtedy własny, oddzielny zestaw zmiennych lokalnych, dzięki czemu poszczególne wątki nie interferują ze sobą; pola obiektu są natomiast wspólne dla wszystkich wątków — przyp. tłum.

12

Czyli formułowania kolejnego żądania przed zakończeniem obsługi poprzedniego — przyp.

13

Wymuszenie określonej kolejności żądania zasobów przez równoległe wątki jest jedną ze standardowych metod unikania zastojów — przyp. tłum.

tłum.

7.6. Analiza przypadku — system ARENA

343

W części drugiej systemu ARENA,zarządzającej rozgrywaniem gier, podsystemy Match "-•FrontEndPeer i GamePeer realizują swe funkcje w oddzielnych procesach, uruchamianych przez podsystem GameManagement zgodnie z harmonogramem turniejów i uczestnictwem graczy w rozgrywkach. Wewnętrzny przepływ sterowania w ramach każdego z komponentów MatchFrontEndPeer i GamePeer może być realizowany zdarzeniowo lub wielowątkowo, zależnie od specyfiki konkretnej gry.

7.6.7. Identyfikowanie usług Po zidentyfikowaniu podsystemów i określeniu strategii przepływu sterowania w ramach każdego komponentu (i systemu jako całości) czas na identyfikację usług oferowanych przez poszczególne podsystemy. W tym przykładzie skupimy się na zależnościach między podsystemami ArenaServer, UserManagement, Adverti sementManagement i TournamentManagement w kontekście przypadku użycia OrganizeTournament (patrz tabela 4.13). Zauważmy najpierw, że wszystkie żądania obsługiwane przez ArenaServer muszą być autoryzowane zgodnie z założeniami kontroli dostępu określonymi w sekcji 7.6.5. Prowadzi do stworzenia dwóch usług: Authentication, realizującej logowanie (uwierzytelnianie) użytkownika i Authorization, weryfikującej, czy dane żądanie dopuszczalne jest dla roli, jaką pełni aktualnie zalogowany użytkownik. Obie wymienione usługi przypisane zostają do podsystemu UserManagement. W początkowych krokach przypadku użycia OrganizeTournament założyciel ligi (LeagueOwner) organizuje nowy turniej (Tournament) w kontekście swojej ligi (League). Dla podsystemu TournamentManagement potrzebne są więc usługi realizujące tę organizację oraz udostępniające informacje na temat turniejów i lig. Usługi te, choć banalne, wymagane są przez każdą z klas modelu analitycznego. Dla zaznaczenia, że podsystem TournamentManagement obejmuje klasy Tournament i League, definiujemy usługi o tych właśnie nazwach. W praktyce usługi tego rodzaju, jako oczywiste, nie są w sposób jawny specyfikowane w modelu, jako że nie wnosiłyby praktycznie żadnej użytecznej informacji, za to powodowałyby dodatkową jego komplikację. W kolejnych krokach przypadku użycia OrganizeTournament założyciel ligi (League "-•Owner) wystosowuje do reklamodawców (Advertiser) zaproszenia do sponsorowania nowego turnieju. Dodajemy zatem do podsystemu Adverti sementManagement usługę Sponsorshi p, umożliwiającą wysyłanie wspomnianych zaproszeń i udzielanie na nie odpowiedzi przez zainteresowanych sponsorów. Łącząc w ramach jednej usługi dwie różne akcje, utrzymujemy w jednym miejscu wszystkie operacje powiązane ze sponsoringiem. W związku z wyborem reklamodawcy sponsora (Avdertiser) przez założyciela ligi (LeagueOwner) stajemy przed koniecznością zdefiniowania kolejnej usługi, którą przyporządkować można by zarówno do podsystemu Adverti sementManagement, jak i podsystemu TournamentManagement, ze względu na łączące te podsystemy skojarzenie między klasami Tournament i Advertiser. Jako że treść przypadku użycia OrganizeTournament koncentruje się wokół definicji turnieju, decydujemy się powierzyć podsystemowi TournamentManagement śledzenie stanu tego przypadku. W kategoriach modelu analitycznego podsystem ten jest właścicielem obiektu sterującego związanego ze wspomnianym przypadkiem. Ostatecznie więc przedmiotowa usługa — nazwana SponsorSel ection — ląduje w podsystemie Tournament "^-Management.

344

Rozdział 7. • Projekt systemu: realizacja celów projektowych

W kolejnych krokach przypadku użycia OrganizeTournament założyciel ligi (League >-*0wner) ogłasza zorganizowanie nowego turnieju, gracze zgłaszają się do udziału w nim, niektóre ze zgłoszeń zostają przyjęte, a my stajemy przed kolejnym problemem przyporządkowania stosownej usługi Player do podsystemu UserManagement albo TournamentManagement. Jako że gracz (Player) jest ściśle powiązany ze swą ligą i turniejem, decydujemy się na wybór podsystemu TournamentManagement. Ponieważ przyjęcie gracza do udziału w turnieju daleko wykracza poza utworzenie obiektu PI ayer, definiujemy nową usługę PI ayerAcceptance realizującą ten krok. Zidentyfikowane do tej pory usługi przedstawione są na diagramie z rysunku 7.15. Analizując powyższą dyskusję, można zauważyć dwie następujące tendencje: • Decydując się na wybór jednego z dwóch podsystemów jako docelowego dla nowej usługi, staramy się koncentrować funkcjonalność w podsystemie, w którym definiowany jest obiekt sterujący odnośnego przypadku użycia. Z jednej strony, prowadzi to do dużej spoistości podsystemu, z drugiej jednak, stwarza ryzyko jego nadmiernej komplikacji. • Definiowanie usług na bazie poszczególnych kroków przypadku użycia prowadzi do drobnoziarnistych usług. Mimo iż drobnoziarnistość ta może być znakomitym sprawdzianem dla dokonanej dekompozycji, to jednak jej konsekwencją jest duża liczba interfejsów dedykowanych pojedynczym operacjom. To znak, że zbyt szybko zbliżamy się do etapu projektowania obiektów. Należy wówczas zastanowić się nad konsolidacją powiązanych ze sobą usług w pojedyncze usługi, co pozwoli zachować stopień abstrakcji właściwy dla poziomu architektury systemu i jednocześnie zapobiegnie utracie czytelności modelu. Staranne wybieranie dla usług odpowiednich nazw, w postaci rzeczowników oznaczających kolekcje operacji, może stanowić czynnik przeciwdziałający wspomnianemu rozdrobnieniu.

Rysunek 7.15. Część organizacyjna dekompozycji systemu ARENA, widoczne są zidentyfikowane usługi. Dla przejrzystości zrezygnowaliśmy z uwidocznienia zależności między podsystemami

7.6. Analiza przypadku — system ARENA

345

7.6.8. Identyfikowanie warunków granicznych Teraz na podstawie podjętych dotąd decyzji projektowych zidentyfikujemy graniczne przypadki użycia. Na początek rozważymy czas życia obiektów trwałych systemu ARENA, czas życia każdego komponentu wykonawczego i możliwe typy awarii systemu.

Konfiguracyjne przypadki

użycia

Zarządzanie obiektami trwałymi jest już w większości opisane w przypadkach użycia opracowanych na etapie analizy wymagań (patrz rysunek 4.9 i tabela 4.11) i w macierzy dostępu (patrz tabela 7.8). I tak na przykład operator ArenaOperator definiuje nowych użytkowników (User) w systemie i usuwa z systemu ich profile, LeagueOwner tworzy ligi (League) i turnieje (Tournament), gracze (Player) rozpoczynają i kończą mecze, reklamodawcy (Advertiser) zarządzają swymi banerami (Adverti sementBanner). Jak dotąd jednak w żadnym z przypadków użycia nie zostało opisane zarządzanie obiektami Arena i Game. Pierwszy z nich reprezentuje instancję systemu ARENA i tworzony jest przy jego instalowaniu, drugi reprezentuje konkretną grę, tworzony jest w momencie wprowadzania tej gry do systemu i usuwany z systemu wraz z ową grą. Wynika stąd konieczność zdefiniowania dwóch dodatkowych przypadków użycia, inicjowanych przez aktora, takich jak ArenaOperator: Instal 1 Arena i ManageGames. Ponadto, jak wspomnieliśmy w sekcji 7.6.4, operator ma możliwość konwersji danych systemu z postaci „płaskich" plików do postaci relacyjnej bazy danych i odwrotnie, co odzwierciedlone zostaje w kolejnym przypadku użycia ConvertPersi stentStorage. Krótki opis trzech nowych przypadków użycia zamieszczamy w tabeli 7.9. Tabela 7.9. Dodatkowe, graniczne przypadki użycia związane z obiektami trwałymi systemu ARENA InstallArena

A r e n a O p e r a t o r tworzy instancję systemu, r e p r e z e n t o w a n ą przez obiekt Arena, nadaje m u nazwę, decyduje o strategii jego przechowywania („płaskie" pliki albo relacyjna baza danych) i konfiguruje jego parametry dotyczące zasobów (między innymi maksymalną liczbę jednoczesnych turniejów, ścieżkę określającą lokalizację pamięci dla obiektów trwałych).

ManageGames

ArenaOperator instaluje lub usuwa grę (Game), wraz z kodem źródłowym specyficznych dla gry k o m p o n e n t ó w GamePeer i MatchFrontEndPeer. Lista gier zostaje zaktualizowana, kolejny założyciel ligi (LeagueOwner) organizujący nową ligę (League) zobaczy ją w aktualnej postaci.

ConvertPersi s t e n t S t o r a g e

ArenaOperator po zamknięciu serwera ArenaServer ma możliwość konwersji danych trwałych z postaci „płaskich" plików na postać relacyjnej bazy danych i odwrotnie.

Przypadki użycia związane z rozpoczynaniem

i kończeniem pracy systemu

Jak to widać na diagramie wdrażania z rysunku 7.14, system ARENA zawiera pięć komponentów wykonawczych: WebBrowser, ArenaServer (zawierający pięć podsystemów — UserManagement, GameManagement, TournamentManagement, Notification i ArenaStorage),

346

Rozdział 7. • Projekt systemu: realizacja celów projektowych

MatchFrontEndPeer, GamePeer i (w drugiej wersji prototypu) DatabaseServer. Komponenty WebBrowser i DatabaseServer są zamkniętymi całościami „z półki", uruchamianymi i zamykanymi niezależnie. Podsystemy MatchFrontEndPeer i GamePeer uruchamiane są i zamykane przez (odpowiednio) WebBrowser i ArenaServer. Uruchamianie i zamykanie komponentu ArenaServer nie zostało jeszcze opisane — brak ten uzupełniają dwa kolejne przypadki użycia wymienione w tabeli 7.10. Tabela 7.10. Dodatkowe, graniczne przypadki użycia związane z k o m p o n e n t a m i wykonawczymi systemu ARENA StartArenaServer

ArenaOperator uruchamia ArenaServer. Jeśli poprzednia praca serwera nie zakończyła się poprawnym zamknięciem, wywoływany jest przypadek użycia CheckDatalntegri ty, opisany w następnej sekcji. Gdy zakończy się inicjowanie pracy serwera k o m p o n e n t u ArenaServer, założyciele lig (LeagueOwner), gracze (PI ayer), kibice ( S p e c t a t o r ) i reklamodawcy (Adverti s e r ) mogą inicjować „swoje" przypadki użycia.

ShutDownArenaServer

ArenaOperator zatrzymuje ArenaServer. Kończone są ewentualne trwające mecze (Match), zapisywane są wszelkie dane obecne w pamięci cache systemu. Z a m y k a n e są wszystkie instancje p o d s y s t e m ó w MatchFrontEndPeer i GamePeer. Po zakończeniu tego p r z y p a d k u użycia żaden z a k t o r ó w LeagueOwner, PI a y e r , S p e c t a t o r i A d v e r t i s e r nie m o ż e uzyskać jakiegokolwiek dostępu do obiektu Arena.

Przypadki użycia dla sytuacji

wyjątkowych

System ARENA może w swej pracy doświadczyć awarii dających się sklasyfikować w ramach czterech następujących kategorii: • awaria sieci powodująca zerwanie jednego lub wielu połączeń między instancjami podsystemów MatchFrontEndPeer i GamePeer, • awaria hosta lub komponentu, wskutek której nieoczekiwanie przerwana zostaje praca jednej lub wielu instancji podsystemu MatchFrontEndPeer lub GamePeer, • awaria sieci powodująca zerwanie jednego lub wielu połączeń między instancjami podsystemów WebBrowser i ArenaServer, • awaria serwera powodująca nagłe przerwanie pracy komponentu ArenaServer. Obsługę dwóch pierwszych klas zdecydowaliśmy się powierzyć obiektom Game reprezentującym konkretne gry. Co prawda, wyposażymy komponenty MatchFrontEndPeer i GamePeer w generyczne metody przywracania zerwanych połączeń i przywracania stanu przerwanych meczów, jednakże szczegółowa reakcja na zerwanie połączenia zależna jest od specyfiki konkretnej gry: gry symulacyjne i gry rozgrywane w czasie rzeczywistym muszą zostać zakończone lub ponownie rozpoczęte, podczas gdy w przypadku gier planszowych krótkotrwałe utraty połączeń mogą być nawet niezauważalne dla graczy. Pozostawiliśmy zatem twórcom gier swobodę w zakresie postępowania z awariami tego typu.

7.6. Analiza przypadku — system ARENA

347

Zerwanie połączenia przeglądarki'WWW z serwerem obsługiwane jest standardowo przez wszystkie popularne przeglądarki: użytkownik zostaje poinformowany o zaistniałej sytuacji i możliwości ponowienia wykonywanej operacji, niestety, dane wprowadzone do formularza mogą przepaść bezpowrotnie. Wynika stąd ważne zalecenie takiego projektowania formularzy, by objętość danych traconych przy takiej okazji była stosunkowo niewielka. Obsługę awarii ostatniego z wymienionych typów opisuje przypadek użycia CheckData ^ I n t e g r i t y (patrz tabela 7.11), odzwierciedlający weryfikację integralności danych po nieoczekiwanym przerwaniu pracy serwera ArenaServer. Przypadek ten może być uruchamiany automatycznie przy starcie systemu (patrz przypadek użycia StartArenaServer w tabeli 7.10), może go także zainicjować operator ArenaOperator. Przy okazji identyfikujemy jeszcze jeden przypadek użycia, opisujący restart przerwanej pracy instancji podsystemu GamePeer i stosowne powiadomienie zainteresowanych graczy. Tabela 7.11. Dodatkowe, graniczne przypadki użycia związane z awariami systemu ARENA CheckDatalntegrity

System ARENA d o k o n u j e zweryfikowania integralności trwałych danych. Dla reprezentacji „płaskich" plików może to ograniczać się do sprawdzenia, czy ostatnio zapoczątkowane transakcje zostały utrwalone na dysku. Dla relacyjnej bazy d a n y c h weryfikacja taka może oznaczać wywołanie specjalnych narzędzi d o k o n u j ą c y c h na przykład ponownego utworzenia indeksów tabel.

RestartGamePeers

System ARENA u r u c h a m i a p r z e r w a n e mecze (Match) i powiadamia wszystkie aktywne instancje p o d s y s t e m u MatchFrontEndPeer o wznowieniu pracy odnośnych instancji podsystemu GamePeer.

7.6.9. Wnioski W zakończonej właśnie sekcji przeanalizowaliśmy problemy związane z projektem systemu ARENA. Zidentyfikowaliśmy cele projektowe i nadaliśmy im priorytety, dokonaliśmy dekompozycji systemu, odwzorowaliśmy poszczególne podsystemy w komponenty i platformę sprzętowo-programową, wybraliśmy strategię przechowywania trwałych danych, zdefiniowaliśmy założenia kontroli dostępu do obiektów systemu, dostarczyliśmy rozwiązania najważniejszych problemów związanych ze współbieżnością, wreszcie skonstruowaliśmy przypadki użycia odzwierciedlające obsługę sytuacji granicznych i wyjątkowych. Na podstawie przeprowadzonej analizy mogliśmy się przekonać, że: • większość problemów towarzyszących projektowaniu systemu jest ze sobą powiązana; przykładowo wybór konkretnego komponentu dla realizacji obiektów brzegowych lub wybór odpowiedniej strategii przechowywania danych ma związek z ogólną strategią przepływu sterowania w systemie, • rozwiązania niektórych problemów projektowych mogą być zróżnicowane, zależnie od tego, do której części systemu obiekty te się odnoszą; przykładowo problemy związane z architekturą, przepływem sterowania, odtwarzaniem danych po awarii czy przechowywaniem danych rozwiązuje się inaczej w części organizacyjnej systemu ARENA, a inaczej w części zarządzającej przeprowadzaniem gier,

Rozdział 7. • Projekt systemu: realizacja celów projektowych

348

• niektóre problemy projektowe można pozostawić otwarte, aż do fazy projektowania obiektów (do takich problemów należą między innymi decyzje związane z podsystemem GamePeer lub nawet z późniejszymi emisjami systemu (takie jak wybór relacyjnej bazy danych, istotny dopiero w drugiej wersji prototypu), • formułując cele projektowe, należy dla każdego z nich określać istotność w formie przypisywania priorytetu i rozważać różne możliwości projektowe.

7.7. Literatura uzupełniająca Rozstrzyganie konfliktów między celami projektowymi jest trudnym zadaniem, które może być wykonywane jedynie metodą prób i błędów, i które wymaga praktyki i doświadczenia. Niewielka jest więc liczba publikacji poświeconych tej tematyce: książki L. Bassa, P. Clementsa i R. Kazmana [Bass i in., 2003] oraz P. Clementsa, R. Kazmana i M. Kleina [Clements i in., 2002] koncentrują się na ogólnych metodach oceny architektury, sformułowanych w postaci zbioru celów projektowych. Obie książki dostarczają także licznych analiz przypadku, ilustrujących ówczesny stan wiedzy w tej dziedzinie. W książce M. Błahy i W Premarlaniego [Błaha i Premerlani, 1998] opisane są metody tworzenia aplikacji bazodanowych, ze szczególnym uwzględnieniem wydajności, rozszerzalności i modyfikowalności. Książka D. P. Siewiorka i R. S. Swarza [Siewiorek i Swarz, 1992], traktująca o projektowaniu niezawodnych systemów, zawiera bogaty przegląd technik i metod osiągania rzeczonej niezawodności, jak również pokaźną liczbę analiz przypadku zaczerpniętych z projektów o charakterze przemysłowym. Zamieszczone w książce N. G. Leveson [Leveson, 1995] liczne przykłady awarii systemów komputerowych i ich konsekwencji skłaniają do wielu ciekawych konkluzji. Książka zawiera także przegląd ówczesnych metod podejścia do problemu bezpieczeństwa i eksponuje potrzebę wszechstronnych metodologii związanych z projektowaniem systemów. Wprowadzenie do projektowania aplikacji webowych zorientowanych na usługi (SOA — Service Oriented Architecture) oraz przewodnik po tej tematyce to treść książki T. Erla [Erl, 2005]. Celem architektury SOA jest strukturalizacja aplikacji pod kątem procesów biznesowych oraz łatwość jej rozbudowy, dzięki elastyczności kojarzenia usług udostępnianych użytkownikom.

7.8. Ćwiczenia 7.1. Rozpatrzmy system złożony z serwera W W W i dwóch serwerów bazodanowych. Oba serwery bazodanowe są identyczne: pierwszy funkcjonuje jako główny serwer, drugi — jako redundantny serwer zapasowy, włączany w przypadku awarii pierwszego. Użytkownicy wykorzystują przeglądarkę W W W w celu dostępu do danych, za pośrednictwem — oczywiście — serwera WWW, lecz mają także do dyspozycji dedykowane narzędzie, umożliwiające dostęp do serwera bazodanowego w sposób bezpośredni. Narysuj diagram wdrażania UML przedstawiający odwzorowanie tego systemu w węzły sprzętowo-programowe.

349

Bibliografia

7.2. Producent samolotów posługuje się przestarzałym, opartym na faksie systemem raportowania błędów. Ty uczestniczysz w projekcie zastąpienia tego systemu systemem komputerowym, obejmującym bazę danych i podsystem powiadamiania. Klient wymaga, by faks nadal pozostał urządzeniem wejściowym, Ty proponujesz e-mail jako narzędzie wprowadzania problemów do systemu. Opisz dekompozycję systemu uwzględniającą interfejsy dla obu tych rozwiązań. Weź pod uwagę fakt, że intensywność raportowania problemów może być bardzo duża (2000 faksów dziennie). 7.3. Wyobraź sobie, że projektujesz założenia kontroli dostępu dla systemu realizującego sklep internetowy. Klienci, kontaktujący się ze sklepem za pośrednictwem przeglądarki WWW, mogą przeglądać ofertę towarów, wprowadzać swoje dane osobowe i informacje na temat płatności, i — oczywiście — kupować towary. Dostawcy mogą rejestrować nowe produkty, modyfikować informacje o swoich produktach i przyjmować zamówienia. Właściciel sklepu ustala ceny detaliczne, formułuje zróżnicowane oferty dla poszczególnych klientów, bazując na ich profilach, i świadczy usługi marketingowe. W systemie tym należy uwzględnić trzech aktorów: administratora (StoreAdmi ni s t r a t o r ) , dostawcę (Supplier) i klienta (Customer). Zaprojektuj listy uprawnień dla każdego z wymienionych aktorów. Nowi klienci mogą sami rejestrować się za pośrednictwem WWW, lecz nowych dostawców rejestrować może tylko administrator. 7.4. Zaproponuj najbardziej odpowiedni mechanizm przepływu sterowania dla każdego z wymienionych poniżej podsystemów. Ponieważ w większości przypadków wybór nie jest oczywisty, uzasadnij swoje preferencje. ® Serwer W W W zdolny wytrzymać długotrwałe duże obciążenie. ® Interfejs GUI procesora tekstów. • Osadzony (wbudowany) system czasu rzeczywistego, na przykład system sterowania startem satelity. 7.5. Dlaczego graniczne przypadki użycia nie są konstruowane na etapie zbierania wymagań ani na etapie ich analizy? 7.6. Projektujesz właśnie podsystem cache'owania informacji pobieranych z internetu (lub innej sieci) na szybszym nośniku, na przykład dysku twardym. W związku ze zmianami w wymaganiach zamierzasz wyposażyć swój podsystem w nową usługę konfiguracyjną, umożliwiającą ustalanie parametrów cache'owania (na przykład maksymalnej wielkości przestrzeni dyskowej, jaką można zużyć na potrzeby tego cache'owania). Których uczestników projektu powinieneś powiadomić o tej zmianie?

Bibliografia [Bass i in., 2003]

L. Bass, P. Clements, R. Kazman Software Architecture wyd. drugie, Addison-Wesley, Reading, MA, 2003.

in Practice,

[Błaha i Premerlani, 1998]

M. Błaha, W. Premerlani Object-Oriented Modeling and Design for Database Applications, Prentice Hall, Upper Saddle River, NJ, 1998.

[Clements i in., 2002]

P. Clements, R. Kazman, M. Klein Evaluating Software Architectures: Methods and Case Studies, SEI Series in Software Engineering, Addison-Wesley, Reading, MA, 2002.

350

Rozdział 7. • Projekt systemu: realizacja celów projektowych

[Erl, 2005] T. Erl

Service-Oriented Architectures:Concepts, Technology, and Design, Prentice Hall, Upper Saddle River, NJ, 2005.

[Gamma i in., 1994]

E. G a m m a , R. H e l m , R. Johnson, J. Vlissides Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1994. Wydanie polskie Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku, Helion 2010.

[Hofmeister, 2000]

C. Hofmeister, R. Nord, D. Soni Applied Software Architecture, Object Technology Series, Addison-Wesley, Reading, MA, 2000.

[Jacobson i in., 1999]

I. Jacobson, G. Booch, J. Rumbaugh The Unified Software Development Process, Addison-Wesley, Reading, MA, 1999.

[JDBC, 2009]

JDBC™ — Connecting Javasoft, 2009.

[Leveson, 1995]

N. G. Leveson Safeware: System Safety And Computers, Addison-Wesley, Reading, MA, 1995.

[RMI, 2009]

Java Remote Method Invocation,

[Siewiorek & Swarz, 1992]

D. P. Siewiorek, R. S. Swarz Reliable Computer Systems: Design and Evaluation, wyd. drugie, Digital, Burlington, MA, 1992.

Java and Databases,

JDK D o c u m e n t a t i o n ,

JDK Documentation. Javasoft, 2009.

8.1.

Wstęp: wpadki produkcyjne

354

8.2.

O projektowaniu obiektów ogólnie

355

8.3.

Koncepcja wielokrotnego wykorzystywania

359

8.3.

Koncepcja wielokrotnego wykorzystywania — dziedziczenie, delegowanie i wzorce projektowe

359

8.3.

Koncepcja wielokrotnego wykorzystywania 8.3.1. Obiekty aplikacyjne i obiekty realizacyjne 8.3.2. Dziedziczenie implementacyjne i dziedziczenie specyfikacyjne 8.3.3. Delegowanie 8.3.4. Zasada zastępowania Liskov 8.3.5. Delegowanie i dziedziczenie we wzorcach projektowych

359 359 360 363 364 364

8.4.

Wybór wzorców projektowych i gotowych komponentów 8.4.1. Hermetyzacja przechowywania danych za pomocą wzorca projektowego Most 8.4.2. Hermetyzacja niekompatybilnych komponentów za pomocą wzorca projektowego Adapter 8.4.3. Hermetyzacja kontekstu za pomocą wzorca projektowego Strategia 8.4.4. Hermetyzacja platformy za pomocą wzorca projektowego Fabryka abstrakcyjna 8.4.5. Hermetyzacja przepływu sterowania za pomocą wzorca projektowego Polecenie 8.4.6. Hermetyzacja hierarchii za pomocą wzorca projektowego Kompozyt 8.4.7. Heurystyki wyboru wzorców projektowych 8.4.8. Identyfikowanie i przystosowywanie frameworków aplikacyjnych

367

8.5.

Zarządzanie wykorzystywaniem gotowych rozwiązań 8.5.1. Dokumentowanie wykorzystywania gotowych rozwiązań 8.5.2. Przydzielanie odpowiedzialności

386 388 389

8.6.

Analiza przypadku — system ARENA 8.6.1. Zastosowanie wzorca projektowego Fabryka abstrakcyjna 8.6.2. Zastosowanie wzorca projektowego Polecenie 8.6.3. Zastosowanie wzorca projektowego Obserwator

390 390 392 393

8.6.4.

Wnioski

393

8.7.

Literatura uzupełniająca

394

8.8.

Ćwiczenia

395

Bibliografia

396

368 371 373 376 377 378 379 381

Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych Oszukujesz, jeśli nie doceniasz wkładu innych. — Uniwersytet Carnegie Mellon, kurs 15-413 „Introduction to Software Engineering" (http:// www. cs. cm u.edul-ałdrichlcourses!413!)

I ^ o d c z a s analizy wymagań opisujemy cel powstania systemu — rezultatem tego są obiekty dziedziny aplikacyjnej. Na etapie projektowania systemu opisujemy ten system w kategoriach jego architektury: dekompozycji na podsystemy, przepływu sterowania i zarządzania trwałymi danymi; definiujemy wówczas także sprzętową i programową charakterystykę platformy, na której eksploatowany będzie powstający system. Pozwala to na wybór gotowych komponentów dostarczających wyższy poziom abstrakcji niż „goły" sprzęt. Kolejny etap — projektowanie obiektów — ma na celu wypełnienie luki między obiektami dziedziny aplikacyjnej a komponentami wybranymi na etapie projektowania systemu; dokonuje się tego zarówno poprzez identyfikowanie nowych obiektów z dziedziny realizacyjnej, jak i przez doskonalenie obiektów już istniejących. Głównymi elementami procesu projektowania obiektów są: • wykorzystywanie gotowych rozwiązań, którymi są i gotowe produkty (komponenty), i wzorce projektowe; • specyfikowanie usług, czyli precyzyjne opisywanie wszystkich interfejsów; • restrukturyzacja modelu obiektowego, w ramach której model ten jest przekształcany w celu przydania mu większej elastyczności (rozszerzalności) i lepszej czytelności; • optymalizacja modelu obiektowego, w ramach której model ten przekształcany jest pod kątem lepszej wydajności, czyli między innymi krótszego czasu reakcji i mniejszego zużycia pamięci. Projektowanie obiektów, podobnie jak projektowanie systemu, nie jest procesem algorytmicznym: sednem rozwiązania problemu staje się właściwa identyfikacja dostępnych wzorców i komponentów — związane z tym aktywności są właśnie treścią tego i dwóch następnych rozdziałów. W tym rozdziale skupimy się na wykorzystywaniu gotowych rozwiązań, w następnym zajmiemy się specyfikowaniem usług; w rozdziale 10. omówimy zabiegi dotyczące optymalizacji modelu.

354

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

8.1. Wstęp: wpadki produkcyjne Żeby było ciekawiej — kilka epizodów ze świata filmu. Speed: Niebezpieczna prędkość (1994) Harry, policjant z Los Angeles, zostaje wzięty jako zakładnik przez Howarda, szalonego pirotechnika. Jack, partner Harry ego, strzela i rani go w nogę, by opóźnić ucieczkę Howarda. Harry postrzelony został w lewą nogę, tymczasem przez cały film utyka na prawą. Budżet filmu: 30 milionów dolarów.

Trylogia Gwiezdne Wojny (1977, 1980, 1983) Pod koniec części V Imperium kontratakuje (1980) Han Solo zostaje pojmany i zamrożony w celu dostarczenia do Jabby. Na początku części VI Powrót Jedi (1983) zamrożony Han Solo zostaje odbity przez swych przyjaciół, odmrożony i przywrócony do życia. Wsadzany do zamrażarki Han Solo ma na sobie kurtkę; gdy przyjaciele go reanimują, ma na sobie białą koszulkę. Budżet obu filmów: 18 milionów i 32,5 milionów dolarów.

Titanic (1997) Jack, prosty rybak, uczy Rose, damę z wyższych sfer, trudnej sztuki plucia. Pokazuje jej tajniki tej sztuki i zachęca do praktykowania, gdy nagle zjawia się matka Rose. Gdy Jack zaczyna odwracać się do niej, ma twarz całkiem czystą; za chwilę jednak, gdy zwrócony jest w stronę matki Rose, widzimy ślady oplucia na jego podbródku. Budżet filmu: 200 milionów dolarów.

Podobne wpadki zdarzają się w wielu filmach, co — zważywszy na budżet ich realizacji i na banalny w gruncie rzeczy charakter wspomnianych niedopatrzeń — może wydawać się cokolwiek zdumiewające. Produkcja filmu jest jednak przedsięwzięciem skomplikowanym znacznie bardziej, niż mogłoby się to wydawać w pierwszej chwili. Przysłowiowe licho, które nie śpi i tylko podstępnie czyha na biednych realizatorów, konspiruje ukryte pod wieloma czynnikami. Przy kręceniu i produkcji filmu współpracuje mnóstwo ludzi, sceny kręcone są zwykle w kolejności innej od tej, w jakiej następują w scenariuszu, niektóre „dokręcane" są poza harmonogramem; szczegóły — kostiumy, rekwizyty — zmieniają się w czasie zdjęć. Po nakręceniu scen, gdy film jest edytowany i montowany, występuje silna presja na ukończenie prac w założonym terminie. Kolejne sceny muszą być nagrywane w sposób spójny — stan każdej sceny musi być zgodny ze stanem scen poprzedzających ją i następujących po niej. Ów „stan" obejmuje mnóstwo drobiazgów: od ubioru aktora, jego fryzury, makijażu, biżuterii, aż po elementy otoczenia odbijające się w szkłach jego okularów. Gdy w którejś ze scen następuje jakaś istotna zmiana, na przykład pojawienie się lub zniknięcie rekwizytu, nie może ona pozostawać w sprzeczności z żadną z pozostałych scen. Gdy pojedyncze sceny integrowane są ze sobą, nad zachowaniem opisywanej spójności czuwa osoba zwana „redaktorem ciągłości" (continuity editor). Nie bez powodu nawiązujemy tu do świata filmu, bowiem tworzenie systemu informatycznego ma w sobie coś z kręcenia i montowania filmu: jest skomplikowane, podatne na zmiany i odbywa się pod presją harmonogramu. Etap projektowania obiektów przypomina jako żywo dokręcanie brakujących scen: jego istotą jest wypełnianie luki, jaka istnieje między obiektami dziedziny aplikacyjnej, zidentyfikowanymi na etapie analizy wymagań, a sprzętowo-programową platformą wykonawczą, jaka określona została na etapie projektowania systemu.

8.2. O projektowaniu obiektów ogólnie

355

Programiści realizują to zadanie, konstruując niezbędne obiekty w warunkach iście hollywoodzkiego rozproszenia: poszczególne obiekty tworzone są niezależnie od siebie, przez różnych programistów i doznają licznych przeobrażeń, zanim przybiorą stabilny kształt. Często bywa tak, że programista tworzący obiekt, a korzystający z funkcji innego obiektu, ma o tych funkcjach tylko ogólne pojęcie, a często polega na domyślnych założeniach dotyczących efektów ubocznych tych funkcji, które to założenia niekoniecznie muszą być prawdziwe. Aby uniknąć nieporozumień tego typu, skutkujących błędną realizacją niektórych elementów funkcjonalnych (lub wręcz brakiem tej realizacji), programiści konstruują szczegółową specyfikację klas, atrybutów i operacji, stosownie do zdefiniowanych warunków i ograniczeń. Ponieważ niecelowe jest ponowne wynajdywanie koła ani ponowne odkrywanie Ameryki, programiści wykorzystują dostępny repertuar gotowych rozwiązań, przystosowując odpowiednio do swych potrzeb komponenty „z półki". Tym wszystkim zabiegom towarzyszy optymalizowanie modelu projektowania obiektów, talc by uczynić zadość sformułowanym celom projektowym dotyczącym efektywności, rozszerzalności, pielęgnowalności i reaktywności systemu; nie wolno przy tym zapomnieć o terminowym dostarczeniu systemu zgodnie z harmonogramem. Ogólny opis fazy projektowania obiektów przedstawiamy w sekcji 8.2. W sekcji 8.3 definiujemy podstawowe koncepcje dotyczące tej fazy, między innymi ograniczenia wykorzystywane do specyfikowania interfejsów. Sekcję 8.4 poświęcamy szczegółowemu opisowi aktywności związanych z projektowaniem obiektów, zaś sekcję 8.5 — problemom menedżerskim związanym z projektowaniem obiektów. Zrezygnowaliśmy z opisywania aktywności, takich jak implementowanie algorytmów i struktur danych czy też wykorzystywanie określonych języków programowania, z dwóch powodów: po pierwsze, zakładamy, że czytelnicy mają pewne doświadczenie w tym temacie, po drugie, wobec coraz większej dostępności gotowych, sprawdzonych rozwiązań i komponentów zagadnienia te nie mają dziś krytycznego znaczenia dla realizacji projektów informatycznych.

8.2. O projektowaniu obiektów ogólnie Koncepcyjnie rzecz biorąc, cały sens tworzenia systemu informatycznego sprowadza się do zasypywania przepaści istniejącej między problemem, którego rozwiązania oczekujemy od szeroko pojętej informatyki, a komputerem (komputerami), który ma to rozwiązanie fizycznie wyprodukować. Zasypywanie to odbywa się w sposób przyrostowy, zilustrowany na stylizowanym diagramie widocznym na rysunku 8.1.1 tale na etapie analizy wymagań system opisany zostaje w kategoriach zewnętrznych przejawów swego zachowania — czyli w kategoriach funkcjonalnych (określonych przez model przypadków użycia), kategoriach koncepcji dziedziny aplikacyjnej, podlegających przetwarzaniu przez system (kategorie te reprezentowane są przez obiekty modelu analitycznego) — zachowania postrzeganego jako przebiegające w czasie interakcje (będące treścią modelu dynamicznego) i wymagania pozafunkcyjne. Z kolei etap projektowania systemu przyczynia się do zmniejszania wspomnianej przepaści na dwa sposoby. Po pierwsze, zdefiniowana na tym etapie maszyna wirtualna stanowi wyższy poziom abstrakcji niż „goły" komputer. Tę wirtualną maszynę tworzą różne gotowe komponenty: oprogramowanie pośrednie (middleware), narzędzia do realizacji interfejsu użytkownika, środowiska uruchomieniowe i biblioteki klas realizujących typowe funkcje usługowe dla aplikacji. Po drugie, gotowe komponenty dedykowane dziedzinie aplikacyjnej, na przykład biblioteki realizujące transakcje finansowe, są tworami bardziej konkretnymi niż abstrakcyjne obiekty modelu analitycznego.

356

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Rysunek 8.1. Projektowanie obiektów jako proces likwidujący przepaść między obiektami dziedziny aplikacyjnej określonymi na etapie analizy a gotowymi k o m p o n e n t a m i wybranymi na etapie projektowania systemu

Po wykonaniu wielu iteracji związanych zarówno z analizą wymagań, jak i projektowaniem systemu tworzony system zdaje się przypominać układankę, w której wciąż brakuje wielu elementów. Owe brakujące elementy mogą być obiektami tworzonymi „od zera", odpowiednio konfigurowanymi obiektami „z półki" lub precyzyjnie określonymi interfejsami dla każdej klasy i każdego podsystemu. Model projektowania obiektów podzielony zostaje na zbiory klas, realizowane niezależnie przez poszczególnych programistów. Zgodnie z rysunkiem 8.2, etap projektowania obiektów obejmuje cztery grupy aktywności. • Wykorzystywanie gotowych komponentów i wzorców projektowych. Komponenty „z półki", zidentyfikowane na etapie projektowania systemu, ułatwiają realizację poszczególnych podsystemów; gotowe biblioteki klas i dodatkowe komponenty udostępniają realizację podstawowych struktur danych i usług. Wzorce projektowe ułatwiają rozwiązywanie powszechnych problemów programistycznych i ochronę wybranych klas przed przyszłymi zmianami. Często gotowe komponenty muszą być adaptowane do konkretnych potrzeb — dokonuje się tego bądź to za pomocą dziedziczenia klas, bądź też przez tworzenie odpowiednich otoczek (wrappers). W trakcie tej aktywności programiści muszą rozstrzygać liczne dylematy między samodzielnym tworzeniem „od zera" tego czy innego komponentu a jego realizacją na bazie gotowych komponentów — ten sam problem mieli zresztą już na etapie projektowania systemu.

8.2. O projektowaniu obiektów ogólnie

357

• Specyfikowanie interfejsów. Usługi poszczególnych podsystemów, zidentyfikowane w czasie projektowania systemu, sformułowane zostają w kategoriach interfejsów poszczególnych klas, czyli zbiorów operacji wraz ze szczegółowym opisem argumentów, sygnatur typu i wyjątków. W ramach tej aktywności często identyfikowane są dodatkowe operacje i obiekty niezbędne do transferowania danych między podsystemami. W ten oto sposób zestaw usług zdefiniowanych dla systemu przeobraża się w kompletny interfejs tego systemu, zwany często interfejsem programisty, w skrócie API (Application Programmer Interface). • Restrukturyzowanie modelu. Restrukturyzacja modelu ma na celu zwiększenie stopnia wykorzystywania gotowego kodu oraz realizację innych celów projektowych. Każda z restrukturyzacji może być reprezentowana w postaci grafu transformacji podzbiorów konkretnego modelu; do typowych transformacji tego typu należy przekształcanie skojarzeń „jeden na wiele" na skojarzenia „jeden do jednego", implementowanie skojarzeń „jeden do jednego" w postaci referencji, łączenie w pojedynczą klasę dwóch podobnych klas należących do różnych podsystemów, redukowanie („kolapsacja") klas niewykazujących wyraźnego zachowania do postaci atrybutów, dzielenie skomplikowanych Idas na prostsze klasy i (lub) reorganizowanie klas i operacji w celu wykorzystania dziedziczenia i pakietów. Zabiegi te zmierzają do realizacji takich celów projektowych jak pielęgnowalność, czytelność i zrozumiałość modelu systemu. • Optymalizowanie modelu. Optymalizacja modelu jest środkiem realizacji jego wydajności jako celu projektowego. Wydajność tę mogą zwiększyć takie zabiegi jak zmiany algorytmów na szybsze lub mniej pamięciożerne, redukowanie krotności skojarzeń lub dodawanie skojarzeń redundantnych w celu przyspieszenia realizacji żądań, zmiana kolejności wykonań operacji, dodawanie atrybutów w celu zwiększenia szybkości dostępu do obiektów czy otwieranie zamkniętej architektury warstwowej z zamiarem zwiększenia szybkości działania przez bezpośredni dostęp do warstw pośrednio niższych'. Projektowanie obiektów nie jest procesem sekwencyjnym. Mimo iż każda grupa wymienionych powyżej aktywności dedykowana jest rozwiązywaniu specyficznego problemu, aktywności poszczególnych grup realizowane są zazwyczaj równolegle. Gotowy komponent „z półki" może na przykład narzucać ograniczenie na liczbę typów wyjątków i powodować konieczność zrewidowania interfejsu podsystemu. Wykorzystywanie gotowych komponentów przyczynia się do redukcji wysiłku programistów, wymaga jednak zwykle konstruowania nowych obiektów spajających owe komponenty z resztą systemu. Wreszcie, restrukturyzowanie i optymalizowanie modelu może prowadzić do zmniejszenia liczby obiektów wymagających implementacji, dzięki zwiększeniu stopnia wielokrotnego wykorzystywania tego samego kodu. Zazwyczaj specyfikowanie interfejsów i poszukiwanie możliwości wykorzystywania gotowych komponentów oraz gotowego kodu odbywa się w pierwszej kolejności. Dostajemy wówczas model projektu obiektów i weryfikujemy go za pomocą przypadków użycia związanych z konkretnym podsystemem. Restrukturyzacje i optymalizacje są odpowiednie raczej dla

1

Różnica między zamkniętą a otwartą architekturą warstwową wyjaśniona jest w sekcji 6.3.4 — przyp.

tłum.

358

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Rysunek 8.2. Aktywności etapu projektowania obiektów

podsystemu względnie ustabilizowanego. Koncentrując się na interfejsach, komponentach i wzorcach projektowych, dostajemy model, który łatwo modyfikować; przedwczesne optymalizowanie modelu utrudnia jego późniejszą modyfikację, a co gorsza, odwraca uwagę od rzeczy istotniejszych. To wszystko nie zmienia jednak ogólnej zasady, że wymienione grupy aktywności przeplatają się ze sobą iteracyjnie.

8.3. Koncepcja wielokrotnego wykorzystywania

359

Wobec bogatego repertuaru aktywności związanych z projektowaniem obiektów, ich opisywanie podzieliliśmy między trzy rozdziały. Treść tego koncentruje się głównie na wykorzystywaniu gotowych produktów, przede wszystkim komponentów i wzorców projektowych. W rozdziale 9. „Projektowanie obiektów: specyfikowanie interfejsów" zajmiemy się aktywnościami związanymi ze specyfikowaniem interfejsów oraz językiem ograniczeń OCL (Object Constraint Language) stanowiącym podzbiór języka UML i jego zastosowaniem do specyfikowania niezmienników, warunków wstępnych i warunków końcowych. Rozdział 10. „Odwzorowywanie modelu na kod" poświęcimy aktywnościom restrukturalizacyjnym i optymalizacyjnym.

8.3. Koncepcja wielokrotnego wykorzystywania — dziedziczenie, delegowanie i wzorce projektowe W tej sekcji zajmiemy się koncepcjami związanymi z wykorzystywaniem gotowych rozwiązań, a szczególnie: • obiektami aplikacyjnymi i obiektami realizacyjnymi (patrz sekcja 8.3.1), • różnicą między dziedziczeniem implementacyjnym a dziedziczeniem specyfikacyjnym (patrz sekcja 8.3.2), • delegowaniem (patrz sekcja 8.3.3), • zasadą zastępowania Liskov (patrz sekcja 8.3.4), • wykorzystywaniem delegowania i dziedziczenia we wzorcach projektowych (patrz sekcja 8.3.5).

8.3.1. Obiekty aplikacyjne i obiekty realizacyjne Jak widzieliśmy w rozdziale 2. „Modelowanie w języku UML", diagramy klas mogą być wykorzystywane do modelowania zarówno dziedziny aplikacyjnej, jak i dziedziny realizacyjnej. Obiekty dziedziny aplikacyjnej, zwane krótko obiektami aplikacyjnymi, reprezentują koncepcje problemowe związane z tworzonym systemem. Obiekty realizacyjne reprezentują natomiast komponenty niemające odpowiedników w dziedzinie aplikacyjnej, na przykład bazy danych, obiekty interfejsu użytkownika czy egzemplarze oprogramowania pośredniego (middleware). Na etapie analizy identyfikujemy obiekty encji, relacje między tymi obiektami oraz ich atrybuty i operacje. Większość obiektów encji to obiekty aplikacyjne, niezależne od konkretnego systemu. W czasie analizy identyfikujemy także te obiekty realizacyjne, które są widoczne dla użytkownika, na przykład obiekty brzegowe i sterujące reprezentujące (odpowiednio) formularze i transakcje definiowane w systemie. Na etapie projektowania systemu identyfikujemy dalsze obiekty realizacyjne, w kategoriach bliskich platformom sprzętowej i programowej. W fazie projektowania obiektów doskonalimy obie grupy obiektów — aplikacyjne i realizacyjne — oraz bardziej je uszczegółowiamy, przy okazji identyfikując nowe obiekty realizacyjne, służące wypełnianiu wspomnianej na wstępie luki projektowej.

360

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

8.3.2. Dziedziczenie implementacyjne i dziedziczenie specyfikacyjne W czasie analizy wymagań wykorzystujemy mechanizm dziedziczenia klas do klasyfikowania obiektów w ramach taksonomii. Pozwala to odróżnić wspólne elementy zachowania, charakterystyczne dla ogólnego przypadku, czyli superklasy (zwanej także klasą bazową), od zachowania charakterystycznego dla poszczególnych specjalizowanych obiektów, będących egzemplarzami subklas (zwanych także klasami pochodnymi). Celem generalizacji (czyli identyfikowania superklasy dla danego zbioru istniejących klas) i specjalizacji (czyli identyfikowania poszczególnych subklas, pochodnych od danej superklasy) jest zorganizowanie obiektów modelu analitycznego w czytelną i zrozumiałą hierarchię. Dzięki temu czytelnikowi zapoznającemu się z modelem analitycznym funkcjonalność systemu jawi się początkowo jako zbiór ogólnych koncepcji, które w miarę lektury zostają coraz bardziej konkretyzowane. Przykładowo w modelu analitycznym wielokrotnie już cytowanego systemu FRIEND, opisywanego dokładnie w rozdziale 4. „Zbieranie wymagań", najpierw skupiamy się na ogólnej strategii reagowania na wypadki różnych rodzajów, a dopiero później zwracamy uwagę na różnice między reagowaniem na wypadki drogowe a reagowaniem na pożary. Celem wykorzystywania dziedziczenia klas na etapie projektowania obiektów jest zmniejszanie redundancji modelu i zwiększanie jego rozszerzalności. Wyodrębniając powtarzające się (redundantne) w poszczególnych klasach elementy zachowania w formie jednej superklasy, zmniejszamy ryzyko spowodowania niespójności związanej z wprowadzaniem zmian w opisie tego zachowania (na przykład zmian wynikających z poprawiania usterek), bo zmiany te dotyczyć będą wówczas tylko jednego miejsca — wspomnianej superklasy. Udostępniając aplikacji abstrakcyjny interfejs (zdefiniowany w abstrakcyjnej superklasie) jako jedyny środek komunikowania się z obiektami subklas, zapewniamy sobie łatwość rozbudowy, polegającą na możliwości definiowania nowych subklas, bez jakichkolwiek zmian we wspomnianym interfejsie. I tak na przykład, tworząc aplikację dedykowaną zarządzaniu kolekcją obrazków, definiujemy klasę abstrakcyjną Image, w ramach której specyfikujemy wszystkie operacje związane z obrazkami w ogólności, niezależnie od ich typów; zachowanie specyficzne dla danego typu obrazka specyfikujemy wówczas w subklasie reprezentującej ten typ (na przykład GIFImage, JPEGImage). Gdy w przyszłości będziemy chcieli wzbogacić funkcjonalność aplikacji o obsługę nowych typów obrazków, wystarczy, że zdefiniujemy nowe specjalizowane subklasy odpowiadające tym typom. Mimo iż wykorzystywanie dziedziczenia może sprawić, że model analityczny stanie się bardziej zrozumiały, a model projektu obiektów — łatwiejszy w modyfikowaniu i rozszerzaniu, korzyści te nie pojawiają się samorzutnie: dziedziczenie jest mechanizmem tak silnym, że nowicjuszom zdarza się przy jego użyciu tworzyć kod udziwniony i zawiły. Załóżmy dla przykładu, że programista nowicjusz nie wie (lub zapomniał) o istnieniu w języku Java abstrakcji Set i postanawia utworzyć własną klasę reprezentującą zbiór elementów. Ponieważ funkcje magazynowania obiektów obecne są w tablicy haszowanej, nasz programista zamierza użyć klasy Hashtable jako punktu wyjścia dla swojej klasy MySet. Sprawdzenie, czy dany element jest obecny w zbiorze, reprezentowane przez funkcję contai nsVal ue(), byłoby realizowane za pomocą odziedziczonej metody contai nsKey (). Zarys tej implementacji widzimy w lewej części rysunku 8.3 i na listingu 8.1.

8.3. Koncepcja wielokrotnego wykorzystywania

361

Rysunek 8.3. Przykład dziedziczenia implementacyjnego. Wątpliwa implementacja widoczna w lewej kolumnie zostaje zamieniona na bardziej sensowną, wykorzystującą delegowanie Listing 8.1. Implementacja klasy MySet przy użyciu dziedziczenia implementacyjnego

class MySet extends Hashtable { MySet() { ... konstruktor ...

} void put(Object element) { if (!containsKey(element)){ put(e1ement, t h i s ) ;

} } boolean containsValue(Object element)! return c o n t a i n s K e y ( e ł e m e n t ) ;

} ...

inne metody

...

}

Wspaniale! Mamy wszystko, czego potrzeba, bez wielkiego wysiłku — wszystko, czego potrzebujemy, wzięliśmy żywcem z klasy Hashtabl e. Hojne dziedziczenie okazało się jednak nazbyt hojne i przez to zdradliwe: nasza klasa MySet posiada bowiem także elementy, które mogą stać się przyczyną poważnych kłopotów. Zauważmy, że klasa MySet dziedziczy po klasie Hashtable metodę containsKey(), która nie dość, że nie jest do niczego potrzebna, to dodatkowo staje się przysłowiową kulą u nogi: funkcjonuje ona identycznie z (przedefiniowaną) metodą containsValue(), więc programista wykorzystujący nową klasę ma prawo używać tych dwóch metod zamiennie. Gdy w przyszłości zaimplementujemy klasę MySet w inny sposób,

362

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

na przykład na bazie listy wiązanej, nieposiadającej metody containsKey(), kod aplikacji wymagać może daleko idących zmian. Owszem, można by przedefiniować metodę contai nsKey () tak, by jej wywoływanie generowało wyjątek, ale czytelności i zrozumiałości kodu raczej to nie poprawi. Dziedziczenie ma w sobie coś z obosiecznego miecza: z jednej strony, zmniejsza powiązanie między podobnymi klasami, „wyciągając" z nich wspólną funkcjonalność do wspólnej superklasy, z drugiej jednak, generuje silne powiązanie tej superklasy z każdą z jej subklas. I podczas gdy jest to zjawisko jak najbardziej pożądane w przypadku budowania hierarchii opartej na taksonomii (jak w przypadku wspomnianej kolekcji obrazków), powiązanie takie staje się kłopotliwe w innych sytuacjach. W naszym przykładzie relacja dziedziczenia wiąże ze sobą klasy Hashtable i MySet, reprezentujące dwie różne koncepcje, co niesie ze sobą niekorzystne konsekwencje co najmniej pod dwoma względami: po pierwsze, wszelkie modyfikacje klasy Hashtable automatycznie odbijać się będą na klasie MySet, po drugie, potraktowanie klasy MySet jako konkretyzacji (specjalizacji) klasy Hashtable może dawać dziwaczne rezultaty w przypadku zastąpienia (w kodzie programu) obiektu klasy Hashtable przez obiekt klasy MySet. Wszystko to wynika z braku taksonomii między obiema klasami — po prostu zbiór elementów nie jest szczególnym przypadkiem tablicy haszowanej. Jeżeli mimo to sięgnęliśmy po dziedziczenie, zrobiliśmy to z zamiarem wykorzystania gotowego kodu, gotowej implementacji. Relacja dziedziczenia, wykorzystywana w takim właśnie celu, mimo różnic koncepcyjnych między powiązanymi klasami, nazywa się dziedziczeniem implementacyjnym. Zamiast pisać od zera własny kod nowej klasy, wykorzystuje się gotowy, wzięty z innej klasy, co najwyżej poddając go niewielkim modyfikacjom. Dla odróżnienia, dziedziczenie mające swe odzwierciedlenie w taksonomii klas nazywane jest dziedziczeniem specyfikacyjnym lub dziedziczeniem interfejsu. Widoczny na rysunku 8.4 diagram klas ilustruje zależności między czterema różnymi typami dziedziczenia.

Rysunek 8.4. Metamodel dziedziczenia. W projektowaniu zorientowanym obiektowo dziedziczenie wykorzystywane jest z myślą o osiągnięciu różnych celów, między innymi do modelowania taksonomii lub wykorzystywania gotowych elementów zachowania innych klas. W pierwszym przypadku relacja dziedziczenia może być identyfikowana w drodze bądź to specjalizacji (gdy na bazie klasy ogólnej definiowane są klasy bardziej szczegółowe), bądź generalizacji (klasa ogólna tworzona jest jako abstrakcja ujmująca wspólne elementy istniejących klas specjalizowanych). W drugim przypadku, gdy przez dziedziczenie klas zamierzamy osiągnąć powtórne wykorzystanie gotowego kodu, dziedziczenie to może mieć charakter specyfikacyjny (gdy jedna klasa jest podtypem drugiej) albo implementacyjny (gdy dwie Masy nie mają ze sobą nic wspólnego pod względem koncepcyjnym, a łączą je tylko identyczne lub podobne fragmenty kodu)

8.3. Koncepcja wielokrotnego wykorzystywania

363

8.3.3. Delegowanie Alternatywą dla dziedziczenia implementacyjnego jest delegowanie implementacji. Zamiast wykorzystywać określoną metodę konkretnej klasy poprzez dziedziczenie, wywołujemy ją najzwyczajniej, a dokładniej — wywołuje ją obiekt lub klasa, do którego to wywołanie delegujemy. Na listingu 8.2 pokazaliśmy, jak można wykorzystać metodę containsKeyO klasy Hashtabl e, nie wprowadzając w ogóle dziedziczenia po tej klasie: jak widać, jedyną znaczącą zmianą jest pojawienie się prywatnego pola tabl e i jego inicjacja przez konstruktor; w wyniku wspomnianej inicjacji pole to wskazuje na obiekt klasy Hashtable, do którego metoda contai nsVal ue () deleguje wywołanie metody contai nsKey (). Odzwierciedlenie tej sytuacji na diagramie klas widoczne jest w prawej części rysunku 8.3. Listing 8.2. Implementacja klasy MySet przy użyciu delegowania

class MySet { private Hashtable t a b l e ; MySet () { t a b l e = Hashtabl e ( ) ;

} void put(Object element) { if (!containsValue(element)){ table.put(element,this);

} } boolean containsValue(Object element) { return

(table.containsKey(element));

) ...

inne metody

...

Delegowanie uwidacznia jawną zależność między klasą nowo definiowaną a klasą, do której deleguje się implementację metody. Likwiduje to oba problemy, które niosło ze sobą dziedziczenie implementacyjne, czyli: • rozszerzalność — klasa MySet w nowej implementacji nie zawiera metody contai ns ^KeyO w swym interfejsie, a obiekt, do którego deleguje się jej wywołanie, należy do sfery prywatności klasy; możemy zatem dowolnie manipulować implementacją klasy (na przykład zamieniając tablicę haszowaną na listę wiązaną) bez wpływu na klientów tej klasy, • podtypowanie — klasa MySet nie wywodzi się z klasy Hashtabl e, nie może więc być jej substytutem w kodzie programu. Zdefiniowanie klasy MySet nie stanowi więc żadnego zagrożenia dla poprawności kodu używającego obiektów klasy Hashtabl e.

364

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Delegowanie jest preferowanym mechanizmem w stosunku do dziedziczenia implementacyjnego, nie interferuje bowiem z istniejącymi komponentami i daje w rezultacie bardziej solidny kod. Dziedziczenie specyfikacyjne jest natomiast lepsze od delegowania w sytuacji podtypowania, bo prowadzi do bardziej elastycznego, rozszerzalnego projektu.

8.3.4. Zasada zastępowania Liskov Zasada zastępowania Liskov [Liskov, 1988] określa formalną definicję dziedziczenia specyfikacyjnego. Jeżeli mianowicie w kodzie klienta występuje odwołanie do metody definiowanej w superklasie, wywoływanej na rzecz egzemplarza tej superklasy lub klasy pochodnej, to egzemplarz ten zastąpić można egzemplarzem dowolnej innej subklasy, pochodnej od tej samej superklasy. Przykładowo w odniesieniu do implementacji klasy MySet przedstawionej na listingu 8.1 oznacza to, że jeżeli w kodzie klienckim występuje odwołanie do obiektu klasy Hashtabl e, można je zastąpić odwołaniem do obiektu klasy MySet, bez modyfikowania samego kodu klienckiego. Jak wiemy, nie jest to prawdą — i dlatego relacja dziedziczenia między klasami MySet i Hashtable nie jest dziedziczeniem specyfikacyjnym. Oto formalna definicja tytułowej zasady. Zasada zastępowania Liskov Jeśli obiekt klasy S może stać się substytutem obiektu klasy T w dowolnym miejscu kodu, w którym oczekiwany jest obiekt klasy T, to klasa S jest podtypem klasy T.

Interpretacja W odniesieniu do projektowania obiektowego zasada zastępowania Liskov oznacza, że jeśli wszystkie klasy są podtypami swych superldas, to wszystkie relacje dziedziczenia między klasami są dziedziczeniem specyfikacyjnym. Innymi słowy, wszystkie metody definiowane w danej superklasie mogą być wywoływane zarówno na rzecz obiektów tejże superklasy, jak i obiektów jej dowolnej subklasy — bez jakiejkolwiek informacji o tym, z którą klasą mamy aktualnie do czynienia. W praktyce oznacza to, że dla dowolnej klasy możemy dowolnie definiować jej nowe subklasy, bez wpływu na kod kliencki; w ten sposób realizuje się postulat rozszerzalności systemu. Jeżeli wszystkie subklasy dziedziczą po swej superklasie, zgodnie z zasadą Liskov, mówimy wówczas o ścisłym dziedziczeniu.

8.3.5. Delegowanie i dziedziczenie we wzorcach projektowych Rozstrzyganie dylematu między użyciem dziedziczenia a delegowaniem implementacji nigdy jest sprawą oczywistą i wymaga pewnego doświadczenia. Obie techniki — dziedziczenie i delegowanie — używane w rozmaitych kombinacjach ułatwić mogą rozwiązywanie wielu problemów, między innymi oddzielanie abstrakcyjnych interfejsów od ich implementacji, tworzenie otoczek wokół przestarzałego kodu czy oddzielanie klas ustanawiających reguły od klas implementujących mechanizmy wymuszania tych reguł. Na gruncie obiektowo zorientowanego tworzenia oprogramowania wzorce projektowe są szablonowymi rozwiązaniami, które programiści mogą, po odpowiednim przystosowaniu, wykorzystywać do rozwiązywania powtarzających się problemów [Gamma i in., 1994], Każdy wzorzec projektowy definiowany jest w postaci czterech następujących elementów:

8.3. Koncepcja wielokrotnego wykorzystywania

365

1. nazwy jednoznacznie identyfikującej go wśród innych wzorców projektowych, 2. opisu problemu, czyli wskazania sytuacji, w której zastosowanie wzorca może być użyteczne; sytuacje takie najczęściej związane są z postulatami modyfikowalności i rozszerzalności systemu, jak również z dotyczącymi systemu wymaganiami pozafunkcyjnymi, 3. rozwiązania mającego postać zbioru współpracujących klas i interfejsów, 4. zbioru konsekwencji opisujących kompromisy i alternatywne rozwiązania w stosunku do problemów, dla jakich użyteczny bywa dany wzorzec. Nawiązując do definiowania nowej Idasy MySet (patrz rysunek 8.3), chcielibyśmy uzyskać jej zgodność z istniejącym interfejsem (czy interfejsem Set języka Java), przy jednoczesnym wykorzystaniu funkcjonalności kryjącej się w klasie Hashtable. Zarówno interfejs Set, jak i klasa Hashtabl e są już zdefiniowane i ich definicji zmienić nie można, aby więc zrealizować nasze zadanie, posłużymy się wzorcem projektowym o nazwie Adapter (patrz tabela 8.1 i sekcja A.2), szablonowym dla rozwiązywania problemów tego typu. Wzorzec ten funkcjonuje następująco: ldasa adaptera (nazwijmy ją umownie Adapter) implementuje wszystkie metody zadeklarowane w klasie klienckiej (Cl i e n t l n t e r f a c e ) w kategoriach żądań wysyłanych do klasy istniejącej i niezmiennej (LegacyCl ass). Wszelkie konwersje danych, zachowania i tym podobne właściwe dla klasy LegacyCl ass wykonywane są wewnątrz klasy Adapter, która na zewnątrz zachowuje się dokładnie tak, jak oczekuje tego klient na podstawie interfejsu Cl i e n t l n t e r f a c e . Tak oto udaje się pogodzić dwa niekompatybilne byty — interfejs Cl ient "-•Interface i klasę LegacyCl ass — bez potrzeby modyfikowania któregokolwiek z nich. Ponieważ ten sam Adapter może być użyty do wszelkich podtypów klasy LegacyCl ass (zgodnie z zasadą zastępowania Liskov), wzorzec projektowy Adapter realizuje postulat rozszerzalności systemu. Odnosząc ten wzorzec do klasy MySet czyniącej zadość interfejsowi Set, otrzymamy schemat odpowiadający delegowaniu przedstawionemu na listingu 8.2 (patrz rysunek 8.5). Zauważmy, że wzorzec projektowy Adapter wykorzystuje zarówno dziedziczenie, jak i delegowanie. Reguła ta odnosi się zresztą do wielu wzorców projektowych, które na pierwszy rzut oka mogą z tego powodu wyglądać łudząco podobnie, lecz jednak te same mechanizmy używane są przez każdy z nich na różne sposoby, często różniące się subtelnymi szczegółami. By zrozumieć te różnice, używać będziemy następujących określeń na poszczególne klasy uwikłane we wzorzec projektowy. • Klasa kliencka to klasa korzystająca ze wzorca. Na diagramie klas zawierającym wzorzec projektowy Adapter (jak na rysunku wewnątrz tabeli 8.1), klasa ta zwyczajowo nosi nawę Cl i ent. Może to być zarówno klasa wchodząca w skład gotowej biblioteki, jak i nowa klasa powstającego systemu. • Interfejs wzorca to część wzorca widoczna dla klasy klienckiej. Fizycznie realizowany jest w postaci interfejsu (w znaczeniu programowania obiektowego) lub klasy abstrakcyjnej. W ramach wzorca projektowego Adapter interfejs ten nosi zwyczajowo nazwę Cl i e n t l n t e r f a c e .

366

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Tabela 8.1. Przykład wzorca projektowego Adapter (zaczerpnięty z książki E. Gammy, R. Heima, R. Johnsona i J. Ylissadesa [Gamma i in., 1994]) Nazwa

Wzorzec projektowy Adapter

Opis problemu

Konwersja interfejsu prezentowanego przez istniejącą klasę na inny interfejs narzucony przez klienta, bez potrzeby modyfikowania zarówno wspomnianej klasy, jak i wymagań klienta.

Rozwiązanie

Klasa Adapter implementuje interfejs C l i e n t narzucony przez klienta, delegując wywołania metod do klasy LegacyCl a s s i przeprowadzając wszelkie niezbędne konwersje.

Konsekwencje

'

Interfejs Cl i ent i klasa LegacyCl ass mogą współpracować ze sobą bez żadnych modyfikacji.

• •

Ten sam Adapter może być zastosowany do wszelkich subklas klasy LegacyCl ass Dla każdej specjalizacji (subklasy) interfejsu Cl i ent konieczne jest utworzenie osobnego Adaptera.

Rysunek 8.5. Zastosowanie wzorca projektowego Adapter do problemu implementacji klasy MySet z rysunku 8.3

• Klasa implementacyjna to klasa realizująca niskopoziomowe zachowanie wzorca. W ramach wzorca projektowego Adapter klasami implementacyjnymi są klasy Adapter i LegacyCl ass. W wielu wzorcach projektowych do implementacji pożądanego zachowania konieczny jest cały zbiór klas implementacyjnych.

8.4. Wybór wzorców projektowych i gotowych komponentów

367

• Klasa rozszerzająca to specjalizacja klasy implementującej dostarczająca odmienną implementację lub rozszerzone zachowania wzorca. W ramach wzorca projektowego Adapter rolę klas rozszerzających mogą pełnić klasy będące podtypami klasy Legacy ^ C l a s s . Klasy rozszerzające najczęściej reprezentują przyszłe zmiany, przewidywane zawczasu przez programistów. Wzorce projektowe stanowią jedynie wskazówki postępowania, nie są czymś w rodzaju gotowych bibliotek i nie dostarczają gotowej recepty rozwiązywania konkretnych problemów. Ponieważ jednak ucieleśniają pokaźny zbiór wiedzy (między innymi w postaci określenia sytuacji, w których dany wzorzec może być przydatny, oraz kompromisów związanych z jego zastosowaniem), stanowią doskonałe przykłady właściwego korzystania z dziedziczenia i delegowania implementacji. W następnej sekcji dokonamy pobieżnego przeglądu najczęściej stosowanych wzorców projektowych w kontekście typowych problemów, w rozwiązywaniu których mogą okazać się pomocne.

8.4, Wybór wzorców projektowych i gotowych komponentów Projektowanie systemu i projektowanie obiektów kryje w sobie pewien paradoks. Z jednej strony, na etapie projektowania systemu stawiamy solidne ściany między podsystemami, ograniczając w ten sposób wpływ poszczególnych podsystemów na siebie i chroniąc każdy podsystem przed konsekwencjami zmian w innych podsystemach. Z drugiej jednak strony, na etapie projektowania obiektów dążymy do tego, by sprawić, żeby oprogramowanie stało się maksymalnie modyfikowalne i rozszerzalne, z zamiarem zminimalizowania kosztu przyszłych zmian. Kierujemy się zatem dwoma sprzecznymi celami: oczekujemy architektury stabilnej (by uporać się ze złożonością), ale też chcemy, by była ona elastyczna (aby można było ją w przyszłości łatwo przystosowywać do nieuniknionych zmian). Sprzeczność tę można rozwiązać, projektując wspomnianą architekturę z uwzględnieniem (przewidywaniem) wspomnianych zmian — dobrą wiadomością jest to, że przyczyny tych zmian są z grubsza te same dla większości systemów. • Nowi dostawcy lub nowe technologie. Komercyjne komponenty służące do budowania aplikacji zyskują sobie z biegiem czasu rozmaitych, bardziej atrakcyjnych, konkurentów. Zmiany tego typu są trudne do przewidzenia, podobnie jak dynamizm samego rynku — bywa że producent wykorzystywanego komponentu znika z rynku jeszcze przed ukończeniem projektu. • Zmiany w implementacji. Etap integrowania podsystemów okazuje się chwilą prawdy w kwestii wydajności systemu, a szczególnie jego reaktywności: częściej niż rzadziej okazuje się, że czas odpowiedzi systemu plasuje się wyraźnie powyżej ograniczenia określonego przez wymagania pozafunkcyjne. Wydajność systemu jest czynnikiem trudnym do przewidzenia i programiści nawet nie próbują jej specjalnie optymalizować przed etapem integracji. Aby spełnić pokładane w systemie oczekiwania, konieczne jest późniejsze ponowne implementowanie niektórych usług, z wykorzystaniem bardziej efektywnych algorytmów i struktur danych.

368

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

• Nowe widoki. Testowanie systemu przez jego użytkowników może obnażyć wiele problemów związanych z użytecznością. Ich rozwiązywanie może wymagać stworzenia dodatkowych widoków dla istniejących danych. • Zmiany w zakresie dziedziny aplikacyjnej. Podczas wdrażania systemu mogą objawić się nowe pomysły o charakterze generalizacyjnym: przykładowo udane wdrożenie systemu bankowego w jednym oddziale może stanowić impuls do stworzenia podobnego systemu zarządzającego wieloma oddziałami równocześnie. Sama dziedzina aplikacyjna też może ulegać przeobrażeniom: przykładowo, o ile poprzednio każdy samolot identyfikowany był pojedynczym numerem lotu, to teraz, w konsekwencji współpracy przewoźników, ten sam lot może być identyfikowany w inny sposób u każdego przewoźnika i z pojedynczym samolotem może być w danej chwili związany nie jeden, ale kilka różnych numerów lotu. • Błędy. Gdy użytkownicy przystąpią do testowania systemu, mogą uwidocznić się błędy (nawet liczne) w wymaganiach stawianych temu systemowi. Wykorzystywanie dziedziczenia i delegowania, w połączeniu z klasami abstrakcyjnymi umożliwia oddzielenie interfejsu podsystemu od jego aktualnej implementacji. W tej sekcji pokażemy na kilku przykładach, jak przy użyciu wzorców projektowych można skutecznie radzić sobie ze zmianami powodowanymi przez wymienione powyżej przyczyny. Po związaniu określonej przyczyny z najbardziej dla niej stosownym wzorcem projektowym (patrz tabela 8.2) opiszemy poszczególne wzorce w kontekście konkretnego problemu i dla każdego z nich pokażemy związek z dziedziczeniem i delegowaniem oraz wpływ tego związku na modyfikowalność i rozszerzalność systemu.

8.4.1. Hermetyzacja przechowywania danych za pomocą wzorca projektowego Most Rozpatrzmy problem przyrostowego tworzenia, testowania i integrowania podsystemów budowanych przez różnych programistów. Poszczególne podsystemy kończone są w różnym czasie, co wymaga wstrzymywania się z ich integrowaniem do czasu ukończenia ostatniego. Aby unikać takich opóźnień, wykorzystuje się często atrapy podsystemów zamiast ich realnej postaci, dzięki czemu testy integracyjne mogą rozpocząć się wcześniej. Często także programiści posługują się kilkoma implementacjami tego samego podsystemu: implementacja „referencyjna", używająca podstawowych algorytmów, prosta, czytelna i prawdopodobnie poprawna, jest jednak mniej wydajna od implementacji optymalizowanej pod kątem wydajności, lecz bardziej skomplikowanej i mniej oczywistej. Tak czy inaczej, stajemy wobec konieczności dynamicznego „przełączania się" między różnymi realizacjami tego samego interfejsu, w celu wykonywania odmiennych zadań. Do radzenia sobie z problemami tego rodzaju służy wzorzec projektowy Most (Bridge) (patrz sekcja A.3 i książka E. Gammy, R. Heima, R. Johnsona i J. Vlassidesa [Gamma i in., 1994]). Rozpatrzmy dla przykładu przechowywanie danych lig (League) w systemie ARENA. We wczesnych etapach tworzenia tego systemu interesuje nas jedynie najbardziej podstawowy mechanizm przechowywania danych bazujący na serializacji obiektów, zależy nam bowiem na uzyskaniu środowiska umożliwiającego testowanie i debugowanie głównych przypadków użycia

369

8.4. Wybór wzorców projektowych i gotowych komponentów

Tabela 8.2. Wybrane wzorce projektowe w powiązaniu z przyczynami zmian, przy których wprowadzaniu mogą okazać się pomocne

Wzorzec projektowy

Przewidywana przyczyna zmian i mechanizm wzorca

Sekcje opisujące wzorzec

Most

Nowy dostawca, nowa technologia, nowa implementacja. Izoluje interfejs klasy od jej implementacji. Służy temu s a m e m u celowi, co wzorzec p r o j e k t o w y Adapter z tą różnicą, że programista nie jest ograniczany przez istniejący k o m p o n e n t .

8.4.1, A.3

Adapter

Nowy dostawca, nowa technologia, nowa implementacja. Hermetyzuje fragment starego kodu, nieprzystosowanego do współpracy z n o w y m środowiskiem. Ogranicza także wpływ zastąpienia wspomnianego fragmentu przez inny komponent.

8.4.2, A.2

Strategia

Nowy dostawca, nowa technologia, nowa implementacja. Izoluje algorytm od jego implementacji. Służy temu samemu celowi, co wzorce projektowe Most i Adapter z tą różnicą, że hermetyzacji podlega określony aspekt zachowania, a nie konkretny komponent.

8.4.3, A.9

Fabryka abstrakcyjna

Nowy dostawca, nowa technologia. Hermetyzuje tworzenie poszczególnych rodzin obiektów, oddzielając klienta od procesu ich tworzenia, zapobiegając jednocześnie używaniu obiektów należących do różnych (niekompatybilnych ze sobą) rodzin.

8.4.4, A.l

Polecenie

Nowa funkcjonalność. Izoluje obiekty odpowiedzialne za przetwarzanie poleceń od samych poleceń, chroniąc te obiekty przed zmianami wynikającymi z modyfikowania funkcjonalności systemu.

8.4.5, A.4

Kompozyt

Nowe elementy w dziedzinie aplikacyjnej. H e r m e t y z u j e hierarchie, dostarczając wspólną superklasę dla agregatów i węzłów-liści. Nowe typy liści można dodawać bez modyfikowania istniejącego kodu.

dla podsystemu TournamentManagement. Obiekty encji będą doznawać jeszcze wielu zmian i na tym etapie trudno zidentyfikować wszelkie „wąskie gardła" związane z ich przechowywaniem. W konsekwencji efektywny system przechowywania danych systemu nie jest na tym etapie zagadnieniem numer 1 i w pierwszym prototypie systemu poprzestajemy na mechanizmie „płaskich" plików. Jak jednak pisaliśmy w sekcji 7.6.4, w drugim prototypie przewidujemy jednak opcję zastosowania relacyjnej bazy danych obok wspomnianych „płaskich" plików. Aby jak najwcześniej można było rozpocząć testy integracyjne, przed ukończeniem implementowania mechanizmów opartych na owych „płaskich" plikach potrzebne będą ich atrapy. Zastosowanie wzorca projektowego Most do rozwiązania tego problemu przedstawione jest na rysunku 8.6. LeagueStore jest klasą interfejsu tego wzorca, która dostarcza wysokopoziomową funkcjonalność związaną z przechowywaniem danych. LeagueStorelmpl ementor

370

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Rysunek 8.6. Zastosowanie wzorca projektowego Most do abstrahowania od konkretnej implementacji mechanizmów przechowywania danych w systemie ARENA

jest wspólnym, abstrakcyjnym interfejsem dla trzech implementacji: StubStorelmpl ementor (dla atrap), XMLStorelmpl ementor (dla „płaskich plików")2 i JDBCStorelmpl ementor dla relacyjnej bazy danych (osiągalnej za pośrednictwem JDBC). Zauważmy, że nawet wówczas gdy większość klas LeagueStorelmpl ementor dostarcza podobne usługi, wykorzystywanie wzorca projektowego Most pogarsza wydajność systemu. Stajemy tym samym przed wyborem między wydajnością a modyfikowalnością, jako celami projektowymi określonymi w sekcji 6.4.2.

Dziedziczenie

i delegowanie we wzorcu projektowym

Most

Interfejs wzorca projektowego Most realizowany jest przez klasę3 Abstract! on*, zaś jego zachowanie — przez jedną z klas Concretelmpl ementor-. Wzorzec może być rozszerzany przez dostarczanie nowych klas RefinedAbstraction- lub Concretelmpl ementor. Stanowi on klasyczny przykład połączenia dziedziczenia specyfikacyjnego z delegowaniem, dzięki czemu zapewnia zarówno wykorzystywanie gotowych rozwiązań, jak i elastyczność. Z jednej strony, relacja dziedziczenia specyfikacyjnego wiąże abstrakcyjny interfejs Impl ementor z klasami Concretelmpl ementor, zatem każda z tych ostatnich może w programie stanowić substytut dla klas Abstraction- i RefinedAbstraction-. Daje to jednocześnie zapewnienie, że programiści, opracowując klasę Concretelmpl ementor, będą się starali upodobnić jej zachowanie do zachowania innych klas tej grupy. Z drugiej strony, między klasami Abstraction- i Impl ementor istnieje relacja delegowania, co zapewnia dystrybucję różnego zachowania po obu stronach mostu. Przykładowo klasa LeagueStore z rysunku 8.6 reprezentuje wysokopoziomowe aspekty przechowywania danych związanych z poszczególnymi ligami, podczas gdy każda z klas grupy

2

W każdym „płaskim" pliku przechowywany będzie obraz jednego lub więcej obiektów, w formacie XML — przyp. tłum.

3

W opisach zastosowań poszczególnych wzorców projektowych autorzy używają nazw pochodzących z dwóch obszarów: bieżących rysunków rozdziału oraz terminologii zawartej w dodatku A. Dla lepszej czytelności nazwy tej drugiej grupy wyróżnione są gwiazdką — przyp. tłum.

8.4. Wybór wzorców projektowych i gotowych komponentów

371

LeagueStorelmpl ementor dostarcza odmienną, niskopoziomową funkcjonalność, realizującą konkretny mechanizm tego przechowywania. Ponieważ klasa LeagueStorelmpl ementor prezentuje zachowanie odmienne niż klasa LeagueStore, nie może być uważana za jej podtyp, zgodnie z zasadą Liskov.

8.4.2. Hermetyzacja niekompatybilnych komponentów za pomocą wzorca projektowego Adapter W miarę jak systemy informatyczne stają się coraz bardziej złożone, a czas dostarczania ich na rynek — coraz krótszy, oprogramowanie staje się droższe od sprzętu. Programiści dążą więc do jak największego wykorzystywania kodu z poprzednich projektów i stosowania gotowych komponentów. Systemy interaktywne na przykład rzadko budowane są dziś „od zera": ich osnowę stanowią narzędzia służące do tworzenia interfejsu użytkownika, udostępniające szeroką gamę okien, dialogów, przycisków i innych obiektów spotykanych w typowym interfejsie. Gdy trzeba zmienić interfejs użytkownika, modyfikowana jest zwykle tylko niewielka część systemu: przykładowo systemy zarządzania przedsiębiorstwami, drogie i czasochłonne w tworzeniu, muszą być sukcesywnie uaktualniane w miarę unowocześniania sprzętu klienckiego. Często tylko ldiencka strona takiego systemu aktualizowana jest pod kątem nowych technologii, jego zaplecze pozostaje nienaruszone. I w tym momencie natrafiamy na problem kompatybilności: programiści muszą poradzić sobie z komponentami, które nie pasują do nowej technologii, a nie są — niestety — modyfikowalne. Skoro nie można ich modyfikować do pożądanej postaci, nie pozostaje nic innego, jak zamknąć je w odpowiedniej otoczce, która tę nową postać będzie symulować. Otoczka taka spełni rolę przegrody, oddzielającej istniejące komponenty od reszty systemu, dzięki czemu komponenty te nie będą nakładać na nowy projekt krępujących ograniczeń. W realizacji takiego podejścia pomocny okazuje się wzorzec projektowy Adapter (patrz sekcja A.2 i przywoływana już tu książka E. Gammy i pozostałych autorów [Gamma i in., 1994]). Istotą tego wzorca jest konwersja interfejsu istniejącego komponentu do postaci, jaka wymagana jest przez jego klienta. Ów wymagany przez klienta interfejs nosi nazwę Clientlnterfacena rysunku wewnątrz tabeli 8.1. Klasa Adapter- pełni rolę spoiwa między tym interfejsem a klasą LegacyCl ass- reprezentującą interfejs istniejącego komponentu. Załóżmy teraz, że klientem jest statyczna metoda s o r t ( ) klasy Array (w języku Java). Wywołanie tej metody wymaga dwóch parametrów: tablicy obiektów i komparatora (obiektu Comparator) dostarczającego metodę compare() porównującą elementy tablicy, czyli definiującą względny porządek dwóch wskazanych elementów. Niech obiektami tablicy będą obiekty klasy MyString, definiującej metody gretaerThan() („większy od ...") i equals() („równe"). By tę tablicę posortować, potrzebujemy komparatora MyStringComparator, którego metoda compare () oparta jest na tych dwu metodach. Klasa tego komparatora jest właśnie adapterem 4 . Zastosowanie wzorca Adapter do opisanego przypadku przedstawiliśmy na rysunku 8.7, zaś definicja klas MyStri ng i MyStri ngComparator widoczna jest na listingu 8.3.

4

Przy tworzeniu systemu całkowicie „od zera" rzadko zdarza się, że trzeba użyć adapterów z tej prostej przyczyny, że nowe ldasy definiować można od razu zgodnie z wymaganymi interfejsami.

372

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Rysunek 8.7. Zastosowanie wzorca projektowego Adapter do sortowania tablicy łańcuchów; patrz także listing 8.3 Listing 8.3. Przykład zastosowania wzorca projektowego Adapter w języku Java. Statyczna metoda s o r t () klasy A r r a y wymaga dwóch a r g u m e n t ó w : s o r t o w a n e j tablicy e l e m e n t ó w (a) oraz k o m p a r a t o r a porównującego elementy (c). Aby posortować tablicę elementów klasy MyStri ng, potrzebujemy komparatora MyStri ngComparator zgodnego z interfejsem Comparator. Klasa MyStri ngComparator jest adapterem /* istniejący interfejs docelowy */ interface Comparator { int c o m p a r e ( O b j e c t o l , O b j e c t o 2 ) ;

} /* istniejący klient */ class Array { static void s o r t ( 0 b j e c t

[] a , Comparator

c);

} /* istniejąca klasa adaptowana */ class MyString extends S t r i n g { boolean e q u a l s ( O b j e c t o ) ; boolean g r e a t e r T h a n ( M y S t r i n g

s);

} /* nowa klasa - klasa adaptera */ class M y S t r i n g C o m p a r a t o r implements Comparator { int c o m p a r e ( O b j e c t o l , O b j e c t o2) { int r e s u l t ; if ( ( ( S t r i n g ) o l ) . g r e a t e r T h a n ( o 2 ) ) { result = 1 } else if ( ( ( S t r i n g ) o l ) . e q u a l s ( o 2 ) ) { r e s u l t = 0; } else ( result = -1;

}

8.4. Wybór wzorców projektowych i gotowych komponentów

373

return result;

}

}

Dziedziczenie i delegowanie we wzorcu projektowym

Adapter

Wzorzec projektowy Adapter wykorzystuje dziedziczenie specyfikacyjne między klasami Cl i e n t l n t e r f a c e - i Adapter. W celu realizowania operacji deklarowanych w interfejsie Cl i ent Interface- klasa Adapter deleguje wywołania do klasy implementatora LegacyCl ass-. Kod kliencki zawierający odwołania do interfejsu Cl i e n t l n t e r f a c e - współpracuje w ten sposób z instancjami klasy Adapter- w sposób niejawny, bez konieczności dokonywania w nim jakichkolwiek zmian. Zauważmy, że ta sama klasa Adapter- może być użyta do dowolnych podtypów klasy LegacyCl ass-. Wzorce projektowe Most i Adapter podobne są do siebie pod względem przeznaczenia i struktury: oba oddzielają interfejs od jego implementacji, oba też korzystają z dziedziczenia i delegowania. Różnią się jednak pod względem kontekstu, w którym są stosowane, i kolejności, w jakiej następują dziedziczenie i delegowanie: we wzorcu Adapter najpierw mamy do czynienia z dziedziczeniem, później z delegowaniem, we wzorcu Most odwrotnie. Wzorzec Adapter przydatny jest w sytuacji, gdy zarówno interfejs kliencki (Cl i ent I nterf ace-), jak i jego implementacja (LegacyCl ass ) są ustalone i niezmienne, wzorzec Most znajduje natomiast zastosowanie w sytuacji tworzenia nowego kodu, zapewniając większą rozszerzalność.

8.4.3. Hermetyzacja kontekstu za pomocą wzorca projektowego Strategia Wyobraźmy sobie aplikację mobilną, pracującą na przenośnym komputerze, którego używa serwisant samochodowy. Aplikacja ta powinna być zdolna do korzystania z różnych protokołów sieciowych, zależnie od ich dostępności — i tak na przykład w rodzimym warsztacie serwisanta wspomniany komputer pracować ma w oparciu o połączenie bezprzewodowe z lokalnym routerem, choć dla celów większej wydajności przy konfigurowaniu aplikacji pożądana jest możliwość współpracy za pomocą połączenia kablowego (Ethernet) z tymże routerem; gdy serwisant znajduje się w drodze, musi mieć możliwość łączności z serwerem w swoim warsztacie za pośrednictwem mobilnych usług 3G, takich jak na przykład UMTS. Oznacza to konieczność nie tylko uwzględnienia w aplikacji różnych protokołów sieciowych, lecz także możliwość dynamicznego, automatycznego przełączania się między nimi, stosownie do lokalnych warunków technicznych (i ekonomicznych). Co więcej, poszerzanie aplikacji o możliwość współpracy z nowymi protokołami ma się odbywać bez konieczności jej rekompilowania. Opisana sytuacja idealnie nadaje się do zastosowania kolejnego wzorca — wzorca projektowego Strategia (patrz sekcja A.9 i książka E. Gammy i innych [Gamma i in., 1994]). Model wspomnianej aplikacji widoczny jest na rysunku 8.8, a na listingu 8.4 przedstawiamy natomiast implementację jej Idas w języku Java. Klasie strategii (Strategy) odpowiada tu abstrakcyjna klasa Networklnterface — wspólna superklasa dla wszelkich protokołów

374

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

sieciowych; kontekst (klasa Context ) realizowany jest przez obiekt NetworkConnection, reprezentujący połączenie „punkt-punkt" między komputerem przenośnym a zdalnym hostem. Rolę klienta (Cl i ent-) pełni aplikacja pracująca na tym komputerze. Klasa ustalająca zasady (Policy) reprezentowana jest tu przez klasę LocationManager, odpowiedzialną za monitorowanie dostępnych warunków połączenia w bieżącej lokalizacji i automatyczne wybieranie odpowiedniej specjalizacji Networklnterface stosownie do tych warunków. Gdy obiekt klasy LocationManager wywołuje metodę s e t N e t w o r k l n t e r f a c e Q , obiekt NetworkConnection kończy pracę aktualnie wybranej implementacji i przełącza się na inną, w sposób przezroczysty dla reszty aplikacji.

Rysunek 8.8. Zastosowanie wzorca projektowego Strategia do hermetyzacji dynamicznego przełączania interfejsu Networklnterface między różnymi jego implementacjami. Za przełączanie to, zgodnie z określonymi zasadami, stosownie do warunków bieżącej lokalizacji odpowiedzialny jest obiekt LocationManager. Aplikacja (Application) nie jest świadoma bieżącej implementacji interfejsu Networklnterface. Patrz także kod na listingu 8.4 Listing 8.4. Zastosowanie wzorca projektowego Strategia do hermetyzacji wielu implementacji interfejsu N e t w o r k l n t e r f a c e (w języku Java). Dla uproszczenia pominęliśmy między innymi obsługę sytuacji wyjątkowych, patrz także diagram klas na rysunku 8.8 /**

Obiekt klasy NetworkConnection

reprezentuje połączenie

Odpowiada on obiektowi Context wzorca projektowego public class NetworkConnection ( private String destination; private Networklnterface intf; private StringBuffer queue;

używane przez

klienta.

Strategia */

public NetworkConnect(String destination, Networklnterface intf) { this.destination = destination; this.intf = intf; this.intf.open(destination); this.queue = new StringBufferQ ;

}

375

8.4. Wybór wzorców projektowych i gotowych komponentów public void send(byte msg[]) { • / / kolejkowanie komunikatów przeznaczonych do wysiania jako środek / / radzenia sobie z tymczasowym brakiem połączenia queue.concat(msg); if (intf.isReady()) { intf.send(queue); queue.setLength(O);

} } public byte [] receiveQ { return intf.receiveQ;

} public void setNetworkInterface(NetworkInterface newlntf) { intf.close(); newlntf.open(destination); intf = newlntf;

} } /**

Klasa LocationManager decyduje o wyborze konkretnej implementacji interfejsu Networklnterface, stosownie do warunków technicznych i ekonomicznych danej lokalizacji */ public class LocationManager { private Networklnterface networklntf; private NetworkConnection networkConn; / / Poniższa metoda wywoływana jest przez procedurę obsługi / / przy zmianie lokalizacji

zdarzenia

public void doLocation() { if (isEthernetAvai1able()) { networklntf = new EthernetNetworkQ; } else if (isWaveLANAvai 1 able()) { networklntf = new WaveLANNetwork(); } else if (isUMTSAvailableO) { networklntf = new UMTSNetworkQ; } else { networklntf = new QueueNetworkQ;

}

networkConn.setNetwork Interface(network Intf);

} }

Dziedziczenie

i delegowanie we wzorcu projektowym

Strategia

W kontekście rysunków 8.6 i 8.8 wzorce Most i Strategia wydają się niemal identyczne. Różnica między nimi kryje się w kreatorze klas implementujących interfejs: w ramach wzorca Most klasy te (Concretelmpl ementati on-) tworzone są oraz inicjowane przez klasę Abstracti on-,

376

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

we wzorcu Strategia kontekst (Context-) jest natomiast niezależny od (i nieświadomy) konkretnych implementacji (ConcreteStrategy) — to klient odpowiedzialny jest za ich tworzenie i odpowiednie konfigurowanie kontekstu. Co więcej, we wzorcu Most konkretna implementacja tworzona jest i wiązana z interfejsem zwykle w czasie inicjowania aplikacji, podczas gdy na gruncie wzorca Strategia konkretne strategie tworzone są i dynamicznie wybierane w czasie działania programu.

8.4.4. Hermetyzacja platformy za pomocą wzorca projektowego Fabryka abstrakcyjna Jako kolejny przykład wyobraźmy sobie inteligentny dom, a konkretnie aplikację rejestrującą sygnały z rozproszonych po domu czujników (żarówka włączona/wyłączona, okno otwarte/ zamknięte, temperatura wewnątrz i na zewnątrz, prognoza pogody) i uruchamiającą — na podstawie predefiniowanych wzorców — mechanizmy wykonawcze (włączenie klimatyzacji, aktualizację statystyki zużycia energii, zamknięcie drzwi garażu, włączenie alarmu przeciwwłamaniowego). Mimo iż rozwiązania sprzętowe tej kategorii dostarczane są przez wielu producentów (między innymi EIB czy Luxmate), możliwość współdziałania produktów pochodzących od różnych wytwórców jest raczej kiepska, co z jednej strony, skazuje klienta na wybór wyposażenia w całości od jednego dostawcy, z drugiej, utrudnia tworzenie oprogramowania uniwersalnego dla wszystkich producentów. Wspomnianą niekompatybilność można jednak pogodzić przy użyciu wzorca projektowego Fabryka abstrakcyjna (patrz sekcja A.l i książka E. Gammy i innych [Gamma i in., 1994]). Dla naszego inteligentnego domu wybrany producent dostarczy wszystkie kategorie czujników — fotokomórki, czujniki przepalenia żarówek, czujniki temperatury i tym podobne. W schemacie przedstawionym na rysunku 8.9 (ograniczyliśmy się do dwóch pierwszych kategorii czujników): klasy abstrakcyjne LightBulb (żarówka) i Blind (fotokomórka) pełnią funkcje produktów abstrakcyjnych (AbstractProduct-), zaś konkretne realizacje tych produktów reprezentowane są przez klasy charakterystyczne dla każdej pary „produktproducent" (EIBLi ghtBul b, LuxmateLi ghtBul b, EIBBlind, LuxmateBl i nd).. Klasy reprezentujące fabryki, po jednej dla każdego producenta (LuxmateFactory, EIBFactory), stanowiące konkretyzacje fabryki abstrakcyjnej (AbstractFactory-, tu reprezentowanej przez HouseFoctory), dostarczają metody „wytwarzające" konkretne kategorie produktów (create ^•LightBulbQ, createBl ind()). Klasa kliencka (Thef tAppl i cati on) ma dostęp jedynie do interfejsów AbstractFactory i AbstractProduct, nie jest więc świadoma pochodzenia produktów od konkretnych dostawców.

Dziedziczenie

i delegowanie we wzorcu projektowym

Fabryka

abstrakcyjna

Wzorzec projektowy Fabryka abstrakcyjna do rozdzielania interfejsu z jego realizacją wykorzystuje dziedziczenie specyfikacyjne. Ponieważ produkty różnych producentów nie mogą być dowolnie mieszane ze sobą: przykładowo w tym samym systemie domu inteligentnego nie jest możliwe równoczesne wykorzystywanie produktów EIBBlind i LuxmateBulb czy produktów EIBBul b i LuxmateBl i nd. Klient, by zapewnić sobie spójność marki otrzymywanych produktów (czyli odpowiedniość typów produkowanych obiektów), deleguje ich tworzenie

8.4. Wybór wzorców projektowych i gotowych komponentów

377

Rysunek 8.9. Zastosowanie wzorca projektowego Fabryka abstrakcyjna do zapewnienia współdziałania różnych platform tworzących infrastrukturę inteligentnego domu. Zależności między klasami odzwierciedlone są przez relacje «cal 1»

do fabryki, reprezentowanej przez abstrakcyjny interfejs (tu HouseFactory); nie jest on przy tym świadom konkretnej fabryki (EIBFactory albo LuxmateFactory) implementującej ów interfejs. Ten właśnie szczegół ułatwia przystosowywanie aplikacji do specyfiki różnych producentów — wystarczy zmienić implementację interfejsu HouseFactory, bez ingerowania w resztę aplikacji.

8.4.5. Hermetyzacja przepływu sterowania za pomocą wzorca projektowego Polecenie W systemach interaktywnych i systemach transakcyjnych często wymagane są funkcje wykonywania, cofania i rejestrowania żądań użytkownika, bez wnikania w treść tych żądań. Przykładowo w podsystemie zarządzania turniejami systemu ARENA (TournamentManagement) chcielibyśmy w każdym z meczów rejestrować kolejne ruchy graczy, by później odtwarzać je na żądanie kibiców. Ponieważ zakładamy przydatność systemu dla szerokiej gamy gier i wynikającą stąd jego niezależność od konkretnej gry, nie chcemy, by klasa odpowiedzialna za nagrywanie historii meczu uzależniona była od konkretnej gry, czyli od konkretnego znaczenia pojęcia „ruch". Dla osiągnięcia tego efektu możemy użyć wzorca projektowego Polecenie (patrz sekcja A.4 oraz książka E. Gammy i innych [Gamma i in., 1994]). Kluczem do odseparowania implementacji ruchów dla konkretnych gier od zarządzania samymi ruchami jako takimi jest reprezentowanie tych ruchów jako obiektów poleceń, wywodzących się z klasy abstrakcyjnej

378

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Command• (na rysunku 8.10 jest to klasa Move). Klasa ta deklaruje operacje wykonywania, cofania i rejestrowania poleceń, podczas gdy każda z klas ConcreteCommand- (czyli na przykład klasy Ti cTacToeMove i ChessMove) implementuje specyfikę tych czynności dla konkretnej gry. Zapewnienie zarządzania ruchami dla nowo dodanej gry sprowadza się do zaimplementowania stosownej subklasy klasy Move.

Rysunek 8.10. Zastosowanie wzorca projektowego Polecenie do rejestrowania historii meczów (Match) w systemie ARENA

Dziedziczenie

i delegowanie we wzorcu projektowym

Polecenie

Wzorzec projektowy Polecenie wykorzystuje dziedziczenie specyfikacyjne między klasami Command• i ConcreteCommand-, co umożliwia dodawanie nowych poleceń, niezależnie od klasy wywołującej (Invoker, tutaj klasa Match). Delegowanie odbywa się w dwóch miejscach: z klasy ConcreteCommand- do klasy Receiver- (tu GameBoard) oraz z klasy Invoker- do klasy Command-, co umożliwia dynamiczne tworzenie obiektów ConcreteCommand- oraz wykonywanie i rejestrowanie reprezentowanych przez nie poleceń. Wzorzec projektowy Polecenie wykorzystywany jest często w architekturze „model-widok-kontroler" (MVC): obiekty klasy Receiver są obiektami modelu, obiekty klas Invoker i Command-— obiektami kontrolera, zaś widoki są klientami (Cl i ent-) wydającymi polecenia.

8.4.6. Hermetyzacja hierarchii za pomocą wzorca projektowego Kompozyt Narzędzia dedykowane budowaniu interfejsów użytkownika, między innymi Swing i Cocoa, dostarczają programiście wiele „cegiełek", czyli specjalizowanych klas przeznaczonych do tego celu. Każda z tych klas prezentuje określone zachowanie — obsługę wprowadzanego tekstu, obsługę „naciskania" przycisków, rozwijanie menu i tym podobne. W ramach projektu interfejsu użytkownika poszczególne jego elementy mogą być łączone (agregowane) w okna w celu tworzenia interfejsów specyficznych dla danej aplikacji, przykładowo okno dialogowe konfigurowania preferencji zawiera zazwyczaj zbiór niezależnych pól wyboru (check box) odpowiadających poszczególnym cechom aplikacji.

8.4. Wybór wzorców projektowych i gotowych komponentów

379

W miarę jak okna te stają się coraz bardziej skomplikowane i obejmują coraz więcej kontrolek, zarządzanie nimi (przesuwanie, zmiana rozmiarów) w sposób zapewniający zachowanie układu i wyglądu stwarza coraz większe ryzyko wymknięcia się spod kontroli. W związku z tym, nowoczesne narzędzia opisywanej kategorii umożliwiają programiście organizowanie interfejsu użytkownika w sposób hierarchiczny: wybrane kontrolki grupowane zostają w panele, które same stają się złożonymi kontrolkami, bowiem każdym panelem zarządza się tak samo jak „zwykłą" kontrolką. W naszym przykładowym oknie wyboru preferencji (patrz rysunek 8.11) można wyodrębnić trzy takie panele: górny — obejmujący tytuł i krótką instrukcję dla użytkownika, środkowy — zawierający kontrolki wyboru i opatrujące je etykiety oraz dolny — zawierający przyciski OK i Cancel. Każdy panel odpowiedzialny jest za zawarte w nim kontrolki i „podpanele", całe okno dialogowe ma więc do czynienia jedynie z trzema panelami zamiast z kilkunastoma niezależnymi kontrolkami.

Rysunek 8.11. Anatomia okna wyboru preferencji. Agregaty zwane panelami grupują kontrolki w celu ich skonsolidowanego zachowania się przy przemieszczaniu i zmianie rozmiaru okna

Hierarchię klas wynikającą z opisanego grupowania przedstawiliśmy na diagramie obiektów widocznym na rysunku 8.12. Swing realizuje opisaną koncepcję za pomocą wzorca projektowego Kompozyt (patrz sekcja A.5 oraz książka E. Gammy i innych [Gamma i in., 1994]). Zastosowanie tego wzorca do przykładowego interfejsu użytkownika pokazane jest na rysunku 8.13. Abstrakcyjna klasa Component jest superklasą dla wszystkich kontrolek: pól wyboru (Checkbox), przycisków (Button) czy etykiet (Label). Composite, jako jedna z subklas klasy Component, jest specjalnym obiektem reprezentującym agregat, na przykład wspomniany wcześniej panel. Zauważmy, że klasa ta specjalizuje się w postaci dwóch subklas: Window, reprezentującej okno, oraz Panel, stanowiącej szczyt hierarchii agregatów; specjalizacja ta wiąże się z wyposażeniem obu klas w mechanizmy współpracy z przeglądarką (Wi ndow) i menedżerem okien (Panel).

8.4.7. Heurystyki wyboru wzorców projektowych Wybór właściwego wzorca projektowego dla rozwiązywania konkretnego problemu jest trudnym zadaniem, szczególnie dla programisty nieposiadającego doświadczenia w tej dziedzinie. Katalog znanych wzorców projektowych jest obszerny i wciąż ewoluuje, nie sposób więc wymagać od jakiegokolwiek programisty kompletnej jego znajomości. Ponieważ wzorce

380

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Rysunek 8.12. Hierarchia obiektów interfejsu przedstawionego na rysunku 8.11

Rysunek 8.13. Zastosowanie wzorca projektowego Kompozyt do organizacji widżetów środowiska Swing. „Korzeniem" hierarchii tego środowiska jest klasa Composite, zaś jej „liśćmi" — pojedyncze kontrolki (Checkbox, Button, Label i tym podobne). Węzły pośrednie tej hierarchii reprezentują agregaty (Panel, Window). Przesuwanie lub zmiana rozmiarów panelu ma wpływ na wszystkie zawarte w nim kontrolki p r o j e k t o w e p o z o s t a j ą w z w i ą z k u z k o n k r e t n y m i celami p r o j e k t o w y m i i w y m a g a n i a m i p o z a f u n k c y j n y m i , m o ż n a p o k u s i ć się o i d e n t y f i k o w a n i e w z o r c ó w p r o j e k t o w y c h n a p o d s t a w i e o k r e ś l o n y c h fraz języka n a t u r a l n e g o w y s t ę p u j ą c y c h w d o k u m e n t a c h analizy w y m a g a ń ( R A D )

381

8.4. Wybór wzorców projektowych i gotowych komponentów

i projektu systemu (SDD), zgodnie z heurystykami analogicznymi do heurystyk Abbotta, opisywanych w rozdziale 5. „Analiza wymagań". Przykłady wspomnianych takich heurystyk dla wzorców projektowych opisywanych w tym rozdziale przedstawiamy w tabeli 8.3. Heurystyki identyfikowania wzorców projektowych na podstawie fraz języka naturalnego Wzorce projektowe dedykowane są konkretnym celom projektowym i wymaganiom pozafunkcyjnym. Podobnie jak w przypadku heurystyk Abbotta, pewne frazy stanowią bardzo prawdopodobne kandydatury na użycie wzorców projektowych, zgodnie z tabelą 8.3.

Tabela 8.3. Heurystyczne reguły identyfikowania fraz języka naturalnego jako kandydatur na stosowanie wzorców projektowych

Fraza

Prawdopodobny wzorzec projektowy

„niezależność od dostawcy"

Fabryka abstrakcyjna

„niezależność od platformy" „musi być zgodny z istniejącym interfejsem"

Adapter

„musi współpracować z istniejącym komponentem" „musi mieć możliwość rozszerzania o nowe protokoły"

Most

„wszystkie polecenia muszą mieć możliwość wycofywania"

Polecenie

„wszystkie transakcje muszą być rejestrowane" „musi obsługiwać zagregowane struktury"

Kompozyt

„musi dopuszczać hierarchie o dowolnej głębokości i szerokości" „zasady muszą być oddzielone od realizujących je mechanizmów"

Strategia

„musi umożliwiać dynamiczną wymianę algorytmów podczas wykonywania programu"

8.4.8. Identyfikowanie i przystosowywanie frameworków aplikacyjnych Frameworki

aplikacyjne

Frameworkiem aplikacyjnym nazywamy częściową aplikację, przeznaczoną do wielokrotnego wykorzystywania, możliwą do przystosowywania potrzeb związanych z tworzeniem specjalizowanych aplikacji [Johnson i Foote, 1988], W odróżnieniu od bibliotek klas, frameworki dedykowane są konkretnym technologiom, takim jak przetwarzanie danych eksperymentalnych czy telefonia komórkowa, lub konkretnym dziedzinom aplikacyjnym, jak interfejsy użytkownika czy kontrola lotów w czasie rzeczywistym. Wykorzystywanie frameworków daje wyraźne korzyści w postaci wielokrotnego wykorzystywania tych samych rozwiązań i rozszerzalności. Możliwość wielokrotnego stosowania gotowych rozwiązań stanowi owoc zarówno doświadczeń zebranych w związku z dziedziną aplikacyjną, jak wysiłku programistów zmierzających do uwolnienia siebie (i kolegów z branży) od konieczności wielokrotnego rozwiązywania tych samych problemów ab ovo. Rozszerzalność frameworków

382

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

realizowana jest za pomocą tak zwanych metod zahaczanych (hook methodsf, które mogą być nadpisywane w docelowych aplikacjach. Metody zahaczane systematycznie oddzielają interfejsy od konkretnego zachowania, specyficznego dla danej dziedziny aplikacyjnej, co daje możliwość szybkiego implementowania nowych cech i usług. Frameworki, pod względem usytuowania w ogólnym procesie tworzenia aplikacji, podzielić można na trzy następujące kategorie: • Frameworki infrastrukturalne przeznaczone są do upraszczania samego procesu tworzenia aplikacji i bardzo rzadko dostarczane są klientowi końcowemu (który może nawet nie być świadom ich istnienia). Związane są między innymi z systemami operacyjnymi opisanymi przez R. H. Campbella i N. Islacna [Campbell i Islam, 1993], debuggerami omówionymi w książce B. Brueggego, T. Gottschalka i B. Luo [Bruegge i in., 1993], zadaniami komunikacyjnymi, które opisane zostały w pracy D. C. Schmidta [Schmidt, 1997], i projektowaniem interfejsów użytkownika omówionym przez A. Weinanda, E. Gammę i R. Martyego [Weinand i in„ 1988], Znanym przykładem frameworku tej kategorii jest Java Swing [JFC, 2009], • Frameworki pośredniczące wykorzystywane są do integrowania istniejących, rozproszonych aplikacji i komponentów. Wśród najbardziej znanych reprezentantów tej kategorii wymienić należy MFC i DCOM firmy Microsoft, Java WebObject opisany przez G. Wilsona i J. Ostrema [Wilson i Ostrem, 1999], WebSphere [IBM], WebLogic Enterprise Application [BEA], implementacje architektury CORBA [OMG, 2008] i transakcyjne bazy danych. •

Frameworki aplikacyjne związane są ze specyficznymi zastosowaniami informatyki w takich obszarach jak telekomunikacja, lotnictwo, modelowanie środowiska naturalnego, zarządzanie liniami produkcyjnymi, inżynieria finansowa omówiona w książce E. T. Birrera [Birrer, 1993] czy modelowanie procesów biznesowych [JavaEE, 2009],

Dwie pierwsze kategorie frameworków mają istotne znaczenie dla (ogólnie pojmowanego) szybkiego tworzenia systemów wysokiej jakości, lecz zwykle nie mają żadnego znaczenia z punktu widzenia użytkowników tych systemów. Frameworki aplikacyjne stanowią natomiast istotne wsparcie dla tworzenia aplikacji użytkowych. W rezultacie zakup gotowych rozwiązań dwóch pierwszych kategorii okazuje się wyraźnie bardziej opłacalny niż ich samodzielne tworzenie, o czym piszą M. E. Fayad i D. S. Hamu [Fayad i Hamu, 1997]. Frameworki, ze względu na możliwy sposób ich użytkowania, można także podzielić na dwie następujące grupy: •

5

Frameworki białoskrzynkowe — ich zastosowanie sprowadza się do możliwości wykorzystywania dziedziczenia po oferowanych klasach oraz ich dynamicznego wiązania, czyli do definiowania Idas pochodnych i nadpisywania predefiniowanych metod zahaczanych, zgodnie ze zdefiniowanymi wzorcami, co omawiają E. Gamma i inni [Gamma i in., 1994],

Zwanych także „punktami zaczepienia" — przyp-

tłum.

8.4. Wybór wzorców projektowych i gotowych komponentów

383

• Frameworki czarnoskrzynkowe — udostępniają one interfejsy dla komponentów, przeznaczonych do integrowania („podłączania") z aplikacją docelową. Gotowa funkcjonalność wykorzystywana jest poprzez definiowanie komponentów zgodnych ze wspomnianymi interfejsami, zaś korzystanie z tych komponentów odbywa się poprzez delegowanie odwołań. Korzystanie z frameworków białoskrzynkowych wymaga dogłębnej znajomości ich struktury wewnętrznej. Docelowa aplikacja staje się silnie związana z detalami odnośnego frameworku, przez co dokonywane wewnątrz niego zmiany wymagają zwykle ponownego kompilowania tej aplikacji. Frameworki czarnoskrzynkowe są łatwiejsze w użytkowaniu, którego podstawą jest delegowanie wywołań zamiast dziedziczenia klas. Stanowią one jednak poważniejsze wyzwanie dla swych twórców, którzy definiując interfejsy i metody zahaczane, muszą przewidywać możliwie jak najpełniejsze spektrum ich przyszłego użycia. Ponieważ jednak bazują na dynamicznych zależnościach między obiektami, a nie na statycznych zależnościach między klasami, są łatwiejsze w rozszerzaniu i dynamicznej rekonfiguracji, o czym piszą R. Johnson i B. Foote [Johnson i Foote,1988].

Frameworki,

biblioteki klas i wzorce

projektowe

Frameworki są ściśle powiązane ze wzorcami projektowymi, bibliotekami klas i komponentami. W przeciwieństwie do wzorców projektowych, frameworki koncentrują się na konkretnych projektach, algorytmach i ich implementacjach w konkretnym języku (językach) programowania, podczas gdy wzorce projektowe reprezentują abstrakcyjne koncepcje i obejmują niewielkie kolekcje współdziałających klas. Frameworki koncentrują się na konkretnych dziedzinach aplikacyjnych, podczas gdy wzorce projektowe mogą być traktowane raczej jako cegiełki do budowania frameworków. Biblioteki klas tym różnią się od frameworków, że są mniej specyficzne z punktu widzenia konkretnego zastosowania i inny jest charakter ich wykorzystywania: składniki takich bibliotek, dedykowane (przykładowo) przetwarzaniu napisów (łańcuchów), arytmetyce liczb zespolonych, tablicom czy zbiorom bitowym 6 nie odnoszą się bezpośrednio do konkretnych zastosowań, dostarczają jedynie niskopoziomowe mechanizmy pomocne w tych zastosowaniach. Biblioteki Idas są konstrukcjami biernymi — w tym sensie, że nie realizują ograniczeń ani nie determinują przepływu sterowania, w przeciwieństwie do aktywnych ze swej natury frameworków. W praktyce biblioteki klas są zarówno używane do tworzenia samych frameworków, jak i wykorzystywane na równi z frameworkami do budowania aplikacji docelowych, w których służą do implementowania odwołań zwrotnych (callbacks) w postaci specyficznego dla tych aplikacji kodu realizującego obsługę zdarzeń na poziomie przetwarzania łańcuchów, zarządzania plikami czy obliczeń numerycznych. 6

Zbiór bitowy to reprezentacja podzbioru pewnego wzorcowego zbioru X, w której każdego elementowi zbioru X odpowiada jeden bit; wartość 1 tego bitu oznacza przynależność elementu do podzbioru. Reprezentacja taka stosowana jest powszechnie w różnych odmianach języka Pascal do implementowania zmiennych typu Set ot T, które są podzbiorami zbioru zawierającego wszystkie wartości typu T — przyp. tłum.

384

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Komponenty są samoistnymi instancjami klas, łączonymi w formę kompletnej aplikacji; w kategoriach wielokrotnego wykorzystywania komponent jest czarną skrzynką definiującą spoisty zbiór operacji określanych przez składnię i semantykę interfejsu. W porównaniu z frameworkami komponenty mają relatywnie mniejszy stopień spoistości i mogą być wykorzystywane nawet na poziomie kodu binarnego — aplikacja może z nich korzystać bezpośrednio, bez konieczności definiowania subklas, niekoniecznie musi też być rekompilowana, gdy zmienia się implementacja samego komponentu. Wzajemna relacja między komponentami a frameworkami nie jest ściśle zdeterminowana: z jednej strony, frameworki mogą być używane do tworzenia komponentów, których interfejs stanowi fasadę (patrz sekcja A.6) dla wewnętrznej struktury klas frameworku; z drugiej jednak strony, komponenty mogą służyć do budowania frameworków czarnoskrzynkowych. Ogólnie jednak rzecz biorąc, frameworki wykorzystywane są w celu uproszczenia tworzenia infrastruktury i oprogramowania pośredniego (middleware), podczas gdy komponenty upraszczają opracowywanie konkretnych aplikacji użytkowych.

Przykład

frameworku

WebObjects to zbiór frameworków przeznaczonych do tworzenia interaktywnych aplikacji webowych wykorzystujących dane przechowywane w relacyjnych bazach danych. Ów „zbiór" to właściwie dwa frameworki infrastrukturalne: pierwszy, główny, o nazwie WebObjects, dedykowany jest zarządzaniu interakcją między przeglądarką a serwerem W W W ; drugi — Enterpri se Object Framework, w skrócie EOF — pełni funkcję adaptera umożliwiającego łączenie się aplikacji z bazą danych konkretnego producenta. Obecnie EOF dostarcza adaptery dla serwerów Informix, Oracle i Sybase oraz interfejsu ODBC. Bardziej szczegółowe informacje na temat EOF znajdą czytelnicy w książce G. Wilsona i J. Ostrema [Wilson i Ostrem, 1999]. Na rysunku 8.14 widoczny jest przykład realizacji dynamicznej witryny W W W zbudowanej przy użyciu WebObjects. Wszystko zaczyna się od wysłania przez przeglądarkę W W W (WebBrowser) żądania do serwera W W W (Webserver). Serwer W W W analizuje treść żądania i jeżeli stwierdzi, że dotyczy ono statycznej strony W W W , przekazuje je do obiektu Stati cHTML, który pobiera żądaną stronę i odsyła ją jako odpowiedź do przeglądarki wyświetlającej otrzymaną stronę użytkownikowi. Jeśli żądanie określa dynamiczną stronę W W W , przekazywane jest do obiektu WOAdaptor, który po wstępnym przetworzeniu przekazuje je do komponentu WebObjectsAppl i c a t i on. Na podstawie szablonów (Tempi a t e ) zdefiniowanych przez programistę i odpowiednich danych otrzymanych z bazy ( R e i a t i o n a l D a t a b a s e ) komponent WebObjectsApplication generuje wynikową stronę W W W , która za pośrednictwem komponentu WOAdaptor odsyłana jest z powrotem do serwera Webserver. Serwer odsyła otrzymaną stronę do przeglądarki, która sformułowała żądanie. Kluczową abstrakcją udostępnianą przez WebObjects jest rozszerzenie protokołu HTTP o funkcję zarządzania stanem. Jak wiadomo, protokół HTTP jest z definicji protokołem bezstanowym; dla każdego żądania generowana jest odpowiedź i nic ponadto — nie jest utrzymywana żadna informacja dotycząca powiązań między poszczególnymi żądaniami. Informacja taka wymagana jest jednak przez większość aplikacji webowych, na przykład w systemie ARENA trudno wymagać od gracza, by identyfikował się przed wykonaniem każdego ruchu; co więcej, gracz musi mieć możliwość kontynuowania meczu nawet w przypadku zamknięcia i ponownego

8.4. Wybór wzorców projektowych i gotowych komponentów

385

Rysunek 8.14. Przykład realizacji dynamicznej witryny W W W przy użyciu WebObjects

uruchomienia przeglądarki. Aplikacje webowe wypełniają tę lukę za pomocą różnorodnych środków, między innymi cookies, dynamicznie generowanych URL-i oraz ukrytych pól w formularzach HTML. WebObjects zdolny jest wyręczyć je w tym dziele, oferując mechanizm przedstawiony na rysunku 8.15.

Rysunek 8.15. Klasy frameworku WebObjects odpowiedzialne za zarządzanie informacją o stanie żądań (State Management Classes)

Obiekt WOAppl i c a t i o n reprezentuje aplikację uruchomioną na serwerze W W W (Webserver), oczekującą na żądania ze zdalnej przeglądarki (WebBrowser). Cykl sekwencji „żądanie-odpowiedż" rozpoczyna się w momencie, gdy WOAdaptor odbiera żądanie HTTP. Po spakowaniu do postaci obiektu WORequest żądanie to przekazywane jest do obiektu WOAppl i cati on. Za śledzenie stanu poszczególnych sesji odpowiedzialna jest klasa WOSessi on, umożliwiająca śledzenie oddzielnie każdego dialogu z użytkownikiem, nawet w ramach tej samej instancji aplikacji. Obiekt klasy WOSessi on powiązany jest z jednym lub kilkoma obiektami klasy WOComponent, reprezentującymi powtarzalne strony W W W (lub ich fragmenty)

386

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

wyświetlane w ramach poszczególnych sesji. Obiekty WOComponents mogą zawierać elementy dynamiczne (Dynami cElement), odpowiedzialne za reprezentowanie treści pobieranych z bazy danych. W ten oto sposób tworzona jest dynamicznie informacja o stanie sesji; obiekt WOSessi on przechowuje ją na serwerze, korzystając z usług obiektu WOSessi onStore, i odtwarza przy każdym żądaniu wchodzącym w skład tej właśnie sesji. Proces tworzenia aplikacji webowej przy użyciu WebObjects sprowadza się zasadniczo do odpowiedniego precyzowania klas WOAppl i cati on, WOSessi on i WOComponent oraz przechwytywania żądań przepływających między nimi. Precyzowanie to rozpoczyna się od momentu tworzenia obiektu WOAppl i cati on (poprzez odpowiedni konstruktor), a kończy w chwili kończenia jego pracy (przez przedefiniowanie metody terminate()) i — oczywiście — przebiega przez szereg etapów pośrednich specyficznych dla aplikacji docelowej.

8.5. Zarządzanie wykorzystywaniem gotowych rozwiązań Historycznie rzecz biorąc, tworzenie oprogramowania rozwijało się jako rzemiosło, a każda aplikacja tworzona była zgodnie z wymaganiami pojedynczego klienta, w sposób określony przez reguły pragmatyki właściwej konkretnej manufakturze. Koszt oprogramowania był niewielki w porównaniu z kosztem sprzętu komputerowego 7 , wobec czego same komputery i ich zastosowania były rarytasem, na który stać było tylko nielicznych. Czasy się zmieniły, osiągnięcia technologiczne i reguły rynku wymusiły drastyczny spadek cen sprzętu, komputery stały się urządzeniami elektroniki użytkowej, na równi z telewizorami. Rosnące wykładniczo z czasem możliwości tanich komputerów znacznie zwiększyły zarówno popyt na ich zastosowania, jak i potencjalne możliwości tych zastosowań. Od komputerów wymaga się coraz więcej (czemu starają się sprostać coraz szybsze procesory, coraz pojemniejsze dyski i tym podobne) znacznie bardziej skomplikowanych rzeczy — co znajduje swój wyraz w rosnącej złożoności tworzonego oprogramowania i zwiększających się kosztach jego wytwarzania. Tendencje te osiągnęły już punkt krytyczny, co zmusiło menedżerów do poszukiwania możliwości zarówno w zakresie zmniejszania kosztów tworzenia systemów informatycznych, jak i pod względem sposobów ułatwiających radzenie sobie z ich złożonością. Wobec braku cudownego panaceum, w tym względzie sprawdzonymi i skutecznymi metodami osiągania tych celów okazały się zabiegi zmierzające do jak największego wykorzystywania istniejących (gotowych) rozwiązań. Wielokrotne używanie — wzorców projektowych, frameworków i komponentów — niesie ze sobą liczne korzyści zarówno w aspekcie technicznym, jak i menedżerskim. Oto one. • Mniejszy wysiłek programistów. Używając gotowych rozwiązań lub komponentów, oszczędzamy sobie wysiłku związanego z rozwiązywaniem standardowych problemów; co więcej, system budowany w zgodzie z wzorcami projektowymi daje się łatwiej rozbudowywać i jest bardziej elastyczny pod względem typowych zmian. Programiści mogą zainwestować swój wysiłek w sposób bardziej produktywny, między innymi w lepsze testowanie zmierzające do uzyskania lepszej jakości.

7

Co więcej, wobec niebotycznych cen sprzętu, liczonych często w milionach dolarów, żądanie dodatkowych pieniędzy za oprogramowanie wydawało się klientowi wręcz niemoralne — przyp. tłum.

8.5. Zarządzanie wykorzystywaniem gotowych rozwiązań

387

• Mniejsze ryzyko. Używając wielokrotnie tych samych wzorców i (lub) komponentów, lepiej potrafimy przewidywać przyszłe problemy. Ponadto łatwiej możemy szacować czasochłonność integrowania komponentów i adaptowania wzorców projektowych do konkretnych potrzeb, co w rezultacie daje proces bardziej przewidywalny i obarczony mniejszym ryzykiem. •

Upowszechnienie standardowej terminologii. Konsekwentne wykorzystywanie wzorców projektowych i komponentów ze standardowego zbioru stanowi zachętę do posługiwania się standardowym słownictwem. Określenia, takie jak Adapter, Most, Polecenie czy Fasada, niosą ze sobą precyzyjne znaczenie dotyczące koncepcji, które powinny być znane każdemu programiście. Zmniejsza to dowolność terminologiczną i zarazem ryzyko nieporozumień przy rozwiązywaniu powszechnych problemów projektowych.



Większa niezawodność. Wielokrotne wykorzystywanie gotowych rozwiązań samo z siebie nie daje gwarancji niezawodności ani też nie umniejsza potrzeby testowania, czego dowodem może być historia z rakietami Ariane 501 opisywana na początku sekcji 3.1. Jednakże wzorce i komponenty wykorzystywane w jednym kontekście mogą obnażyć usterki i uczynić ewidentnymi problemy, które mogłyby pojawić się w innym kontekście. Ponadto opisywane wcześniej przesłanki — lepsze testowanie podsystemu, dzięki oszczędności czasu uzyskanej przy jego tworzeniu, lepsze prognozowanie problemów i promowanie standardowej terminologii — przekładają się w oczywisty sposób na większą niezawodność tworzonego oprogramowania.

Niestety, perspektywa korzystania z gotowych rozwiązań nie zawsze traktowana jest entuzjastycznie w firmach programistycznych, za sprawą wielu przesłanek; oto najczęściej spotykane. •

Syndrom

„to nie nasz wynalazek"

( N I H — Not Invented

Here).

Jako że e d u k a c j a

informatyczna koncentruje się (dotychczas) raczej na tworzeniu nowych rozwiązań niż na korzystaniu z istniejących, propozycje korzystania z gotowych rozwiązań traktowane są przez programistów nieufnie, zwłaszcza gdy wiążą się z pewnymi ograniczeniami. Programiści są wówczas przekonani, że samodzielnie potrafią wyprodukować rozwiązanie znacznie lepiej przystosowane do rozwiązania konkretnego problemu (co przeważnie jest prawdą) i to w czasie krótszym od tego, jaki potrzebny byłby na konfigurowanie gotowego rozwiązania (co zwykle jest mrzonką). Co gorsza, korzyści ze stosowania gotowych rozwiązań widoczne są dopiero w dłuższej perspektywie, podczas gdy splendor (i inne korzyści) wynikający z samodzielnego opracowania wspaniałego kodu daje natychmiastową satysfakcję. • Nikłe wsparcie dla procesów. Procesy związane z identyfikowaniem, wykorzystywaniem i przystosowywaniem istniejących rozwiązań mają charakter inny niż te związane z tworzeniem rozwiązań całkowicie nowych. Aktywności pierwszej grupy to przede wszystkim odnajdywanie i skrupulatne selekcjonowanie dostępnych technologii i produktów, a także ich wszechstronne ocenianie i wzbogacanie własnych doświadczeń w tej materii. Druga grupa aktywności wymaga natomiast kreatywności i doskonałego rozumienia problemu. Znakomita większość narzędzi i metod programistycznych bardzo dobrze przystosowana jest do owej drugiej grupy, brakuje

388

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

natomiast tendencji promujących wielokrotne wykorzystywanie efektów raz wykonanej pracy. Przykładowo istnieje wiele katalogów wzorców projektowych, trudno jednak znaleźć jakąś systematyczną metodę, która pozwalałaby programistom nowicjuszom na szybkie identyfikowanie wzorca najbardziej użytecznego w rozwiązywaniu określonego problemu. • Szkolenie i trening. Wobec braku stosownych narzędzi wspierającym wielokrotne wykorzystywanie gotowych rozwiązań, jedyną bodaj drogą budowania kultury programistycznej w tym względzie pozostają szkolenia i treningi. Nie trzeba dodawać, że ciężar ich organizowania spada na barki firm zatrudniających programistów. W następnych sekcjach przyjrzymy się dokumentowaniu wykorzystywania gotowych rozwiązań, jak również ustanawianiu ról związanych z rozwiązywaniem opisanych powyżej problemów.

8.5.1. Dokumentowanie wykorzystywania gotowych rozwiązań Wykorzystywanie gotowych rozwiązań wymaga każdorazowo działań dokumentacyjnych dwojakiego rodzaju: dokumentowania zastosowanego rozwiązania wzorcowego oraz dokumentowania systemu, na potrzeby którego rozwiązanie to zostaje użyte. Dokumentacja powtarzalnego rozwiązania (wzorca projektowego, frameworku, komponentu) powinna obejmować nie tylko opis samego rozwiązania, lecz także wskazanie problemów, które rozwiązanie to potencjalnie zdolne jest rozwiązywać, kompromisów, jakie prawdopodobnie rozstrzygać będzie musiał w związku z tym programista, jak również propozycje rozwiązań alternatywnych, no i — oczywiście — przykłady typowych zastosowań. Dokumentacje taką tworzy się jednak bardzo trudno, bowiem jej autor nie jest w stanie przewidzieć wszystkich przyszłych problemów związanych z jej wykorzystywaniem. Ponadto dokumentacja taka z konieczności jest raczej generyczna, abstrakcyjna i początkujący programiści mogą ją zrozumieć jedynie na podstawie reprezentatywnych przykładów. Dokumentacja ta jest więc daleka od ideału, niemniej jednak można łagodzić jej niedostatki przez sukcesywne uzupełnianie przez samych programistów wykorzystujących opisywane rozwiązanie. Wśród elementów kwalifikujących się na takie uzupełnienia wymienić można między innymi: •

Odwołania

do systemów

używających

przedmiotowego

rozwiązania,

czyli p r z y n a j m n i e j

zestawienie takich systemów. Jeśli bowiem we wspomnianym rozwiązaniu odkryty zostanie błąd, oznaczać będzie potencjalne zagrożenia dla każdego z tych systemów. • Przykłady zastosowań. Przykłady są najbardziej skutecznym mechanizmem ułatwiającym rozumienie skomplikowanych koncepcji i mechanizmów, czyli również zalet i wad konkretnego rozwiązania. Każdy przykład powinien zawierać opis problemu, w związku z którym programiści sięgnęli po odnośne rozwiązanie. • Rozważane rozwiązania alternatywne. Jak mieliśmy okazję przekonać się w tym rozdziale, wiele wzorców projektowych jest bardzo podobnych do siebie. Stwarza to ryzyko wyboru wzorca niewłaściwego dla danego problemu, który to wybór może powodować problemy znacznie większe niż opracowywanie własnego rozwiązania. Programiści powinni więc udokumentować swój wybór, wskazując przesłanki, dla których zrezygnowali z takiego czy innego rozwiązania alternatywnego.

8.5. Zarządzanie wykorzystywaniem gotowych rozwiązań

389

• Rozstrzygane kompromisy. Używanie gotowych rozwiązań, zwłaszcza wybór właściwego frameworku czy komponentu, wiąże się często z koniecznością godzenia sprzecznych wymagań. Przykładowo z dwóch komponentów do wyboru jeden oferować może wspaniale rozszerzalny interfejs i jednocześnie kiepską wydajność, drugi natomiast może być niesamowicie wydajny, ale posiadać mało elastyczny interfejs. Dokumentacja wykorzystania wzorcowych rozwiązań w systemie powinna przynajmniej wyszczególniać każde z tych rozwiązań. Użycie danego wzorca projektowego zwykle nie jest bezpośrednio widoczne w kodzie programu, bowiem nazwy klas uczestniczących w tym wzorcu różnią się od nazw wykorzystywanych w jego standardowej specyfikacji. Wiele wzorców okazuje się pożytecznych głównie dzięki odseparowaniu od siebie pewnych klas (na przykład klienta Mostu od jego implementacji), zatem klasy te powinny konsekwentnie pozostawać rozseparowane w trakcie przyszłych zmian wprowadzanych do systemu. Podobnie jawne opisanie, która klasa korzysta z których gotowych dokumentów, ułatwi późniejsze przystosowywanie klas klienckich do nowszych wersji wspomnianych komponentów. Podobnie pożyteczne okażą się powiązania wykorzystywanych gotowych rozwiązań z odnośnymi fragmentami kodu źródłowego, zapisane jako uzupełnienie standardowej dokumentacji projektowania obiektów (patrz rozdział 9. „Projektowanie obiektów: specyfikowanie interfejsów"). Znaczącym czynnikiem zwiększania kosztu przyszłych zmian w systemie jest utrata kontekstu projektowego: programiści szybko zapominają o przyczynach, dla których użyli takiej czy innej skomplikowanej struktury danych bądź dla których zastosowali takie, a nie inne obejście (jakiegoś) problemu. Wzrasta wówczas ryzyko wprowadzenia nowych błędów do systemu. W tym kontekście (nomen omen) krytycznego znaczenia nabiera dokumentowanie wszelkich kompromisów, przykładów, rozwiązań alternatywnych i innych istotnych informacji tego typu. Problemem systematycznego zbierania i dokumentowania takich informacji zajmiemy się dokładniej w rozdziale 12. „Zarządzanie racjonalizacją".

8.5.2. Przydzielanie odpowiedzialności Trudno oczekiwać od poszczególnych programistów, że spontanicznie rzucą się do wykorzystywania gotowych wzorców i komponentów, jeśli nie mają w tym temacie odpowiedniego doświadczenia. Można ich jednak do tego zachęcić, organizując łatwy dostęp do niezbędnej wiedzy, a konkretnie — do porad ze strony doświadczonych programistów. Zmniejszy to zdecydowanie frustrację związaną z opanowywaniem nowej, trudnej wiedzy. Także eksponowanie w czasie przeglądów systemu wykorzystywania (albo niewykorzystywania) gotowych komponentów zwiększyć może atrakcyjność tej techniki wśród programistów. W związku z tym, menedżer projektu może wyznaczyć odpowiednim programistom role podobne do poniższych: • Ekspert komponentu. To programista posiadający obszerną wiedzę na temat określonego komponentu, być może uzyskaną w drodze szkolenia u producenta tegoż komponentu. • Ekspert wzorca projektowego. To rola analogiczna do roli eksperta komponentu, programista pełniący tę rolę posiada doświadczenie w zakresie stosowania jednego lub kilku wzorców. Doświadczenie to zdobywa jednak zwykle we własnym zakresie.

390

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

• Dokumentalista techniczny. Dokumentalista musi być świadom wykorzystywania poszczególnych wzorców i komponentów oraz powinien dokumentować zależności między komponentami, wzorcami projektowymi i systemem, zgodnie z dyskusją w poprzedniej sekcji. Wymaga to znajomości zwyczajowo stosowanych w firmie rozwiązań i dotyczącej tych rozwiązań terminologii. • Menedżer konfiguracji. W uzupełnieniu do śledzenia konfiguracji i wersji poszczególnych podsystemów, menedżer konfiguracji musi być także świadom wersji używanych komponentów. Gdy tylko pojawią się nowe wersje komponentów, ich zastosowanie (oczywiście, udokumentowane) musi być poprzedzone stosownymi testami. Techniczne środki umożliwiające korzystanie z gotowych rozwiązań (dziedziczenie, delegowanie, wzorce projektowe, frameworki aplikacyjne) dostępne są dla programistów od prawie dwudziestu lat. Sukces w ich wykorzystywaniu uwarunkowany jest dziś jednak nie tyle zabiegami technologicznymi, ile menedżerskimi. Tylko firmy z wypracowaną kulturą korzystania z gotowych wzorców projektowych i komponentów, posiadające narzędzia umożliwiające skuteczną selekcję dostępnej ich oferty, mogą odnosić rzeczywiste korzyści z ich stosowania.

8.6. Analiza przypadku — system ARENA Pokażemy teraz, jak w systemie ARENA wykorzystać można trzy wzorce projektowe. Zgodnie z wymaganiami, system obsługiwać ma wiele różnych typów gier, w tej sekcji skupimy się więc na klasach związanych z grami (Game), meczami (Match) i na obiektach brzegowych. Realizując postulat niezależności systemu od konkretnej gry, wykorzystamy trzy następujące wzorce projektowe: • Fabrykę abstrakcyjną (patrz sekcja 8.6.1) w celu oddzielenia obiektów Tournament i League od obiektów specyficznych dla konkretnych gier. Rolę fabryki pełnić będzie klasa Game, stanowiąca abstrakcję dowolnej gry. Dzięki temu dla konkretnej gry automatycznie tworzone będą obiekty właściwych typów, bez żadnego nadzoru ze strony systemu. • Polecenie (patrz sekcja 8.6.2) — ten wzorzec posłuży nam do odizolowania obiektów związanych z rozgrywaniem i odtwarzaniem meczów (abstrakcyjna klasa Move reprezentująca ruchy graczy) od obiektów specyficznych dla konkretnych gier. •

Obserwator (patrz sekcja 8.6.3), dzięki któremu nadamy standardową formę interakcjom występującym między obiektami encji Match i Move a obiektami widoków MatchVi ew, dla wszystkich gier, za pomocą paradygmatu „subskrybent-publikator".

Ograniczymy się przy tym do gier polegających na wymianie ruchów (Move) między graczami (PI ayer), pomijając na razie problem gier obejmujących równoległe akcje ze strony wielu graczy.

8.6.1. Zastosowanie wzorca projektowego Fabryka abstrakcyjna Osiągnięcie niezależności systemu ARENA od konkretnej gry nie jest zadaniem tak prostym, jak mogłoby się zrazu wydawać. W modelu analitycznym (patrz rysunek 8.16) zdefiniowaliśmy

10.6. Analiza przypadku — system ARENA

391

Rysunek 8.16. Obiekty modelu analitycznego systemu ARENA związane z jego niezależnością od konkretnej gry

abstrakcyjny interfejs Game, oddzielający obiekty Tournament i League od obiektów reprezentujących konkretne gry. Problem jednak w tym, że wsparcie systemu dla konkretnej gry wymaga ścisłej współpracy wielu obiektów reprezentujących reguły tej gry (Game), stany trwających meczów (Match) oraz wyników (Result) gromadzonych w statystykach ( S t a t i s t i c s ) turniejów i lig. Musimy zatem zbudować w systemie ARENA środowisko oddzielające obiekty Tournament i League od konkretnych gier (specjalizacji klasy Game), przy jednoczesnym zachowaniu standardowych interakcji między specjalizacjami klas Game, Match, Move, Result i Statistics. Zaczniemy od wzorca projektowego Fabryka abstrakcyjna, realizując cel projektowy niezależności systemu od konkretnej gry (patrz rysunek 8.17). Abstrakcyjny interfejs Game pełni rolę wspomnianej fabryki, udostępniając metody createMatch() i c r e a t e S t a t i s t i c s Q , za pomocą których tworzone są specjalizacje obiektów8 Match i Stati s t i cs dla konkretnej gry — w kółko i krzyżyk (TicTacToe) albo szachów (Chess): dla pierwszej z wymienionych są to obiekty TTTMatch i TTTStats, dla drugiej — obiekty ChessMatch i ChessStats. Konkretne specjalizacje obiektu Match (TTTMatch i ChessMatch) odpowiedzialne są za śledzenie stanu trwających meczów i wymuszanie reguł określonych przez specjalizacje obiektu Game (Ti cTacToe i Chess). Każdy z tych ostatnich obiektów posiada stowarzyszony obiekt będący specjalizacją obiektu S t a t i s t i cs, odpowiedzialny za gromadzenie statystyki wartości średnich (między innymi średniej długości meczu w danej grze, średniej liczby ruchów w meczu danej gry, liczby przegranych i wygranych w danej grze dla każdego z graczy i specyficznych statystyk związanych z samą grą). Specjalizacje obiektu Stati s t i cs przypisane są także do każdego turnieju (Tournament) i każdej ligi (Leaciuel w celu gromadzenia statystyk na poziomie poszczególnych turniejów i lig. Ponieważ ol i v Tournament i League odwołują się do całej opisanej reszty za pośrednictwem abstrrl n ; c h interfejsów Game, Match i S t a t i s t i c s , funkcjonują identycznie dla wszystkich gier zgounych ze strukturą systemu ARENA.

8

Dla uproszczenia piszemy o „specjalizacjach obiektów", choć prawidłowo powinno się pisać o „specjalizacjach klas": dla uproszczenia, pod pojęciem „specjalizacji obiektu T" rozumieć będziemy „obiekt będący instancją klasy stanowiącej specjalizację klasy T" — przyp. tłum.

392

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

Rysunek 8.17. Zastosowanie wzorca projektowego Fabryka abstrakcyjna w systemie ARENA

8.6.2. Zastosowanie wzorca projektowego Polecenie Mimo iż kibice ( S p e c t a t o r ) mogą oglądać mecze (Match) „na żywo", przewidujemy także konieczność zapewnienia odtwarzania meczów już zakończonych. Do tego potrzebne jest rejestrowanie sekwencji ruchów w każdym meczu, które to sekwencje mogą być odtwarzane na żądanie. Jak już pisaliśmy w sekcji 8.4.5, zrealizujemy to zadanie za pomocą wzorca projektowego Polecenie, w ramach którego każdy ruch reprezentowany będzie jako specjalizacja obiektu Command-. Odpowiadający m u obiekt Move dostarcza w tym celu odpowiedni interfejs dla obiektów Tournament i League, umożliwiając im manipulowanie kolejnymi ruchami graczy w sposób niezależny od konkretnej gry (Game) i konkretnego meczu (Match). Poszczególne ruchy rejestrowane są — w postaci obiektów stanowiących specjalizacje obiektów Move — w kolejce związanej z odpowiednim meczem (Match), tak jak na rysunku 8.18.

Rysunek 8.18. Zastosowanie wzorca projektowego Polecenie do rejestrowania i odtwarzania ruchów graczy w poszczególnych meczach

10.6. Analiza przypadku — system ARENA

393

Z odtwarzaniem archiwalnych meczów wiąże się kolejny problem, wynikający z konieczności obsługi wielu kibiców chcących oglądać ten sam mecz równocześnie (i w różnych stadiach zaawansowania). Niezależność tę realizują obiekty Repl ayedMatch. Każdy z nich posiada własną planszę (GameBoard) przechowującą aktualny stan odtwarzanego meczu (Match), z którego kolejki czerpie kolejno specjalizacje obiektów Move reprezentujące kolejne ruchy. Kolejne obiekty Repl ayedMatch mogą w danym czasie odczytywać tę kolejkę w różnych miejscach, co uniezależnia od siebie poszczególnych kibiców.

8.6,3. Zastosowanie wzorca projektowego Obserwator Gry rozgrywane na forum systemu ARENA mogą angażować wielu graczy. Każdy z nich uczestniczy w meczu za pośrednictwem przeglądarki W W W w swoim komputerze, a to oznacza, że z danym meczem związanych jest jednocześnie wiele widoków, które muszą być utrzymywane w spójny sposób. Zadanie to zrealizujemy za pomocą rozproszonej wersji wzorca projektowego Obserwator (patrz rysunek 8.19). Rolę obserwatorów (Observer) pełnią tu odpowiednie obiekty brzegowe każdego klienta, zaś obserwowanym tematem (Subject) jest plansza gry (GameBoard). Powiązania między obiektami Observer i Subject realizowane są za pomocą zdalnych referencji oferowanych przez Java RMI. Oprócz utrzymywania spójności każdego widoku z obserwowanym meczem, rozwiązanie to umożliwia zastosowanie tego samego schematu dla wszystkich obiektów Repl ayedMatch.

Rysunek 8.19. Zastosowanie wzorca projektowego Obserwator do utrzymywania spójności widoków w systemie ARENA

8.6.4. Wnioski Pokazaliśmy praktyczne zastosowanie trzech wybranych wzorców projektowych w realizacji celu, jakim jest niezależność systemu ARENA od konkretnych gier. Nawet ten prosty przykład dostarczył dowodów na to, że: •

Zastosowanie

różnych

wzorców

projektowych

może

się nakładać

w tym

samym

systemie, przykładowo klasa Match uczestniczy w dwóch wzorcach: we wzorcu Fabryka abstrakcyjna pełni rolę abstrakcyjnego produktu (AbstractProduct-), zaś we wzorcu Polecenie — rolę inicjatora poleceń (Invoker).

394

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych •

Wybór

odpowiedniego

wzorca projektowego

nie jest łatwym

zadaniem.

Dostępnych

wzorców projektowych jest dużo, są zebrane w różnych katalogach i ich trafna ocena pod kątem przydatności w danym zastosowaniu wymaga sporego doświadczenia. To ważki argument na rzecz dokumentowania wzorców projektowych także w kontekście przykładów ich zastosowań, na użytek programistów mniej doświadczonych. •

Wzorce projektowe wymagają przystosowywania. Każdy wzorzec projektowy jest jedynie „surowym" szablonem, który musi zostać zaadaptowany do specyfiki konkretnego problemu. W procesie tej adaptacji należy bardzo uważnie przeanalizować każde użycie dziedziczenia i delegowania, by nie zniwelować korzyści wnoszonych przez użyty wzorzec projektowy.

8.7. Literatura uzupełniająca Dziedziczenie klas stawia przed programista wiele wyzwań. Mimo iż dostarcza potężnego mechanizmu umożliwiającego budowanie klas i komponentów wielokrotnego użytku, to jednocześnie pozwala na tworzenie skomplikowanych struktur sterowania, trudnych w zrozumieniu i testowaniu, prowadzących do kreowania zagmatwanych systemów. Od samego początku, gdy tylko pojawiło się na gruncie języków programowania, teoretycy i badacze podejmowali próby odróżnienia „złego" użycia dziedziczenia od „dobrego" i opracowania konkretnych zasad projektowych w tym temacie. Marvin Minsky jest pomysłodawcą wykorzystania dziedziczenia na gruncie firameworku Frames, służącego do reprezentowania wiedzy w systemach sztucznej inteligencji [Minsky, 1975], Koncepcja dziedziczenia została nieco później udoskonalona i pojawiła się na gruncie pierwszych obiektowych języków programowania, takich jak Smalltalk opisany w pracy A. Goldberg i A. Kay [Goldberg i Kay, 1976]. Rumbaugh i jego koledzy wprowadzili po raz pierwszy dziedziczenie do modelowania, w postaci języka o nazwie OMT [Rumbaugh i in., 1991], który wywarł duży wpływ na postać języka UML. Zasada zastępowania Liskov [Liskov, 1988], określająca formalne zasady rozróżniania między dziedziczeniem specyfikacyjnym a implementacyjnym i definicję podtypu, jakkolwiek prosta i zrozumiała, trudna jest w zastosowaniu jako zasada projektowania. W swojej książce [Meyer, 1997], której pierwsze wydanie ukazało się w roku 1989, B. Meyer formułuje zasadę, że klasy abstrakcyjne powinny być otwarte na rozszerzanie i jednocześnie zamknięte pod względem możliwości ich modyfikowania. Zasadę ta rozwinięta jest książce J. Martina i J. J. Odella [Martin i Odell, 1992], Prawdziwym przełomem było ukazanie się książki E. Gammy, R. Heima, R. Johnsona i J. Vlissadesa [Gamma i in., 1994] definiującej nowe podejście do wielokrotnego wykorzystywania gotowych rozwiązań, oferującej szablonowe rozwiązania dla problemów najczęściej pojawiających się w każdym niemal projekcie. Przez kombinowanie, krzyżowanie i nakładanie poszczególnych wzorców programiści zyskują potężne narzędzie do rozwiązywania wielu problemów związanych z rozszerzalnością i wielokrotnym wykorzystywaniem tego samego kodu. Koncepcja reprezentowania wiedzy w postaci wzorców stała się tak popularna, że inni autorzy postanowili rozszerzyć ją na inne obszaiy zastosowań, takie jak architektura oprogramowania, opisana przez F. Buschmanna, R. Meuniera, H. Rohnerta, P. Sommerlada i M. Stała [Buschmann i in., 1996], i analiza wymagań omówiona przez M. Fowlera [Fowler, 1997].

8.8. Ćwiczenia

395

8.8. Ćwiczenia 8.1. Dla każdego z poniższych obiektów modelu systemu ARENA wskaż przynależność do konkretnej dziedziny — aplikacyjnej albo realizacyjnej: •

League,



LeagueStore,



LeagueXMLStorelmpl ementor,



Match,



MatchVi ew,



Move,



ChessMove.

8.2. Które z poniższych przykładów dziedziczenia odzwierciedlają dziedziczenie specyfikacyjne, a które dziedziczenie implementacyjne? • Klasa Rectangl e (prostokąt) dziedzicząca po klasie Polygon (wielokąt). • Klasa Set (zbiór) dziedzicząca po klasie Bi naryTree (drzewo binarne). • Klasa Set dziedzicząca po klasie Bag (nieuporządkowana kolekcja). • Klasa PI ayer (gracz w systemie ARENA) dziedzicząca po klasie User (użytkownik systemu ARENA). • Klasa Window (okno) dziedzicząca po klasie Polygon. 8.3. Przypuśćmy, że chcemy zintegrować z systemem ARENA popularny brydż, zaimplementowany w języku Java. Który wzorzec projektowy będzie najodpowiedniejszy do tego celu? Narysuj diagram klas UML odnoszący się do obiektów systemu ARENA, zawierający niektóre klasy specyficzne dla brydża. 8.4. Rozpatrzmy system organizujący pracę programistów, umożliwiający menedżerowi modelowanie tej pracy w kategoriach aktywności i produktów. Dla każdego z programistów menedżer ustala listę aktywności oraz terminy dostarczania poszczególnych produktów. W systemie przewidziano obsługę różnych typów produktów: sformatowanego testu, grafiki i lokałizatorów URL. Menedżer edytujący przepływ pracy może dynamicznie określać (zmieniać) typ istniejącego lub nowo dodawanego produktu. Przyjmując za jeden z celów projektowych oczywiste założenie, iż system opisywany musi być rozszerzalny o nowe typy produktów, jaki wzorzec projektowy wybrałbyś do ich reprezentowania? 8.5. Wyobraźmy sobie aplikację bazodanową składającą się ze strony klienckiej i dwóch redundantnych serwerów. Oba serwery są identyczne, drugi pełni rolę zapasowego na wypadek awarii pierwszego. Dostęp klienta do strony serwerowej odbywa się za pośrednictwem komponentu zwanego „bramą", klient nie wie zatem, do którego z serwerów jest aktualnie podłączony. Osobny obiekt, zwany „Cerberem", monitoruje ciąg żądań napływających do serwera głównego oraz produkowanych przez niego odpowiedzi i zależnie od wyników tego monitorowania może wysłać do wspomnianej „bramy" żądanie przełączenia serwera na zapasowy. Jak nazwałbyś ten wzorzec projektowy? Uzasadnij swoją odpowiedź, rysując odpowiedni diagram klas UML.

396

Rozdział 8. • Projektowanie obiektów: wielokrotne wykorzystywanie rozwiązań wzorcowych

8.6. W sekcji 8.4.1 opisaliśmy wykorzystywanie wzorca projektowego Most do zaimplementowania podsystemu LeagueStore, w celu ułatwienia testowania jego integracji z innymi podsystemami. Byłoby wspaniale, gdyby ten zabieg można było zastosować do pozostałych podsystemów (w celu łatwiejszego ich testowania), lecz — niestety — nie zawsze jest to możliwe. Podaj przykład podsystemu, w którym nie da się zastosować wzorca projektowego Most. 8.7. Wskaż listę wzorców projektowych, których zastosowanie rozważyłbyś dla każdego z poniższych celów projektowych: • Należy wykorzystać komponent realizujący logikę biznesową w obecnie eksploatowanej aplikacji bankowej. « Dla tworzonego programu szachowego należy uwzględnić możliwość przyszłej wymiany algorytmu planowania kolejnego ruchu na lepszy wariant. • Należy wyposażyć program szachowy w komponent monitorujący styl gry oraz czas reakcji przeciwnika i na tej podstawie przełączający dynamicznie algorytmy planowania kolejnego ruchu. • Dla symulacji myszy próbującej wydostać się z labiryntu trzeba zapewnić komponent dokonujący ewałuacji różnych ścieżek, niezależny od typów ruchów przewidzianych dla symulowanej myszy. 8.8. Jaki wzorzec projektowy wybrałbyś dla aplikacji wybierającej dynamicznie algorytm szyfrowania, w zależności od bieżąco wymaganego stopnia zabezpieczeń i dostępnej aktualnie mocy obliczeniowej? Uzasadnij swój wybór, rysując diagram UML przedstawiający klasy wspomnianego wzorca.

Bibliografia [BEA]

BEA WebLogic Platform,

[Birrer, 1993]

E. T. Birrer „Frameworks in the financial engineering domain: An experience report", ECOOP'93 Proceedings, Lecture Notes in Computer Science, n r 707, 1993.

[Bruegge i in., 1993]

B. Bruegge, T. Gottschałk, B. Luo A framework for dynamic program analyzers, OOPSLA' 93, (Object-Oriented Programming Systems, Languages, and Applications), Washington DC, str. 65 - 82, wrzesień 1993.

[Buschmann i in., 1996]

F. Buschmann, R. Meunier, H. Rohnert, P. Sommerlad, M. Stal Pattern-Oriented Software Architecture: A System of Patterns, Wiley, Chichester, U.K., 1996.

[Campbell i Islam, 1993]

http://www.bea.com/products/weblogic/platform.

R. H. Campbell, N. Islam A technique for documenting

the framework

of

an object-oriented system, „Computing Systems", nr 6, str. 363 - 389, 1993. [Fayad i Hamu, 1997]

M. E. Fayad, D. S. Hamu Object-oriented enterprise frameworks: Make vs. buy decisions and guidelines for selection, „The Communications of ACM", 1997.

[Fowler, 1997]

M. Fowler Analysis Patterns: Reusable Object Models, Addison-Wesley,

[Gamma i in., 1994]

E. Gamma, R. Helm, R. Johnson, J. Vlissides Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1994. Wydanie polskie Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku, Helion 2010.

Reading, MA, 1997.

Bibliografia [Goldberg i Kay, 1976] [IBM]

397 A. Goldberg, A. Kay Smalltalk-72 Instruction Manual, Xerox Palo Alto, CA, 1976. IBM WebSphere http://www.

Software Platform for

E-Business,

ibm.com/websphere/.

[JavaEE, 2009]

Java Platform, Enterprise Edition, Javasoft 2009,

[JFC, 2009]

Java Foundation

http://java.sun.com/.

Classes, JDK Documentation. Javasoft 2009.

R. Johnson, B. Foote Designing reusable classes, „Journal of Object-Oriented [Johnson i Foote, 1988]

Programming", t. 1, nr 5, str. 22 - 35, 1988. B. Liskov Data abstraction

[Liskov, 1988]

and hierarchy,

J. Martin, J. J. Odell Object-Oriented [Martin i Odell, 1992]

Analysis and Design, Prentice Hall,

Englewood Cliffs, NJ, 1992. B. Meyer Object-Oriented

[Meyer, 1997]

„SIGPLAN Notices", t. 23,

nr 3, maj 1988.

Software Construction,

wyd. drugie, Prentice

Hall, Upper Saddle River, NJ, 1997. M. Minsky „A framework for representing knowledge," w P. Winston

[Minsky, 1975]

(red.) The Psychology of Computer

Vision, McGraw-Hill, 1975.

Object Management Group, Common [OMG, 2008]

Object Request Broker

(CORBA) Specification: Version 3.1, http://www.omg.org

Architecture

2008.

J. Rumbaugh, M. Blaha, W. Premerlani, F. Eddy, W. Lorensen Object[Rumbaugh i in., 1991]

Oriented Modeling and Design, Prentice Hall, Englewood Cliffs, NJ, 1991. D.C. Schmidt „Applying design patterns and frameworks to develop

[Schmidt, 1997]

object oriented communication software" w Peter Salus (red.) of Programming

[Weinand i in., 1988]

Languages, t. 1, MacMillan Computer, 1997.

A. Weinand, E. Gamma, R. Marty „ET++ — An object-oriented application framework in C++" w Object-Oriented and Applications

[Wilson i Ostrem, 1999]

Handbook

Programming

Systems,

Languages,

Conference Proceedings, San Diego, CA, wrzesień 1988.

G. Wilson, J. Ostrem WebObjects Developer's Guide, Apple, Cupertino, CA, 1998.

9.1.

Wstęp: kolej miejska i tramwaje

400

9.2.

O specyfikowaniu interfejsów ogólnie

401

Koncepcje specyfikowania interfejsów

403

9.3.1. 9.3.2. 9.3.3. 9.3.4. 9.3.5. 9.3.6.

Implementator, ekstender i użytkownik klasy Typy, sygnatury i widzialność Kontrakty: niezmienniki, warunki wstępne i warunki końcowe Język OCL (Object Constraint Language) Kolekcje OCL: zbiory, wielozbiory i ciągi Kwantyfikatory OCL: forAllO i exists()

403 403 406 407 411 415

9.4.

Aktywności specyfikowania interfejsów 9.4.1. Identyfikowanie brakujących atrybutów i operacji 9.4.2. Specyfikowanie typów, sygnatur i widzialności 9.4.3. Specyfikowanie warunków wstępnych i warunków końcowych 9.4.4. Specyfikowanie niezmienników 9.4.5. Dziedziczenie kontraktów

416 417 418 419 421 424

9.5.

Zarządzanie projektowaniem obiektów 9.5.1. Dokumentowanie projektowania obiektów 9.5.2. Przydzielanie odpowiedzialności 9.5.3. Wykorzystywanie kontraktów w analizie wymagań

425 425 431 432

9.6.

Analiza przypadku — system ARENA 9.6.1. Identyfikowanie brakujących operacji w klasach TournamentStyle i Round 9.6.2. Specyfikowanie kontraktów dla klas TournamentStyle i Round 9.6.3. Specyfikowanie kontraktów dla klas KnockOutStyle i KnockOutRound 9.6.4. Wnioski

433

9.7.

Literatura uzupełniająca

440

9.8.

Ćwiczenia

440

Bibliografia

442

9.3.

434 435 438 439

Projektowanie obiektów: specyfikowanie interfejsów Jeśli masz procedurę z dziesięcioma parametrami, prawdopodobnie zapomniałeś o niektórych. — Alan Perlis, Epigrams in Programming

W czasie projektowania obiektów identyfikujemy i doskonalimy obiekty realizacyjne podsystemów zdefiniowanych na etapie projektu systemu. Nasze rozumienie poszczególnych obiektów staje się coraz lepsze: definiujemy sygnaturę typów i widzialność każdej operacji oraz opisujemy warunld, których spełnienie konieczne jest do wywołania danej operacji, a także te, w których operacja ta powinna generować wyjątek. Podczas gdy celem projektowania systemu był „gruboziarnisty" podział systemu na podsystemy przydzielane poszczególnych programistom lub zespołom, projektowanie obiektów sprowadza się do „drobnoziarnistego" definiowania granic między poszczególnymi obiektami. Na tym etapie projektów duża liczba programistów równolegle opracowuje i doskonali obiekty oraz ich interfejsy. Coraz bardziej odczuwalna staje się presja na terminowe dostarczanie produktów, a w konsekwencji zwiększa się ryzyko wprowadzenia błędów do projektu. Ryzyko to można wydatnie zmniejszyć, zapewniając efektywne i precyzyjne komunikowanie się programistów w kwestii coraz większej liczby niskopoziomowych szczegółów systemu. Temu celowi służy specyfikowanie interfejsów, obejmujące między innymi następujące aktywności: • identyfikowanie przeoczonych atrybutów i operacji, • specyfikowanie sygnatur typów i widzialności, • specyfikowanie niezmienników, • specyfikowanie warunków wstępnych i końcowych. W tym rozdziale dokonamy przeglądu koncepcji związanych ze specyfikowaniem interfejsów. Przedstawimy podstawowe elementy języka OCL (Object Constraint Language) służącego do definiowania niezmienników, warunków wstępnych i warunków końcowych, przedyskutujemy heurystyki i wskazania stylistyczne pomagające w czytelnym formułowaniu ograniczeń. Na koniec zajmiemy się problemami związanymi z zarządzaniem aktywnościami chodzącymi w skład specyfikowania interfejsów oraz dokumentowaniem tych aktywności.

400

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

9.1. Wstęp: kolej miejska i tramwaje Stuttgart: od tramwajów do kolei miejskiej Aż do roku 1976 masowy transport publiczny Stuttgartu oparty był na tramwajach. Z rozległej sieci tramwajowej korzystało mnóstwo pasażerów, sieć ta w pewnym momencie osiągnęła jednak nasycenie: ograniczenia wynikające zarówno z niewielkiej pojemności wagonów, jak i rosnącego ruchu samochodowego zmusiły do poszukiwania lepszego rozwiązania — rozsądną alternatywą okazała się „lekka kolej" miejska. Większy rozstaw szyn takiej kolei, w porównaniu z torowiskiem tramwajowym, oznaczał większą pojemność taboru, a większa prędkość handlowa 1 oznaczała zwiększoną przepustowość przewozów; co więcej, ponieważ kolej miejska jest z założenia niezależna od ruchu samochodowego, można dla niej układać rozkłady jazdy realizowalne nawet w godzinach szczytu. Zapoczątkowana w 1976 roku konwersja tramwajów na lekką kolej okazała się przedsięwzięciem gigantycznym w kontekście rozmiarów sieci i długości torowisk, było więc oczywiste, iż musi odbywać się ewolucyjnie, stopniowo: dla niektórych linii oznaczało to tymczasowe kursowanie obu środków lokomocji — tramwajów i kolei. Środków — oczywiście — niekompatybilnych, co wiązało się z problemami dotyczącymi przede wszystkim: •

rozstawu szyn. Kolej miejska poruszała się po torowisku o standardowym rozstawie 1450 mm, podczas gdy tramwaje były taborem „metrowym", czyli korzystającym z rozstawu 1000 m m . Uniwersalne torowisko umożliwiające ruch obu typów taboru musiało więc składać się z trzech, nie dwóch szyn, co — oprócz zwiększonego zużycia stali — przekładało się natychmiast na bardziej skomplikowane zwrotnice.

wysokości peronów. Tramwaje przystosowane są do wsiadania z poziomu ulicy, podczas gdy wagony kolejowe dostosowane są do wysokich peronów. Na niektórych przystankach konieczne było więc zbudowanie peronów dwuczęściowych; ponieważ każda z części musiała być tak samo długa jak długość odpowiadającego jej taboru, oznaczało to mniej więcej dwukrotne wydłużenie peronu.



1

systemu sygnalizacji. T r a m w a j e korzystają z sygnalizacji ulicznej, tej samej co samochody, a to oznacza mechaniczne, naprzemienne kierowanie ruchem na skrzyżowaniach, niezależnie od natężenia tego ruchu. Sygnalizacja kolei miejskiej jest natomiast oparta na ogólnych stdardardach ruchu kolejowego, między innymi na centralnym sterowaniu z nastawni, zależnie od wyników bieżącego monitorowania ruchu. Ponieważ w okresie przejściowym kolej miejska z konieczności musiałaby się wpisywać w ruch uliczny, jej sygnalizacja musiałaby być zsynchronizowana z sygnalizacją uliczną.

Prędkość handlowa środka transportu jest średnią jego prędkością na danej trasie, czyli ilorazem długości tej trasy przez czas, w jakim została przebyta. Zależy nie tylko od możliwości samego pojazdu, lecz także od bieżących warunków, w jakich się porusza, między innymi postojów na przystankach pośrednich i zatłoczenia („zakorkowania") dróg, jest zatem miarodajnym czynnikiem oceny środka transportu z perspektywy jego pasażera — przyp. tłum, na podstawie pl.wikipedia.org.

9.2. O specyfikowaniu interfejsów ogólnie

401

W kontekście rozciągnięcia w czasie opisanej modernizacji system dualnego taboru znakomicie zdał egzamin: udało się sprostać rosnącym potrzebom transportu publicznego, również dzięki wyeliminowaniu konieczności wyłączania z ruchu poszczególnych linii. Cały projekt zakończył się szczęśliwie w grudniu 2007 roku, kiedy to z ulic Stuttgartu zniknął ostatni tramwaj.

Powyższy przykład znakomicie ilustruje koncepcję interfejsu: pojazd szynowy, podobnie jak obiekt programu, dostarcza klientom pewne usługi pod warunkiem spełnienia przez środowisko określonych wymogów. Z perspektywy torowiska interfejsem tramwaju są jego koła — gdy zmienia się rozstaw kół pojazdu, odpowiednio musi zmienić się rozstaw szyn. Z perspektywy podróżnego (pasażera) interfejsem pojazdu są jego drzwi: jeśli schody w drzwiach usytuowane są wyżej, podróżni potrzebują wyższego peronu. Wreszcie, z perspektywy systemu sygnalizacji interfejsem pojazdu jest jego kierowca (motorniczy, maszynista): wprowadzenie nowego systemu sygnalizacji wymaga odpowiedniego przeszkolenia kierujących pojazdami. Każdy z wymienionych elementów interfejsu (rozstaw kół, wysokość schodów, interpretacja sygnałów przez kierującego) musi pozostawać w zgodzie z odpowiednim elementem środowiska — rozstawem szyn, wysokością peronu i systemem sygnalizacji. Identyczna zasada stosuje się do tworzenia oprogramowania: każdy obiekt współdziała z innymi obiektami za pośrednictwem swego interfejsu obejmującego zarówno zbiór operacji o określonych parametrach i (ewentualnym) określonym wyniku, jak i zbiór założeń na temat znaczenia poszczególnych operacji. Gdy zmieni się postać wspomnianego interfejsu — to znaczy gdy zmieni się definicja którejś operacji bądź przestaną być spełnione związane z nią założenia — obiekt nie będzie w stanie świadczyć swych usług na dotychczasowych warunkach. W tym rozdziale skupimy się na aktywnościach związanych ze specyfikowaniem interfejsów. W sekcji 9.2 przedstawimy ogólnie problematykę specyfikowania interfejsów, w sekcji 9.3 zajmiemy się natomiast głównymi koncepcjami interfejsu — sygnaturami typów, ograniczeniami i kontraktami, po czym zaprezentujemy podstawowe elementy języka OCL służącego do specyfikowania ograniczeń. Zastosowanie opisanych koncepcji omówimy w sekcji 9.4 na przykładzie systemu ARENA. Sekcję 9.5 poświęcimy zagadnieniom menedżerskim związanym ze specyfikowaniem interfejsów, między innymi przydzielaniu odpowiedzialności i dokumentowaniu interfejsów, całość zaś zakończy sekcja 9.6 dedykowana systemowi ARENA.

9.2. O specyfikowaniu interfejsów ogólnie Rozpoczynamy etap specyfikowania interfejsów, mamy już za sobą wiele ważnych decyzji i dysponujemy bogatym zbiorem modeli, mianowicie: • analitycznym modelem obiektowym opisującym obiekty encji, obiekty brzegowe i obiekty sterujące widoczne dla użytkownika, wraz z atrybutami i operacjami każdego z tych obiektów, • wynikiem dekompozycji systemu w postaci jego podziału na spoiste podsystemy, z których każdy realizowany jest przez osobny zespół programistów; dla każdego z tych podsystemów istnieje wysokopoziomowy opis usług świadczonych na rzecz innych podsystemów,

402

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów



odwzorowaniem sprzętowo-programowym identyfikującym budowaną maszynę wirtualną, dla której tworzone są obiekty realizacyjne; elementami tego odwzorowania są klasy i interfejsy API definiowane w ramach istniejących komponentów,

• zbiorem granicznych przypadków użycia opisujących, z perspektywy użytkownika, czynności administrowania systemem oraz sytuacje wyjądcowe, którym system ten musi sprostać, •

wzorcami projektowymi wybranymi na etapie wykorzystywania gotowych rozwiązań, dedykowanymi częściowemu rozwiązywaniu poszczególnych problemów projektowych.

Każdy z tych modeli odzwierciedla jedynie fragmentaryczne spojrzenia na system; są one elementami swoistej układanki, w której jednak brakuje jeszcze wiele elementów, a niektóre z elementów wymagają przystosowania. Stajemy zatem przed zadaniem stworzenia modelu integrującego w sposób spójny i precyzyjny informację reprezentowaną przez wspomnianą układankę. Aby poszczególne elementy tej układanki, opracowywane przez różne zespoły programistyczne, można było do siebie dopasować, muszą się one charakteryzować precyzyjnie określonymi interfejsami, uzgodnionymi między programistami — tak by faza ich integrowania odbyła się bezproblemowo (no, może „niemal bezproblemowo"). Specyfikowanie interfejsów może być postrzegane w postaci trzech następujących aktywności: •

identyfikowania brakujących atrybutów i operacji. Analizując dokładnie usługi każdego podsystemu i każdy obiekt modelu analitycznego, wykrywamy brakujące atrybuty i operacje niezbędne do świadczenia wspomnianych usług i odpowiednio doskonalimy model projektu obiektów.

• definiowania widzialności i sygnatur. W stosunku do poszczególnych operacji decydujemy, które z nich mają być dostępne dla innych obiektów i podsystemów, a które używane są tylko wewnętrznie przez podsystem. Dla każdej operacji (w obu kategoriach) określany ponadto liczbę i typy parametrów oraz typ wyniku. W ten oto sposób redukujemy powiązania między podsystemami i tworzymy proste interfejsy, zrozumiałe dla pojedynczych programistów. • specyfikowania kontraktów. Każdą z operacji poszczególnych obiektów opisujemy w kategoriach narzuconych na nią ograniczeń, określając między innymi warunki, które muszą być spełnione przed wywołaniem operacji, i warunki, których spełnienie jest gwarantowane po zakończeniu tej operacji. Multum obiektów, duża liczba programistów pracujących nad tymi obiektami, wysoka dynamika zmian i mnóstwo podejmowanych równolegle decyzji — wszystko to sprawia, że faza projektowania obiektów jest znacznie bardziej skomplikowana niż analiza wymagań czy projektowanie systemu. Złożoność ta pociąga za sobą niebagatelne wyzwania dla menedżera projektu, bowiem wiele wspomnianych decyzji podejmowanych jest niezależnie i większość z nich nie jest zwykle komunikowana ogółowi uczestników. Projektowanie obiektów łączy się z zapewnieniem dostępności dużej ilości informacji dla dużej liczby programistów, tak by podejmowane przez nich decyzje były spójne i zgodne z celami projektowymi. Informację taką zapewnia Dokument Projektu Obiektów, w skrócie ODD (Object Design Document) — „żywy" dokument zawierający szczegółową specyfikację każdej klasy.

9.4. Aktywności specyfikowania interfejsów

403

9.3. Koncepcje specyfikowania interfejsów W tej sekcji opiszemy następujące koncepcje dotyczące specyfikowania interfejsów: • Implementator, ekstender i użytkownik klasy (patrz sekcja 9.3.1), • Typy, sygnatury i widzialność (patrz sekcja 9.3.2), • Kontrakty: niezmienniki, warunki wstępne i warunki końcowe (patrz sekcja 9.3.3), • Język OCL (Object Constraint Language) (patrz sekcja 9.3.4), • Kolekcje OCL: zbiory, wielozbiory i ciągi (patrz sekcja 9.3.5), • Kwantyfikatory OCL: forAl 1 () i e x i s t s ( ) (patrz sekcja 9.3.6).

9.3.1. Implementator, ekstender i użytkownik klasy Dotychczas nie czyniliśmy żadnego rozróżnienia między programistami, gdy jednak przychodzi do szczegółów projektowania i implementowania obiektów, musimy spojrzeć na te czynności z kilku punktów widzenia. Podczas gdy dla wszystkich programistów specyfikacja interfejsu danej klasy jest podstawą wzajemnej komunikacji, to w kontekście tej specyfikacji każdy programista mający z nią do czynienia pełnić może jedną z trzech następujących ról (patrz rysunek 9.1). Oto one. • Implementator klasy odpowiedzialny jest za jej realizację: zaprojektowanie wewnętrznych struktur i zaimplementowanie kodu wszystkich operacji. Specyfikacja interfejsu tej klasy jest dla implementatora przydziałem pracy. • Użytkownik klasy wykorzystuje operacje udostępniane przez daną klasę na potrzeby realizacji innej klasy (klas), zwanej klasą kliencką. Specyfikacja interfejsu przedmiotowej klasy wyznacza w tym przypadku jej granice pod kątem oferowanych usług i założeń przyjmowanych względem klas klienckich. • Ekstender klasy zajmuje się specjalizacjami przedmiotowej klasy. Podobnie jak użytkownik klasy, ekstender wykorzystuje oferowane przez nią operacje; podobnie jak implementator, ekstender implementuje nowe klasy, skupiając się jednak na specjalizowaniu określonego zbioru usług. Specyfikacja interfejsu jest więc dla ekstendera zarówno obrazem zachowania przedmiotowej klasy, jak i wyznacznikiem ograniczeń narzuconych na usługi oferowane przez klasy specjalizowane. W kontekście abstrakcyjnej klasy Game systemu ARENA (patrz rysunek 9.2) programista opracowujący operacje wspólne dla wszystkich subklas tej klasy jest jej implementatorem. Użytkownikami klasy Game są między innymi implementatorzy klas League i Tournament — obiekty tych klas korzystają z klasy Game w celu organizowania i rozpoczynania meczów (Match). Z kolei programiści implementujący klasy Ti cTacToe i Chess, będące subklasami (specjalizacjami) klasy Game, są z jej perspektywy ekstenderami.

9.3.2. Typy, sygnatury i widzialność Identyfikując podczas analizy wymagań atrybuty i operacje, nie zajmujemy się ich typami i parametrami, na to bowiem przychodzi czas teraz, w czasie projektowania obiektów. Dla

404

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

Rysunek 9.1. Role implementatora, użytkownika i ekstendera klasy

Rysunek 9.2. Zależności klas systemu ARENA od klasy Game w kontekście ról programistycznych przedstawionych na rysunku 9.1

każdego atrybutu określamy jego typ, dla każdej operacji — liczbę i typy jej parametrów oraz typ wyniku. Dla każdego atrybutu i każdej operacji ustalamy też zakres ich widzialności. Pod pojęciem typu atrybutu rozumiemy zbiór (zakres) wartości, jakie ów atrybut może przyjmować, oraz operacji, jakie mają zastosowanie do tego atrybutu. Przykładowo wartościami atrybutu maxNumPl ayers klasy Tournament (patrz rysunek 9.3), określającego maksymalną liczbę graczy (PI ayer), mogą być liczby całkowite, co jest odpowiednikiem typu i nt 2 . Do wielkości tego typu stosują się operacje mające sens dla liczb całkowitych: porównywanie, dodawanie, odejmowanie, mnożenie przez liczbę całkowitą i tym podobne. Identyczne znaczenie ma pojęcie typu w odniesieniu do każdego z parametrów operacji i do zwracanego przez nią wyniku. Wektor (krotkę) danej operacji, jaki tworzą kolejne typy jej parametrów i typ wyniku, nazywamy jej sygnaturą. Przykładowo operacja acceptPl ayer() klasy Tournament wymaga jednego parametru typu PI ayer i nie zwraca żadnej wartości, jej sygnatura ma zatem postać (Pl ayer): voi d;podobnie operacja getMaxNumPl ayers () tej samej klasy ma sygnaturę (voi d ) : i nt. Programiści w każdej z trzech ról — implementatora, użytkownika i ekstendera klasy — korzystają z operacji i atrybutów tej klasy. Zależnie jednak od konkretnej, roli mają zróżnicowane potrzeby w tym względzie i nie dla każdego z nich dostęp do wszystkich operacji jest potrzebny lub pożądany, przykładowo atrybuty i operacje związane z wewnętrznymi strukturami i mechanizmami klasy z oczywistych względów dostępne dla jej implementatora,

2

O d ang. integer — „całkowity" — przyp.

tłum.

9.4. Aktywności specyfikowania interfejsów

405

public class Tournament { private int maxNumPlayers; public public public public public public

Tournament(League 1, int maxNumPlayers) int getMaxNumPlayers() { ... ); List getPlayers() { ... }; void acceptPlayer(Player p) { ... }; void removePlayer(P1ayer p) { ... }; boolean isPlayerAccepted(Player p) { ... };

} Rysunek 9.3. Deklaracja klasy Tournament

niekoniecznie muszą być (a nawet nie powinny być) dostępne dla jej użytkownika. Z kolei dla ekstendera klasy interesujące mogą być jedynie wybrane elementy zbioru jej wewnętrznych atrybutów i operacji. Zróżnicowana dostępność poszczególnych atrybutów i operacji określana jest mianem ich widzialności. W języku UML rozróżnia się cztery następujące poziomy widzialności. • Prywatny (pri vate) atrybut widoczny jest jedynie w obrębie implementacji swej macierzystej klasy, podobnie prywatna operacja może być wywoływana wyłącznie w ramach tej implementacji. Prywatne atrybuty i operacje przeznaczone są wyłącznie dla implementatora klasy i nie są widoczne dla innych klas, nawet dla subklas danej klasy; nie są zatem widoczne ani dla użytkownika tej klasy, ani dla jej ekstendera. •

Chroniony (protected) atrybut i chroniona operacja dostępne są zarówno w obrębie implementacji swej macierzystej klasy, jak i w obrębie implementacji jej subklas. Dla innych klas są niedostępne. Widoczne są zatem dla implementatora i ekstendera klasy, ale nie dla jej użytkownika.

• Publiczny (publ i c) atrybut lub klasa dostępne są bez ograniczeń dla wszystkich klas. Zbiór publicznych atrybutów i operacji danej klasy składa się na to, co nazywamy jej publicznym interfejsem, przeznaczonym dla użytkowników. • Pakietowy (package) atrybut lub operacja widoczne są bez ograniczeń w obrębie wszystkich klas wchodzących w skład tego samego pakietu. Umożliwia to współdzielenie atrybutów i operacji przez powiązane ze sobą klasy tworzące spójną całość (na przykład podsystem), jednocześnie chroni przedmiotowe atrybuty i operacje przed dostępem z zewnątrz do macierzystego pakietu.

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

406

W języku UML widzialność elementu (atrybutu lub klasy) oznaczana jest przez jednoznakowy przedrostek poprzedzający nazwę: minus (-) oznacza element prywatny (pri vate), kratka (#) — element chroniony (protected), plus (+) — element publiczny (pub! ic), zaś tylda (-) — element pakietowy (package). W diagramie na rysunku 9.3 atrybut maxNumPl ayers klasy Tournament jest jej atrybutem prywatnym, podczas gdy wszystkie jej operacje są publiczne. Typ atrybutu nie zawiera jednak kompletnej informacji na temat dopuszczalnego zbioru wartości, jakie atrybut ten może przyjmować; przykładowo typ i nt atrybutu maxNumPl ayers zezwala na nadawanie mu wartości ujemnych, bezsensownych jednak z punktu widzenia dziedziny aplikacyjnej. Ten problem rozwiązywany jest za pomocą mechanizmu zwanego kontraktem.

9.3.3. Kontrakty: niezmienniki, warunki wstępne i warunki końcowe Kontraktem nazywamy zbiór ograniczeń narzuconych na daną klasę, który umożliwia jej użytkownikom, implementatorom i ekstenderom współdzielenie dokładnie tych samych założeń [Meyer, 1997], Kontrakt obejmuje zarówno warunki konieczne do prawidłowego używania klasy, jak i warunki konieczne do spełnienia przez jej implementatorów i ekstenderów. W kontrakcie zawarte są trzy typy ograniczeń: • niezmienniki — to predykaty zawsze prawdziwe dla każdej instancji klasy, dotyczące samej klasy lub jej interfejsu; każdy niezmiennik jest elementem zapewnienia spójności między poszczególnymi atrybutami klasy, • warunki wstępne — to predykaty związane z konkretną operacją; ich prawdziwość przed wywołaniem tej operacji jest konieczna do prawidłowego jej wykonania; warunki wstępne stanowią rodzaj zobowiązania dla użytkownika klasy, • warunki końcowe — to predykaty, również związane z konkretną operacją, których prawdziwość musi być gwarantowana po zakończeniu tej operacji, przy założeniu spełnienia jej warunków wstępnych. Warunki końcowe stanowią rodzaj zobowiązania dla implementatorów i ekstenderów klasy. Jako przykładem posłużmy się interfejsem klasy Tournament z rysunku 9.3. Klasa ta posiada metodę acceptPlayer() realizującą dodawanie nowego gracza (Player) do turnieju, metodę removePl ayer() wycofującą gracza z turnieju i metodę getMaxNumPl ayers () zwracającą maksymalną dopuszczalną liczbę graczy w danym turnieju. Przykładem niezmiennika tej klasy jest dodatnia wartość zwracana przez metodę getMaxNumPlayers(); gdyby była ona zerowa, każde wywołanie metody acceptPl ayer () stanowiłoby naruszenie jej kontraktu i turniej reprezentowany przez instancję klasy Tournament nie mógłby się w ogóle rozpocząć. Niezmiennik ten zapisać możemy w postaci wyrażenia boolowskiego: t.getMaxNumPlayers() > 0 gdzie t jest instancją klasy Tournament. Przykładem warunku wstępnego dla metody acceptPl ayer () jest wymaganie, by gracz (PI ayer) stanowiący argument wywołania tej metody nie uczestniczył jeszcze w odnośnym turnieju oraz by dla turnieju tego nie wyczerpano jeszcze maksymalnej liczby uczestników. Oznaczając przez t instancję klasy Tournament reprezentującą wspomniany turniej, zaś

9.4. Aktywności specyfikowania interfejsów

407

przez p instancję klasy PI ayer reprezentującą gracza aplikującego do udziału w turnieju, warunek ten możemy zapisać w postaci następującego wyrażenia boolowskiego: !t.isPlayerAccepted(p) and t.getNumPlayersQ < t.getMaxNumPlayers() Po poprawnym wykonaniu metody acceptPl ayer() liczba uczestników turnieju musi być dokładnie o jeden większa niż przed jej wywołaniem, co uważać możemy za jej warunek końcowy i zapisać w postaci kolejnego wyrażenia boolowskiego: t.getNumPlayers_afterAccept = t.getNumPlayers__beforeAccept + 1 gdzie metody getNumPlayers_afterAccept i getNumPlayers_beforeAccept oznaczają liczbę uczestników turnieju (odpowiednio) po wywołaniu metody i przed jej wywołaniem. Niezmienniki, warunki wstępne i warunki końcowe wykorzystywane są do jednoznacznego definiowania sytuacji specjalnych i wyjątkowych. Teoretycznie są one nawet wystarczające do kompletnego określenia zachowania reprezentowanego przez daną operację — nazywa się to „specyfikacją opartą na ograniczeniach" — w praktyce jednak sporządzenie takiej specyfikacji jest trudne i bardziej skomplikowane niż implementacja samej operacji. W książce tej nie będziemy więc wykorzystywać specyfikacji tego rodzaju, w zamian połączymy formalne definicje ograniczeń z opisami operacji w języku naturalnym i dokładnie przeanalizujemy możliwe sytuacje wyjątkowe — takie rozwiązanie jest bardziej czytelne i zapewnia lepszą komunikację między programistami.

9.3.4. Język OCL (Object Constraint Language) Ograniczenia mogą być opisywane zarówno w języku naturalnym, jak i w postaci formalnej przy użyciu odpowiedniego języka. Takim językiem jest OCL (Object Constraint Language) [OMG, 2006] umożliwiający formułowanie ograniczeń zarówno w kategoriach pojedynczych elementów modelu (atrybutów, operacji, klas), jak i grupowanie tych elementów (między innymi skojarzeń między klasami czy uczestnictwa klas w skojarzeniu, dziedziczeniu i tym podobnych). W tej i następnej sekcji przedstawimy podstawową składnię języka OCL; czytelników zainteresowanych jego dalszymi szczegółami odsyłamy do książki ]. Warmera i A. Kleppe [Warmer i Kleppe, 2003]. Formalna definicja ograniczenia ma postać wyrażenia boolowskiego, czyli wyrażenia zwracającego jedną z dwóch wartości: True albo Fal se. Na diagramach UML ograniczenia mogą być reprezentowane w formie notatek przyporządkowywanych przedmiotowym elementom — w diagramie na rysunku 9.4 uwidocznione zostały opisane wcześniej ograniczenia dotyczące klasy Tournament. Ponieważ dołączanie wyrażeń OCL do diagramu może pogarszać jego czytelność, wyrażenia te mogą być formułowane także w formie tekstowej. Przykładowo warunek, by wartość atrybutu maxNumPl ayers klasy Tournament była dodatnia, zapisać można w postaci następującej: context Tournament inv: self.getMaxNumPlayersO > 0

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

408

Rysunek 9.4. Przykłady niezmienników, warunków wstępnych i warunków końcowych, wyrażonych w języku OCL i reprezentowanych w formie notatek na diagramie UML

Słowo kluczowe context wskazuje encję, do której odnosi się ograniczenie, po czym następuje określenie rodzaju graniczenia w postaci jednego z trzech słów kluczowych: i nv (niezmiennik, od invariant), pre (warunek wstępny, od precondition) albo post (warunek końcowy, od postcondition). Odpowiednikami tych słów kluczowych na diagramie UML są stereotypy (odpowiednio) «invariant», «precondition» i «postcondition». Potem następuje odpowiednie wyrażenie języka OCL. Składnia języka OCL podobna jest do składni języków zorientowanych obiektowo, takich jak Java czy C++. Jednak w przeciwieństwie do nich język OCL nie jest językiem proceduralnym — operacje mogą być używane w jego wyrażeniach, pod warunkiem że nie powodują efektów ubocznych. Dla niezmienników kontekstem wyrażenia jest klasa, której one dotyczą. Instancje tej klasy reprezentowane są w wyrażeniach przez słowo kluczowe sel f 3 . Atrybuty i operacje identyfikowane są za pomocą kwalifikowanej notacji „kropkowej", na przykład sel f.maxNumPl ayers oznacza wartość atrybutu maxNumPl ayers dla instancji łdasy stanowiącej kontekst ograniczenia. Słowo sel f można pominąć, jeśli to nie prowadzi do niejednoznaczności. Dla warunku wstępnego i warunku końcowego kontekstem wyrażenia OCL jest operacja; parametry przekazywane do tej operacji mogą być używane jako zmienne w tym wyrażeniu. Przykładowo ograniczenie w postaci: context T o u r n a m e n t : : a c c e p t P ł a y e r ( p : P l a y e r )

pre:

lisPlayerAccepted(p) specyfikuje warunek wstępny, na mocy którego gracz, reprezentowany przez parametr p operacji acceptPl ayer (), nie może być już uczestnikiem turnieju, do którego właśnie się zgłasza. Wspomniany parametr p występuje jako zmienna w wyrażeniu formułującym warunek. Jeśli dla danego kontekstu definiuje się lalka warunków wstępnych, wszystkie one muszą być spełnione przed wywołaniem operacji. Do powyższego warunku wstępnego możemy na przykład dorzucić następny stanowiący, by w turnieju tym nie został już wyczerpany limit liczby uczestników: context T o u r n a m e n t : : a c c e p t P l a y e r ( p : P l a y e r )

pre:

getNumPlayers() < getMaxNumPlayers()

Stanowiące odpowiednik słowa kluczowego t h i s w językach C++ i Java.

9.4. Aktywności specyfikowania interfejsów

409

Warunki końcowe zapisywane są w identyczny sposób, z tą różnicą, że zamiast słowa kluczowego pre występuje słowo post oznaczające, że wartości wyrażeń ewaluowane są po wykonaniu operacji. Przykładowo efektem wykonania operacji acceptPl ayer () dla gracza p jest uczestnictwo tegoż gracza w turnieju: context T o u r n a m e n t : : a c c e p t P l a y e r ( p : P I a y e r )

post:

isPlayerAccepted(p) Jako że wyrażenia będące warunkami końcowymi ewaluowane są po zakończeniu operacji będącej kontekstem ograniczenia, powstaje problem dostępności wartości (atrybutów i operacji) sprzed jej wykonania — jeżeli przykładowo żądamy, by w wyniku wywołania operacji acceptPl ayer () liczba uczestników turnieju była o jeden większa niż przed jej wywołaniem, powinniśmy mieć dostęp do wartości zwracanej przez operację getNumPlayersQ zarówno przed wywołaniem, jak i po zakończeniu operacji acceptPl ayer(). Problem ten rozwiązuje się w języku OCL za pomocą przedrostka @pre: context T o u r n a m e n t : : a c c e p t P l a y e r ( p : P l a y e r )

post:

getNumPlayersQ = self@pre. getNumPlayersQ + 1 [email protected]() oznacza tu wartość, jaką metoda getNumPlayersQ zwracała przed wywołaniem operacji będącej kontekstem ograniczenia (acceptPl ayer()), zaś getNum "-•PlayersO (bez przedrostka) oznacza wartość zwracaną przez nią po zakończeniu operacji stanowiącej kontekst ograniczenia. Podobnie jak w przypadku warunków wstępnych, także warunków końcowych może być kilka — wszystkie one muszą być spełnione. Na podobnej zasadzie sformułować możemy warunki wstępne i końcowe, składające się na kontrakt operacji removePl ayer (): context Tournament::removePlayer(p:PIayer) pre: isPJayerAccepted(p) context T o u r n a m e n t : : r e m o v e P l a y e r ( p : P I a y e r )

post:

lisPlayerAccepted(p) context T o u r n a m e n t : : r e m o v e P l a y e r ( p : P I a y e r )

post:

getNumPlayersQ = [email protected] - 1 Ograniczenia OCL zapisywane są i wykorzystywane przez programistów na etapie projektowania obiektów i na etapie ich implementowania. Dla programów tworzonych w języku Java dostępne są narzędzia w rodzaju iContract opisane przez R. Kramera [Kramer, 1998], które umożliwiają dokumentowanie ograniczeń wprost w kodzie źródłowym, za pomocą znaczników Javadoc, dzięki czemu ograniczenia te są łatwo dostępne i modyfikowalne. Przykład kodu opatrzonego taką dokumentacją widoczny jest na listingu 9.1.

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

410

Listing 9.1. Deklaracje metod klasy Tournament opatrzone adnotacjami dotyczącymi warunków wstępnych, warunków końcowych i niezmienników, zapisanymi przy użyciu znaczników Javadoc / ** Turniej (Tournament) jest serię meczów (Match) rozgrywanych między grupami graczy * (Player) i kończęcych się wyłonieniem jednego zwycięzcy. Reguły gry i styl rozgrywania * turnieju określone są przez obiekty będęce specjalizacjami klas Game i TournamentStyle * skojarzonymi z klasę Tournament. V

public class Tournament { /** maksymalna liczba graczy uczestniczęcych * @invariant maxNumPlayers > 0

w turnieju musi być zawsze

7 private int maxNumPlayers; /** List jest listę graczy (w postaci referencji do obiektów * uczestniczęcych w turnieju

Player)

7 private L i s t ....

players;

konstruktor.... /** Zwraca liczbę graczy aktualnie uczestniczęcych

w turnieju

7 public int g e t N u m P l a y e r s ( )

/** Zwraca maksymalnę

{ ... }

liczbę graczy uczestniczęcych

w turnieju.

7 public int getMaxNumPlayers()

{ ... }

/** Dla operacji acceptPlayerQ zakładamy, że gracz reprezentowany * przez parametr jej wywołania nie jest jeszcze uczestnikiem turnieju * @pre lisPlayerAccepted(p) * @pre getNumPlayersQ < maxNumPlayers * @post isPlayerAccepted(p) * @post getNumPlayersQ = [email protected]() +1 V

public void a c c e p t P l a y e r

( P l a y e r p) { . . . }

/** Dla operacji removePlayer() zakładamy, że gracz reprezentowany * przez parametr jej wywołania jest aktualnie uczestnikiem turnieju * @pre isPlayerAccepted(p) * @post HsPlayerAccepted(p) * @post getNumPlayersQ = [email protected]() V

public void r e m o v e P l a y e r ( P I a y e r p) { . . . ... pozostałe

)

metody...

}

-1

dodatnia.

9.4. Aktywności specyfikowania interfejsów

411

9.3.5. Kolekcje OCL: zbiory, wielozbiory i ciągi Ogólnie ograniczenia mogą obejmować dowolną liczbę klas i atrybutów. Na rysunku 9.5 widoczny jest model klas uwidaczniający skojarzenia między klasami League, Tournament i PI ayer. Chcielibyśmy ten model udoskonalić, wprowadzając doń następujące ograniczenia: 1. Czas trwania turnieju (Tournament) nie może przekraczać tygodnia, 2. Gracze (PI ayer) mogą uczestniczyć w turnieju (Tournament) tylko wtedy, gdy zarejestrowani są w lidze (League) organizującej ten turniej, 3. Aktywnymi graczami ligi są ci, którzy wzięli udział w chociaż jednym turnieju organizowanym przez tę ligę.

Rysunek 9.5. Skojarzenia między klasami League, Tournament i PI ayer systemu ARENA

Aby lepiej zrozumieć powyższe ograniczenia, rozważmy przykładową konfigurację instancji wymienionych klas, widoczną na rysunku 9.6. W lidze t t t E x p e r t : League zarejestrowanych jest czworo graczy (PI ayer): al i ce, bob, marc i joe. Liga ta organizuje dwa turnieje: wi nter:Tournament i xmas:Tournament; w pierwszym z nich uczestniczą al i ce i bob, w drugim — bob, marc i joe. Do ligi chessNovi ce: League należy tylko jeden gracz — zoe; liga ta nie organizuje żadnych turniejów. Zobaczmy, jak wygląda to na wspomnianym rysunku. 1. Turniej wi nter :Tournament trwa dwa dni, turniej xmas :Tournament trzy dni — czyli oba poniżej tygodnia. 2. Wszyscy gracze (PI ayer) uczestniczący w turnieju wi nter: Tournament, a także wszyscy uczestnicy turnieju xmas:Tournament, zarejestrowani są w lidze tttExpert:League. Gracz zoe nie jest jednak członkiem ligi t t t E x p e r t : League i nie uczestniczy w żadnym turnieju.

412

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

Rysunek 9.6. Przykład ograniczeń obejmujący dwie ligi, dwa turnieje i pięcioro graczy

3. W lidze t t t E x p e r t : League jest czworo aktywnych graczy; liga chessNovi ce nie ma aktywnych graczy, bowiem jedyny jej członek — zoe — nie uczestniczy w żadnym turnieju. Powyższe ograniczenia już na pierwszy rzut oka wyglądają odmiennie: pierwsze z nich ogranicza się do jednej klasy, drugie obejmuje trzy klasy (PI ayer, Tournament, League) i skojarzenia między nimi, przedmiotem trzeciego jest zbiór meczów (Match) powiązanych z określonym turniejem. W każdym z tych przypadków startujemy od konkretnej klasy i nawigujemy po innych klasach modelu. W ogólnym przypadku nawigacja ta obejmować może trzy różne zakresy (co schematycznie przedstawiono na rysunku 9.7): •

lokalne atrybuty — czyli atrybuty pojedynczej klasy, takie jak data rozpoczęcia ( s t a r t ) i zakończenia (end) turnieju (Tournament),

• klasy bezpośrednio powiązane — nawigacja odbywa się wyłącznie na jednym poziomie skojarzenia (na przykład skojarzenia graczy z turniejami lub skojarzenia ligi z turniejem), • klasy powiązane pośrednio — nawigacja obejmuje kilka poziomów skojarzeń (na przykład skojarzenie ligi z graczami biorącymi udział w co najmniej jednym turnieju organizowanym przez tę ligę). Wyróżniliśmy owe trzy podstawowe typy nawigacji, bowiem każde ograniczenie w języku OCL da się wyrazić za ich pomocą. Na podstawie treści poprzedniej sekcji możemy już z łatwością zapisać pierwsze z przedstawionych ograniczeń — ograniczenie maksymalnego czasu trwania turnieju do tygodnia.

9.4. Aktywności specyfikowania interfejsów

413

Rysunek 9.7. Trzy podstawowe typy nawigacji — za pomocą ich kombinacji można formułować wszelkie ograniczenia w języku OCL context Tournament inv:

self.end - s e l f . s t a r t < 7 W przypadku drugiego ograniczenia — członkostwo ligi jako warunek uczestnictwa w turnieju — sprawa nie jest już tak prosta, bowiem skojarzenie graczy (Player) z ligami (League) ma krotność „wiele na wiele", zatem ograniczenie dotyczy wielu obiektów. Sytuacja taka obsługiwana jest w języku OCL przez kolekcje danych, które dzielą się na trzy typy: • zbiory (set) — używane przy nawigowaniu wzdłuż pojedynczego skojarzenia. Przykładowo nawigacja wzdłuż skojarzeń łączących turniej wi n t e r : Tournament z jego uczestnikami daje w wyniku zbiór {al i ce, bob}, zaś nawigacja wzdłuż skojarzeń łączących ligę tttExpert:League z jej członkami daje zbiór wynikowy {alice.bob, marc, joe}. Należy jednak pamiętać, że wynikiem nawigacji po skojarzeniu o krotności 1 jest pojedynczy obiekt, a nie zbiór obiektów: przykładowo nawigacja od turniejów winter:Tournament i xmas:Tournament w kierunku organizującej je ligi daje w wyniku obiekt t t t E x p e r t : League, a nie zbiór { t t t E x p e r t : League}. • ciągi (sequence) — używane przy nawigowaniu wzdłuż pojedynczego uporządkowanego skojarzenia. Takim uporządkowanym skojarzeniem jest na przykład skojarzenie między klasami League i Tournament; rezultatem nawigowania po tym skojarzeniu jest ciąg [winter:Tournament, xmas:Tournament]. Elementy ciągu mogą być identyfikowane przez indeksy, pierwszy element ma indeks 1, drugi 2 i tak dalej. • wielozbiory (bag) — w przeciwieństwie do zbioru, elementy wielozbioru mogą się powtarzać, dany element może wielokrotnie należeć do wielozbioru. Wielozbiory są wynikiem akumulowania obiektów osiągalnych za pomocą skojarzeń pośrednich. Przykładowo w celu określenia aktywnych członków ligi musimy „odwiedzić" wszystkie turnieje organizowane przez tę ligę i dla każdego turnieju pobrać listę aktywnych graczy. Dla ligi t t t E x p e r t da to w rezultacie wielozbiór {al ice, bob, bob, marc, joe}, dla ligi chessNovice wynikiem takiej iteracji będzie (wielo)zbiór pusty.

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

414

W sytuacjach, gdy istotny jest jedynie fakt przynależności danego obiektu do wielozbioru, bez względu na krotność tej przynależności, wielozbiór można łatwo skonwertować na zwykły zbiór za pomocą operacji asSet () (patrz tabela 9.1). Operowanie na kolekcjach OCL umożliwiają różne operacje udostępniane przez ten język; najczęściej używane z nich przedstawiamy w tabeli 9.1. Tabela 9.1. Wybrane operacje kolekcji OCL

Operacja

Zwracana wartość

size

Liczba obiektów kolekcji.

i ncl udes(oóie/ri)

True, gdy obiekt

select(wyrażenie)

Kolekcja złożona z tych elementów oryginalnej kolekcji, dla których wyrażenie ma wartość True.

u n i o n ( k o l e kej a)

Suma teoriomnogościowa dwóch kolekcji — oryginalnej i kolekcji stanowiącej p a r a m e t r wywołania: o b e j m u j e elementy należące

należy do kolekcji, False w przeciwnym razie.

do przynajmniej jednej z obu kolekcji. i ntersecti on(kolekcja)

Iloczyn teoriomnogościowy dwóch kolekcji — oryginalnej i kolekcji stanowiącej parametr wywołania: obejmuje elementy należące do obu kolekcji równocześnie.

a s S e t ( k o l e kej a)

Zbiór zawierający wszystkie elementy obecne w kole kej i stanowiącej parametr wywołania.

W języku OCL odwołania do atrybutów różnią się od odwołań do kolekcji: pierwsze mają postać notacji „kropkowej", drugie używają operatora ->. Przykładowo drugie ze sformułowanych wcześniej wymagań — dostępność danego turnieju wyłącznie dla członków ligi organizującej ten turniej — można wyrazić następująco 4 : context T o u r n a m e n t : : a c c e p t P l a y e r ( p : P l a y e r )

pre:

league.piayers->includes(p) Zwróćmy uwagę na sposób identyfikowania klas League i PI ayer skojarzonych z klasą Tournament, której operacja stanowi kontekst warunku: zgodnie z zasadami języka OCL, klasy takie identyfikowane są przez nazwy prowadzących do nich skojarzeń, a jeżeli któreś ze skojarzeń nie posiada nazwy, klasa identyfikowana jest przez swą własną nazwę, pisaną jednak z małej litery. Wychodząc z klasy Tournament (stanowiącej element kontekstu) docieramy najpierw do klasy League (identyfikowanej jako 1 eague — skojarzenie nie posiada nazwy), po czym na podstawie jej agregacji z graczami (PI ayer), będącymi jej członkami, otrzymujemy kolekcję pl ayers (wspomniane skojarzenie również nie jest nazwane). Dla tej kolekcji wywołujemy operację i ncl udes (), parametr wywołania jest ten sam, co w operacji stanowiącej kontekst warunku.

4

Choć autorzy nie piszą tego wyraźnie, z treści i późniejszych rysunków wynika, ze p l a y e r s jest tutaj nazwą skojarzenia — przyp. tłum.

9.4. Aktywności specyfikowania interfejsów

415

Poniższy przykład stanowi natomiast ilustrację użycia operacji a s S e t ( ) . Modyfikując nieco założenie nr 3, załóżmy, że chcemy w klasie League jako wynik (resul t) operacji League: :getActivePlayers:Set uzyskiwać zbiór aktywnych graczy w tej lidze. Kolekcjonowanie w tym celu graczy uczestniczących w poszczególnych turniejach organizowanych przez tę ligę to dopiero połowa roboty, bowiem dany gracz może się wielokrotnie znaleźć w wynikowym wielozbiorze — „wielozbiorze", bo wynikiem iterowania dwóch łub więcej skojarzeń typu „jeden na wiełe" lub „wiele na wiele" jest zawsze wiełozbiór. Pożądany wynik otrzymamy dopiero po jego skonwertowaniu na zwykły zbiór5: context L e a g u e : : g e t A c t i v e P l a y e r s : S e t

post:

result = tournaments.players->asSet()

9.3.6. Kwantyfikatory OCL: forAll() i exists() W uzupełnieniu do operacji opisanych w tabeli 9.1, język OCL udostępnia także dwie operacje o charakterze kwantyfikatorów, opisane w tabeli 9.2. Tabela 9.2. Kwantyfikatory języka OCL

Kwantyfikator

Zwracana wartość

forAl1(zmienna:kolekcja\wyrażeń ie)

True, jeżeli wyrażenie

ma wartość True w wyniku

podstawienia pod zmi enną dowolnego elementu ko l ekcj i. exists

{zmienna -.kolekcja] wyrażeń i e)

True, jeżeli w kolekcji istnieje przynajmniej jeden element taki, że wyrażenie zwraca wartość True w wyniku jego podstawienia pod zmienną.

Gdy na przykład chcemy się upewnić, że wszystkie mecze w ramach danego turnieju rozpoczynają się i kończą w zakreślonych dla tego turnieju ramach czasowych, możemy sformułować następujący niezmiennik: context Tournament inv: matches->forAl1(m:Match

| !m.start.before(start)

and

Im.end.after(end))

Zatem ogólnie rzecz biorąc, kwantyfikator forAl 1 () weryfikuje spełnienie pewnego warunku przez wszystkie elementy kolekcji, podczas gdy kwantyfikator e x i s t s ( ) weryfikuje istnienie w kolekcji elementu (co najmniej jednego) spełniającego pewien warunek. Gdy na przykład chcemy mieć pewność, że w każdym turnieju w pierwszym dniu zostanie rozegrany co najmniej jeden mecz, formułujemy następujące ograniczenie: context Tournament inv:

matches->exists(m:Match | m . s t a r t . e q u a l s ( s t a r t ) )

5

Podobnie i tutaj tournaments i pl ayers są nazwami odnośnych skojarzeń — przyp.

tłum.

416

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

9.4. Aktywności specyfikowania interfejsów Na specyfikowanie interfejsów składają się następujące aktywności: • Identyfikowanie brakujących atrybutów i operacji (patrz sekcja 9.4.1), • Specyfikowanie typów, sygnatur i widzialności (patrz sekcja 9.4.2), • Specyfikowanie warunków wstępnych i warunków końcowych (patrz sekcja 9.4.3), • Specyfikowanie niezmienników (patrz sekcja 9.4.4), • Dziedziczenie kontraktów (patrz sekcja 9.4.5). Zilustrujemy je na przykładzie modelu projektu obiektów systemu ARENA, a konkretnie na przykładzie obiektów uczestniczących w przypadku użycia AnnounceTournament (patrz sekcja 5.6). W czasie analizy wymagań zidentyfikowaliśmy kilka klas brzegowych, sterujących i encji — i tak na przykład klasa TournamentForm odpowiedzialna jest za generowanie i przetwarzanie wszystkich formularzy składających się na interfejs użytkownika, zaś klasa TournamentControl — za koordynowanie wszystkich transakcji odbywających się między klasą TournamentForm a klasami encji Tournament, PI ayer i Match. Na rysunku 9.8 przedstawiono atrybuty, operacje i skojarzenia tych klas zidentyfikowane na etapie analizy wymagań. Uzupełnianie widocznego na rysunku modelu rozpoczniemy od zidentyfikowania brakujących atrybutów i operacji.

Rysunek 9.8. Obiekty modelu analitycznego systemu ARENA zidentyfikowane w ramach analizy przypadku użycia AnnounceTournament. Dla uproszczenia pominięto niektóre elementy

9.4. Aktywności specyfikowania interfejsów

417

9.4.1. identyfikowanie brakujących atrybutów i operacji Przeanalizujemy teraz opis usług świadczonych przez poszczególne podsystemy systemu ARENA i spróbujemy zidentyfikować brakujące atrybuty i operacje. To, że w czasie analizy wymagań nie uwzględniliśmy wielu atrybutów, wynika z faktu, że koncentrowaliśmy się wówczas na funkcjonalności systemu, którą opisywaliśmy głównie w formie modelu przypadków użycia (w przeciwieństwie do operacji modelu projektu obiektów). Kierując się wiedzą z dziedziny aplikacyjnej, podczas konstruowania modelu analitycznego ignorowaliśmy po prostu detale systemu niemające związku z tą dziedziną. Jedną z rzeczy, które uświadomiliśmy sobie już na początku projektowania obiektów systemu ARENA, był fakt, iż liczba równolegle rozgrywanych turniejów i meczów stanowi podstawowy czynnik wpływający na obciążenie zasobów tegoż systemu i prowadzący do powstania „wąskiego gardła" przepustowości. Stało się jasne, że nie przewidzieliśmy możliwości nadużyć ze strony graczy, którzy chcieliby rozgrywać kilka meczów równocześnie (oczywiście, każdy mecz w innym turnieju). Po negocjacjach z ldientem zgodziliśmy się, że taka sytuacja nie sprzyja ani serwerowi (który doznaje silnego obciążenia), ani graczowi (który zamiast skupić się na jednym meczu, rozprasza swą uwagę na kilka, co może negatywnie odbić się na jakości gry), ani operatorowi czerpiącemu zyski z reklam (wielokrotne wysyłanie tych samych banerów na ten sam komputer w tym samym czasie nie zwiększa opłaty sponsoringowej). W zamian można by zaproponować graczowi opcję sekwencyjnego uczestniczenia w poszczególnych turniejach, która likwiduje wszystkie opisane niedostatki. Aby więc zapobiec uczestnictwu gracza w dwóch różnych turniejach zachodzących na siebie czasowo, narysowaliśmy najpierw diagram sekwencji reprezentujący związany z tym przepływ sterowania i danych (patrz rysunek 9.9). Tworzenie tego diagramu uświadomiło nam brak operacji i sPl ayerOverbooked() sprawdzającej, czy gracz zgłaszający udział do danego turnieju nie jest już uczestnikiem innego turnieju odbywającego się w tym samym czasie.

Rysunek 9.9. Diagram sekwencji dla operacji applyForTournament(), prowadzący do zidentyfikowania brakującej operacji i sPl ayerOverbooked () wykrywającej uczestnictwo gracza w dwóch (lub więcej) turniejach równocześnie

418

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

Ponieważ operacja isPlayerOverbooked() wymusza zachowanie reguł, które ostatnio zidentyfikowaliśmy, a więc związana jest z organizacją turnieju, a nie z konkretnym graczem czy konkretnym turniejem, przeto powierzyliśmy ją klasie TournamentControl. Dzięki temu model obiektów encji stał się prostszy i łatwiej modyfikowalny. Przykładowo kolejne reguły organizacyjne, jakich zażądać może klient (uczestnictwo gracza w co najwyżej jednym turnieju w ciągu tygodnia czy zapobieganie udziałowi nieletnich graczy w późnych godzinach wieczornych i nocnych), mogą być implementowane wyłącznie w drodze modyfikacji klasy TournamentControl, bez naruszania klas encji. I tak oto zidentyfikowaliśmy dodatkową operacją związaną z przypadkiem użycia Appl yForTournament i nie jest tajemnicą, że ta modyfikacja modelu projektu obiektów nie jest ostatnia — analiza kolejnych podsystemów i przypadków użycia na pewno doprowadzi do wielu kolejnych.

9.4.2. Specyfikowanie typów, sygnatur i widzialności Teraz przystąpimy do specyfikowania typów atrybutów, sygnatur operacji i zakresów widoczności poszczególnych atrybutów i operacji. Specyfikowanie typów prowadzi do ulepszenia modelu projektu obiektów pod dwoma względami. Po pierwsze, konkretyzując typ atrybutu, wybieramy zakres wartości, jakie może on przyjmować: gdy przykładowo określamy typ atrybutów wyznaczających chwilę rozpoczęcia i zakończenia turnieju, decydujemy się na konkretną granulację pomiaru czasu, czyli dokładność, z jaką czas ten będzie kontrolowany przez aplikację. Przyjmując granulację na poziomie poszczególnych sekund (czyli czasu dnia), dajemy kapitanom lig możliwość organizowania wielu turniejów w ciągu jednego dnia, kiedy przyjmiemy granulację na poziomie dni (czyli dat), pozbawiamy ich tej możliwości. Po drugie, specyfikowanie typów atrybutów prowadzi do odwzorowywania klas i atrybutów modelu we wbudowane typy oferowane przez środowisko programistyczne. Wybierając na przykład typ Stri ng języka Java do reprezentowania atrybutu name klas League i Tournament, sprawiamy, że dostępne dla tego atrybutu stają się wszystkie operacje, jakie Java oferuje dla danych tego typu. Przy okazji konfrontujemy też klasy naszego modelu z klasami oferowanymi przez istniejące komponenty. W pakiecie J a v a . u t i 1 na przykład znajduje się kilka klas implementujących kolekcje elementów — i tak interfejs Li st dostarcza środki do operowania kolekcjami uporządkowanymi, bez względu na ich fizyczną realizację, a interfejs Map oferuje mechanizm mapowania tablicy unikalnych kluczy w dowolny zbiór encji. Wybierzemy interfejs Li s t do reprezentowania kolekcji obiektów, na przykład kolekcji graczy uczestniczących w danym turnieju, zaś interfejs Map posłuży do odwzorowań między obiektami, na przykład odwzorowywania poszczególnych graczy w ich punktacje. Na zakończenie określimy widzialność każdego atrybutu i każdej operacji; w tym celu podzielimy atrybuty na dwie grupy: te, które powinny być publicznie dostępne, i te, do których dostęp możliwy będzie wyłącznie za pośrednictwem metod klasy. Na podobnej zasadzie dokonamy rozróżnienia między operacjami wchodzącymi w skład interfejsu klasy a operacjami będącymi metodami użytkowymi klasy na jej wewnętrzne potrzeby. W przypadku klas

9.4. Aktywności specyfikowania interfejsów

419

abstrakcyjnych i klas przeznaczonych dó specjalizowania zidentyfikujemy także operacje chronione, przeznaczone na użytek tworzonych subklas. Na rysunku 9.10 widoczny jest efekt wzbogacenia modelu z rysunku 9.8 o typy atrybutów, sygnatury operacji i zakresy widzialności.

Rysunek 9.10. Wzbogacenie modelu projektu obiektów systemu ARENA o specyfikację typów, sygnatur i widzialności. Dla przejrzystości pominięto niektóre elementy

Mając za sobą sprecyzowanie typów, sygnatur i widoczności, zajmiemy się specyfikowaniem zachowania poszczególnych klas i przypadkami granicznymi tych zachowań. Temu celowi służy definiowanie kontraktów.

9.4.3. Specyfikowanie warunków wstępnych i warunków końcowych Jak już wspominaliśmy, kontrakt dla danej klasy jest wyrazem porozumienia jej implementatora (implementatorów) z użytkownikami. Warunki wstępne dla wszystkich operacji są częścią kontraktu wiążącą użytkownika klasy, natomiast warunki końcowe stanowią zobowiązanie dla implementatora, o ile użytkownik klasy dotrzyma wspomnianych warunków wstępnych. Ekstender rozwijający klasę dziedziczy kontrakt po implementatorze tej klasy. I tak na przykład w sekcji 9.4.1 zdefiniowaliśmy operację i sPl ayerOverbooked () klasy TournamentControl, wykrywającą okoliczności wykluczające gracza z udziału w danym turnieju. Jako implementatorzy przyjęliśmy milcząco założenie, iż weryfikowany gracz nie jest już w tym turnieju zarejestrowany i że sprawdzanie nie odbywa się post factum (bo — oczywiście

420

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

— otrzymalibyśmy wtedy werdykt wykluczający — każdy turniej zachodzi przecież czasowo sam na siebie). Zweryfikowaniem prawdziwości tego założenia obarczyliśmy użytkownika klasy, co znajduje odzwierciedlenie w warunku wstępnym poniższego kontraktu: /* działanie operacji isPlayerOverbooked opiera się na założeniu, * że gracz nie jest już zarejestrowany w odnośnym turnieju V

context T o u r n a m e n t C o n t r o l : : i s P l a y e r O v e r b o o k e d ( p )

pre:

not p.tournaments->includes(self.tournament) /* Gracz nie może uczestniczyć w dwóch turniejach nakładających

się czasowo

V

context T o u r n a m e n t C o n t r o l : : i s P l a y e r O v e r b o o k e d ( p )

r e s u l t = p.tournaments->exists(t|

post:

t.overlaps(self.tournament))

Warunki wstępne i warunki końcowe mogą być także wykorzystywane do specyfikowania zależności między operacjami tej samej klasy, a szczególnie żądanej kolejności ich wywoływania, jak w przypadku klasy TournamentControl. Dla danego turnieju (Tournament) nie możemy rozwiązać kwestii sponsorowania bez uprzedniego otrzymania listy reklamodawców zainteresowanych sponsoringiem. Nie możemy ogłosić rozpoczęcia turnieju przed dokonaniem wyboru sponsora (lub rezygnacją kapitana ligi z takiego wyboru). Właściwą kolejność poszczególnych operacji określimy w formie zestawu warunków wstępnych i końcowych, badających stan obiektu klasy Tournament. I tak konieczność uzyskania listy reldamodawców przed wybraniem sponsora zapisać możemy następująco: context T o u r n a m e n t C o n t r o l : : s e l e c t S p o n s o r s ( a d v e r t i s e r s )

pre:

interestedSponsors->notEmpty() Kolejny warunek chroni przed wielokrotnym wyborem sponsora dla turnieju — operacja TournamentControl . s e l e c t S p o n s o r s ( ) może być wywołana tylko raz: context T o u r n a m e n t C o n t r o l : : s e l e c t S p o n s o r s ( a d v e r t i s e r s )

pre:

tournament.sponsors->isEmpty() Wreszcie, dla zweryfikowania prawidłowości kojarzenia turniejów ze sponsorami przez metodę TournamentControl .sel ectSponsors (), dodamy następujący warunek końcowy: context T o u r n a m e n t C o n t r o l : : s e l e c t S p o n s o r s ( a d v e r t i s e r s )

post:

tournament.sponsors.equals(adverti sers) Poniżej przedstawiamy kompletny zbiór ograniczeń OCL, wymuszających właściwą kolejność wywoływania operacji sel ectSponsors (), adverti seTournamentQ i acceptPl ayer ().

421

9.4. Aktywności specyfikowania interfejsów

/* Warunki

wstępne i końcowe wymuszające

* operacji klasy

właściwą kolejność

wywoływania

TournamentControl

7 context T o u r n a m e n t C o n t r o l : : s e l e c t S p o n s o r s ( a d v e r t i s e r s )

pre:

interestedSponsors->notEmpty() and tournament.sponsors->isEmpty() context T o u r n a m e n t C o n t r o l : : s e l e c t S p o n s o r s ( a d v e r t i s e r s )

post:

tournament.sponsors.equals(adverti sers) context T o u r n a m e n t C o n t r o l : : a d v e r t i s e T o u r n a m e n t ( )

pre:

tournament.sponsors->isEmpty() and not tournament.advertised context T o u r n a m e n t C o n t r o l : : a d v e r t i s e T o u r n a m e n t ( )

post:

tournament.advertised context T o u r n a m e n t C o n t r o l : : a c c e p t P l a y e r ( p )

pre:

tournament.advertised and i nterestedPlayers->includes(p) and not isPlayerOverbooked(p) context T o u r n a m e n t C o n t r o l : : a c c e p t P l a y e r ( p )

post:

tournament.piayers->i ncludes(p)

9.4.4. Specyfikowanie niezmienników Po opanowaniu składni i koncepcji języka OCL pisanie kontraktów dla poszczególnych operacji wydaje się dość proste. Koncepcja kontraktu między implementatorem a użytkownikiem jest intuicyjnie jasna („zapewniam ci to a to, pod warunkiem, że spełnisz to a to") i opiera się na krótkich odcinkach czasu (czyli wykonywaniu poszczególnych operacji). Nie ułatwia to jednak zrozumienia natury samej klasy i jej zasadniczych własności, bowiem związana z tym informacja jest rozproszona na wiele warunków i wiele operacji. W tym kontekście istotnego znaczenia nabierają niezmienniki klasy. Niezmienniki formułuje się jednak trudniej niż warunki wstępne i końcowe, choćby z tego względu, że odzwierciedlają one w bardziej bezpośredni sposób naturę samej klasy — naturę zwykle skomplikowaną. Stanowią one długoterminowy kontrakt, rozszerzający, a niekiedy przedefiniowujący kontrakty dotyczące poszczególnych operacji. Aktywności związane z identyfikowaniem niezmienników podobne są do związanych z konstruowaniem klas abstrakcyjnych na etapie analizy wymagań (patrz sekcja 5.4.10): niektóre z niezmienników są wręcz oczywiste, niektóre dają się wydedukować z kontraktów dla poszczególnych operacji. I tak na przykład w systemie ARENA oczywistym niezmiennikiem jest rozpoczynanie i kończenie każdego meczu turnieju w założonych dla tego turnieju ramach czasowych:

422

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

context Tournament inv: matches->forAl1(m|im.start.before(start)

and

im.end.after(end))

Niezmiennikiem mniej oczywistym, choć dającym się łatwo wydedukować z kontraktu dla klasy TournamentControl, jest rozłączność czasowa dwóch różnych turniejów, w których bierze udział ten sam gracz — innymi słowy, wśród wszystkich turniejów, w jakich dany gracz uczestniczy, dowolne dwa różne są czasowo rozłączne. Mimo iż można wyczytać tę interesującą właściwość, analizując operację TournamentControl .isPlayerOverbookedQ, możemy zapisać ją w sposób jawny jako niezmiennik: context TournamentControl inv: tournament.piayers->forAl1(p[p.tournaments->forAl 1(t| t tournament implies not t.overlap(tournament)))

Gdy ograniczenia definiowane są na bazie wielu skojarzeń, stają się skomplikowane i trudne do zrozumienia, zwłaszcza gdy zawierają zagnieżdżone kwantyfikatory forAl 1. Jako przykład weźmy pod uwagę kolejny niezmiennik, którym jest zarejestrowanie w turnieju jako warunek rozgrywania meczów w ramach tego turnieju:

W powyższym ograniczeniu występują trzy kolekcje: players, p. tournaments i t . matches. Możemy jednak uprościć jego zapis, wykorzystując wielozbiór powstający w wyniku nawigowania po szeregu skojarzeń: context Match inv: piayers.tournaments.matches.i ncl udes(sel f)

Podobne zabiegi redukujące liczbę operacji w zapisie ograniczeń są jak najbardziej pożądane z punktu widzenia czytelności tych ograniczeń. Jak wynika z przytoczonych przykładów, stosunkowo łatwo generować obszerne nawet zbiory ograniczeń dla poszczególnych klas. Łatwość ta nie zawsze jednak idzie w parze z czytelnością, bo formułowanie czytelnych i poprawnych ograniczeń takie łatwe już nie jest. Nie zapominajmy, że celem artykułowania niezmienników jest wyeksponowanie założeń, jakie implementator klasy przyjmuje względem jej użytkowników. Niezmienniki powinny zatem

423

9.4. Aktywności specyfikowania interfejsów

w p i e r w s z y m rzędzie o d z w i e r c i e d l a ć w y r a ź n e w a r u n k i g r a n i c z n e , k t ó r e p o d i n n ą p o s t a c i ą n i e byłyby tak oczywiste. P o n i ż e j p r z e d s t a w i a m y kilka h e u r y s t y k , k t ó r e ułatwić m o g ą f o r m u ł o wanie ograniczeń w sposób czytelny i zrozumiały. Heurystyki p o m o c n e w c z y t e l n y m f o r m u ł o w a n i u o g r a n i c z e ń Skoncentruj się na czasie życia instancji danej klasy. Ograniczenia specyficzne dla operacji lub tylko dla pewnych stanów obiektu najlepiej wyrażają się jako warunki wstępne lub końcowe, na przykład: •

„różni gracze m a j ą różne adresy e-maił" — to i n f o r m a c j a p e r m a n e n t n a o charakterze niezmiennika,



„dany gracz może być w danym momencie zarejestrowany w co najwyżej jednym z odbywających się równolegle turniejów" — to warunek wstępny operacji Tournament Form. appl y ForTournament ().

Zidentyfikuj wartości specjalne dla każdego atrybutu. Wartość zero, wartość pusta, wartości szczególne w danym zakresie oraz atrybuty zależne od innych atrybutów to najczęstsze źródła nieporozumień i błędów, na przykład: •

w meczu musi brać udział co najmniej jeden gracz,

® rezultatem meczu jest wyłonienie dokładnie jednego zwycięzcy. Zidentyfikuj szczególne przypadki skojarzeń, zwłaszcza te ich aspekty, których nie sposób wyrazić za pomocą krotności, na przykład: •

zbiór graczy uczestniczących w turnieju (tournament. pl ayers) jest podzbiorem zbioru członków ligi organizującej ten turniej (tournament. 1 eague.pl ayers).

Zidentyfikuj wymaganą kolejność wykonywania poszczególnych TournamentControl opisywanym w sekcji 9.4.3.

operacji, jak w przypadku klasy

Wykorzystuj metody pomocnicze do hermetyzacji skomplikowanych obliczeń. W rezultacie otrzymasz zarówno bardziej czytelne ograniczenia, jak i łatwiejszy w testowaniu kod programu. Przykładem takiej metody pomocniczej może być: •

Tournament .overl aps ( t : Tournament) sprawdzająca, czy dwa turnieje nie są rozłączne czasowo.

Unikaj ograniczeń związanych z trawersowaniem wielu skojarzeń. Formułowanie takich ograniczeń zwiększa stopień zależności między zbiorami niepowiązanych klas, co — oczywiście — jest efektem niepożądanym. Przykładowo specyfikując, iż długość trwania meczu nie może przewyższać wartości granicznej ustalonej dla danej gry (właściwej temu meczowi), możemy tę wartość graniczą wyrazić jako: •

match.tournament.1eague.game.maxMatchDurati on

Jeżeli jednak zdefiniujemy dla klasy Match operację pomocniczą getGame (), zwracającą obiekt subklasy Game reprezentujący grę właściwą danemu meczowi, powyższe ograniczenie będziemy mogli napisać w bardziej czytelnej postaci: •

match.getGame().maxMatchDuration

N i e z m i e n n i k i , w a r u n k i w s t ę p n e i w a r u n k i k o ń c o w e określają s e m a n t y k ę d a n e j operacji p r z e z j a w n e w y a r t y k u ł o w a n i e o k o l i c z n o ś c i , k t ó r e m u s z ą w y s t ę p o w a ć p r z e d jej w y w o ł a n i e m i p o jej z a k o ń c z e n i u . K o n t r a k t klasy s t a n o w i więc czytelną d o k u m e n t a c j e dla jej u ż y t k o w n i k a . Jak za chwilę z o b a c z y m y , k o n t r a k t d a n e j klasy jest r ó w n i e ż i s t o t n y dla jej e k s t e n d e r a .

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

424

9.4.5. Dziedziczenie kontraktów W języku programowania uwzględniającym polimorfizm klasa może być zastępowana przez swe klasy pochodne. Oznacza to między innymi, że operacje zdefiniowane w superldasie mogą być wywoływane zarówno na rzecz jej samej, jak i na rzecz jej subklas. Użytkownik danej klasy ma zatem prawo oczekiwać, że obowiązujące w stosunku do niej kontrakty respektowane będą także dla jej subklas. Zasadę tę nazywamy dziedziczeniem kontraktów. Rozpatrzmy hierarchię dziedziczenia klas w systemie ARENA, widoczną na rysunku 9.11, i załóżmy, że dla klasy User określony został niezmiennik w postaci reguły, iż dla każdego użytkownika (User) powinien istnieć niepusty adres e-mail, dzięki czemu użytkownik ten będzie mógł być powiadamiany o zdarzeniach. Jeżeli teraz w dowolnym momencie projektu zdecydujemy, że kibic (Spectator) nie musi dostarczać adresu e-mail, kontrakt dla ldasy User zostanie złamany przez klasę Spectator. By takiej sytuacji uniknąć, należy bądź to usunąć klasę Spectator z drzewa subklas klasy User, bądź też zrezygnować ze wspomnianego niezmiennika (prawdopodobnie na rzecz podobnego, traktującego o adresach e-mail).

Rysunek 9.11. Prosty przykład dziedziczenia kontraktu. Niezmiennik zdefiniowany dla superklasy musi być zachowany w każdej z jej subklas

Ogólnie dziedziczenie kontraktów rządzi się trzema regułami, dotyczącymi kolejno:

6



warunków wstępnych: przedefiniowywanie metody w subklasie może osłabiać związane z nią warunki wstępne, bowiem funkcjonalność metody przedefiniowanej może być bogatsza niż funkcjonalność metody oryginalnej. Przykładowo w kontrakcie dla klasy SimpleKnockOutStyle wywodzącej się z klasy abstrakcyjnej TournamentStyle znajduje się warunek wstępny dla wszystkich jej operacji, zgodnie z którym liczba graczy zarejestrowanych w turnieju musi być potęgą liczby 2. W klasie pochodnej Compl ex '-•KnockOutStyl e możemy ten warunek osłabić, dopuszczając dowolną liczbę graczy.



warunków końcowych: przedefiniowana metoda musi zapewnić te same (lub bardziej rygorystyczne) warunki końcowe, co metoda oryginalna. Załóżmy, że implementujemy interfejs Set za pomocą dziedziczenia implementacyjnego po klasie Li st (patrz sekcja 8.3.2), co — jak wiemy — nie jest praktyką zalecaną. Warunkiem końcowym metody List.add() jest zwiększenie o 1 liczby elementów zawartych w kolekcji tej klasy. Wywołanie metody Set. add () niekoniecznie musi zwiększać liczbę elementów kolekcji6, klasa Set łamie więc kontrakt ustanowiony dla klasy List, co jest ważną przesłanką na rzecz unikania dziedziczenia implementacyjnego.

Dodawanie do zbioru elementu już w nim obecnego nie wywołuje żadnej akcji, nie zwiększa zatem liczebności zbioru — przyp. tłum.

9.5. Zarządzanie projektowaniem obiektów

425

• niezmienników: subklasa musi respektować wszystkie niezmienniki zdefiniowane dla superklasy, co najwyżej definiując nowe. Przykładowo jednym z niezmienników klasy Collection jest nieujemny rozmiar kolekcji. Klasa List, jako subklasa klasy Collection, respektuje ten niezmiennik, dodając nowy, dotyczący uporządkowania elementów. Dziedziczenie kontraktów staje się szczególnie użyteczne przy definiowaniu klas abstrakcyjnych lub interfejsów podlegających na przedefiniowywaniu (implementowaniu) przez ekstenderów. Precyzyjne zdefiniowanie granic między klasą kliencką a interfejsem umożliwia ekstenderom implementowanie specjalizowanych subklas bez znajomości kodu źródłowego, w którym będą wykorzystywane. Dziedziczenie kontraktów jest ponadto konsekwencją zasady zastępowania Liskov (patrz sekcja 8.3.4), ponieważ każde wystąpienie klasy oryginalnej może być zastąpione wystąpieniem jej subklasy.

9.5. Zarządzanie projektowaniem obiektów Z zarządzaniem etapem projektowania obiektów wiążą się dwa podstawowe wyzwania. • Rosnąca złożoność komunikacji. W tej fazie liczba uczestników zaangażowanych w projekt zwiększa się pokaźnie. Model projektu obiektów i kod programu powstają w wyniku współpracy wielu ludzi. Zadaniem menedżera jest zapewnienie spójności podejmowanych decyzji i ich zgodności z celami projektowymi. • Spójność z poprzednimi decyzjami i istniejącymi dokumentami. Tak to już jest, że programiści nie doceniają należycie znaczenia decyzji podejmowanych na etapie analizy wymagań i etapie projektowania systemu; gdy przychodzi do doskonalenia modelu projektu obiektów, programiści mogą kwestionować wiele z tych decyzji bądź weryfikować ich podjęcie. Menedżer odpowiedzialny jest wówczas za zapewnienie dokumentowania tych zdarzeń, tak by wszystkie dokumenty odzwierciedlały bieżący stan rzeczy. Zajmiemy się tymi wyzwaniami w sekcji 9.5.1, w której też opiszemy dokument projektu obiektów (ODD) — jego tworzenie i utrzymywanie aktualności oraz relacje z innymi dokumentami. W sekcji 5.9.1 omówimy natomiast role przypisywane uczestnikom w związku z projektowaniem obiektów.

9.5.1. Dokumentowanie projektowania obiektów Etap projektowania obiektów dokumentowany jest w formie Dokumentu Projektu Obiektów, w skrócie ODD (Object Design Document). W dokumencie tym odzwierciedlane są kompromisy rozstrzygane przez programistów, wytyczne, jakimi kierowali się przy projektowaniu interfejsów poszczególnych podsystemów, podział (dekompozycja)7 podsystemów na pakiety i klasy oraz interfejsy poszczególnych klas. Dokument ODD używany jest do wymiany informacji

7

Nie mylić z dekompozycją systemu na podsystemy, dokonywaną na etapie projektowania systemu — przyp. tłum.

426

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

między zespołami oraz jako punkt odniesienia na etapie testowania. Odbiorcami dokumentu ODD są między innymi architekci systemu (czyli programiści, którzy uczestniczyli w fazie projektowania systemu), programiści implementujący podsystemy oraz testerzy. Podczas budowania dokumentu ODD możemy skorzystać z trzech podejść. Oto one. • Samoistny dokument ODD tworzony na podstawie modelu. Odwzorowywanie modelu w dokument odbywa się tu tak samo jak na etapach analizy wymagań i projektowania systemu: tworzymy model, pilnując jego aktualności, i na jego podstawie generujemy dokument w sposób automatyczny. W rezultacie w dokumencie ODD znajdzie się także opis obiektów dziedziny aplikacyjnej, zidentyfikowanych na etapie analizy. Wadą takiego rozwiązania jest duża redundancja, spowodowana powieleniem dużej części dokumentu RAD i wynikający stąd spory wysiłek związany z utrzymywaniem spójności obu dokumentów — RAD i ODD — w przypadku modyfikowania któregoś z nich, jak również podczas zmiany kodu źródłowego. • Dokument ODD budowany jako rozszerzenie dokumentu RAD. Z perspektywy tej metody model projektu obiektów traktowany jest jak rozszerzenie modelu analitycznego — czyli jak zbiór obiektów dziedziny aplikacyjnej wzbogacony o obiekty realizacyjne. Pozbywamy się w ten sposób redundancji i ułatwiamy sobie utrzymywanie spójności dokumentów ODD i RAD, ale też ryzykujemy wypełnienie dokumentu RAD informacjami nieistotnymi dla klienta i użytkowników. Co więcej, projektowanie obiektów wykracza zwykle daleko poza samo identyfikowanie „dodatkowych" obiektów, jakimi są obiekty realizacyjne, wymaga bowiem często modyfikowania lub transformowania obiektów aplikacyjnych w celu zapewnienia ich zgodności z celami projektowymi lub sprostania wymogom wydajnościowym. • Dokument ODD wbudowany w kod źródłowy aplikacji. Podobnie jak w pierwszym przypadku, sam dokument ODD generowany jest za pomocą narzędzi modelowania (patrz rysunek 9.12). Gdy tylko model projektowania obiektów okaże się w miarę stabilny, dokonujemy na jego podstawie (automatycznego) generowania namiastek (stubs) poszczególnych klas. Interfejs każdej klasy opisywany jest wówczas przy użyciu znacznikowanych komentarzy, umożliwiających rozróżnienie „zwykłych" komentarzy do kodu od opisów obiektów modelu. Tak wygenerowany kod możemy poddać przetwarzaniu przez parser ekstrahujący istotną informację i w ten sposób wygenerować dokument ODD. Dysponując modelem projektu obiektów osadzonym integralnie w kodzie źródłowym programu, możemy zapomnieć o modelu projektu obiektów w pierwotnej postaci. Zaletą tego rozwiązania jest łatwość utrzymywania spójności między (osadzonym) modelem projektu obiektów a kodem źródłowym oraz samym dokumentem ODD: gdy zmienia się kod źródłowy, uaktualnione zostają znaczniki opisu, po czym na ich podstawie można wygenerować aktualny dokument ODD. Podstawowa trudność w projektowaniu obiektów wiąże się z utrzymywaniem spójności między dwoma modelami i kodem źródłowym. Byłoby idealnie, gdybyśmy mogli utrzymywać całą trójkę — model analityczny, model projektu obiektów i kod źródłowy — za pomocą jednego narzędzia: każdy obiekt opisywany byłby wówczas w jednym miejscu, a spójność między dokumentacją, namiastkami klas i kodem byłaby zapewniona automatycznie.

9.5. Zarządzanie projektowaniem obiektów

427

Rysunek 9.12. Wbudowywanie modelu projektu obiektów w kod programu. Model ten dokumentowany jest w formie znacznikowanych komentarzy, na podstawie których generować można dokument O D D

Aktualnie jednak dysponujemy narzędziami opartymi na języku UML, które umożliwiają generowanie zarówno dokumentacji, jak i namiastek klas na podstawie modelu. Przykładowo drogą kolekcjonowania opisów towarzyszących poszczególnym klasom można automatycznie tworzyć słowniki wchodzące w skład dokumentów RAD (patrz rysunek 9.12). Generowanie namiastek klas, zwane inżynierią postępującą {forward, engineering), jest wtedy punktem startowym tworzenia interfejsów poszczególnych klas i kodu poszczególnych metod. Niektóre narzędzie do modelowania udostępniają technikę odwrotną — inżynierię odwracającą (reverse engineering), polegającą na regenerowaniu modelu na postawie kodu źródłowego. Technologia ta bywa pomocna przy tworzeniu modelu projektu obiektów dla istniejącego, starszego kodu. Trudno jednak spodziewać się pełnego automatyzmu w tej mierze, technologia wymaga sporo „ręcznej" pracy, choćby w związku z faktem, że nie jest możliwe odtwarzanie dwukierunkowych skojarzeń na podstawie samych tylko atrybutów referencyjnych.

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

428

Oferta narzędzi modelujących kurczy się wydatnie, gdy żądamy obsługi zależności w dwóch kierunkach, szczególnie zależności między modelem analitycznym a kodem źródłowym. Niektóre narzędzia, między innymi Rationale Rose [Rational, 2002] i Together Control Center [TogetherSoft, 2002], realizują to zadanie przez wbudowywanie w kod źródłowy komentarzy zawierających informacje dotyczące modelu analitycznego, takich jak informacje o skojarzeniach i innych konstrukcjach UML. Mimo iż umożliwia to śledzenie zmian syntaktycznych w kodzie programu, programiści wciąż muszą aktualizować opisy zawarte w modelu w celu czytelnego uwzględniania dokonywanych zmian. W efekcie programista zmuszony jest do używania dwóch różnych narzędzi, dla kodu źródłowego i dla modelu, nie jest więc niespodzianką, że zwykle cierpi na tym model. Dopóki więc nie pojawią się narzędzia oferujące lepszą obsługę utrzymywania spójności między modelami obiektowymi a kodem źródłowym, najbardziej rozsądną metodologią wydaje się generowanie dokumentu ODD w oparciu o kod źródłowy i ograniczanie dokumentu RAD wyłącznie do dziedziny aplikacyjnej. Redukuje to rozmiar redundantnej informacji, którą utrzymywać trzeba w aktualnym stanie, w dalszym ciągu jednak spójność między kodem źródłowym a modelem analitycznym musi być utrzymywana ręcznie. Nie jest to trudne zadanie, bowiem zmiany w kodzie źródłowym wymagają w większości modyfikowania tylko dokumentu ODD, rzadziej dokumentu RAD. Poniżej widoczny jest przykładowy szablon generowanego automatycznie dokumentu ODD. Dokument projektu obiektów 1. Wstęp 1.1. Kompromisy rozstrzygane w związku z projektowaniem obiektów 1.2. Wytyczne projektowania interfejsów 1.3. Definicje, akronimy i skróty 1.4. Odwołania 2. Pakiety 3. Interfejsy klas 4. Słownik

W pierwszej sekcji dokumentu ODD, mającej charakter wstępu, opisywane są ogólne kompromisy, jakie rozstrzygać musieli programiści (zakup gotowych komponentów albo tworzenie własnych, optymalizacja czasu odpowiedzi kontra optymalizacja wykorzystania pamięci i tym podobne), wskazówki i konwencje (między innymi konwencje nazewnicze, przypadki graniczne, mechanizmy obsługi wyjątków), a także ogólny układ dokumentu. Wytyczne projektowania interfejsów i konwencje kodowania to bodaj najważniejsze czynniki mogące usprawnić komunikację między programistami projektującymi interfejsy i opatrującymi ich elementy odpowiednimi nazwami. Oto przykłady wytycznych tego rodzaju. • Nazwy klas mają postać rzeczowników w liczbie pojedynczej. • Metody opatrywane są nazwami w postaci fraz czasownikowych, zaś pola i parametry — nazwami w postaci fraz rzeczownikowych.

9.5. Zarządzanie projektowaniem obiektów

429

• Sytuacje błędne sygnalizowane'są poprzez generowanie wyjątków, nie poprzez zwracanie kodu błędu przez metody. • Kolekcje i kontenery obowiązkowo posiadają metodę i t e r a t o r ( ) zwracającą Iterator. • Iteratory zwracane przez metody i t e r a t o r () są odporne na usuwanie elementów. Takie i podobne konwencje pomagają programistom (nieraz bardzo wielu) projektować interfejsy w sposób spójny. Gdy owe konwencje są jasne i wiadome jeszcze przed rozpoczęciem projektowania obiektów, łatwe jest przystosowanie się do nich — jest przy tym oczywiste, że powinny one pozostać niezmienne przez cały czas realizacji projektu. Przykład zalecanych konwencji kodowania w języku Java znaleźć można w publikacji [Sun, 2009]. Druga część dokumentu ODD, poświęcona pakietom, opisuje dekompozycję podsystemów na pakiety oraz organizację plików zawierających kod źródłowy programu. Dla każdego pakietu przewidziany jest ogólny opis, lista pakietów, od których jest zależny, i wskazanie oczekiwanego sposobu jego używania. W części trzeciej, dedykowanej interfejsom klas, opisywane są poszczególne klasy i ich publiczne interfejsy. Opis ten zawiera ogólną charakterystykę każdej z klas, jej zależności od innych klas i pakietów, jej publiczne atrybuty i operacje oraz wyjątki, jakie może generować. Początkowa wersja dokumentu ODD może być sporządzona już wtedy, gdy ustabilizuje się kształt dekompozycji systemu na podsystemy. Dokument ten będzie później aktualizowany za każdym razem, gdy pojawi się nowy interfejs lub zmodyfikowany zostanie któryś z istniejących. Nawet jeżeli niektóre podsystemy nie będą jeszcze funkcjonalne, znajomość ich interfejsów w kategoriach kodu źródłowego umożliwi programistom kodowanie podsystemów od nich zależnych i komunikowanie się w sposób jednoznaczny, a przy okazji odkrycie brakujących parametrów i nowych przypadków granicznych. Opracowywanie dokumentów ODD różni się od opracowania dokumentów innego typu przede wszystkich większą liczbą uczestników i większą dynamiką zmian. W celu sprostania konsekwencjom tej specyfiki można wykorzystywać narzędzia automatyzujące generowanie drugiej i trzeciej części dokumentu. Dla programów w języku Java umożliwia to Javadoc — narzędzie generujące strony W W W na podstawie znacznikowanych komentarzy towarzyszących kodowi źródłowemu. Na listingu 9.2 widoczny jest przykład tak skomentowanego kodu, przedstawiającego interfejs klasy Tournament systemu ARENA. Komentarz nagłówkowy zawiera opis przeznaczenia klasy, jej autorów, bieżącą wersję i odniesienie do powiązanych klas. Znaczniki @see wykorzystywane są przez Javadoc do generowania wykazów odwołań skrośnych (cross reference) między klasami. Po komentarzu nagłówkowym następują deklaracje klas i metod. Stosowny dla każdej metody komentarz zawiera jej krótki opis i przeznaczenie oraz specyfikację parametrów i typu zwracanego wyniku. Gdy z daną metodą związane są ograniczenia, są one specyfikowane w tym komentarzu za pomocą znaczników @pre, @post i (^invariant. Pierwsze zdanie każdego komentarza i wszystkie zawarte w tym komentarzu znaczniki ekstrahowane są i formatowane przez Javadoc. Listing 9.2. Znacznikowane komentarze Javadoc stowarzyszone z kodem źródłowym klasy Tournament /** Turniej, reprezentowany przez obiekt klasy Tournament, jest serię meczów, * reprezentowanych przez instancje klasy Match, rozgrywanych między graczami,

430

Rozdział 9. • Projektowanie obiektów: specyfikowanie interfejsów

* reprezentowanymi przez instancje klasy Player. Mecz kończy się wyłonieniem * dokładnie jednego zwycięzcy. Gra (reprezentowana przez specjalizację klasy * Game) i styl rozgrywania turnieju (reprezentowany przez specjalizację klasy * TournamentStyle) scj specyficzne dla ligi organizującej * przez instancję klasy League).

turnie)

(reprezentowanej

* Każdy turniej jest początkowo pusty; po jego ogłoszeniu dołączają do niego gracze, * planowane są mecze, które następnie zostają rozgrywane. *

Niezmienniki:

*

* Maksymalna

liczba graczy biorących udział w turnieju jest liczbą

* @invariant getMaxNumPlayers

* Aktualna liczba graczy zarejestrowanych * maksymalnej dopuszczalnej liczby: * @invariant getPlayersQ.sizeQ

dodatnią:

>0