O UML 2.0, czyli co nowego w projektowaniu: część 1

Sebastian Wyrwał wyrwal@firma-informatyczna.com

Ogólne uwagi i słów kilka o diagramach interakcji

Język UML staje się niepisanym standardem w projektowaniu oprogramowania wśród firm w Polsce. Wśród nich znajdują się firmy telekomunikacyjne, banki oraz mniejsze firmy wytwarzające różnego rodzaju oprogramowanie. Świadczy o tym zarówno wzrost zainteresowania narzędziami do projektowania jak i szkoleniami. Jest to niewątpliwie dobra tendencja. Niestety wiele osób uważa że znajomość języka UML równa jest umiejętności projektowania, co nie jest prawdą. Trzeba pamiętać, iż UML jest językiem zapisu, a nie metodyką projektowania. Widoczne jest jednak pewne niepokojące zjawisko: wiele osób zawodowo związanych z projektowaniem używa języka UML w sposób niewłaściwy, dokonując niebezpiecznych uproszczeń. Warto więc przyjrzeć się zagadnieniu w kontekście typowych zastosowań w projektowaniu (zwłaszcza nowych konstrukcji) i typowych błędów.

Niniejszy artykuł stanowi wprowadzenie do rozważań o użyciu języka UML w projektowaniu systemów informatycznych oraz zmian w wersji 2.0, które istotne są z punktu widzenia praktycznego użycia. Punktem wyjścia jest pobieżna analiza postaci specyfikacji w omawianej wersji oraz historia języka. Omówiono typowe błędy związane z zastosowaniem przypadków użycia oraz nowe konstrukcje w obrębie interakcji.

Specyfikacje

Specyfikacja języka UML w obecnej wersji składa się z dwóch części: Infrastructure (206 stron) oraz Superstructure 2.0 (694 strony). Pierwsza z nich dotyczy samego języka i stanowi podstawę architektoniczną dla drugiej, która opisuje język UML z punktu widzenia osoby, która go używa.

W specyfikacji Infrastructure opisany jest pakiet InfrastructureLibrary. Pierwszym jego składnikiem jest pakiet Core będący kompletnym metamodelem powtórnie wykorzystywanym przez inne metamodele dla UML, CWM, MOF, który także stanowi jądro MDA. W innych metamodelach wykorzystuje się byty z pakietu Core poprzez ich import lub specjalizację. W skład pakietu Core wchodzą następujące pakiety: PrimitiveTypes, Abstractions, Basic, Constructs. W skład pakietu PrimitiveTypes wchodzą typy danych używane w pakiecie Core (Integer, Boolean, String, UnlimitedNatural). Pakiet Abstractions zawiera w większości abstrakcyjne metaklasy, które są powtórnie używane przy tworzeniu innych metamodeli. Byty zawarte w pakiecie Constructs są raczej konkretne i są mocno związane z paradygmatem obiektowym. Pakiet Basic jest podzbiorem pakietu Constructs. Przyjrzyjmy się pakietowi BehavioralFeatures, który wchodzi w skład pakietu Abstractions. Są tam dwa elementy: BehavioralFeature (cecha zachowania) i Parameter (parametr). W definicji języka UML operację definiuje się jako dziedziczącą z BehavioralFeature.

Drugim pakietem wchodzącym w skład InfrastructureLibrary jest pakiet Profiles. Dostarcza on mechanizmów, które służą do dostosowania metamodeli do konkretnych technologii implementacji.

Uważna lektura specyfikacji Infrastructure przydatna jest twórcom narzędzi oraz osobom chcącym dogłębnie zrozumieć UML oraz inne specyfikacje.

Historia języka UML

Język UML wywodzący się od OMT przeszedł już dość długą ewolucję. Obecna wersja specyfikacji UML to 2.0 (sierpień 2005), a poprzednie istotne wersje to 1.5 (marzec 2003), 1.4 (wrzesień 2001) oraz 1.3 (marzec 2000). Wg OMG najważniejsza jest specyfikacja 1.4. Na rynku polskim pojawiło się kilka książek o języku UML, jednak choćby pobieżna lektura ich spisów treści dostępnych w Internecie nie nastraja optymistycznie. Można odnieść wrażenie, iż niektórzy tłumacze nie rozumieją o czym piszą, a przynajmniej stosują bardzo dziwną dla praktyków terminologię. Ponieważ na jakość publikacji książkowych zarówno, autor jak i czytelnicy mają mały wpływ, warto może na UML 2.0 spojrzeć z perspektywy użytkownika wersji 1.3 lub 1.4 nakreślając pewne typowe błędy w zastosowaniu języka UML (być może niecharakterystyczne dla wersji 2.0) Wydaje się, iż największe zmiany dotyczą diagramów aktywności, a dość duże – diagramów sekwencji. Prezentowany opis nie będzie ograniczał się do zmian w wersji 2.0 ale będzie obejmować najistotniejsze z punktu widzenia praktyki zmiany od wersji 1.3/1.4 do 2.0. Z uwagi na wagę problemu, wstępem do dalszych rozważań będą przypadki użycia, a w dalszej części zostaną omówione pobieżnie zmiany dotyczące interakcji.

