eGospodarka.pl
eGospodarka.pl poleca

eGospodarka.plGrupypl.comp.programmingTworzenie Programów Nieuprzywilejowanych Opartych Na WtyczkachTworzenie Programów Nieuprzywilejowanych Opartych Na Wtyczkach
  • Data: 2024-08-05 21:26:49
    Temat: Tworzenie Programów Nieuprzywilejowanych Opartych Na Wtyczkach
    Od: Jacek Marcin Jaworski <j...@a...pl> szukaj wiadomości tego autora
    [ pokaż wszystkie nagłówki ]

    Tworzenie Programów Nieuprzywilejowanych Opartych Na Wtyczkach

    autor:

    [1]Jacek Marcin Jaworski

    pseudonim:

    [2]Energo Koder Atlant

    utworzono:

    2023-01-27

    miejsce:

    Pruszcz Gd.

    wersja: 2308 z dnia:

    2024-08-05

    sys. op.:

    Linux Triskel, Kubuntu

    prog.:

    Libre Office

    Spis treści

    [3]Wprowadzenie 2

    [4]1 Narzędzia programisty 2

    [5]2 Standardowa proc. tworzenia prog. opartego na wtyczkach 3

    [6]3 Analiza zstępująca (funkcjonalna) 3

    [7]4 Analiza wstępująca (techniczna) 4

    [8]5 Arch. prog. 4

    [9]5.1 Arch. typowego prog. nieuprzywilejowanego opartego na
    wtyczkach 4

    [10]5.2 Arch. kat. ze źródłami 5

    [11]5.2.1 Model asemblerowy i model C 5

    [12]5.2.2 Główne kat. z kodami żródłowymi 5

    [13]5.2.3 Kat. bibl. narzędziowej 5

    [14]5.2.4 Kat. prog. 5

    [15]5.2.5 Kat. ppr 5

    [16]5.2.6 Kat. wtyczki 6

    [17]5.2.7 Kat. testy-aut 6

    [18]5.3 Arch. pliku z kodem 6

    [19]5.3.1 Sekwencyjna 6

    [20]5.3.2 Funkcyjna 7

    [21]5.3.3 Proceduralna 7

    [22]5.3.4 Obiekty w proceduralnym j. C 8

    [23]5.3.5 Obiektowa 9

    [24]5.4 Wtyczki w proj. 10

    [25]5.5 Prog. konsolowe wywoływane przez prog. 10

    [26]5.6 Przenośność programu 10

    [27]6 Algorytmy w prog. 10

    [28]6.1 Logika w programie 10

    [29]6.2 Kontrola stanu Logiki 11

    [30]6.3 Spr. niezmienników 11

    [31]6.4 Obsługa wyjątków w Logice 11

    [32]6.5 Sposób interakcji z prog. 12

    [33]6.6 Sposób przetwarzania danych w prog. 12

    [34]6.6.1 Przetwarzanie strumieniowe danych tekstowych 12

    [35]6.6.2 Praca z plikami 13

    [36]6.6.3 Przetwarzanie bazodanowe 13

    [37]6.6.4 Przetwarzanie sieciowe 13

    [38]6.6.5 Przetwarzanie wielowątkowe 13

    [39]6.7 Sposób wykrywania błędów 13

    [40]7 Styl prog. 14

    [41]7.1 Izolacja proj. od świata zewnętrznego 14

    [42]7.2 Projektowanie i kodowanie wg optymistycznego
    scenariusza 14

    [43]7.3 Sposób prezentacji wyników 15

    [44]7.4 Sposób zgłaszania błędów 15

    [45]7.4.1 Wartość zwracana z f. 16

    [46]7.4.2 errno 16

    [47]7.4.3 Wyjątki 17

    [48]7.5 Komentarze 18

    [49]Dodatek 1: Mały sabotaż 19

    [50]Dodatek 2. Wzorce proj. - jak na nie patrzeć? 19

    [51]Wzorce 19

    [52]Antywzorce 20

    [53]Dodatek 2: Zasady używania baz danych SQL 20

    Wprowadzenie

    W informie najważniejsze są 3 sprawy: arch., algorytmy i styl
    programów. Reszta to blichtr.

    W programowaniu najważniejsze są 2 sprawy: programy i skrypty:

    Programy są to języki kompilowane: Asembler, Fortran, C, C++,
    D, Paskal, Delfi, Rust.

    Skrypty są to języki interpretowane: Pyton, Jawa, Jawa Skrypt,
    PHP, C#, Perl. Specjalną odmianą skryptów są powłoki: Bash, sh,
    zsh, csh, command.com, PowerShell.

    Prog. koduję gdy mają one krytyczne znaczenie (albo dla mnie,
    albo dla klienta).

    Skrypty piszę, gdy nie ma znaczenia ani szybkość wykonania ani
    jakość kodu. W praktyce skrypty piszę by ułatwić sobie życie i
    przyspieszyć codzienną pracę.

    Są tylko 3 rodzaje programów: prog. główny, programy
    uprzywilejowane i programy nieuprzywilejowane:

    Program główny to rdzeń każdego systemu operacyjnego: zarządza
    on procesami, wątkami, pamięcią i urządzeniami. Robi to za
    pomocą sterowników;

    Programy uprzywilejowane to głównie demony: demony dzieją się
    na sieciowe i lokalne.

    Demony sieciowe świadczą usługi sieciowe. Ale nie oznacza to,
    że tylko zdalne komputery mogą z nich korzystać. Wiele systemów
    składa się z demonów sieciowych współdziałających z programem
    nieuprzywilejowanym na tym samym komputerze.

    Demony lokalne sterują określonymi elementami systemu
    operacyjnego. Demony lokalne to faktycznie wydzielone elementy,
    które powinny być w programie głównym, ale są dla
    bezpieczeństwa lub po prostu dla wygody poza nim;

    Program główny i programy uprzywilejowane działają w trybie
    uprzywilejowanym, czyli mają pełne uprawnienia (mówi się, że
    one z komputerem mogą zrobić wszystko).

    Programy nieuprzywilejowane, to programy używane przez
    użytkownika: są to polecenia konsoli, oraz programy
    interaktywne działające w trybie tekstowym lub graficznym.

    Programy nieuprzywilejowane działają bez specjalnych uprawnień.
    Jednak nawet te programy mogą być szkodliwe bo standardowo mają
    pełen dostęp do katalogu domowego oraz standardowo mają pełen
    dostęp do globalnej sieci Internet. Skutek tego może być taki,
    że ściągnięty z sieci programik po uruchomieniu bez
    najmniejszego problemu może wesoło wysyłać w świat listy plików
    lub nawet całe pliki wybrane przez zdalnego operatora. Można
    się przed tym zabezpieczyć własnoręcznie konfigurując
    piaskownicę i zaporę sieciową.

    W tej publikacji będę omawiać tylko architekturę programów
    nieuprzywilejowanych. Bo od programowania takich "zwykłych
    programów" należy zaczynać karierę programisty.

    Tak jak nie ma jednego przepisu na świetny budynek, tak nie ma
    jednego przepisu na doskonały prog.. Arch. programów
    komputerowych to wieloaspektowy temat, który postaram się tu
    zarysować.

    ZASADY PODAWANE W SZKOLE CZY NA UCZELNI UMOŻLIWIAJĄ ZALICZENIA
    PRZEDMIOTÓW, ALE NIE ZASTĄPIĄ MYŚLENIA W ŻYCIU, BO SĄ JEDYNIE
    WSKAZÓWKĄ JAK RADZIĆ SOBIE Z PROBLEMAMI.

    Podobnie należy myśleć o zasadach tutaj opisanych.

    1Narzędzia programisty

    Podst. narzędzia programisty, to:
    1. Dok.: plan proj., dok. funkcjonalna, dok. techniczna,
    sprawozdanie z wykonania prototypu, dok. użytych prog. i
    bibl., sprawozdanie z podsumowaniem wykonania
    wcześniejszych proj. (oraz wcześniejszych wydań w ramach
    bieżącego proj.);
    2. Polecenia konsolowe do przetwarzania tekstu oparte na wyr.
    reg. to podstawa skryptów jakie na co dzień trzeba tworzyć
    by pchać proj. na przód.
    3. Kod: prog., bibl. proj., firmowe i kupione, wtyczki,
    konwertery, testy aut.;
    4. Pliki z zasobami prog., dane testowe;
    5. Edytor programisty (ulubiony);
    6. Debuger (do uruchamiania ze śledzeniem);
    7. Profiler (do wykrywania wycieków pam. i do wykrywania
    nadmiarowych wywołań f.).

    Zasada Inżynierska: Należy dostosowywać sobie narzędzia z
    dostępnym kodem źródłowym.

    Zasada Inżynierska: Wydajność zwiększa się przez automatyzację
    a nie przez pośpiech.

    2Standardowa proc. tworzenia prog. opartego na wtyczkach

    Przygotowania:
    1. Plan realizacji proj.;
    2. Szkolenie zespołu[54]^1 (ze sprawozdaniem);
    3. Dok. funkcjonalna proj. (wynik analizy zstępującej);
    4. Dok. techniczna (wynik analizy wstępującej);
    5. Prototyp (ze sprawozdaniem);
    Prototyp to luksus jaki powoduje skok świadomościowy ze
    sfery domniemywań do sfery wiedzy realnej na temat problemu
    i proj.
    6. Korekta dok.

    Realizacja:
    7. Kodowanie konwerterów danych i ich testów aut.;
    8. Kodowanie bibl. proj. i ich testów aut.;
    9. Kodowanie logiki prog.;
    10. Kodowanie okien prog. i ich testy manualne;
    11. Kodowanie PPR (Publiczny Pakiet Rozwojowy, w j. ang. SDK):
    Powinien on się składać wył. z kl. abs.
    Problemem wtedy są kl. pochodne od QObject (bo one nie mogą
    być abs.). Jest na to proste obejście: wystarczy utworzyć
    kl. pochodną od QObject (lub jej pochodnej) i umieścić w
    niej wymagane dodatkowe f. wirt. - ale puste. Natomiast ich
    implementacja będzie dopiero w kol. kl. dziedziczącej, ale
    umieszczonej nie w PPR, tylko w kodzie prog. (o. tych kl.
    będą param. wtyczek z interfejsem z PPR).
    12. Kodowanie wtyczek z interfejsem PPR;
    13. Pods. proj.

    3Analiza zstępująca (funkcjonalna)

    Analiza zstępująca mówi co ma robić prog.:
    1. Na jakim sprzęcie i pod jakim sys. op. będzie pracował;
    2. Jakie dane będzie przyjmował;
    3. Jakie dane będzie zwracał;
    4. Z czym i jak będzie współpracował;
    5. jakie będą zasady jego poprawnego użycia;
    6. Jakie będą jego najważniejsze opcje;
    7. Jakie będą jego najważniejsze funkcje.
    8. Diagramy przypadków użycia (dostarcza je klient[55]^2);
    9. Diagramy sekwencji (dostarcza je klient);
    10. Makiety wszystkich okien programu z opisem działania
    (dostarcza je projektant).

    Analiza zstępująca nic nie mówi o algorytmach ani implementacji
    programu.

    Wynikiem analizy zstępującej jest "dokumentacja funkcjonalna".

    4Analiza wstępująca (techniczna)

    Analiza wstępująca mówi jak wewnętrznie ma działać prog.:
    1. Dane wej. (formaty i położenie);
    2. Alg. przetwarzania danych w prog.;
    3. Dane wyj. (formaty i położenie);
    4. Definiuje jakie będą użyte protokoły komunikacyjne;
    5. Definiuje jakie mają być wtyczki (API i położenie);
    6. Definiuje wymagane kl. narzędziowe;
    7. Definiuje kl. logiki prog. i ich maszyny stanów;
    8. Wskazuje jakie mają być użyte wzorce proj.;
    9. Definiuje jak będą wyglądać testy;
    10. Definiuje jaki ma być poziom bezp. i jak go osiągnąć.

    Wynikiem analizy wstępującej jest "dokumentacja techniczna".

    5Arch. prog.

    5.1Arch. typowego prog. nieuprzywilejowanego opartego na wtyczkach

    Aplikacja zawiera kl. narzędzi, logiki i okien, natomiast bibl.
    zawierają jedynie dodatkowe kl. narzędzi. Ta arch. została
    wymyślona przeze mnie i nazwałem ją wzorcem
    Narzędzia-Logika-Okna. Tym wzorcem zastępuję bardziej
    specjalistyczny wzorzec Model-Widok-Kontroler (w j. ang. MVC).

    Na obrazku są strzałki jednokierunkowe. Obrazuje to zasadę, że
    kod z wyższych warstw wywołuje kod z warstw niższych. Natomiast
    warstwy niższe nie wywołują kodu warstw wyższych. Jednak czasem
    warstwa wyższa musi zaczekać na coś co ma dostarczyć warstwa
    niższa. W tym celu korzysta się z mechanizmu f. zwrotnej (w j.
    ang. callback). Polega to na tym, że po prostu jawnie podaje
    się wsk. do f. jaki ma być wywołany gdy w warstwie niższej
    pojawią się dane.

    F. zwrotna będzie wywoływana przez inny wątek. Dlatego w
    wywołaniach zwrotnych konieczna jest sych. muteksami lub
    semaforami.

    W proj. mamy 3 rodzaje bibl.: dostawcy, firmowe i proj.:
    1. Bibl. dostawcy: to bibl. jakie dostajemy z sys. op. oraz
    jakie kupuje firma;

    Jakość bibl. dostawcy są odbiciem jego kultury technicznej,
    ekonomicznej i osobistej jego kraju.
    2. Bibl. firmowe: to bibl. jakie zawierają narzędzia własne
    firmy.

    Jakość bibl. firmowych jest odbiciem kultury technicznej,
    ekonomicznej i osobistej twojej firmy (są to narzędzia własne
    firmy).
    Bibl. firmowe mają też separować proj. od świata zewnętrznego.
    Oto powody:

    1. Rozwijanie bibl. firmowych jest konieczne z powodu
    konieczności używania darmowych bibl. czyli gratisów z
    SZAP. A gratis ma za zadanie psuć umysł naiwnego pasożyta.
    2. Otoczenie firmy jest niestabilne - nawet gdy firma płaci za
    narzędzia programisty. Dlatego te bibl. przede wszystkim
    przezywają wszystkie używane w firmie typy danych w celu
    ew. ich podmiany, rozszerzania lub przedefiniowania;
    3. Przestarzałe zew. bibl. w j. C trzeba opakowywać kl. C++
    tak by można było ich normalnie używać.



    Bibl. proj.: Te bibl. wyodrębnia się by w przyszłości przesunąć
    je do bibl. firmowych.

    5.2Arch. kat. ze źródłami

    5.2.1Model asemblerowy i model C

    Obecnie w j. prog. stosuje się 2 modele org. kodu w kat. proj.:
    1. Model asemblerowy występuje w j. Asembler i D (oraz w
    skryptach, ale nimi tu się nie zajmuję). Jego cechą
    charakterystyczną jest jedność i nierozerwalność deklaracji
    f. i jej definicji. To powoduje, że konieczne w nim jest
    udogodnienie w postaci widoczności f. w całym pliku. Z tego
    powodu:

    W modelu asemblerowym wywołanie f. może poprzedzić jej
    deklarację-definicję. Mimo, że jest to niedopuszczalne z
    logicznego p. widzenia.
    2. Model C: Występuje tylko w j. C i C++. Jego cechą jest
    możliwość rozdzielenia deklaracji i definicji f.: można
    wydzielać pliki nagłówkowe z deklaracjami f. i pliki
    źródłowe z definicjami f. Użycie f. nie może poprzedzić jej
    deklaracji.

    Powrót w j. D do modelu asemblerowego oznacza brak możliwości
    stosowania tego j. do dużych proj. Wynika to po prostu z braku
    plików nagłówkowych, co oznacza brak możliwości szybkiego
    wglądu w zawartość plików źródłowych.

    W obu sposobach organizacji kodu istnieje możliwość tworzenia
    f. wstawianych. W j. Asembler, C i C++ można je wstawiać
    makrami. Dodatkowo w C, C++ i D wybrane f. można oznaczać jako
    inline (jednak mimo takiej dyrektywy kompilator wcale nie musi
    wstawić kodu tej f. w miejscu wywołania - powody tego typu
    zachowania są nieznane).

    5.2.2Główne kat. z kodami żródłowymi

    1. Kod prog.;
    2. PPR: ,,Pub. Pakiet Rozwojowy" (w j. ang. SDK);
    3. Kod wtyczek prog.;
    4. Skrypty;
    5. Testy aut.

    5.2.3Kat. bibl. narzędziowej

    NazwaBibl/CMakeLists.txt
    NazwaBibl/h++/NazwaBibl
    NazwaBibl/c++
    NazwaBibl/zasoby

    5.2.4Kat. prog.

    NazwaProg/CMakeLists.txt
    NazwaProg/Logika
    NazwaProg/Narzedzia
    NazwaProg/Okna
    Zasoby

    5.2.5Kat. ppr

    ppr/wspolne/kontrolki
    ppr/wspolne/logika
    ppr/wspolne/narzedzia
    ppr/wspolne/wspolne.h++
    ppr/wtyczki/InterAbsWtyczki1.h++

    5.2.6Kat. wtyczki

    wtyczki/NazwaWtyczki1/CmakeLists.txt
    wtyczki/NazwaWtyczki1/c++
    wtyczki/NazwaWtyczki1/zasoby

    5.2.7Kat. testy-aut

    testy-aut/c++
    testy-aut/CmakeLists.txt
    testy-aut/dane-testowe

    5.3Arch. pliku z kodem

    Nowsze j. prog. zwykle wprowadzają nowy styl programowania, ale
    często pozwalają też programować w starszych stylach. Jednak w
    starszych j. prog. trudno uzyskać efekty jakie wprowadzono w
    nowych - ogromny wysiłek jaki jest do tego konieczny całkowicie
    niweczy zysk. Dlatego nonsensem jest kodowanie w j. C w stylu
    C++.

    5.3.1Sekwencyjna

    Wszystkie j. prog. umożliwiają programowanie sekwencyjne.

    W arch. sekwencyjnej kolejne linie prog. są wykonywane w
    obrębie jednej f.; po kolei z góry na dół; od pierwszej linii
    do ostatniej. Skoki wykonuje się wyłącznie w obrębie lokalnych
    pętli. Tak zaczyna się nauka programowania.

    Przykład prog.[56]^3 sekwencyjnego w j. Asembler[57]^4:
    ; Obliczenie N-tej wart. ciągu Fibonaciego:
    format ELF64 executable
    entry main

    include 'import64.inc'
    interpreter '/lib64/ld-linux-x86-64.so.2'
    needed 'libc.so.6'
    import printf,exit

    segment readable executable
    main:
    push rcx
    push rdx
    mov rcx, [gIloscIteracji]

    ; Obl. wart. ciągu Fibonaciego:
    ; Param rcx: nr wyr. ciągu.
    ; Wynik rdx: szukana wart.
    cmp rcx, 1
    jne f1
    mov rdx, 1
    ret
    f1:
    cmp rcx, 2
    jne f2
    mov rdx, 2
    ret
    f2:
    mov rax, 1
    mov rbx, 1
    dec rcx
    dec rcx
    f3:
    mov rdx, rax
    add rdx, rbx
    mov rax, rbx
    mov rbx, rdx
    dec rcx
    jnz f3

    ; Drukowanie wyniku:
    ; Param rdx: wrart do wypisania na konsolę.
    xor rax, rax ; Bez xmm.
    mov rdi, gWartCiaguFibonaciego
    mov rsi, [gIloscIteracji]
    call [printf]

    pop rdx
    pop rcx

    ; Wyj. z prog.
    xor rax, rax
    call [exit]

    segment readable writable
    gIloscIteracji: dq 10
    gWartCiaguFibonaciego: db "Wynik po %d iteracjach wynosi: %d.", 10, 0

    5.3.2Funkcyjna

    J. programowania funkcyjnego jest Fortran. Natomiast j. C, C++
    i D umożliwiają m. in. programowanie funkcyjne.

    W arch. funkcyjnej z pierwszej f. wyodrębnia się kolejne. Robi
    się to w celu poprawy przejrzystości oraz w celu ponownego
    użycia f. Prowadzi to do tworzenia bibli. (linkowanych
    statycznie lub dynamicznie).

    Cechą szczególną prog. funkcyjnego jest całkowita eliminacja
    zarządzania stanem programu. Wynika to z faktu, że wszystkie
    dane są przekazywane jako param. f. i przez swoją kompletność
    stanowią one dokładne określenie stanu prog. Ten schemat
    powielają niektóre (lepsze?) protokoły sieciowe, np. HTTP,
    jednak niegdyś popularny FTP bazuje już na jawnych stanach po
    s. serwera i po s. klienta.

    W j. Fortran wszystkie zmienne alokuje się na stosie, czyli nic
    nie można zaalokować na stercie.

    Przykład prog. funkcyjnego w j. D:
    // Obliczenie silni:
    import std.stdio;
    import std.conv;
    import std.format;
    import std.string;
    import core.stdc.stdlib;

    ulong wczytaj()
    {
    writeln("Podaj nr wyrazu ciągu silni jaki chcesz poznać [zatw. klaw.
    Enter]: ");
    return to!long(readln().strip());
    }

    ulong oblicz(ulong n)
    {
    writeln(format("Obliczam %d wyr. silni.", n));
    ulong lWynik = 1;
    for(ulong i = 1; i <= n; ++i)
    lWynik *= i;
    return lWynik;
    }

    void drukuj(ulong s)
    {
    writeln(format("Silnia wynosi: %d", s));
    }

    int main(string[] a)
    {
    wczytaj().oblicz().drukuj();
    return EXIT_SUCCESS;
    }

    5.3.3Proceduralna

    Klasycznymi j. prog. proceduralnego są j. C i Paskal. Program
    podzielony jest na f., a jego dane są ujęte w struktury.
    Struktury najczęściej są oparte o typy proste, czyli o typy
    wbudowane w j. (rzadziej struktury składają się z innych
    struktur).

    Arch. proceduralna jest podobna do funkcyjnej, jednak nie
    przekazuje się do f. kompletu danych definiujących stan prog.
    Zmienna określająca stan prog. może być zm. w strukturze logiki
    aplikacji lub może być po prostu zm. globalną prog.

    Częsty błąd początkujących programistów prog. proceduralnych i
    obiektowych to brak jawnej kontroli stanu w aplikacji (brak zm.
    typu enum z aktualnym stanem), prowadzi to do porażki proj. w
    raz ze wzrostem jego komplikacji prog.

    Jawnej kontroli stanu na pewno wymaga programowanie
    wielowątkowe i sieciowe (wiele spotykanych prot. sieć. jest
    stanowych mimo, że znacznie komplikuje to demony sieciowe i
    programy klienckie).

    Przykład prog. proceduralnego w j. C:
    /* Przykład proceduralnego przetwarzania danych: */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    /* Struktury:::::::::::::::::::::::::::::::::::::*/
    struct Dane
    {
    int cX, cY, cWysokosc;
    double cPaliwo;
    double cLadunek;
    /* Tu zmienne... */
    };

    /* Funkcje:::::::::::::::::::::::::::::::::::::::*/
    struct Dane* wczytajDane()
    {
    struct Dane* d = malloc(sizeof(struct Dane));
    memset(d, 0, sizeof(struct Dane));
    /* Tu kod... */
    return d;
    }
    int sprDane(struct Dane* d)
    {
    /* Tu kod... */ return 0;
    }
    int oblicz(struct Dane* d)
    {
    /* Tu kod... */ return 0;
    }
    int wypiszWyniki(struct Dane* d)
    {
    /* Tu kod... */ return 0;
    }
    int zwolnijPamiecDanych(struct Dane* d)
    {
    free(d);
    return 0;
    }

    int main(int n, char** w)
    {
    /* [Pominięte dla czytelności:] Wczytywanie opcji:
    param. lini komend i plików ustawień. */

    struct Dane* lDane = wczytajDane();
    int lWynik = sprDane(lDane);

    if(!lWynik)
    lWynik = oblicz(lDane);

    if(!lWynik)
    lWynik = wypiszWyniki(lDane);

    zwolnijPamiecDanych(lDane);

    if(lWynik)
    {
    fprintf(stderr, "Wystąpił błąd! Nr: %d\n", lWynik);
    exit(lWynik);
    }

    printf("Wykonanie prawidłowe!\n");
    exit(EXIT_SUCCESS);
    }

    5.3.4Obiekty w proceduralnym j. C

    W tym miejscu można podpowiedzieć kombinatorom trik pozwalający
    uzyskać efekt dziedziczenia struktur w j. C. Pomysł polega na
    kapsułkowaniu rodzica w potomku. Jest to uproszczone
    dziedziczenie jednobazowe. Działa to tylko dzięki temu, że w j.
    C odwzorowanie struktur w pam. komp. Jest dokładnie takie jak w
    deklaracji struktury - nic nie jest dodawane przez kompilator.
    Schemat kapsułkowania w celu uzyskania efektu dziedziczenia
    pokazuje poniższy kod:
    #include <stdio.h>
    #include <stdlib.h>

    struct StrukturaA
    {
    char a;
    int b;
    };

    struct StrukturaB
    {
    struct StrukturaA A;
    long long c;
    };

    int main(int n, char** p)
    {
    struct StrukturaB b;
    b.A.a = 100;
    b.A.b = 101;
    b.c = 102;

    struct StrukturaA* a = (struct StrukturaA*) &b;

    printf("a.a=%d, a.b=%d, b.c=%lld\n", a->a, a->b, b.c);

    return EXIT_SUCCESS;
    }

    Trzeba jednak pamiętać, że C++ pozwala na wielobazowe
    dziedziczenie (można dziedziczyć po wielu kl. jednocześnie) i
    wygodne f. wirt (w strukturach C można je emulować używając
    wsk. do f.).

    Ogólnie można powiedzieć, że wszystko co jest możliwe w j. C++
    jest też możliwe w j. C. Można dodać, że pierwsze kompilatory
    C++ były konwerterami kodu do j. C. Z tego co pamiętam tak było
    w przypadku kompilatora FSF GNU g++.

    5.3.5Obiektowa

    Klasycznymi j. prog. obiektowego są C++ (C z klasami), D i
    Delfi (Paskal z klasami). Arch. obiektowa jest rozwinięciem
    arch. proceduralnej:

    Struktury, zwane klasami mogą mieć f.;

    Kl. czysto abstrakcyjne służą do definiowania interfejsów;

    Kl. można dziedziczyć w celu implementacji interfejsów lub w
    celu rozszerzania lub przedefiniowania zachowania starszych kl.

    W C++ każda kl. powinna być deklarowana w osobnym pliku *.h++ i
    implementowana w osobnym pliku *.c++.

    Uzasadnienie: Czytelność w sensie łatwości analizy proj.

    W odróżnieniu do struktur, kl. często składają się ze zmiennych
    innych klas.

    Przykład prog. obiektowego w j. C++:
    // Przykład obiektowego przetwarzania danych:
    #include <iostream>
    #include <exception>

    // Klasy:::::::::::::::::::::::::::::::::::::::::
    class Dane // Kl. typu struktura.
    {
    public:
    int cX = 0, cY = 0, cWysokosc = 0;
    double cPaliwo = 0;
    double cPredkosc = 0;
    double cLadunek = 0;
    /* Tu zmienne... */

    public:
    void wczytajDane() { /* Tu kod... */ }
    };

    class PrzetwarzanieDanych // Kl. typu usługa.
    {
    public:
    PrzetwarzanieDanych(const Dane& d)
    : cDane(d) {}

    public:
    void przetwarzaj()
    {
    sprDane();
    oblicz();
    wypiszWyniki();
    }

    protected:
    void sprDane() { /* Tu kod... */ }
    void oblicz() { /* Tu kod... */ }
    void wypiszWyniki() { /* Tu kod... */ }

    protected:
    const Dane& cDane;
    };

    // Funkcje:::::::::::::::::::::::::::::::::::::::
    int main(int n, char** w)
    {
    try
    {
    // [Pominięte dla czytelności:] Wczytywanie opcji:
    // param. lini komend i plików ustawień.

    Dane d;
    d.wczytajDane();
    PrzetwarzanieDanych p(d);
    p.przetwarzaj();
    }
    catch(const std::exception& pWyjatek)
    {
    std::cerr << "Wystąpił wyjatek! Treść: "
    << pWyjatek.what() << std::endl;
    exit(EXIT_FAILURE);
    }

    std::cout << "Wykonanie prawidłowe!" << std::endl;
    exit(EXIT_SUCCESS);
    }

    5.4Wtyczki w proj.

    Rodzaje wtyczek w proj.:
    1. Wtyczki w bibl. narzędzi służą do obsługi różnych formatów
    plików lub do obsługi różnych protokołów;
    2. Wtyczki w Logice prog. służą do obsługi różnych alg. pracy.
    Wtyczki te reagują na zmianę stanu w Logice prog.;
    3. Wtyczki w oknach prog. standardowo dodają nowe el.
    interfejsu co wzbogaca funkcjonalność prog.

    Zasady tworzenia wtyczek:
    1. Wtyczki w bibl. narzędziowych działają pojedynczo (np. do
    ładowania pliku w danym formacie). Często te wtyczki są
    ładowane aut. na podstawie kontekstu wynikającego z
    bieżącej pracy prog., np. wybór pliku o określonym roz.;
    2. By użyć równolegle 2 wtyczek z bibl. narzędziowej konieczne
    jest utworzenie dwóch o. kl. narzędziowej. Tak jest np. w
    przypadku otwarcia 2 różnych plików lub w przypadku
    jednoczesnej obsługi 2 różnych protokołów w jednym celu,
    np. do obsługi bazy danych.
    3. Należy jawnie zdefiniować kol. ładowania wtyczek. Ta kol.
    określa też kol. wywołań f. z tych wtyczek. Bez znajomości
    kol. wywołań też nie było by wiadomo co się dzieje w prog.
    Oznacza to, że:
    4. Wewnętrznie wtyczka powinna działać synchronicznie i
    powinna powodować synchroniczne skutki w prog. To
    zabezpiecza przed chaosem który powstaje gdy używa się
    darzeń do oddziaływania na prog.
    Jedyną asynchroniczną akcją jaka jest dopuszczalna w
    przypadku wtyczek to zdarzenie w prog. które po kolei
    wyzwala wtyczki reagujące na tą akcję.

    Można dodać, że coś takiego jak plik json we wtyczkach Qt jest
    kompletnie nie na miejscu, bo on służy do określenia jakie
    wtyczki są wymagane przez daną wtyczkę.

    5.5Prog. konsolowe wywoływane przez prog.

    Są powody by używać poleceń konsolowych w prog.:
    1. Są łatwiejsze do zrozumienia;
    2. Mają lepszą dokumentację;
    3. Są mniej kłopotliwe;
    4. Są bardziej żywotne;
    5. Łatwo je testować;
    6. Są b. szybkie gdy dłużej pracują niż wynosi czas ich
    uruchomienia.

    5.6Przenośność programu

    Przenośność też wpływa na arch. prog. Sam kod C, C++ i D jest w
    pełni przenośny. Jednak trzeba go dopasowywać do różnych bibl.
    w różnych sys. op. Oto wskazówki jak dbać o przenośność kodu:
    1. Izolacja kodu: przezywanie typów i klas oraz tworzenie
    interfejsów obiektowych (wzorzec proj. konwerter) do
    interfejsów w czystym C;
    2. Wydzielanie plików *.uniks.c++, *.mak.c++ i *.okna.c++. W
    tych plikach należy umieszczać wszystkie f. w których
    pojawia się nieprzenośny kod. Natomiast skrypt CMake
    warunkowo włącza te pliki w zależności od sys. op. na jaki
    się kompiluje prog.

    Dyrektywy warunkowe: #ifdef Q_OS_UNIX, #ifdef Q_OS_MACOS i
    #ifdef Q_OS_WIN są zakazane, bo robią jatkę w programie (a tym
    samym również w głowie programisty)!

    6Algorytmy w prog.

    6.1Logika w programie

    Logika w programie musi:
    1. Obsługiwać stany aplikacji i na ich podst. kontr. jej
    działanie;
    2. Ładować i zapisywać opcje (pliki INI, linia komend, okno z
    parametrami);
    3. Ładować wtyczki prog.;
    4. Tworzyć i niszczyć okna prog.;
    5. Spr. niezmienniki;
    6. Łapać i obsługiwać wyjątki (rzucane z logik i z narzędzi).

    6.2Kontrola stanu Logiki

    Typowo stan prog. można kontrolować na 2 sposoby:
    1. Bezstanowo: ma dwie odmiany:
    1. Prog. bazujące na kontekście: w prostych sekwencyjnych
    prog. z niewielką liczbą zmiennych jest oczywiste co
    się dzieje. Dlatego "jakoś to działa" bez specjalnego
    myślenia o stanach prog.
    2. Prog. funkcyjne: Wywoływana f. dostaje wszystkie
    potrzebne dane do jej działania (nie używa ona żadnych
    zmiennych globalnych, ani nie używa zm. kl. do której
    należy).
    2. Stanowo: Maszyna stanów to po prostu enum z jawnie
    zdefiniowaną listą możliwych stanów.

    Zasady projektowania maszyny stanów:
    1. Maszyny stanów należy projektować wspierając się programami
    do wizualizacji takimi jak dot z pakietu Graphiz.
    2. W enum nie należy używać heksów do definiowania podstanów.
    Podstany należy definiować jako odrębne maszyny stanów.

    Czyli zamiast:
    enum Stan { oczekiwanie = 0, start=1, platnosc=2, wysyłanie=4, pytanieOP
    odpis=8, drukowanieParagonu=16, drukowaniePotwierdzenia=32, ...};

    należy zdefiniować 2 stany:
    enum StanTermianala { oczekiwanie, start, platnosc, raportDobowy, ...};

    enum StanPlatnosci { oczekiwanie, start, wysyłanie pytanieOPodpis, druko
    wanieParagonu, drukowaniePotwierdzenia, ...};
    3. Należy zakodować f. stanDoNapisu() w calu wypisywania
    czytelnego komunikatu o próbie nielegalnej zmiany stanu (co
    oznacza awaryjne zamknięcie prog.).

    Działanie f. zmiany stanu:
    1. Spr. czy przejście do innego stanu jest dopuszczalne?
    2. Zmienia stan na nowy;
    3. Reaguje na zmianę stanu;
    4. Informuje wtyczki (i inne obiekty) o zmianie stanu.

    6.3Spr. niezmienników

    Niezmienniki są to warunki jakie muszą zawsze być spełnione aby
    prog. działał prawidłowo. Za prawidłowe działanie prog.
    odpowiada jego logika więc tylko w logice należy spr.
    niezmienniki. Niezmienniki należy spr. na końcu konstruktora i
    na końcu każdej f. publicznej z wyj. destruktora;

    6.4Obsługa wyjątków w Logice

    Wyjątki należy łapać w prog. we wszystkich f. pub. kl. logiki.
    Wynika to z faktu, że tylko na poziomie logiki wiadomo co z
    tymi wyjątkami robić.

    W celu automatyzacji typowych wyjątków które kończą się
    zamknięciem prog. należy zrobić makro w którym:
    1. Komunikat jest zapisywany do pliku z listą błędów (zawsze);
    2. Komunikat jest wypisywany na konsolę (zawsze);
    3. Komunikat jest wyświetlany w okienku gdy jest to prog.
    graf. (należy to spr. makrem takim jak #ifdef QT_GUI_LIB).

    Takie makro automatyzujące łapanie wyjątków możliwe jest
    jedynie w prog. (a nie w bibl). Jest to argument by logiki były
    tylko i wyłącznie w prog. (a nie w bibl.).

    6.5Sposób interakcji z prog.

    Do podstawowych sposobów interakcji z programem należą:
    1. Zadania wsadowe: To typowo seria poleceń połączonych
    rurkami w którym pierwsze polecenie przyjmuje dane wej. a
    ostatnie wypisuje wynik na konsolę. Interakcja z
    użytkownikiem ogranicza się do podania danych wej.,
    wywołania i odbierania wyniku w formie tekstowej.

    Prog. zadań wsadowych jest prostsze niż prog. oparte o f.
    getchar().
    2. F. getchar(): To f. bibl. j. C. Umożliwia ona prostą
    interakcję w terminalu polegającą na wywołaniu w pętli f.
    getchar() w celu pobrania znaku lub linii tekstu od
    użytkownika. Normalnie użycie f. getchar() wstrzymuje pracę
    programu na czas wpisywania znaków.

    Prog. oparte o f. getchar() jest prostsze niż prog. oparte o
    pętlę zdarzeń.
    3. Pętla zdarzeń: Szczególnie często jest stosowana w prog.
    graficznych zwanych "programami użytkowymi". Jej działanie
    polega na monitorowaniu deskryptorów wybranych urządzeń
    (np. klawiatury, myszy, karty sieciowej, itd.) w celu
    pobrania od nich danych. Wtedy następuje propagacja
    zdarzenia po obiektach mogących je odbierać. W przypadku
    myszy będzie to kontrolka interaktywna która zostanie
    aktywowana (dostanie ognisko - w j. ang. focus). W
    przypadku klawiatury będzie to aktywna kontrolka
    interaktywna (mająca ognisko).

    Prog. oparte o pętlę zdarzeń jest prostsze niż prog. oparte o
    pętlę gry.
    4. Pętla gry: W pętli gry oblicza się położenie obiektów na
    ekranie oraz fizykę wirtualnego świata (kolizje i
    uszkodzenia). Mierzy się czas przerysowania każdej klatki w
    celu przewidzenia czasu koniecznego do przerysowania
    kolejnej klatki, a to jest konieczne by wyliczyć stan
    obiektów w grze w kolejnej klatce (o ile się ruszyły
    postacie i jakie zaszły zjawiska fizyczne).
    Pętla gry charakteryzuje systemy czasu rzeczywistego, które
    muszą być przewidywalne w sensie czasowym. Pętla zdarzeń,
    nie spełnia tych wymagań, bo nikt nie gwarantuje kiedy
    zdarzenie będzie obsłużone.

    W źle napisanych grach nawet wejście w menu oznacza 100%
    obciążenia procesora. Dlaczego tak jest? Bo w grach nawet menu
    jest rysowane przez silnik gry. I jak komp. Jest zbyt wolny do
    danej gry, to menu też przycina i są slajdy.

    Mimo, że na sys. Linuks i Windows są biblioteki z pętlą gry to
    jednak te sys. op. nie są systemami czasu rzeczywistego.
    Dlatego gry na nich działają wolno (tak samo zresztą wszystkie
    inne uruchamiane na nich prog.). Wynika to z faktu pisania
    wolnych prog. - to dlatego od pocz. XXIw. komputery działają
    tak samo wolno. Szybkie sys. op. i szybkie prog. koduje się
    SZAP w tzw. tajnych czarnych projektach.

    Częstym błędem początkujących programistów próbujących używać
    pętli zdarzeń lub z pętli gry jest sytuacja gdy kontrolka graf.
    jednocześnie służy do wyświetlania i wprowadzania danych. Wtedy
    jest ona jednocześnie kontrolowana przez użytkownika oraz przez
    urządzenie współpracujące z programem - to jest sytuacja
    konfliktowa. Dlatego zawsze należy używać osobnych kontrolek do
    wyświetlania i do ustawiania wart. (ta druga może pojawiać się
    tylko w razie potrzeby) - mimo, że jest to nieeleganckie, ale
    ma tą zaletę, że działa normalnie.

    6.6Sposób przetwarzania danych w prog.

    Niezależnie od sposobu przetwarzania mam nast. zasady:
    1. Używam grup wyspecjalizowanych prog., które współdziałają
    ze sobą w celu realizacji naszych zad.;
    2. Prog. narzędziowy można napisać od nowa gdy jest to
    opłacalne, czyli: jest to tanie, albo perspektywiczne, albo
    inaczej się nie da, albo jest to po prostu ciekawe;
    3. Nigdy nie tworzymy monstrualnych omnibusów, które mają
    zastąpić wszystkie inne prog. (syndrom Qt Creator).

    6.6.1Przetwarzanie strumieniowe danych tekstowych

    Pobiera się dane z stand. we. (lub z pliku) i przetwarza. Wynik
    zwykle wypisuje się na stand. wy. To przetwarzanie występuje w
    dwóch odmianach:
    1. Linijkowe: przetwarzanie odbywa się linia po linii;
    2. Blokowe: wczytuje się całość i przetwarza na raz (hurtem).
    Ten model stosuje się przy wieloliniowych wyr. reg.
    Oczywiście, w porównaniu do modelu linijkowego, ten model
    ma dużo większe wymagania pamięciowe. Dlatego należy go
    stosować w ostateczności - najlepiej gdy użytkownik sam o
    tym zadecyduje wybierając taką opcję.

    Zwraca uwagę nacisk na przetwarzanie linijkowe w poleceniach
    konsolowych sys. Linuks GNU. Jednak jest to tylko uciążliwa
    konwencja, gdyż są polecenia które nie mogą jej wypełniać - np.
    sort - więc również sed i grep mogłyby mieć opcjonalny tryb
    przetwarzania blokowego (odpowiednik multiline z normalnych
    wyr. reg.).

    6.6.2Praca z plikami

    Są to wszelkiego rodzaju edytory plików w ściśle zdefiniowanych
    formatach: dźwiękowe, obrazy 2D i 3D, filmy, schematy el., lub
    arch. i wiele innych.

    Te prog. charakteryzują się tym, że obrabiany plik w całości
    ładują do pam. RAM.

    6.6.3Przetwarzanie bazodanowe

    Opiera się na pracy z bazą danych za pośrednictwem f.
    przykrywających silnik bazy danych (który w szczególności może
    być silnikiem SQL, który w szczególności może być na serwerze
    sieciowym). W tym modelu ważne jest by nie dać się ogłupić
    specyficznemu silnikowi bazy danych.

    6.6.4Przetwarzanie sieciowe

    Najczęściej dotyczy synchronizacji danych w bazach. Takie
    zadania należy realizować w wątkach wg wzorca proj.
    Producent-Konsument. Zarówno po stronie aplikacji jak i po
    stronie demona sieciowego.

    6.6.5Przetwarzanie wielowątkowe

    Gdy mowa o wątkach zwykle mówi się o muteksach i semafoarach -
    czyli o tym jak ważne jest chronienie współdzielonych zasobów.
    Jednak można unikać muteksów i semaforów kopiując dla każdego
    wątku komplet danych roboczych. Jednak w programowaniu
    wielowątkowym jest jeszcze coś niebezpieczniejszego co podbija
    stopień trudności:

    Podstawą jakiejkolwiek pracy w wątkach jest maszyna stanów jaka
    definiuje nam gdzie jesteśmy, kiedy i gdzie się udamy.

    6.7Sposób wykrywania błędów

    Generalnie można wyróżnić nast. etapy wykrywania błędów przez
    programistę:
    1. Podczas przeglądu kodu:
    Każdą f. zmienianą przez siebie przejrzyj 2x (od góry do
    dołu).
    Każdą f. zmienianą przez innych przejrzyj 3x (od góry do
    dołu).
    Każde włączenie kodu do gałęzi głównej powinny zaakceptować
    co najmniej 2 osoby[58]^5.
    2. Automaty do spr. popr. kodu należy stosować wszędzie gdzie
    to możliwe. Nie musi być to SI!;
    3. W kl. logiki w f. pub. spr. niezmienniki zgodnie z roz.
    Spr. niezmienników;
    4. W kl. przyjmujących dane z zew. stosujemy prog.
    kontraktowe. Są to kl.: opcje (pliki ini, param. linii
    komend), pobieranie danych przez int. uż., wczytywanie
    danych z dysku oraz pobieranie danych z sieci.

    Niezmienniki kl. różnią się od prog. kontraktowego tym, że
    niezmiennik musi być spełniony w każdej sytuacji (w każdej f.
    danej kl.) natomiast testy w prog. kontraktowym związane są
    ściśle z daną f.
    5. Podczas testów jednostkowych:

    TESTY JEDNOSTKOWE DODAJEMY DO PROJ. TAK DŁUGO AŻ WYCZERPIĄ SIĘ
    NAM POMYSŁY NA NOWE TESTY (lub dopóki nie skończy się czas
    przewidziany na testy automatyczne). TYLKO W TEN SPOSÓB MOŻNA
    MIEĆ PEWNOŚĆ, ŻE PROG. DZIAŁA PRAWIDŁOWO.
    6. Testy manualne: Gdy kodujesz coś nowego i już wszystko gra,
    wymyśl i zrób jeszcze 2 dodatkowe testy manualne.
    Dokładne przetestowanie okienek i kontrolek zostawiamy do
    przetestowania testerom manualnym.

    Programowanie sterowane przez testy należy odrzucić gdyż jego
    istotą jest odwrócenie procesu twórczego i zaczęcie go od
    końca, czyli od testów. Jest to kolejna podpucha z SZAP.

    7Styl prog.

    7.1Izolacja proj. od świata zewnętrznego

    W głowie ojca dyrektora wszystko się może zdarzyć, podobnie jak
    wszystko może się zdarzyć w głowach bankierów co płacą za
    rozwój darmowego oprogramowania. Dlatego zgodnie z Ustawą o
    Ochronie Zdrowia Psychicznego trzeba się zabezpieczać przed ich
    sabotażem. W tym celu należy przestrzegać tych zasad:
    1. Zachowanie dyskrecji:
    1. Ze światem zew. komunikuje się wył. kiero. proj.;
    2. Klienta nie wtajemnicza się w szczegóły stosowanych
    rozw. tech.;
    3. O postępach prac wie wyłącznie zespół i klient.
    2. Praca offline:
    1. Zespół pracuje w sieci lokalnej odciętej od Internetu;
    2. W sieci lokalnej jest repo z lustrzanym serwerem
    pakietów używanej dystrybucji (wszyscy muszą pracować
    na tym samym distro w tej samej wer.);
    3. W razie potrzeby zespół wyszukuje info w sieci
    Internet za pomocą sprytnych tel.;
    4. Dane do sieci Internet wysyła się kopiując dane na
    patyki USB.
    3. Izolacja kodu:
    1. Należy ograniczać il. zew. bibl.;
    2. Wszystkie typy proste należy przezwać własnymi. Są ku
    temu co najmniej 2 powody: typy te zmieniają się
    między kompilatorami i sys. op., może zaistnieć kiedyś
    konieczność zmiany typu na inny.
    3. W razie stosowania zew. bibl. kl. należy je przezywać
    aliasami (oczywiście tylko te kl. których się używa).
    Robi się tak by w razie potrzeby można było taki alias
    zastąpić własną, ROZSZERZONĄ klasą bez ruszania jego
    wszystkich wystąpień w kodzie.
    4. W razie stosowanie zew. bibl. f. W j. C należy je
    opakowywać własnymi kl. w celu uproszczenia ich użycia
    i możliwości wymiany danej bibl. w j. C (bez zmiany
    reszty kodu).
    5. Należy się bronić przed j. SQL stosując go w
    minimalnym stopniu: do serializacji obiektów i do ich
    wyszukiwania w bazie. Nie należy stosować SQL do
    żadnego przetwarzania danych, bo to j. skryptowy i ma
    nienormalną składnię (psuje umysł).

    7.2Projektowanie i kodowanie wg optymistycznego scenariusza

    Projektowanie prog. (w dok. funkcjonalnej) należy prowadzić wg
    prostej zasady: realizujemy "optymistyczny scenariusz" w którym
    wszystko się udaje. Proj. wg optymistycznego scenariusza jest
    prosty i naturalny, bo stanowi logiczną ścieżkę od startu do
    zakończenia.

    Jak się okazuje można również kodować wg tej zasady. Ma to tą
    zaletę, że kod jest wiernym odbiciem proj. - czyli jest
    zrozumiały dla każdego kto poznał dok. funkcjonalną.

    Kodowanie wg optymistycznego scenariusza jest trochę niezwykłe:
    należy kodować z minimalną liczbą zagnieżdżeń. Instrukcje "if"
    dotyczą przede wszystkim spr. czy wystąpił błąd. Kluczowe jest
    jednak, żeby nie dodawać bloku "else", bo to "else" to normalny
    przebieg programu - czyli optymistyczny scenariusz. Jednak
    mimo, że takie kodowanie jest logiczne z projektowego p.
    widzenia, to jednak wprowadza konieczność znajomości kontekstu
    w jakim występuje dana linia kodu - może to być utrudnieniem
    dla recenzentów i dla następców.

    Programując optymistyczny scenariusz po wykryciu błędu po
    prostu przerywamy optymistyczny scenariusz. W kodzie oznacza to
    zwrócenie kodu błędu (w j. Asembler i C) lub rzucenie wyjątku
    (w j. C++ i D).

    Aby to wyjaśnić przedstawię 2 f. w j. C jedną zgodną z
    optymistycznym scenariuszem, a drugą w stylu "byle jak" (kod
    skompilowany i uruchomiony ale okrojony dla potrzeb
    publikacji):
    [...]

    enum KodBledu
    {
    eSukces,
    eDaneWe,
    eZuzyciePaliwa,
    eDrukowanie
    };

    [...]

    int funkcjaOptymistycznyScenariusz(struct Dane* d)
    {
    if(sprDaneWe(d) != eSukces)
    {
    fprintf(stderr, "%s", "Błąd danych we.!\n");
    return eDaneWe;
    }

    if(obliczZuzyciePaliwa(d) != eSukces)
    {
    fprintf(stderr, "%s", "Błąd zużycia paliwa!\n");
    return eZuzyciePaliwa;
    }

    if(drukuj(d) != eSukces)
    {
    fprintf(stderr, "%s", "Błąd drukowania!\n");
    return eDrukowanie;
    }

    return eSukces;
    }

    int f_byle_jak(struct Dane* d)
    {
    if(sprDaneWe(d) != eSukces)
    {
    fprintf(stderr, "%s", "Błąd danych we.!\n");
    return eDaneWe;
    }
    else
    {
    if(obliczZuzyciePaliwa(d) != eSukces)
    {
    fprintf(stderr, "%s", "Błąd zużycia paliwa!\n");
    return eZuzyciePaliwa;
    }
    else
    {
    if(drukuj(d) != eSukces)
    {
    fprintf(stderr, "%s", "Błąd drukowania!\n");
    return eDrukowanie;
    }
    else
    return eSukces;
    }
    }
    }

    [...]

    7.3Sposób prezentacji wyników

    Dla porządku wymienię tu sposoby prezentacji danych programu:
    1. Wyjście na konsolę: typowy sposób działania poleceń linii
    komend. Mimo, że jest to bardzo prosta prezentacja wyniku
    działania, to jest ona nadal bardzo użyteczna. Sprawdza się
    ona równie dobrze w codziennej pracy programisty jak i w
    warunkach domowych przy przetwarzaniu plików tekstowych.

    W sys. Windows programy graf. nie mogą pisać na konsolę - przy
    pierwszej próbie pisania na konsolę Windows ubije ten prog. W
    sys. Linuks nie ma tego ograniczenia.
    2. Interfejs tekstowy: ma 2 odmiany: linijkowy i
    pełnoekranowy.
    1. Interfejs linijkowy polega na wyświetleniu pyt. i
    oczekiwania na odp. wpisywaną z klawiatury.
    2. Interfejs pełnoekranowy: działa podobnie do
    interfejsów graficznych. Jednak ma inne skróty
    klawiszowe i inną obsługę myszy. Do pełnoekranowych
    interfejsów tekstowych często stosuje się bibl.
    NCurses (pętla gry).
    3. Interfejs graficzny: To interfejs głównie do klikania. Od
    dziesięcioleci bezkonkurencyjne bibl.[59]^6 do prog.
    interfejsów graf. jest Qt (pętla zdarzeń) i SDL (pętla
    gry).

    7.4Sposób zgłaszania błędów

    Jak się okazuje nawet sposób zwracania błędów przez f. ma wpływ
    na architekturę prog. Są 3 główne sposoby zwracania błędów:

    7.4.1Wartość zwracana z f.

    Po prostu wartość zwracana zawiera kod błędu. W sytuacji gdy
    wszystko gra f. zwraca 0 (tak samo jest w programach powłoki) w
    przeciwnym wypadku zwrócona war. jest kodem błędu.

    Problem ze zwracaniem kodu błędu w wyniku f. jest taki, że nie
    ma możliwości normalnego zwracania jej wyniku (chyba, że przez
    któryś z parametrów tej f.).
    /* Zwracanie kodu błędu przez wart. zw.:*/
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    /* Zmienne globalne::::::::::::::::::::::::::::::*/
    enum KodyBledow
    {
    eOK,
    eDrukowanie
    };

    const char gJakisFajnyTekst[] = { "Jakiś fajny tekst!" };

    /* Funkcje:::::::::::::::::::::::::::::::::::::::*/
    int drukuj(const char* pNapis)
    {
    int cosNieTak = (printf("%s\n", pNapis) == 0);

    if(cosNieTak)
    return eDrukowanie;

    return eOK;
    }

    int main()
    {
    int lKodBledu = drukuj(gJakisFajnyTekst);

    if(lKodBledu)
    {
    fprintf(stderr, "Wystąpił błąd! Kod: %d\n", lKodBledu);
    exit(EXIT_FAILURE);
    }

    printf("Wykonanie prawidłowe!\n");
    exit(EXIT_SUCCESS);
    }

    7.4.2errno

    Cytat: "errno jest definiowana przez standard ISO C jako
    modyfikowalna l-wartość typu int, która nie może zostać jawnie
    zadeklarowana; errno może być makrem. Wartość errno jest
    lokalna w obrębie wątku, jej zmiana w jednym wątku nie wpływa
    na wartość w innym."[60]^7

    Koncepcja enrno jest nietrafiona, bo nie jest znany używany
    zakres wart. errno. Dlatego by unikać konfliktów do nr kodu
    błędu konieczna jest nazwa bibl. która go zgłasza. Wtedy
    programista mógłby sobie ustawić jako nazwę bibl. nazwę swojej
    aplikacji i samodzielnie zdefiniować jej kody błędów.
    /* Przykład użycia errno do zwracania kodów błędów. */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>

    /* Zmienne globalne::::::::::::::::::::::::::::::*/
    enum KodyBledow
    {
    eOK,
    eMalloc,
    eSprntf
    };

    const char gJakisFajnyTekst[] = { "Jakiś fajny tekst!" };

    /* Funkcje:::::::::::::::::::::::::::::::::::::::*/
    const char* konwertuj(const char* pNapis)
    {
    char* lWynik = malloc(100);

    if(!lWynik)
    {
    errno = eMalloc;
    return pNapis;
    }

    if(sprintf(lWynik, "%s %s\n", pNapis
    , "I jakiś fajny dodatek!") == 0)
    {
    free(lWynik);
    errno = eSprntf;
    return pNapis;
    }

    return lWynik;
    }

    int main()
    {
    printf("%s", konwertuj(gJakisFajnyTekst));

    if(errno)
    {
    fprintf(stderr, "Wystąpił błąd! errno: %d\n", errno);
    exit(EXIT_FAILURE);
    }

    printf("Wykonanie prawidłowe!\n");
    exit(EXIT_SUCCESS);
    }

    7.4.3Wyjątki

    W j. C++ wprowadzono wyjątki (oczywiście j. D też je ma).
    Wyjątki składają się z dwóch el.: najpierw deklaruje się dwa
    bloki kodu:
    try
    {}
    catch()
    {}

    Następnie w bloku try wywołuje się f. która potencjalnie może
    rzucić wyjątek (wyjątek może rzucić f. wywołana z bloku try
    bezpośrednio lub pośrednio). Wyjątek rzuca się dyrektywą throw.
    Wtedy następuje zwinięcie stosu (wyskoczenie z f.) do bloku try
    i przekazanie kontroli do bloku catch.

    W bloku obsługi wyjątków (blok catch) normalnie działa
    polimorfizm, tak więc można swobodnie definiować typy wyjątków
    i w dowolnych miejscach można je łapać.

    Złapane wyjątki można dalej propagować. Poniżej pokazałem
    propagację wyjątku z dodaniem dodatkowej informacji.

    Podobnie jak w przypadku errno z j. C tak samo koncepcja
    wyjątków w C++ jest nietrafiona. Wynika to z faktu, że Komitet
    Centralny C++ zaleca tworzenie nowej kl. dla każdego rodz.
    wyjątku.

    Różnica jest jedynie taka, że errno nie można poprawić, a
    wyjątki tak. Oto co trzeba w tym celu zrobić:
    1. Należy stworzyć własną kl. Wyjątek której konstruktor
    będzie pobierał nast. param.: nazwę modułu, kod błędu,
    treść błędu, plik, nazwa f. (razem z nazwą kl.), nr linii;
    2. Należy stworzyć makro które będzie pobierało nazwę modułu,
    kod błędu i treść błędu (reszta danych jest pobierana
    makrami typu __FILE__). To makro należy stosować w plikach
    definicji (C++);
    3. Należy stworzyć makro które będzie pobierało nazwę modułu i
    treść błędu (wywołuje on makro z p. 2 z kodem błędu -1). To
    makro należy stosować w plikach deklaracji (H++), np. w
    szablonach lub w f. wstawianych/inline.

    Ogólnie bardzo nieufnie przyjmowano nowinki C++. Brak wiary w
    wyjątki pokutuje do dziś, np. w bibl. Qt wer. 6 z 2020r. nadal
    nie używa się wyjątków.

    Perfidnym rozwiązaniem w bibl. Qt uniemożliwiającym wygodną,
    globalną obsługę wyjątków jest łapanie wyjątków w kodzie
    obsługi pętli zdarzeń w Qt. To jest tym bardziej perfidne, że
    qt.io oficjalnie głosi się propagandę, że Qt nie używa
    wyjątków. To perfidne rozw. działa tak, że w razie wystąpienia
    takiego nieprzechwyconego wyjątku program Qt wypisuje na
    konsolę komunikat i kończy działanie. Co w normalnym przypadku
    jest całkowitym zaskoczeniem dla użytkownika prog.
    // Przykład użycia wyjątków:
    #include <exception>
    #include <string>
    #include <iostream>

    using namespace std;

    // Zmienne globalne::::::::::::::::::::::::::::::
    enum KodyBledow
    {
    eOK,
    eDrukowanie
    };

    char gJakisFajnyTekst[] = { "Jakiś fajny tekst!" };

    // Klasy:::::::::::::::::::::::::::::::::::::::::
    class Wyjatek : public exception
    {
    public:
    Wyjatek(string pKomunikat, int pKod)
    : cKomunikat(pKomunikat), cKod(pKod)
    {
    }

    const char* what() const noexcept
    {
    return cKomunikat.c_str();
    }
    int kod() const
    {
    return cKod;
    }

    protected:
    const string cKomunikat;
    int cKod;
    };

    // Funkcje:::::::::::::::::::::::::::::::::::::::
    void drukuj(string pNapis)
    {
    try
    {
    cout << pNapis << endl;
    }
    catch(const exception& pWyjatek)
    {
    string lTresc = "Coś jest nie tak z cout w f. drukuj()!";
    lTresc += pWyjatek.what();
    throw Wyjatek(lTresc, eDrukowanie);
    }
    }

    int main(int n, char** w)
    {
    try
    {
    drukuj(gJakisFajnyTekst);
    }
    catch(const Wyjatek& pWyjatek)
    {
    cerr << "Wystąpił wyjatek! Treść: "
    << pWyjatek.what()
    << " Kod błędu: " << pWyjatek.kod() << endl;
    exit(EXIT_FAILURE);
    }
    catch(const exception& pWyjatek)
    {
    cerr << "Wystąpił wyjatek! Treść: "
    << pWyjatek.what() << endl;
    exit(EXIT_FAILURE);
    }
    catch(...)
    {
    cerr << "Wystąpił nieznany wyjatek!" << endl;
    exit(EXIT_FAILURE);
    }

    cout << "Wykonanie prawidłowe!" << endl;
    exit(EXIT_SUCCESS);
    }

    7.5Komentarze

    Komentarze dzielimy na 2 rodz.:
    1. Komentarze dokumentujące, z których generowana jest
    dokumentacja dla użytkowników (gł. Doxygen);
    2. Komentarze techniczne, czyli wyjaśniające szczegóły
    programistom utrzymującym kod.

    Kod powinien być samoopisowy tak jak to tylko możliwe. Kod nie
    powinien wymagać komentarzy. Komentarzy należy unikać po to by
    ułatwiać analizę i utrzymanie kodu.

    Komentarze powinny wyłącznie dokumentować udostępniane
    interfejsy API. Tylko dlatego, żeby inni programiści mogli
    szybko je poznać i szybko zacząć je używać.

    Komentarze eliminujemy:
    1. Nazwami opisowymi f., zmiennych i makr.
    2. Tworzymy nową f. pomocniczą z nazwą opisową. Po prostu
    wycinamy ten fragment kodu (który chcieliśmy skomentować) i
    tworzymy nową f. W normalnej sytuacji spadek wydajności
    jest niezauważalny na dzisiejszych prockach (po za tym
    można tą f. zrobić wstawianą/inline co powoduje, że koszt
    wywołania będzie zerowy).
    3. Tworzymy komunikat wypisujący treść komentarza. Na
    marginesie: szczególnie często stosuję to w skryptach.

    Okazuje się, że komentarze należy umieszczać wyłącznie w
    plikach C++, bo umieszczanie ich w H++ prowadzi do degeneracji
    zalet plików nagłówkowych (przejrzystość konieczną do szybkiego
    ich analizowania).

    Dodatek 1: Mały sabotaż

    Mały sabotaż to nowoczesna odmiana dywersji Szarych Szeregów
    praktykowana współcześnie przez Etycznych Hakierów
    zatrudnianych przez tajną policję. Mały sabotaż obejmuje m.in.:
    1. Publikowanie opasłych książek i monografii (np. monografie
    prof. Jana Pająka). Zmusza to do pisania własnych skryptów
    i instrukcji (takich jak ta broszura);
    2. Psucie prog. konsoli (brak potrzebnych opcji, np. sed i
    brak przetwarzania blokowego). Zmusza to do pisania
    własnych narzędzi konsoli (np. energo-wymiana z
    przetwarzaniem linijkowym i blokowym);
    3. Brak polskiej dok. (np. większość dok. sys. Ubuntu 20.04).
    Zmusza to do ogłupiającego tłumaczenia w myślach;
    4. Psucie prog. okienkowych przez brak przestrzegania zasady
    ,,zero-conf" czyli zmuszanie do ogłupiającego, wielokrotnego
    powtarzania tych samych czynności (np. Qt Creator). Zmusza
    to do prog. własnych prog. (np. edytor tekstu Tekstprofan);
    5. Psucie składni i organizacji kodu w j. programowania. (np.
    brak dziedziczenia operatorów w C++). Zmusza to do
    programowania makr oraz parserów kodu źródłowego i
    generatorów kodu;
    6. Psucie interfejsów bibl. (API) (np. kl. QFile oraz QProcess
    z Qt). Zmusza to to prog. własnych normalnych kl.
    opakowujących (np. bibl. energo-protekcja);
    7. Kasowanie pam.. (np. o tym co b. chciałem zapamiętać
    czytając dany podręcznik). Zmusza to do programowania prog.
    w stylu SuperMemo (np. Turborety).

    Dodatek 2. Wzorce proj. - jak na nie patrzeć?

    Programistyczne wzorce projektowe to robota tzw. Bandy Czworga
    z 1993r. Można powiedzieć, że to też podpucha z SZAP, bo jak
    się czyta zestawienie tych wzorców to wiele z nich jest
    bliźniaczo podobnych do siebie.

    Podam tu jakie wzorce są zdublowane i jakie na prawdę należy
    sobie przyswoić.

    Wzorce

    1. Stan

    Robi: Przechowuje aktualny stan logiki, kontroluje przejścia
    stanów i informuje o zmianie stanu zainteresowane kl. i
    wtyczki.

    Za pomocą: Zwykły enum i zmienna tego typu w kl. logiki.
    2. Prototyp

    Robi: Jest to wirt. f. kopiuj() pełniąca w kl. rolę wirt.
    konstruktora.

    Za pomocą: Zwykła f. wirt.
    3. Singleton

    Robi: Jedyny w aplikacji obiekt świadczący określone usługi.

    Za pomocą: Jest on tworzony na żądanie tuż przed pierwszym
    użyciem.
    4. Budowniczy

    Zamiast: Fabryka abstrakcyjna, Metoda wytwórcza.

    Robi: Buduje obiekty za pomocą f. cząstkowych, lub przez
    łańcuch zobowiązań.

    Za pomocą: Abstrakcyjnego interfejsu.
    5. Kompozyt

    Robi: Dostarcza interfejs abstrakcyjny dla podobnych o.

    Za pomocą: Zwykłe dziedziczenie.
    6. Iterator-Wizytator

    Robi: Umożliwia iterację po kolekcji, której elementy odwiedza
    wizytator.

    Za pomocą: Pary f. lub pary kl.
    7. Wywołanie Zwrotne

    Zamiast: Dekorator, Obserwator

    Robi: Dodaje nowe f. do o. kl. w czasie działania prog.

    Za pomocą: Rejestruje f. które ma wywoływać w określonych
    sytuacjach.

    W przypadku wywołań zwrotnych często zdarza się, że trzeba je
    synchronizować w celu unikania konfliktu z innymi wątkami.
    8. Producent-Konsument

    Robi: odbiera dane z wątku głównego, buforuje je i wysyła przez
    sieć.

    Za pomocą: 2 dodatkowe wątki do zapisu i do odczytu z gniazda z
    użyciem 2 buforów pierścieniowych (tryb full-duplex).
    9. Polecenie

    Robi: Buforuje napływające rozkazy.

    Za pomocą: Zwykła kl.
    10. Pamiątka

    Robi: Realizuje transakcje w aplikacjach finansowych i historii
    działań we wszelkich edytorach graficznych i tekstowych.

    Za pomocą: Zwykła kl.
    11. Metoda szablonowa

    Robi: Implementuje alg. niezależnie od danych na których
    działa.

    Za pomocą: Zwykły szablonu f. lub szablon kl.
    12. Konwerter

    Zamiast: Adapter, Fasada, Most.

    Robi: Udostępnia nowy interfejs dla starej funkcjonalności.

    Za pomocą: Zwykła kl.
    13. Narzędzia-Logika-Okna

    Zamiast: Model-Widok-Kontroler (w j. ang. MVC)

    Robi: Logika steruje zarówno narzędziami jak i oknami. Dane do
    edycji w oknach dostarcza logika. Kontrola popr. wprowadzanych
    danych powinna się odbywać zarówno na poziomie kontrolek okna
    jak i na poziomie f. pub. w logikach.

    Za pomocą: kl. narzędzi, kl. logiki i kl. okien.

    Antywzorce

    1. Pyłek

    Robi: Dostarcza interfejs abstrakcyjny dla podobnych o.

    Za pomocą: Zwykłe dziedziczenie z leniwym niszczeniem o. w celu
    unikania ich ponownego tworzenia.

    Dlaczego bezcelowy: Szczegół optymalizacyjny.
    2. Fabryka abstrakcyjna, Metoda wytwórcza

    Robi: To samo co budowniczy.

    Za pomocą: Zwykła kl.

    Dlaczego bezcelowy: Zbędne powielenie.
    3. Dekorator, Obserwator

    Robi: To samo co wywołanie zwrotne.

    Za pomocą: Zwykłe f.

    Dlaczego bezcelowy: Zbędne powielenie.
    4. Adapter, Fasada, Most

    Robi: To samo co konwerter.

    Za pomocą: Zwykłe kl.

    Dlaczego bezcelowy: Zbędne powielenie.
    5. Interpreter

    Robi: Parsuje dok. w określonych j. opisujących zawartość.

    Za pomocą: o. kl.

    Dlaczego bezcelowy: To program a nie wzorzec.
    6. Model-Widok-Kontroler (w j. ang. MVC)

    Robi: Tworzy spójne trio kl. obsługujących okno prog.

    Za pomocą: kl. model zawiera dane edytowane w oknie prog, kl.
    widok to okno i jego kontrolki, kontroler weryfikuje dane przed
    wprowadzeniem ich do modelu.

    Dlaczego bezcelowy: Zastępuję go wzorcem Narzędzia-Logika-Okna

    Dodatek 2: Zasady używania baz danych SQL

    Jak wiadomo całe szaleństwo j. SQL wynika jedynie z chęci
    spowolnienia działania komputerów oraz wymuszania ogłupiających
    rozw. Dlatego aby temu przeciwdziałać należy:

    W bibl. firmowej należy zakodować kl. BazaSQL z podst. f.

    1. utworzTablice();
    2. zapis();
    3. jestRekord();
    4. odczyt();
    5. usun().

    Dla każdej tabeli należy utworzyć odpowiadające jej kl.

    NazwaProg/Narzedzia/Baza/Tabela1Dane.h++
    NazwaProg/Narzedzia/Baza/Tabela1Dane.c++
    NazwaProg/Narzedzia/Baza/Tabela1Funkcje.h++
    NazwaProg/Narzedzia/Baza/Tabela1Funkcje.c++
    [...]
    NazwaProg/Narzedzia/Baza/TabelanDane.h++
    NazwaProg/Narzedzia/Baza/TabelanDane.c++
    NazwaProg/Narzedzia/Baza/TabelanFunkcje.h++
    NazwaProg/Narzedzia/Baza/TabelanFunkcje.c++

    Kl. Tabela1Dane ma zmienne odpowiadające polom z Tabela1.
    Natomiast Tabela1Funkcje zawiera proste f. odczytu, zapisu i
    wyszukiwania danych w tej tabeli (do tego wszelkie f.
    pomocnicze związane z tą tabelą).

    Zabronione jest strzelanie SELECT do bazy z dowolnego miejsca w
    prog. Wszelkie tego typu zadania wykonuje kl. Tabela1Funkcje.

    Należy zakodować f. gNormSort

    Ta f. powinna normalnie sortować napisy i numery w tych
    napisach łącznie z przypadkami występowania zer wiodących. Robi
    się to używając 2 zakresów w obu porównywanych ciągach znaków.
    Te zakresy przesuwa się od pocz. do końca obu porównywanych
    ciągów znaków i kolejno porównuje. Do porównania ciągów znaków
    używa się wtedy f. Unicode. Natomiast przy wykryciu numerów
    konwertuje się je do liczb 64bit. i porównuje jako l. Użycie f.
    gNormSort może być takie:
    SELECT Imię, Nazwisko, WIEK FROM Użytkownik ORDER BY Imię, Nazwisko COLL
    ATE gNormSort

    Należy zakodować f. gWyrReg

    Ta f. powinna oferować normalne wyrażenia regularne zamiast
    ,,place holders". Tą f. należy zakodować gdyż nie ma sensu
    szarpać się z wbudowanym operatorem LIKE polecenia SELECT, bo
    to całkowicie nienormalne rozw. Użycie f. gWyrReg może być
    takie:
    SELECT Imię, Nazwisko, WIEK FROM Użytkownik WHERE gWyrReg(Nazwisko, 'JAW
    O.*KI') = 1

    Raporty należy generować lokalnie na serwerze bazodanowym

    Podczas pracy z bazą sieciową do realizacji raportów nie należy
    kopiować wszystkich rekordów przez sieć. Zamiast tego należy
    zaprogramować demona który będzie działał na tym samym serwerze
    co motor SQL i który będzie uruchamiał programiki generujące
    raporty. Ich cechą będzie komunikacja z motorem SQL na
    localhost (zamiast przez sieć) i to będzie główny powód ich
    wydajnego działania. Natomiast wydzielenie raportów do osobnych
    programików będzie pozwalało na ich łatwe przerywanie (przez
    ubijanie procesów - jest to chyba jedyna metoda na przerywanie
    zapytań - bo awaryjne przerwanie zapytania jest zazwyczaj jest
    blokowane na poziomie C/C++ przez bibl. dostępowe).

    Dodatkowo takie raporty łatwo uruchamiać zdalnie, co jest
    wygodne przy kodowaniu i testowaniu.

    [61]1Jednak należy mieć na uwadze, że biegłe opanowanie nowych
    narzędzi i procedur zajmuje 2 lata. To wynika z niemieckich
    doświadczeń wojennych gdy, w latach 1939-41 przestawiali
    produkcję z rzemieślniczej (manufaktury opartej na
    stanowiskach) na seryjną (opartą na linie produkcyjne tak jak w
    amerykańskich fabrykach Forda).

    [62]2Nawet gdy jest on klientem wew.

    [63]3Wszystkie prezentowane tu przykłady kodu zostały
    skompilowane i uruchomione.

    [64]4Jest to polski asemebler FASM na proc. AMD64 (umożliwia
    też kodowanie na proc. Intel od 8080 do Pentium włącznie).
    UWAGA: aby skompilować kod trzeba skopiować do kat. bieżącego
    pliki import64.inc i elf.inc z kat.
    /usr/share/fasm/examples/elfexe/dynamic .

    [65]5W dok. Biblia Tysiąclecia, copyright Wydawnictwo
    Pallottinum, PISMO-SW 3.0 BETA (26 lutego 2002 roku), copyright
    1994-2002 by Piotr Kłosowski k...@i...polsl.gliwice.pl:
    Pwt 17,06: "Na słowo dwu lub trzech świadków skaże się na
    śmierć; nie wyda się wyroku na słowo jednego świadka.". Ten
    cytat znaczy tyle: Żadna ważna sprawa nie będzie oparta na
    mowie jednego świadka. Dlatego jak tylko jeden będzie mówił, że
    zachowałeś się w porządku, to uczciwy sędzia mu nie uwierzy.

    [66]6Można powiedzieć, że to stagnacja ponieważ nie są one
    jakieś nadzwyczajne.

    [67]7man errno

    References

    1. mailto:j...@a...pl
    2. mailto:e...@a...pl

Podziel się

Poleć ten post znajomemu poleć

Wydrukuj ten post drukuj

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: