Calea Eiffel si coerenta în abordarea obiectuală

Criza ingineriei softului este încă, din păcate, o realitate. Eiffel este una din încercările de solutionare a acestei probleme.

Iulian Ober

Acest articol, ferindu-se pe cât posibil de publicitate gratuită, încearcă să risipească din ceata ce pluteste încă (pe teritoriul României) asupra acestui limbaj de programare - de referintă!

Limbajul Eiffel a fost creat de către Bertrand Meyer si este expresia unor principii enuntate de către autor cu privire la proiectarea si evolutia limbajelor de programare. Limbajul se revendică mostenitor al mai bătrînului Simula, dar de-a lungul timpului a suferit influente si de la alte limbaje orientate obiect, printre care merită mentionat Smalltalk. În prezent limbajul a ajuns la versiunea a treia iar, recent, Interactive Software Engineering, firma cea mai activă în sustinerea limbajului cu unelte de lucru, a lansat versiunea 3.3 a unui mediu de lucru pentru platforme UNIX si Windows. Desi prin conceptia particulară a limbajului si prin politica de promovare pe care o duce firma ISE, Eiffel constituie unul din punctele fierbinti ale lumii programatorilor sursele de informare în acest domeniu sunt destul de greu accesibile, cel putin în această parte a lumii. De vină este probabil si răspândirea mai redusă a limbajului, datorată în parte si absentei unor medii decente (ca performante si pret), cel putin până de curând, pe cele mai populare platforme (DOS, Windows).

Principii de proiectare

Complexitatea unora dintre limbajele de programare existente a împins pe unii proiectanti si utilizatori de limbaje spre o atitudine adversă: apologia simplitătii. Am auzit cu totii păreri conform cărora un limbaj care nu poate fi definit în mai putin de zece pagini nu prezintă nici un interes. Adevărul pare a fi undeva la mijloc: s-au văzut limbaje care au avut initial o conceptie unitară si la care, adăugarea ulterioară a unor extensii le-a facut greu de utilizat(după mine este cazul C/C++). În aceeasi ordine de idei, limbaje proiectate din start mult mai complex decât era acceptabil de către comunitatea programatorilor (ex. PL/I), precum si limbaje la care grija de a nu le complica a fost prea mare, le-a facut neinteresante din punct de vedere industrial (ex. Pascal).

Simplitatea este însă o notiune dificil de definit obiectiv. În practica proiectării limbajelor se pot utiliza criterii ceva mai palpabile iar satisfacerea unora dintre acestea a fost urmărită de către Meyer în cazul Eiffel-ului:

Unicitatea: proiectarea unui limbaj trebuie să asigure un mod de a exprima orice operatie de interes; oferirea a două moduri poate distrage atentia programatorului de la problema pe care o are de rezolvat către aspecte secundare - de formă. Un exemplu este iteratia: majoritatea limbajelor imperative oferă mai multe moduri de a efectua o iteratie (cu preconditie, cu postconditie, cu test direct sau invers, cu trecere automată la urmatorul element, s.a.). Eiffel oferă o singură structură de control pentru iteratii (do-until-loop). În plus, deoarece principala utilizare a buclelor este parcurgerea unor structuri de date, Meyer recomandă scrierea unei rutine în care să se surprindă caracteristicile esentiale ale unui astfel de proces de calcul (existenta unei faze de initializare, a unui test de sfârsit, a unei actiuni ce se repetă,etc.), într-o clasă reutilizabilă, care ulterior să poată fi adaptată pentru cazuri concrete.

Din experienta personală însă trebuie să spun că unele aspecte ce tin de sintaxa limbajului sunt destul de supărătoare; astfel poate fi mentionată inconsecventa în utilizarea separatorilor între componentele unor structuri sintactice.

Consistenta: este după Meyer existenta unui scop. Proiectantul unui limbaj trebuie să aibă în vedere un numar mic de idei puternice si să nu se concentreze asupra aspectelor care nu se încadrează în conceptia generală. Eiffel poate fi definit în mai putin de douăzeci de concepte cheie printre care autorul mentionează în [1]:

Obiectivele Eiffel

Scopul cu care a fost proiectat limbajul Eiffel a fost de a oferi un instrument puternic celor dornici să pună în practică o serie de idei ale autorului cu privire la productia de soft de calitate. Factorii de calitate ai soft-ului sunt împărtiti în două categorii: interni si externi; calitatea internă a softului (lizibilitate, modularitate, s.a.) este fateta percepută doar de către informaticieni, însă este un mijloc de a ajunge la calitatea externă. Dintre factorii de calitate externă ai soft-ului, cei avuti în vedere de către autorul limbajului sunt: validitatea, robustetea, extensibilitatea, reutilizabiliatea, compatibilitatea, eficienta, portabilitatea, verificabilitatea, usurinta în utilizare. Toti acesti factori se găsesc discutati mai amplu, împreună cu cerintele pe care le impun asupra limbajului în [2]. Totusi cred că este util să amintesc, măcar în treacăt, câteva definitii:

Mijloacele Eiffel: obiecte, contracte

Pentru a asigura reutilizabilitatea, extensibilitatea si siguranta în utilizare a sistemelor soft, cea mai bună optiune la ora actuală pare a fi proiectarea orientată obiect. Autorul dă în [2] o definitie proiectării orientate obiect pe care ulterior o rafinează. Într-o primă fază aceasta este definită ca metoda care conduce la arhitecturi soft bazate pe obiectele pe care le manipulează sistemul. Ulterior aceasta devine: constructia sistemelor soft ca si colectii structurate de clase privite ca fiind implementări ale unor tipuri abstracte de date. Poate o exprimare mai fericită ar fi combinarea celor două definitii ale lui Meyer: constructia sistemelor soft ca si colectii structurate de obiecte, privite ca entităti apartinând unei clase. Obiectul poate fi definit ca o entitate a sistemului cu identitate, structură si comportament proprii, structura si comportamentul unui obiect fiind specificate si implementate în clasa din care face parte. Accentul în proiectare se pune deci pe structurarea sistemului în jurul obiectelor si tipurilor lor si nu în jurul actiunilor efectuate de sistem asupra diverselor sale entităti. Structurarea se referă la existenta unor relatii între obiecte, sau respectiv între clase. Relatiile dintre obiecte sînt o reflectare a relatiilor dintre clasele lor.

Am vorbit până acum despre proiectare si mai putin despre implementare. Prin constructiile sale limbajul de implementare trebuie să sustină conceptele folosite în proiectare. Utilizarea unor metode diferite de exprimare în cadrul proiectării si implementării poate duce la pierderea din vedere a unor aspecte la trecerea de la o fază la alta. De aceea, Eiffel oferă posibilitatea de a fi utilizat atât în cadrul proiectării cât si al implementării fiind, după cunostinta mea, primul limbaj care oferă această facilitate.

Clasa este unitatea de bază de structurare a "programelor" Eiffel. Termenul de program este impropriu în Eiffel ca de altfel în majoritatea limbajelor orientate obiect (exceptia este dată poate de unele limbaje cu extensii orientate obiect). Un proiect Eiffel este constituit din definitiile mai multor clase. Executia sistemului constă în crearea unei instante a unei clase speciale, numită clasa rădăcină (root), si executarea unei anumite metode a clasei.

Clasa, ca si implementare a unui tip abstract de date, descrie un set de obiecte existente la faza de rulare, caracterizate prin mai multe trăsături (features) specifice. Aceste caracteristici se pot clasifica astfel:

Rutinele pot fi de două tipuri: proceduri, reprezentând comenzi adresate obiectelor si functii reprezentând calcule aplicabile obiectelor si care întorc valori. Deoarece în decursul evolutiei unui sistem decizia ca o anumită caracteristică să fie calculată sau retinută poate să se schimbe; nu există o diferentă la nivel sintactic între atribute (stocate) si functii. Totusi mostenirea impune o restrictie asupra acestei decizii si anume: dacă o caracteristică a fost stocată într-o clasă, va fi stocată în toate descendentele clasei.

