Wprowadzenie
Oprogramowanie na obecnym rynku, w ogromnej ilości, nadal stanowią produkty powstałe ponad dwie dekady temu (legacy systems). Znakomita większość powstawała ewolucyjnie. Lata 90-te to bardzo często monolity budowane w oparciu o EJB, JavaEE i nieco później Microsoft .NET. Są to wzorce powstała na bazie relacyjnego modelu danych i skryptów transakcyjnych.
“W anemicznym projekcie domeny logika biznesowa jest zwykle implementowana w oddzielnych klasach, które przekształcają stan obiektów domeny. Fowler nazywa takie zewnętrzne klasy skryptami transakcyjnymi. Ten wzorzec jest powszechnym podejściem w aplikacjach Java, wspieranym przez technologie takie jak wczesne wersje Entity Beans EJB, a także w aplikacjach .NET zgodnych z architekturą Three-Layered Services Application, gdzie takie obiekty należą do kategorii “Business Entities” (chociaż Business Entities mogą również zawierać zachowanie).” https://en.wikipedia.org/wiki/Anemic_domain_model
“Jest to jeden z tych anty-wzorców, który istnieje już od dłuższego czasu, ale wydaje się, że obecnie przeżywa szczególny rozkwit. Rozmawiałem na ten temat z Ericiem Evansem i obaj zauważyliśmy, że wydają się one być coraz bardziej popularne. Jako świetni zwolennicy właściwego modelu domeny, nie jest to dobra rzecz.” (M.Fowler) https://martinfowler.com/bliki/AnemicDomainModel.html
https://martinfowler.com/eaaCatalog/transactionScript.html
Z powyższych powodów nadal powstają diagramy jak ten poniżej:

W artykule Ten straszny diagram klas opisałem przyczyny, dla których jest to bardzo zły (anemiczny) model: powyższy diagram tak na prawdę nie jest dokumentacją działającego oprogramowania (o ile w ogóle ten diagram powstanie i ktoś go aktualizuje).
Od dłuższego czasu kolejne firmy zgłaszają podobny problem: mamy nieudokumentowaną aplikację a jej autorzy odchodzą na emerytury, czy można coś z tym zrobić?
Czym jest architektura aplikacji?
W artykule Architektura kodu aplikacji, opisywałem komponentowe i obiektowe wzorce projektowe. Komponentowo zorientowane oprogramowane ma standardowo cztery poziomy budowania architektury:
- aplikacja zbudowana jest z komponentów,
- komponenty zbudowane są z klas,
- klasy zbudowane są z operacji,
- operacja to nazwa procedury, zbudowanej z poleceń wyrażonych w określonym języku (metoda).
Graficznie można to zilustrować tak:

Na powyższym diagramie pokazano wymienione poziomy architektury. Oprogramowanie to kod, lista instrukcji dla procesora. Z perspektywy kodu (i kodera) są to setki i tysiące linii kodu, który stanowi sobą ciąg instrukcji dla procesora. Razem (procesor i kod w pamięci) stanowią komputer, który realizuje określony mechanizm .
Jest to odpowiednik tak zwanego V-Modelu:

Problem w tym, że tego mechanizmu, jako całości, nie widać z perspektywy kodu. Dlatego zaprojektowanie oprogramowania realizującego złożone mechanizmy wymaga pracy od ogółu do szczegółu. Uzyskany efekt to setki procedur wyrażone w postaci kodu, ale przemyślane i przetestowane jeszcze przed pisaniem kodu, które to kodowanie jest najkosztowniejszą częścią w inżynierii oprogramowania .
Problem w tym, że sam kod źródłowy to tekstowy opis. Proszę sobie wyobrazić tekstowy opis konstrukcji i zasady działania okrętu, samochodu czy nawet tylko zwykłej pralki. Na pewno można opisać prozą istniejącą obserwowaną działającą konstrukcję, ale raczej nie uda się jej bezbłędne zaprojektowanie w ten sposób za pierwszym razem. Zawsze bezie to kosztowne pasmo prób i błędów.
Dlatego: grupujemy procedury w klasy (operacja klas), klasy grupujemy w komponenty, całość testujemy na modelach (diagramy sekwencji). To wszystko jako całość, po wykonaniu implementacji, stanowi sobą działającą aplikację.
Mamy aplikację ale nie mamy jej dokumentacji
Kodowanie z pominięciem projektowania i jego skutki opisywałem nie raz. A jeżeli aplikacja istnieje? Skoro istnieje i działa (bez wnikania ile kosztowało jej powstanie) to znaczy, że można ją opisać. Działający komputer to “czarna skrzynka”, w której wykonywane są procedury (oprogramowanie). Bez dokumentacji nie jesteśmy w stanie “odkryć” jej architektury, ale analizując wejścia i wyjścia (formularze, zrzuty ekranowe, wydruki), można podjąć próbę odtworzenia procedur .
Jak rozwiązać problem długu informacyjnego (patrz: Dług informacyjny)? Należy opisać mechanizm działania aplikacji abstrahując całkowicie od jej architektury, której niestety nie znamy a nakłady na jej odtworzenie na bazie analizy kodu (code review) były by wyższe niż napisanie całości od zera. Wiele aplikacji jest niestety słabo napisana, ich kod jest trudny do zrozumienia (What is Bad Code?), więc taka analiza kodu najczęściej nie ma uzasadnienia ekonomicznego. Nie ma też żadnego sensu tworzenie diagramów klas takich jak ten na początku tego artykułu.
Bardzo często słyszę, że analityk nie ma wpływu na architekturę modelu dziedziny, bo robi to deweloper, i niestety często jest to prawdą. Analogiczny problem:
Jak zaprojektować (opisać) system nie mając wpływu na architekturę?
Jak w UML dokumentować model systemu, którego budowy nie znamy?
Patrząc na wcześniej przytoczony diagram opisujący poziomy abstrakcji architektury, pomijamy dwie środkowe warstwy: HLD i LLD. Wtedy zostają nam do udokumentowania: przypadki użycia i ich scenariusze oraz procedury.
W efekcie tworzymy:
- diagram Przypadków Użycia (PU) reprezentujący komu i do czego ta aplikacja służy (co ma powstać, czego aktor oczekuje jako efekt),
- dla każdego PU:
- przyporządkowujemy formularz/dokument (szablon ekranu, graficzna forma, zalecana forma to diagram struktur złożonych UML),
- dokumentujemy dialog (scenariusz) aktor-system w postaci listy wypunktowanej (to także test odbiorczy),
- każdy scenariusz odwzorowujemy na diagramie aktywności jako łańcuch procedur:
- każdą procedurę dokumentujemy na osobnym diagramie UML,
- jest możliwe, że procedury te będą wielokrotnie wykorzystywane w różnych scenariuszach różnych przypadków użycia.
W repozytorium narzędzia CASE powstaje hierarchia diagramów:
– Diagram Przypadków Użycia
— scenariusz PU 1 (łańcuch procedur)
— procedura 1 (kroki procedury, algorytmy)
— procedura 2 (kroki procedury, algorytmy)
— procedura 3 (kroki procedury, algorytmy)
— scenariusz PU 2(łańcuch procedur)
— procedura 3(kroki procedury, algorytmy)
— procedura 4 (kroki procedury, algorytmy)
— procedura 5 (kroki procedury, algorytmy)
— scenariusz PU 3 (łańcuch procedur)
— procedura 6 (kroki procedury, algorytmy)
— procedura 7 (kroki procedury, algorytmy)
— procedura 8 (kroki procedury, algorytmy)
Możliwe jest reużywanie procedur (dlatego maja unikalne nazwy). Mona je grupowa osobno (stos procedur) i wywoływać niezależnie ze scenariuszy PU. Taki model nie zawiera diagramów klas (wyjątkiem jest diagram struktur złożonych jako model formularza/komunikatu), komponentów, sekwencji.
Przykład
Co do zasady Diagram Przypadków Użycia to zakres projektu.
UseCases są sposobem na uchwycenie wymagań systemów, tj. tego, co systemy mają robić. Kluczowymi pojęciami
w tym rozdziale są aktorzy, UseCases i przedmiot modelowania (system). Kontekstem każdego UseCase jest rozważany system, do którego, do którego odnosi się UseCase. Użytkownicy i wszelkie inne systemy, które mogą wchodzić w interakcje z systemem, są reprezentowane jako aktor.
Poniżej prosty model: zakres projektu “System Fakturowanie Zamówień”.

Przypadek użycia Faktury ma tu następujący scenariusz:

