-
Path: news-archive.icm.edu.pl!news.icm.edu.pl!fu-berlin.de!uni-berlin.de!individual.n
et!not-for-mail
From: Jacek Marcin Jaworski <j...@a...pl>
Newsgroups: pl.comp.lang.c,pl.comp.programming,pl.comp.os.linux.programowanie
Subject: Tworzenie Programów Nieuprzywilejowanych Opartych Na Wtyczkach
Followup-To: pl.comp.lang.c
Date: Mon, 5 Aug 2024 21:26:49 +0200
Organization: Energo Kod
Lines: 1848
Message-ID: <l...@m...individual.net>
Mime-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
X-Trace: individual.net AiMzhUF7xND9qBvZSBchJAe797E7YepFM3+dtyLgFc8aqx5sWP
Cancel-Lock: sha1:iD6VvRIaNbWVkO+Aj78Alq1l5NI=
sha256:usJYM5NWXKucdWgvdqheTtCfaqSNxNOOxQlG3rpsuZo=
User-Agent: Mozilla Thunderbird
Content-Language: pl-PL
Xref: news-archive.icm.edu.pl pl.comp.lang.c:308942 pl.comp.programming:216001
pl.comp.os.linux.programowanie:73123
[ ukryj 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
Najnowsze wątki z tej grupy
- 7. Raport Totaliztyczny: Sprawa Qt Group wer. 424
- TCL - problem z escape ostatniego \ w nawiasach {}
- Nauka i Praca Programisty C++ w III Rzeczy (pospolitej)
- testy-wyd-sort - Podsumowanie
- Tworzenie Programów Nieuprzywilejowanych Opartych Na Wtyczkach
- Do czego nadaje się QDockWidget z bibl. Qt?
- Bibl. Qt jest sztucznie ograniczona - jest nieprzydatna do celów komercyjnych
- Co sciaga kretynow
- AEiC 2024 - Ada-Europe conference - Deadlines Approaching
- Jakie są dobre zasady programowania programów opartych na wtyczkach?
- sprawdzanie słów kluczowych dot. zła
- Re: W czym sie teraz pisze programy??
- Re: (PDF) Surgical Pathology of Non-neoplastic Gastrointestinal Diseases by Lizhi Zhang
- CfC 28th Ada-Europe Int. Conf. Reliable Software Technologies
- Młodzi programiści i tajna policja
Najnowsze wątki
- 2024-11-25 Karty przedpłacone (podarunkowe) Google Play - pytanie do korzystających
- 2024-11-26 wina Tóska
- 2024-11-26 Rewolucja/Rewelacja!
- 2024-11-25 grupa ożyła ;)
- 2024-11-24 Być jak Clint
- 2024-11-24 Rura kanalizacja konceptu Franke = problem
- 2024-11-25 Wrocław => Lead Java EE Developer <=
- 2024-11-25 Warszawa => Business Development Manager - Network and Network Securit
- 2024-11-25 Kraków => Programista Full Stack (.Net Core) <=
- 2024-11-25 Lublin => Senior PHP Developer <=
- 2024-11-25 Karlino => Konsultant wewnętrzny SAP (FI/CO) <=
- 2024-11-25 Warszawa => ECM Specialist / Consultant <=
- 2024-11-25 Katowice => Regionalny Kierownik Sprzedaży (OZE) <=
- 2024-11-25 Warszawa => Senior Frontend Developer (React + React Native) <=
- 2024-11-25 Lublin => Inżynier Serwisu Sprzętu Medycznego <=