Conceptia lui Meyer despre programarea orientată obiect are ca si punct central vederea claselor ca si implementări ale unor tipuri abstracte de date. Însă un tip abstract de date nu este doar o listă de simboluri de operatii ci si proprietătile lor semantice, exprimate de preconditii si axiome. Autorul introduce notiunea de contract, privind relatia dintre o clasă si clientii săi ca un acord formal care exprimă drepturile si obligatiile fiecărei părti. Acest acord este materializat la nivelul limbajului prin preconditii si postconditii la metode si functii si respectiv, invarianti pentru clase. El prevede conditiile pe care un client trebuie să le satisfacă pentru a putea emite o cerere către un obiect server, conditiile în care serverul are posibilitatea să respundă (preconditie), ce efect trebuie să aibă actiunea răspuns (postconditie) si ce conditii asigură consistenta unui obiect (invariantul obiectului). Utilizarea asertiunilor este o metodă de a asigura conformitatea unui produs soft cu specificatiile sale. Încălcarea unui contract duce la activarea unui mecanism de exceptii dând posibilitatea sistemului să reactioneze în modul considerat rezonabil. Monitorizarea asertiunilor în timpul rulării poate asigura o metodă foarte eficientă de testare si depanare.

Utilizarea unor astfel de mecanisme formale, independente de implementare, combinată cu claritatea si expresivitatea limbajului au făcut Eiffel foarte atractiv atât pentru scopuri didactice cât si pentru scrierea de aplicatii ce se doresc a fi de calitate.

În rândurile de mai sus se observă o anumită inconsecventă în utilizarea unor notiuni cum sunt cea de comportament, structură, contract, client, server, folosite în legătură când cu clasele când cu obiectele. Probabil că nu este locul aici pentru o discutie teoretică cu privire la relatia dintre un obiect si clasa sa. Câteva clarificări practice cu privire la relatia client-server sunt însă necesare. Un obiect este client al altui obiect dacă utilizează serviciile acestuia. O clasă este client al altei clase dacă există posibilitatea ca un obiect din prima clasă să apeleze la serviciile unui obiect din a doua clasă. Comportamentul, structura si contractele unui obiect cu clientii săi sunt specificate în propria clasă.

Elemente de particularitate

Am încercat ca pe parcursul prezentării să nu mă las influentat de stilul caracteristic majoritătii materialelor pe această tema din diversele reviste de specialitate sau publicatiile electronice accesibile prin Internet, care au în general o puternică tentă de reclamă. Totusi nu pot trece mai departe fără a spune că multe din caracteristicile limbajului merită a fi trecute în această sectiune. Voi încerca în continuare să mentionez câteva dintre cele care plasează Eiffel în fata altor limbaje orientate obiect, chiar dacă spatiul nu îmi permite nici măcar o enumerare exhaustivă a lor.

Despre contracte am vorbit în sectiunea anterioară. Ele constituie probabil cel mai important concept introdus de autorul limbajului. Avantajele utilizării acestor tehnici de specificare a softului sunt evidente si au fost discutate în sectiunea anterioară. De asemenea, am amintit si de posibilitatea utilizării limbajului atât la proiectare cât si la implementare. Să detaliem putin: limbajul oferă posibilitatea definirii unor clase care nu sunt o implementare a unui tip abstract de date si care specifică o multime de implementări posibile. Altfel spus, este vorba de implementări partiale. Acestea se numesc clase amânate (deferred) si au un rol esential la faza de proiectare unde scopul este de a descrie arhitectura unui sistem, fără a intra în detalii de implementare. Proiectantul are posibilitatea de a specifica comportamentul claselor pe care le concepe, prin intermediul asertiunilor, iar aceste specificatii vor trebui respectate de către implementările respectivelor clase (care vor fi clase descendente din ele). În legătură cu proiectarea trebuie mentionat de asemenea faptul că mediile de lucru de la ISE contin printre diversele componente si un instrument CASE pentru metoda BON (Bussines Object Notation).

