Sunteți pe pagina 1din 159

Cuprins

Introducere ......................................................................................................... 4

1. Programarea calculatoarelor ........................................................................ 7

1.1. Limbaje de programare ........................................................................................................ 7

1.2. Implementarea algoritmilor ............................................................................................... 10

1.3. Lucrul cu fişiere .................................................................................................................... 15

1.4. Subprogramele ....................................................................................................................... 17

1.5. Instrucţiunea GoTo .............................................................................................................. 20

2. Ciclul de dezvoltare software ..................................................................... 23

2.1. Activitatea de programare ................................................................................................. 23

2.2. Graful asociat unui program .............................................................................................. 26

2.3. Complexitatea programelor ............................................................................................... 31

2.4. Etapele ciclului de dezvoltare ........................................................................................... 36

3. Abordarea clasică .......................................................................................... 41

3.1. Specificaţiile de programare ............................................................................................. 41

3.2. Preluarea algoritmilor ......................................................................................................... 45

3.3. Elaborarea schemelor logice .............................................................................................. 47

3.4. Scrierea programelor .......................................................................................................... 58

3.5. Testarea şi implementarea programelor ........................................................................ 61

3.6. Între artă, meşteşug şi industrie ..................................................................................... 65

4. Programarea standard .................................................................................. 67

4.1. Reguli de construire ............................................................................................................. 67

4.2. Subprogramul ........................................................................................................................ 69

4.3. Generalitatea subprogramelor .......................................................................................... 79

4.4. Listele cu parametri ............................................................................................................ 83

4.5. Programul principal, programul apelator ......................................................................... 85


5. Abordarea structurată ................................................................................. 89

5.1. Reguli de bază ........................................................................................................................ 89

5.2. Programarea fără GoTo ...................................................................................................... 94

5.3. Conversii de programe ......................................................................................................... 97

5.4. Limbaje de programare structurate ................................................................................ 102

6. Programarea modulară .................................................................................. 106

6.1. Abordarea bottom-up .......................................................................................................... 106

6.2. Abordarea top-down ............................................................................................................ 110

6.3. Raportul subprograme – modul .......................................................................................... 113

6.4. Parametrizarea ..................................................................................................................... 118

7. Programarea orientată obiect .................................................................... 122

7.1. Proiectarea orientată obiect .............................................................................................. 122

7.2. Clasele ..................................................................................................................................... 125

7.3. Constructuri şi destructori ............................................................................................... 128

7.4. Proprietăţile claselor ........................................................................................................... 129

7.4.1. Încapsularea ............................................................................................................ 129

7.4.2. Moştenirea ............................................................................................................... 132

7.4.3. Polimorfism .............................................................................................................. 134

8. Utilizarea de componente ............................................................................ 140

8.1. Premise .................................................................................................................................... 140

8.2. Software orientat pe componente ................................................................................... 141

8.3. Biblioteci de componente ................................................................................................... 149

8.4. Asigurarea interoperabilităţii prin utilizarea de componente ................................... 150

9. Concluzii ........................................................................................................... 153

Anexa 1 – Lista de variabile ....................................................................................................... 156

Bibliografie .......................................................................................................... 158


Introducere

Obiectivul lucrării este prezentarea tehnicilor de programare cu


avantajele şi dezavantajele lor, precum şi realizarea unei analize comparate
a complexităţii software pentru entităţile text generate cu ajutorul acestor
tehnici, folosind aceeaşi problemă spre exemplificare. Codul sursă al
programelor este considerat entitate text pentru că are în alcătuire cuvintele
unui vocabular şi respectă regulile de construire a elementelor complexe de
limbaj asociate acestuia.
Necesitatea lucrării este dată de evidenţierea progresului înregistrat de
trecerea de la o tehnică veche de programare la una nouă, în raport cu
criteriul creşterii performanţei produselor software, dar şi cu cel al creşterii
complexităţii software.
Mijloacele întrebuinţate sunt diferite întrucât fiecare dintre tehnicile
de programare au promovat un anumit limbaj care a evidenţiat în primul
rând avantajele generate. Limbaje precum FORTRAN sau ALGOL sunt
deja istorie, iar limbajul COBOL mai este prezent doar prin intermediul
numeroaselor sisteme informatice dezvoltate cu el. Limbajul C şi evoluţia sa
C++ sunt folosite în continuare foarte intens, în special ca limbaje de
dezvoltare pentru sisteme de operare, însă limbaje precum Java sau C#
reprezintă la acest moment principalele instrumente de dezvoltare a
produselor software.
Limbajele de programare oferă resurse diferite, dacă prin resurse se
înţelege: tipuri de date, cuvinte cheie asociate instrucţiunilor, implementarea
recursivităţii, blocurile, expresiile compuse şi bibliotecile de funcţii.
Capitolele prezintă caracteristicile tehnicilor de programare, ipotezele
de lucru şi efectele acestora asupra structurii produsului finit – programul –
pe care îl construiesc. Se fac referiri asupra capacităţii tehnicilor de
programare de a dezvolta lucrul în echipă, de a asigura un nivel calitativ
ridicat produsului software şi de a permite transmiterea experienţei de la un
proces la altul prin reutilizarea de componente.
Dezvoltarea tehnicilor de programare este dinamică. Pentru a
identifica trendul evoluţiei tehnicilor de programare se studiază influenţa
acestora asupra complexităţii software. La finalul fiecărui capitol dedicat
4
Tehnici de programare

unei tehnici de programare, se analizează complexitatea software a codului


sursă generat cu ajutorul tehnicii respective pentru rezolvarea problemei
alese.
În capitolul în care se prezintă Ciclul de dezvoltare software sunt
descrise etapele, sarcinile, intrările şi ieşirile acestora, aşa cum decurg din
standardele ingineriei software actuale. De asemenea, se prezintă conceptul
de complexitate software şi se definesc instrumentele de măsurare ce sunt
folosite în cadrul acestei lucrări.
Tehnica de programare clasică este prezentată într-un capitol
distinct, în care se accentuează caracterul artizanal, individual al muncii de
programare. Arta programării joacă un rol important, întrucât fiecare
program este văzut ca un produs unicat, produs ce include foarte multă
muncă intelectuală, inspiraţie şi, desigur, inteligenţă.
Programarea standard este o tehnică de programare care pentru
prima dată ia în considerare necesitatea constituirii de biblioteci de
subprograme, după nişte reguli foarte precise. Orice aplicaţie apare, în
viziunea programării standard, sub forma exclusivă a apelului de
subprograme. Multe dintre ideile de finalizare specifice programării
standard se regăsesc în programarea orientată obiect.
În capitolul Abordarea structurată se evită teoretizarea legată de o
bază formală care impune şi dezvoltă această tehnică. Teorema de structură,
ipotezele privind transformarea programelor sunt numai abordări de care
practica programării nu a ţinut seama în nici un fel. În acest capitol se
prezintă structurile fundamentale, cerinţele pe care un program trebuie să le
îndeplinească pentru a fi considerat program structurat. Sunt prezentate, de
asemenea, avantajele privind omogenitatea textelor sursă, capacitatea de a
dezvolta aplicaţii structurate şi extinderea pe care au luat-o limbajele de
programare în care instrucţiunea GOTO nu mai trebuie folosită.
Revoluţia în programare prin introducerea de clase, obiecte,
proprietăţi şi dezvoltarea tehnicilor de analiză şi proiectare bazate pe aceste
concepte este abordată în capitolul Programarea orientată obiect. Se
descriu structurile claselor, proprietăţile de încapsulare, moştenire şi
polimorfism precum şi modul în care acestea influenţează filosofia întregii
activităţi de programare. Programarea orientată obiect reprezintă altceva.
Continuarea păstrării vechilor mentalităţi din programare devine imposibilă.
Saltul realizat de programatori este vizibil, iar rezultatele sunt dintre cele
mai spectaculoase.

5
Introducere

Utilizarea de componente reprezintă o extindere firească a


programării orientate obiect. Se pune problema reutilizării funcţionalităţii şi
nu neapărat a codului care o implementează. Sunt prezentate tehnici de
definire a componentelor, utilizarea acestora în cadrul aplicaţiilor eterogene
din punct de vedere al limbajelor de programare şi/sau tehnologiilor folosite
la dezvoltarea lor, importanţa componentelor în implementarea
interoperabilităţii aplicaţiilor.
Lucrarea se încheie cu concluzii în care se evidenţiază necesitatea
sintetizării experienţelor pozitive în vederea trecerii la noi tehnici de
programare.
Exemplificările se fac numai în limbajul C++. Se defineşte problema
PROB care este prezentată în capitolul Ciclul de dezvoltare software. În
celelalte capitole sunt date soluţii corespunzătoare fiecărei tehnici de
programare analizate pentru problema PROB. S-a adoptat această
modalitate de lucru pentru a putea fi comparate efectele utilizării fiecărei
tehnici de programare în cazul implementării aceluiaşi algoritm. S-a ales o
problemă cunoscută pentru a facilita urmărirea paşilor algoritmului,
simultan cu structurile de program pe care fiecare tehnică le generează.
Analiza comparată se realizează prin soluţionarea aceleaşi probleme,
pentru a surprinde mai bine, diferenţele de abordare pentru fiecare dintre
tehnicile de programare. Pentru fiecare soluţie se fac comentarii privind
lungimea codului sursă şi complexitatea programelor.
Autorii aduc mulţumirile lor membrilor Catedrei de Informatică
Economică din Academia de Studii Economice şi, în special, colectivelor de
programarea calculatoarelor ale căror lucrări au stat la baza cercetărilor
întreprinse şi la obţinerea rezultatelor incluse în capitolele destinate celor
mai noi tehnici de programare aflate acum în atenţia dezvoltării de aplicaţii
informatice moderne.
Lucrarea este elaborată în cadrul contractului de cercetare INFOSOC
„Sistem de evaluare a entităţilor bazate pe text”, nr.148/29.09.2004.
Autorii mulţumesc tuturor celor care, prin contribuţii şi sugestii, vor
îmbunătăţi ediţia viitoare a lucrării de faţă, lucrare care se adresează tuturor
celor care dezvoltă programe, pentru a găsi noi resurse şi pentru a se
perfecţiona continuu.

6
1.1 Limbaje de programare

Limbajele de programare sunt elaborate pentru a implementa


algoritmi. Orice algoritm se descrie ca succesiune de paşi în care intervin
operanzi şi operatori pentru efectuarea de prelucrări.
Orice limbaj de programare conţine:
• o listă de operatori care arată care sunt operaţiile aritmetice, logice
şi de compunere pe care le acceptă limbajul, devenind resurse la
dispoziţia programatorilor atunci când construiesc expresii mai
simple sau mai complexe; lista operatorilor conţine şi priorităţile
asociate precum şi regula de evaluare;
• o listă a cuvintelor cheie asociate unor comenzi executabile; este
preferabil să existe o continuitate de la un limbaj la altul, pentru a
nu se crea confuzii; aşa se explică faptul că pentru implementarea
structurii alternative se păstrează construcţia if() then else, pentru
implementarea structurii repetitive se păstrează instrucţiunile for(),
while(), do until(), cu mici variaţii;
• o listă a cuvintelor cheie pentru definirea tipurilor de date
fundamentale;
• o listă de cuvinte cheie care joacă rolul de atribute sau de
specificatori;
• o listă de delimitatori de blocuri şi de instrucţiuni;
• mecanisme de construire a constantelor şi identificatorilor;
• mecanisme de construire a expresiilor de definire şi a expresiilor
de referire;
• mecanisme de construire a subprogramelor;
• mecanisme de implementare a structurilor de control.

7
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Orice limbaj are la bază un alfabet şi un vocabular format din cuvinte


cheie şi cuvinte construite de utilizatori. Limbajele de programare au
înregistrat evoluţii spectaculoase, trecând prin stadii care au impus
modificări sau chiar dispariţia. Aşa s-a întâmplat cu limbajul FORTRAN,
care a devenit în evoluţia sa FORTRAN-4, FORTRAN-77, FORTRAN-90,
iar variantele care au urmat au avut o asimilare limitată, limbajul fiind cu o
răspândire extrem de redusă. În schimb limbajul C++ prin capacitatea sa de
a asimila construcţii cu grad de complexitate foarte variat, s-a impus şi s-a
dezvoltat.
Orientarea spre programarea vizuală are o largă răspândire prin
sugestivitatea abordării în raport cu problema de rezolvat şi operaţiile
mecanice pe care le presupune implementarea unui algoritm. Limbajele de
nivel scăzut presupun instrucţiuni pentru operaţiile elementare.
Programatorul trebuie să gestioneze descompunerea expresiilor pentru
evaluarea din aproape în aproape.
Limbajele de nivel ridicat includ construcţii complexe, compuneri de
expresii şi prin atribuirea de calificative se obţin efecte speciale în ceea ce
priveşte referirile de operanzi şi operatori. Sunt implementate structuri
dintre cele mai variate, iar mecanismele de definire de operanzi şi operatori
au corespondent mecanisme de referire care generează efecte ce asigură
generalitate şi eficienţă tuturor construcţiilor; numai aşa se explică creşterea
complexităţii şi diversităţii aplicaţiilor informatice de azi.
Exemplificarea limbajelor de programare este realizată folosind numai
limbajul C++. Elementele care definesc un limbaj sunt: alfabetul limbajului,
vocabularul, operatorii şi precedenţele lor, identificatorii, declaraţiile de
funcţii şi variabile. Vocabularul limbajului C++ include cuvintele cheie:
asm auto bool break case
catch char class const const_cast
continue default delete do double
dynamic_cast else enum explicit extern
false float for friend goto
if inline int long mutable
namespace new operator private protected
public register reinterpret_cast return short
signed sizeof static static_cast struct
switch template this throw true
try typedef typeid typename union
unsigned using virtual void volatile
wchar_t while

8
Programarea calculatoarelor

Lista de operatori şi priorităţile sunt date în tabelul 1.1:

Operatorii identificaţi în limbajul C++


Tabelul 1.1
Precedenţă Operatori
1 () [] -> .
::
2 ! ~ ++ --
- (operator unar) * (dereferire)
& (adresa) Sizeof
3 ->* .*
4 * (înmulţire) / %
5 + -
6 << >>
7 < <= > >=
8 == !=
9 & ( AND pe biţi)
10 ^
11 |
12 &&
13 ||
14 ?:
15 = += -= etc.
16 ,

Alfabetul limbajului C++ este alcătuit din următoarele caractere:


abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123 56789
!“#%&‘()*+,-./
:;<=>?[\]^_{|}~
spaţiu, TAB, CR/LF

Identificatorul este numele dat de programator variabilelor, funcţiilor,


etichetelor şi a altor obiecte definite de el. Mecanismul de construire al
acestuia este acelaşi în toate limbajele de programare importante: primul
caracter este o literă sau un caracter de subliniere, iar caracterele care
urmează sunt litere, cifre sau caractere de subliniere. Cuvintele cheie
asociate limbajului nu sunt acceptate ca identificatori.

9
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Exemple de identificatori valizi sunt: _matrice, mat1234, arb43bin,


for123.
Exemple de identificatori invalizi sunt: 12matrice, for, etc.
Prototipul de funcţie în C++ este următorul:
tip_rezutat nume_funcţie ( listă_parametrii ) {
corp_funcţie
}

Declararea operanzilor se face astfel:


tip_operand listă_de_variabile;

Există numeroase lucrări, [IVANI98], [SMEU01], [SMEU02],


[STROU87], care detaliază limbajul C++ evidenţiind caracteristicile sale
cele mai puternice, studiul acestui limbaj nefiind obiectivul acestei lucrări.

1.2 Implementarea algoritmilor

Algoritmii presupun operanzi şi operatori. Pentru operanzi există:


• iniţializări de date;
• afişări de rezultate;
• prelucrări.
Operatorii arată modul în care sunt transformaţi operanzii, pentru a
deveni din date iniţiale, date prelucrate, adică rezultate care să determine
acţiuni sau să permită luarea unor decizii.
Orice problemă P are asociat un algoritm căruia îi corespunde
structura arborescentă cu două niveluri dată în figura 1.1.

Figura 1.1 Structură de algoritm pe două niveluri

10
Programarea calculatoarelor

Iniţializarea se realizează prin:


• citire date din fişiere sau baze de date;
• introducere date de la tastatură;
• evaluarea expresiilor de atribuire.
Secvenţa de rezultate conţine proceduri pentru:
• afişare rezultate pe ecranul monitorului;
• scrierea la imprimantă;
• creare sau actualizare de fişiere, respectiv actualizare de baze
de date.
Implementarea unui algoritm include instrucţiuni pentru una sau mai
multe combinaţii de iniţializare, respectiv gestionare a rezultatelor.
Există numeroşi algoritmi daţi în literatura de specialitate şi în cărţile
de bază ale programării. Sunt algoritmi verificaţi, aduşi la o formă rafinată,
programatorul trebuind numai să-i preia. Sunt şi situaţii în care,
programatorul trebuie să-şi construiască algoritmi proprii. În toate cazurile,
trebuie avute în vedere:
• generalitatea pe care trebuie să o asigure, nefiind interesant
acel algoritm care soluţionează o problemă concretă, ci acela
care soluţionează problemele aparţinând unei clase;
• corectitudinea, în sensul că pentru orice problemă aparţinând
clasei de probleme pentru care a fost construit algoritmul,
trebuie să furnizeze rezultate corecte;
• reproductibilitatea, în ideea că repetând prelucrările pentru
aceleaşi date, se vor obţine aceleaşi rezultate, prelucrările
având caracter determinist;
• finitudine, în sensul că volumul de prelucrări trebuie limitat la
o dimensiune acceptabilă în raport cu obiectivul urmărit.
La implementarea unui algoritm se procedează mai întâi la elaborarea
unei scheme logice. Cu acest prilej, se analizează gradul de înţelegere a
problemei. Testul de birou folosind date de control are menirea de a da un
plus de siguranţă soluţionării problemei prin metoda folosită.
Pentru implementarea algoritmului sunt alese niveluri de detaliere.
Este important ca indiferent de procedeul definit şi utilizat, programul sa-şi

11
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

atingă obiectivul pentru care a fost elaborat. De exemplu, se doreşte


implementarea algoritmului de sortare prin interschimbare cu contorizarea
interschimbărilor.
Se consideră şirul:

şi se doreşte ordonarea descrescătoare. Se interschimbă perechile, începând


cu primul element:

şirul devine:

Numărul de interschimbări a fost 4. Se interschimbă perechile


începând cu al doilea element:

şirul devine:

Numărul de interschimbări a fost 3. Se reiau interschimbările cu


primul element:

12
Programarea calculatoarelor

şirul devine:

Numărul de interschimbări a fost 4. Se interschimbă începând cu al


doilea element:

Şirul devine:

Numărul interschimbărilor a fost 3. Se reiau paşii:

13
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Algoritmul este finit pentru că în momentul în care nu mai există


interschimbări, înseamnă ca şirul este sortat. Pentru implementare trebuie
răspuns la întrebările:
• cât de lungi sunt şirurile de termeni care se sortează;
• dacă sortarea este însoţită şi de interschimbarea altor câmpuri.
Funcţie de răspunsurile date se procedează la definirea unui algoritm
de sortare în fişiere sau la construirea unui algoritm de sortare exclusiv în
memorie. Implementarea algoritmilor depinde de:
• experienţa programatorilor;
• resursele hardware şi software disponibile;
• resursele financiare care stau la baza demersului;
• timpul în care trebuie rezolvată problema.
În mod normal, trebuie să se pornească de la problema de rezolvat.
Aceasta impune cum se stochează datele iniţiale, cum se gestionează
rezultatele intermediare şi cum sunt memorate rezultatele finale.
Implementarea algoritmilor necesită efectuarea de calcule privind
necesarul de memorie pentru a stoca rezultatele intermediare şi finale,
precum şi datele iniţiale în timpul prelucrării. Sunt preferaţi algoritmii care
nu necesită zone importante de memorie pentru rezultate intermediare.
Pentru calculul mediei aritmetice care utilizează cât mai puţină
memorie, programul ce implementează algoritmul este următorul:
#include <stdio.h>

int main() {
int i, n, s, x;
float xm;
s = 0;
printf("Introduce numarul de elemente:");
scanf("%d", &n);
for ( i = 0; i < n; i++ ) {
printf( "Introduce x[%d] = ", i );
scanf("%d", &x);
s += x;
}
xm = (float)s/n;
printf( "Media aritmetica este %f", xm );
}

14
Programarea calculatoarelor

Se constată că pentru a reutiliza datele pentru alte calcule, acest


deziderat este imposibil de realizat. Pentru a reutiliza datele, este necesar ca
ele să se salveze în memorie, în structuri de date corespunzătoare, şi apoi să
intre în prelucrarea efectivă. Algoritmul este un aspect, programul este o
concretizare, una din concretizări, totul depinzând de programator.

1.3 Lucru cu fişiere

Fişierele, bazele de date, sunt fundamentale pentru rezolvarea


problemelor în care se manipulează volume mari de date. Fişierul este o
colecţie de date reprezentând informaţii privind elementele unei
colectivităţi. Dacă datele privind un element Ai al colectivităţii {A1, A2, ….,
An} are lungime constantă L baiţi, atunci fişierul F are o lungime lg(F) = n *
L baiţi.
Fiecare articol descrie un element al colectivităţii. Poziţia pi a
articolului care se referă la elementul Ai din colectivitate împreună cu o
valoare unică, numită cheie, notată ki, formează o pereche ( ki, pi). Unui
fişier F i se asociază un şir de perechi (k1, p1), (k2, p2), ..., (kn, pn). Dacă se
efectuează operaţii precum adăugarea, inserarea, ştergerea, interschimbarea
de articole, este preferabil ca ele să se efectueze asupra şirului de perechi,
fără a provoca schimbări la nivel fizic, întrucât operarea pe zonele de
memorie asociate fişierului au durate care reduc volumul tranzacţiilor pe
unitatea de timp.
Dacă între cheile k1, k2, .........., kn şi poziţiile p1, p2, ...., pn se identifică
o dependenţă, atunci se construieşte o expresie analitică folosind relaţia:
pi = f (ki)
Se obţine deplasarea articolului Ai prin relaţia:
Di = pi * LAi
Problemele se tratează similar şi în cazul în care lungimile articolelor
sunt diferite. Dacă articolul Ai are o lungime proprie Li, deplasarea sa Di se
obţine din relaţia:
i −1
Di = ∑ LA j + D0
j =1

unde D0 este deplasarea primului articol.

15
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Pentru a creşte viteza de identificare a unui articol, fişierul F este


împărţit în r părţi, figura 1.2.

Figura 1.2 Divizarea fişierului F

Dacă se efectuează traversarea fişierului F pentru a găsi articolul cu


cheia kx, sunt necesare suficient de multe operaţii de citire. Dacă se
construieşte şirul de r perechi (k1`, p1`), (k2`, p2`)…..(kr`, pr`) se traversează
mai întâi şirul de perechi, se obţine selectarea zonei din fişier unde se află
articolul cu cheia kx după care se procedează la traversarea secvenţială a
zonei, articol cu articol, până la identificarea celui căutat.
Este important ca de la început articolele să fie dispuse în zonele
fişierului F cu condiţia:
k1 < k2 < ……….. < kn
Subşirului de perechi i se asociază un alt subşir, căutarea secvenţială
efectuându-se pe două niveluri. Raţionamentul se dezvoltă, fiind necesar un
echilibru între numărul nivelurilor de indexare şi efortul de traversare
secvenţială în cadrul fiecărui nivel.
Dacă se adaugă atribute, se construiesc tripletele care se asociază
articolelor fişierului (k1,p1,a1), (k2,p2,a2), ...., (kn,pn,an).
Operaţiile de intersecţie, reuniune de şiruri de triplete conduc la
extragerea din fişier a articolelor ale căror câmpuri au valori ce corespund
unor combinaţii de atribute.
Viteza de derulare a tranzacţiilor depinde strict de întreaga filosofie de
a organiza procesele de referire ale articolelor din zona de date a fişierului.
Lucrul cu indexuri, cu deplasări şi mai ales structurile arborescente asociate
perechilor de căutare influenţează duratele tranzacţiilor.

16
Programarea calculatoarelor

1.4 Subprogramele

Acum se concepe dezvoltarea unei aplicaţii numai folosind


subprograme. Un subprogram este o entitate de sine stătătoare, care
concentrează o prelucrare cu caracter repetitiv. Nu se scriu subprograme
pentru prelucrări care nu vor mai fi realizate şi cu alte prilejuri.
Subprogramul are un nume sugestiv, uşor de manevrat, fără a fi
definite mai multe subprograme diferite ca prelucrare cu nume având
coeficient de similitudine peste un prag considerat limită superioară. Spre
exemplificare, se consideră următoarea listă de subprograme:
• read_file() – subprogram care citeşte un articol dintr-un fişier;
• update_file() – subprogram pentru actualizarea unui fişier;
• write_file() – subprogram pentru scrierea unui articol într-un
fişier;
• addmat() – subprogram care adună două matrice;
• prodmat() – subprogram care înmulţeşte două matrice;
• copymat() – subprogram pentru copierea unei matrice într-o
altă matrice;
• initmat() – subprogram care iniţializează o matrice cu o
constantă;
• sort_file() – subprogram pentru sortarea fişierelor;
• sort_vect() – subprogram pentru sortarea elementelor unui
vector;
• concat_vect() – subprogram care concatenează doi vectori;
• concat_lists() – subprogram care concatenează două liste
simple;
• concat_listd() – subprogram pentru concatenarea două liste
duble;
• concat_nlists() – subprogram pentru concatenarea a n liste
simple;
• concat_nlistd() – subprogram care concatenează n liste duble;
• concat_nvect() – subprogram care concatenează n vectori.

17
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Subprogramele conţin în construcţiile lor:


• liste de parametri formali în care se regăsesc operanzii care fac
obiectul prelucrării, operanzii ce conţin rezultatele şi variabile
ce conţin informaţii privind modul în care s-au efectuat
prelucrările; listele fixe de parametri au aceeaşi structură şi
lungime, indiferent de numărul apelurilor; listele variabile au
atât tipuri diferite de parametrii, cât şi număr variabil;
• instrucţiuni care asigură revenirea în programul apelator,
return; este preferabil să se construiască subprograme care
conţin o singură instrucţiune return, folosind fie compuneri de
expresii, fie o variabilă de control utilizată ulterior;
• instrucţiuni repetitive pentru a asigura traversarea structurilor
de date omogene (masiv, listă, arbore, fişier), dacă pentru
evaluarea de expresii acest lucru este necesar;
• instrucţiuni de iniţializare pentru variabile care îşi modifică
conţinutul în vederea returnării de rezultate care vor fi utilizate
în programul apelator; programul apelator este şi el tot un
subprogram; int main() este, de asemenea, un subprogram, aşa
cum într-un arbore binar, rădăcina este tot un nod;
• un tip de rezultat care se returnează; în cazul în care lista
rezultatelor returnate este vidă, tipul subprogramului este void.
Forma generală a unui subprogram este:
tip_rezultat nume_subprogram( listă_parametrii_formali ) {
definiri variabile locale subprogramului
instrucţiuni de prelucrare
return ( expresie )
}

Figura 1.3 Forma generală a unui subprogram

Este de dorit ca subprogramele să fie înzestrate cu un grad înalt de


generalitate pentru a permite refolosirea lor ori de câte ori e nevoie. Testarea
sistematică a subprogramelor le dă un nivel de corectitudine care generează
încrederea utilizatorilor în a le încorpora în programele proprii.
Există mai mulţi termeni care desemnează acelaşi lucru: proceduri,
subprograme, funcţii; sunt concepte prin care, de regulă, se înţelege o
construcţie independentă în raport cu un context, care se referă şi care
permite efectuarea de prelucrări ale căror efecte sunt utilizate ulterior.

18
Programarea calculatoarelor

Pentru evaluare expresiei:


n
S = ∑ xi
i =1

se scriu mai multe forme de subprograme, care diferă după cum sunt
transmişi parametrii.
În textul sursă:
int suma ( int x[], int n ) {
int s, i;
for ( s = 0, i = 0; i < n ; i++ ) {
s += x[i];
}
return s;
}

subprogramul returnează rezultatul. În textul:


void suma ( int x[], int n, int* ps ) {
int i;
*ps = 0;
for ( i = 0; i < n; i++ ) {
*ps += x[i];
}
}

rezultatul se regăseşte în zona de memorie a cărei adresă se află în variabila