O przypadkach użycia raz jeszcze

Choć nowa specyfikacja nie ma istotnego wpływu na diagramy przypadków użycia, to temat jest aktualny z uwagi na ilości błędów popełnianych przez różne osoby przy okazji specyfikowania funkcjonalności przy ich użyciu. Warto przypomnieć: służą one do modelowania funkcjonalności systemu (podsystemu, klasy) widzianej z zewnątrz tego systemu. Diagramy PU składają się z przypadków użycia, aktorów i relacji. Relacje można roboczo podzielić na 3 kategorie: relację generalizacji, która może występować pomiędzy aktorami; relację asocjacji, która występuje pomiędzy PU, a aktorem oraz relacje ze stereotypami <<extend>> i <<include>>, które występują pomiędzy samymi PU. Zamiany to niewielkie różnice redakcyjne i formalne, które na sam sposób użycia diagramów nie mają wpływu. W procesie modelowania i na szkoleniach typowe są spory, czy „coś” jest PU, czy nie – czyli czy jest aktywnością w obrębie „większego PU”. Karygodny błąd polega na tym, iż nowe przypadki użycia wprowadza się w miejsce jakiegoś istniejącego PU metodą dekompozycji na mniejsze części, gdy jedynym kryterium podziału jest zbyt duża wielkość scenariusza istniejącego PU, co przypomina podział funkcji na podfunkcje w programowaniu strukturalnym (gdy wielkość danej funkcji przekracza jeden ekran, dzieli się ją). Algorytm działania jest następujący: dla systemu (podsystemu, klasy, komponentu) znajduje się kilka PU, które się opisuje np. przy pomocy diagramów sekwencji. Jeśli diagram sekwencji jest „dłuższy” niż ekran (strona wydruku) to w miejsce danego „dużego” PU wprowadza się kilka „mniejszych”. Postępowanie takie w wypadku, gdy jedynym kryterium podziału jest rozmiar specyfikacji danego PU, świadczy dobitnie o całkowitym niezrozumieniu zasad modelowania wymagań przy pomocy PU. Niestety wielu projektantów w poważnych instytucjach popełnia tego typu błędy. Warto w tym miejscu dodać pewną dygresję: wiele osób chce od razu poznać i stosować „cały UML”. Chcąc go jednak używać właściwie należy opanować podstawy, a dopiero później używać bardziej zaawansowanych konstrukcji. Niestety proces uczenia się, czy wdrażania języka UML rzadko bywa sterowany przez eksperta; często osoby uczące się same tego języka są zadowolone, gdy uda im się coś narysować. Projektując idą na zbyt daleko idące kompromisy. Rezultatem jest to, że wynikowy projekt jest tylko z pozoru projektem w języku UML. Często zdarza się, iż dopiero potrzeba narysowania jakiegoś diagramu uwidacznia deficyty w rozumieniu własnego projektu i różnice w jego postrzeganiu przez różne osoby zaangażowane w ten projekt.

Połowa osób twierdzi np. iż daną czynność związaną z klientem „robi się” zawsze przy okazji jego rejestracji w systemie, inni twierdzą, iż można ją robić „bez rejestracji”. Takich przykładów można mnożyć dziesiątki jeśli nie setki. Niepokojące jest jednak to, iż projektanci za swoje problemy winią… język UML, a w żadnym wypadku siebie. Niektórzy postrzegają język UML jako „maszynkę” do robienia projektów, która za nich zrozumie otaczającą rzeczywistość i podejmie ważne decyzje projektowe. Rozważając sprawy związane z PU należy pamiętać, iż są to zagadnienia niezwykle istotne. Nieoszacowanie liczby PU może skutkować błędną wyceną projektu w kategoriach zasobów, czasowych oraz finansowych. Sam UML nie narzuca podziału na PU biznesowe i systemowe, ale warto dla pewnej klasy systemów stosować taki podział.

Wbrew prostocie zapisu przypadków użycia w języku UML, są one jednym z najtrudniejszych „miejsc”. Niektórzy analitycy zapominają (lub nie chcą się z tym pogodzić), że aktor zawsze jest na zewnątrz systemu i ulega pokusie definiowania modelowanego systemu jako aktora, w przypadku, gdy ów system „sam coś robi” np. nalicza odsetki. Duże trudności w użyciu języka UML mogą mieć dwie przyczyny. Pierwsza z nich to nieznajomość samego języka, sposobu zapisu i semantyki. Druga zaś to niezrozumienie własnego projektu i funkcjonowanie na poziomie różnego rodzaju „intuicji”. Niedopuszczalne uproszczenie, w którym modelowany system jest aktorem wynika prawdopodobnie z niezrozumienia zasad funkcjonowania organizacji, która systemu używa i skupiania się na szczegółach technicznych na nieodpowiednim etapie modelowania.

Diagramy interakcji w specyfikacji UML 2.0