În ceea ce priveste mostenirea, Eiffel are cîteva particularităti, pe care le voi enumera în continuare. Limbajul permite mostenirea multiplă si repetată: o clasă poate avea mai multi părinti, si poate avea o altă clasă ca părinte de mai multe ori. Un părinte al unei clase se specifică într-o sectiune specială din declaratia clasei, în cadrul căreia se mai pot aduce o serie de modificări caracteristicilor mostenite de la respectivul părinte. Astfel, unei caracteristici i se poate schimba numele, poate fi redefinită, poate fi amânată (aceasta specifică faptul că desi în părinte caracteristica era implementată, în descendent ea va rămîne fără implementare) si i se poate schimba statutul de export. Începând cu versiunea a treia a limbajului, care este si ultima apărută, ierarhia de clase a Eiffel-ului a suferit o modificare esentială, primind o rădăcină unică (clasa ANY) si fiind completată cu o clasă descendentă din toate clasele din sistem (clasa NONE).

În rândurile de mai sus am mentionat statutul de export al unei caracteristici a unei clase. În Eiffel, spre deosebire de alte limbaje orientate obiect, are loc o discriminare între obiectele din clase diferite, în sensul că apartenenta unui obiect la o clasă este cea care îi dă dreptul să utilizeze ca si client o caracteristică a unui obiect al altei clase. Astfel, pentru fiecare caracteristică a unei clase se specifică la începutul sectiunii feature în care este introdusă, clasele obiectelor care vor putea face uz de respectiva caracteristică. Un obiect nu va putea trece peste această regulă, de apelare la caracteristicile altor obiecte, nici măcar dacă obiectele fac parte din aceeasi clasă. De asemenea, un obiect nu va putea modifica vreun atribut al altui obiect în mod direct (fără a apela la o metodă), chiar dacă cele două obiecte sunt din aceeasi clasă.

Genericitatea este un domeniu în care Eiffel este de asemenea cu un pas în fata semenilor săi. Limbajul permite parametrizarea claselor cu tipuri. Mai întâi câteva cuvinte despre sistemul de tipuri: Eiffel este în întregime bazat pe clase iar limbajul este tipizat static. Astfel, un tip în Eiffel poate fi un tip de clasă (eventual expandat, aceasta însemnând că entitătile declarate de acel tip vor fi chiar obiecte de respectivul tip si nu referinte - pointeri - la astfel de obiecte), un parametru generic formal sau un tip ancorat. Un tip de clasă este dat de o clasă, urmată eventual de parametrii generici actuali (tipuri) în cazul în care clasa este parametrizată. O entitate se declară ca fiind de un tip ancorat dacă tipul ei se dă ca fiind acelasi cu al altei entităti (prin constructia ...:like <entitate>). Această viziune asupra tipurilor conferă limbajului o uniformitate deosebită. Tipurile de bază (întreg, real, caracter, boolean) sunt bazate tot pe clase si se comportă întru totul conform cu acest statut, putând fi chiar mostenite.

Relatia de mostenire între clase generează o relatie între tipuri, numită de conformantă. Astfel, o definitie grosieră este: un tip este conform cu alt tip dacă clasa de bază a primului este descendentă a clasei de bază a celui de al doilea (în cazul existentei parametrilor formali o definitie este mai dificil de dat într-un spatiu mic).

Să revenim asupra afirmatiei cu privire la genericitatea în Eiffel. Noutatea introdusă de acesta este diferentierea genericitătii în genericitate constrânsă si ne-constrânsă. Un parametru generic formal al unei clase se spune că este constrâns la un tip dacă în declaratia clasei se specifică faptul că parametrii actuali corespunzători vor trebui să fie conformi cu un anumit tip dat. Un exemplu: implementarea unei tabele de dispersie poate fi făcută printr-o clasă parametrizată cu tipul elementelor continute. Prin intermediul parametrizării constrânse se poate impune ca tipul elementelor respective să fie conform cu un tip dotat cu o functie ce întoarce un cod întreg, necesară pentru căutări în tabelă. Avantajele oferite de mecanismul parametrizării constrânse sunt încă discutate dar existenta acestei posibilităti este fără îndoială un pas înainte.

