Granice złożoności - prostota i kompleksowość w systemach informacyjnych

Niezależnie od szybkości rozwoju informatyki i całej naszej cywilizacji chcielibyśmy, aby postęp był jeszcze szybszy. Zajmujemy się coraz bardziej komplikowanymi problemami. Czy istnieją ograniczenia tego wzrostu?

Niezależnie od szybkości rozwoju informatyki i całej naszej cywilizacji chcielibyśmy, aby postęp był jeszcze szybszy. Zajmujemy się coraz bardziej komplikowanymi problemami. Czy istnieją ograniczenia tego wzrostu?

Permanentny kryzys

Mimo ponad 40-letnich doświadczeń praktycznych w dziedzinie organizacji projektów informatycznych, zjawiska kryzysowe, charakterystyczne dla tego typu przedsięwzięć, nadal dają o sobie znać (tab. 1).

W tym kontekście mówi się wręcz o permanentnym kryzysie organizacji projektów IT (Information Technology). Dla użytkownika ów fenomen objawia się w postaci niezadowalającej jakości oprogramowania. Niekiedy wydaje się, że pakiety software'owe wymykają się spod kontroli ich twórcom, którzy pod presją wymagań rynkowych, za wszelką cenę chcą wprowadzić do sprzedaży nową wersję produktu, aby wyprzedzić konkurencję.