Na tym poziomie tworzymy Diagramy Aktywności:
Aktywności mogą opisywać obliczenia proceduralne, tworząc hierarchie działań wywołujących inne działania lub, w modelu obiektowym, mogą być wywoływane pośrednio jako metody związane z operacjami, które są bezpośrednio wywoływane. […]
Aktywności mogą być również wykorzystywane do modelowania systemów informatycznych w celu określenia procesów na poziomie systemu.
Diagram Aktywności reprezentujący powyższy scenariusz:

Na diagramie pozostawiono numery linii ze scenariusza opisanego tekstem, dla ułatwienia porównania ich ze sobą. Powyższe to pierwszy poziom modelowania mechanizmu działania systemu. Czynność “2. SYSTEM wyświetla Formularz Lista Zamówień został oznaczony symbolem “grabie” (prawy dolny róg). Podobnie jak 4. SYSTEM wyświetla wstępnie wypełniony Formularz Faktura. Oznacza to, że istnieje detaliczny opis tego zadania. Wygląda on tak:

oraz

Dokładny opis tworzenia powyższych konstrukcji zawiera rozdz. 16 spec. UML:

Pozostaje udokumentowanie elementów “Object Node” (prostokąty reprezentujące formularze i komunikaty). Najwygodniej w narzędziu CASE użyć do tego Diagramu Struktur Złożonych notacji UML, gdyż pozwala to na pełną kontrolę spójności modeli:


Oba powyższe formularze (komunikaty) mają wczesną formę, są ogólne i zawierają obszary danych. W toku projektu są one doprowadzane do postaci detalicznie opisującej treść tych dokumentów i ich walidacje.
Przykład tworzenia nowego zamówienia:

Podsumowanie
Jako ludzie używamy komputerów (ogólnie różnych urządzeń, komputer jest jednym z nich) a nie oprogramowania. Komputer to procesor, pamięć i program . Oprogramowanie to skrypty konfigurujące zachowanie się tego urządzenia jakim jest komputer. Każda aplikacja jest reprezentowana w postaci kodu (maszynowego), który powstaje z pomocą (za pośrednictwem) języków programowania wysokiego poziomu (Pyton, Java, C, PHP, itp.). Ten kod to procedury.
Proste rozwiązania to kilkanaście do kilkudziesięciu procedur, które mieszczą sie na kilkunastu stronach wydruku. Jednak większe systemy to setki powiązanych ze sobą procedur. Zapamiętanie i zrozumienie ich wzajemnych powiązań jest dla człowieka praktycznie niemożliwe, dlatego już w latach 80-tych powstały koncepcje podziału dużych systemów na moduły, a późnej w latach 90-tych, na komponenty . Moduł to tematycznie powiązane procedury, które nadal stanowią sobą jedną całość (monolit).
Komponent to wydzielona, odseparowana od reszty ich grupa procedur, stanowiąca sobą odrębną zamkniętą całość . Tak budowane są systemy zorientowane obiektowo (komponent to także obiekt). Tu przypomnę, że tak zwany paradygmat obiektowy to “hermetyczne komponenty i komunikacja między nimi” a nie “dziedziczenie i łączenie funkcji i danych w obiekty” (polecam referat: Object Oriented Programming vs Functional Programming).
Odpowiedź na tytułowe pytanie: Jak udokumentować monolit, brzmi: przeanalizować jego działanie na podstawie danych (dokumentów) wprowadzanych oraz danych (dokumentów) jakie on tworzy, i udokumentować wyniki tej analizy tak jak pokazano powyżej. Na pewno się okaże, że jest to duży i skomplikowany system, więc już na etapie analizy, od początku jej prowadzenia, grupujemy dokumenty i procedury na tematyczne grupy (moduły). Jeżeli zapadnie decyzja o wykonaniu tego systemu jeszcze raz, projektujemy jego architekturę HLD, i prowadzony projekt metodami obiektowymi (nie mylić z dziedziczeniem i łączeniem danych i funkcji w obiekty), bo to podejście pozwala “opanować” złożoność takiego systemu a projekt architektury grupujący wszystkie procedury w komponentu i klasy, stanowi doskonała dokumentację całości .
Polecam artykuł Jak wyjść z długu technologicznego jakim jest centralny monolityczny system, jako kontynuacje tego tekstu.