Avînd deja câteva informatii despre sistemul de tipuri, putem spune ceva mai mult despre mostenire. Este vorba despre posibilitatea oferită de limbaj de a schimba signatura unei caracteristici mostenite de către o clasă. Signatura unei caracteristici este o pereche de secvente de tipuri, prima fiind constituită din tipurile parametrilor, a doua eventual din tipul rezultatului (dacă există). Schimbarea signaturii trebuie să respecte regula covariantei, adică tipurile parametrilor din caracteristica descendentului trebuie să fie respectiv conforme cu tipurile parametrilor din caracteristica ancestorului. Un exemplu: metoda Add(x: ADDABLE) din clasa ADDABLE, poate fi mostenită în clasa MATRIX sub forma schimbată Add(x: MATRIX). Exemplul anterior este pur didactic si nu are nici o legătură cu realitatea).

O altă caracteristică a limbajului care este mentionată de către autor în [2] ca fiind esentială pentru un limbaj orientat obiect, desi tine mai mult de mediu, este gestiunea automată a memoriei. Mediile Eiffel dispun obligatoriu de un garbage collector, obiectele care nu mai sunt utile (accesibile) fiind dealocate automat.

Limbajul dispune si de un mecanism pentru gestiunea exceptiilor. Desi mai putin complex comparativ cu cel din C++, si lucrând la nivel de metodă (functie) si nu la nivel de bloc de instructiuni, el este suficient de expresiv si ceva mai usor de utilizat. În plus, oferă posibilitatea reluării executiei metodei care a generat exceptia.

După această enumerare obositoare care probabil a semănat a reclamă exact în măsura în care nu am dorit acest lucru, este momentul pentru câteva cuvinte despre instrumentele cu care ISE a dotat limbajul. Având în vedere preturile nu tocmai mici ale produselor acestei firme autorul a dispus doar de o versiune minimală numită Personal Eiffel for Windows release 3.2, nu tocmai bine portată de sub UNIX, si care nu face prea mare cinste firmei. Totusi am avut posibilitatea să văd "pe viu" câteva biblioteci de clase cu care vine dotat mediul, si impresia a fost plăcută. Multe din informatiile de mai jos nu sunt însă din păcate la "prima mână", fiind luate în majoritate de la http://www.eiffel.com .

"Eiffel Bench" este un mediu grafic de dezvoltare de aplicatii. El dispune de facilităti de editare, depanare, generare automată de documentatii, browsing. Este disponibil pe platforme UNIX (X-Window), si în curînd pentru Windows ('95, NT). Una dintre noutătile introduse este combinarea compilării cu interpretarea (melting ice technology) pentru obtinerea rapidă a unui prototip functional al aplicatiei chiar si în faza de depanare care implică modificări frecvente. Oferă posibilitatea translatării în cod C portabil.

"Eiffel Build" este un generator vizual de aplicatii si interfete grafice. Calitatea si lizibilitatea codului generat este, cel putin după zisele ISE, foarte mare.

"Eiffel Case" este un instrument CASE folosind metoda BON (analiză, proiectare si reverse engineering).

Câteva biblioteci puse la dispozitia utilizatorului (în general vândute separat) sunt:

În loc de concluzii

Nu stiu dacă după această trecere sumară în revistă a ceea ce înseamnă Eiffel cititorul a rămas cu ceva informatii utile. Sper că a rămas cel putin cu dorinta de a încerca unele concepte si facilităti oferite de limbaj.

Asa după cum se stie nu s-a găsit încă limbajul ideal, desi multi l-au căutat. Eiffel are premizele necesare pentru a putea constitui o bună aproximare a acestuia. Astfel stând lucrurile, ne putem astepta ca în viitor să auzim tot mai mult acest nume, atât în mediile industriale cât si în cele universitare, si putem spera că în curând vom avea si la noi în tară o comunitate de programatori în Eiffel bine închegată.

Ultimă oră: Pe 3 noiembrie 1995 ISE a anuntat ersiunea 3.3. Considerată o revizie majoră a ISE Eiffel, ea a fost pusă deja la dispozitie pentru platformele DEC Alpha OSF1, IBM RS6000, HP 9000, Silicon Graphics, Sun OS/Sparc, Solaris 2.4/Sparc, SCO si Linux. În scurt timp (probabil decembrie 1995, vor apare si versiunile pentru Windows (3.x, 95, NT), DEC Alpha Open VMS si UnixWare. Un exemplu pentru un sir generic.


(C) Copyright Computer Press Agora