Języki programowania II
Technologia programowania
Olsztyn 2005
Wojciech Sobieski
Technologia programowania
Technologia programowania - nauka o procesach wytwarzania
systemów informatycznych (programów komputerowych).
Technologia programowania
W zakres technologii programowania wchodzą:
●
sposoby prowadzenia projektów informatycznych,
●
metody analizy i projektowania systemów,
●
techniki planowania, szacowania kosztów,
●
harmonogramowania i monitorowania projektów
informatycznych,
●
techniki zwiększania niezawodności oprogramowania,
●
sposoby testowania systemów i szacowania niezawodności,
●
sposoby przygotowania dokumentacji technicznej i użytkowej,
●
procedury kontroli jakości,
●
metody redukcji kosztów konserwacji,
●
techniki pracy zespołowej.
Technologia programowania
Program komputerowy - ciąg instrukcji do wykonania dla
komputera. Można też powiedzieć, że program to algorytm zapisany
w języku programowania. Program może występować w dwóch
postaciach: jako program wykonywalny (czyli zapisany w języku
maszynowym) albo jako kod źródłowy, czyli postać zrozumiała dla
programisty.
Fazy powstawania programów
Fazy powstawania programu komputerowego:
1. Określanie wymagań (specyfikacja),
2. Wybór języka programowania,
3. Tworzenie algorytmu,
4. Projektowanie systemu,
5. Implementacja,
6. Scalanie systemu,
7. Testy końcowe,
8. Tworzenie dokumentacji użytkowej,
9. Konserwacja systemu
.
Fazy powstawania programów
1. Określanie wymagań – służy do sprecyzowania potrzeb. Na tym
etapie formułuje się wyobrażenia o programie i jego działaniu oraz
precyzuje wymagania. Na podstawie wymagań tworzona jest tzw.
specyfikacja projektu, czyli zakres czynności, jaki dany program ma
wykonywać. Jeżeli program jest tworzony na zamówienie, należy w
pewnym momencie zażądać zamrożenia specyfikacji – w
przeciwnym razie klient może zażądać zmian (nawet po napisaniu
programu), a wykonawca będzie to musiał zrobić nieodpłatnie.
Czasami nawet mała zmiana wymagań może w decydujący sposób
wpłynąć na sposób realizacji zamówienia, co może nawet
doprowadzić do konieczności rozpoczęcia praktycznie od nowa.
Fazy powstawania programów
Tworzenie wymagań możliwe jest to na kilka sposobów:
●
wywiad strukturalny – polega na zadawaniu wcześniej
●
opracowanej listy pytań,
●
wywiad swobodny – polega na zadawaniu dowolnych
●
pytań i notowaniu odpowiedzi,
●
ankieta wysyłana do dużej liczby potencjalnych klientów,
●
analiza formularzy i innych dokumentów klienta,
●
analiza cyklu pracy i wykonywanych czynności,
●
analiza scenariuszy używania systemu,
●
konstrukcja prototypu.
Fazy powstawania programów
Dokumentacja fazy określania wymagań powinna zawierać:
●
wprowadzenie: zakres systemu, cele i konteksty jego używania,
●
opis ewolucji systemu: przewidywane zmiany w systemie,
●
specyfikację wymagań funkcjonalnych: jakie czynności i
operacje system powinien wykonywać,
●
specyfikację wymagań niefunkcjonalnych: przy jakich
ograniczeniach system powinien powstać i działać.
●
opis modelu systemu lub jego prototypu,
●
opis wymagań sprzętowych,
●
słownik zawierający wyjaśnienia używanych
w dokumentacji pojęć fachowych i informatycznych.
Fazy powstawania programów
Jeżeli program komputerowy powstaje na użytek programisty, faza
określania wymagań może zostać skrócona do minimum.
Nie powinno się jednak pomijać fazy dokumentacji – powinna oba
być prowadzona przynajmniej w zakresie podstawowym,
umożliwiającym rozwój i modyfikację programu nawet po wielu
latach.
Fazy powstawania programów
2. Wybór języka programowania – zależy głównie od
przeznaczenia aplikacji. Przeważnie istnieje przynajmniej kilka
języków programowania nadających się budowy systemu
spełniającego wymagania.
Wybór języka programowania może być dokonany przed lub po
stworzeniu algorytmu. W pierwszym przypadku dobór języka może
w znaczny sposób wpłynąć na tok rozumowania i zdeterminować
budowę algorytmu.
Fazy powstawania programów
Wybór języka programowania może być:
●
narzucony przez zamawiającego,
●
pozostawiony woli wykonawcy – musi on wówczas uwzględnić:
•
dziedzinę aplikacji,
•
doświadczenie zespołu programistów,
•
posiadane narzędzia,
•
dostępność gotowych
modułów i bibliotek,
•
potrzeby innych prowadzonych
równolegle prac.
Fazy powstawania programów
3. Tworzenie algorytmu - jest to jeden z najważniejszych etapów
projektowania, wymaga on od programisty (bądź projektanta)
starannego przemyślenia i przygotowania. Nie należy przystępować
do pisania programu nie mając wyobrażenia o jego budowie. Takie
postępowanie wydłuży zapewne czas realizacji projektu, zwiększy
prawdopodobieństwo wystąpienia błędów oraz nie daje gwarancji
optymalności rozwiązań. Dużych i rozbudowanych programów
praktycznie nie da się napisać bez odpowiednich przygotowań. Jeżeli
to jest tylko możliwe należy korzystać z istniejących i sprawdzonych
algorytmów.
Fazy powstawania programów
4. Projektowanie systemu – służy do stworzenia szkieletu kodu
źródłowego (systemu).
Techniki projektowania:
●
projektowanie strukturalne:
•
zorientowane na akcję,
•
zorientowane na dane,
●
projektowanie obiektowe.
Fazy powstawania programów
Faza projektowania składa się z następujących etapów:
●
projektowanie architektury systemu,
●
organizacja danych i ich nazewnictwa,
●
szczegółowe projektowanie modułów,
●
weryfikacja i testowanie.
Fazy powstawania programów
5. Implementacja – jest to właściwa faza budowy programu. Małe
systemy informatyczne mogą być realizowane przez jednego
programistę. Systemy większe wymagają utworzenia zespołu bądź
też grupy zespołów programistycznych.
Fazy powstawania programów
6. Scalanie systemu – w przypadku tworzenia aplikacji przez zespół
programistów należy połączyć wszystkie moduły w jedną całość.
Rozróżnia się trzy podstawowe techniki integracji systemu
informatycznego:
●
metoda zstępująca – najpierw integrowane są moduły sterujące, a
później moduły wykonawcze,
●
metoda wstępująca – najpierw integrowane są moduły
wykonawcze a później sterujące,
●
metoda mieszana – moduły sterujące
integrowane są metodą zstępującą,
a wykonawcze – wstępującą.
Integracja całości
Model
Zalety
Wady
-
Brak izolacji błędów.
Poważne błędy projektowe
wykrywane są bardzo późno.
Porównanie metod integracji systemu.
Fazy powstawania programów
Metoda zstępująca
Izolacja błędów.
Poważne błędy projektowe są
wykrywane bardzo wcześnie.
Moduły, które mogą być powtórnie
użyte nie są w pełni przetestowane.
Metoda wstępująca
Izolacja błędów.
Moduły, które mogą być powtórnie
użyte są dobrze przetestowane.
Poważne błędy projektowe są
wykrywane bardzo późno.
Metoda mieszana
Izolacja błędów.
Poważne błędy projektowe są
wykrywane bardzo wcześnie.
Moduły, które mogą być powtórnie
użyte są dobrze przetestowane.
-
Fazy powstawania programów
7. Testy końcowe – służą do ostatecznego potwierdzenia
poprawności systemu. Rozróżnia się następujące etapy testów:
●
testowanie przez Dział Kontroli Jakości,
●
alfa-testy przez wybraną (najczęściej małą) grupę użytkowników,
●
beta-testy przez wybraną (większą) grupę użytkowników,
●
testy akceptacji (na rzeczywistych danych), wykonywane przez
odbiorcę.
Fazy powstawania programów
Kryteria wykonywania testów:
●
użyteczność aplikacji (łatwość użycia, użyteczność oferowanych
funkcji, opłacalność),
●
solidność (częstość krytycznych awarii systemu, czas
podniesienia systemu po awarii, czas odbudowy systemu po
awarii),
●
odporność (na zmieniające się warunki otoczenia, błędne dane,
nieoczekiwane reakcje użytkowników),
●
efektywność (szybkość, czas reakcji, zajętość pamięci),
●
poprawność (zgodność ze specyfikacją).
Fazy powstawania programów
Metody wykonywania testów:
●
test „czarnej skrzynki” - odbywa się na poziomie interfejsu,
●
test „szklanej skrzynki” - odbywa się na poziomie kodu
źródłowego.
Fazy powstawania programów
8. Dokumentacja użytkowa – powinna zawierać następujące
elementy:
●
opis funkcjonalny – opis przeznaczenia oraz głównych
możliwości programu,
●
podręcznik użytkownika – opis przeznaczony dla typowych
użytkowników, powinien zawierać:
•
sposoby uruchamiania oraz kończenia pracy z systemem,
•
sposoby realizacji podstawowych funkcji systemu,
•
metody obsługi błędnych sytuacji,
•
sposoby korzystania z systemu pomocy,
•
kompletny przykład korzystania z systemu.
Fazy powstawania programów
●
kompletny opis systemu – część przeznaczona dla
doświadczonych użytkowników, powinien zawierać:
•
szczegółowy opis wszystkich funkcji systemu,
•
informacje o sposobach wywoływania tych funkcji,
•
opisy formatu danych,
•
opisy błędów, które mogą pojawić się w trakcie pracy z
systemem,
•
informacje o wszelkich ograniczeniach dotyczących np.
zakresu danych.
Fazy powstawania programów
●
opis instalacji – przeznaczony dla administratorów systemu.
Powinien zawierać opis procedury instalacyjnej i konfiguracji
programu,
●
podręcznik administratora systemu – zawierający opis
możliwości modyfikacji ustawień systemu oraz sposoby
udostępniania go użytkownikom końcowych.
Fazy powstawania programów
Na jakość dokumentacji mają wpływ następujące czynniki:
●
struktura podręcznika,
●
zachowanie standardów,
●
sposób pisania
(stosowanie formy aktywnej, poprawność gramatyczna i
ortograficzna, krótkie zdania, oszczędność sów, precyzyjna
definicja używanych terminów, powtarzanie trudnych
opisów, stosowanie tytułów i podtytułów sekcji, wyliczeń i
wyróżnień, zrozumiałe odwołania do innych rozdziałów).
Fazy powstawania programów
9. Konserwacja systemu – zawiera zespół czynności,
dokonywanych po stworzeniu aplikacji, obejmujących:
●
poprawianie błędów,
●
udoskonalanie produktu,
●
dostosowywanie do nowych warunków.
Fazy powstawania programów
Koszty konserwacji są często bardzo duże – do 2/3 nakładów
finansowych na aplikacje. Wynika to z następujących przyczyn:
●
poprawki mogą być przyczyną nowych błędów,
●
udoskonalenie wymaga przejścia przez wszystkie fazy produkcji i
dokonania zmian w specyfikacji, projekcie i dokumentacji,
●
poprawione powinny być wszystkie dokumenty, w których
znajdują się informacje dotyczące zmienionych fragmentów
produktu.
Fazy powstawania programów
Zarządzanie fazą konserwacji:
●
raport o błędzie – formalna metoda dokumentacji wykrytych
przez użytkowników błędów systemu – musi zawierać informacje
wystarczające do odtworzenia błędu,
●
błędy krytyczne muszą być poprawiane natychmiast, dla
pozostałych powinny zostać opisane przyczyny błędu i
ewentualne metody ich obejścia,
●
opisane raporty o błędach wraz z datą ich poprawienia powinny
być udostępniane wszystkim użytkownikom,
●
błędy niekrytyczne powinny być poprawiane paczkami – obniża
to koszty testowania i modyfikacji dokumentacji,
●
zmodyfikowany produkt powinien być sprawdzony przez
niezależny zespół kontroli.
Modele tworzenia programów
Użytkowanie
Koniec
Poprawiaj aż klient
będzie zadowolony
Zbuduj pierwszą wersję
Model
Zbuduj i Poprawiaj
Budowanie
Konserwacja
Modele tworzenia programów
Określenie wymagań
Sprawdzenie
Specyfikacja
Sprawdzenie
Projektowanie
Sprawdzenie
Implementacja
Sprawdzenie
Integracja
Sprawdzenie
Użytkowanie
Koniec
Zmiana wymagań
Sprawdzenie
Model
kaskadowy
Budowanie
Konserwacja
Modele tworzenia programów
Szybki prototyp
Sprawdzenie
Specyfikacja
Sprawdzenie
Projektowanie
Sprawdzenie
Implementacja
Sprawdzenie
Integracja
Sprawdzenie
Użytkowanie
Koniec
Zmiana wymagań
Sprawdzenie
Prototypowanie
Budowanie
Konserwacja
Modele tworzenia programów
Określenie wymagań
Sprawdzenie
Dla każdej składowej:
•zaprojektuj
•zaimplementuj
•zintegruj
•przetestuj
•dostarcz do klienta
Użytkowanie
Koniec
Specyfikacja
Sprawdzenie
Projekt architektury
Sprawdzenie
Realizacja
przyrostowa
Budowanie
Konserwacja
Modele tworzenia programów
Ryzykowna
realizacja
przyrostowa
Grupa projektująca
Grupa specyfikująca
Specyfikacja
Projektowanie
Dostarczenie
Klientowi
Implementacja
Integracja
Specyfikacja
Projektowanie
Dostarczenie
Klientowi
Implementacja
Integracja
Specyfikacja
Projektowanie
Dostarczenie
Klientowi
Implementacja
Integracja
S
kł
ad
ow
a
2
Sk
ła
do
w
a
n
Sk
ła
do
w
a
1
Grupa Implementująca
Modele tworzenia programów
Analiza ryzyka
Szybki prototyp
Użytkowanie
Koniec
Sprawdzenie
Analiza ryzyka
Specyfikacje
Sprawdzenie
Analiza ryzyka
Projektowanie
Sprawdzenie
Analiza ryzyka
Implementacja
Sprawdzenie
Analiza ryzyka
Integracja
Sprawdzenie
Analiza ryzyka
Zmiana wymagań
Sprawdzenie
Uproszczony
model spiralny
Budowanie
Konserwacja
Modele tworzenia programów
Użytkowanie - Dalszy rozwój
Konserwacja
Integracja
Implementacja
Projektowanie obiektowe
Analiza obiektowa
Określenie wymagań
Obiektowy
model fontanny
Budowanie
Konserwacja
Modele tworzenia programów
Zbuduj i poprawiaj
Model
Model kaskadowy
Prototypowanie
Realizacja przyrostowa
Model spiralny
Modele obiektowe
Zalety
Wady
Nadaje się dla małych programów,
które nie wymagają konserwacji.
Nie nadaje się dla
nietrywialnych programów.
Systematyczne podejście
i sterowanie dokumentami.
Dostarczony produkt może nie
spełniać oczekiwań zamawiającego.
Zapewnia, że produkt będzie
spełniał oczekiwania zamawiającego.
Koszt budowy prototypu.
Szybki zwrot inwestycji
i łatwość konserwacji.
Wymaga otwartej architektury.
Grożba degradacji do modelu
Zbuduj i Poprawiaj
Zawiera wszystkie cechy
powyższych modeli.
Pozwalają na integrację w fazach.
Pozwalają na równoległość faz.
Tylko dla bardzo dużych projektów.
Potrzebna wiedza o analizie ryzyka.
-
Porównanie modeli tworzenia oprogramowania.
Struktury zespołów
1
2
3
Rys. Ścieżki komunikacji pomiędzy trzema informatykami.
Struktury zespołów
Rys. Ścieżki komunikacji pomiędzy sześcioma informatykami.
1
2
3
4
5
6
Struktury zespołów
Rys. Klasyczny zespół głównego programisty.
Sekretarz
zespołu
Zastępca GP
Programista
Programista
Programista
Główny programista
Struktury zespołów
Rys. Współczesny zespół programistyczny.
Programista
Programista
Programista
Manager
zespołu
Lider
zespołu
Struktury zespołów
Rys. Struktura zarządzania technicznego w dużym projekcie.
Programiści
Lider
zespołu
Programiści
Lider
zespołu
Programiści
Lider
zespołu
Kierownik projektu
Struktury zespołów
Rys. Podejmowanie decyzji w dużym projekcie.
Programiści
Lider
zespołu
Programiści
Lider
zespołu
Programiści
Lider
zespołu
Kierownik projektu
Modele tworzenia programów
Zespół demokratyczny
Model
Zalety
Wady
Wysokiej jakości kod jako efekt
pozytywnego nastawienia do szukania
błędów.
Skuteczny w trudnych problemach.
Trudno rozszerzalny.
Porównanie różnych sposobów organizacji zespołu programistycznego.
Klasyczny zespół
głównego programisty
-
Niepraktyczny.
Współczesny zespół
głównego programisty
Brak w strukturze wysoko
kwalifikowanego
głównego programisty.
Można go zdecentralizować.
Problemy z podziałem zadań
pomiędzy lidera a menagera zespołu.
Rodzaje programowania
Programowanie (ang. programming) - czynności związane z
wpisywaniem poleceń języka programowania przez programistów
celem tworzenia oprogramowania komputerowego.
Rodzaje programowania
Programowanie proceduralne – styl programowania, w którym
kod źródłowy podzielony jest na fragmenty wykonujące ściśle
określone operacje. Procedury nie powinny korzystać ze zmiennych
globalnych (w miarę możliwości) lecz pobierać i przekazywać
wszystkie dane (czy też wskaźniki do nich) jako parametry
wywołania.
Rodzaje programowania
Programowanie strukturalne – metoda programowania, w której
kod źródłowy podzielony jest na niewielkie moduły - procedury,
bądź funkcje - komunikujące się ze sobą jedynie poprzez
odpowiednie interfejsy.
Interfejs (interface) – część danego obiektu widoczna na zewnątrz
dla innych obiektów. Jego zadaniem jest umożliwienie współpracy z
tym obiektem np. komunikacja z nim, czy wykorzystanie jego
innych właściwości umieszczonych wewnątrz.
Do najpopularniejszych języków strukturalnych zalicza się Pascal, C,
Modula-2.
Rodzaje programowania
Programowanie modularne (modular programming) - paradygmat
programowania zalecający stosowanie modułów, jako nadrzędnych
w stosunku do procedur, bloków tworzących program. Moduł w
pojęciu programowania modularnego grupuje funkcjonalnie
związane ze sobą dane i procedury i jest reprezentacją obiektu
występującego w programie w jednym egzemplarzu.
Rodzaje programowania
Programowanie imperatywne – technika programowania, w której
program składa się ze zmiennych oraz modyfikujących je operacji, z
jawnie określonym przepływem sterowania.
Programista używający języka imperatywnego osiąga zamierzony
efekt przez manipulowanie wartościami zmiennych. Przykładami
języków imperatywnych są Fortran, Pascal i C.
Największą zaletą jest bliskość takiej reprezentacji do tego co
rzeczywiście wykonywane jest przez komputer.
Rodzaje programowania
Programowanie obiektowe (object-oriented programming) – jest to
metodologia tworzenia programów komputerowych, która definiuje
programy za pomocą „obiektów” - elementów łączących stan (czyli
dane) i zachowanie (czyli procedury, tu: metody). Obiektowy
program komputerowy wyrażony jest jako zbiór takich obiektów,
komunikujących się pomiędzy sobą w celu wykonywania zadań.
Podejście to różni się od tradycyjnego programowania
proceduralnego, gdzie dane i procedury nie są ze sobą bezpośrednio
związane. Programowanie obiektowe ma ułatwić pisanie,
konserwację i wielokrotne użycie programów lub ich fragmentów.
Popularnymi językami obiektowymi są: C++, Java, Smalltalk,
Object Pascal, Beta, Theta, CLOS, Eiffel, Ada98, Python i in. Od
lat osiemdziesiątych języki obiektowe uważa się za szczytowe
osiągnięcie inżynierii oprogramowania.
Rodzaje programowania
Programowanie funkcjonalne (functional programming) -
koncepcja
programowania
bez
używania
przypisań;
w
programowaniu funkcjonalnym obliczenie tej samej procedury z
tymi samymi parametrami powoduje zawsze powstanie tego samego
wyniku, procedury w programowaniu funkcjonalnym mogą zatem
być rozważane jako funkcje w rozumieniu matematycznym.
Rodzaje programowania
Programowanie RAD (Rapid Application Development) - termin
komercyjny (“błyskawiczne opracowywanie aplikacji”) określający
możliwości pakietu oprogramowania działającego w graficznym
środowisku okienkowym, służącego do zestawiania aplikacji,
w szczególności – ich interfejsów, z modułów wybieranych z
bibliotek udostępnianych w postaci drzew tematycznych.
W zależności od wybranego elementu pakiet RAD wytwarza fragmenty
kodu (np. w języku C++) wraz z odpowiednimi wywołaniami. Szczegóły
odbiegające od standardu uzupełnia ręcznie programista.
Aplikacje wytworzone w środowiskach RAD są z reguły słabo przenośne i
zajmują
dużo
pamięci,
jednak
możliwość
automatycznego
oprogramowania ich najżmudniejszych elementów (interfejs graficzny)
oraz ułatwiony kontakt ze standardowymi bazami danych powoduje, że
programowanie RAD staje się coraz popularniejsze.
Rodzaje programowania
Programowanie zdarzeniowe (sterowane zdarzeniami) – jest to
metoda programowania według którego program jest cały czas
bombardowany zdarzeniami (events), na które musi odpowiedzieć.
Przepływ kontroli w takim programie jest całkowicie niemożliwy do
przewidzenia z góry. Programowanie zdarzeniowe jest dominującym
typem programowania GUI - zdarzenia to naciśnięcia myszy,
klawiszy, żądania odświeżenia przez system okienkowy, różne
zdarzenia sieciowe i inne.
W programowaniu zdarzeniowym ważne jest żeby nie obsługiwać
zbyt długo danego zdarzenia, bo blokuje się w ten sposób obsługę
innych. Można to osiągnąć za pomocą asynchronicznego I/O,
wielowątkowości, rozbijania zdarzenia na pod-zdarzenia i wielu
innych mechanizmów.
Rodzaje programowania
Programowanie liniowe (w ujęciu matematycznym) -
maksymalizacja lub minimalizacja funkcji wielu zmiennych, gdy
zmienne te, lub niektóre z nich, podlegają liniowym warunkom
ograniczającym w postaci równań lub nierówności. Nazwa
"programowanie" wskazuje w tym kontekście na schemat działań.
Rodzaje programowania
Programowanie nieliniowe (w ujęciu matematycznym) - metody
rozwiązywania problemów opisywanych przez równania
różniczkowe nie dające się sprowadzić do równań liniowych
(programowanie liniowe).
Rodzaje programowania
Programowanie dynamiczne - metoda zapewniająca wybór ciągu
zgodnych rozwiązań optymalnych w wielostopniowych problemach
decyzyjnych. W istocie jest to proces sekwencyjnego podejmowania
decyzji.
Rodzaje programowania
Programowanie w logice (logic programming) - kierunek
rozwojowy języków programowania, korzystający istotnie z
doświadczeń i osiągnięć logiki matematycznej. Instrukcje języków
programowania w logice przyjmują postaci wzorów logicznych
(predykatów). Programowanie w logice znajduje zastosowanie przy
budowaniu inteligentnych baz wiedzy, systemów ekspertowych,
w eksperymentach ze sztuczną inteligencją.
Rodzaje programowania
Programowanie współbieżne (concurrent
programming) - programowanie procesów
współbieżnych realizowane za pomocą
specjalnie
zaprojektowanych
języków
programowania
(np.
Linda),
języków
odpowiednio dostosowanych (np. Concurrent
Pascal)
lub
przy
użyciu
bibliotek
synchronizacji procesów i wątków.
Rodzaje programowania
Programowanie z użyciem obiektów (object-based programming)
- paradygmat programowania dopuszczający możliwość tworzenia
typów zdefiniowanych przez użytkownika i posługiwania się w
programach danymi nowo zdefiniowanych typów. Programowanie z
użyciem obiektów jest zalecane tam, gdzie programowanie
proceduralne lub modularne okazują się niewystarczające z uwagi na
potrzebę użycia nowych typów danych i liczne ich reprezentacje
(obiekty), natomiast programowanie obiektowe jest niepotrzebne z
powodu prostych właściwości przedmiotu programowania.
Rodzaje programowania
Programowanie wstępujące - w
programowaniu
strukturalnym
rozwiązywanie problemu za pomocą
wydzielonych prostszych zadań.
Programowanie zstępujące - w
programowaniu
strukturalnym
rozwiązywanie problemu w jednym
ogólnym, dobrze przemyślanym planie.
Olsztyn 2004
Dziękuję za uwagę
Wojciech Sobieski