Wytwarzanie oprogramowania I - paradygmaty programowania
Posty z tej serii:
- Wytwarzanie oprogramowania I - paradygmaty programowania
- Wytwarzanie oprogramowania II - techniki programowania
- Wytwarzanie oprogramowania III - architektury systemów informatycznych
- Wytwarzanie oprogramowania IV - NServiceBus - framework, który zmienia zasady gry
Jest to pierwszy wpis z serii, którą nazwałem “Wytwarzanie oprogramowania”. W serii tej, przedstawię elementy, które pomagają mi zrozumieć, czym jest programowanie, a także elementy, które pomogły\pomagają mi w codziennej pracy, od momentu kiedy byłem młodszym programistą do teraz, kiedy jestem trochę starszym programistą ;).
Wpis ten zawiera krótki przegląd wybranych paradygmatów programowania.
Programowanie niskopoziomowe
Język maszynowy
Z punktu widzenia komputera najbardziej przyjaznym językiem do komunikacji jest język składający się z dwóch znaków: 0 i 1. Jest to tak zwany język maszynowy. “Rozmowa” z komputerem w tym języku odbywa się poprzez wprowadzanie rozkazów reprezentowanych przez ciągi zer i jedynek. Każdy komputer posiada jednostkę zwaną procesorem, a każdy rodzaj procesora posiada swoją interpretację języka maszynowego. Dla przykładu, poniższy kod reprezentuje język maszynowy zrozumiały dla procesora Sextium II. “Rozkazuje” on komputerowi wczytanie dwóch liczb ze standardowego wejścia, dodanie ich do siebie oraz wypisanie wyniku na standardowe wyjście:
Wersja w zapisie 16-kowym:
Asembler
Dla komputera powyższy kod jest jasny i łatwy w zrozumieniu, natomiast dla człowieka już nie (a może tak ;)), dlatego następnym etapem w rozwoju języków programowania było powstanie tak zwanych języków adresów symbolicznych (Asemblerów). Program napisany w takim języku, reprezentowany jest w postaci symboli, które reprezentują rozkazy procesora. Symbole te następnie tłumaczone są na język maszynowy procesora. Poniższy kod jest tym samym programem z poprzedniego akapitu, napisanym w języku Asemblera zrozumiałego dla procesora Sextium II:
Tłumaczenie z Asemblera na kod maszynowy
Języki wysokiego poziomu
Asembler pozwala komunikować się z komputerem w dużo bardziej przyjazny sposób niż ciągi zer i jedynek, umożliwiając pisanie bardziej zaawansowanych programów. Jednak nadal jest to język dość trudny, zwłaszcza, jeśli chodzi o analizę kodu lub jego modyfikację. Między innymi z tego powodu następnym krokiem w rozwoju języków programowania było powstanie tak zwanych języków wysokiego poziomu. Języki te umożliwiają porozumiewanie się z komputerem w jeszcze bardziej przyjazny sposób, uwalniając programistę od konieczności znajomości konkretnej architektury procesora. Kod napisany w języku wysokiego poziomu jest tłumaczony na język Asemblera (lub bezpośrednio na język maszynowy) w tak zwanym procesie kompilacji. Jednym z pierwszych stworzonych języków z tej kategorii jest język o nazwie Fortran. Niektóre jego cechy to:
- deklaracje zmiennych
- definiowanie procedur
- operatory logiczne
- operatory arytmetyczne
- operatory działań na liczbach
Poniższy kod jest tym samym programem z poprzedniego akapitu napisanym w języku Fortran:
Programowanie imperatywne
Dzięki językom wysokiego poziomu komunikacja z komputerem została mocno uproszczona w stosunku do języka Asemblera, przez co analiza programów oraz ich modyfikacja jest dużo prostsza i mniej podatna na błędy. Na bazie takiego poziomu abstrakcji, powstały różnego rodzaju paradygmaty (lub metodyki) programowania. Jednym z takich paradygmatów jest tzw. programowanie imperatywne, które charakteryzuje się tym, że program reprezentowany jest w postaci stanu maszyny, który zmieniany jest przez ciąg instrukcji. Innymi słowy, programując imperatywnie mówimy komputerowi jak ma rozwiązać dany problem. Przykładowo, poniższy kod napisany w języku C oblicza sumę kwadratów liczb od 1 do n:
Programowanie strukturalne
Patrząc na powyższy przykład, można zauważyć, że wykorzystuje on tak zwaną instrukcję skoku goto
. Instrukcja ta nakazuje komputerowi, przy spełnieniu odpowiednich warunków, przejście do odpowiedniej części programu i kontynuowanie wykonywania programu od tego miejsca. Takie “skakanie” bardzo utrudnia analizę kodu. Paradygmat programowania strukturalnego ogranicza używanie instrukcji skoku oraz definiuje strukturę kodu. Kod taki powinien składać się z kilku dobrze zdefiniowanych instrukcji:
- sekwencja – wykonanie instrukcji jedna po drugiej
- wybór – if-then-else
- iteracja – for, while
Przykład, w języku C obliczania sumy kwadratów liczb od 1 do n z użyciem powyższych instrukcji może wyglądać np. tak:
Programowanie proceduralne
Głównym założeniem tego paradygmatu jest to, aby kod dzielić na mniejsze kawałki, wykonujące jasno określone operacje. Większość języków wysokiego poziomu (a może wszystkie ;)) dostarczają konstrukcję zwaną procedurą. W procedurach można zakodować poszczególne fragmenty programu tak, aby odseparować je od reszty, a także móc je wykorzystać w innych miejscach. Przykład w języku C, podziału na procedury (main(...), squaresSum(...), square(...)
) powyższego programu może wyglądać np. tak:
Programowanie obiektowe
Pewną trudnością w programowaniu proceduralnym jest to, że dane, na których operują procedury nie są ze sobą powiązane. Dotyczy to zwłaszcza zadeklarowanych zmiennych globalnych, do których można mieć dostęp z dowolnego fragmentu kodu. Ponieważ główną koncepcją programowania imperatywnego, a co za tym idzie również programowania proceduralnego, jest zmiana stanu maszyny, a stan ten może być zmieniany w dowolnym fragmencie kodu, może prowadzić to do powstawania błędów w programie, poprzez nieprawidłowe ustawienie tego stanu. Aby zminimalizować takie operacje, powstały tzw. języki obiektowe, a co za tym idzie również paradygmat programowania obiektowego. Głównym założeniem tego paradygmatu jest to, aby dane oraz procedury, które operują na tych danych, były ze sobą powiązane w takim sensie, aby zmiany na tych danych mogły dokonywać tylko te jasno określone procedury. W większości języków obiektowych konstrukcja do budowy takich powiązań nazywa się klasą (class
), a instancję konkretnej klasy nazywa się obiektem (object
). Procedury operujące na danych obiektu nazywa się metodami (method
). Przykład programu w języku C#, obliczającego sumę kwadratów liczb od 1 do n może wyglądać np. tak:
W metodzie Main
jest tworzony obiekt SquaresSumCalculator
, na którym to obiekcie wywoływana jest metoda Calculate
. Metoda ta jest zadeklarowana jako publiczna w klasie SquaresSumCalculator
. Pozostałe metody są oznaczone modyfikatorem dostępu private
, który to modyfikator “zabrania” dostępu do tych metod fragmentom kodu innym niż te, które znajdują się w klasie SquaresSumCalculator
. Jak widać na przykładzie w ramach implementacji klasy, wykorzystane jest programowanie proceduralne, ale mogłoby być użyte dowolne inne. “Na zewnątrz klasy” dostępna jest tylko metoda Calculate
, a konsumer (klasa Program
) wywołujący tę metodę nie wie nic o szczegółach jej wewnętrznej implementacji.
Programowanie funkcyjne
Równolegle z rozwojem paradygmatów bazujących na programowaniu imperatywnym rozwijała się koncepcja programowania funkcyjnego. W programowaniu imperatywnym główną koncepcją jest stan maszyny oraz zmiana tego stanu, natomiast w programowaniu funkcyjnym główną koncepcją jest funkcja oraz wyliczanie wyrażeń przez funkcje. Mówimy komputerowi bardziej, co ma zrobić, a nie, jak ma to zrobić. W większości przypadków języki funkcyjne są bardziej zwięzłe w konstrukcji od języków imperatywnych. Dla porównania przykład programu obliczającego sumę kwadratów liczb od 1 do n, zapisany w języku F#, może wyglądać np. tak:
Programowanie logiczne
Jeszcze innym paradygmatem jest tzw. programowanie logiczne, gdzie program reprezentowany jest przez zapis stwierdzeń rachunku predykatów pierwszego rzędu, a wyniki programu jako rezultat automatycznego wnioskowania z tych stwierdzeń. Przykład programu stwierdzającego relację rodzeństwa w języku Prolog:
Podsumowanie
Z komputerem można “rozmawiać” na wiele sposobów, ale sama znajomość języków programowania oraz paradygmatów programowania nie wystarcza do tworzenia złożonych programów cechujących się dobrą jakością. W kolejnych seriach z cyklu “Wytwarzanie oprogramowania” zostaną opisane wybrane techniki pozwalające zbliżyć się do jakości akceptowalnej przez odbiorcę, bo przecież nie istnieją programy w 100% odporne na błędy, a może tak ;).
=