pointer ps.
Faptul că lista de parametri include o mare diversitate de tipuri de
date, inclusiv pointeri spre funcţii, subprogramele devin cu grad de
generalitate foarte mare. În loc să se scrie, de exemplu, două subprograme
pentru a găsi elementul minim, respectiv elementul maxim dintr-un şir, se
va scrie un singur subprogram ce are în lista de parametri o funcţie ce
returnează evaluări diferite de expresii condiţionale, care vor determina
acţiuni diferite în cadrul subprogramului.
Subprogramele trebuie să fie generale atât prin dimensiunea
problemelor de rezolvat, cât mai ales prin cuprinderea a cât mai multor
cazuri care definesc clasa de probleme rezolvată. În programarea orientată
obiect problematica tipului de rezultate returnate şi a tipurilor de variabile
din lista de parametrii se soluţionează prin construcţii de tip template.
Limbajele de programare dispun de numeroase modalităţi de
transmitere a parametrilor, dintre care transmiterea prin valoare,

19
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

transmiterea prin adrese şi transmiterea prin referinţă, sunt cele mai frecvent
întâlnite. Este rezonabilă cunoaşterea în detaliu a acestor mecanisme pentru
a înţelege efectele.
Dacă în cazul transmiterii prin valoare operarea pe variabilele asociate
parametrilor subprogramului este locală în raport cu domeniul definit de
subprogram, în celelalte cazuri aceste operaţii conduc la modificarea
conţinutului zonelor de memorie asociate parametrilor formali, la nivelul
subprogramului apelator.
Subprogramele nu conţin operaţii de intrare/ieşire, pentru a li se
asigura un înalt grad de generalitate şi mentenabilitate. Ele primesc datele de
intrare prin intermediul listei de parametrii şi oferă rezultate fie prin
parametrii din listă, fie prin valoarea returnată de subprogram, fie prin
amândouă modalităţile.

1.5 Instrucţiunea GOTO

Această instrucţiune a fost mărul discordiei de-a lungul timpului


pentru programatori. Ea înseamnă salt necondiţionat spre o instrucţiune a
cărei etichetă este specificată. Salturile înapoi revin la situaţiile în care
eticheta e1 spre care se efectuează saltul necondiţionat precede instrucţiunea
GOTO, figura 1.4.

e1

GO TO e1
e2

Figura 1.4 Instrucţiune salt înapoi

Salturile înainte revin situaţiilor în care etichetele e2 şi e3 ale


instrucţiunilor spre care se efectuează saltul urmează instrucţiunilor GOTO,
figura 1.5.

20
Programarea calculatoarelor

Figura 1.5 Instrucţiune salt înainte

Dacă se asociază un graf unui program şi se include într-o matrice


instrucţiunile precedente şi instrucţiunile următoare, în cazul unei structuri
liniare, figura 1.6, matricea include asteriscuri numai deasupra diagonalei
principale:

Figura 1.6 Structură liniară

Figura 1.7 Matricea de precedenţă a instrucţiunilor

În cazul includerii în program a instrucţiunilor de salt necondiţionat,


matricea precedenţelor devine mult mai complexă. Pentru programul al
cărui graf este dat în figura 1.8, rezultă matricea de precedenţă, din figura
1.9.

21
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Figura 1.8 Graf asociat unui program ce include instrucţiuni de salt

I1 I2 I3 I4 I5 I6 I7
I1
I2
I3
I4
I5
I6
I7
Figura 1.9 Matricea de precedenţă asociată programului cu salturi mixte

Programarea modulară exclude apariţia instrucţiunii GOTO, deşi la


nivelurile de programare în limbaj de asamblare este evident că nici o
structură de control nu se implementează fără a folosi această instrucţiune.
Sunt situaţii în care optimizarea programelor impune o altă abordare care să
elimine salturile necondiţionate între componente aflate în segmente
diferite.
Programarea calculatoarelor se defineşte ca meserie ce foloseşte
resurse, iar eficienţa sa este cu atât mai mare cu cât procesul de alocare şi
nivelare a acestor resurse este mai aproape de nivelul optim. Limbajele,
subprogramele, instrucţiunile, tehnicile de programare sunt resurse,
nivelarea şi alocarea lor o efectuează programatorul.

22
2.1 Activitatea de programare

Activitatea de programare, aşa cum este definită în [MSPCD97],


reprezintă arta şi ştiinţa de a crea programe pentru calculator. Programarea
începe cu cunoaşterea unuia sau a mai multor limbaje de programare, cum
sunt Basic, C, C++, Java, Pascal sau limbajul de asamblare. Cunoaşterea
unui limbaj nu este suficient pentru realizarea unui program bun. Alte
aspecte necesare se referă la teoria algoritmilor, proiectarea interfeţelor cu
utilizatorul şi caracteristici ale dispozitivelor hardware.
Activitatea de programare este colectivă, echipele de programatori
dezvoltă procese de alegere şi nivelare a resurselor - echipamente hardware,
limbajele de programare, algoritmii - pentru a obţine un software de cea mai
bună calitate. Programatorii sunt persoane cu înaltă calificare care scriu şi
depanează programe. În funcţie de dimensiunea proiectului şi de mediul de
lucru, un programator lucrează singur sau într-o echipă, implicat parţial sau
total în procesul de dezvoltare, care începe cu faza de analiză şi se încheie
cu faza de testare şi implementare.
Activitatea de programare este deosebit de complexă, de dinamică şi
implică eforturi deosebit de mari pentru actualizarea cunoştinţelor, pentru
înţelegerea corectă a instrumentelor de dezvoltare a aplicaţiilor.
Programatorii trebuie să fie la curent cu ultimele noutăţi în domeniu, să îşi
extindă permanent aria de cunoaştere spre noi limbaje şi tehnologii.
Programarea presupune existenţa sistemelor de calcul şi a software
necesar pentru dezvoltarea de noi produse, în vederea satisfacerii unor
cerinţe precis definite. Acest software reprezintă sistemul de programare şi
include următoarele categorii de instrumente: programele editoare de text,
programele translatoare, programele editoare de legături, programele
depanatoare şi mediile de programare.
Pentru a dezvolta software de bază, au fost create specializări, unii
dintre programatori stăpânind foarte bine limbajele de asamblare. Alţii,
pentru a dezvolta software cu caracter aplicativ, utilizează limbajele de nivel
23
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

înalt. În cazul aplicaţiilor care operează cu seturi foarte mari de date,


programatorii acumulează experienţă în a lucra cu sisteme complexe de
gestiune a bazelor de date.
În contextul creşterii complexităţii aplicaţiilor informatice s-au
cristalizat următoarele direcţii:
• activitatea de programare este o activitate care se derulează în
cadrul unei echipe, în care fiecare membru are definite
inputurile, respectiv outputurile, foarte clar;
• s-au adâncit specializările, încât rolul analistului de sistem,
rolul designerului, rolul programatorului şi rolul celor care se
ocupă de testare, implementare şi mentenanţă a crescut foarte
mult; deja există profesiuni foarte bine conturate, iar
managerul de proiecte IT este deja o profesie necesară şi, mai
ales, recunoscută în societatea informaţională;
• necesitatea utilizării de instrumente tehnice văzută ca
necesitate a înzestrării posturilor de muncă, fie că e vorba de
analişti, de designeri, de programatori, de testeri de software şi
de personal care administrează aplicaţia.
Activitatea de programare este privită, uneori, ca activitate pură de
creaţie, aspect exagerat, cum tot exagerat este a-l privi pe programator ca pe
un simplu codificator. Programatorul trebuie să se instruiască continuu,
forma concretă – programul – a întregii activităţi dintr-o companie de
software este rezultatul muncii sale, chiar dacă în spate se află eforturile
concertate şi ale analiştilor şi designerilor.
Apar numeroase întrebări referitoare la:
• numărul limbajelor de programare pe care trebuie să le
cunoască un programator; dacă programatorul stăpâneşte
foarte bine un limbaj de programare, are experienţă în a folosi
facilităţile respectivului limbaj, posedă cunoştinţe de algoritmi
şi tehnici de programare şi dacă stăpâneşte elementele de bază
ale unui sistem de operare, va dezvolta produse de bună
calitate; atunci când un alt limbaj de programare devine
dominant, se impune ca programatorul să treacă la elaborarea
de software folosind resursele noului limbaj dominant;
înseamnă că activitatea de programare presupune un continuu
efort de instruire, dar, mai ales, autoinstruire;

24
Ciclul de dezvoltare software

• traiectoria pe care o urmează un programator; dacă acesta e o


persoană perseverentă adânceşte cunoştinţele dintr-un domeniu
sau extinde aria de cuprindere spre alte limbaje sau tehnici de
programare; o traiectorie normală este aceea în care
programatorul se îndreaptă spre activitatea de proiectare, iar
după ce capătă o mai mare experienţă se orientează spre
analiza de sistem şi spre a deveni manager de proiect;
• caracterul industrial al activităţii de programare; conceptul de
fabrică de software este privit ca o translatare forţată a unor
laturi cu caracter repetat, reproductibil şi responsabilizat din
producţia industrială spre o zonă unde imaterialitatea
produsului imprimă particularităţi mai ales legate de absenţa
uzurii fizice şi de costul nul al operaţiei de copiere;
• necesitatea de a compara produsele software prin definirea de
metrici ale calităţii, prin utilizarea unor coeficienţi de
transformare;
• apariţia pieţii de software ca principală pârghie de reglare a
tendinţelor centrifuge din domeniul producţiei de aplicaţii
informatice cu grad de generalitate deosebit de ridicat.
Activitatea de programare a calculatoarelor presupune calităţi
remarcabile din partea celor care doresc să profeseze meseria de
programator. Piaţa de joburi în evoluţia sa a impus meserii deosebit de
diverse, cum sunt: programator, dezvoltator, inginer software, inginer de
sistem etc.
Pentru analizarea avantajelor şi dezavantajelor tehnicilor de
programare prezentate în această carte, a fost aleasă o problemă comună,
care este dezvoltată folosind tehnicile prezentate. Se presupune o matrice cu
m linii şi n coloane, m şi n numere impare. Pentru aceasta se determină
valorile maxime şi minime, se testează simetria matricei, iar dacă matricea
este simetrică, atunci se numără elementele pozitive, negative şi nule ale
acesteia. Dimensiunile şi elementele matricei sunt informaţii ce vor fi citite
de la tastatură, în programele de test.

25
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

2.2 Graful asociat unui program

Orice program este alcătuit din instrucţiuni neexecutabile, prin care se


definesc operanzii şi operatorii, cât şi prin instrucţiunile executabile I1, I2,
….., In. Lungimea L a unui program este dacă ca număr de instrucţiuni. Însă
nu există o corespondenţă biunivocă între o instrucţiune şi o linie din codul
sursă. O linie din codul sursă poate conţine 0, 1 sau mai multe instrucţiuni.
Programul:
int main ()
{
int a, b, c, d, e, f;
a = b = c = 7;
d = 89;
e = -1;
if ( e + d * a ) f = e + a;
else
f = e - a;
}

are un număr de zece linii sursă.


În cazul în care un alt programator rescrie acelaşi program sub forma:
int main () {
int a, b, c, d, e, f;
a = b = c = 7;
d = 89; e = -1;
if ( e + d * a ) f = e + a;
else f = e - a;}

se obţine un program cu doar şase linii sursă, însă ambele au acelaşi număr
de instrucţiuni. Se impune stabilirea unor reguli foarte precise pentru
dispunerea instrucţiunilor şi separatorilor de blocuri pentru a se obţine
construcţii comparabile ca efort de realizare şi claritate a codului.
În graful asociat unui program, fiecărei instrucţiuni i se asociază un
nod, legarea acesteia de instrucţiunea ce urmează a fi executată realizându-
se cu ajutorul unui arc orientat.

26
Ciclul de dezvoltare software

Textul sursă al programului pentru alegerea minimului dintre trei


elemente este următorul:
int main () {
int a, b, c, min; I1
printf( "Introduceti a = " ); I2
scanf("%d", &a); I3
printf( "Introduceti b = " ); I4
scanf("%d", &b); I5
printf( "Introduceti c = " ); I6
scanf("%d", &c); I7

if ( a > b ) { I8
if ( b > c ) { I9
min = c; I10
} else { I11
min = b; I12
}
} else { I13
if ( a > c ) { I14
min = c; I15
} else { I16
min = a; I17
}
}

printf( "Minimul este %d", min); I18


}

şi are asociat structura arborescentă dată în figura 2.1.

27
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Figura 2.1 Graful asociat programului pentru alegerea minimului


dintre a, b, c

Secvenţa de program destinată numărării elementelor pozitive,


negative şi nule ale unei matrice este următoarea:
int splus = 0; I1
int snegativ = 0; I2
int snul = 0; I3

for ( i = 0;i < m; i++ ) { I4


for ( j = 0; j < n; j++ ) { I5
if ( a[i][j] > 0 ) splus++; I6
if ( a[i][j] < 0 ) snegativ++; I7
if ( a[i][j] == 0 ) snul++; I8
}
}

printf("\nNumarul de elemente pozitive este %d", splus ); I9


printf("\nNumarul de elemente negative este %d", snegativ I10
);
printf("\nNumarul de elemente nule este %d\n", snul ); I11

28
Ciclul de dezvoltare software

iar graful asociat este dat în figura 2.2.

Figura 2.2 Graf asociat secvenţei de numărare a elementelor

Graful asociat unui program permite evidenţierea modului în care este


construit programul. Pentru programul:
int splus = 0; I1
int snegativ = 0; I2
int snul = 0; I3
for ( i = 0;i < m; i++ ) { I4
for ( j = 0; j < n; j++ ) { I5
if ( a[i][j] > 0 ) { I6
splus++; I7
} else { I8
if ( a[i][j] < 0 ) { I9
snegativ++; I10
} else { I11
snul++; I12
}
}
}
}

29
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

printf("\nNumarul de elemente pozitive este %d", splus ); I13


printf("\nNumarul de elemente negative este %d", snegativ I14
);
printf("\nNumarul de elemente nule este %d\n", snul ); I15

este dezvoltat graful din figura 2.3.

Figura 2.3 Graful asociat programului cu structuri alternative imbricate

Abordarea structurii graf permite dezvoltarea de operaţii de


transformare care să conducă la construcţii echivalente, însă optimizate.
Numărul de arce şi orientarea acestora deosebeşte tehnicile de programare.

30
Ciclul de dezvoltare software

2.3 Complexitatea programelor

Complexitatea software este definită în standardul IEEE 729 – 1983


ca fiind gradul de complicare a unui sistem sau a unei componente dintr-un
sistem, determinată de factori cum sunt numărul şi complexitatea
interfeţelor, numărul şi complexitatea ramurilor condiţionale, gradul de
imbricare, tipurile structurilor de date şi alte caracteristici ale sistemului. O
definiţie mult mai completă a fost cea dată de Zuse în 1991, şi anume,
complexitatea software este gradul de dificultate în analiza, întreţinerea,
testarea, proiectare şi modificarea software-ului. Rezultă că, este de aşteptat
ca diferite tipuri de complexitate să apară în diferite etape ale ciclului de
dezvoltare.
Intuitiv, fiecare programator îşi dă seama ca un program este mai
simplu sau mai complex. Se spune că un program este mai simplu dacă are
mai puţine instrucţiuni şi dacă legăturile dintre instrucţiuni sunt mai reduse
ca număr şi ca importanţă.
Se consideră un program PR căruia i se asociază o structură de tip graf
în care se notează:
na - numărul arcelor
nn - numărul nodurilor din graf
Complexitatea în sens McCabe CM a unui program este dată de relaţia:
CM = na – nn + 2
Complexitatea în sens McCabe sau complexitatea ciclomatică, este
cea mai utilizată metrică software a complexităţii. Ea măsoară numărul de
căi de execuţie liniar independente, printr-un modul de program. Această
măsură oferă ca rezultat un singur număr care serveşte ca instrument de
comparaţie cu complexitatea altor programe.
O aplicaţie frecventă a complexităţii ciclomatice este compararea
rezultatului obţinut cu un set de valori-prag recunoscute. Un astfel de set
este dat în tabelul 2.1.

31
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Complexitatea ciclomatică
Tabelul 2.1
Complexitate ciclomatică Evaluarea riscului
1 – 10 un modul/program simplu, fără risc
mare
11 – 20 un modul/program mai complex, cu
risc mediu
21 – 50 un modul/program foarte complex, cu
risc ridicat
mai mare de 50 un modul/program netestabil, cu un
risc extrem de ridicat

Dacă un program este format din instrucţiunile I1, I2, …, IK, cărora li
se asociază graful dat în figura 2.4.

Figura 2.4 Structură secvenţială de program

Complexitatea CM = 1 pentru că:


na = K–1
nn = K

Pentru programul cu structura de graf dată în figura 2.5, complexitatea


CM = 14 – 10 + 3 = 6, pentru că numărul de arce este 14, iar numărul de
noduri este 10.

32
Ciclul de dezvoltare software

Figura 2.5 Structură de program cu salturi înapoi

Pentru programul de alegere a elementului minim şi a elementului


maxim dintr-un şir dat prin textul sursă:

int x[N], i, n, minim, maxim;


n = citeste(); //functie de pentru numarul de elemente
citeste(n, x); // functie de citire pentru elemente

maxim = minim = x[0];


for ( i = 0; i < n; i++ ) {
if ( minim > x[i] ) {
minim = x[i];
}
if ( maxim < x[i] ) {
maxim = x[i];
}
}

printf( "Maximul este %d\n", maxim );


printf( "Minimul este %d\n", minim );

33
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

cu graful asociat dat în figura 2.6, complexitatea este


CM = 13 – 11 + 2 = 4

4
if(min > x[i])
12
min = x[i]
13 11 7
if (max < x[i])
9
max = x[i]
8 10
i++
5

Figura 2.6 Graf asociat programului de alegere a minimului şi maximului

Se observă că de la o tehnică de programare la alta diferă nivelul


complexităţii în sens McCabe.
O altă metrică a complexităţii software o reprezintă complexitatea
Halstead. Ea a fost dezvoltată în special pentru a măsura complexitatea unui
modul de program pe baza codului sursă, cu accent pe complexitatea de
calcul. Metricile Halstead se bazează pe patru valori numerice, rezultate din
codul sursă:
n1 - numărul de operatori distincţi
n2 - numărul de operanzi distincţi
N1 - numărul total de operatori
N2 - numărul total de operanzi

34
Ciclul de dezvoltare software

Din aceste numere, rezultă cinci metrici:


Metrică Simbol Formula
Lungimea programului N N = N1 + N2
Vocabularul programului n n = n1 + n 2
Volumul V V = N * log2n
Dificultatea D D = (n1/2)*(N2/n2)
Efort E E=D * V

Volumul modulului, V, reprezintă conţinutul informaţional al


modulului. El descrie dimensiunea implementării unui algoritm. Calculul
lui V este bazat pe numărul de operaţii realizate şi de numărul de operanzi
cu care lucrează algoritmul. Astfel determinat, volumul unei funcţii trebuie
să rezulte între valorile 20 şi 1000. Un volum mai mare de 1000 reprezintă o
indicaţie că funcţia respectivă conţine mult prea multe prelucrări. Volumul
unui fişier cu cod sursă trebuie să se găsească între 100 şi 8000.
Complexitatea în sens Halstead CH este dată de relaţia:
CH = N1 log2N1 + N2 log2 N2
Astfel, pentru secvenţa:

a = 7;
b = 8;
c = a + b;
d = a * c + b * ( a – 1 )

prin numărare operanzi şi operatori N1 = 10, N2 = 13.


CH = 10 log2 10 + 13 log2 13
deoarece cele două paranteze sunt un singur operand.
În secvenţa:
a += b++ + c++;
a*=--b - --c;

numărul operanzilor N2 = 6, iar numărul operatorilor N1 = 10, ceea ce


conduce la complexitatea
CH = 10 log2 10 + 6 log2 6

35
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Pentru programe de mari dimensiuni este preferabil să se utilizeze


instrumente care numără operanzii, respectiv operatorii, permiţând
automatizarea procesului de măsurare a complexităţii în sens Halstead.

2.4 Etapele ciclului de dezvoltare

Ciclul de viaţă al unui produs software este, conform [MIHAL98],


intervalul de timp de la momentul deciziei de realizare şi până la retragerea
sau înlocuirea totală a acestuia cu un produs software, reprezentând
orizontul de timp în care operează şi evoluează produsul program. Ciclul de
dezvoltare este parte a ciclului de viaţă al unui produs software şi este
definit ca totalitatea etapelor pe care le parcurge un produs software de la
idee, până la utilizarea lui de către beneficiar. Acest interval, împărţit în
general în faze, presupune definirea şi analiza problemei, proiectarea
produsului software, implementarea de cod, testarea şi experimentarea
acestuia, deci reprezintă o parte a ciclului de viaţă şi anume cea care are ca
scop dezvoltarea produsului. Ciclul de viaţă adaugă ciclului de dezvoltare
etapa de utilizare, etapa de mentenanţă, precum şi momentul scoaterii din
uz, datorită uzurii morale a produsului software.
Etapa de definire şi analiză a problemei constă în stabilirea
informaţiilor de intrare, a rezultatelor, a modelelor. Există probleme bine
definite, când pentru toate rezultatele există modele şi date iniţiale
suficiente. Problemele subdefinite fie nu au toate modelele definite, fie
lipsesc datele iniţiale. Problemele supradefinite conţin fie modele
complementare, fie mai multe date iniţiale decât cer rezultatele. Definirea
problemei conduce la elaborarea de specificaţii ale cerinţelor produsului, ce
trebuie să fie complete, consistente şi riguroase. Aceste cerinţe trebuie
documentate şi agreate cu clientul înainte de a trece la următoarea fază.
Un document riguros de analiză cuprinde, pentru fiecare cerinţă
funcţională în parte, următoarele informaţii:
• Intrările;
• Ieşirile;
• modelele
• datele de test;
• dimensiunile volumelor de date;
• precizia rezultatelor;
• condiţii tehnice.
36
Ciclul de dezvoltare software

Etapa de proiectare constă în elaborarea soluţiei, corelând cerinţele


care rezultă din definirea problemei, cu resursele pe care le oferă limbajele
de programare, tehnologiile software existente, precum şi sistemele de
gestiune a bazelor de date. Această fază asigură programatorului suficiente
informaţii pentru ca acesta să scrie rutine software care realizează acţiuni
bine definite în cadrul aplicaţiei.
Soluţia elaborată este documentată şi prezentată clientului spre
aprobare. Ea se adresează următoarelor arii de interes în cadrul dezvoltării
produsului software:
• proiectarea interfeţei cu utilizatorul, ce reprezintă imaginea
sistemului pentru utilizator;
• proiectarea bazei de date, prin care se stabileşte cum vor fi
stocate şi structurate datele utilizate de produs;
• stabilirea acţiunilor realizate de aplicaţie sub aspectul datelor
ce sunt prelucrate şi a algoritmilor folosiţi pentru aceasta;
• identificarea şi definirea interfeţelor externe, prin care
sistemul se conectează la alte sisteme existente (automat sau
manual); stabilirea formatelor de fişiere folosite pentru
exportul şi importul de date;
• conversiile de date, ce se referă la posibilitatea ca, în cazul în
care datele existente sunt încărcate într-o aplicaţie nouă,
anumite conversii şi reformatări să fie necesare.
Pe lângă cele menţionate mai sus, designul unui produs software
precizează arhitectura generală a aplicaţiei, modul în care modulele
componente sunt combinate pentru a oferi funcţionalitatea dorită, nivelul
planificat pentru caracteristicile de calitate.
Dacă etapa de analiză a problemei spune ce trebuie să se facă, etapa de
proiectare stabileşte cum trebuie să se facă. Documentul de proiectare este
folosit de programatori şi de testeri, în etapele următoare, pentru a dezvolta
produsul şi pentru a testa dacă funcţionalitatea rezultată în urma codificării
corespunde funcţionalităţii proiectate.
Etapa de implementare a codului constă în scrierea efectivă a
produsului software. Software trebuie scris ca să respecte cerinţele
specificate în faza de analiză şi testate în timpul dezvoltării. De regulă există
standarde de programare şi recomandări de soluţii pentru problemele
comune care sunt aplicate în timpul dezvoltării produsului pentru a spori

37
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

calitatea acestuia. Principalele rezultate ale acestei faze sunt aplicaţia în


sine, documentaţia şi rezultatele testelor. Dintre activităţile de asigurare şi
control al calităţii, cele mai prezente în timpul acestei faze sunt cele de
revizuire a codului sursă şi inspecţiile de cod şi de documente.
Etapa de testare are ca obiectiv să stabilească dacă sistemul este
robust din punct de vedere tehnic, fiabil şi corespunde cerinţelor pentru care
a fost dezvoltat. Caracteristicile principale ale testării sunt:
• posibilitatea de a identifica ulterior testele realizate;
• posibilitatea de a repeta execuţia unui test;
• documentarea paşilor urmaţi în testarea unei funcţionalităţi şi
a rezultatelor obţinute.
Există câteva tipuri de testare, printre care:
• testarea fiecărei rutine software în parte;
• testare de integrare a modulelor asamblate în aplicaţia finală
sau într-un subsistem;
• testare de sistem a întregii aplicaţii pe aceeaşi platformă ca cea
pe care clientul o va folosi;
• testele de instalare prin care se verifică că procedurile şi
instrucţiunile de instalare sunt corecte;
• testele de regresie, realizate după upgrade-uri de software
(adăugări de funcţionalitate nouă, corectări de defecte);
• teste de acceptanţă, care sunt parte din faza de instalare, şi prin
care clientul verifică dacă ceea ce i se livrează corespunde cu
ceea ce a cerut.
Testarea reprezintă o arie extrem de complexă căreia, de cele mai
multe ori, i se acordă insuficientă atenţie. Trebuie înţeles că oricât de mult s-
ar testa un produs, nu se poate garanta că aplicaţia este complet lipsită de
defecte. Costul testării trebuie analizat prin prisma costului potenţial al
funcţionării proaste, prin prisma defectelor software. Pentru aplicaţiile
necritice, este acceptabilă de exemplu, lansarea unei versiuni beta pentru ca
utilizatorii să o testeze în producţie. O astfel de abordare nu este acceptată în
cazul sistemelor software critice.

38
Ciclul de dezvoltare software

Etapa de implementare reprezintă predarea oficială a produsului


împreună cu documentele asociate clientului. Procesul efectiv de instalare
presupune:
• instalarea software pe sistemele de calcul ale clientului;
• executarea testelor de acceptanţă împreună cu clientul;
• semnarea unor documente prin care se acceptă produsul de
către client;
• pregătirea viitorilor utilizatori ai sistemului pentru folosirea
acestuia.
Etapa de mentenanţă, deşi nu face parte din ciclul de dezvoltare
software, este o etapă extrem de importantă a ciclului de viaţă, pe care nici o
companie producătoare de software nu trebuie să o neglijeze. Instalarea
produsului şi predarea lui către client nu înseamnă că din acel moment,
producătorul software nu mai are nici o legătură cu produsul. Mentenanţa
include următoarele activităţi:
• rezolvarea problemelor care apar în timpul rulării aplicaţiei în
mediul de producţie – detectarea, analiza şi corectarea
defectelor software;
• modificarea interfeţelor – determinată de adăugări sau
schimbări la platforma hardware pe care rulează software;
• extinderea funcţionalităţii sau îmbunătăţirea performanţelor –
clientul solicită modificări după ce livrarea iniţială şi instalarea
au fost realizate.
Perioada de mentenanţă care urmează imediat instalării este inclusă ca
parte a unui contract de garanţie furnizat de producătorul software.
Mentenanţa pe termen lung are un alt statut în cadrul relaţiei client –
producător software.
Pentru ca un proiect de dezvoltare să fie un succes, o abordare
sistematică este necesară. Proiectul trebuie împărţit în faze, fiecare cu un
obiectiv clar. Munca efectuată în cadrul fiecărei faze furnizează
dezvoltatorului informaţii necesare pentru a estima efortul din faza
următoare. Costul total al proiectului este, astfel, mai scăzut decât estimarea
realizată la începutul proiectului.
Nu toate proiectele urmează acest ciclu de dezvoltare. Dimensiunea
proiectului, în termeni de cost şi efort necesar, şi natura proiectului

39
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

determină ce faze sunt relevante şi ce efort suplimentar trebuie alocat pentru


fiecare fază.
Efectele pe care le generează un produs sunt multiplicate cu numărul
de utilizatori simultani ai produsului. Este necesar ca activitatea de
dezvoltare software să aibă la bază procese de optimizare dinamică, adică de
alegere din aproape în aproape a acelor resurse ce în final asigură realizarea
de produse de foarte bună calitate.

40
3.1 Specificaţiile de programare

Elaborarea de programe a depins şi va depinde în continuare de


