eGospodarka.pl
eGospodarka.pl poleca

eGospodarka.plGrupypl.comp.programmingilu jest programistow na swiecie? › Re: ilu jest programistow na swiecie?
  • Path: news-archive.icm.edu.pl!news.gazeta.pl!not-for-mail
    From: Andrzej Jarzabek <a...@g...com>
    Newsgroups: pl.comp.programming
    Subject: Re: ilu jest programistow na swiecie?
    Date: Sat, 21 May 2011 13:00:35 +0100
    Organization: "Portal Gazeta.pl -> http://www.gazeta.pl"
    Lines: 361
    Message-ID: <ir89h4$kkh$1@inews.gazeta.pl>
    References: <iqjp8e$led$1@inews.gazeta.pl> <iqr4u7$qpo$1@news.onet.pl>
    <iqr7pi$r95$1@node2.news.atman.pl> <iqrujs$b8$1@news.onet.pl>
    <iqs0o4$85o$1@news.onet.pl> <1...@l...localdomain>
    <iqtglc$5c5$1@news.onet.pl> <iqthln$9gp$1@news.onet.pl>
    <iqtirb$9kr$1@news.onet.pl> <iqtj7p$fel$1@news.onet.pl>
    <c...@w...googlegroups.com>
    <iqtpbn$80t$1@news.onet.pl>
    <7...@t...googlegroups.com>
    <0...@1...googlegroups.com>
    <iqu14k$9ee$1@news.onet.pl>
    <6...@g...googlegroups.com>
    <iqucfc$jta$1@news.onet.pl> <iquoqb$ijm$1@inews.gazeta.pl>
    <ir1765$sji$1@news.onet.pl>
    <9...@n...googlegroups.com>
    <ir2r6p$gmn$1@solani.org> <ir2sv6$899$1@news.onet.pl>
    <a...@n...gazeta.pl>
    <ir55ji$ist$1@news.onet.pl> <ir71js$dc4$1@inews.gazeta.pl>
    <ir7ndm$mgd$1@news.onet.pl>
    NNTP-Posting-Host: 5acd7098.bb.sky.com
    Mime-Version: 1.0
    Content-Type: text/plain; charset=UTF-8; format=flowed
    Content-Transfer-Encoding: 8bit
    X-Trace: inews.gazeta.pl 1305979236 21137 90.205.112.152 (21 May 2011 12:00:36 GMT)
    X-Complaints-To: u...@a...pl
    NNTP-Posting-Date: Sat, 21 May 2011 12:00:36 +0000 (UTC)
    X-User: septi
    In-Reply-To: <ir7ndm$mgd$1@news.onet.pl>
    User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-GB; rv:1.9.2.17)
    Gecko/20110414 Thunderbird/3.1.10
    Xref: news-archive.icm.edu.pl pl.comp.programming:190550
    [ ukryj nagłówki ]

    On 21/05/2011 07:51, Michal Kleczek wrote:
    > Andrzej Jarzabek wrote:
    >>
    >> Dlatego testuje się przez _wszystkie_ iteracje. Np. jeśli masz release
    >> po trzech miesiącach od rozpoczęcia kodowania, to to, co masz w tym
    >> release jest już testowane od trzech miesięcy.
    >
    > Jak mozesz miec "juz przetestowana" calosc skoro wlasnie zakonczyles n-ta
    > iteracje i dolozyles funkcjonalnosc.

    Każda funkcjonalność jest testowana w każdej iteracji od momentu jej
    zaimplementowania, bo najpierw się pisze test, a potem implementację.

    > To sie da tak robic jak twoj zestaw testow da sie wykonac w ciagu 15 minut.

    To oczywiście zależy od produktu.

    > Taki zestaw testow to sobie w dupe
    > mozna wsadzic. _Realne_ testowanie to jest takie, ze _automatyczne_ (nie
    > manualne) testy trwaja na tyle dlugo, ze nie da sie tego robic "na biezaco",
    > bo wstrzymywaloby to zbyt dlugo prace programistow (np. kilka dni).

    To zależy od produktu. Generalnie im więcej funkcjonalności, tym dłużej
    trwają testy, ale wiele produktów w stanie release można automatycznie
    przetestować przez noc, więc jeśli codziennie robisz build/integrację,
    to możesz go testować przez noc.

    Jeśli nie dasz rady zrobić wszystkich testów przez noc, to możesz
    wyizolować, które testy mają kiepski stosunek czasu do
    prawdopodobieństwa faila, i te testy robisz tylko raz na iterację. Jeśli
    dwa-trzy dni wystarczą, to w niektórych krajach jest takie coś, jak weekend.

    Ostatecznie też nie jest tak, że jak się robią automatyczne testy, to
    programiści muszą siedzieć na rękach. Możesz spokojnie ustalić proces
    tak, że testy się wykonują, a programiści już kodują nowe testy i nowe
    ficzery.

    Zakładając jednak, że masz mały zespół w którym jest najwyżej 9
    programistów, to zanim dojdziesz do etapu, gdzie automatyczne testy
    trwają tydzień, to upłynie sporo wody (to oczywiście zależy od specyfiki
    produktu, ale typowo tak jest). Ponieważ cały czas rewidujesz proces, to
    zanim do tego dojdzie zespół będzie mógł zdecydować, co z tym zrobić.
    OPcji jest kilka:
    * Zrównoleglić testy
    * Wydłużyć iterację (owszem, generalnie metodologie z krótką iteracją
    uznają, że w dojrzałych produktach w części przypadków wydłużenie
    iteracji ma sens)
    * Podzielić produkt
    * Zmienić proces tak, że między zakończeniem iteracji a "ready to
    release" trwa tydzień.

    > Jezeli takie testy maja sie odbywac "non stop", to oznacza, ze release'y
    > musisz robic w cyklach rownych dlugosci trwania testow. A jesli trafi ci sie
    > blad tzw. "krytyczny", ktory stopuje testowanie w polowie, to co robisz?

    Od razu: "ready to release" nie oznacza "robisz release".

    Jeśli któryś test failuje, to dana iteracja nie jest "ready to release".

    > Masz dwa wyjscia:
    > 1) Czekasz na kolejny release (co oznacza, ze przestajesz testowac _ten_
    > release)
    > 2) Robisz galaz rownolegla do biezacego developmentu i poprawiasz ten blad i
    > odpalasz testy od poczatku. Tyle, ze wtedy albo
    > a) nastepna iteracja nie jest testowana (bo pojawia sie zanim testy
    > poprzedniej sie skoncza) - co de facto oznacza po prostu wydluzenie iteracji
    > (a co jesli w drugim podejsciu znajdziemy drugi taki blad)
    > b) testujesz rownolegle dwie (lub wiecej) iteracje-releasy (jesli to w ogole
    > mozliwe) ze wszystkimi konsekwencjami utrzymywania wielu galezi, mergy,
    > regresji itd - i tak sie robi, ale zeby nie zapanowal chaos releasy nie moga
    > byc zbyt czesto

    Przede wszystkim tak: jeśli masz buga w danej iteracji, to naprawienie
    tego buga staje się absolutnym priorytetem. Jeśli ma to sens, to
    wsztrzymujesz pracę nad nowymi stories do czasu naprawienia buga,
    ewentualnie możesz wstrzmać część nowych prac i zabronić dodawania
    czegokolwiek, co ma potencjał do konfliktu z bugfixem. Jeśli nie chcesz
    tracić czasu i chcesz, żeby programiści nie zajmujący się naprawianiem
    buga pracowali nad swoimi stories, to mogą robić to na robiczym branchu.

    Po drugie: jak naprawiasz buga, to naprawiasz go tak, żeby był
    naprawiony we wszystkich kolejnych iteracjach, a nie tylko w tej jednej,
    w której został znaleziony.

    Po trzecie: istotnym priorytetem jest to, żeby bugi były wyjątkiem, a
    nie regułą. Jeśli bugi są na tyle częste, że regularnie musisz opóźniać
    release, odwoływać demo lub ogłaszać, że dana iteracja nie jest "ready
    to release", to masz problem gdziee indziej. Robisz wtedy root cause
    analysis i zajmujesz się naprawą przyczyny tego stanu rzeczy.

    Biorąc pod uwagę powyższe, zazwyczaj możesz zrobić tak, że wyrzucasz
    builda z poprzedniej iteracji i pracujesz nad naprawieniem buga w
    bieżącej iteracji, zachowując wszystkie stories, które są już zakończone
    i decydując case-by-case o tym, czy stories które są zaplanowane lub w
    trakcie będziesz nadal próbował zrealizować w aktualnej iteracji, czy
    odłożysz do następnej. Jeśli nie masz kompletnie spierdzielonego
    procesu, to naprawienie buga znalezionego w n-tej iteracji powoduje, że
    n+1 iteracja nie ma buga, więc jesteś ready to release.

    >> Problem z testowaniem "od A do Z" polega na tym, że metaforycznie masz
    >> swoje A, swoje Z i jakieś literki pomiędzy, ale nie wiesz ile w ogóle
    >> jest literek w alfabecie.
    >
    > To do cholery jak przygotujesz testy jak nie wiesz co masz testowac?

    Co to znaczy "nie wiesz, co testować"? Jeśli zrobisz dokładną analizę,
    to masz w tej analizie absolutnie kompletny zestaw testów, który
    gwarantuje jakość produkcyjną? Włącznie z testowaniem na problemy
    wynikające ze współbieżności, performance test itd.?

    W metodologiach agile każda iteracja jest planowana, więc na początku
    każdej iteracji wiesz, co w tej iteracji jest do zrobienia, i na każdą z
    tych rzeczy piszesz testy. Problem masz taki, że przecież w ten sposób
    nie wyłapiesz przecież wszystkich potencjalnych problemów wynikających
    np. ze współbieżności, performance, niespodziewanych zachowań interfejsu
    użytkownika itd. Część takich rzeczy programiści wyłapią jako
    potencjalne ryzyko, ale jednak na ogół bugi biorą się z tego, czego
    programiści nie przewidzą. Dlatego w danym momencie masz zestaw testów A
    B C D E F G H I J K L M N O P Q R S T U V W X Y Z, które zgodnie z twoją
    wiedzą testują całość funkcjonalności programu, ale jednocześnie masz
    exploratory testing, który wykrywa ci buga, spowodowanego np. przez race
    condition, na którego piszesz test Ń, który zostaje dopisany doo skryptu
    testującego i jest testowany w każdej kolejnej iteracji.

    >> W dodatku alfabet zmienia się w miarę rozwoju
    >> oprogramowania. Dlatego też wszystkie literki, o których wiesz,
    >> testujesz automatycznie za każdą iteracją, a może nawet codziennie,
    >
    > Patrz wyzej - nie da sie tego porzadnie zrobic "codziennie".

    Dlaczego? W wielu produktach da się je przetestować automatem w 15
    godzin lub mniej. Biorąc pod uwagę, że testy mogą być równolegle
    wykonywane na np. ośmiu maszynach, a testy mogą de facto lecieć przez
    22-23 godziny na dobę, to zbiór produktów dających się testować w ten
    sposób jest naprawdę szeroki. A jeśli już naprawdę się nie da, to zawsze
    można zrobić fall back do warianty "za każdą iteracją".

    >> Jak się robi porządnie, to się ma przeznaczone zasoby tylko do
    >> testowania danego produktu. Jak go nie testujesz, to te zasoby leżą
    >> odłogiem i kosztują z grubsza tyle samo.
    >
    > Normalnie (nie "agile") tobi sie to tak, ze masz oddzielny zespol tzw. QA
    > (moze byc nawet outsourcing), ktory obsluguje ci kilka produktow. Taki QA
    > nie kiwnie nawet palcem, jak mu nie dasz analizy wymagan, bo niby na jakiej
    > podstawie ma przygotowac testy?

    Tu znowu raczej niż się zastanawiać co jest a co nie jest "agile",
    odniosę się do konkretnej metodologi Shore&Warden. Otóż w niej zakłada
    się, że zespół nie pracuje z oddzielnym działem QA, tylko sam robi
    testowanie swojego produktu. Ma w związku z tym swoich testerów i swoje
    zasoby do testowania.

    Jeśli chodzi o to, jak jest "normalnie" to też bywa różnie: ja
    pracowałem w dużej firmie (spółka akcyjna z indeksu FTSE100), wcale nie
    w procesie agile, ale właśnie na takiej zasadzie, że mieliśmy testowanie
    tylko wewnętrzne, nie było żadnego działu QA. Działało to bardzo dobrze.

    >> Agile proponuje dokładniejszą specyfikację uzyskać w ten sposób, że
    >> programista jak nie wie co dalej robić, to rozmawia z customerem (lub
    >> analitykiem) i on mu to specyfikuje.
    >
    > O! pojawia sie analityk... Czy to nadal jeszcze "agile"?

    Czemu nie? W Shore&Warden jest opisana rola analityka w zespole. Poza
    tym to jest "lub analityk", czy faktycznie analityk jest potrzebny
    zależy od projektu.

    > A skad "analityk" wie, co chce klient? Pewnie robi "analize"? To ja zapytam
    > - kiedy robi te analize? Bo przeciez musi byc "on site" z programistami,
    > zeby mogl z nimi "rozmawiac" - znaczy "analize" wykonal wczesniej. Cos mi
    > pachnie "kaskada".

    No więc ja się nie dziwię, że masz takie dziwne wyobrażenie, skoro nie
    wyłapałeś i nie doczytałeś kluczowej sprawy: klient siedzi on site z
    programistami i ewentualnymi analitykami. Konkretnie masz rolę tzw.
    "customer representative" lub "on-site customer" (w żargonie XP skrótowo
    nazywanego customerem), która z definicji jest osobą rozumiejącą i
    mogącą podejmować decyzje co do tego, jak program ma być używany. W
    przypadku programu robionego na zewnętrzne zlecenie powinien to
    zazwyczaj być faktycznie przedstawiciel zleceniodawcy (ewentualnie ktoś,
    komu zleceniodawca w tej kwestii ufa).

    >> Jako metodę dokładniejszej
    >> weryfikacji proponuje się pokazanie customerowi działającego prototypu i
    >> spytanie czy właśnie o to chodziło.
    >
    > To "prototypu", czy tez "produktu"? Bo jezeli "prototypu" to po ch... tracic
    > pieniadze na np. "pair programming" zeby go przygotowac?

    Żeby potem nie tracić pieniędzy na przepisywanie jeszcze raz programu,
    który działa i robi to, co trzeba.

    Jeśli spodziewasz się, że szansa na to, że jakakolwiek znacząca część
    funkcjonalności prototypu trafi do produkcji, to można pisać go jako
    throwaway code, który nie musi być programowany parami.

    >> Przecież to, co jest w specyfikacji też potencjalnie nie jest nikomu
    >> potrzebne. Na szybko z co najmniej dwóch powodów:
    >> a) było potrzebne w momencie tworzenia specyfikacji, ale już nie jest
    >> b) nigdy nie było potrzebne, ale analityk popełnił błąd i wpisał, bo
    >> uznał że potrzebne.
    >
    > Obydwa przypadki sprowadzaja sie do tego, ze popelniono blad w trakcie
    > analizy wymagan. Oczywiscie - zdarza sie. Ale to nie jest tak, ze skoro
    > analiza wymagan jest trudna, to po prostu nie robmy analizy wymagan, za to
    > "robmy produkt i zobaczymy czy bedzie dobry" (co jak rozumiem proponuje
    > "agile").

    Czemu nie? Jeśli z powodu błędów korzyści z analizy nie równoważą
    kosztów, to nie robisz analizy.

    >> W agile masz tę zaletę, że najdalej na początku danej iteracji, w której
    >> to coś jest implementowane, customer potwierdził że tak, na pewno jest
    >> potrzebne, a co więcej jest to jedna z najważniejszych rzeczy, jakie
    >> zostały do zrobienia.
    >
    > I projekt trwa bez konca...

    Co to znaczy "bez końca"? Jeśli to, że po otrzymaniu wersji 1.0
    zleceniodawca płaci i zamawia 1.1, 1.2 etc. ad infinitum, to super.

    Jeśli masz sytuację, że customer uniemożliwia dostarczenie nawet MMF
    przez ciągłe zmiany wymagań na kompletnie odmienne, to jest pewien
    problem. Ale też nie jest to problem nie do przejścia: tutaj działa to,
    że masz customera on-site i feedback loop: w pierwszej kolejności pytasz
    "no dobra, tydzień temu mówiłeś tak, zrobiliśmy z tego story, dałeś to
    jako priorytet, a teraz mówisz, że jest śmak, o co chodzi?"

    No i może być faktycznie tak, że nie jest to jakiś celowy sabotaż, tylko
    np. albo się pomylił, albo nastąpiła jakaś obiektywna zmiana
    okoliczności (powiedzmy dostawca zewnętrznych danych zmienił definicję
    jednego z pól). Ale w takim przypadku idziesz do zleceniodawcy i
    tłumaczysz, że są obiektywne trudności i trzeba wydłużyć termin i
    zapłacić ekstra.

    Zleceniodawca ma zaufanie, że faktycznie tak jest, bo:
    * customer mu to potwierdza,
    * customer jest jego pracownikiem,
    * customer został zatrudniony na podstawie tego, że rozumie wymagania i
    * że zleceniodawca mu ufa, ponadto
    * customer rozmawia z innymi specjalistami w swojej firmie i oni
    potwierdzają, że faktycznie tak jest.

    Oczywiście jeśli wychodzi, że customer jest niekompetentny, to też to
    zgłaszasz stakeholderom i wtedy jest to oczywista wina zleceniodawcy, że
    wyasygnował niekompetentną osobę jako swojego przedstawiciela.

    Jeśli customer jest niekompetentny a zleceniodawca nie chce tego przyjąć
    do wiadomości, lub np. zleceniodawca każe customerowi celowo sabotować
    projekt (bo np. w międzyczasie uznał, że nie jest mu potrzebny, a nie
    chce ponosić konsekwencji rozwiązania kontraktu), to masz sytuację
    utraty zaufania. Nie jest to koniec świata, ale jest to zasadniczo
    koniec agile, bo agile (a przynajmniej Shore&Warden) zakłada zaufanie.
    Prawdopodobnie i tak w tym momencie twoim priorytetem przestaje być
    maksymalizacja dostarczonej wartości dla zleceniodawcy i długotrwała
    współpraca, a raczej powinieneś przyjąć strategię jak najszybszego
    możliwie bezstratnego zakończenia współpracy - poczynając od propozycji
    rozwiązania umowy za porozumieniem stron, a kończąc na wypełnieniu
    warunków umowy przez dostarczenie bezużytecznego produktu i/lub
    rozprawie sądowej. Tak czy inaczej, w tym przypadku pomaga ci to, że
    kontrakt jest sformułowany tak, że możesz go wypełnić w najgorszym
    przypadku dostarczając wersję 1.0 z MMF, po czym oczywiście się nie
    zgadzasz na żadne kolejne wersje, a w lepszym przypadku (time&materials)
    możesz z niego wyjść na zasadzie ("my przestajemy robić, wy nam
    przestajecie płacić, wypowiedzenie z dwumiesięcznym wyprzedzeniem".

    Przy czym tak w ogóle to jest czarny scenariusz, bo jednak zazwyczaj jak
    ktoś zamawia program, to dlatego, że go potrzebuje, a jeśli celowo bądź
    przez niekompetencję opóźnia pierwszy release w nieskończoność, to nigdy
    go przecież nie dostanie.

    > Ew. konczy jak c2 (tak, tak - pierwszy projekt
    > XP) - czyli przychodzi ktos z gory i go brutalnie przerywa.

    C3. I C3 wyprodukował jednak działającą wersję. Nie będę wnikał, czy to,
    co można przeczytać o przyczynach politycznych jest prawdą czy wymówką,
    natomiast zwrócę uwagę, że C3 to był "prototyp" XP. Bracia Wright też w
    swoim czasie rozbili trochę samolotów.

    Jeśli chodzi o udane wdrożenia XP, to znane jest studium przypadku JP
    Morgan Chase.

    >> Co to znaczy "stają się"? Że są w takiej postaci releasowane? Nikt tego
    >> nie postuluje. Staje się, w sensie że jest wykorzystywany do tworzenia
    >> produktu, owszem. Przy wszystkich krytykach do agilee, że to czy tamto
    >> jest wyrzucaniem pieniędzy, to pisanie działającego programu, który robi
    >> to co trzeba, po to, żeby go wyrzucić i napisać to samo od nowa też jest
    >> jednak marnowaniem pieniędzy.
    >>
    >
    > Jest roznica, miedzy "prototypem" i "dzialajacym programem". Wytworzenie
    > "prototypu" jest nieporownywalnie _tansze_ niz wytworzenie "dzialajacego
    > programu".

    Zgadza się. Ale niekoniecznie dlatego, że wytworzenie danego kawałka
    funkcjonalności jest nieporównywalnie tańsze, przede wszystkim dlatego,
    że prototyp nie ma pełnej funkcjonalności i dlatego, że nie jest tak
    skrupulatnie testowany. Pisanie czytelnego, przejrzystego kodu zgodnie z
    coding standards nie jest samo w sobie znacząco droższe od pisanie
    spaghetti code.

    >> Tu jest trochę fałszywe założenie, że taniej jest pisać kod niższej
    >> jakości od kodu wysokiej jakości.
    >
    > Drozej jest?

    Mniej więcej tak samo drogo.

    >> Może być tańsze tylko o tyle, że
    >> możesz do tego wykorzystywać słabszych programistów którym gorzej
    >> płacisz,
    >
    > Albo, ze dobry programista robi "na kolanie" prototyp w 15 min, zeby go
    > pokazac i omowic, po czym wyrzucic.

    Może tak być, ale dobry programista nawet pisząc na kolanie tworzy
    porządny kod bez bugów.

    >> Poza tym do wyrównywania (w górę) poziomu kodu masz takie praktyki jak
    >> coding standards, collective code ownership i pair programming (lub code
    >> review).
    >
    > Ale po co je stosowac do czegos, co i tak zaraz przepiszemy od nowa?

    Żeby nie tracić czasu i pieniędzy na pisanie od nowa. I jeszcze raz:
    oczywiście, czasem lepiej nie stosować i potem ewentualnie pisać od
    nowa. XP obejmuje też takie przypadki.

    >>> jezeli np. na poczatku podejmiemy decyzje o tworzeniu tego oprogramowania
    >>> dajmy na to w C# (bo zespol uznal, ze tak najlepiej) a potem okaze sie,
    >>> ze klient zapomnial nam powiedziec o tym, ze to ma dzialac na Solarisie
    >>> (z jakichs tam powodow), to jak niby mamy zrobic "refaktoring" nie
    >>> przepisujac tego oprogramowania w calosci???
    [...]
    > To byl tylko przyklad _jednej_ decyzji, ktora musisz podjac. Problem nie
    > lezy w tym, ze kazdy taki problem jest trudny, tylko ze tych problemow jest
    > duzo.

    Nie takich problemów (że refactoring jest praktycznie niemożliwy) jest
    bardzo mało. Spróbuj wymyśleć jakiś inny przykład, niż zmiana języka.

    (Niech zgadnę: zleceniodawca powiedział, że chce program do księgowości
    a potem się okazuje, że zapomniał dodać, że właściwie to on ma sterować
    promem kosmicznym)

    > Bo takich decyzji projektowych, ktore trzeba podjac na poczatku (zeby w
    > ogole mozna bylo zaczac programowac) i ktorych odwrocenie bedzie pozniej
    > kosztowne jest cale multum. Czynnikow, ktore je determinuja tez jest cale
    > multum.
    > Wiara w to, ze mozna je wszystkie podjac na podstawie lub w czasie
    > polgodzinnej "rozmowy" z "customerem" jest doprawdy naiwna.

    Przecież nie półgodzinnej rozmowy, tylko ciągłej pracy z. I w sumie nie
    tak łatwo o przykład takiej decyzji, której odwrócenie np. po tygodniu
    jest "bardzo kosztowne". Jedno, co mi przychodzi na myśl, to konieczność
    zakupu już na początku jakichś bardzo drogich narzędzi czy systemów,
    mówimy o wydatku rzędu setek tysięcy dolarów (dla - przypominam -
    najwyżej kilkunastoosobowego zespołu). Bo wyrzucenie nawet całego kodu,
    który w pierwszym tygodniu napiszą programiści nie jest "kosztowne", a
    informacje w tym czasie uzyskane będą cenniejsze niż wiedza z analizy i
    zbierania wymagań robionych przez miesiąc.

Podziel się

Poleć ten post znajomemu poleć

Wydrukuj ten post drukuj


Następne wpisy z tego wątku

Najnowsze wątki z tej grupy


Najnowsze wątki

Szukaj w grupach

Eksperci egospodarka.pl

1 1 1

Wpisz nazwę miasta, dla którego chcesz znaleźć jednostkę ZUS.

Wzory dokumentów

Bezpłatne wzory dokumentów i formularzy.
Wyszukaj i pobierz za darmo: