InŜynieria oprogramowania Język UML 1
Jak podaje Słownik języka polskiego PWN, paradygmat to «przyjęty sposób widzenia rzeczywistości w danej dziedzinie, doktrynie itp.» lub «zespół form fleksyjnych (deklinacyjnych lub koniugacyjnych), właściwy danemu typowi wyrazów; wzorzec, model deklinacyjny lub koniugacyjny». Jak to się ma do programowania? Trudno orzec; sięgnijmy jeszcze do greckich korzeni słowa. Greckie παράδειγµα oznacza wzorzec bądź przykład. CzyŜby chodziło więc o typowy, wzorcowy sposób pisania programów? Niezupełnie; chodzi raczej o zbiór mechanizmów, jakich programista uŝywa, pisząc program, i o to, jak ów program jest następnie wykonywany przez komputer. Zatem paradygmat programowania to ogół oczekiwań programisty wobec języka programowania i komputera, na którym będzie działał program. 2
Co to jest paradygmat programowania? Donald Ervin Knuth (1938-) John von Neumann (1903-1957 Niklaus E. Wirth (1934-) Przykład pierwszy: programowanie imperatywne Programowanie imperatywne to najbardziej pierwotny sposób programowania, w którym program postrzegany jest jako ciąg poleceń dla komputera Obliczenia rozumiemy tu jako sekwencję poleceń zmieniających krok po kroku stan maszyny, aŝ do uzyskania oczekiwanego wyniku. Stan maszyny naleŝy rozumieć jako zawartość całej pamięci oraz rejestrów i znaczników procesora. Paradygmat związany jest ściśle z budową sprzętu komputerowego o architekturze von Neumanna, w którym poszczególne instrukcje (w kodzie maszynowym) to właśnie polecenia zmieniające globalny stan. 3
Przykład pierwszy: programowanie imperatywne Języki wysokiego poziomu takie jak Fortran, Algol, Pascal, Ada lub C posługują się pewnymi abstrakcjami, ale wciąŝ odpowiadają paradygmatowi programowania imperatywnego. Przykładowo, instrukcje podstawienia działają na danych pobranych z pamięci i umieszczają wynik w tejŝe pamięci, zaś abstrakcją komórek pamięci są zmienne. Przykładowy program w języku imperatywnym program pierwszy; var i, n, s: integer; begin read(n); s := 1; for i := 2 to n do s := s * i; write(s) end. 4
Przykład drugi: programowanie obiektowe W programowaniu obiektowym program to zbiór porozumiewających się ze sobą obiektów, czyli jednostek zawierających pewne dane i umiejących wykonywać na nich pewne operacje Przykład drugi: programowanie obiektowe WaŜną cechą jest tu powiązanie danych (czyli stanu) z operacjami na nich (czyli poleceniami) w całość, stanowiącą odrębną jednostkę obiekt. Cechą niemniej waŝną jest mechanizm dziedziczenia, czyli moŝliwość definiowania nowych, bardziej złoŝonych obiektów, na bazie obiektów juŝ istniejących. 5
Przykład drugi: programowanie obiektowe Zwolennicy programowania obiektowego uwaŝają, Ŝe ten paradygmat dobrze odzwierciedla sposób, w jaki ludzie myślą o świecie Nawet jeśli pogląd ten uznamy za przejaw pewnej egzaltacji, to niewątpliwie programowanie obiektowe zdobyło ogromną popularność i wypada je uznać za paradygmat obecnie dominujący. Przykład trzeci: programowanie funkcyjne Tutaj program to po prostu złoŝona funkcja (w sensie matematycznym), która otrzymawszy dane wejściowe wylicza pewien wynik 6
Przykład trzeci: programowanie funkcyjne Zasadniczą róŝnicą w stosunku do poprzednich paradygmatów jest brak stanu maszyny: nie ma zmiennych, a co za tym idzie nie ma Ŝadnych efektów ubocznych. Nie ma teŝ imperatywnych z natury, tradycyjnie rozumianych pętli (te wymagają np. zmiennych do sterowania ich przebiegiem). Przykład trzeci: programowanie funkcyjne Konstruowanie programów to składanie funkcji, zazwyczaj z istotnym wykorzystaniem rekurencji. Charakterystyczne jest równieŝ definiowanie funkcji wyŝszego rzędu, czyli takich, dla których argumentami i których wynikami mogą być funkcje (a nie tylko proste dane jak liczby lub napisy). 7
Przykład czwarty: programowanie w logice (programowanie logiczne) Na program składa się zbiór zaleŝności (przesłanki) i pewne stwierdzenie (cel) Wykonanie programu to próba udowodnienia celu w oparciu o podane przesłanki. Obliczenia wykonywane są niejako przy okazji dowodzenia celu. Podobnie jak w programowaniu funkcyjnym, nie wydajemy rozkazów, a jedynie opisujemy, co wiemy i co chcemy uzyskać. Konkretny język programowania ucieleśnia jeden lub więcej paradygmatów Fortran, Pascal i C to języki pozwalające stosować paradygmat programowania imperatywnego. Mówi się wręcz, Ŝe są to języki imperatywne. Java bądź C# to z kolei języki obiektowe, w których typowe programowanie imperatywne zostało mocno ograniczone. Natomiast C++ jest językiem zarówno obiektowym, jak i imperatywnym. Do pewnego stopnia moŝna zresztą uznać, Ŝe programowanie imperatywne to szczególny, wynaturzony przypadek programowania obiektowego, gdzie wszystko rozgrywa się wewnątrz jednego superobiektu. 8
Uwagi historyczne Programowanie imperatywne jest tak stare jak komputery, a nawet starsze. Przepisy kuchenne czy sformalizowane procedury urzędowe moŝna uznać za przejaw programowania imperatywnego. Oczywiście uŝywane dzisiaj języki imperatywne są znacznie bogatsze niŝ te z czasów pionierskich. Niesłychanie waŝnymi elementami, o które wzbogacił się ten paradygmat, są programowanie strukturalne (uŝywanie prostych, dobrze zdefiniowanych struktur jak np. pętla while, a unikanie skoków) i proceduralne. Uwagi historyczne Programowanie funkcyjne sięga korzeniami okresu międzywojennego, kiedy to stworzony został rachunek lambda. Pierwsze funkcyjne języki programowania powstały w połowie lat pięćdziesiątych XX wieku. Przełom lat pięćdziesiątych i sześćdziesiątych to pierwsze propozycje programowania w logice, natomiast narodziny paradygmatu obiektowego przypadają na drugą połowę lat sześćdziesiątych. Za datę graniczną moŝna przyjąć powstanie języka Simula 67. 9
Rachunek lambda Rachunek lambda to system formalny uŝywany do badania zagadnień związanych z podstawami matematyki jak rekurencja, definiowalność funkcji, obliczalność, podstawy matematyki np. definicja liczb naturalnych, wartości logicznych, itd. Rachunek lambda został wprowadzony przez Alonzo Churcha i Stephena Cole'a Kleene'ego w 1930 roku. Rachunek lambda jest przydatny do badania algorytmów. Wszystkie algorytmy, które dadzą się zapisać w rachunku lambda, dadzą się zaimplementować na maszynie Turinga i odwrotnie. Istnieje wiele rodzajów rachunku lambda, z czego najprostszym jest rachunek lambda bez typów, stanowiący pierwotną inspirację dla powstania programowania funkcyjnego (Lisp). Rachunek lambda z typami jest podstawą dzisiejszych systemów typów w językach programowania. Rachunek lambda 10
Maszyna Turinga Maszyna Turinga jest prostym urządzeniem algorytmicznym, uderzająco prymitywnym w porównaniu z dzisiejszymi komputerami i językami programowania, a jednak na tyle silnym, Ŝe pozwala na wykonanie nawet najbardziej złoŝonych algorytmów. Maszyna Turinga jest mechanizmem powstałym w wyniku ciągu uproszczeń: danych, sterowania nimi oraz uproszczeń podstawowych operacji. Maszynę tą wymyślił w roku 1936 brytyjski matematyk Alan M.Turing Alan Turing 11
Podstawowy model maszyny Turinga Podstawowy model ma skończone sterowanie, taśmę wejściową podzieloną na komórki (kwadraty) oraz głowicę taśmy, mogącą obserwować w dowolnej chwili tylko jedna komórkę taśmy. Taśma ma komórkę połoŝoną najbardziej na lewo, ale jest prawostronnie nieskończona. KaŜda z komórek taśmy moŝe zawierać dokładnie jeden symbol z skończonego alfabetu symboli. Przyjmuje się umownie, Ŝe ciąg symboli wejściowych umieszczony jest na taśmie począwszy od lewej, pozostałe komórki ( na prawo od symboli wejściowych) są wypełnione specjalnym symbolem taśmowym noszącym nazwę symbolu pustego. Podstawowy model maszyny Turinga 12
Maszyna Turinga składa się z następujących elementów: skończonego alfabetu symboli; skończonego zbioru stanów; nieskończonej taśmy z zaznaczonymi kwadratami, z których kaŝdy moŝe zawierać pojedynczy symbol; ruchomej głowicy odczytująco - zapisującej, która moŝe wędrować wzdłuŝ taśmy przesuwając się na raz o jeden kwadrat diagramu przejść między stanami, zawierającego instrukcje, które powodują, Ŝe zmiany następują przy kaŝdym zatrzymaniu się. Maszyna Turinga W zaleŝności od obserwowanego symbolu przez głowicę taśmy oraz stanu sterowania skończonego, maszyna Turinga w pojedynczym ruchu: zmienia stan, wpisuje symbol w obserwowanej komórce taśmy, zastępując symbol tam wpisany, przesuwa głowicę o jedną komórkę w prawo lub w lewo. 13
Teza Churcha-Turinga RóŜnorodność zadań stawianych przed maszyną Turinga postawiło pytanie : Jakie problemy moŝna rozwiązać odpowiednio zaprogramowaną maszyną Turinga (oczywiście pomijając czas)? OtóŜ okazuje się Ŝe: Maszyny Turinga potrafią rozwiązać kaŝdy efektywnie rozwiązywalny problem algorytmiczny. Teza Churcha-Turinga Mówiąc inaczej, kaŝdy problem algorytmiczny dla którego moŝemy znaleźć algorytm dający się zaprogramować w pewnym dowolnym języku, wykonujący się na pewnym dowolnym komputerze, nawet na takim, którego jeszcze nie zbudowano, ale moŝna zbudować, i nawet na takim, który wymaga nieograniczonej ilości czasu i pamięci dla coraz większych danych, jest takŝe rozwiązywalny przez maszynę Turinga. To stwierdzenie jest jedną z wersji tzw. tezy Churcha-Turinga (Alonz Church, Alan M. Turing), którzy doszli do niej niezaleŝnie w połowie lat trzydziestych. Istotną sprawą jest aby uświadomić sobie, Ŝe teza Churcha-Turinga jest tezą a nie twierdzeniem, zatem nie moŝe być dowiedziona w matematycznym tego słowa znaczeniu. Jedno z pojęć, do której się odwołuje, jest bowiem nieformalnym i nieprecyzyjnym, pojęciem efektywnej obliczalności. 14
Teza Churcha-Turinga Na długo przedtem nim wymyślono pierwszy komputer, Turing zaproponował swoją maszynę, a Church wymyślił matematyczny formalizm funkcji zwany rachunkiem lambda (jako podstawa języka programowania Lisp). Mniej więcej w tym samym czasie Emil Post zdefiniował pewien typ systemu produkcji do manipulowania symbolami, a Stephen Kleene zdefiniował klasę obiektów zwanych funkcjami rekurencyjnymi. Wszyscy oni próbowali uŝyć tych modeli do rozwiązania wielu problemów algorytmicznych, do których znane były efektywnie wykonalne algorytmy. Przełomowym zdarzeniem istotnym dla wszystkich tych modeli stało się udowodnienie, iŝ są one równowaŝne w kategoriach problemów algorytmicznych, które rozwiązują. Teza Churcha-Turinga Z tezy Churcha-Turinga wynika, Ŝe najpotęŝniejszy superkomputer z wieloma najwymyślniejszymi językami programowania, interpretatorami, kompilatorami i wszelkimi zachciankami, nie jest potęŝniejszy od domowego komputera z jego uproszczonym językiem programowania. Mając ograniczoną ilość czasu i pamięci mogą obydwa rozwiązać te same problemy algorytmiczne, jak równieŝ Ŝaden z nich nie moŝe rozwiązać problemów nierozstrzygalnych (nieobliczalnych). 15
Teza Churcha-Turinga ODMIANY MODELU MASZYNY TURINGA Problem algorytmiczny, do którego nie ma Ŝadnego algorytmu jest nazywany nieobliczalnym; jeśli to problem decyzyjny nazywa się nierozstrzygalnym. Dla problemów tego typu w Ŝaden sposób nie moŝna skonstruować algorytmu, wykonywanego na dowolnym komputerze, bez względu na ilość wymaganego czasu i pamięci, który mógłby je rozstrzygnąć. 16
Język UML 17
Język UML Język UML 18
Język UML 19
Programowanie zwinne Programowanie zwinne ((ang.) Agile software development) grupa metodyk wytwarzania oprogramowania opartego na programowaniu iteracyjnym (model przyrostowy). Wymagania oraz rozwiązania ewoluują przy współpracy samozarządzalnych zespołów, których celem jest przeprowadzanie procesów wytwarzania oprogramowania. Pojęcie zwinnego programowania zostało zaproponowane w 2001 w Agile Manifesto. Programowanie zwinne Generalnie metodyka oparta jest na zdyscyplinowanym zarządzaniu projektem, które zakłada częste inspekcje wymagań i rozwiązań wraz z procesami adaptacji (zarówno specyfikacji jak i oprogramowania). Metodyka ta najczęściej znajduje zastosowanie w małych zespołach programistycznych, w których nie występuje problem komunikacji, przez co nie trzeba tworzyć rozbudowanej dokumentacji kodu. Kolejne etapy wytwarzania oprogramowania zamknięte są w iteracjach, w których za kaŝdym razem przeprowadza się testowanie wytworzonego kodu, zebranie wymagań, planowanie rozwiązań itd. Metoda nastawiona jest na szybkie wytwarzanie oprogramowania wysokiej jakości. 20
Programowanie zwinne Skład zespołów jest zazwyczaj wielofunkcyjny oraz samozarządzalny, bez zastosowania jakiejkolwiek hierarchii korporacyjnej. Członkowie zespołu biorą odpowiedzialność za zadania postawione w kaŝdej iteracji. Sami decydują jak osiągnąć postawione cele. Metoda nastawiona jest na bezpośrednią komunikację pomiędzy członkami zespołu, minimalizując potrzebę tworzenia dokumentacji. Jeśli członkowie zespołu są w róŝnych lokalizacjach, to planuje się codzienne kontakty za pośrednictwem dostępnych kanałów komunikacji (wideokonferencja, e-mail itp.). Programowanie zwinne Manifest Agile ((ang.) Agile Manifesto) załoŝenia: osiągnięcie satysfakcji klienta poprzez szybkość wytwarzania oprogramowania, działające oprogramowanie jest dostarczane okresowo (raczej tygodniowo niŝ miesięcznie), podstawową miarą postępu jest działające oprogramowanie, późne zmiany w specyfikacji nie mają destrukcyjnego wpływu na proces wytwarzania oprogramowania, bliska, dzienna współpraca pomiędzy biznesem a developerem, bezpośredni kontakt, jako najlepsza forma komunikacji w zespole i poza nim, ciągła uwaga nastawiona na aspekty techniczne oraz dobry projekt (design), prostota, samozarządzalność zespołów, regularna adaptacja do zmieniających się wymagań. 21