Zasadniczymi elementami tych diagramów są linie życia i komunikaty. Mogą one być używane właściwie na każdym etapie życia systemu informatycznego, począwszy od analizy poprzez projektowanie aż do testowania. Mogą one być produkowane przez osoby związane z projektem (w przypadku tworzonego systemu), jak i przez sam system. Poziom szczegółowości diagramów interakcji może być różny od poglądowego do szczegółowego. Istotne jest to, iż diagramy interakcji pokazują wybrane („przykładowe”) scenariusze ale nie wszystkie. Zmiany wprowadzone w wersji 2.0 ułatwiają przede wszystkim zapis. Istnieją różne typy diagramów interakcji: diagramy sekwencji, diagramy komunikacji (zwane dawniej diagramami kolaboracji), oraz poglądowe diagramy interakcji będące w istocie skrzyżowaniem diagramów sekwencji z diagramami aktywności (diagramy sekwencji opisują to, co dzieje się w obrębie danej aktywności). Nowe (tj. wprowadzone w wersji 2.0) konstrukcje mają przede wszystkim zapewnić łatwiejszy zapis diagramów poprzez elementy pozwalające na ponowne używanie jednego diagramu (poddiagramu) na innym diagramie, łączenie diagramów, łatwiejszy zapis warunków, iteracji oraz równoległości. Wprowadzono ponadto konstrukcje przydatne szczególnie przy testowaniu systemu, pozwalające np. na pominięcie na diagramach pewnych komunikatów.

Najważniejsze konstrukcje wprowadzone w specyfikacji 2.0 w obrębie diagramów interakcji:

  1. Fragmenty kombinowane (ang. Combined fragments)
  2. Bramki (ang. Gates)
  3. Kontynuacja
  4. Użycie interakcji (ang. interaction use)
  5. Rodzaj komunikatu (wprowadzono możliwość specyfikowania komunikatów „zgubionych” „znalezionych”)

Fragment kombinowany składa się z operatora i operandów. Te ostatnie są rozdzielone linią przerywaną (InteractionOperand separator).

Ich liczba zależy od typu operatora. W specyfikacji zdefiniowano następujące operatory interakcji:

  • alt – oznacza wybór zachowania (w języku programowania odpowiednikiem jest instrukcja switch)
  • opt – oznacza zachowanie opcjonalne (w j.p. odpowiednikiem jest instrukcja if)
  • break – oznacza, że w przypadku, gdy warunek jest spełniony wykonywany jest operand, zaś reszta otaczającego fragmentu jest pomijana
  • par – oznacza, że zdarzenia z różnych operandów mogą się przeplatać
  • seq – słaby porządek. Mówiąc w dużym uproszczeniu zachowana kolejność w obrębie jednej linii życia dla różnych operandów
  • strict – silny porządek
  • neg – oznacza niepoprawne przebiegi, które nie powinny wystąpić
  • critical – region traktowany priorytetowo, nie może być zakłócany innymi komunikatami
  • ignore – pozwala określić zbiór komunikatów, które ignorujemy
  • consider – pozwala określić zbiór komunikatów, które bierzemy pod uwagę
  • assert – asercja
  • loop – iteracja

Dość silnym narzędziem są kombinacje operatorów ingnore, consider, assert, critical, które umożliwiają elegancki i zwięzły zapis niektórych scenariuszy. Bramki będące formalnie rodzajem końca komunikatu umożliwiają „wprowadzanie” komunikatów do i „wyprowadzenie” komunikatów z diagramu. Umożliwia to łączenie diagramów. Kontynuacja jest rodzajem etykiety, która upraszcza zapis i czyni go bardziej czytelnym. Konstrukcja „użycie interakcji” pozwala na wydzielenie pewnych interakcji do osobnego diagramu, który może być następnie ponownie używany.

Przykłady

Na rysunku 1 przedstawiono dwie linie życia, komunikat, fragment kombinowany z operatorem loop. Komunikat m jest synchronicznym wywołaniem.


Rys. 1. Iteracja zapisana na diagramie sekwencji przy pomocy fragmentu kombinowanego

Na diagramie na rysunku 2, w zależności od warunku komunikat sortuj zostaje przesłany do kubełka k1 lub k2. W ogólności mogą być wysyłane różne komunikaty. Komunikat m pochodzi spoza diagramu, co wyrażono przez użycie bramki.


Rys. 2. Użycie operatora alt

Na rysunku 3 przedstawiono przykład użycia operatorów ignore, consider i critical na przykładzie realizacji scenariusza kupna napoju. Najbardziej zewnętrzny fragment z operatorem ignore określa, iż brane są pod uwagę komunikaty inne niż rezygnacja i awaria. Operator consider w bardziej wewnętrznym fragmencie określa, iż brane są pod uwagę komunikaty wybor, zaplata, odebranie. Najbardziej wewnętrzny fragment z operatorem consider określa, iż komunikaty znajdujące się wewnątrz niego nie mogą być przeplatane innymi komunikatami.


Rys. 3. Użycie operatorów ingnore, consider i critical

c.d.n.

Literatura
[1]. Unified Modeling Langage: Infrastructure version 2.0. www.omg.org, Marzec 2006
[2]. Unified Modeling Langage: Superstructure version 2.0. www.omg.org, Sierpień 2005