In prima parte a discutiei noastre cu Mihai am dezbatut relatia dintre hardware si software precum si challenge-uri si limitari ale hardware-ului curent. Astazi continuam discutia concentrandu-ne mai mult pe software, cu trimiteri la starea pietii de software din Romania (si nu numai), provocari ale acestui domeniu, arhitectura software si pasi in procesul de dezvoltare.
Techtorials: Mihai, daca ar trebui sa exprimi starea dezvoltarii de software in acest moment in romania sau la nivel mondial in cateva cuvinte, ce ne-ai spune ?
Mihai: In Romania se face foarte mult outsourcing. Asta este pe de o parte un lucru pozitiv intrucat ne conecteaza la cele mai noi tehnologii la nivel mondial. Pe de alta parte in Romania se face prea putin pe partea de conceptie si cercetare software. Sunt doar cateva exemple (merituoase) de produse software de conceptie romaneasca care au primit recunoasterea internationala. Cred ca exista potential pentru mult mai mult in Romania.
Relativ la industria de soft la nivel mondial, imi place sa cred ca inca este abia la “pubertate”. A existat o crestere fara precedent, in ton cu cresterea puterii de procesare hardware. Totusi tehnologia software este inca ne-cristalizata. Exista deja foarte multe tehnologii, framework-uri, dar nu exista inca o tehnologie a alegerii tehnologiei potrivite unei anumite nevoi. Software-ul are inca multe bug-uri dupa ce ajunge la client, iar eficienta producerii software-ului este inca destul de mica. Inca re-inventam de multe ori roata si de multe ori nici nu iese atat de rotunda pe cate ar trebui. Pe de alta parte, exista o alta problema care nu tine direct de scrierea codului-program, ci de insasi formularea problemei (specificatiile). Specificatiile nu reusesc decat in mica masura sa exprime asteptarile clientului, ceea ce duce la greseli de implementare.
Cu toate aceaste probleme, software-ul ne face astazi viata mai usoara, ajutandu-ne sa comunicam, sa stocam, sa organizam si sa regasim informatii, sa rezolvam probleme pe care mintii umane i-ar lua ani sau vieti sa le rezolve. Software-ul este prezent deja in cele mai neasteptate locuri, controleaza combustia in motorul autovehiculelor sau ajuta pilotii de avioane sa ia cele mai bune decizii. Viitorul va aduce software-ul si mai aproape de om, pana la a deveni o extensie naturala a gandirii si vointei sale.
Techtorials: Care sunt la ora actuala cele mai mari dificultati de care te lovesti in procesul de software development, in orice stadiu al sau ? Dar cele mai mari provocari ?
Mihai: O sa abordez problematica cu care confrunta dezvoltarea software in fiecare etapa a dezvoltarii sale, precum si cateva directii de dezvoltare pe care le intrevad.
Specificatiile
Scrierea unor specificatii detaliate este la limita la fel de complexa precum scrierea codului-program corespunzator. Specificatiile utilizeaza insa de obicei limbajul natural, care este de multe ori ambiguu, necesitand ca programatorul se “ghiceasca” ceea ce se doreste pe baza bunului simt. Nu intotdeauna ghiceste corect…
Scrierea unor specificatii clare este chiar mai complicata decat scrierea codului-program care implementeaza acele specificatii. La limita se poate argumenta ca “Teorema de Incompletitudine” a lui Gödel demonstreaza ca nu se pot crea specificatii absolut ne-contradictorii (intotdeauna se poate formula o intrebare la care specificatiile sa nu poata raspunde). Totusi, suntem departe de aceste limite teoretice, specificatiile au lipsuri de multe ori din lipsa de timp ori rigurozitate.
Ma astept ca limbajele de programare sa se apropie din ce in ce mai mult de limbajul natural, astfel incat sa se poata descrie specificatiile intr-un limbaj accesibil, dar direct traductibil in cod-program executabil. Va fi in continuarea nevoie de un efort de transpunere a nevoii intuitive intr-o descriere riguroasa a ceea ce se doreste, dar se va elimina sper aceasta duplicare a efortului de descriere, care aduce cu ea si multe din problemele de interpretare.
Arhitectura software
Arhitectura software trebuie sa gaseasca un echilibru intre doua deziderate oarecum contrare: sa rezolve problema (specificatiile) cu cel mai mic cost posibil, dar in acelasi timp sa rezolve problema intr-un mod mai general, care ar putea scade costul de rezolvare a unor probleme viitoare (necunoscute acum), crescand insa costul de implementare al problemei curente.Este ca in problema “schiorului incepator”, care trebuie sa decida intre a inchiria schiuri sau a cumpara o pereche, fara sa stie exact de cate ori va folosi schiuri pe viitor. Daca va abandona hobby-ul in curand, este mai ieftin sa inchirieze. Daca va folosi de multe ori schiuri, este mai convenabil sa investeasca in schiuri personale. La fel ca in problema schiorului, nu exista alegere corecta 100% a arhitecturii softului, dar exista strategii de minimizare a pierderii procentuale. Schiorul ar putea sa inceapa sa inchirieze la inceput schiurile, iar daca ajunge sa fi cheltuit pretul schiurilor sa investeasca intr-o pereche proprie. Cu aceasta strategie are garantia ca nu va cheltui mai mult de dublul valorii optime (pe care nu o poate sti apriori).
Arhitectura software trebuie deci sa fie flexibila. Arhitectura initiala merita sa porneasca de la rezolvarea problemei punctuale, cu un grad mic de generalizare. Pe masura ce se identifica probleme similare care ar fi putut fi rezolvate mai eficient printr-o generalizare, se poate evolua arhitectura, generand un “refactoring” al codului. Arhitectul cu experienta poate intui posibilele zone in care va apare nevoia de generalizare, si poate proiecta de la inceput “caramida de legatura”, fara a creste excesiv costul in cazul in care generalizarea nu se dovedeste necesara.
Arhitecturile software de baza au inceput sa se cristalizeze in asa-numitele “design-patterns”, solutii arhitecturale la probleme uzuale care apar in constructia software. Sunt totusi “caramizi” destul de mici, necesita inca multa ingeniozitate si efort de combinare pentru a rezolva probleme reale. Exista si “prefabricate” mai mari, diverse “framework-uri”, dar se dovedesc deseori eficiente pentru o gama ingusta de probleme, in ciuda dorintei lor de generalitate. Astept in viitor sa apara “prefabricate” mai flexibile, care sa elibereze arhitectura software de probleme de detaliu, pastrand insa flexibilitatea de a modela usor cazuri particulare de probleme.
Scrierea codului-program:
In ciuda Programarii Orientate pe Obiecte (OOP), reutilizarea codului are inca o rata de economisire cost destul de mica. Pe de o parte scrierea unor module foarte generale este dificila, deoarece permanent apar noi situatii particulare neprevazute. Pe de alta parte, insusi managementul acestor parti generale este foarte costisitor. Daca 2 module de program A si B folosesc un al treilea (C), iar A are nevoie de o modificare a lui C, asta poate schimba comportarea lui C pentru B. Alegerea este dificila, pe termen scurt poate fi mai putin riscant crearea unui C2 pentru A. Pe termen lung orice corectie a lui C ar trebui facuta si in C2, crescand costul de mentenanta.
Limbajele de programare au evoluat in scopul unei mai mari puteri de descriere a algoritmilor, incercand in acelasi timp sa detecteze o parte din erorile frecvente de scriere a programelor. Limbajul poate detecta o greseala sintactica in program, poate detectea o eroare semantica de folosire a unor structuri de date, dar nu poate bineinteles detecta o eroare de logica in descrierea algoritmului de solutionare a problemei.
Programarea plateste inca mult tribut greselilor de traducere a unor algoritmi corecti in cod-program. Limbajele care utilizeaza mai multe “corzi de siguranta” (precum Java in comparatie cu C++) sunt percepute uneori a limita usurinta de rezolvare a anumitor probleme, si de a avea costuri de performanta. Pe masura ce hardware-ul avanseaza insa, estimez ca preocuparea pentru “corzi de siguranta” va prevala din ce in ce mai mult fata de un cost modic de preformanta.
Integrarea modulelor program
Aplicatiile profesionale nu pot fi in general realizate in timp util de o singura persoana. De multe ori se combina bucati de software realizate de catre zeci sau chiar sute de programatori, eventual de la firme diferite. Este ceva mai complicat decat in cazul in care totul este sub decizia unei singure persoane.
Bucatile program sunt agregate in programul final prin procesul de link-are (“editare de legaturi”). Acest proces este o sursa potentiala a multor probleme. Daca discutam de linkarea clasica a bibliotecilor (.dll, .so, .a), folosita pentru asamblarea modulelor scrie in C/C++, o preconditie este ca toate modulele sa cunoasca aceeasi modalitate de comunicare (interfata). In C/C++ interfata este descrisa in headere (.h, .hpp). Daca o biblioteca isi schimba interfata de comunicare (fisierul header), programul care o foloseasca trebuie re-compilat pentru a invata sa foloseasca aceasta interfata.
Sa luam ca exemplu o biblioteca care obisnuia sa primeasca ca argument lungimea fisierului pe 32biti (avand un maxim de 2^32, aproximativ 4GB). Evolutia hardware a facut sa devina uzuale fisiere de ma mult de 5GB, deci o versiune noua a bibliotecii va astepta sa primeasca lungimea fisierului ca 64 biti (asta inseamna mai mult de 4.000.000.000 * 4GB). Daca un program mai vechi va trimite doar 32 biti, biblioteca noua va incerca oricum sa citeasca 64 biti, citind inca 32 biti care nu au nici o legatura cu ce a trimis programul. Rezultatul nu este predictibil, in cel mai “bun” caz programul “crapa” cu o eroare urata. In cazul mai “rau”, programul foloseste niste date incorecte, producand coruperea informatiilor. Acesta este doar un exemplu care ilustreaza cat de usor se pot produce incurcaturi intre sutele sau miile de module care colaboreaza la realizarea unei aplicatii. Parca nu mai pare atat de neasteptat faptul ca unele programe “crapa” din cand in cand.
La aceasta problema au incercat sa raspunda limbajele care folosesc o masina virtuala (Java, C# si altele), unde link-area se face la un nivel mai inalt, verificandu-se intotdeauna corespundenta tipurilor. Pentru aplicatii financiare de exemplu coruperea unui numar reprezentand soldul contului unui client inseamna mai mult decat simpla neplacere de a restarta aplicatia. In plus, limbajele care folosesc masina virtuala sunt cel putin teoretic imune la coruperi intre diverse module de program. In afara cazului in care s-ar descoperi o problema in insasi masina virtuala (putin probabil), poti fi sigur ca modulul cu probleme a functionat doar pe baza datelor sale de intrare, fara a fi afectat de suprascrierea accidentala a datelor de catre alt modul. Asta usureaza mult depanarea.
Testarea
Realitatea este ca nu se pot scrie programe fara greseli. Fie apare o greseala de intelegere a cerintei, fie se pierd din vedere situatii particulare speciale, fie se greseste pur si simplu la “tastare”. Inainte de a ajunge la client, programele profesionale trebuie sa treaca printr-o faza de testare. Testarea trebuie inceputa de la momentul codarii modulelor, de catre programator. Fiecare traseu logic din program trebuie testat pentru a fi sigur ca implementeaza exact ce a sperat programatorul. Aceasta se numeste testare “unitara”. Este foarte recomandata testarea automata a unitatilor de program, care are avantajul de a usura reverificarea atunci cand se modifica modulul.
Testarea de integrare detecteaza deseori probleme de comunicare intre module, care sunt scrise deseori de persoane diferite. Se pot descoperi chiar module lipsa, sau functionalitati care nu au fost implementate. Testarea de integrare este vitala pentru a asigura o calitate minima a programului livrat.
Pentru aplicatii profesionale se apeleaza uneori si la o validare externa a aplicatiei, de catre o entitate care nu este interna companiei. Aceasta validare poate da mai multa credibilitate produsului in ochii clientului.
Inainte de livrarea produsului software customizat catre un client, se executa teste de acceptanta impreuna cu clientul, pentru ca acesta sa fie sigur ca i s-a livrat produsul care ii satisface nevoile. Abia dupa ce clientul a fost multumit de testele de acceptanta se executa plata catre furnizor.
Nota: Pentru eficienta testarii si a depanarii cand este cazul, este necesar ca logurile/trace-urile pe care le genereaza fiecare modul sa fie suficient de descriptive si sa prezinte corect gravitatea situatiei intalnite. Daca modulul detecteaza ca o anumita situatie nu este normala (primeste date inconsistente), trebuie sa genereze o alarma pentru a semnala situatia. Daca problema indica o inconsitenta grava, poate fi indicata oprirea imediata a programului pentru a nu propaga eroarea in alte module, facand depanarea mai dificila sau putand chiar distruge informatii. De asemenea, prea multe alarme false duc la o anumita relaxare in luarea lor in consideratie, facand sa fie trecute cu vederea alarme importante, deci este nevoie de un control riguros al situatiilor in care se da “alarma”. De obicei se foloseste un sistem centralizat de trace-uri/log-uri/alarme. Se folosesc cel putin 5 nivele de trace, de la DEBUG in care se pot urmari detaliat pasii de executie normala a programului, pana la CRITICAL in care apar situatii exceptionale care cer luarea de masuri din partea utilizatorului sau a producatorului. Nivelul de trace trebuie sa poata fi schimbat dupa nevoi, deoarece un numar mare de trace-uri poate afecta performanta softului.
MentenantaO aplicatie software critica, care controleaza activitatea unei firme (de exemplu un sistem de gestionare a clientilor) are nevoie de asigurarea unui suport post-livrare. Firmele platesc de obicei o anumita taxa anuala pentru a fi sigure ca la aparitia unei probleme vor avea o rezolvare rapida la problema care le blocheaza activitatea. Problemele aparute in exploatare sunt numeroare, schimbari in tipicul datelor sau chiar acumularea de date pot provoca disfunctionalitati in software. Odata aparuta problema, se impune reproducerea ei in laborator si crearea unui “patch” care sa rezolve problema.
<va urma>