resursele hardware şi software existente la un moment dat. Elaborarea
specificaţiilor este un moment extrem de important în ciclul de elaborare a
programelor. Specificaţiile sunt într-un context mai larg, descrieri
sistematice, riguroase a rezultatelor finale care trebuie obţinute, a datelor
iniţiale şi, respective, a formulelor de calcul. Specificaţiile definesc cadrul în
care se dezvoltă un program, prin facilităţi, restricţii. Mult mai târziu au
apărut problemele legate de grupurile ţintă, de obţinerea unor produse de
înaltă calitate.
Elaborarea specificaţiilor presupune un nivel de experienţă ridicat, o
bună capacitate de analiză a proceselor şi calităţi de cuprindere a
elementelor generale, în succesiunea lor logică. După ani lungi de
experienţă în programare s-a pus problema elaborării unor sisteme
formalizate, neambigui pentru elaborarea de specificaţii.
Există mari deosebiri între specificaţiile necesare pentru o problemă
de calcule ştiinţifice şi specificaţiile pentru dezvoltarea de software pentru
contabilitate, de exemplu. În primul caz, o formulă sau un set de formule
stabilesc riguros care sunt rezultatele şi care sunt datele de intrare. În cazul
problemelor economice, de exemplu, există legi, regulamente, există o
experienţă acumulată, există specialişti. Construirea modelelor de calcul,
stabilirea rapoartelor finale şi a datelor iniţiale reprezintă o artă,
specificaţiile sunt producţiile unor artişti.
Dacă de exemplu, trebuie soluţionată o problemă de statistică, în
sensul calculului mediei aritmetice ponderate se definesc:
• datele de intrare:
¾ n – numărul de grupe;
¾ fi – frecvenţa de apariţie a datelor din grupa i;
¾ xi – nivelul grupei i pentru variabila x.
41
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

• datele de ieşire:
¾ xm – nivelul mediei aritmetice.
• domeniile de variaţie:
n ∈ {5,6,....,1000}
x i ∈ [−1000;1000]
f i ∈ [0;100] ∩ N
x m ∈ [−10000;10000]
• formule de calcul:
n

∑f i * xi
xm = i =1
n

∑f i =1
i

Pentru a analiza calitatea specificaţiilor se construiesc tabelele 3.1 şi


3.2 în care sunt stabilite relaţiile între elementele definite.

Dependenţa între variabila endogenă şi variabilele exogene


Tabelul 3.1
Variabila endogenă xm
Variabilele exogene
n *
fi *
xi *

Utilizarea în formule a variabilelor


Tabelul 3.2
Variabile xm n fi xi
Formula
n
* * * *
∑f i * xi
xm = i =1
n

∑fi =1
i

42
Abordarea clasică

Având în vedere că în tabelele 3.1 şi 3.2 nu există linii sau coloane


fără asteriscuri, rezultă că definirile legăturilor dintre date de intrare şi
rezultate sunt complete. Asteriscurile marchează prezenţa unei variabile în
formulă sau legătura dintre variabilele exogene şi variabila endogenă, aşa
cum rezultă din formulă.
Specificaţiile tratează şi cazurile de excepţie.
Pentru f1 = f2 = ..... = fn = 0, xm = 0.
În specificaţii sunt prezentate date de test, pentru seturile din tabelele
3.3, 3.4 şi 3.5 şi nivelurile variabilei xm.

Set de date identice


Tabelul 3.3
Nr. crt fi xi
1 10 10
2 10 10
.....
15 10 10
xm = 10

Serii de date compensate simetric


Tabelul 3.4
Nr. crt fi xi
1 10 -15
2 8 -13
3 6 -11
4 4 -9
5 2 -7
6 2 +7
7 4 +9
8 6 +11
9 8 +13
10 10 +15
xm = 0

43
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Serii de date recursiv dependente


Tabelul 3.5
Nr. crt fi = fi-1 + 2 xi = 4*fi - 1
1 1 3
2 3 11
3 5 19
4 7 27

Datele din tabele se construiesc aşa fel încât să existe o formulă de


calcul pentru ele ca verificarea corectitudinii prelucrărilor realizate de
program să fie mult mai uşoară, ca în exemplul datelor din tabelul 3.6

Serii de date generate


Tabelul 3.6
Nr. crt fi = 2*i + 1 xi = i 2 + 1
1 3 2
2 5 5
3 7 10
…………
N 2*n + 1 n2 + 1

Aplicând relaţia:
n

∑f i * xi
xm = i =1
n

∑fi =1
i

pentru datele din tabelul 3.6 rezultă:


n

∑ (2i + 1)(i + 1)
xm = i =1
n

∑ (2i + 1)
i =1

după efectuarea calculelor rezultă:


n 2 + 6n + 8
xm =
3(n + 2)

44
Abordarea clasică

Se construieşte o procedură care generează seriile fi, xi şi calculează


xm. Prin apelul programului de calcul pentru media aritmetică cu aceste date
generate se obţine un rezultat x`m. Dacă x`m = xm, rezultă că programul scris
pentru calculul mediei aritmetice este corect în raport cu setul de date pentru
care a fost testat.
Specificaţiile de programare sunt construcţii care sistematizează
informaţii extrem de variate. Cei care elaborează specificaţiile au rol
hotărâtor în definirea contextului prin restricţiile asupra variabilelor, prin
formulele construite şi prin informaţiile care însoţesc algoritmii de
prelucrare.
Absenţa unor restricţii, definirea incorectă a domeniilor, construirea
de formule incomplete generează probleme serioase procesului de elaborare
a programelor. În contextul actual sunt soluţionate multe dintre problemele
legate de elaborarea de specificaţii. În contextul primelor abordări, în ceea
ce priveşte scrierea de programe, programatorul era singurul care îşi definea
datele de intrare, definea rezultatele şi crea sau prelua algoritmi. Fără o
gândire sistematică, fără instrumente adecvate, specificaţiile includeau o
serie de inexactităţi sau eliminau o serie de aspecte, ceea ce reducea aria de
cuprindere a programelor. Arta elaborării de programe îşi avea izvorul în
arta de a elabora specificaţii.

3.2 Preluarea algoritmilor

Rezolvarea oricărei probleme are la bază un algoritm, dat fie printr-o


formulă, fie prin paşi care descriu exact care sunt prelucrările. Pentru o
aceeaşi problemă există mai mulţi algoritmi. Trebuie ales acela care
îndeplineşte unul dintre criteriile:
• volumul de prelucrări este cât mai redus;
• volumul de prelucrări depinde de particularităţile pe care le
prezintă datele de intrare; de exemplu, datele sortate crescător
deja trebuie să genereze un volum minim de prelucrări în cazul
algoritmului de sortare crescătoare;
• numărul rezultatelor intermediare care trebuie stocate în
memorie să fie cât mai redus pentru a asigura dimensiuni cât
mai mari problemelor de rezolvat;
• volumul prelucrărilor să fie cât mai redus, cu accent pe
scăderea numărului de operaţii de înmulţire sau de împărţire.

45
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Programatorii preiau algoritmii deja analizaţi din punct de vedere a


calităţii soluţiei sau construiesc algoritmi proprii. Pentru verificarea unui şir
x1, x2, ...., xn care are componentele ordonate crescător se construiesc de
către programator doi algoritmi:
Algoritmul A1:
• pasul 1 : se iniţializează variabila iv cu 0;
• pasul 2 : se traversează şirul;
• pasul 3 : se compară xi cu xi+1;
• pasul 4 : dacă xi > xi+1, se incrementează variabila iv;
• pasul 5 : după încheierea procesului de comparare se testează
variabila iv;
• pasul 6 : dacă iv = 0, se afişează mesajul „şirul este ordonat”,
în caz contrar se afişează mesajul „şirul nu este ordonat”.
Algoritmul A2:
• pasul 1 : se traversează şirul;
• pasul 2 : se compară xi cu xi+1;
• pasul 3 : dacă xi > xi+1 se întrerupe traversarea şi se afişează
mesajul „şirul nu este ordonat”;
• pasul 4 : dacă traversarea se încheie normal, se afişează
mesajul „şirul este ordonat”.
Este important să se construiască algoritmi compleţi şi corecţi pentru
problemele de rezolvat şi abia după aceea să se urmărească îmbunătăţirea
lor, fără a accepta conceptul de algoritm optim. Preluarea algoritmilor este o
necesitate şi modul concret prin care se realizează constă în:
• asimilarea unei mulţimi de algoritmi de bază, pentru operaţii
de prelucrare bine definite;
• alegerea algoritmilor care se potrivesc clasei de probleme în
raport cu structurile de date utilizate; într-un fel sunt concepuţi
algoritmii care lucrează cu fişiere, altfel sunt construiţi
algoritmii care operează pe masive;
• combinarea de algoritmi pentru a obţine fluxurile de prelucrare
specifice soluţionării unei probleme.

46
Abordarea clasică

Există mai multe moduri de descriere a algoritmilor, dintre care sunt


prezentate în continuare următoarele:
• forma text, în care operaţiile şi operanzii sunt daţi sub formă
de fraze precum: „se iniţializează de la tastatura...”; „rând pe
rând se adună fiecare element”; „se compară element cu
element....”; „primul element se interschimbă cu ultimul, al
doilea se interschimbă cu penultimul, procedeul continuă în
acelaşi fel până când...”.
• limbaj de descriere a operaţiilor în succesiunea efectuării –
pseudo-cod :
x1 = 2
xi = xi-1, i = 2, 3, ...., n
sau
x1 = 0
x2 = 1
xi = xi – 1 + xi-2, i = 2, 3, ....., n
sau
dacă xi > xi+1 interschimb xi cu xi+1
• schemele logice, care reprezintă o transcriere grafică a etapelor
(paşilor) unui algoritm.
Limbajul utilizat în descrierea algoritmului trebuie să conducă la
elaborarea de programe, fără confuzii, care prin testare să evidenţieze că
pentru date de intrare complete şi corecte se obţine rezultatul corect indicat.

3.3 Elaborarea schemelor logice

Schema logică reprezintă o modalitate uzuală de reprezentare a


algoritmilor folosind simboluri precum cele date în figura 3.1:

Figura 3.1 Simboluri pentru scheme logice

47
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

De asemenea, schemele logice includ conectori:

şi arce orientate
Nivelul de detaliere depinde de modul de înţelegere a algoritmului, de
rolul pe care îl are cel care elaborează schema logică şi persoana care
elaborează textul sursă. Între schema logică şi program trebuie să existe o
corespondenţă clară în ceea ce priveşte numele variabilelor, etichetelor
întrebuinţate şi formulelor de calcul.
Pentru aflarea elementului minim dintr-un şir şi a poziţiei acestuia se
utilizează variabilele:
n - numărul de componente al vectorului x[];
x[i] - componenta i a vectorului;
xmin - valoarea elementului minim;
pozmin - poziţia elementului minim;
i - variabilă de control.

48
Abordarea clasică

Schema logică este dată în figura 3.2:

Figura 3.2 Schema logică pentru aflarea minimului şi poziţiei

49
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Pentru a verifica dacă o matrice este simetrică se construieşte schema


logică din figura 3.3.

Figura 3.3 Schema logică pentru verificarea simetriei unei matrice

50
Abordarea clasică

Pentru numărarea componentelor nule, negative şi pozitive ale unui


masiv a[m][n] bidimensional se elaborează schema logică din figura 3.4.
START

Citeste m,n

Citeste a[i][j],
i=1, …, m
j=1,…..,n

nrnul = 0
nrpoz = 0
nrneg = 0

i=0

e1

j=0

e2

Da
a[i][j] > 0 e3 nrpoz = nrpoz + 1 e5

Da
a[i][j] < 0 e4 nrneg = nrneg + 1 e5

nrnul = nrnul + 1

51
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Figura 3.4 Schema logică pentru numărarea elementelor negative,


pozitive şi nule

Schemele logice clasice corespund unui stadiu incipient al dezvoltării


limbajelor de programare. De exemplu, limbajul FORTRAN este înzestrat
cu instrucţiunea IF cu sintaxa:
IF ( expresie ) e1, e2, e3
unde:
e1 - eticheta instrucţiunii care se execută dacă expresia este negativă;
e2 - eticheta instrucţiunii care se execută dacă expresia este nulă;
e3 - eticheta instrucţiunii care se execută dacă expresia este pozitivă.

52
Abordarea clasică

O astfel de construcţie impune utilizarea de instrucţiuni cu etichete şi


continuarea prelucrării prin efectuarea unui salt necondiţionat la secvenţa cu
eticheta e4, figura 3.5.

Figura 3.5 Secvenţa cu salturi condiţionate complete

53
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Pentru numărarea elementelor nule, pozitive şi negative ale unui


masiv bidimensional se construieşte secvenţa de schemă logică dată în
figura 3.6.

Figura 3.6 Schemă logică pentru scrierea de programe în limbajul FORTRAN

54
Abordarea clasică

Pe măsură ce se dezvoltă limbajele de programare, în schemele logice


apar modificări. Programul scris în C++ după schema logică din figura 3.4
este:
#include <stdio.h>

#define M 10
#define N 10

int main () {
int i, j, m, n, a[M][N];
int nrplus, nrnegativ, nrnul;

nrplus = nrnegativ = nrnul = 0;


printf( "Introduce numarul de linii (<%d) = ", M );
scanf( "%d", &m );
printf( "Introduce numarul de coloane (%d) = ", N );
scanf( "%d", &n );

i = 0;
et11:

if ( i < m ) {
j = 0;
et12:
if ( j < n ) {
printf("a[%d][%d] = ", i, j );
scanf("%d", &a[i][j] );
j++;
goto et12;
}
i++;
goto et11;
}

i = 0;

et1:
if ( i < m ) {
j = 0;
et2:
if ( j < n ) {
if ( a[i][j] > 0 ) {
nrplus++;
} else {
if ( a[i][j] < 0 ) {
nrnegativ++;
} else {
nrnul++;
}
}
j++;
goto et2;

55
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

}
i++;
goto et1;
}

printf( "Numarul de elemente pozitive este %d\n", nrplus );


printf( "Numarul de elemente negative este %d\n", nrnegativ
);
printf( "Numarul de elemente nule este %d\n", nrnul );
}

Datorită caracterului său artizanal, tehnica nu se baza pe reutilizarea


codului. Programatorul rescria funcţionalităţi ce altfel se regăseau în
biblioteci scrise deja. Prin aceasta dimensiunea codului sursă creştea foarte
mult şi odată cu ea şi complexitatea software. De asemenea, frecvente erau
şi programele monolit, care conţineau toată funcţionalitatea în programul
principal, neexistând apeluri către subrutine.
Pentru aplicarea formulei complexităţii ciclomatice, se identifică
următoarele valori pentru parametrii acesteia:
na = 36
nn = 31

Rezultă că valoarea complexităţii ciclomatice este:


CM = 36 – 31 + 2 = 7
Valoarea obţinută este relative mare, având în vedere că complexitatea
problemei este mică, însă ea se datorează în special faptului că pe de o parte
au fost folosite instrucţiuni de salt necondiţionat, care au mărit numărul de
arce din cadrul grafului asociat, dar şi deoarece nici varianta de algoritm
implementată nu este optimă. Conform setului de valori-prag, acest program
se încadrează în categoria de risc minim.
Pentru aplicarea formulelor Halstead, se identifică următoarele valori
pentru parametrii acestora:
n1 = 5
n2 = 8
N1 = 22
N2 = 36

56
Abordarea clasică

Pentru aceste valori, aplicând formulele de calcul pentru metricile


Halstead, se obţine:
Metrică Valoare
Lungimea programului = 64
Vocabularul programului = 14
Volumul = 384
Dificultatea = 14,25
Efort = 5472

Dacă se raportează valorile obţinute la valorile-prag, se constată că ele


se încadrează în intervalele acceptate. Trebuie menţionat că, deşi limbajul
C++, folosit pentru a implementa rezolvările la problema aleasă, oferă
implementări pentru structuri repetitive (for, while), s-a ales varianta cu
instrucţiuni de salt necondiţionat specifică pentru perioada în care această
tehnică de programare era folosită. Există limbaje, cum este şi cel de
asamblare, care nu au implementări de structuri repetitive, şi prin urmare ele
trebuie scrise de către programator.
Programarea clasică are o serie de imperfecţiuni pe care programatorii
de azi nu le acceptă sau pentru care au găsit soluţii mai ales prin elaborarea
de expresii condiţionale compuse. Acum, pentru a verifica dacă o matrice
este sau nu simetrică se scrie secvenţa:
………….
k = 1;
for ( i = 0; i < m && k == 1; i++ ) {
for ( j = 0; j < n && k == 1; j++ ) {
if ( a[i][j] != a[j][i] ) k = 0;
}
}
if ( k == 1 ) printf (“matrice simetrica”);
else
printf (“matrice nesimetrica”);
………………….

Instrucţiunea if se înlocuieşte cu expresia de atribuire:


k = a[i][j] == a[j][i].
secvenţa devenind:
………………………
k = 1;
for ( i = 0; i < m && k == 1; i++ ) {
for ( j = 0; j < n && k == 1; j++ ) {
k = a[i][j] == a[j][i];
}
}
printf( k == 1 ? “matrice simetrica” : “matrice nesimetrica”);
………………….

57
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Programarea clasică are un caracter direct, având de evaluat expresii


simple şi efectuând prelucrări cu paşi mărunţi, din aproape în aproape.

3.4 Scrierea programelor

Şi acum 40 de ani, ca şi acum, programatorul scria programele în


limbajele pentru care avea compilatoare, dimensiona probleme pentru
resurse de memorie internă, proiecta fişiere încât să respecte restricţiile de
stocare pe suport intern sau extern. Şi atunci, ca şi acum, au existat restricţii.
Datele şi programele erau stocate pe benzi perforate, pe cartele, iar
programatorii care au lucrat între 1985 – 1990 ştiu că un loc la un terminal
pentru a lucra interactiv era un lux, în timp ce în ziua de azi nu poate fi
conceput în nici un chip decât lucru în reţea, la un post de lucru. Cartelele
perforate banda perforată, sunt azi piese de muzeu.
Şi acum 30 – 40 de ani, programatorii, care erau selectaţi dintre cele
mai inteligente persoane datorită restricţiilor legate de resursa calculator, au
avut de traversat aceleaşi etape ale ciclului de dezvoltare software. Şi atunci,
ca şi acum, existau mari probleme, existau preocupări de a le soluţiona şi
chiar au existat produse software de foarte bună calitate pentru calcule
economice, pentru optimizări, iar cine consideră că problemele s-au
dezvoltat odată cu posibilitatea pe care o dau monitoarele VGA, SVGA sau
altele de ultimă generaţie, limitează rezultatele prin eliminarea unor soluţii
extreme de avansate obţinute încă din anii ’50.
Întotdeauna au existat preocupări pentru programe foarte bune. Este
cunoscută colecţia de algoritmi şi programe din biblioteca ACM, publicate
şi comentate în numerele mai vechi ale revistei Communications of ACM. Şi
azi programatorii care urmăresc adevărata performanţă, consultă această
colecţie pentru a preleva rezultate dintre cele mai bune, ştiut fiind că
limbajul ALGOL a fost bază pentru tot ce este deosebit de avansat în
programarea de azi.
Deşi, limbajele de programare limitau tipurile de date pentru tipurile
fundamentale întreg, real simplă precizie şi real dublă precizie, orice
problemă avea o rezolvare prin :
• atribuirea de coduri numerice pentru şiruri de caractere;

• utilizarea exclusivă a masivelor unidimensionale sau


bidimensionale.

58
Abordarea clasică

Problema ordonării întreprinderilor după cifra de afaceri, îi


corespunde azi textul sursă:
#define N 10
#define MAX_STRING 255

int main() {
int n, i, j, min;
char* sc[N];
float cf[N];

memset(sc, 0, N);

printf( "Introduce numarul de societati comerciale (<=%d)", N


);
scanf( "%d", &n);

for ( i = 0; i < n; i++ ) {


sc[i] = (char*)malloc(MAX_STRING);
printf("Numele firmei = ");
scanf("%s", sc[i]);

printf("Cifra de afaceri = ");


scanf("%f", &cf[i]);
}

for ( i = 0; i < n - 1; i++ ) {


min = i;
for ( j = i+1; j < n; j++ ) {
if ( cf[j] < cf[i] ) {
min = j;
}
}

//muta elementul minim pe pozitia i


char* temp = sc[i];
sc[i] = sc[min];
sc[min] = temp;

float f = cf[i];
cf[i] = cf[min];
cf[min] = f;
}

for ( i = 0; i < n; i++ ) {


printf(" Societatea %s are CF %f\n", sc[i], cf[i]);
}
}

Programarea clasică soluţiona problema prin codificarea


întreprinderilor şi operarea cu doi vectori, unul de tip întreg pentru codurile
întreprinderilor şi unul de tip real pentru valorile cifrei de afaceri, obţinând

59
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

programul care, la acel moment, din cauza imposibilităţii de a construi


blocuri şi datorită absenţei instrucţiunii while, era scris folosind
instrucţiunile if şi goto care aveau menirea de a selecta situaţiile diferite, cu
efecte negative asupra fluenţei întregului demers. Se obţine textul sursă:
i = 0;

et1:
if ( i < n - 1 ) {
min = i;
j = i;

et2:
if ( j < n ) {
if ( cf[j] < cf[i] ) {
min = j;
}
j++;
goto et2;
}

//interschimba pozitia i cu pozitia min


int temp1 = sc[i];
sc[i] = sc[min];
sc[min] = temp1;

float temp2 = cf[i];


cf[i] = cf[min];
cf[min] = temp2;

i++;
goto et1;
}

i = 0;

et3:
if ( i < n ) {
printf(" Societatea %d are CF %f\n", sc[i], cf[i]);
i++;
goto et3;
}

Comparând textele sursă, devine foarte clară necesitatea acceptării


faptului că evoluţia conţinutului unui program a depins strict de resursele
care au fost la dispoziţia programatorilor. Mulţi dintre programatorii cu
părul alb sunt cei care au scris programe foarte bune în limbajul FORTRAN
4 acum 35 de ani, tot ei fiind cei care lucrează în C# sau Java cu aceeaşi
uşurinţă.

60
Abordarea clasică

3.5 Testarea şi implementarea programelor

Trebuie spus că în acele vremuri, utilizatorii calculatoarelor nu erau


nici pe departe ceea ce sunt azi. Azi, cetăţeanul dispune de calculator, de
internet. Acum 35 – 40 de ani, numai companiile importante achiziţionau
calculatoare. Existau analişti, programatori, iar operatorii din sălile cu
climatizare unde erau amplasate computerele de mărimea unor şifoniere,
erau deja persoane foarte importante. Utilizatorii de software ştiau să
perforeze cartele de date, ştiau să şi depaneze programe. Înseamnă că
programatorii rămâneau în continuare alături de produsele software pe care
le realizau.
Ceva mai târziu, programele aplicative au dezvoltat o relaţie nouă.
Utilizatorul acestor programe trebuia să ştie exact cum se dispun datele pe
cartele pentru a stabili intrări complete şi corecte.
Bibliotecile de program includeau programe de clasă A, testate sub
toate aspectele, care nu mai produceau întreruperi în execuţie fără mesaje
specifice. Programele de clasă B erau testate suficient de bine, însă, nu erau
excluse unele întreruperi cu mesaje ale sistemului de operare. Programele de
clasă C erau deja programe despre care nu se dădeau aprecieri asupra
comportamentului şi asupra riscurilor de apariţie a incidentelor de
prelucrare.
Se observă acum scăderea interesului spre dezvoltarea de tehnici de
testare probabil datorită existenţei instrumentelor de asistare a procesului de
elaborare a produselor software. Pentru testarea unui program se parcurg
mai mulţi paşi:
• se construiesc seturi de date de test pentru care se stabilesc
rezultatele pe care programul le oferă;
• se preiau din literatură exemple rezolvate folosind atât datele
de intrare ale acestora, cât şi rezultatele intermediare;
• se introduc în program instrucţiuni de afişare a rezultatelor
intermediare;
• se introduc în program instrucţiuni care permit afişarea
etichetelor instrucţiunilor care se execută.
De exemplu, programul pentru însumarea elementelor unui şir se
procedează astfel:
• se iniţializează şirul cu valorile 1,2, 3, ….., n;

61
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

• se afişează valoarea variabilei de control a şirului, valorile


şirului şi sumele intermediare;
• se afişează mesaje care arată că au fost parcurse etapele
programului.
Pentru testare, programul are textul sursă:
#define N 100
#define n 10
void main() {
int i, x[N], suma;
printf(“Generare date de test”);
for (i = 0; i < n; i++) {
x[i] = i;
}
printf(“\n Intrare in procedura”);
suma = aduna(x, n);
printf(“\n Iesire din procedura”);
printf(“\n suma = %d”, suma);
}

int aduna(int x[], int n) {


int suma = 0;
for ( i = 0; i < n; i++ ) {
suma += x[i];
printf(“Suma dupa adunarea %d element cu
valoarea %d este %d”, i, x[i], suma);
}
printf(“\n Gata”);
return suma;
}

Pe monitorul computerului pentru n = 10, se afişează:


Generare date de test
Intrare in procedura
0 0 0
1 1 1
2 2 3
3 3 6
4 4 10
5 5 15
6 6 21
7 7 28
8 8 36
9 9 45
10 10 55
Gata
Iesire din procedura
Suma = 55

62
Abordarea clasică

Din analiza acestor informaţii se verifică faptul că programul


efectuează corect prelucrările. În cazul în care se testează procedura:
int expresie( int a, int i, int k) {
int result = 0;

if ( i % 2 ) {
if ( !(i % k) ) {
printf("Numar divizibil cu %d\n", k);
result = a * a;
} else {
printf("Numar nedivizibil cu %d\n", k);
result = a;
}
} else {
printf("Numar par\n");
result = a * a * a;
}

return result;
}

pentru evaluarea expresiei:


⎧a, dacă i este impar

e = ⎨a 2, dacă i este numău impar diviyibil prin k
⎪⎩a 3, în rest

se introduce în programul principal şi în procedură instrucţiuni care afişează


informaţii despre fluxul de execuţie al programului şi din care să rezulte
programul execută ceea ce trebuie şi nu evaluarea unei alte expresii.
int expresie( int a, int i, int k) {
int result = 0;

if ( i % 2 ) {
if ( !(i % k) ) {
printf("Numar divizibil cu %d\n", k);
result = a * a;
} else {
printf("Numar nedivizibil cu %d\n", k);
result = a;
}
} else {
printf("Numar par\n");
result = a * a * a;
}

return result;
}

63
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