Najgorzej jest z oprogramowaniem specjalistycznym, przeznaczonym dla profesjonalistów, a jego producent zakłada, iż:

  • użytkownikami jest wąska grupa osób, a liczba instalacji jest niewielka

  • większa wiedza informatyczna odbiorcy prowokuje niższą jakość produktu

  • ludzie z branży szybciej wybaczają kolegom błędy, które sami popełniają

  • informatycy "lubią" walczyć z komputerami, gdyż jest to ich praca

    decyzje o zakupie podejmują menedżerowie, a nie specjaliści od software'u.

    Lecz nawet aplikacje, które masowo instalowane są na tysiącach konfiguracji, nie są wolne od "chorób wieku dziecięcego". Dotyczy to zarówno "kombajnów" software'owych dużego kalibru, jak i oprogramowania "biurkowego", przeznaczonego dla zwykłych zjadaczy bitów i bajtów.

    Przejawem "grzeczności" ze strony renomowanych firm jest dostarczanie bez żenady pakietów wraz z pokaźnymi listami zlokalizowanych błędów (bugs). W przypadku problemów eksperci serwisowi doradzają jak najszybsze przejście na nową wersję programu, która jest pozbawiona starych błędów, ma natomiast wiele nowych. A może nawet mechanizm ten bardziej jeszcze zbliżony jest do bezwzględnego prawa Murphy'ego: "Stary program to stare błędy, nowy program to nowe i stare błędy". Żadna bowiem aplikacja nie działa samotnie, a tak się dziwnie składa, że współpraca różnych programów potrafi przynieść komunikat o błędzie, po którym nie pozostaje nic innego, jak użyć "małpiego chwytu", czyli kombinacji wiadomych klawiszy: CTRL+ALT+DEL.

    Z organizacyjnego punktu widzenia, problemy tworzenia bądź wdrażania systemów informatycznych rozgrywają się w "trójkącie sił": koszty, czas, jakość.

    Ponieważ czynniki te są wzajemnie zależne, wynika stąd, iż jednym z głównych zadań kierownika projektu informatycznego jest uzyskanie wyważonego między nimi stosunku. Proporcje te istotnie rzutują na powodzenie całego przedsięwzięcia. Niezależnie wszakże od poziomu wiedzy i doświadczeń najbardziej świadomego menedżera, istnieją pewne "warunki brzegowe", których nie można bezkarnie ignorować. Jednym z nich jest złożoność problemu.

    Miary prostoty

    Co to znaczy, że problem nie jest "prosty"? Jakie systemy mamy prawo nazywać "złożonymi"? Wiadomo że bardzo złożone są modele procesów gospodarczych (np. zachowania giełdowe). Kompleksowym przedsięwzięciem jest przewidywanie pogody i wszelkie prognozowanie. Nie tylko rozwój informatyki, ale i całej cywilizacji przebiega od systemów prostych w stronę bardziej złożonych. Prawa termodynamiki natomiast pokazują wyraźnie, że procesy w przyrodzie zmierzają od stanów mniej prawdopodobnych do bardziej prawdopodobnych.

    Jeśli usuniemy przegrodę oddzielającą od siebie dwa gazy, to najbardziej prawdopodobnym stanem będzie powstanie ich mieszaniny - ponowne "odmieszanie" się gazów jest wielce wątpliwe. Podobnie nieprawdopodobnym zjawiskiem byłoby powstanie różnic energetycznych w obiekcie, który początkowo charakteryzuje się równomiernym rozkładem temperaturowym - mikrocząsteczki ciała wszędzie zachowają podobny poziom energetyczny i podobną temperaturę. Zatem niemal wszystko, co robimy, można by, zgodnie z prawami przyrody, zinterpretować jako dążenie do tworzenia najbardziej nieprawdopodobnego ze światów. Fizyka wskazuje w tym kontekście na wzrost entropii, skojarzony ze wzrostem nieuporządkowania badanych elementów. W entropię wiąże się z ilością informacji zawartej w komunikacie emitowanym przez źródło.

    Przyjęcie entropii, jako miary prostoty systemu, pozwala na stwierdzenie, że jest on tym bardziej złożony, im mniej prawdopodobny! Koresponduje to z potocznym rozumieniem złożoności - coś, co jest oczywiste, uważamy za proste. Zauważmy, że w życiu codziennym, w obliczu złożonych systemów, wszędzie tam, gdzie nie znamy przyczyn zjawiska, chętnie posługujemy się słowem "przypadek". Czy szereg liczb: 4, 1, 5, 9, 2, 6, 5, 3 jest przypadkowy? Dla kogoś, kto nie zna zasady jego konstrukcji, na to wygląda. Tymczasem ów ciąg jest jak najbardziej deterministyczny - to kolejne rozwinięcia liczby (dla utrudnienia zagadki od drugiego miejsca po przecinku. Ucieczka w przypadkowość nie tłumaczy zatem świata do końca, a raczej ma na celu pokrycie naszej niewiedzy.

    Nie ma przecież nic przypadkowego w zderzeniu dwóch samochodów na skrzyżowaniu. Nieszczęście jest, pardon, wypadkową prędkości obu pojazdów i oczywiście wielu innych czynników, jak najbardziej mierzalnych i przewidywalnych tyle, że składających się na bardzo złożony system ruchu drogowego. Dodatkowe trudności wiążą się z występowaniem tzw. czynnika ludzkiego, ale i każda myśl w naszym umyśle ma również przyczynowe źródło, choćby było ono dla nas niepoznawalne i pozornie przypadkowe. Zauważmy, że takie założenie w niczym nie umniejsza wolności człowieka. Postawmy wreszcie kropkę nad "i" w kontekście tomów, jakie na ten temat napisali fizycy: mikrofizyczna zasada nieoznaczoności nie jest "granicą złożoności" w ogólnym przypadku, jest bowiem indeterministyczna tylko dla pewnych wymiarów opisywanego układu, a ponadto mówimy tu jedynie o opisie w kategoriach mechaniki kwantowej.

    Zegary i pamięci

    Czy zatem układ zachowujący się w ustalony i przewidywalny sposób jest prosty? Niekoniecznie. Zegarki, które nosimy na rękach, tak właśnie działają. Do znudzenia przesuwają się w nich, tym samym rytmem, wskazówki - bez niespodzianek przeskakują cyferki. A jednak pod cyferblatem mogą ukrywać się wielce przemyślne struktury. Z kolei dość prosta mechanicznie struktura, jaką jest tzw. podwójne wahadło, prowadzi do nieprzewidywalnych jego zachowań. A co z programem, któ- ry w najmniej oczekiwanym momencie radośnie informuje nas o "błędzie ochrony pamięci B3670:E0024 w wyniku przepełnienia bufora odwoławczego wskutek niedopuszczalnego zagnieżdżenia procesów zarezerwowanych"?

    Czy jest to efekt złożoności software'u, czy też raczej efekt bylejakości jego implementacji? Niestety, ale co najmniej na pierwsze z tych pytań trzeba odpowiedzieć twierdząco. Oczywiście, istnieją komunikaty, które wskazują jednoznacznie na niepoważne traktowanie klienta, np. "Klawiatura nie jest podłączona do jednostki centralnej, naciśnij F1 w celu otrzymania dalszych wskazówek" (ha! ha!). Z reguły jednak firma software'owa nie jest zainteresowana wprowadzaniem na rynek wadliwie działającego produktu. Nie szukając jednak łatwych usprawiedliwień dla denerwujących użytkownika błędów, warto pamiętać właśnie o złożoności współczesnego oprogramowania.

    Dla złożonych pakietów programowych (systemy operacyjne) można przyjąć, że bezpośrednio po ich napisaniu liczba błędów stanowi ok. 20% linii kodu źródłowego. Dla kilkunastu milionów linii kodowych Windows 95 daje to ok. 2-3 mln błędów przed pierwszym testem. Windows NT (5.0) to już 25 mln linii i 5 mln "pierwszych" błędów. Kolejna reguła praktyczna powiada, iż każdy test może usunąć 1/3 błędów. W prostym ciągu arytmetycznym za trzecim razem byłoby zero, ale tu trzeba potęgować 2/3 (tab. 2), asymptotycznie zbliżając się do upragnionego minimum.

    Cena złożoności

    Liczby w tabeli nie nastrajają do śmiechu. Po dziesięciu przebiegach testowych można oczekiwać, że z początkowych błędów zostaną nawet nie tysiące, ale wręcz dziesiątki tysięcy! Dodajmy, iż test tak złożonego oprogramowania to coś więcej niż kliknięcie myszką na opcji "kompiluj" i spokojne dostawianie brakujących kropek i średników. Zresztą mówiąc zupełnie poważnie: błędy kompilacji to drobnostka w porównaniu z błędami wykonania. Czego można zatem oczekiwać od pakietu składającego się z milionów linii kodowych? "Zera tolerancji"?

    Praktyka pokazuje, że takie żądania są mało realistyczne. W przypadku NT 5.0 nieoficjalnie mówi się o zejściu poniżej 4000 błędów. To oznaczałoby niemal 20 testów, a każdy z nich wiąże się z kosztami rzędu 20 mln USD. Dość powiedzieć, że Microsoft zaplanował zaangażowanie niemal ćwierć miliona użytkowników wersji beta do testowania. To właśnie rozbudowane testy powodują, że mimo coraz doskonalszych narzędzi software"owych, nie powinniśmy spodziewać się, iż programista napisze dziennie więcej niż kilkadziesiąt LOC (Line of Code). W latach 70. i 80. statystyki wskazywały na poziom 10 LOC/dzień.

    Nie trzeba dodawać, że i taki współczynnik mógł być bardzo dobrym osiągnięciem dla zwartych programów algorytmicznych z obszaru matematycznotechnicznego. Z kolei oprogramowanie przetwarzania danych gospodarczych można było produkować nawet w tempie 100 LOC/dzień. Dzisiaj mówi się o 40-60 LOC/dzień. Statystycznie rzecz biorąc, na rynku światowym, za "wklepanie" instrukcji podstawienia typu j := 0; trzeba zapłacić 5 USD (w tym przypadku dolara za znak). Gdyby programy można było pisać "ciurkiem", to programista mógłby po kilku minutach pracy na dzień iść do domu, zadowolony ze swoich zarobków.

    Tak się jednak nie dzieje i nie powinniśmy zapominać, że w uśrednionym koszcie LOC zawierają się wszystkie fazy projektu software'owego, którego rozkład czasowy często wygląda następująco:

    projektowanie - 25% czasu

    programowanie - 20% czasu

    testowanie - 40% czasu

    pozostałe czynności (dokumentacja, szkolenia) - 15%.

    Kosmiczne błędy

    W oprogramowaniu, które powinno być szczególnie niezawodne (np. medycyna, wojsko, badania kosmiczne, lotnictwo), proporcje te mogą wyglądać inaczej - wydłuża się faza testowania. Ale nawet wówczas nie ma 100-proc. gwarancji bezpieczeństwa. Można to było zobaczyć gołym okiem w postaci wielkiego wybuchu, jaki miał miejsce w Gujanie Francuskiej 4 czerwca 1996 r. Koszt nie zamierzonego fajerwerku: miliard dolarów. To eksplodowała rakieta nośna Ariane 5 wraz z kilkoma satelitami na pokładzie. Początkowy kamyczek prowadzący do katastrofalnej lawiny zdarzeń - komputer nawigacyjny nieprawidłowo dokonał konwersji 64-bitowej liczby zmiennoprzecinkowej na 16-bitową stałoprzecinkową.

    Ściślej rzecz ujmując, początkowa wartość liczbowa była zbyt duża i logika programu skierowała jego przebieg do modułu obsługi sytuacji wyjątkowych (exception section). Tu natomiast nie bardzo było wiadomo, co zrobić z tymi danymi - część systemów pomiaru pozycji wyłączyła się, inne pracowały na podstawie fałszywych informacji. Komputer pokładowy zmienił pozycję dysz w przekonaniu, że rakieta nagle zmieniła kurs. W rzeczywistości kurs był przez cały czas prawidłowy i doszło do niepotrzebnej zmiany kąta lotu rakiety o ponad 20Ą, a w efekcie do narastających obciążeń aerodynamicznych. Wszystko to dzieje się w ciągu sekund. Wreszcie, jedna z bardziej złożonych konstrukcji, jakie stworzyła nasza cywilizacja, zostaje zdetonowana automatyzmem mechanizmu samozniszczenia.

    Jak mogło do tego dojść? Szczegóły zawarto w specjalnym raporcie ESA (Flight 501 Failure, Paris, 19.06.96). Odpowiedź tkwi właśnie w złożoności tego typu systemów. Żaden z nich nie powstaje na nowo od podstaw. Złożone systemy konstruuje się warstwami, etapami i modułami. Zakłada się przy tym, że poprzedni poziom jest czymś sprawdzonym - budowniczy domu koncentruje się na jego konstrukcji jako całości. Nie prowadzi obliczeń czy testów dla pojedynczych cegieł, przyjmując, że mają one określone właściwości, wytrzymałość itp. Podobnie było w przypadku Ariane 5. Jej twórcy nie wyrzucali do kosza dorobku zgromadzonego przy budowie poprzednich modeli. Wręcz przeciwnie, postanowiono wykorzystać sprawdzone oprogramowanie Ariane 4.

    I słusznie, tyle że Ariane 5 miała inne właściwości mechaniczne niż jej poprzedniczka. To spowodowało, że procesor "zgłupiał", gdy zaczął przetwarzać zmienne o wartościach nie przewidzianych dla wcześniejszego typu rakiety. Czy od kierownictwa projektu i zatrudnionych w nim informatyków można było wymagać ponownej analizy i szczegółowych testów feralnego, jak się okazało, oprogramowania? Pytanie jest, oczywiście, retoryczne, lecz wydaje się, że słabym punktem organizacyjnym przedsięwzięcia była specyfikacja wymagań dla systemu software'owego. Nie "zagrała" współpraca na linii inżynierowie - mechanicy - programiści. Ci ostatni wykonali swoją pracę, tyle że na podstawie źle sprecyzowanych warunków.

    Nieco inaczej wyglądała sytuacja podczas wypadku samolotu Lufthansy w Warszawie przed kilkoma laty. Airbus A320 uderzył w wał ziemny na końcu lotniska. To prawda, że tego wału mogło nie być, lecz każde lotnisko i tak gdzieś się musi kończyć. Zasadnicze pytanie brzmiało zatem: dlaczego maszyna nie wylądowała prawidłowo i nie zahamowała na przewidzianym dla niej pasie? Czy błąd popełnił pilot, czy też zawiodły pokładowe komputery? Bezpośredniej przyczyny zderzenia można dopatrywać się w logice oprogramowania, które dopuszcza możliwość hamowania tylko przy stwierdzeniu określonych parametrów ruchu samolotu. Krótko mówiąc: w momencie, gdy samolot był już fizycznie na ziemi, komputer tego "nie wiedział", załoga bowiem dokonała "ręcznej" korekty prędkości z uwagi na dane pogodowe.

    I znowu kłania nam się złożoność opisywanej sytuacji, wynikająca ze współdziałania całej palety urządzeń technicznych, programów, ludzi. Formalnie nie stwierdzono, że wina leżała po stronie oprogramowania, ale na wszelki wypadek dokonano w nim pewnych modyfikacji. Bardziej jednoznacznie (na niekorzyść komputerów) wyglądała katastrofa B757 w Kolumbii, w 1995 r., chociaż i tu załoga najpierw dokonała zmiany parametrów lotu, a potem zostawiła sterowanie komputerowi. Podobne przykłady można mnożyć, wystarczy sięgnąć do opracowań ACM (Association for Computing Machinery) "The Risk Digest in Computers and Related Systems". Tytuły mówią same za siebie: "Bankomaty Toronto bez pieniędzy", "Eksplozja gazu w Malezji", "Zablokowane telefony w stanie Utah" czy "Ciemności w Dublinie".

    Już tylko to pobieżne zestawienie potwierdza znaną prawdę o znaczeniu i wszechobecności komputerów. są niemal wszędzie, więc po każdym wypadku jakże łatwo sadza się na ławie oskarżonych komputer.

    Kucharek sześć

    Jak zatem radzić sobie ze złożonością? Zapewne coraz bardziej złożonymi narzędziami i zespołami ludzkimi. Ale i tu są pewne granice. Do kopania dołu metr na metr nie da się zatrudnić naraz więcej niż jednego człowieka. Również w zarządzaniu bardziej kompleksowymi przedsięwzięciami istnieje optymalna wielkość zespołu projektowego. Czy można taką wielkość teoretycznie wyliczyć? Ba. Pozostaje oprzeć się na doświadczeniach praktycznych, pamiętając, że te są trudno porównywalne w różnych sytuacjach. Chociaż każdy praktyk potwierdzi, iż projekt, z którym mamy problemy, będzie miał ich jeszcze więcej po powiększeniu zespołu projektowego.

    No dobrze, ale właściwie dlaczego rzeczywistość jest tak bezlitosna? Odpowiedź tkwi w liczbie kanałów komunikacyjnych między uczestnikami procesu projektowania, połączonej z możliwością komunikowania się na zasadzie "każdy z każdym". Oznacza to przykładowo, że powiększając grupę 4-osobową (6 kanałów informacyjnych) o jedną osobę, zwiększamy jednocześnie liczbę kanałów informacyjnych do 10. Liczba osób w zespole zwiększa się o 25%, lecz liczba kanałów informacyjnych aż o 67%! Ten nieproporcjonalny przyrost może przynieść w efekcie relatywny spadek produktywności całego zespołu.

    Liczbę kanałów informacyjnych w zespole projektowym łatwo wyliczymy za pomocą formuły: n(n-1)/2. Kwadratowa postać tej funkcji nie pozostawia złudzeń co do szybko rosnących wartości dla kolejnych n. Zwróćmy uwagę, że jedyną strukturą, gdzie liczba kanałów informacyjnych jest mniejsza niż osób w zespole, jest para. Niezwykle stabilna to i efektywna konfiguracja. Stwierdzenie wydaje się banalne, wszak potwierdza je zwykła obserwacja sprawdzonych wzorów organizacyjnych czy przyrodniczych. Zapominamy jednak często o tych prawidłowościach, inicjując rozbudowane procesy zarządzania, angażujące niepotrzebnie nadmierną liczbę uczestników. Powiedzmy wprost: do pomyślenia byłaby organizacja o strukturze dwójkowej, bazująca na dwuosobowych zespołach.

    Wystarczy rozejrzeć się wokół, by skonstatować, jak wiele gremiów, komisji, grup roboczych itp. ciał składa się z zawyżonej liczby osób. Taki stan rzeczy wynika z reguły z przyczyn pozamerytorycznych, np. prestiżowych, politycznych czy automatyzmu tradycji. Owszem, podczas spotkania w zespole dwuosobowym zdarzają się sytuacje, gdy trzeba zasięgnąć opinii osoby trzeciej, ale nie musi to oznaczać jej pełnej i fizycznej asysty podczas rozwiązywania problemu. Często wystarczyłaby krótka konsultacja telefoniczna. Lecz zamiast ustalać taką potencjalną "gotowość telefoniczną" w określonym czasie, łatwiej jest zwołać "na wszelki wypadek" wszystkich, którzy "mogą się przydać" albo tych, których "głupio byłoby nie zaprosić". W efekcie, zamiast konstruktywnej wymiany informacji w cztery oczy, dochodzi do ogólnorozwojowych dysput i zbędnych samoprezentacji ich uczestników.

    Prawo Amdahla

    "Wąskie gardła" obserwowane podczas zarządzania zespołami ludzkimi uwidoczniają się także w systemach wieloprocesorowych. I tu istnieją granice złożoności, poza którymi opłacalność zwiększania liczby procesorów w konfiguracji szybko zaczyna maleć. Zjawisko to opisuje model Gene'a Amdahla z IBM. Przyjmijmy, że jeden procesor może wykonać określone zadanie w ustalonym, jednostkowym czasie. Gdyby problem dało się w 100% "zrównoleglić", to czas potrzebny n procesorom byłby n razy mniejszy i wynosił 1/n. Niestety, w praktyce nie wszystkie fragmenty programu można sparalelizować. Przyjmijmy, że 30% kodu trzeba wykonać sekwencyjnie, i opiszmy ten czas współczynnikiem a = 0.3.

    Zysk na czasie uzyskamy jedynie dla pozostałej części algorytmu: 1-a, proporcjonalnie do liczby użytych procesorów. Tak więc czas obliczeń T wyrazi się wzorem:

    T = (1 - a ) / n + a

    Podstawmy zatem do wzoru przyjętą wartość 0,3 dla przypadku, gdy decydujemy się na dodatkowy procesor (n = 2). Otrzymamy wówczas T = 0,65. Uzyskaliśmy spodziewane skrócenie czasu, ale zbadajmy teraz, jak wygląda przyrost wydajności, którą można opisać odwrotnością czasu: 1/T = 1,538. Wniosek: drugi procesor przyniósł poprawę wydajności jedynie o 54%! Moglibyśmy dalej "bawić się" podaną zależnością dla różnych wartości czynnika "a" i liczby użytych procesorów, kreśląc odpowiednie krzywe i zbierając wyniki obliczeń do tabel.

    I bez tego łatwo zauważyć hamujący wpływ współczynnika nierównoległości na przyrosty wydajności: im jest mniejszy, tym owe przyrosty są większe. Dla 0,2 użycie drugiego procesora to wzrost wydajności o 67%. Zwróćmy z kolei uwagę, że nawet nieskończona liczba procesorów nie podniesie nam relatywnej wydajności powyżej granicy, jaką jest 1/a. Dla 0.3 będziemy się zatem asymptotycznie zbliżać do poziomu 3,33 w stosunku do wydajności podstawowej. W takiej sytuacji użycie jednego, cztery razy szybszego procesora, przyniesie lepszy efekt niż 1000 wolniejszych procesorów równoległych. Jasne jest też, że największe przyrosty wydajności uzyskuje się przy pierwszych, dodatkowych procesorach.

    Kolejnym przybliżeniem opisywanego prawa jest rozszerzenie modelu o współczynnik charakteryzujący wzrost potrzeb wzajemnego komunikowania się procesorów wraz z ich liczbą. Wiadomo bowiem, że im więcej procesorów, tym więcej ich "pary idzie w gwizdek" z uwagi na potrzebę koordynacji działań, co koresponduje z wcześniej wzmiankowanym efektem "6 kucharek". Widzimy więc, że na razie nasze informatyczne granice wzrostu są wyznaczane na płaszczyźnie makroskopowej i daleko nam do tzw. granicy Bremermanna, wynikającej z fizyki kwantowej: "Żaden system materialny, sztuczny bądź żywy, nie może wykonywać więcej obliczeń niż 2 x 1047 bitów na sekundę i gram jego masy".