int main() {

int a, i, k, n, j;

printf( "Introduce numarul de seturi de date i = " );


scanf( "%d", &n );
printf( "Introduce numarul de variabile de control k =
" );
scanf( "%d", &k );

printf("i\ti*i\ti*i*i\tk\ta\texpresie\n");
for ( j = 1; j <= k; j++ ) {
for ( i = 0; i < n; i++ ) {
printf("%d\t%d\t%d\t%d\t%d\t%d\n", i,
i*i, i*i*i, j, i, expresie(i, i, j));
}
}
}

Pentru k = 7 şi n = 10, secvenţa care se afişează este:


i i*i i*i*i k a expresie
Numar par
0 0 0 7 0 0
Numar nedivizibil cu 7
1 1 1 7 1 1
Numar par
2 4 8 7 2 8
Numar nedivizibil cu 7
3 9 27 7 3 3
Numar par
4 16 64 7 4 64
Numar nedivizibil cu 7
5 25 125 7 5 5
Numar par
6 36 216 7 6 216
Numar divizibil cu 7
7 49 343 7 7 49
Numar par
8 64 512 7 8 512
Numar nedivizibil cu 7
9 81 729 7 9 9

Analizând rezultatele intermediare, rezultă că procedura efectuează


prelucrări corecte. Pentru programele cu grad de complexitate ridicată,
procedurile de testare sunt mai elaborate. Cei care elaborează specificaţiile
aveau obligaţia să furnizeze situaţiile care trebuie testate, cu luarea în
considerare a situaţiilor care în practică ofereau cazuri greu de controlat,

64
Abordarea clasică

aşteptându-se ca prin implementarea produsului software, tocmai acele erori


de prelucrare manuală să fie înlăturate.

3.6 Între artă, meşteşug şi industrie

Activitatea de programare, prin faptul că era dezvoltată de persoane


deosebit de inteligente şi avea o pondere de peste 90% din costul
produsului, a fost înconjurată de misterul creaţiei artistice, nefăcându-se
deosebiri radicale de modul în care se scriau programele şi modul în care se
realizau operele artistice. În plus, programul nu are o formă materială
asemeni unui strung sau a unei statui. Nu întâmplător, David E. Knuth şi-a
intitulat monumentala lucrare care stă la baza activităţii oricărui
programator, ART OF PROGRAMMING.
Programarea s-a învăţat prin exemple, ca de la maestru la discipol.
Numai după apariţia cărţilor de programare care au prezentat limbajele de
programare folosind multe exemple s-au conturat unele direcţii de bază.
Programarea calculatoarelor a rămas o artă atât timp cât bibliotecile de
subprograme nu au fost realităţi cotidiene. De fiecare dată programatorul
scria subprograme pe care deja le mai scrisese cândva de mai multe ori. În
plus, nu a existat încrederea în programele scrise de alţii. Se justifică această
reţinere prin:
• calitatea slabă a programelor incluse în paginile cărţilor;
• contribuţiile nefaste ale redactorilor de carte, cărora
ghilimelele li se păreau că prin înlocuire cu apostrofuri dau
frumuseţe textului, iar prin eliminarea slash-urilor(//) sau prin
includerea pe toate liniile sursă, se obţine un aspect estetic
acceptabil pentru carte; iar înlocuirea caracterului _ cu minus
deja are efecte dintre cele mai compromiţătoare.
Trecerea spre meşteşug s-a impus când au trebuit scrise multe
programe pentru a dezvolta sisteme informatice complexe. Deja există un
responsabil de proiect, deja trebuia să existe un mod de comunicare prin
specificarea structurilor de articole, dimensiunilor de matrice. Trebuia pusă
ordine în modul în care sunt prelucrate datele pentru ca un program să nu
distrugă informaţii pe care un alt program trebuie să le preia. Discuţiile
dintre analişti, discuţiile dintre programatori au cristalizat tehnici de analiză
şi stiluri de programare.

65
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

A început perioada definirii unor reguli care să conducă la creşterea


calităţii programelor. De la caracterul meşteşugăresc spre caracterul
industrial era doar un pas. Exagerările legate de transferul mecanic a unor
elemente de structură şi de management din industria prelucrătoare spre
producţia de software a avut un singur efect: compromiterea ideii de
industrie de software. Lucrurile apar acum cu totul sub altă lumină în
contextul organizaţiei virtuale de producţie software pentru că:
• posturile de lucru sunt legate prin reţea;
• accesul la resurse este dat de facilităţile internetului;
• instrumentele de dezvoltare a aplicaţiilor distribuite sunt o
realitate;
• concepţia dezvoltării de aplicaţii complete care să aducă
acoperire avantajoasă pentru cetăţean în planul fluxurilor
materiale şi a fluxurilor băneşti este deja o realitate.
Aşa cum societatea umană a trecut prin comuna primitivă, epoca
bronzului, epoca fierului, prin sclavagism, feudalism, capitalism, comunism
şi retur, programarea calculatoarelor a reflectat salturile spectaculoase din
domeniul hardware. În software nu există penurie de idei. Există numai
restricţii în plan tehnic. De la o etapă la alta, pe măsură ce barierele sunt
doborâte, se obţin şi salturi calitative în producţia de software; iar ceea ce se
întâmplă an de an este o evoluţie normală şi necesară care ajută dezvoltarea
societăţii informaţionale.

66
4.1 Reguli de construire

Acumularea experienţei de către programatori a permis gruparea de


subprograme în biblioteci. În primul rând, programele de bibliotecă sunt
toate scrise într-un acelaşi limbaj de programare. În al doilea rând, toate
programele sunt regrupate astfel încât să conducă la rezolvarea de clase de
probleme bine conturate. Bibliotecile matematice au programe de calcul
polinomiale, de calcule matriceale, de rezolvare de sisteme de ecuaţii şi de
implementare a algoritmilor analizei numerice pentru calcul diferenţial.
Bibliotecile statistice conţin programe de analiză dispersională, de
covarianţă, de regresie şi de calcul a indicatorilor statistici. Bibliotecile de
programe sunt astfel proiectate încât să permită lansarea în execuţie a
programelor, cât mai uşor posibil.
În al treilea rând, programele de bibliotecă au interfeţe prietenoase
care asigură definirea problemelor de către utilizatori, introducerea de date
şi alegerea de opţiuni în vederea obţinerii rezultatelor dorite.
În al patrulea rând, bibliotecile de programe conţin programe testate
din toate punctele de vedere, încât să existe garanţia că acestea rezolvă
corect problemele. Pentru a dezvolta o bibliotecă de programe se elaborează
un plan riguros care include:
• obiectivul bibliotecii;
• limbajul de programare utilizat;
• algoritmii de prelucrare pentru care se elaborează programe;
• nivelul de testare al programelor;
• gradul de generalitate atins;
• precizia rezultatelor;
• dimensiunile problemelor de rezolvat;
• modul de tratare a erorilor.

67
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Regulile de construire a bibliotecilor de programe sunt stricte pentru a


se obţine programe de foarte bună calitate. Prima regulă constă în acceptarea
unui stil de programare caracterizat prin:
• modul de alegere a numelor de variabile;
• modul în care sunt iniţializate variabilele;
• construirea etichetelor şi dispunerea lor, de exemplu, în ordine
crescătoare e10, e20, ... , e300;
• utilizarea instrucţiunilor pentru a asigura un mod unitar de
interpretare; astfel, dacă se adoptă un mod de parcurgere a unui
masiv unidimensional cu ajutorul instrucţiunii for, atunci
aceasta este aplicată în toate cazurile din cadrul programului,
evitându-se introducerea unor parcurgeri cu while sau chiar
instrucţiuni de salt necondiţionat, goto;
• separarea instrucţiunilor de citire şi scriere de celelalte
subprograme pentru a nu influenţa procesul de mentenanţă
atunci când se doreşte efectuarea de modificări în program, dar
şi pentru a obţine creşterea flexibilităţii în ceea ce priveşte
iniţializarea variabilelor;
• stabilirea structurilor de date utilizate pentru a obţine
maximizarea gradului de utilizare a memoriei alocate; de la un
program la altul, pentru aceleaşi prelucrări se folosesc aceleaşi
structuri de date, denumirile lor nefiind diferite, mai ales, în
ceea ce priveşte pointerii cu care se referă componentele;
• asigurarea continuităţii atât în ceea ce priveşte interfeţele, cât
şi în ceea ce priveşte modul de elaborare a programelor.
Biblioteca de programe trebuie să apară ca un întreg, format
din componente omogene – programele.
Pe măsură ce s-au impus bibliotecile de programe, dezvoltatorii de
software au dezvoltat şi biblioteci de subprograme, cele mai multe
subprograme dezvoltând funcţii de prelucrare fundamentale în raport cu
asigurarea calităţii şi mai ales cu creşterea generalităţii programelor. Şi
subprogramele de bibliotecă sunt elaborate pe baza unor reguli, din care mai
importante sunt:
• construirea numelui de subprogram în aşa fel încât să se reţină
cu uşurinţă şi să se intuiască semnificaţia prelucrării; de
exemplu, invmat() este subprogramul pentru inversarea de

68
Programarea standard

matrice, sortstring() este subprogramul pentru sortarea unui


şir, sortfile() este subprogramul pentru sortarea unui fişier,
prodpol() este subprogramul pentru produsul a două
polinoame;
• pentru tipul rezultatului returnat se indică de fiecare dată
semnificaţia;
• nivelul de autodocumentare este foarte ridicat, fiind incluse
comentarii legate de semnificaţia parametrilor, a secvenţelor
conţinând elemente de identificare a programatorului şi a
algoritmului utilizat;
• listele de parametrii au elemente comune care să fie uşor
interpretate şi mai ales să fie uşor de construit şi de iniţializat
pentru a efectua un apel corect pentru fiecare subprogram;
• se stabileşte regula privind modul de revenire în programul
principal; într-un fel este privit programul ce conţine o singură
instrucţiune return, faţă de programul care are mai multe
instrucţiuni return.
Şeful de proiect al unei biblioteci de programe sau de subprograme,
când constituie echipa de analişti şi programatori prezintă reguli, care sunt
adoptate, chiar cu unele modificări. După ce regulile sunt adoptate, devin
obligatorii şi toate programele sau subprogramele sunt elaborate cu
utilizarea acestor reguli. Prin respectarea regulilor, biblioteca de programe
este o construcţie unitară, operaţională şi, mai ales, care este utilizată de toţi
cei care dezvoltă software, datorită economiei de efort pe care o generează.

4.2 Subprogramul

Programarea standard are la bază lucru cu subprograme performante,


stocate în biblioteca de subprograme. Programatorul are menirea de a
selecta cele mai potrivite subprograme pentru a soluţiona fiecare problemă
în modul cel mai eficient. Pentru problema definită în capitolul Ciclul de
dezvoltare software se consideră o bibliotecă de subprograme care
operează cu masive unidimensionale.
Regulile după care se construiesc subprogramele sunt următoarele:
• numărul de componente ale şirului este n;
• elementele şirului sunt x[0], x[1], …, x[n-1];

69
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

• denumirea subprogramului arată operaţia şi operandul de bază;


• variabila i permite referirea elementelor unui şir sau referă
liniile unui masiv bidimensional;
• variabila j referă elementele unei coloane din masivul
bidimensional;
• masivul bidimensional este referit prin numele a[M][N].
Biblioteca de subprograme BVECTMAT se organizează pe două
niveluri:
• nivelul de bază conţine proceduri de lucru cu masive
unidimensionale (vectori);
• nivelul derivat conţine proceduri de lucru cu masive
bidimensionale văzute ca vectori de vectori, folosind
subprogramele de lucru cu vectori.
Pentru copierea elementelor unei linii k dintr-un masiv bidimensional
a[M][N] într-un masiv unidimensional x[N], se foloseşte subprogramul:
void copylinvect( int a[][N], int x[], int n, int k ) {
for ( int j = 0; j < n; j++ ) {
x[j] = a[k][j];
}
}

Pentru copierea unei coloane k a matricei a[M][N] în vectorul x[M] se


foloseşte subprogramul:
void copycolvect( int a[][N], int x[], int m, int k ) {
for ( int i = 0; i < m; i++ ) {
x[i] = a[i][k];
}
}

Pentru însumarea elementelor unui vector se foloseşte subprogramul:


int sumvect( int x[], int n ) {
int sum = 0;

for ( int i = 0; i < n; i++ ) {


sum += x[i];
}

return sum;
}

70
Programarea standard

Pentru produsul scalar a vectorilor x[N], y[N] se defineşte


subprogramul:
int prodscalar ( int x[], int y[], int n ) {
int prod = 0;

for ( int i = 0; i < n; i++ ) {


prod += x[i] * y[i];
}

return prod;
}

Pentru copierea unui vector x[N] pe linia k a unei matrice a[M][N] se


utilizează subprogramul:
void copyvectlin( int a[][N], int x[], int n, int k ) {
for ( int j = 0; j < n; j++ ) {
a[k][j] = x[j];
}
}

Pentru copierea vectorului x[M] pe coloana k a matricei a[M][N] se


foloseşte subprogramul:
void copyvectcol( int a[][N], int x[], int m, int k ) {
for ( int i = 0; i < m; i++ ) {
a[i][k] = x[i];
}
}

Pentru numărarea elementelor unui şir x[] mai mari decât valoarea k,
se foloseşte subprogramul:
int contorgreater ( int x[], int n, int k ) {
int contor = 0;

for ( int i = 0; i < n; i++ ) {


if ( x[i] > k ) {
contor++;
}
}

return contor;
}

71
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Pentru numărarea elementelor şirului x[] egale cu o valoare k se


utilizează subprogramul:
int contorequal ( int x[], int n, int k ) {
int contor = 0;

for ( int i = 0; i < n; i++ ) {


if ( x[i] == k ) {
contor++;
}
}

return contor;
}

Pentru a contoriza elementele şirului mai mici decât o valoare k, se


scrie subprogramul:
int contorless ( int x[], int n, int k ) {
int contor = 0;

for ( int i = 0; i < n; i++ ) {


if ( x[i] < k ) {
contor++;
}
}

return contor;
}

Iniţializarea prin atribuire a elementelor vectorului x[] cu o valoare k


se realizează prin procedura:
void initvect ( int x[], int n, int k ) {
for ( int i = 0; i < n; i ++ ) {
x[i] = k;
}
}

Pentru iniţializarea componentei de pe poziţia poz a şirului x[] cu o


valoare k se utilizează procedura:
void initpoz ( int x[], int poz, int k ) {
x[poz] = k;
}

Pentru realizarea procedurilor derivate se utilizează procedeul de lucru


cu masive unidimensionale. Astfel, generarea matricei unitate linie de linie
presupune conturarea unei structuri repetitive de tip for dată în figura 4.1.

72
Programarea standard

void genunit ( int a[][N], int m, int n ) {


int i, j;
for ( i = 0; i < m; i++ ) {

}
}

Figura 4.1 Funcţia de generare a matricei unitate


folosind funcţii de lucru cu vectori

Procedura completă are textul sursă:


void genunit (int a[][N], int m, int n ) {
int x[N];

for ( int i = 0; i < m; i++ ) {


initvect(x, n, 0);
initpoz(x, i, 1);
copyvectlin(a, x, m , n, i);
}
}

Subprogramul pentru scăderea a două matrice este:


void scadmat(int a[][N], int b[][N], int m, int n) {
int i, x[N], y[N], z[N];

for ( i = 0; i < m; i++ ) {


copylinvect(a, x, m, n, i);
copylinvect(b, y, m, n, i);
scadvect(x, y, z, n);
copyvectlin(a, z, m, n, i);
}
}

73
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Subprogramul scadvect() care evaluează expresiile z[i] = x[i] – y[i],


are textul sursă:
void scadvect(int x[], int y[], int z[], int n) {
int i;
for ( i = 0; i < n; i++ ) {
z[i] = x[i] - y[i];
}
}

Pentru operaţia de adunare a două matrice a şi b cu rezultat în


matricea c, se construieşte subprogramul:
void adunmat( int a[][N], int b[][N], int m, int n) {
int i, x[N], y[N], z[N];

for ( i = 0; i < m; i++ ) {


copylinvect(a, x, m, n, i);
copylinvect(b, y, m, n, i);
adunvect(x, y, z, n);
copyvectlin(a, z, m, n, i);
}
}

Subprogramul adunvect() care evaluează expresiile z[i] = x[i] + y[i],


are textul sursă:
void adunvect(int x[], int y[], int z[], int n) {
for ( int i = 0; i < n; i++ ) {
z[i] = x[i] + y[i];
}
}

Dacă trebuie scris subprogramul pentru transpunerea unei matrice


a[][N], obţinându-se matricea b[][M], procedura corespunzătoare este:
void transpune ( int a[][N], int b[][N], int m, int n) {
int j;
int x[N];

for ( j = 0; j < n; j++ ) {


copycolvect(a, x, m, j);
copyvectlin(b, x, m, j);
}
}

74
Programarea standard

Pentru produsul a două matrice se scrie procedura:


void prodmat( int a[][N], int b[][P], int c[][P], int m, int
n, int p) {
int i, j;
int x[N], y[N];

for ( i = 0; i < m; i++ ) {


copylinvect(a, x, n, i);
for ( j = 0; j < n; j++ ) {
copycolvect(b, y, n, j);
c[i][j] = prodscalar(x, y, n);
}
}
}

Iniţializării de la tastatură a elementelor vectorului x[N] îi corespunde


procedura:
void citvect (int x[], int n) {
for ( int i = 0; i < n; i++ ) {
printf("x[%d] = ", i);
scanf("%d", &x[i]);
}
}

Pentru afişarea elementelor unui şir se scrie procedura:


void scrievect (int x[], int n) {
for ( int i = 0; i < n; i++ ) {
printf("x[%d] = %d", i, x[i]);
}
}

Iniţializarea unei matrice de la tastatură linie de linie se efectuează


prin procedura:
void citmat(int a[][N], int m, int n) {
int i, x[N];
for ( i = 0; i < m; i++ ) {
citvect(x, n);
copyvectlin(a, x, n, i);
}
}

75
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Procedura pentru numărarea elementelor pozitive, nule respectiv


negative ale unei matrice se realizează cu ajutorul procedurilor:
int contorplus (int a[][N], int m, int n) {
int i, x[N];
int contor = 0;

for ( i = 0; i < m; i++ ) {


copylinvect(a, x, n, i);
contor += contorgreater(x, n, 0);
}

return contor;
}

int contorminus (int a[][N], int m, int n) {


int i, x[N];
int contor = 0;

for ( i = 0; i < m; i++ ) {


copylinvect(a, x, n, i);
contor += contorless(x, n, 0);
}

return contor;
}
int contorzero (int a[][N], int m, int n) {
int i, x[N];
int contor = 0;

for ( i = 0; i < m; i++ ) {


copylinvect(a, x, n, i);
contor += contorequal(x, n, 0);
}

return contor;
}

Pentru rezolvarea problemei PROB formulate în capitolul Ciclul de


dezvoltare software, programul apelator va conţine:
• apelul subprogramului citmat() pentru iniţializarea de la
tastatură;
• apelul subprogramului contorplus() pentru contorizare
elemente mai mari decât k = 0;
• apelul subprogramului contorzero() pentru numărare elemente
egale cu 0;

76
Programarea standard

• apelul subprogramului contorminus() pentru numărarea


elementelor mai mici decât k = 0;
• apelul unui subprogram pentru afişarea valorilor nrplus,
nrminus şi nrzero cum este următoarea:
void printint (char w[], int x) {
printf(" %s %d\n", w, x);
}

Se observă că programul apelator conţine exclusiv definiri de operanzi


şi apeluri de funcţii.
int main() {
int a[M][N];
int m, n;
int nrplus, nrminus, nrnul;

printf("Introduce numarul de linii = ");


scanf("%d", &m);

printf("Introduce numarul de coloane = ");


scanf("%d", &n);

citmat(a, m, n);
nrplus = contorplus(a, m , n);
nrminus = contorminus(a, m, n);
nrnul = contorzero(a, m, n);

printint("Numarul de elemente pozitive este ",


nrplus);
printint("Numarul de elemente negative este ",
nrminus);
printint("Numarul de elemente zero este ", nrnul);

return 1;
}

Pentru aplicarea formulei complexităţii ciclomatice pentru programul


apelator (funcţia main) se identifică următoarele valori pentru parametri:
na = 11
nn = 12

Rezultă că valoarea complexităţii ciclomatice este:


CM = 11 – 12 + 2 = 1

77
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

O astfel de valoare are mai multe semnificaţii:


¾ datorită atomicizării acţiunilor, şi grupării lor în biblioteci de
programe şi subprograme, construirea unui produs software
este mult simplificată, ajungându-se la situaţia ideală de a avea
de scris doar o succesiune de apeluri de rutine pentru
implementarea unei funcţionalităţi; din acest punct de vedere
complexitatea produsului este redusă, mai ales în condiţiile
unei bune documentări a rutinelor folosite;
¾ dacă bibliotecile sunt concepute în cadrul aceluiaşi proiect ca
şi produsul software, atunci valoarea complexităţii este
înşelătoare, pentru că ea nu exprimă şi complexitatea ascunsă a
rutinelor folosite.
În cazul în care se calculează şi complexităţile ciclomatice ale
rutinelor apelate din cadrul programului apelator, se obţin următoarele
rezultate pentru parametrii formulei şi respectiv pentru complexitate:
Rutină na nn Complexitate
main 11 12 1
citmat 5 4 3
contorplus 7 6 3
contorminus 7 6 3
contorzero 7 6 3
printint 0 1 1

Complexitatea totală este 14, cu următoarele ipoteze:


¾ instrucţiunea for este considerată o singură instrucţiune şi nu
trei;
¾ nu s-a mers mai departe de primul nivel de derivare, adică nu a
fost dimensionată şi complexitatea rutinelor de pe nivelul de
bază al bibliotecii de subprograme.
Se observă, doar în condiţiile în care se iau în considerare şi
complexităţile rutinelor apelate, valoarea complexităţii ciclomatice totale se
apropie de valoare obţinută pentru abordarea clasică. Însă, în mod firesc, are
sens calculul complexităţii doar pe baza codului din programul apelator, fie
şi din simplul motiv că biblioteca de subprograme este deja compilată şi prin
urmare nu există un acces la codul-sursă.

78
Programarea standard

În condiţiile în care se consideră semnificativ, analiza doar asupra


programului apelator, pentru aplicarea formulelor Halstead se identifică
următoarele valori pentru parametrii:
n1 = 2
n2 = 6
N1 = 5
N2 = 20
Numărul total de operanzi este semnificativ mai mare decât celelalte
valori datorită utilizării unora dintre aceştia în apeluri repetate către
subrutine pentru a implementa funcţionalităţi distincte.
Valorile pentru metricile Halstead, în aceste condiţii sunt:
Metrică Valoare
Lungimea programului = 25
Vocabularul programului = 8
Volumul = 25*log28
Dificultatea = 3,3
Efort = 75,75*log28
Comparativ cu valorile obţinute pentru abordarea clasică, acestea sunt
mult inferioare, ceea ce dovedeşte o mai bună implementare a rezolvării
problemei, cu o complexitate şi un efort mult diminuat prin reutilizarea
subrutinelor din biblioteca de subprograme. Programarea standard se
caracterizează, în principal, printr-o productivitate mult crescută.

4.3 Generalitatea subprogramelor

Programarea standard impune dezvoltarea de subprograme cu un grad


de generalitate foarte ridicat. În primul rând, subprogramele trebuie să
acopere o arie mai ridicată a problemelor. De exemplu, pentru calculul
mediei, se impune a abordare gradată. Mai întâi se construieşte procedura
pentru calculul mediei aritmetice simple, cu textul sursă:
float mediaaritm( float x[], int n) {
int i;
float xmed = 0;

for ( i = 0; i < n; i++ ) {


xmed += x[i];
}

xmed /= n;
return xmed;
}

79
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

După aceea se construieşte procedura pentru calculul mediei


aritmetice ponderate, cu textul sursă:
float mediapond ( float x[], int f[], int n) {
int i, sumf = 0;
float sumxf = 0, xmed;

for ( i = 0; i < n; i++ ) {


sumf += f[i];
sumxf += x[i] * f[i];
}

xmed = sumxf / sumf;


return xmed;
}

Dacă se doreşte creşterea generalităţii unui subprogram în sensul că


acesta să permită fie calculul mediei aritmetice simple, fie calculul mediei
aritmetice ponderate, se procedează la elaborarea textului sursă:
float media ( float x[], int f[], int n, int k) {
int i, sumf = 0;
float sumxf = 0, xmed;

if ( k == 0 ) {
for ( i = 0; i < n; i++ ) {
f[i] = 1;
}
}

xmed = mediapond(x, f, n);


return xmed;
}

Utilizatorul iniţializează variabila k pe zero dacă doreşte să calculeze


o medie aritmetică simplă. Pentru a calcula media aritmetică ponderată
variabila k se iniţializează cu o valoare diferită de zero. Mai mult, dacă se
doreşte calculul altor tipuri de medii (media geometrică, media armonică) în
acelaşi subprogram, se impune efectuarea de modificări adecvate.
În al doilea rând, generalitatea trebuie privită prin prisma acceptării
cazurilor particulare. Trebuie tratate distinct cazurile particulare. De exemplu,
subprogramul care tratează situaţia în care suma frecvenţelor din relaţia este nulă.
n

∑x i fi
x= i =1
n

∑f
i =1
i

80
Programarea standard

Textul sursă al subprogramului mediapond() este:


float mediapond ( float x[], int f[], int n ) {
.....
if ( sumf == 0 ) {
xmed = 0;
}else {
xmed = sumxf / sumf;
}
...............
}

Proiectantul bibliotecii trebuie să stabilească outputurile


subprogramelor pentru situaţiile particulare; de cele mai multe ori
subprogramul returnează coduri asociate poziţiilor unei liste de mesaje care
trebuie afişate. Codurile trebuie testate pentru a determina fluxuri de
prelucrare diferenţiate funcţie de outputurile subprogramelor.
În al treilea rând, subprogramele conţin teste suficient de puternice
pentru a permite efectuarea de calcule.
Expresia:
n

∑x i fi
x= i =1
n

∑f
i =1
i

pentru a fi evaluată corect trebuie ca fi >= 0, i = 1, 2, ..., n şi n > 0. Aceste


condiţii trebuie avute în vedere de designerii de subprograme. Se ia în
considerare atitudinea acestora fie de a lăsa programatorul care dezvoltă
aplicaţii folosind subprograme de bibliotecă pentru a face validările, fie de a
le încorpora în subprograme şi de a furniza mesaje referitoare la modul în
care s-a derulat prelucrarea.
Astfel, se dă subprogramul:
int mediapondt ( float x[], int f[], int n, float &xmed ) {
int rezultat = 0;

if ( n < 1 ) {
rezultat = -1;
}

if ( contorless (f, n, 0) ) {
rezultat = -2;
}

81
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

if ( rezultat == 0 ) {
xmed = mediapond( x, f, n);
}
return rezultat;
}

Subprogramul returnează 0 dacă prelucrarea s-a efectuat complet şi


corect. Subprogramul returnează -2 dacă există frecvenţe negative şi
returnează -1 dacă numărul de componente ale şirului nu conţine cel puţin
un element.
În al patrulea rând, generalitatea problemei creşte atunci când
subprogramele sunt construite pentru a accepta diferite tipuri de operanzi.
Pentru produsul scalar a doi vectori de tip întreg, se construieşte
subprogramul:
long int prodscalar( int x[], int y[], int n) {
long int rezultat = 0;

for ( int i = 0; i < n ; i++ ) {


rezultat += x[i] * y[i];
}

return rezultat;
}

Pentru produsul scalar a doi vectori de tip float se construieşte:


float prodscalar( float x[], float y[], int n) {
float rezultat = 0;

for ( int i = 0; i < n ; i++ ) {


rezultat += x[i] * y[i];
}

return rezultat;
}

În programarea orientată obiect această problemă de multiplicare a


codului funcţie de tipul operanzilor este soluţionată folosind descriptorul
template.
Biblioteca de subprograme este construită sub formă de componente
interdependente. Dacă se doreşte soluţionarea unei probleme, mai întâi se
analizează dacă există deja subprogram în bibliotecă. În caz contrar, se
identifică subprogramele apte să rezolve problemele fiecărui pas al
algoritmului. Dacă acest aspect impune dezvoltarea de subprograme de

82
Programarea standard

bază, evident, acestea se elaborează. Din aproape în aproape se dezvoltă o


bibliotecă de subprograme care, în al cincilea rând, acoperă prin diversitatea
de prelucrări de bază, toate cerinţele pentru a soluţiona orice problemă,
generalitatea bibliotecii fiind apreciată în raport cu diversitatea de probleme.

4.4 Listele de parametrii

Când se elaborează o bibliotecă de subprograme, trebuie respectate o


serie de reguli, care încep cu lista de parametrii, continuă cu stilul de
programare şi se încheie cu modul în care este testată fiecare componentă a
bibliotecii.
Lista de parametrii, aşa cum arată experienţa multor ani de
programare, este alcătuită din trei liste şi anume:
• lista parametrilor ce corespund datelor de intrare; aceşti
parametrii apar în membrul drept al unei expresii aritmetice;
este indicat să nu se opereze modificări asupra acestor
variabile pentru a nu schimba premisele altor subprograme
care au nevoie de acele variabile cu valorile lor iniţiale;
• lista rezultatelor, conţine numele parametrilor care apar în
membrul stâng al unei expresii de atribuire;
• lista variabilelor de stare care conţin coduri ce privesc cazurile
de excepţie, codurile mesajelor de eroare care vor fi afişate; se
construieşte o listă de coduri care se asociază unei variabile de
stare, în toate subprogramele fiind atribuite numai valori în
lista de coduri; de exemplu, variabila ik este o variabilă de
stare; valorile pe care le ia aceasta sunt date în tabelul 4.1.

Valori asociate stărilor unui subprogram


Tabelul 4.1
Valoare Semnificaţie
0 Prelucrare completă care a condus la rezultate corecte
1 Întreruperea prelucrărilor înainte de a efectua o împărţire la zero
-1 Un masiv are număr de componente negativ
-2 O variabilă are valori în afara unui interval impus
-3 Argumentul unui logaritm sau al unui radical este negativ
2 Parametrul are o valoare în afara elementelor enumerate
3 Numele fişierului este necunoscut
4 Elementul cu cheia specificată nu există

83
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Dacă toate subprogramele sunt înzestrate cu variabila de stare ik în


lista de parametrii, în programul apelator sau în subprograme se introduce o
secvenţă de program obligatorie de testare a ei. Continuarea prelucrării se
realizează dacă şi numai dacă, variabila ik are la ieşirea din procedură
valoarea zero, aşa cum se arată în figura 4.2.

Figura 4.2 Flux cu testarea variabilei de stare

84
Programarea standard

Listele de parametrii pentru subprogramele dintr-o bibliotecă sunt


extrase dintr-o listă comună de nume, utilizatorii bibliotecii trebuind
obligatoriu să cunoască această listă comună de nume. Mai mult, pentru
subprograme care au prelucrări ce se referă la aceleaşi variabile, se impune
ca listele de parametrii să fie identice. De exemplu, în tabelul 4.2 sunt date
proceduri care au liste de parametrii identice ca structură şi ca poziţie a
elementelor şi ca nume pentru parametri.

Proceduri cu liste identice de parametri


Tabelul 4.2
Prototipul procedurii Semnificaţia
addmat (a, b, m, n, c) adună matricele a, b, rezultatul este matricea c
scadmat (a, b, m, n, c) evaluează expresia c = a – b, unde a, b, c sunt
matrice cu m linii şi n coloane
prodmat (a, b, m, n, c) calculează elementele unei matrice c după relaţia
cij = aij * bij
compartmat (a, b, m, n, c) compară elementele a două matrice
cij = 0 dacă aij = bij
cij = 1 dacă aij > bij
cij = -1 dacă aij < bij

Programarea standard îşi dovedeşte eficienţa dacă subprogramele sunt


bine construite, având un grad de omogenitate maxim, încât programatorii
să se recunoască pe ei înşişi prin nivelul ridicat al performanţei încorporate
în secvenţele de texte sursă. În cazul în care un programator identifică
secvenţe care prin înlocuirea cu altele mult mai performante schimbă
calitatea unei proceduri, încrederea în bibliotecă scade, programarea
standard fiind periclitată.

4.5 Programul principal, programul apelator

Dorinţa oricărui programator este maximizarea gradului de reutilizare


de componente. Programarea standard are ca obiectiv elaborarea de
biblioteci de subprograme care să asigure maximizarea gradului de

85
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

reutilizare. Este important să fie cunoscute atât bibliotecile, cât şi


componentele lor, adică subprogramele incluse în ele.
Dacă există o problemă P de rezolvat, în faza de analiză se stabilesc
datele de intrare DI1, DI2, ...., DIn şi rezultatele care trebuie obţinute RI1,
RI2, ...., RIm. În funcţie de volum, de tip, de modul de regrupare, datele de
intrare şi rezultatele sunt puse în corespondenţă cu unele dintre structurile de
date cunoscute. Rezultă automat tipologia de probleme în raport cu
operanzii de prelucrat.
Dacă sunt luate în considerare următoarele structuri de date:
VE - variabile elementare
M1 - masiv unidimensional
M2 - masiv bidimensional
FS - fişier secvenţial
LS - listă simplă
LD - listă dublă
ST - stivă
AB - arbore binar
GR - graf
BB - arbore B
MR - matrice rară

modulele sau subprogramele, în faza de proiectare sunt cu un grad de


omogenitate ridicat, în primul rând, dacă includ operanzi de acelaşi tip, în
proporţie covârşitoare. De exemplu, în figura 4.3 e dată o structură de
program apelator care include apel de subprograme cu grad maxim de
omogenitate a structurilor de date utilizate.

86
Programarea standard

FS

LS
Conversie()

Calcul3()
LS

LS
Calcul1()

Calcul4()
LS

LS
Calcul2()

Conversie()

FS

Figura 4.3 Program apelator omogen din punct de vedere


al structurilor de date

Stabilirea structurilor de date din lista de parametrii ai subprogramului


determină care este biblioteca utilizabilă, care sunt subprogramele care se
reutilizează de către programator pentru a-şi soluţiona problema. Problema
P se descompune în subproblemele P1, P2, ....., Pr. Problemei P îi va
corespunde programul apelator PA, figura 4.4. Fiecărei subprobleme îi
corespunde apelarea unui subprogram SP1(), SP2(), ...SPk(). Descompunerea
subproblemei Pi în alte subprobleme Pi1, Pi2, ....., Piki impune includerea în
subprogramul SPi() a apelurilor de subprograme SPi1(), SPi2(), ....SPiki().
Programarea standard impune ca structura arborescentă să includă un
număr de niveluri rezonabil, iar frunzele arborescenţei sunt componente ale
unei biblioteci de subprograme. Rezultă că programatorul care stăpâneşte
programarea standard, trebuie să dezvolte o structură arborescentă, trebuie
să ştie care sunt componentele bibliotecii, tehnologia bottom-up fiind cea
mai nimerită de a construi soluţia ca program a problemei. Astfel, pentru o
problemă P dată în figura 4.4 se construieşte programul dat în figura 4.5.

87
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Figura 4.4 Structura problemei P

void main() {
p_1();
p_2();
p_3();
}
p_1() {
p_1_1();
p_1_2();
p_1_3();
}
p_2() {
p_2_1();
p_2_2();
p_2_3();
p_2_4();
}
p_3() {
p_3_1();
p_3_2();
p_3_3();
}

Figura 4.5 Program asociat structurii arborescente P

Toate componentele de pe nivelul al doilea sunt subprograme de


bibliotecă. Programarea standard este programarea reutilizării de
subprograme. Activitatea de programare este reorientată spre a dezvolta
subprograme reutilizabile, iar comunicarea între programatori este esenţială.
În momentul în care activitatea unui programator este analizată calculând
raportul dintre secvenţele originale de instrucţiuni şi lungimea programului,
toată problematica efortului de programare, se rezolvă de la sine. Secvenţele
originale se obţin dintr-un program, după ce se elimină toate secvenţele
pentru care există subprograme de bibliotecă. La un moment dat,
programarea standard s-a constituit în factor progres, impunând o
componentă de bază în ingineria software, partea dedicată reutilizării de
subprograme.

88
5.1 Reguli de bază

Programarea structurată, conform [MIHAL83], constă dintr-o mulţime


de restricţii şi reguli de programare pe care programatorul trebuie să le
respecte, în acest fel, eliminându-se mulţi dintre factorii care conduc la erori
şi care complică problemele de testare şi de întreţinere.
Se identifică trei structuri de control şi anume:
• structura liniară: se consideră instrucţiunile I1, I2, ..., Iin care se
execută una după cealaltă aşa cum arată arcele orientate din figura
5.1:

Figura 5.1 Structură liniară

într-un program, structura liniară apare sub forma unor instrucţiuni


dispuse una după cealaltă; de exemplu secvenţa:
s = 0;
i++;
s = x[i] + x[i] * x[i];

formează o structură liniară;


• structura alternativă, care presupune o expresie condiţională C1 şi
două secvenţe S1 şi S2; dacă, după evaluarea expresiei condiţionale
C1, se obţine valoarea logică adevărat, se execută secvenţa S1, în
caz contrar se execută secvenţa S2, figura 5.2:

89
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Figura 5.2 Structură alternativă

De exemplu, pentru evaluarea expresiei


⎧⎪a 2 + b 2 , daca a > b
e=⎨ 2
⎪⎩a + b 3 , daca a ≤ b

se realizează schema logică din figura 5.3:

Figura 5.3 Structură alternativă pentru evaluarea expresiei E

90
Abordarea structurată

• structura repetitivă sau de ciclare, care presupune o expresie


condiţională C şi o secvenţă S de program care se execută de un
număr finit de ori, până când condiţia îşi schimbă valoarea. O
astfel de structură are una din formele:
¾ repetitivă cu test iniţial, în care secvenţa S se repetă atâta timp
cât condiţia C este adevărată, apoi se execută secvenţa S`;

Figura 5.4 Structură repetitivă cu test iniţial

¾ repetitivă cu test final, în care se execută secvenţa S, până când


condiţia C devine adevărată, apoi se execută secvenţa S`;

Figura 5.5 Structură repetitivă cu test final

¾ repetitivă cu contor, care presupune existenţa unei variabile de


control i, a unei valori iniţiale a acesteia, vinit, a unei valori
finale vfin si a unei raţii r, precum şi o secvenţă S care se
execută, iar variabilele incluse în ea depind de variabila de
control i; schema logică corespunzătoare este prezentată în
figura 5.6;

91
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Figura 5.6 Structură de control având repetări contorizate

Pentru problema de determinare a numărului de elemente


pozitive, negative şi respectiv nule dintr-o matrice, prezentată
în capitolul al doilea, schema logică corespunzătoare utilizării
în exclusivitate a celor trei tipuri de structuri este dată în figura
5.7.

92
Abordarea structurată

Figura 5.7 Schema logică pentru numărarea elementelor negative, pozitive şi


nule

Programarea structurată determină o ordonare a dezvoltării


succesiunii operaţiilor, obligându-i pe programatori să realizeze construcţii
cât mai clare.

93
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

5.2 Programarea fără GOTO

Introducerea în limbajele de programare a instrucţiunii GOTO are


menirea de a dezvolta programe cu arce orientate care se întretaie, figura
5.8.

Figura 5.8 Graf asociat unui program cu salturi înapoi (A), cu salturi înainte
(B) şi cu salturi înapoi şi înainte (C) întretăiate

O utilizare frecventă a salturilor necondiţionate este pentru


implementarea structurilor repetitive, în lipsa unor construcţii oferite de
limbajul de programare. De asemenea, o altă utilizare apare în situaţia în
care se doreşte creşterea vitezei de execuţie. Secvenţele de cod cu salturi
necondiţionate sunt mai rapide, însă mai greu controlabile sub aspectul
fluxului de execuţie. Limbajele de nivel înalt doar includ printre cuvintele
lor rezervate această instrucţiune, însă nu furnizează nici o implementare.
Salturile necondiţionate rămân apanajul limbajelor de asamblare şi ale
limbajelor de nivel mediu (C/C++), însă şi la acestea din urmă, utilizarea lor
nu este recomandată, preferându-se abordările structurate.
Programarea structurată este o tehnică în care programatorul nu
trebuie să folosească instrucţiunea de salt GOTO. Cele trei tipuri
fundamentale de structuri permit implementarea acestei cerinţe. Dacă se
consideră limbajul de asamblare şi se analizează procesul de compilare, se
observă că instrucţiunea de salt necondiţionat – jmp este inevitabilă.

94
Abordarea structurată

Structurii alternative din figura 5.2 i se asociază secvenţa din figura


5.9.

Figura 5.9 Construcţia secvenţială asociată structurii alternative


pentru limbajul de asamblare

Implementarea unei structuri alternative presupune operaţii anterioare


testării indicatorilor de condiţie, iar în cazul unui rezultat negativ al evaluării
se execută un salt necondiţionat la secvenţa ce trebuie executată în acest caz
(corespondentul într-un limbaj de programare evoluat este varianta else a
structurii if).
Şi în cazul structurii repetitive, instrucţiunea jmp este inevitabilă,
figura 5.10.

95
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Figura 5.10 Construcţie secvenţială asociată structurii repetitive


pentru limbajul de asamblare

Limbajele de programare evoluate includ apeluri de funcţii de


bibliotecă. De exemplu, în programul din figura 5.11, programul principal
PROG1 apelează subprogramele calcul1, calcul2 şi calcul3.

96
Abordarea structurată

Figura 5.11 Instrucţiuni de salt necondiţionat la apeluri


şi reveniri la lucru cu subprograme

Instrucţiunile call şi ret conţin şi salturi necondiţionate care definesc


modificări ale registrului IP cu valori mai mari de 6 baiţi. Rezultă că
programarea fără GOTO este un deziderat şi limbajele evoluate reuşesc să
mascheze apariţiile de bază ale salturilor necondiţionate.

5.3 Conversii de programe

Din punct de vedere al teoremei de structură, a lui Böhm-Jacopini, un


program structurat este unul care are numai structuri de tip secvenţial, if-
then-else şi repetitive condiţionate anterior. Variantele de structuri de tip if-
then sau repetitive condiţionate posterior trebuie transformate în structurile
menţionate anterior, numite şi structuri fundamentale. Principala metodă de
transformare o constituie duplicarea de cod. De exemplu, se consideră
structura if-then următoare:

97
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Figura 5.12 Structură alternativă de tip if-then

Pentru a o transforma într-o structură în care să se regăsească doar


structuri fundamentale, se duplică codul, obţinându-se următoarea schemă:

Figura 5.13 Structură if-then rescrisă folosind doar structuri fundamentale

În practică însă nu se optează pentru scrierea structurii de tip if-then


folosind doar structuri fundamentale, pentru că, aşa cum se observă în
figură, cantitatea de cod duplicată este semnificativă, iar introducerea unei

98
Abordarea structurată

noi testări a condiţiei C1 pe lângă faptul că afectează performanţa codului


(este ştiut faptul că operaţiile de comparare sunt costisitoare, din punct de
vedere ale procesorului), afectează şi lizibilitatea algoritmului.
În cazul structurii repetitive condiţionate posterior, se realizează tot o
duplicare de cod, însă impactul asupra performanţei şi lizibilităţii codului
este mult mai mic. Considerându-se schema din figura 5.5, ea se rescrie
folosind doar structuri fundamentale în figura 5.14:

Figura 5.14 Structura repetitivă rescrisă folosind doar structuri fundamentale

Se observă că se duplică doar blocul funcţional S, ceea ce nu afectează


deloc performanţa codului şi într-o măsură foarte mică lizibilitatea lui.
Probleme sunt atunci când blocul S trebuie modificat, fiindcă modificarea
trebuie realizată în două locuri.
Atunci când un programator este format să lucreze conform cerinţelor
programării structurate, construirea algoritmilor, construirea secvenţelor de
blocuri în schema logică se realizează din start pentru cele trei tipuri de
structuri fundamentale, nefiind necesară conversia. În cazul în care
programatorul are la dispoziţie software mai vechi în care apare frecvent
instrucţiunea GOTO trebuie să realizeze conversia de programe, adică să
dezvolte astfel de construcţii încât să fie eliminată instrucţiunea GOTO din
program.
Structurile de program ce conţin instrucţiuni GOTO sunt greu de
analizat, modificat, restructurat şi testat. De aici şi nevoia de a transforma
aceste structuri de program în unele din care instrucţiunile GOTO să
lipsească.

99
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Pentru transformarea programelor ce conţin instrucţiuni GOTO în


programe structurate, cea mai utilizată tehnică este cea a introducerii unei
variabile de control. Se consideră următoare structură posibilă în cadrul unei
aplicaţii:

Da
C1

S1

Da
C2

S2

Da
C3

S3

Figura 5.15 Exemplu de structură de program


în care se folosesc instrucţiuni GOTO

Pentru structurarea secvenţei de program din figura 5.15 se introduce


variabila de control vb de tip boolean, care va fi iniţializată cu valoarea true.
Schema rezultată este prezentată în figura 5.16:

100
Abordarea structurată

Figura 5.16 Schema structurată folosind variabila de control vb


Nu toate programele nestructurate au corespondent sub formă de
program structurat.
Se observă că există situaţii, cum este cazul structurilor de tip if-then
sau repetitive condiţionate posterior, în care rescrierea lor folosind doar
structuri fundamentale nu îşi are rostul. Este şi motivul pentru care toate
limbajele de programare acceptă construcţii de acest fel, existând chiar
cuvinte cheie speciale, de exemplu, do-while. Nu acelaşi lucru se spune
despre secvenţele ce folosesc instrucţiuni GOTO, care trebuiesc eliminate
din cod.
Pentru programele deja scrise folosind GOTO, transformarea lor
manuală reprezintă un efort semnificativ, motiv pentru care au fost

101
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

dezvoltate o serie de instrumente software cu ajutorul cărora această


transformare este automatizată.

5.4 Limbaje de programare structurate

Limbajele PASCAL, C, C++ şi toate care urmează acestora sunt


proiectate în vederea implementării fără nici o rezervă a structurilor de
control fundamentale. Având în vedere că limbajul C++ este utilizat pentru
toate exemplificările, pentru implementarea structurii alternative din figura
5.2 se realizează secvenţa:
if ( C1 ) {
S1;
} else {
S2;
}

Pentru structura repetitivă condiţionată anterior în limbajul C++ există


construcţia:
while ( condiţie ) {
S;
}

Structura repetitivă condiţionată posterior este implementată în C++


prin:
do {
S;
} while ( condiţie );

Pentru structura repetitivă cu contor se realizează secvenţa:


initializari;
for ( i = vinit; conditie; i += r ) {
S;
}

Structura liniară se implementează printr-o structură de forma:


I1;
I2;
......
Iin;

102
Abordarea structurată

Rezultă că pentru schema logică dată în figura 5.7 textul sursă C++
este dat în figura 5.17.
#include <stdio.h>

#define M 10
#define N 10

int m, n, i, j, a[M][N];

int main() {
printf("\nIntroducetie numarul de linii: ");
scanf("%d", &m);
printf("\nIntroduceti numarul de coloane: ");
scanf("%d", &n);
for ( i = 0; i < m; i++ ) {
for ( j = 0; j < n; j++ ) {
printf("\na[%d][%d] = ", i, j);
scanf("%d", &a[i][j]);
}
}

int nrplus = 0;
int nminus = 0;
int nrzero = 0;

for ( i = 0;i < m; i++ ) {


for ( j = 0; j < n; j++ ) {
if ( a[i][j] == 0 ) nrzero++;
if ( a[i][j] > 0 ) nrplus++;
if ( a[i][j] < 0 ) nrminus++;
}
}

printf("\nNumarul de elemente pozitive este %d",nrplus);


printf("\nNumarul de elemente negative este%d",nrminus);
printf("\nNumarul de elemente nule este %d\n", nrzero);

return 1;
}

Figura 5.17 Codul sursă în C++ pe baza schemei logice din figura 5.7

Aplicând metricile de complexitate definite în capitolul Ciclul de


dezvoltare software, se obţine următoarele valori pentru complexitatea
ciclomatică:
CM =35 – 25 + 2 = 12

103
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

şi pentru metricile Halstead:


Metrică Valoare
Lungimea programului = 69
Vocabularul programului = 14
Volumul = 421,49
Dificultatea = 15,38
Efort = 6480,38
Analizând variaţia complexităţii faţă de abordarea clasică, unde
implementarea a fost tot monolitică, se observă o creştere a complexităţii pe
fondul diminuării numărului de noduri din graf. Aceasta deoarece au fost
folosite structuri repetitive ale limbajului şi elaborate de programator. Prin
urmare, mai multe instrucţiuni din programarea clasică au fost grupate
într-una singură. Acest lucru însă nu a diminuat fluxul de execuţie care
rămâne acelaşi, dovadă numărul aproape identic de arce.
Metricile Halstead nu diferă foarte mult de valorile înregistrate pentru
abordarea clasică, lucru normal, având în vedere se efectuează aceleaşi
operaţii logice, pe aceiaşi parametrii.
Comparând cu abordarea standard, lucrurile stau total diferit. Datorită
organizării mult mai riguroase a codului, în librării de subprograme,
complexitatea programului scade foarte mult, el devenind o succesiune de
apeluri de funcţii din librărie. În situaţia în care se iau în considerare şi
complexităţile funcţiilor se observă o apropiere a valorilor, în condiţiile în
care nu s-a mers decât pe primul nivel de derivare, în cadrul bibliotecii de
subprograme. Dacă însă se realizează o implementare şi în variantă
structurată, cu ajutorul mai multor subprograme, se constată o creştere a
complexităţii. Se construieşte un subprogram care realizează citirea
elementelor matricei de la tastatură şi un subprogram care numără
elementele din matrice pe baza rezultatului returnat de o funcţie transmisă
ca parametru subprogramului de numărare. Această funcţie presupune trei
implementări pentru comparaţiile de tip „mai mare”, „egal” şi „mai mic”.
Rezultatele obţinute pentru metricile de complexitate devin în acest caz:
CM =15
respectiv:
Metrică Valoare
Lungimea programului = 83
Vocabularul programului = 40
Volumul = 356,5
Dificultatea = 20,08
Efort = 2116,73

104
Abordarea structurată

Trebuie menţionat că evaluarea a fost făcută prin cumularea valorilor


obţinute şi la nivelul subprogramelor, la fel ca în cazul programării standard.
Rezultă că, în general, introducerea de subprograme creşte
complexitatea programului. Avantajul principal este legat de reutilizare. În
plus, dacă subprogramele nu sunt dezvoltate în cadrul aceluiaşi proiect, ele
nu participă la calculul complexităţii, şi atunci valoarea acesteia este foarte
mică.
Programarea structurată reprezintă o tehnică de programare care ajută
la dezvoltarea de software cu un design şi un flux de execuţie clare şi prin
care se asigură un grad ridicat de modularitate a produsului. Eliminarea
instrucţiunii GOTO din programe folosind diverse metode, unele prezentate
şi în cadrul acestui capitol, ajută la eliminarea unor categorii de erori de
programare foarte grave şi la îmbunătăţirea calităţii codului. Trebuie
menţionat că această tehnică de programare este fundamentată ştiinţific, prin
teoremele de structură, spre deosebire de alte tehnici anterioare ei, care nu
posedă un astfel de fundament.

105
6.1 Abordarea bottom-up

Tehnica bottom-up presupune construirea la început a unor elemente


primitive, de bază, apoi combinarea lor în elemente mai complexe, dar şi
adăugarea de noi elemente, până când se obţine produsul final. În
programarea aceasta înseamnă construirea iniţial a unor funcţii primitive, ce
implementează elemente atomice de funcţionalitate, apoi utilizarea lor în
construirea de funcţii mai complexe. Rezultatul este o structură arborescentă
care are ca rădăcină produsul software final.
Se consideră o problemă P definită prin date de intrare – DI, date de
ieşire – DE, şi algoritmi de calcul pe baza cărora se obţin datele de ieşire.
Pornind de la rezultate, se definesc din aproape în aproape prelucrări şi se
ajunge în final la problema P în integralitatea ei. Datele iniţiale sunt
transformate în date finale după un număr finit de prelucrări care generează
şi date intermediare. Pornind de la datele de ieşire DEij, care pot fi cele
finale sau unele intermediare, se asociază o prelucrare sau un set de
prelucrări cu un modul Mij al produsului software, care permite obţinerea lor
pe baza unor date de intrare intermediare, DIij figura 6.1.

Figura 6.1 Fluxul corespunzător unui modul

106
Programarea modulară

În notaţia din figura 6.1, indicele inferior arată numărul modulului, iar
indicele superior arată nivelul pe care se află modulul în structura
arborescentă asociată produsului software. Datele de ieşire ale unui modul
de pe nivelul j, reprezintă datele de intrare pentru unul sau mai multe
module de pe nivelul j-1.
Proiectantul sistemului software abordează întreaga problematică de la
elementele concrete spre elementul de sinteză, care se află la nivelul
superior. Se construieşte soluţia sub formă de structură arborescentă pornind
de la ultimul nivel de descompunere – frunzele arborelui, către rădăcină.
Asamblarea se efectuează de la nivelul j+1 spre nivelul j, continuându-se de
la nivelul j spre nivelul j–1 până la nivelul 0 ce corespunde rădăcinii. O
astfel de abordare permite construcţia produsului pornind de la funcţiile
primitive, specifice domeniului problemei, spre funcţiile complexe ce
implementează logica aplicaţiei; tehnica este potrivită pentru situaţii în care
specificaţiile nu sunt clare, şi de asemenea, asigură o mai bună organizare a
codului sursă.
Modulele de pe nivelurile inferioare sunt asamblate în unul sau mai
multe module de pe nivelurile superioare. De exemplu, un set de funcţii
pentru accesul la fişiere este folosit atât într-un modul de salvare a datelor
manipulate de aplicaţia software, cât şi într-un modul de prelucrare care
necesită lucrul cu fişiere temporare.

P=M10

M11 M21 M3 1

………………………………………………………..

M1p-1 M2p-1 …………... Mmp

M1p M2p M3p …………... Mnp

Figura 6.2 Asamblare bottom-up în sensul de la frunze spre rădăcină

107
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

De exemplu, pentru problema de calcul matriceal aleasă pentru


ilustrarea particularităţilor tehnicilor de programare, soluţia prin tehnologia
bottom-up presupune:
• cunoaşterea exactă a rezultatelor obţinute: numărul
componentelor negative, numărul componentelor pozitive şi
numărul componentelor nule;
• cunoaşterea exactă a condiţiilor pe care matricea trebuie să le
îndeplinească: matricea este simetrică.
Printr-o abordare bottom-up, programatorul identifică, în primul rând,
funcţiile primitive: citirea informaţiilor de la tastatură, afişarea informaţiilor
pe ecran, compararea a două numere pentru care rezultatul are valoarea
adevărat sau fals. Aceste funcţii se vor găsi pe nivelul de bază a structurii.
Pornind de la aceste primitive, se construiesc pe nivelul următor, modulul de
citire a dimensiunilor şi elementelor matricei, modulul de afişare a
rezultatelor şi a unor mesaje de informare sau de eroare dintr-o listă asociată.
Pe nivelul al treilea de la bază, apar modulele de stabilire a simetriei matricei,
de determinare a minimului şi a maximului, precum şi de numărare a
elementelor pozitive, negative şi nule din matrice. Aceste module se bazează
pe funcţiile de comparare a două numere, de afişare de rezultate şi afişare de
mesaje. Pe nivelul rădăcină, se apelează aceste module conform fluxului cerut
de problemă. Înseamnă că structura arborescentă are 4 niveluri, (figura 6.3).

Figura 6.3 Dezvoltarea bottom-up pentru problema PROB

108
Programarea modulară

Se impun următoarele observaţii referitoare la figura 6.3:


¾ un modul de pe un nivel inferior, cum este cazul modului de
„Afişare mesaje din lista de mesaje” să fie implicat în
construirea mai multor module de pe nivelurile superioare;
¾ un modul de pe un nivel inferior, cum este cazul modulului
„Compararea a două numere” participă la construcţia unui alt
modul care nu se află neapărat pe următorul nivel în ierarhie.
Rezultă că, structura nu este arborescentă în proporţie de 100%, ea
permiţând ca un nod copil să aibă mai mult de un nod părinte. Însă
caracterul ierarhic se menţine, prin faptul că modulele sunt dezvoltate
începând cu cele primitive, de bază, şi terminând cu cele complexe,
respectiv cu produsul final ca ultim modul, iar acestea din urmă au în
construcţia lor apeluri către modulele de bază.
Se construieşte tabelul 6.1 care evidenţiază faptul că datele de intrare
ale unui modul de pe nivelul k sunt ieşiri ale modulului de pe nivelul k – 1.

Corelaţia nivel – mod de utilizare variabile


Tabelul 6.1
Nivel Modul c1 c2 c3 c4 c5 c6 c7
3 citire informaţii
3 afişare informaţii
3 comparare numere E I
2 citire matrice E E
2 afişare rezultate I I I I
2 afişare mesaje I
1 contorizare E E E I I
1 maxim/minim I I E
1 simetrie E I I
0 main() I/E I/E I/E I/E I/E I/E I/E

Semnificaţia coloanelor c1 – c7 este prezentată mai jos.


c1 - nrplus
c2 - nrnegativ
c3 - nrnull
c4 - text
c5 - matricea
c6 - dimensiuni matrice
c7 - max/min

109
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Avantajul acestei abordări este dat de faptul că programatorul rezolvă


părţi ale problemei, oferă rezultate intermediare şi pe măsură ce poate
asambla module, obţine în final produsul finit. Dezvoltarea bottom-up
corespunde proceselor industriale care realizează repere, prin asamblare se
obţin subansambluri, iar la cel mai înalt nivel se obţine produsul finit.

6.2 Abordarea top-down

Abordarea top-down presupune divizarea problemei mari în


subprobleme mai mici care sunt tratate separat până la nivelul unor rutine
sau module primitive. Proiectarea unui program modular, prin această
tehnică se face astfel: modulul de nivel superior specifică ce niveluri
descendente ale sale sunt necesare şi precizează ce date se transmit şi ce
rezultate se aşteaptă de la ele. Modulele terminale se vor identifica şi
implementa, deci, ultimele. Testarea programului se face în aceeaşi
abordare: mai precis, proiectarea, programarea şi testarea modulului
descendent se vor realiza împreună. Dacă modulul părinte a fost pus la
punct atunci, pe parcursul implementării modulului descendent, eventualele
erori vor apărea numai în acesta din urmă. Programatorul se va putea, deci,
concentra numai pe realizarea acestui modul, munca lui devenind mult mai
eficientă.
Problema P a afişării numărului de elemente pozitive, negative, nule, a
maximului şi minimului unei matrice simetrice, este privită, ca orice
aplicaţie informatică, ca având o structură arborescentă pe două niveluri,
figura 6.4.

Figura 6.4 Structura arborescentă top-down pe două niveluri

• SP1 reprezintă subproblema corespunzătoare iniţializării


datelor;
• SP2 reprezintă subproblema de prelucrare a datelor;
• SP3 reprezintă subproblema de afişare a rezultatelor.

110
Programarea modulară

Dezvoltarea top-down presupune efectuarea unor detalieri pentru


fiecare subproblemă, după cum urmează:
• S-SP11 corespunde iniţializării dimensiunilor matricei, care
este subproblemă a lui SP1;
• S-SP12 corespunde iniţializării matricei, care este subproblemă
a lui SP1;
• S-SP13 corespunde alegerii minimului dintre numărul de linii şi
numărul de coloane ale matricei pentru a stabili dimensiunea
matricei pentru care se analizează simetria.
Structura arborescentă asociată subproblemei SP1 este dată în figura
6.5.

Figura 6.5 Descompunerea pe subprobleme a funcţiei de iniţializare

Pentru efectuarea prelucrărilor, subproblema SP2 este descompusă în


subprobleme după cum urmează:
• S-SP21 corespunde testării caracterului simetric după diagonala
principală;
• S-SP22 corespunde testării caracterului simetric după diagonala
secundară;
• S-SP23 corespunde testării simetriei după coloana din mijloc;
• S-SP24 corespunde testării simetriei după linia din mijloc;
• S-SP25 corespunde aflării elementului minim din matricea
simetrică;
• S-SP26 corespunde aflării elementului maxim din matricea
simetrică
• S-SP27 corespunde contorizării.

111
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Structura arborescentă este dată în figura 6.6.

Figura 6.6 Descompunerea pe subprobleme a funcţiei de calcul

Afişarea rezultatelor impune descompunerea subproblemei SP3 astfel:


• S-SP31 corespunde afişării elementului minim;
• S-SP32 corespunde afişării elementului maxim;
• S-SP33 corespunde afişării numărului de elemente nule,
pozitive, respectiv negative;
• S-SP34 corespunde afişării de mesaje.
Descompunerea acestei funcţii este dată în figura 6.7.

SP3

S-SP31 S-SP32 S-SP33 S-SP34

Figura 6.7 Descompunerea pe subprobleme a funcţiei de afişare

Această descompunere corespunde situaţiei în care problema este


foarte bine înţeleasă, experienţa analiştilor şi designerilor permit asocierea
unei structuri arborescente clare, iar duplicarea de cod este controlată.
Pentru această abordare se procedează astfel:
• se scrie programul apelator SP0, definind variabilele de bază şi
apelurile la procedurile corespunzătoare lui SP1, SP2, SP3; în
faza de analiză sunt clarificate numărul de niveluri, inputurile
corespunzătoare fiecărui nivel;

112
Programarea modulară

• se scriu procedurile SP1, SP2, SP3, în fiecare dintre ele,


incluzându-se apelurile de proceduri de tipul S-SPij, i = 1,2,3
j = 1,2, ...., ni;
• se scriu procedurile corespunzătoare frunzelor structurii
arborescente.
Produsul se dezvoltă de la întreg spre parte, ceea ce conferă
designerului resurse suficiente pentru a obţine un produs complet, prin
adăugări de proceduri la fiecare nivel, dacă este cazul.

6.3 Raportul subprograme – modul

Conceptul de modul este foarte cuprinzător, fiind asociat unei


prelucrări cu un grad de complexitate acceptabil, care să-i ofere un nivel de
autonomie ridicat. De exemplu, există modulul de validare date, modul de
sortare date, modul de calcul matriceal, modul de actualizare fişier, modul
de preluare inputuri, modul de afişare, modul de conversie etc. Fiecare
modul este format din unul sau mai multe subprograme. Într-un caz
particular, un modul este format dintr-un singur subprogram. În realitate, un
modul are în structura sa mai multe subprograme. De exemplu, modulul de
validare a datelor include:
• validarea câmpului alfabetic;
• validarea unui câmp dacă este număr întreg;
• validarea apartenenţei numărului întreg la un interval;
• validarea apartenenţei unui şir la o mulţime de subşiruri
descrisă explicit;
• validarea numerelor reale;
• validarea apartenenţei unui număr real la un interval;
• validarea apartenenţei unui număr la o mulţime dată prin
elemente.
Modulul pentru iniţializarea unui masiv unidimensional include
subprograme pentru:
• iniţializarea de la tastatură;
• iniţializarea dintr-un fişier;
• iniţializarea prin atribuire;
• iniţializarea prin rezultatul evaluării unei expresii.

113
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Complexitatea unui modul este rezultatul analizei contextului şi


filosofiei adoptate de către echipa de proiectanţi software. Dacă se
urmăreşte un nivel de complexitate ridicat pentru module, structura
arborescentă se organizează pe un număr scăzut de niveluri. Modulele
complexe se află pe acelaşi nivel, funcţie de raportul cu părinţii, respectiv
descendenţii săi.
Dacă se adoptă ipoteza ca modulele să aibă un nivel de complexitate
redus, structura arborescentă are un număr de niveluri ridicat. În acest
context, pentru problema de calcul matriceal, în ipoteza un modul = un
subprogram, lista subprogramelor este prezentată în continuare:
• citire_dim() - citeşte dimensiunile matricei de la tastatură;
• citire_mat() - citeşte elementele matricei de la tastatură;
• simetrie1() – stabileşte simetria matricei faţă de diagonala
principală;
• simetrie2() – stabileşte simetria matricei faţă de diagonala
secundară;
• contorplus() – numără elementele pozitive dintr-o matrice;
• contorminus() – numără elementele negative dintr-o matrice;
• contornul() – numără elementele nule ale unei matrice;
• minim() – identifică minimul elementelor unei matrice;
• maxim() – identifică maximul elementelor unei matrice;
• afiseaza() – afişează rezultatele prelucrărilor;
• mesaj() – afişează mesaje specifice ale aplicaţiei.
Dacă se procedează la regruparea într-un modul a mai multor
subprograme, structura pentru aceeaşi problemă este dată în figura 6.9.

Figura 6.9 Structura aplicaţiei, după gruparea mai multor subprograme


într-un singur modul

114
Programarea modulară

Se urmăreşte realizarea unui echilibru între numărul de module şi


dimensiunea aplicaţiei. O aplicaţie simplă, dar cu un număr mare de module
nu face decât să crească complexitate. Pe de altă parte, o aplicaţie complexă
nu foarte bine delimitată pe module, afectează la rândul ei complexitatea,
prin faptul că influenţează alte caracteristici de calitate cum sunt
mentenabilitatea sau reutilizabilitatea.
Modulele din figura 6.9, sunt transpuse în codul sursă, în limbajul
C++, sub forma unor perechi de fişiere cu extensiile .h respectiv .cpp.
Astfel, există citire.h şi citire.cpp, validare.h şi validare.cpp,
prelucrare.h şi prelucrare.cpp, respectiv afisare.h şi afişare.cpp. Codul
sursă pentru operaţiile de citire este dat în figura 6.10 (citire.cpp).
void citire_dimensiuni(int *m, int *n) {
printf("Introduceti numarul de linii ale matricei = ");
scanf("%d", m);
printf("\nIntroduceti numarul de coloane ale matricei =
");
scanf("%d", n);
}

void citire_mat(int a[][N], int m, int n) {


for (int i = 0; i < m; i++ ) {
for (int j = 0; j < n; j++) {
printf("a[%d][%d] = ", i, j);
scanf("%d", &a[i][j]);
}
}
}
Figura 6.10 Codul sursă al modulului de citire

Modulul de validare are codul sursă din figura 6.11 (validare.cpp).


int simetrie1( int a[][N], int m, int n) {
int result = 1;
for (int i = 0; result && i < m; i++ ) {
for (int j = 0; result && j < n; j++ ) {
if ( a[i][j] != a[j][i] ) {
result = 0;
}
}
}
return result;
}

int simetrie2( int a[][N], int m, int n) {


int result = 1;
for (int i = 0; result && i < m; i++ ) {

115
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

for (int j = 0; result && j < n; j++ ) {


if ( a[i][j] != a[m-i-1][n-j-1] ) {
result = 0;
}
}
}
return result;
}
Figura 6.11 Codul sursă al modulului de testare a simetriei

Pentru modulul de prelucrări, implementare în C++ este dată în figura


6.12 (prelucrare.cpp).

int contor(int a[][N], int m, int n, int prag,


int(*func)(int, int)) {
int contor = 0;
for ( int i = 0; i < m; i++ ) {
for ( int j = 0; j < n; j++ ) {
if ( func(a[i][j],prag) > 0) {
contor++;
}
}
}
return contor;
}

int minim (int a[][N], int m, int n) {


int min = a[0][0];
for ( int i = 0; i < m; i++ ) {
for ( int j = 0; j < n; j++ ) {
if ( min > a[i][j] ) min = a[i][j];
}
}
return min;
}

int maxim (int a[][N], int m, int n) {


int max = a[0][0];
for ( int i = 0; i < m; i++ ) {
for ( int j = 0; j < n; j++ ) {
if ( max < a[i][j] ) max = a[i][j];
}
}
return max;
}
Figura 6.12 Codul sursă pentru modulul de prelucrări

116
Programarea modulară

În final, codul sursă pentru modulul de afişare mesaje şi rezultate este


dat în figura 6.13 (afisare.cpp)
void afiseaza(char* mesaj, int result) {
printf("%s %d\n", mesaj, result);
}

void mesaj(int cod) {


switch(cod) {
case 1:
printf("Matricea este simetrica!\n");
break;
case 2:
printf("Matricea nu est simetrica!\n");
break;
}
}
Figura 6.13 Codul sursă pentru modulul de afişare
În aceste condiţii, programul care determină numărul de elemente
pozitive, negative şi nule din cadrul unei matrice citită de la tastatură, şi care
este scris folosind modulele descrise anterior, are următorul cod sursă:
int maimare(int a, int b) {
return a > b;
}

int maimic(int a, int b) {


return a < b;
}
int egal( int a, int b) {
return a == b;
}
int main() {
int a[M][N], m, n;
citire_dimensiuni(&m, &n);
citire_mat(a, m, n);
//numara elementele pozitive, negative si nule
afiseaza("Numarul de elemente pozitive este",
contor(a, m, n, 0, maimare));
afiseaza("Numarul de elemente negative este",
contor(a, m, n, 0, maimic));
afiseaza("Numarul de elemente nule este",
contor(a, m, n, 0, egal));
return 1;
}

117
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Cele trei funcţii maimare, maimic, egal sunt transmise ca parametrii


funcţiei de contorizare, în aşa fel încât aceasta din urmă capătă un grad
ridicat de generalitate, putând număra orice tip de element din matrice care
trebuie să respecte o condiţie, condiţie stabilită prin funcţia transmisă ca
parametru.
Programarea modulară se bazează tot pe filozofia programării
structurate. Aceasta din urmă permite divizarea programelor în module ce
sunt implementate şi testate pe calculator în mod independent. Abordarea
modulară, fie ca este bottom-up sau top-down, nu aduce un plus de
complexitate, ci doar reorganizează acelaşi cod sursă rezultat din
programarea structurată. Prin urmare, pentru exemplul considerat, valorile
complexităţii obţinute la abordarea structurată, pentru cazul folosirii de
funcţii în implementarea soluţiei problemei şi nu în abordarea tip monolit,
rămân valabile şi pentru programarea modulară. Există o componentă a
complexităţii care se modifică, şi anume, cea cognitivă, pentru că, prin
descompunerea programului în module, cu funcţionalităţi clare, bine
delimitate, creşte inteligibilitatea codului şi prin urmare el devine mai uşor
de înţeles de către programator, chiar dacă, în esenţă este acelaşi cod.
Această componentă însă nu se reflectă în nici o măsură cantitativă a
complexităţii.

6.4 Parametrizarea

La dezvoltarea programelor trebuie să se ia în considerare modificări


ce vor apare în timp, la nivelul:
• datelor de intrare, în sensul introducerii de câmpuri,
modificării de domeniu, creşterii dimensiunii şi volumului de
date, schimbării criteriilor de căutare;
• formulelor de calcul, prin adăugarea, eliminarea şi înlocuirea
de operatori şi de operanzi, prin adăugarea şi eliminarea de
formule;
• rezultatelor, prin adăugarea de noi rezultate care trebuie
obţinute, prin schimbarea conţinutului unor rezultate existente
şi prin eliminarea unora dintre acestea.
Dacă structura produsului software este gândită rigid, orice modificare
la nivel conceptual se reflectă prin succesiuni de modificări în textul sursă,
modificări caracterizate prin efecte de antrenare multiplă, imprevizibile. De

118
Programarea modulară

aceea, trebuie ca programele să fie concepute astfel încât să preia toate


modificările prin introducerea unor parametri de către utilizatorul produsului
software. De exemplu, în matricea pentru care se efectuează prelucrări,
trebuie şterse unele linii, respectiv, unele coloane. Dacă programul este
construit rigid, trebuie definite două proceduri, una de ştergere linie din
matrice şi alta de ştergere coloană, iar programatorul trebuie să le activeze
în program, după ce s-a făcut citirea matricei iniţiale din fişier. O altă
variantă constă în definirea încă de la începutul elaborării produsului
software a doi vectori, unul pentru gestionarea liniilor, iar celălalt pentru
gestionarea coloanelor, care vor avea valorile 1, dacă linia, respective
coloana este activă şi 0 în caz contrar.
Dacă programul este conceput rigid, iar la un moment dat se impune
ca operaţiile pe matrice să nu ia în considerare una din simetrii, trebuie ca
subprogramele de testare a simetriei să fie scoase, necesitând operaţii în
cascadă asupra produsului. Proiectarea flexibilă va impune un parametru pe
care îl iniţializează utilizatorul cu 1 dacă se lucrează pe matrice simetrică şi
0 dacă se lucrează pe toată matricea.
În cazul unor evaluări de expresii, se introduc coeficienţi, care prin
iniţializare cu -1 sau cu 1, vor determina evaluări de expresii diferite, iar alţi
coeficienţi iniţializaţi cu 1 sau 0 vor extinde sau vor restrânge formulele. De
exemplu, pentru evaluarea expresiilor:
E1 = a + b + c + d + h + g + p
E2 = a + b - c + d - h + g - p
E3 = a - b - c - d
E4 = a + b + d + h + p
E5 = a - b + c - d + h + g – p
se procedează astfel:
• variabilele a, b, c, d, h, g, p sunt memorate în componentele
x[0], x[1], x[2], x[3], x[4], x[5], respectiv, x[6];
• se construieşte vectorul k[7], care se iniţializează cu +1, dacă
termenii se adună sau cu -1, dacă termenii se scad;
• se construieşte vectorul s[7] care se iniţializează cu 1 dacă
variabila participă la evaluarea expresiei, respectiv cu 0, în caz
contrar;

119
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

• se construieşte vectorul e[5], în care se evaluează una din cele


6 expresii.
Utilizatorul produsului software nu are altceva de făcut decât să
iniţializeze vectorii k[] şi s[]. Tabelul 6.2 conţine valorile celor doi vectori
pentru cele cinci expresii.

Iniţializările variabilelor de structură ale expresiei


Tabelul 6.2
a b c d h g p a b c d h g P
k[0] k[1] k[2] k[3] k[4] k[5] k[6] s[0] s[1] s[2] s[3] s[4] s[5] s[6]
E1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1 +1
E2 +1 +1 -1 +1 -1 +1 -1 +1 +1 +1 +1 +1 +1 +1
E3 +1 -1 -1 -1 +1 +1 +1 +1 +1 +1 +1 0 0 0
E4 +1 +1 +1 +1 +1 +1 +1 +1 +1 0 +1 +1 0 +1
E5 +1 -1 +1 -1 +1 +1 -1 +1 +1 +1 +1 +1 +1 +1

În cazul în care apar probleme de stabilire a apartenenţei la intervale şi


prin program se definesc în instrucţiuni if limitele intervalelor, ori de câte
ori se modifică limitele, se modifică numărul de intervale, trebuie operat în
program. De exemplu, pentru evaluarea expresiei
⎧a, dacă x < -1
⎪ 2
⎪a , dacă x ∈ [−1, 1]
e=⎨ 3
⎪a , dacă x ∈ (1, 7]
⎪a 4 , în rest

secvenţa de program este
………
if ( x < -1 ) {
e = a;
} else if ( x <= 1 ) {
e = a * a;
} else if ( x <= 7 ) {
e = a * a * a;
} else {
e = a * a * a * a;
}
............

120
Programarea modulară

Se acceptă ideea introducerii de la tastatură a numărului K de


intervale, iniţializarea a doi vectori cu limitele inferioare şi limitele
superioare ale K intervale. Se stabileşte o modalitate de preluare a
expresiilor ce trebuie calculate. Ori de câte ori se modifică numărul de
intervale K şi limitele intervalelor, utilizatorul modifică parametrii. Nu se
intervine asupra textului sursă.
Programarea modulară presupune existenţa unor subprograme cu
caracteristici de calitate excelente, destinate execuţiei unor operaţii de bază.
Subprogramele acestea se încorporează în module, permiţând creşterea
gradului de complexitate a prelucrărilor, menţinându-se nivelul ridicat al
performanţei.

121
7.1 Proiectarea orientată obiect

Paradigma orientării pe obiecte are marele avantaj al unor instrumente


mai bune de reprezentare a problemei, prin combinarea caracteristicilor şi a
comportamentului unei entităţi într-o singură construcţie. De exemplu,
pentru problema definită în capitolul Ciclul de dezvoltare software,
dezvoltarea aplicaţiei software în manieră structurată, presupune:
• definirea unor variabile în cadrul programului, pentru a
memora informaţiile cu privire dimensiunile matricei şi la
elementele componente ale acesteia;
• definirea de funcţii cărora li se transmit aceste informaţii şi
care realizează prelucrările specifice.
Dezavantajul acestei abordări rezidă în faptul că datele sunt gestionate
în cadrul aplicaţiei separat de funcţionalitate. Aceasta înseamnă că o
secţiune de cod în aplicaţie este legată strâns prin control şi transfer de
informaţii, de multe alte secţiuni ale aplicaţiei. Aceste dependenţe apar când
sunt folosite variabile globale, de exemplu. În abordarea orientată obiect,
separarea nu mai există. Informaţiile sunt memorate în interiorul obiectului,
fiind astfel protejate la accesul din exterior, iar funcţiile au acces direct la
ele, fără a mai fi nevoie să fie transmise ca parametrii.
Datorită acestor avantaje au fost dezvoltate metodologii de analiză şi
proiectare orientate obiect a aplicaţiilor software.
Proiectarea orientată obiect are în vedere identificarea şi separarea
responsabilităţilor. Posibilitatea reutilizării codului rezidă tocmai în faptul
că acel cod nu are elemente specifice pentru un anumit domeniu sau
aplicaţie; el trebuie să delege toată responsabilitatea legată de aspecte
specifice ale domeniului către secţiuni specifice ale aplicaţiei. Designul
orientat obiect este unul „responsability-driven”. De aceea, procesul de
design începe cu analiza comportamentului, pentru că, spre deosebire de
structurile de date şi specificaţiile formale ale apelurilor de funcţii, care
ajung să fie cunoscute şi stabilite mult mai târziu, în urma analizei mult mai

122
Programarea orientată obiect

profunde a problemei, comportamentul, adică ceea ce aşteaptă utilizatorul să


facă aplicaţia pentru el, este descris încă de la început, în termeni cu
semnificaţie atât pentru programatori cât şi pentru client.
Prima acţiune a echipei de proiectare software este de a rafina şi
clarifica specificaţiile iniţiale ale clientului, care, de cele mai multe ori sunt
neclare şi incomplete. De asemenea, trebuie avut în vedere posibilele
modificări de specificaţii ce apar ulterior şi care afectează produsul în
procesul de dezvoltare. De aceea, structura produsului software trebuie
gândită în aşa fel încât impactul modificărilor de specificaţii să fie minim.
Ingineria software este simplificată prin identificarea şi dezvoltarea de
componente software. O componentă este o entitate abstractă care
realizează anumite acţiuni, adică îndeplineşte anumite responsabilităţi. În
fazele iniţiale ale procesului de dezvoltare nu este important să se cunoască
reprezentarea exactă a unei componente sau cum realizează o anumită
acţiune. În final, o componentă se concretizează într-o funcţie, structură sau
clasă, sau o colecţie de alte componente. Sunt importante două caracteristici:
• o componentă trebuie să aibă un set redus bine definit de
responsabilităţi;
• interacţiunea dintre componente trebuie să fie minimă.
Identificarea componentelor se realizează în momentul în care se
imaginează procesul de execuţie a sistemului; asta înseamnă stabilirea
acţiunilor efectuate şi identificarea entităţilor care le efectuează.
Oricât de bine este condus procesul de specificare a cerinţelor şi de
proiectare a produsului software, întotdeauna este nevoie să se intervină
ulterior în formularea cerinţelor şi în structura produsului, datorită unor noi
cerinţe ale utilizatorului sau rafinării unora deja existente. Programatorii şi
designerii software trebuie să anticipeze aceste situaţii şi să minimizeze
impactul lor. Printre acţiunile pe care trebuie să le întreprindă pentru
aceasta, se numără:
• obiectivul principal este minimizarea numărului de
componente afectate de o schimbare de cerinţe. Chiar şi în
cazul schimbărilor majore, acesta nu trebuie să afecteze prea
multe secţiuni de cod;
• identificarea încă de la început a codului sursă cel mai
predispus în viitor la schimbări şi izolarea efectelor acestor
schimbări; cele mai frecvente modificări ţin de interfeţe, de
formatele de comunicare, de formatele rezultatelor;

123
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

• reducerea cuplării dintre componente, ceea ce reduce


dependenţa dintre ele şi creşte posibilitatea modificării uneia
cu implicaţii minime asupra celeilalte.
Pe lângă comportament, componentele conţin şi informaţii. O
componentă reprezintă o pereche constituită din comportament şi stare.
• comportamentul unei componente este setul de acţiuni pe care
le realizează; descrierea completă a comportamentului unei
componente se mai numeşte şi protocol.
• starea unei componente reprezintă toate informaţiile memorate
în interiorul ei.
Nu este necesar ca toate componentele să menţină informaţii de stare,
însă majoritatea componentelor vor fi o combinaţie de comportament şi
stare.
Două concepte importante folosite în proiectarea componentelor
software sunt cuplarea şi coeziunea. Coeziunea reprezintă măsura în care
responsabilităţile unei singure componente formează un tot semnificativ. O
coeziune ridicată este obţinută prin asocierea acţiunilor care sunt relaţionate
dintr-un anumit punct de vedere, într-o singură componentă. Cuplarea
descrie relaţia dintre componentele software. În general, este de dorit
minimizarea dependenţelor dintre componente, din moment ce acestea
afectează reutilizarea codului, uşurinţa în modificare şi dezvoltare. În
particular, cuplarea este crescută atunci când o componentă software trebuie
să acceseze informaţiile de stare ale altei componente. Aceste situaţii trebuie
evitate, o modalitate este ca acţiunea respectivă să fie transferată ca
responsabilitate componentei care memorează informaţia de stare dorită.
Atunci când o componentă dezvoltată de un programator este utilizată
de un altul, este obligatoriu ca acesta din urmă să ştie cum să o folosească şi
mai puţin să ştie cum a fost implementată. Folosirea unei componente
presupune cunoaşterea responsabilităţilor pe care le are, adică a acţiunilor
disponibile şi nu cum aceste acţiuni sunt implementate. Aceasta reprezintă
separarea dintre interfaţă şi implementare. Separarea este necesară din mai
multe motive:
• ascunderea detaliilor de implementare ţine de o caracteristică
fundamentală a programării orientate obiect, şi anume
încapsularea, deoarece nu este relevant şi nici nu ar trebui să
intereseze pe un programator care foloseşte o componentă cum
sunt implementate funcţionalităţile sale;

124
Programarea orientată obiect

• implementarea comportamentului unei componente se


schimbă; atâta timp cât interfaţa expusă pentru celelalte
componente nu se schimbă, impactul modificării implementării
este minim. De exemplu, există o componentă folosită pentru
salvarea unor informaţii pe disc; o implementare iniţială este
salvarea într-un fişier binar; ulterior însă, se doreşte salvarea
într-o bază de date sau un fişier xml. Modificarea modului în
care se salvează datele nu trebuie să afecteze implementarea
celorlalte componente care utilizează această funcţionalitate.
Implementarea componentelor software se face prin intermediul
construcţiilor de tip clasă, puse la dispoziţie de limbajele de programare. Nu
toate limbajele de programare sunt 100% orientate obiect. De exemplu,
limbajul C++ permite în continuare construcţii din limbajul C, care nu sunt
orientate obiect: funcţii definite în afara claselor, variabile globale etc.

7.2 Clasele

Programarea orientată obiect aduce în plus faţă de celelalte construcţii


ale altor tehnici de programare, un conglomerat numit clasă. În
[MSPCD97], clasa este definită ca o categorie generalizată ce descrie un
grup de elemente particulare, numite obiecte, care fac parte din ea. O clasă
reprezintă un instrument de descriere utilizat în programare pentru a defini o
entitate sub cele două aspecte: al caracteristicilor sale, definite prin
atributele clasei şi al comportamentului său, definit prin metodele clasei.
Prin comportamentul unei entităţi se înţelege modul în care aceasta
reacţionează la interacţiunea cu alte entităţi.
Definirea unei clase se realizează prin:
• precizarea operanzilor sau a atributelor;
• funcţiile membre;
• proprietăţile asociate componentelor pentru a stabili modalităţi
de referire.
O clasă este un tip de dată definit de utilizator printr-o construcţie de
forma:
class nume {
element 1;
element 2;
………….
element n;
}

125
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Un element este fie variabilă de tip fundamental sau de tip derivat, în


cazul atributelor, fie respectiv o funcţie.
Rezultă că o clasă se descrie sub forma:
class nume {
tip1 variabila1;
tip2 variabila2;
…………….
tipn variabilan;
tip1 functie1 ( lista parametrii 1 );
tip2 functie2 ( lista parametrii 2 );
………………….
tipm functiem ( lista parametrii m );
}

Clasele diferă de structurile de tip articol prin faptul că elementelor


din componenţă li se asociază restricţii de referire cu ajutorul
specificatorilor de control. Aşa cum variabilele în programe sunt de tip
global şi local sau după modul de alocare sunt statice şi dinamice, tot
astfel, pentru a defini o anumită disciplină în manipularea claselor se
asociază grupurilor de elemente din clasă un specificator de control al cărui
câmp de acţiune este anulat de un altul.
Specificatorii de control sunt:
• public – ceea ce înseamnă că elementul respectiv este accesibil
oricărei clase externe;
• private – înseamnă că numai elementele componente ale
aceleaşi clase au dreptul de a accesa acest membru;
• protected – prin care disponibilitatea elementului include
disponibilitatea specificatorului private, şi, în plus, asigură
disponibilitatea elementului pentru elementele ce urmează a fi
definite în clasele derivate din clasa curentă.
Funcţiile membru ale clasei referă oricare dintre variabilele definite în
clasă. La proiectarea unei clase se au în vedere următoarele aspecte:
• funcţiile membru să acopere întreaga gamă de prelucrări;
• să fie definite cât mai multe forme de iniţializare a operanzilor;
• între variabilele şi funcţiile membre să fie o concordanţă
perfectă pentru a nu apare erori în execuţia programelor ce
folosesc clase.

126
Programarea orientată obiect

Programarea orientată obiect este tehnica de programare fundamentată


pe conceptul de reutilizare software. Pentru a atinge acest obiectiv trebuie ca:
• procedurile să efectueze prelucrări complete şi corecte;
• referirea procedurilor să se efectueze rapid şi uşor, fără a fi
nevoie de informaţii inutile din moment ce funcţiile membre
utilizează variabile definite în aceeaşi clasă;
• manipularea claselor să conducă la obţinerea de noi clase, iar
gradul de generalitate să fie cât mai ridicat.
De exemplu, dacă se doreşte implementarea calculului cu numere
complexe, se defineşte clasa Complex care trebuie să aibă o structură de tip
articol pentru a grupa partea reală şi partea imaginară a numărului complex
şi să conţină funcţii membre pentru operaţiile de adunare, scădere, înmulţire,
împărţire, ridicare la putere şi extragere de radical.
Această clasă se defineşte în secvenţa de sursă:
class Complex {

private:
struct compl {
float real;
float img;
} c;

public:
……………………

void aduna ( Complex& cpl ) {


c.real = c.real + cpl.c.real;
c.img = c.img + cpl.c.img;
}

void scade ( Complex& cpl ) {


c.real = c.real - cpl.c.real;
c.img = c.img - cpl.c.img;
}

void produs ( Complex& cpl ) {


c.real = c.real * cpl.c.real - c.img *
cpl.c.img;
c.img = c.real * cpl.c.real + c.img *
cpl.c.img;
}

void divide ( Complex& cpl ) {


float temp;
temp = cpl.c.real * cpl.c.real - cpl.c.img *
cpl.c.img;

127
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

c.real = ( c.real * cpl.c.real - c.img *


cpl.c.img ) / temp;
c.img = ( c.real.*cpl.c.img - c.img *
cpl.c.real ) / temp;
}

………………………..
}

Aşa cum sunt definite funcţiile membru ale clasei Complex, au asociat
specificatorul de control public. Clasele sunt bine construite dacă la
elaborarea unui program principal apar numai referiri de funcţii membre din
clase.

7.3 Constructori şi destructori

Constructorii sunt metode cu acelaşi nume ca al clasei, utilizaţi pentru


construirea instanţelor clasei. Ei se execută de câte ori se alocă memorie
pentru o nouă instanţă a clasei, după ce s-a produs alocarea de memorie.
Este recomandat ca o clasă să conţină mai multe declarări de constructori,
care vor diferi între ele prin numărul şi tipul parametrilor transmişi. În felul
acesta, se asigură un grad mai mare de utilizabilitate al clasei, dat fiind
posibilităţile diferite de instanţiere.
Limbajul C++ pune la dispoziţia utilizatorilor trei tipuri de alocări de
memorie:
• statică, atunci când o variabilă este declarată în afara oricărei
funcţii sau când pentru un atribut se foloseşte modificatorul
static. Un atribut static este comun tuturor instanţelor derivate
clasei din care provine;
• automatică, care se face pentru variabile definite în cadrul
corpului unei funcţii; memoria alocată este dealocată atunci
când se părăseşte corpul funcţiei;
• dinamică, care se face pe heap, la cererea explicită a
programatorului.
Destructorii sunt metode ale clasei referiţi înainte de a se produce
dealocarea zonei de memorie asociate elementelor clasei. O clasă nu are
decât un singur destructor. Destructorul este o procedură care se defineşte
prin numele clasei, fără a avea listă de parametrii, fiind precedat de
operatorul ~; pentru clasa Complex destructorul are forma ~Complex().

128
Programarea orientată obiect

Atât constructorii, cât şi destructorii nu au tipuri asociate.


Forma constructorilor variază de la o clasă la alta, în funcţie de
specificul fiecăreia, dar şi de experienţa şi obiectivele proiectantului clasei.
Există o formă de constructor, numită constructor de copiere, foarte utilă în
special atunci când se transmit obiecte ca parametrii prin valoare.
Constructorul de copiere primeşte ca unic parametru o referinţă la un alt
obiect al aceleaşi clase. Implementarea cea mai frecventă presupune
copierea efectivă a valorilor atributelor obiectului transmis ca parametru în
atributele obiectului nou.
De exemplu, pentru clasa Complex se identifică următorii constructori
pentru iniţializarea:
• cu variabile corespunzătoare membrilor structurii care
stochează atributele numărului complex;
• cu un alt obiect din aceeaşi clasă.
Corespunzător, textele sursă de definire a constructorilor sunt:
Complex ( float a, float b ) {
c.real = a;
c.img = b;
}

Complex ( Complex& cpl ) {


c.real = cpl.c.real;
c.img = cpl.c.img;
}

7.4 Proprietăţile claselor

Programarea orientată obiect dezvoltă noi abordări prin faptul că


obiectele şi clasele se bucură de o serie de proprietăţi.

7.4.1 Încapsularea
Este rezultatul regrupării operanzilor şi funcţiilor membre într-un
conglomerat numit clasă. Operanzii şi operatorii clasei interacţionează,
formând un tot unitar.
De exemplu, se consideră clasa Matrice. Încapsularea revine la a
defini operanzii care se referă la numărul de linii, numărul de coloane şi
elementele matricei în clasă, precum şi funcţiile care efectuează operaţiile
de calcul, formând un tot unitar. Funcţiile folosesc direct operanzii întrucât

129
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

prin încapsulare este dreptul lor de a referi ceva definit pe un domeniu


comun. În limbajul FORTRAN instrucţiunea COMMON avea menirea de a
implementa germeni ai încapsulării asociind zone de memorie grupate unor
operanzi, fără a mai fi nevoie de a-i înscrie în lista de parametrii exact ca în
cazul încapsulării propriu-zise.
Pentru efectuarea operaţiilor de numărare elemente pozitive, negative
şi nule, precum şi pentru testarea simetriei matricei lista parametrilor este
nulă, deoarece toate informaţiile necesare sunt deja definite în cadrul clasei
şi folosite ca atare, nemaifiind nevoie să fie transmise ca parametrii, codul
sursă al clasei Matrice este:
class Matrice {
protected:
int nrCol;
int nrLinii;
int mat[M][N];

public:
Matrice( int m, int n ) {
nrCol = m;
nrLinii = n;
for ( int i = 0; i < m; i++ ) {
for ( int j = 0; j < n; j++ ) {
mat[i][j] = 0;
}
}
}

Matrice( int a[][N], int m, int n ) {


nrCol = m;
nrLinii = n;
for ( int i = 0; i < nrLinii; i++ ) {
for ( int j = 0; j < nrCol; j++ ) {
mat[i][j] = a[i][j];
}
}
}

Matrice( Matrice& m ) {
nrCol = m.nrCol;
nrLinii = m.nrLinii;
for ( int i = 0; i < nrLinii; i++ ) {
for ( int j = 0; j < nrCol; j++ ) {
mat[i][j] = m.mat[i][j];
}
}
}

bool simetrie1() {
bool result = true;
if ( nrCol != nrLinii ) {

130
Programarea orientată obiect

result = false;
} else {
for ( int i = 0; result && i < nrLinii; i++ ) {
for ( int j = 0; result && j < nrCol; j++
) {
result = mat[i][j] == mat[j][i];
}
}
}
return result;
}

int contorplus() {
int contor = 0;
for ( int i = 0; i < nrLinii; i++ ) {
for ( int j = 0; j < nrCol; j++ ) {
if ( mat[i][j] > 0 ) {
contor++;
}
}
}
return contor;
}

int contorminus() {
int contor = 0;
for ( int i = 0; i < nrLinii; i++ ) {
for ( int j = 0; j < nrCol; j++ ) {
if ( mat[i][j] < 0 ) {
contor++;
}
}
}
return contor;
}

int contornul() {
int contor = 0;
for ( int i = 0; i < nrLinii; i++ ) {
for ( int j = 0; j < nrCol; j++ ) {
if ( mat[i][j] == 0 ) {
contor++;
}
}
}
return contor;
}

131
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

7.4.2 Moştenirea
Este o proprietate deosebit de importantă, pe baza ei sunt construite
clasele din aproape în aproape, organizându-se pe niveluri de agregare.
Clasele agregate de pe nivelul k preiau operanzii şi funcţiile membre
ale claselor de pe nivelul k-1, care intră prin derivare în componenţa lor,
precum şi proprietăţile acestora.
De exemplu, dacă se doreşte îmbogăţirea clasei Matrice cu funcţii
pentru determinarea minimului şi maximului elementelor din cadrul
matricei, se construieşte o nouă clasă Matrice2, unde, pe lângă funcţiile deja
existente ale clasei se adaugă noile metode pentru aflarea minimului,
respectiv, maximului, clasa obţinută având codul sursă:
class Matrice2 : public Matrice {

public:
int minim () {
int min = mat[0][0];
for ( int i = 0; i < nrLinii; i++ ) {
for ( int j = 0; j < nrCol; j++ ) {
if ( min > mat[i][j] ) {
min = mat[i][j];
}
}
}

return min;
}

int maxim () {
int max = mat[0][0];
for ( int i = 0; i < nrLinii; i++ ) {
for ( int j = 0; j < nrCol; j++ ) {
if ( max < mat[i][j] ) {
max = mat[i][j];
}
}
}

return max;
}
};

Avantajele folosirii moştenirii sunt:


• reutilizabilitatea – când o funcţionalitate este moştenită din altă
clasă, codul respectiv nu trebuie rescris, el trebuie doar apelat în
noul context; o altă implicaţie este legată de fiabilitatea codului,

132
Programarea orientată obiect

deoarece prin moştenire, o anumită funcţionalitate este scrisă doar


la nivelul unei clase şi apoi moştenită şi utilizată în toate clasele
derivate;
• consistenţa interfeţei – când două sau mai multe clase sunt derivate
din aceeaşi clasă părinte, se asigură faptul că comportamentul
moştenit este acelaşi pentru toate clasele;
• componentele software – moştenirea dă posibilitatea programatorilor
să construiască componente software reutilizabile şi gruparea lor în
biblioteci; în acest fel, efortul de dezvoltare al unui produs nou este
diminuat prin utilizarea de librării cu funcţionalitate deja
implementată;
• dezvoltarea rapidă de prototipuri – atunci când sistemul software
este construit folosindu-se componente reutilizabile, timpul de
dezvoltare este concentrat pe înţelegerea elementelor specifice ale
sistemului; astfel se construiesc versiuni de sistem, numite
prototipuri, care pun accent pe aspectele critice ale sistemului. Un
prototip este dezvoltat, utilizatorii îl folosesc, iar a doua versiune a
sistemului este realizată pe baza experienţei acumulată cu prima şi
a feedbackului de la utilizatori.
Deşi moştenirea prezintă foarte multe avantaje, există şi o serie de
costuri de care trebuie să se ţină seama în momentul proiectării ierarhiilor de
clase:
• viteza de execuţie – este influenţată prin prisma faptului că
metodele moştenite, care au un caracter mai general, sunt de regulă
mai lente decât codul specializat; însă afectarea vitezei de execuţie
este compensată cu creşterea vitezei de dezvoltare;
• dimensiunea programelor – este influenţată în sensul că devine
mai mare în cazul programelor care folosesc librării de
componente, decât programele care folosesc cod specializat,
deoarece nu toate componentele dintr-o librărie sunt folosite în
proiect, dar librăria trebuie adăugată în totalitatea sa;
• complexitatea programelor – o ierarhie de clase introduce un
anumit grad de complexitate în sistem; cu cât ierarhia este mai
mare, nivelurile de abstractizare mai multe, cu atât sistemul care
foloseşte această ierarhie este mai complex.

133
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

7.4.3 Polimorfism
În limbajele de programare, un obiect polimorfic este orice entitate,
cum ar fi o variabilă sau argument de funcţie, căreia îi este permis să
stocheze valori de tipuri diferite pe durata execuţiei programului. Funcţiile
polimorfice sunt acelea care au argumente polimorfice. În programarea
orientată-obiect, polimorfismul reflectă principiul substituibilităţii, şi
anume, o variabilă polimorfică poate stoca o valoare a tipului său declarat
sau a oricărui subtip al tipului declarat. Polimorfismul funcţiilor reprezintă
posibilitatea de a asocia acelaşi nume la diferite funcţii. Evitarea
ambiguităţii în procesul de referire se obţine prin:
• atribuirea de liste de parametrii de lungimi diferite;
• atribuirea de liste de parametrii cu aceeaşi lungime, dar
parametrii corespunzători ca poziţie au tipuri diferite;
În cazul funcţiilor, polimorfismul poartă şi numele de
supraîncărcare.
Pentru clasa Matrice se defineşte operaţia de adunare a două matrice.
Implementarea operaţiei se face prin două metode, denumite aduna,
diferenţiate prin lista de parametrii. Clasa Matrice devine:
class Matrice {
………………………….
void aduna (Matrice& m ) {
aduna(m.mat, m.nrLinii, m.nrCol );
}

void aduna ( int a[][N], int m, int n ) {


for ( int i = 0; i < m; i++ ) {
for ( int j = 0; j <n ; j++ ) {
mat[i][j] += a[i][j];
}
}
}
};

Cele două metode sunt polimorfice; au acelaşi nume, iar stabilirea


apelului corect se face la execuţie, în funcţie de numărul, tipul şi poziţia
parametrilor.
O altă situaţie în care se pune problema polimorfismului o reprezintă
suprascrierea metodelor. Prin moştenire, clasa derivată preia metodele
expuse de clasa părinte, conform cu regulile de derivare. Însă, ea are
posibilitatea să furnizeze o altă implementare pentru o metoda moştenită.

134
Programarea orientată obiect

Practic, se defineşte o metodă în clasa derivată cu aceeaşi semnătură cu cea


din clasa părinte, dar care are o altă implementare. În momentul execuţiei,
metoda din clasa derivată este identificată şi executată înaintea metodei din
clasa părinte.
Suprascrierea se foloseşte atunci când, pentru o anumită
funcţionalitate, există o implementare implicită la nivelul clasei părinte,
implementare care poate fi rafinată prin suprascriere, în clasele derivate,
dacă se doreşte acest lucru. De exemplu, pentru metoda simetrie1() din clasa
Matrice se furnizează o altă implementare în clasa derivată Matrice2, prin
care simetria este testată atât pentru diagonala principală, cât şi pentru
diagonala secundară. Textul sursă este:
class Matrice {
………………………………

public:

virtual bool simetrie1() {


……………………………….
}
};

class Matrice2 : public Matrice {

public:
………………………………
bool simetrie1() {
//test pentru diagonala principala
bool result = Matrice::simetrie1();

//test pentru diagonala secundata


if ( nrCol == nrLinii ) {
for ( int i = 0; result && i < nrLinii;
i++ ) {
for ( int j = 0; result && j <
nrCol; j++ ) {
if ( mat[i][j] != mat[nrCol-
i-1][nrCol-j-1] ) {
result = false;
}
}
}
} else {
result = false;
}

return result;
}
};

135
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Se observă că în implementarea din clasa Matrice2 se foloseşte


rezultatul implementării din clasa Matrice, în plus testându-se şi simetria
faţă de diagonala secundară. Apelarea implementării din clasa părinte nu
este o cerinţă obligatorie atunci când se suprascrie o metodă, dar este o
practică recomandată, deoarece, teoretic, suprascrierea trebuie să aducă ceva
în plus faţă de o prelucrare generală, într-un context particular, al clasei
derivate.

Supraîncărcarea operatorilor
Reprezintă un mod elegant de a pune în corespondenţă simbolurile
unor operatori cu proceduri complexe de prelucrare, specifice unor tipuri de
date derivate.
Limbajul Basic a avut implementată adunarea, scăderea, înmulţirea de
numere şi adunarea de matrice. Prin supraîncărcarea de operatori se creează
premisele evaluării de calcule matriceal scriind expresiile direct, cu
operatori de calcul şi operanzi matrice.
În acest fel, operatorul + este pus în corespondenţă cu procedura de
adunare matrice, operatorul – este pus în corespondenţă cu procedura
scădere matrice, operatorul * este pus în corespondenţă cu înmulţirea de
matrice, iar operatorul = este pus în corespondenţă cu copierea de matrice.
Următorii operatori nu se supraîncarcă:
• ([]) operatorul de selectare a unei componente membre într-o
structură de tip articol;
• (*) operatorul de deferire a unei componente din clasă;
• (::) operatorul de selecţie a funcţiei membru dintr-o clasă;
operatorul de tratare variabile globale;
• (?:) operatorul condiţional.
Pentru supraîncărcarea operatorului binar numit operator, se
utilizează definirea:
tip1 operator simbol_operator ( tip2, tip3 )
unde:
• tip1 – tipul rezultatului returnat;
• tip2 – tipului primului operand;
• tip3 – tipul celui de-al doilea operand.

136
Programarea orientată obiect

Dacă supraîncărcarea operatorului simbol_operator este realizată în


clasa numită classa, definirea operatorului în afara clasei se realizează prin:
tip1 classa::calcul simbol_operator (tip2, tip3)
În exemplul de mai jos se prezintă supraîncărcarea operatorului +
pentru a însemna adunarea a două matrice şi a operatorului – pentru a
însemna scăderea a două matrice pornind de la clasa Matrice.
class Matrice {
…………………………
void aduna (Matrice& m ) {
aduna(m.mat, m.nrLinii, m.nrCol );
}

Matrice& operator + (Matrice& m ) {


aduna(m);
return *this;
}

Matrice& operator - (Matrice& m ) {


for (int i = 0; i < nrLinii; i++ ) {
for ( int j = 0; j < nrCol; j++ ) {
mat[i][j] = mat[i][j] -
m.mat[i][j];
}
}
return *this;
}
};

Datorită concepţiei total diferite cu privire la modul în care o aplicaţie


este proiectată şi implementată, evaluarea complexităţii necesită utilizarea
unor metrici specifice. Rezultatele furnizate de metricile prezentate în
capitolul Ciclul de dezvoltare software, şi anume, complexitatea
ciclomatică şi metricile Halstead nu sunt relevante. Pentru aplicaţia care
rezolvă problema considerată ca exemplu de control în această carte, se
construieşte clasa Matrice, iar programul apelator instanţiază această clasă
cu date citite de la tastatură şi va apela diverse metode ale clasei pentru a
obţine rezultatele cerute.
Valorile metricilor enunţate anterior suportă, în acest caz două niveluri
de agregare; ele sunt determinate la nivelul metodelor clasei, sunt agregate
la nivelul clasei, şi în final, la nivelul întregii aplicaţii. Rezultatele sunt:
Metrică Valoare
Complexitatea ciclomatică (v) = 29
Lungimea programului = 238
Vocabularul programului = 92
Dificultatea = 35,33

137
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Analizând comparativ cu rezultatele obţinute prin celelalte tehnici de


programare, există clar o creştere de complexitate, care are diverse cauze:
¾ existenţa unor construcţii specifice programării orientate-
obiect, cum sunt constructorii, metodele de acces la atributele
clasei etc.;
¾ analiza întregii clase, nu numai a unora dintre funcţiile
implicate în fluxul aplicaţiei, din motivul că, o clasă este
privită ca un tot unitar, şi prin urmare trebuie înţeleasă de către
programator în totalitatea sa;
¾ deşi informaţiile referitoare la dimensiunile şi elementele
matricei sunt înglobate în clasă, şi prin urmare, nu sunt
transmise ca parametrii, acest lucru nu este surprins de
metricile Halstead de complexitate, care iau în considerare, în
fiecare dintre metodele clasei, atributele acesteia ca fiind
operanzi distincţi, lucru ce contribuie la creşterea nejustificată
a valorilor acestor metrici.
De aceea, pentru analiza complexităţii programelor orientate obiect,
sunt utilizate metrici specifice. Există două tipuri de metrici: unele se referă
la interdependenţa dintre clase şi se numesc metrici de cuplare, altele se
referă la consistenţa clasei şi se numesc metrici de coeziune. Aceste metrici
sunt în legătură cu caracteristicile specifice ale programării orientate obiect:
încapsulare, moştenire, polimorfism, interacţiunea dintre obiecte.
Metricile de coeziune sunt legate în principal de dimensiunea claselor.
Unele cele mai relevante metrici din această clasă de metrici sunt:
¾ numărul de atribute ale clasei (NOA), împărţit în numărul de
atribute ale instanţei de clasă (NOI) şi numărul de atribute
statice (NOS);
¾ numărul de metode ale clasei (NOM), împărţit în numărul de
metode publice (NOP), şi numărul de metode private (NOPV);
aceste metrici sunt mărimi ale încapsulării;
¾ gradul de încapsulare (GI), văzut ca raport între numărul de
elemente private ale clasei şi numărul total de elemente.
Această mărime trebuie să fie cât mai aproape de valoarea 1.
Pentru atribute, el trebuie să fie întotdeauna 1.

138
Programarea orientată obiect

Pentru exemplul considerat, în care există clasele Matrice şi Matrice2,


se obţin următoarele valori pentru metricile prezentate mai sus:
Metrică Matrice
NOA = 3
NOI = 3
NOS = 0
NOM = 7
NOP = 7
NOPV = 0
GI = 0,33

Se observă lipsa metodelor de tip privat, ceea ce înseamnă că


funcţionalitatea expusă este foarte mare, practic toate metodele clasei sunt
prezente în interfaţa acesteia. O clasă cu o interfaţă bogată în metode are un
nivel de complexitate mai mare decât al unei clase care expune mai puţine
metode în interfaţa sa, deoarece prima necesită un efort de înţelegere din
partea programatorului mai mare, cel puţin teoretic, decât a doua.
Metricile de cuplare sunt legate, în principal, de interacţiunea dintre
obiecte sau dintre clase. Interacţiunea dintre obiecte se identifică în
parametrii transmişi în apelurile de metode, instanţierea de obiecte locale de
tipuri diferite, tipul rezultatului apelului unei metode. Două clase sunt
cuplate atunci când metodele declarate în una dintre ele folosesc metode
declarate în cealaltă. O cuplare excesivă afectează nu numai
reutilizabilitatea claselor, dar şi complexitatea acestora datorită numărului
mare de legături dintre clase care determină o înţelegerea mai greoaie a
codului de către programator.
Avantajele programării orientate obiect sunt nenumărate, printre cele
mai importante regăsindu-se şi reutilizarea codului. Prin intermediul
mecanismelor de derivare, se obţine acces la funcţionalitatea de bază a
obiectului, cu posibilitatea modificării acesteia prin mecanisme polimorfice,
cum sunt suprascrierea metodelor sau supraîncărcarea operatorilor. Prin
derivare, se obţin clase noi, ce modelează mai atent anumite aspecte ale
entităţilor reale sau abstracte utilizate în modelul aplicaţiei software.

139
8.1 Premise

Programarea orientată obiect oferă o abordare unitară asupra


fenomenelor modelate de aplicaţiile software atât din punct de vedere al
datelor, cât şi al prelucrărilor. Acest lucru a făcut ca această tehnică de
programare să se impună în detrimentul celorlalte. Programarea orientată
obiect a dus şi la construirea de metodologii de dezvoltare software orientate
obiect.
Un aspect foarte important în activitatea de programare îl constituie
reutilizarea codului. Acest lucru se face prin construirea de librării cu
elemente de cod, cum sunt funcţiile sau clasele de obiecte, şi utilizarea
acestora în noi aplicaţii. Principiile de construire a unor astfel de librării au
fost expuse în capitolele Programarea standard şi Programarea
orientată obiect. Datorită dezvoltării şi răspândirii de noi limbaje şi
tehnologii de programare, a apărut o primă mare problemă: cum se poate
folosi o funcţionalitate scrisă într-un limbaj de programare şi înglobată
într-o bibliotecă, în cadrul unei aplicaţii scrise în alt limbaj de programare?
Aceasta este problema interoperabilităţii dintre limbajele şi tehnologiile
diferite de programare.
Situaţia aceasta este foarte frecvent întâlnită, deoarece o companie
care utilizează software pentru desfăşurarea activităţilor sale, achiziţionează
acest software pe principii de eficienţă şi cost, mai puţin pe principii legate
de tehnologii şi limbaje folosite. Inevitabil, la un moment dat, acest software
va trebui să comunice, pentru rezolvarea unor probleme mai complexe.
Pentru aceasta, au fost fundamentate componentele.
Programarea bazată pe componente reprezintă o extensie a
programării orientate obiect în care elementul central este componenta.
Componenta este definită ca program sau obiect binar de dimensiuni mici,
cu funcţionalitate specifică, bine definită, proiectat într-o manieră care

140
Utilizarea de componente

permite combinarea cu alte componente şi/sau aplicaţii. Modelul unei


aplicaţii care utilizează componente este prezentat în figura 8.1:

I1 COMP1

I2 COMP2
PRODUS
SOFTWARE
……………………
……….

In COMPn

Figura 8.1 Modelul unei aplicaţii care foloseşte componente

Componentele se împart în trei mari categorii, după cum urmează:


• componente care oferă servicii utilizatorilor de aplicaţii; aici
intră în principal componentele vizuale, care sunt afişate pe
ecranele cu care lucrează utilizatorii aplicaţiei;
• componente care oferă servicii de business şi care
implementează reguli de business, cum sunt de exemplu, reguli
de calcul al impozitelor şi taxelor, modalităţi de livrare etc.;
rezultatele lor sunt furnizate componentelor din prima
categorie, pentru afişarea către utilizator;
• componente care oferă acces la date; acestea sunt componente
prin care se permite conectarea la baze de date, la alte aplicaţii
mai vechi, şi care furnizează date serviciilor din categoria
anterioară, de business, pentru prelucrări.

8.2 Software orientat pe componente

Ingineria software bazată pe componente foloseşte principii legate de


modularitate, abstractizare, încapsulare. Primul pas este crearea
specificaţiilor pentru interfaţă. Interfaţa unei componente reprezintă setul de
metode de prelucrare pe care componenta le expune spre utilizare celorlalte

141
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

componente şi/sau aplicaţii. Principiul este simplu şi are precedent în


abordarea orientată obiect: atâta timp cât funcţionalitatea oferită nu se
schimbă, modul în care aceasta este implementată nu contează pentru
utilizatorul componentei.
Interfaţa trebuie să aibă o serie de caracteristici:
• să fie consistentă, adică funcţionalităţile oferite să se refere la o
aceeaşi entitate reală; de exemplu, o interfaţă care expune
funcţionalităţi legate de lucrul cu matrice, nu poate conţine
funcţionalităţi legate de lucrul cu fişiere;
• să fie clară, ceea ce înseamnă că metodele din interfaţă să
exprime clar funcţionalitatea oferită;
• să fie scurtă, deoarece o interfaţă cu foarte multe metode este
greu de înţeles şi de folosit de către programatori.
Rezultatul acestei faze este un document de specificaţii privind
interfaţa componentei şi, eventual, o descriere a acesteia într-un limbaj
specializat.
O componentă poate expune mai multe interfeţe, gândite ca subseturi
de funcţionalitate pe care componenta o implementează. Motivul este
următorul: diverşi utilizatori ai componentei au nevoie doar de anumite
metode ale acesteia, prin urmare, expunerea întregii interfeţe către ei, în
primul rând îngreunează utilizarea componentei, dar şi deschide posibilităţi
de afectare a stării componentei de către utilizatori care nu ar trebui să o
afecteze. Prin urmare, expunerea de interfeţe diferite pentru tipuri diferite de
utilizatori înseamnă şi implementarea unui mecanism de securitate la nivelul
componentei.
Pentru problema definită în capitolul Ciclul de dezvoltare software,
se imaginează următoarea interfaţă ce este expusă unei alte aplicaţii,
interfaţă descrisă folosind notaţia UML:

Figura 8.2 Exemplu de interfaţă pentru componente

Se observă că interfaţa are o metodă de iniţializare a componentei cu


date referitoare la matrice, o metodă pentru determinarea minimului şi una

142
Utilizarea de componente

pentru determinarea maximului dintre elementele matricei, iar ultima


metodă numără elementele din matrice, astfel:
• dacă flag = 1, elementele mai mari decât valoarea specificată
în parametrul k;
• dacă flag = 2, elementele mai mici decât valoarea specificată în
parametrul k;
• dacă flag = 3, elementele egale cu valoarea specificată în
parametrul k;
Cuvântul “in” care precede fiecare parametru indică faptul că
respectivul este parametru de intrare.
O altă interfaţă a componentei conţine operaţiile de adunare, scădere şi
înmulţire. Descrierea, folosind notaţia UML este dată în figura 8.3:
«interface»
MatriceINTF2
+initializare(in nrLinii : long(idl), in nrCol : long(idl), in elemente : any(idl))
+aduna(in matrice : any(idl))
+scade(in matrice : any(idl))
+inmulteste(in matrice : any(idl))
+numarLinii() : long(idl)
+numarColoane() : long(idl)

Figura 8.3 Interfaţa componentei Matrice pentru operaţii cu masive

Se observă că, pe lângă metodele corespunzătoare operaţiilor cu


masive, există şi două metode, cu rol de accesorii pentru câmpuri de date ale
componentei. Fiecare dintre operaţiile de bază, adunare, scădere, înmulţire
primeşte ca parametru o altă componentă de acelaşi tip cu aceasta, iar
rezultatul este memorat în componenta curentă.
Pentru că o componentă este scrisă într-un limbaj de programare şi
folosită în alt limbaj de programare, mecanismul de construire a instanţelor
de componente este diferit faţă de programarea orientată obiect. Din acest
motiv este necesară o metodă separată de iniţializare a datelor componentei.
Pasul al doilea înseamnă crearea specificaţiilor pentru componente, ce
includ interfeţele şi funcţiile membre. Pornind de la specificaţiile
interfeţelor, se elaborează modelul obiectual al componentei care include, pe
lângă clasa principală, şi un set de clase ajutătoare. Obiectivul este de a
asigura dezvoltatorilor o specificaţie completă atât din punct de vedere al
funcţionalităţii implementate, cât şi din punct de vedere al modului în care
aceasta va fi implementată. Prin urmare, în această etapă se ţine cont de
tehnologia ce este folosită la dezvoltarea componentei.

143
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

Odată ce specificaţiile pentru componentă au fost întocmite,


programatorii încep dezvoltarea propriu-zisă. Unitatea de testare în ingineria
software bazată pe componente este realizată prin crearea unei alte
componente, care testează interfeţele şi funcţiile membre ale acesteia.
Fiecare programator va trebui să îşi construiască o a doua componentă prin
care să testeze funcţionalitatea componentei iniţiale. Motivaţia acestei
abordări este următoarea:
• în primul rând, fiecare funcţionalitate, înainte de a fi folosită
trebuie supusă unei faze de testare; programatorul are obligaţia
ca funcţionalitatea implementată, pe care el o oferă, să fie deja
testată; tehnica se numeşte unit-testing şi vizează stabilirea
următoarelor:
¾ conformitatea funcţionalităţii cu specificaţiile:
componenta face ceea ce este specificat să facă;
¾ implementarea reacţionează corespunzător şi în
situaţiile excepţionale de prelucrare: cazuri extreme,
fluxuri alternative de prelucrare etc.
• în al doilea rând, se testează comunicarea între componentă şi
mediul extern; practic, se simulează modul în care componenta
va fi folosită în cadrul unor aplicaţii concrete.
Faza de testare individuală (unit-testing) a componentei presupune la
rândul alte două faze:
• testarea componentei ca şi clasă (pentru că la bază nu este
altceva decât o clasă); aceasta presupune testarea directă a
metodelor componentei atât cele private, cât mai ales cele
publice;
• testarea componentei prin interfeţele expuse; această testare
trebuie să aducă în plus, faţă de rezultatele celei anterioare,
informaţii despre cum sunt primiţi şi trataţi parametrii de
intrare, cum se scriu datele în parametrii de ieşire.
Dezvoltarea unei componente diferă în funcţie de tipul de componentă
şi tehnologia folosită. Fiecare tehnologie are propriile mecanisme. Într-un
fel sunt scrise componentele folosind tehnologia COM şi limbajul C++ şi în
alt mod sunt scrise componentele folosind tehnologia EJB împreună cu
limbajul Java. Pentru exemplificare, s-a folosit tehnologia COM folosind

144
Utilizarea de componente

ATL şi limbajul C++. Astfel, interfaţa din figura 8.2 se scrie în felul
următor, folosind IDL + Interface Definition Language:
[
object,
uuid(F8AE2674-8836-433E-B809-D6DB90BFA8E9),
dual,
helpstring("IMatrice Interface"),
pointer_default(unique)
]
interface IMatrice : IDispatch
{
[id(1), helpstring("method initializare")] HRESULT
initializare(int nrLinii, int nrCol, int* mat);
[id(2), helpstring("method minim")] HRESULT minim(int*
min);
[id(3), helpstring("method maxim")] HRESULT maxim(int*
max);
[id(4), helpstring("method contor")] HRESULT
contor(int flag,int k, int* numar);
};

[
uuid(E64BA31E-7FBC-47CE-A993-474502FD2CB2),
version(1.0),
helpstring("Matrix 1.0 Type Library")
]

Pentru scrierea interfeţei au fost utilizate mecanismele de tip wizard


din cadrul mediului de dezvoltare Microsoft Visual Studio 6.0. Clasa C++
care implementează interfaţa are următorul cod sursă:
class ATL_NO_VTABLE CMatrice :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMatrice, &CLSID_Matrice>,
public IDispatchImpl<IMatrice, &IID_IMatrice,
&LIBID_MATRIXLib>
{
public:
CMatrice()
{
}

DECLARE_REGISTRY_RESOURCEID(IDR_MATRICE)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CMatrice)
COM_INTERFACE_ENTRY(IMatrice)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

// IMatrice

145
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

public:
STDMETHOD(contor)(int flag,int k, int* numar);
STDMETHOD(maxim)(int* max);
STDMETHOD(minim)(int* min);
STDMETHOD(initializare)(int nrLinii, int nrCol, int*
mat);
private:
int n;
int m;
int a[M][N];
};
…………………….
STDMETHODIMP CMatrice::initializare(int nrLinii, int nrCol,
int* mat)
{
m = nrLinii;
n = nrCol;

for ( int i = 0; i < m; i++ ) {


for ( int j = 0; j < n; j++ ) {
a[i][j] = mat[i*m + j];
}
}
return S_OK;
}

STDMETHODIMP CMatrice::minim(int *min)


{
*min = a[0][0];
for ( int i = 0; i < m; i++ ) {
for ( int j = 0; j < n; j++ ) {
if ( a[i][j] < *min ) {
*min = a[i][j];
}
}
}

return S_OK;
}

STDMETHODIMP CMatrice::maxim(int *max)


{
*max = a[0][0];
for ( int i = 0; i < m; i++ ) {
for ( int j = 0; j < n; j++ ) {
if ( a[i][j] < *max ) {
*max = a[i][j];
}
}
}

return S_OK;
}

146
Utilizarea de componente

STDMETHODIMP CMatrice::contor(int flag, int k, int *numar)


{
int i = 0;
int j = 0;
*numar = 0;

switch ( flag ) {
case 1:
for ( ; i < m; i++ ) {
for ( ; j < n; j++ ) {
if ( a[i][j] > k ) ( *numar )++;
}
}
break;
case 2:
for ( ; i < m; i++ ) {
for ( ; j < n; j++ ) {
if ( a[i][j] < k ) ( *numar )++;
}
}
break;
case 3:
for ( ; i < m; i++ ) {
for ( ; j < n; j++ ) {
if ( a[i][j] == k ) ( *numar )++;
}
}
break;
}

return S_OK;
}

Se observă că, pe lângă clasa C++ propriu-zisă, care implementează


funcţionalitatea componentei, mai sunt implicate o serie de alte clase,
specifice tehnologiei de a căror scriere se ocupă wizard-ul, îmbunătăţindu-se
astfel productivitatea. De asemenea, tipurile de date şi mai ales modul în
care sunt transmişi parametrii şi rezultatele diferă faţă de situaţiile normale
din programarea orientată obiect. De exemplu, rezultatul prelucrării se
returnează printr-un parametru de tip ieşire declarat în lista de parametrii;
funcţia returnează un cod de succes sau de eroare specific. De asemenea, nu
orice tip de dată este permis; numai tipurile fundamentale, şirurile de
caractere şi pointerii apar în listele de parametrii ale metodelor componentei
expuse prin interfaţă. Pentru metodele din interiorul componentei, nevizibile
în afară, se folosesc şi alte tipuri de date.
Pentru evaluarea complexităţii componentelor, metricile din capitolul
Ciclul de dezvoltare software nu sunt relevante pentru că filozofia
componentelor se referă la utilizarea acestora sub formă binară, în care nu

147
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

are importanţă caracteristicile codului sursă ci capacitatea componentei de a


oferi funcţionalitatea aşteptată. Tot din acelaşi motiv, al lipsei accesului la
codul sursă, nici metricile specifice abordării orientate obiect, unele
enunţate în capitolul Programarea orientată obiect nu sunt aplicabile. De
asemenea, deoarece, componentele se bazează pe diferite tehnologii,
dezvoltarea lor presupune nu numai doar scrierea funcţionalităţii dar şi a
codului sursă ajutător, specific fiecărei tehnologii în parte. Prin mecanismele
de tip wizard, mare parte din codul sursă ajutător este generat deja,
programatorul trebuind doar să adauge metodele specifice ce implementează
funcţionalitatea componentei.
Datorită caracterului binar al componentelor, trebuie luate în
considerare metrici care au legătură cu utilizarea componentei în aplicaţii şi
practic surprind complexitatea componentei din punct de vedere al utilizării
ei şi nu din punct de vedere al construcţiei; astfel de metrici sunt:
¾ numărul de metode dintr-o interfaţă (NMI), numărul de
parametrii ai metodelor dintr-o interfaţă (NPM), împărţit în
numărul parametrilor de intrare (NPI), numărul parametrilor de
ieşire (NPE) şi numărul parametrilor de intrare/ieşire (NPIE);
acestea sunt mărimi ale complexităţii interfeţei componentelor;
¾ numărul de interfeţe expuse de componentă (NI), numărul de
metode de iniţializare a acesteia (NIM); acestea sunt mărimi
ale disponibilităţii componentei.
Pentru exemplul considerat, valorile metricilor enumerate anterior
sunt:
Metrică Interfaţa1 Interfata2
NMI = 4 6
NPM = 9 8
NPI = 5 6
NPE = 4 2
NPIE = 0 0

Referitor la metricile din categoria a doua, componenta construită


expune două interfeţe şi are o singură metodă de iniţializare.
Se observă că o metodă poate face parte din mai multe interfeţe; de
aceea, o agregare a metricilor anterioare la nivelul componentei nu are
relevanţă, datorită riscului multiplicării valorilor pentru metodele prezente
în mai multe interfeţe.

148
Utilizarea de componente

8.3 Biblioteci de componente

Pentru a fi distribuite şi utilizate, componentele sunt grupate în


biblioteci de componente. Bibliotecile de componente sunt asemeni
bibliotecilor de funcţii sau de clase. Deşi, în ambele cazuri, reutilizarea
codului se face sub formă binară, prin specificul construcţiei lor, bibliotecile
de componente, permit utilizarea unor funcţionalităţi scrise într-un limbaj de
programare sau folosind o anumită tehnologie, în cadrul unor aplicaţii scrise
folosind alte limbaje de programare sau tehnologii. În acest scop au fost
dezvoltate mecanisme specifice prin care se realizează integrarea de
componente în aplicaţii.
O bibliotecă de componente trebuie să îndeplinească o serie de criterii,
ca să poate fi utilizabilă în mod corespunzător în aplicaţii:
• să fie generală: componentele din cadrul librăriei să ofere
funcţionalităţi comune, generale;
• să fie consistentă: toate componentele din cadrul bibliotecii să
se refere la un anumit subiect sau domeniu; de exemplu, o
bibliotecă de componente pentru lucrul cu structuri de date
conţine o componentă pentru lucrul cu masive, o componentă
pentru lucrul cu liste simple şi dublu înlănţuite, o componentă
pentru lucrul cu arbori;
• să fie documentată: bibliotecile sunt structuri binare
reutilizabile. Dezvoltatorul nu are acces la codul sursă pentru a
stabili care este interfaţa componentei pe care doreşte să o
folosească şi nici nu poate ştii exact ce anume este
implementat pentru o anumită funcţionalitate; de aceea orice
bibliotecă trebuie însoţită de o documentaţie detaliată, care să
ofere toate aceste informaţii astfel încât utilizarea ei să fie
foarte facilă.
Proiectarea şi dezvoltarea bibliotecilor de componente se face într-o
manieră asemănătoare cu proiectarea şi dezvoltarea bibliotecilor de funcţii şi
clase pentru un limbaj de programare.
Construirea bibliotecilor de componente se realizează în mai multe
moduri:
• arbitrar, atunci când componente potenţial reutilizabile sunt
grupate în librării, iar reutilizarea lor depinde foarte mult de
cunoştinţele despre ele ale programatorilor;

149
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

• bazat pe cerere, prin care dezvoltatorii de produse software


sunt încurajaţi să solicite pieţei componente pentru diferite
funcţionalităţi; de exemplu, se solicită o componentă pentru
lucru cu masive bidimensionale. Dacă există suficient de multe
cereri pentru o anumită componentă, atunci acea componentă
este dezvoltată, la cerere şi înglobată într-o librărie;
• bazat pe dezvoltarea de către companie a unui set de
componente, proprietare, pe care dezvoltatorii să le folosească
în cadrul aplicaţiilor. Această abordare este de regulă la
îndemâna firmelor puternice, care îşi pot permite să
investească timp şi bani în dezvoltarea de librării de
componente. Pe de altă parte, pentru produse specifice şi foarte
pretenţioase, este necesar chiar ca firma să aibă propriile
componente pentru funcţionalităţile critice, chiar dacă acestea
ar putea fi achiziţionate din altă parte, tocmai pentru a avea un
control mai bun asupra produsului.

8.4 Asigurarea interoperabilităţii prin utilizarea


de componente

Obiectivul principal al dezvoltării de componente este de a permite


reutilizarea unei anumite funcţionalităţi implementată într-un limbaj de
programare, în cadrul unei aplicaţii dezvoltată în alt limbaj de programare.
Decizia de a implementa şi folosi componente trebuie să se bazeze pe
următoarele argumente:
• costul dezvoltării unei componente, raportat la numărul
previzibil de utilizări ale acelei componente; dacă o
componentă este dezvoltată spre a fi folosită doar în cadrul
unei aplicaţii, atunci efortul necesar acestui proces este prea
mare, pentru că tehnologiile folosite pentru construirea de
componente, în general sunt complexe, nu sunt facil de
manevrat şi pentru aceasta sunt necesari oameni bine pregătiţi.
Dacă utilizarea ulterioară este foarte scăzută, se optează pentru
o implementare clasică, orientată obiect a funcţionalităţii, cu
riscul duplicării implementării pentru fiecare din utilizările
ulterioare.
• există situaţia în care o anumită funcţionalitate este optim
implementată folosind un anumit limbaj de programare; de

150
Utilizarea de componente

exemplu, lucrul cu fişiere sau cu periferice este mult mai rapid


pentru programele dezvoltate în C/C++ decât Java sau C#. De
aceea, se optează pentru dezvoltarea de componente în C++
care să lucreze cu periferice, la un nivel mai apropiat de
hardware, urmând ca restul funcţionalităţii să fie dezvoltată
într-un limbaj ca Java sau C#, din care se vor face apeluri către
metodele componentei respective. Bineînţeles, această
abordare este viabilă doar în situaţia în care câştigul de
performanţă contează foarte mult în economia execuţiei
aplicaţiei; dacă nu este atât de important acest câştig de
performanţă, din nou, soluţia clasică, orientată obiect este mult
mai puţin costisitoare.
Interoperabilitatea aplicaţiilor reprezintă capacitatea acestora de a
comunica, de a partaja funcţionalitate şi informaţii, chiar dacă aplicaţiile
rulează în medii diferite, au fost concepute şi realizate folosind limbaje de
programare sau tehnologii diferite. Soluţiile de interoperabilitate sunt
numeroase, dar de cele mai multe ori sunt specifice unui anumit grup de
aplicaţii.
Soluţiile de tip componente au la bază un standard de implementare şi
utilizare, care asigură o uniformitate în abordarea interoperabilităţii.
Componentele sunt dezvoltate pe baza unor schelete bine stabilite,
interfeţele sunt descrise prin instrumente standardizate, utilizarea
componentelor în aplicaţii se face într-o manieră clară şi bine definită.
Standardizarea mecanismelor de comunicare dintre componente are mai
multe avantaje:
• din punct de vedere al dezvoltatorului, este suficient să
cunoască un singur mecanism pentru a înţelege diversele
implementări şi utilizări ale acestuia;
• din punct de vedere al producătorului de software, permite un
mai bun management al aplicaţiilor pe care le produce şi le
distribuie şi, implicit, costuri mult mai mici;
• din punct de vedere al calităţii aplicaţiilor, pentru că, deşi
poate o soluţie particulară, specifică unui anumit context, este
mai performantă, costul pierderii de performanţă este mult mai
mic decât costul mentenanţei ulterioare a unei soluţii specifice,
proprietare.
În ultima vreme, a apărut un nou tip de arhitectură de aplicaţii, care
vizează reutilizare maximă de funcţionalitate şi permite interoperabilitate

151
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

facilă, şi anume arhitecturile orientate pe servicii. Serviciul este, de fapt, tot


o componentă, însă, cu mecanismul de comunicare complet standardizat şi
bazat pe un schelet mult mai uşor de folosit. Problema majoră a
componentelor tradiţionale, de tip COM/DCOM, din punct de vedere al
dezvoltării, o reprezenta complexitatea foarte mare a tehnologiei, lucru care
era descurajant de multe ori pentru producătorii de software, puşi în situaţia
de a decide utilizarea lor. Prin intermediul serviciilor, acest impediment este
eliminat; dezvoltarea este mult mai uşoară, comunicarea cu un astfel de
serviciu de asemenea. Pentru servicii, se foloseşte SOAP ca protocol de
invocare a metodelor, folosit peste protocolul HTTP. SOAP se bazează pe
XML pentru structurarea informaţiei transferate. În acest fel, nu mai sunt
implicate mecanisme de comunicare specifice unui anumit sistem de
operare, cum era cazul componentelor tradiţionale.
Din punct de vedere al unui dezvoltator, serviciile WEB sunt o
extensie a tehnologiei distribuite. Practic, aplicaţiile sunt rezultatul
combinării prelucrărilor interne şi prelucrărilor asigurate se serviciile WEB
disponibile. Apare conceptul de „software ca serviciu” (software as
service). Asemeni componentelor, serviciile WEB sunt încapsulate complet;
dezvoltatorul care le foloseşte nu trebuie să cunoască detalii de
implementare; funcţionalitatea expusă de serviciu este descrisă în interfaţa
sa, prin intermediului unui limbaj specific, WSDL – Web Services
Definition Language.
Conceptul de software ca serviciu contribuie foarte mult la depăşirea
graniţelor de integrare, inclusiv în cadrul organizaţiei. Mulţi dezvoltatori şi
arhitecţi de sistem văd serviciile WEB ca o opţiune excelentă pentru
integrarea aplicaţiilor şi mult mai uşor de implementat decât soluţiile clasice
bazate pe componente COM/DCOM de exemplu, sau cele orientate pe
mesaje (comunicarea dintre aplicaţii se face prin intermediul unui strat
intermediar de mesaje). Acest lucru devine foarte important când se pune
problema integrării în extranet şi permite interoperabilitatea dintre diversele
locaţii ale companiei sau dintre parteneri, clienţi şi furnizori. Dacă se
expune funcţionalitate prin servicii WEB, bazate pe SOAP/XML şi descrise
într-un fişier WSDL, oricine o poate accesa fără să fie nevoie de aplicaţii
specifice care să se potrivească platformei de tehnologie folosită pentru
dezvoltarea funcţionalităţii.

152
Prezentarea tehnicilor de programare are menirea de a evidenţia
evoluţia în timp a unui domeniu deosebit de important pentru tehnologia
informaţiei. Trecerea de la o tehnică de programare la alta este rezultatul
natural al acumulării de experienţă şi rezultatul evoluţiei structurilor
hardware marcată de creşterea vitezei de calcul, creşterea capacităţii de
memorare şi creşterea numărului şi tipurilor de echipamente periferice.
Toate acumulările evidenţiază tendinţa spre performanţă, spre
creşterea productivităţii muncii şi creşterea calităţii produselor software.
Indiferent de tehnica de programare întrebuinţată în dezvoltarea de aplicaţii
informatice, un rol deosebit revine persoanelor – analişti şi programatori,
care definesc probleme, proiectează soluţii şi dezvoltă cod sursă. În mod
natural, dezvoltarea de aplicaţii informatice trebuie să urmeze cerinţele
tehnologiei pe obiecte sau cele ale tehnologiei pe componente. În cazul în
care apare necesitatea reutilizării de software, integrarea de componente
elaborate folosind tehnici de programare mai vechi, devine eficientă dacă şi
numai dacă aceste tehnici sunt cunoscute şi se operează pe textele sursă
existente.
Cunoaşterea tehnicilor de programare permite identificarea unor părţi
stabile care se regăsesc în toate structurile de programe şi stabilirea acelor
părţi care diferă radical de la o tehnică la alta. Separarea acestora permite
îndreptarea eforturilor spre a executa trecerea a cât mai multor componente
dintr-o categorie în alta în vederea obţinerii unui echilibru cât mai stabil.
Există situaţii când sisteme informatice din generaţii mai vechi, aflate
în exploatare curentă, trebuie reproiectate pentru a prelua facilităţile oferite
de noile tehnologii. Existenţa de programe translatoare care automatizează
procesul impune definirea unor parametri. Corectitudinea procesului de
translatare depinde de nivelul de cunoaştere a inputurilor, adică de nivelul
de cunoaştere a tehnicii sub care a fost dezvoltat sistemul informatic mai
vechi.
Este important să se cunoască tehnicile de programare existente pentru
a crea premisele evoluţiei spre alte noi tehnici, atunci când se identifică noi

153
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

cerinţe, noi exigenţe şi se stabilesc limitele prin care se caracterizează atât


tehnicile de programare vechi, cât şi cele de programare noi.
Analiza complexităţii software realizată pentru implementările soluţiei
problemei PROB folosind diferite tehnici de programare, a arătat ca există o
influenţă a abordărilor pe care fiecare tehnică o promovează asupra
complexităţii softwareului rezultat. Însă, de asemenea, trebuie remarcat
faptul că, pentru anumite tehnici de programare cum este cea orientată
obiect sau cea bazată pe componente, metricile clasice, cum sunt cele
prezentate în această carte, respectiv complexitatea ciclomatică şi metricile
Halstead, nu au relevanţă.
În tabelul 9.1 sunt sintetizate rezultatele obţinute pe codurile sursă ale
implementărilor pentru diferitele tehnici de programare, ale problemei
PROB.
Complexităţile implementărilor problemei PROB

Tabelul 9.1
Indicator Complexitate Dificultate
Tehnica ciclomatică (Halstead)
de programare
Abordarea clasică 7 14,25
Programarea standard 14 3,3
Programarea structurată 12 15 15,38 20,08
Programarea modulară 15 20,08
Programarea orientată obiect 29 35,33
Programarea orientată pe componente * *

În cazul programării structurate, cele două variante corespund


situaţiilor în care programul a fost dezvoltat ca un monolit, respectiv
folosind funcţii.
Componentele sunt văzute ca şi cutii negre, astfel că importantă este
funcţionalitatea expusă şi nu implementarea. Componentele pot fi scrise
într-un limbaj de programare şi utilizate în aplicaţii dezvoltate folosind alt
limbaj de programare. Prin urmare, analiza pe codul componentelor nu are
relevanţă; în plus, în funcţie de diferitele tehnologii orientate pe
componente, există elemente suplimentare de cod care apar în dezvoltare.
Setul de metrici pentru evaluarea complexităţii componentelor nu conţine

154
Concluzii

indicatori ce se aplică asupra codului sursă, mai ales că acesta poate fi


rescris, fără a se afecta utilizarea componentei.
Se observă o creştere a complexităţii de la abordarea clasică către cea
orientată obiect, creştere datorată specificităţii diverselor tehnici. Diferenţele
de complexitate nu sunt mari, cel puţin în cazul primelor tehnici (clasică,
standard, structurată, modulară). În cazul tehnologiei orientate obiect, există
un salt semnificativ de complexitate, explicabil prin faptul că există multe
construcţii de limbaj care aduc un plus de complexitate (de exemplu,
existenţa constructorilor), dar şi prin faptul că metricile alese nu au mare
relevanţă în cazul acestei tehnici de programare. De aceea, în practica
curentă, se pune accentul pe alegerea unui alt set de metrici atunci când se
discută despre complexitatea codului sursă orientat obiect sau despre
complexitatea aplicaţiilor bazate pe componente, acest lucru fiind subliniat
în capitolele 7 şi 8 ale lucrării.
Într-o analiză de acest tip, importantă este reprezentativitatea setului
de date pe care se face analiza, respectiv codul sursă. Analiza efectuată în
cadrul lucrării oferă indicii primare cu privire la relaţia dintre complexitate
şi tehnica de programare folosită la dezvoltarea produsului, însă, pentru o
analiză completă, se impune aplicarea acestor metrici pe un lot mai mare de
probleme, pentru care să se construiască variante corespunzătoare tuturor
tehnicilor de programare prezentate.

155
Anexa 1

P - denumire problemă pentru care se dezvoltă software


Ai - element al unei colecţii de date (articol)
F - fişier care memorează o colecţie de date (articole)
LAi - lungimea în baiţi a unui element Ai
lg(F) - lungimea totală a fişierului F, în baiţi
pi - poziţia în fişier a unui element Ai
ki - cheia elementului Ai
Di - deplasarea elementului Ai faţă de începutul fişierului F
xi - element al unui şir de numere
S - suma elementelor xi ale unui şir
Ij - instrucţiunea de pe poziţia j din program
PR - program care rezolvă o problemă dată
L - lungimea în număr de instrucţiuni a unui program
na - numărul de arce din graful asociat programului PR
nn - numărul de noduri din graful asociat programului PR
CM - complexitatea software în sens McCabe
n1 - frecvenţa operanzilor din program
n2 - frecvenţa operatorilor din program
CH - complexitatea în sens Halstead
n - numărul de elemente ale unui şir de numere
fi - frecvenţa de apariţie a elementului xi în şir
x[i] - elementul de pe poziţia i din vectorul x
xmin - valoarea elementului minim din vectorul x
pozmin - poziţia din vectorul x pe care o ocupă elementul cu valoarea
minimă
ei - eticheta i din program
x[N] - masiv unidimensional cu dimensiunea maximă N
a[M][N] - masiv bidimensional cu dimensiunile maxime M linii şi N
coloane
x - media aritmetică a elementelor unui masiv unidimensional
ik - variabilă de stare
Pi - subproblemă a problemei P

156
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

SPi - subprogramul corespunzător subproblemei Pi


Pij - subproblema j a subproblemei Pi
in - numărul de instrucţiuni dintr-o secvenţă
C1 - expresie condiţională
S1 - secvenţa de instrucţiuni care se execută dacă expresia
condiţională C1 este adevărată
S2 - secvenţa de instrucţiuni care se execută daca expresia
condiţională C1 este falsă
i - variabila de control
vinit - valoarea iniţială a variabilei de control
vfin - valoarea finală a variabilei de control
r - raţia
S - secvenţa de instrucţiuni care se execută în cadrul structurii
repetitive
S` - secvenţa de instrucţiuni care se execută după încheierea
execuţiei unei structuri repetitive
C - expresia condiţională dintr-o secvenţă repetitivă
DI - datele de intrare ale problemei
DE - datele de ieşire ale unei probleme
NE - numărul datelor de ieşire furnizate de produsul software
Mij - modulul pentru obţinerea outputurilor DEi aflat pe nivelul j,
care utilizează datele de intrare DEij
SPi - subproblema i a problemei P
S-SPij - subproblema j, a subproblemei i, a problemei P

157
[ATHAN95] Irina ATHANASIU, Eugenia KALISZ, Valentin CRISTEA –
Iniţiere în TURBO PASCAL, Bucureşti, Editura Teora, 1995

[BARBU97] Gheorghe BARBU, Ion VĂDUVA, Mircea BOLOŞTEANU


– Bazele Informaticii, Bucureşti, Editura Tehnică, 1997

[BUDD97] Timothy BUDD – An Introduction to Object Oriented


Programming, Second Edition, Addison-Wesley, 1997

[CATR94] Octavian CATRINA, Iuliana COJOCARU – Turbo C++,


Bucureşti, Editura Teora, 1994

[CRIST98] Valeriu CRISTEA, Irina ATHANASIU, Eugenia KALISZ,


Valeriu IORGA – Tehnici de programare, Bucureşti, Editura
Teora, 1998

[GMICU02] Bodgan GILIC-MICU, Ion Gh. ROŞCA, Constantin


APOSTOL, Marian STOICA, Cătălina COCIANU –
Algoritmi în Programare, Bucureşti, Editura ASE, 2002

[GMICU03] Ion Gh. ROŞCA, Bogdan GHILIC-MICU, Cătălina


COCIANU, Marian STOICA, Cristian USCATU –
Programarea calculatorului. Ştiinţa învăţării unui limbaj de
programare. Teorie şi aplicaţii, Bucureşti, Editura ASE,
2003

[IVANI98] Ion SMEUREANU, Ion IVAN, Marian DÂRDALĂ –


Structuri şi obiecte în C++, Bucureşti, Editura Cison, 1998

[LOREN94] Mark LORENTZ, Jeff KIDD – Object-oriented software


metrics: a practical guide, Englewood Cliffs, NJ PTR
Prentice Hall, 1994

[MIHAL83] Rodica MIHALCA, Ion IVAN, Ioan ODĂGESCU –


Programe applicative, Bucureşti, Editura ASE, 1983

158
Analiza comparată a complexităţii entităţilor text generate prin tehnici de programare

[MIHAL98] Rodica MIHALCA, Csaba FABIAN, Adina UŢĂ, Felix


SIMION – Analiză şi proiectare orientate obiect.
Instrumente de tip CASE, Bucureşti, Editura Societatea
Autonomă de Informatică, 1998

[MSPCD97] Microsoft Press – Computer Dictionary, Third Edition,


Microsoft Press, 1997

[ROŞCA94] Ion Gh. ROŞCA, Constantin APOSTOL, Valer ROŞCA,


Bogdan GHILIC-MICU – Prelucrarea fişierelor în PASCAL,
Bucureşti, Editura Tehnică, 1994

[ROŞCA03] Ion Gh. ROŞCA, Cătălina COCIANU, Cristian USCATU –


Programarea calculatoarelor. Aplicaţii, Bucureşti, Editura
ASE, 2003

[ROŞCA103] Ion Gh. ROŞCA, Bogdan GHILIC-MICU, Constantin


APOSTOL, Valer ROŞCA, Cătălina COCIANU –
Programarea calculatoarelor. Tehnica programării în
limbajul PASCAL, Bucureşti, Editura ASE, 2003

[ROTAR96] Eugen ROTARU – Limbajul Java, Târgu-Mureş, Computer


Press Agora, 1996

[SMEU01] Ion SMEUREANU, Marian DÂRDALĂ – Programarea în


limbajul C/C++, Editura Cison, Bucureşti, 2001

[SMEU02] Ion SMEUREANU, Marian DÂRDALĂ – Programarea


orientată obiect în limbajul C++, Bucureşti, Editura Cison,
2002

[SMEUR04] Ion SMEUREANU, Marian DÂRDALĂ, Adriana REVEIU


– Visual C#. .NET, Bucureşti, Editura Cison, 2004

[SPIR95] Claudia SPIRCU, Ionuţ LOPĂTAN – POO Analiza,


proiectarea şi programarea orientate obiect, Bucureşti,
Editura Teora, 1995

[STROU87] Bjarne Stroustrup – The C++ Programming Language,


Addison Wesley, 1987

159