ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 169
Numărul 168 Numărul 167 Numărul 166 Numărul 165 Numărul 164 Numărul 163 Numărul 162 Numărul 161 Numărul 160 Numărul 159 Numărul 158 Numărul 157 Numărul 156 Numărul 155 Numărul 154 Numărul 153 Numărul 152 Numărul 151 Numărul 150 Numărul 149 Numărul 148 Numărul 147 Numărul 146 Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 169
Abonamente

De la programarea spartană la dezvoltarea asistată de IA

Vlad Petrean
Software Engineer @ RebelDot



PROGRAMARE

Imaginați-vă că sunteți în anul 2017 (nimic accidental, atunci participasem la cursul de Algoritmi Fundamentali) și trebuie să implementați algoritmul quicksort în C sau în Python. Ce limbaj ați alege? Cum ați aborda problema? Vă gândiți să copiați codul de pe Stack Overflow? (Sau, mai ușor, de la colegii din ani mai mari.) Adresând aceleași întrebări aproape 10 ani mai târziu, cunoaștem, cel mai probabil, răspunsul primit: "L-am implementat folosind Python" (cel oficial), "I-am spus unui LLM să îmi genereze un quicksort în cel mai ușor mod posibil" (cel neoficial, o abstracție a metodei Stack Overflow).

Așadar, avem două moduri de a rezolva un task în perioade diferite de timp. Mergând pe firul logic al ideii, s-ar presupune că trebuie să comparăm metodele anterioare pentru a găsi argumente pro și contra și, respectiv, să formulăm concluzia privind modalitatea de programare mai eficientă. Însă, dacă am merge și mai departe în trecut, la începutul anilor 2000 sau chiar mai devreme, cu siguranță lucrurile ar deveni mai interesante. De aceea, nu vom face o comparație între diferite moduri de programare, ci, mai degrabă, vom analiza cum stăteau lucrurile mai demult și unde am ajuns.

Programare de tip "şcoală veche"

Pentru a înțelege unde am ajuns astăzi, este necesar să ne întoarcem puțin în timp, mai exact în momentul în care noțiuni precum framework-urile, AI copilots sau chiar IDE-urile fie nu existau, fie erau într-o formă rudimentară. Înainte de această perioadă, procesul de dezvoltare software era mult mai apropiat de ceea ce am putea numi astăzi "programare pură". Dacă astăzi pornirea unui proiect presupune, în multe cazuri, alegerea unui framework alături de instalarea unor dependințe, în trecut o mare parte din aceste aspecte trebuiau dezvoltate manual. Programatorul nu avea la dispoziție zeci de pachete gata făcute pentru orice nevoie posibilă sau documentații accesibile și, eventual, generate automat.

Mai important însă decât volumul de muncă este nivelul de control. În limbaje precum C, dezvoltatorul lua în mod explicit (și pe propria răspundere) decizii privind gestionarea memoriei, structurile de date utilizate, comunicarea dintre componente etc. Cu alte cuvinte, era aproape imposibil să delegi responsabilități tehnice. Dacă aplicația avea nevoie de optimizare, trebuia înțeleasă cauza. Dacă se întâmpla un "memory leak", acesta trebuia identificat, iar dacă apărea o eroare logică, nu exista un tool care să o analizeze și să vină automat cu o soluție.

Acest mod de lucru a contribuit la formarea unei generații de programatori cu o înțelegere mai aprofundată, aș zice, asupra sistemelor dezvoltate. Concepte precum complexitatea, concurența, organizarea codului și managementul resurselor, care în zilele noastre sunt considerate fundamentale, erau mult mai des întâlnite în practică. Privind retrospectiv, este interesant de observat că principalul obiectiv al industriei software nu a fost eliminarea acestor probleme, ci, mai degrabă, ascunderea lor în spatele unor niveluri succesive de abstractizare.

Din această perspectivă, evoluția paradigmei de scriere a codului poate fi privită ca o tranziție continuă de la control total către delegare controlată.

Librării și framework-uri

Un prim pas în simplificarea efortului de a dezvolta cod a fost reprezentat de apariția librăriilor și a diverselor framework-uri. Pentru a ilustra importanța mecanismului de reutilizare a codului, sub formă de librării sau framework-uri, vă propun să vă imaginați dezvoltarea unei aplicații web de la nivelul zero, folosind doar un limbaj de programare compilat, nu interpretat. Probabil am ajunge undeva la nivelul a mii de linii de cod dacă am folosi C sau Java.

Reutilizarea codului împachetat sub forma unor librării reprezintă un pas important în simplificarea procesului de programare. Majoritatea limbajelor conțin diverse librării ce ne permit o multitudine de operații în varii domenii, precum: concurență (thread-uri, async/await), colecții (liste, hash map-uri), rețele (socket-uri, TCP/UDP), crypto, regex, error handling etc. Mergând mai departe, framework-urile, care în fond sunt o colecție organizată de pachete și librării, devin în acest mod punctul central al unui proiect, în jurul căruia se conturează arhitectura. În acest sens, mă refer, de exemplu, la framework-uri populare precum Spring sau Django, ce conțin mecanisme de securitate sau de conectare la baze de date care trebuie doar activate și configurate. Organizarea codului, respectiv a pachetelor aferente, ține cont de structura framework-ului, lucru care aduce avantaje precum mentenanța mai ușoară, standardizarea noilor features sau, de ce nu, simplificarea onboarding-ului pe proiect.

Desigur, utilizatorului îi rămâne sarcina de a citi documentația framework-urilor, proces laborios în multe situații și nu neapărat pe placul oricui. În altă ordine de idei, anumite versiuni de librării pot conține vulnerabilități de securitate sau probleme de performanță, fapt care impune dezvoltatorului să fie mereu la curent cu ultimele versiuni disponibile.

În esență, odată cu această perioadă, o parte din programare se mută din perspectiva "cum pot implementa această funcționalitate" în cea de "ce tehnologii existente pot folosi pentru a rezolva problema". Această schimbare a reprezentat un prim pas în reducerea complexității dezvoltării, însoțit de sarcina developerului de a studia în detaliu documentațiile aferente.

IDE-uri

Concomitent cu librăriile și framework-urile menționate anterior, la începutul anilor 2000 apar primele versiuni de IDE-uri pentru scris cod, care au îmbunătățit mediul în care se desfășoară procesul de development. Inițial, rolul unui astfel de tool consta în verificarea sintaxei limbajului de programare, identare, operații de prelucrare a textului (find, replace etc.) și, eventual, integrarea unui compilator. Însă, odată cu trecerea timpului, acestea au evoluat până într-acolo încât au ajuns să fie mai mult decât un simplu mediu de scris cod. Funcționalități precum generarea de constructori, getters, setters, analiza importurilor, integrarea de VCS și multe altele au devenit standard.

Funcționalitățile menționate anterior se bazau, în principiu, pe analiza codului dezvoltat, care cuprinde, bineînțeles, tipurile de variabile, importurile și, uneori, chiar și pattern-urile folosite. Avansul în acest sens a fost atât de benefic încât s-a ajuns ca procesul de refactoring să fie realizat automat prin două "click-uri". Operații care mai demult erau riscante, în special în proiectele mari, precum redenumirea unor variabile, mutarea unor clase sau schimbarea unor metode, au devenit acum mult mai sigure. Astfel, IDE-urile au ajuns să fie mai mult decât un editor de text, anume un mediu activ de feedback continuu. Spre deosebire de primul paragraf, efectele rezultate se referă, în principiu, la viteza de scriere a codului. Cum este de așteptat, aceasta a crescut considerabil odată cu funcționalitățile anterioare. Un alt avantaj este scăderea complexității proiectelor, deoarece multe greșeli sunt prevenite în mod automat. Sunt afișate erori de compilare, sunt sugerate anumite opțiuni de optimizare etc.

Pe de altă parte, în acest mod apare și o dependență între programator și un anumit IDE. Dezvoltatorul se bazează pe faptul că tool-ul va observa anumite probleme pe care, mai demult, era nevoit să le anticipeze manual. La fel ca în paragraful următor, putem observa faptul că o parte din gândire este externalizată.

Vibe coding with LLMs

Ajungem în timpuri mai apropiate, mai exact la finele anului 2022, când este lansat ChatGPT 3.5, unul dintre primele LLM-uri capabile să genereze cod pe bază de input-uri. Personal, am folosit ChatGPT abia în 2023, mai mult din curiozitate, pentru a-i testa capabilitățile, iar ce scenariu mai bun decât acela de a-i cere să dezvolte un unit test pe baza definiției unei clase? Rezultatul a fost destul de bun pentru acea perioadă. În prezent nu mai putem ține pasul cu numărul tot mai mare de LLM-uri capabile să genereze cod: GPT, Opus, Gemini, DeepSeek etc. Aceasta fără să mai menționăm faptul că majoritatea dintre ele sunt actualizate prin versiuni majore la intervale de aproximativ 6-12 luni.

Diferența față de precedentele metode este fundamentală. Dacă IDE-urile îl ajutau pe programator să scrie, să compileze și să depaneze codul mai repede, LLM-urile oferă soluții complete, atât la nivel de cod, cât și la nivel de arhitectură sau stack tehnologic. Îndrăznesc să spun că, pentru prima dată în istorie, există o unealtă care nu doar asistă procesul de software development, ci participă activ la el. Totuși, asemenea tuturor inovațiilor discutate până acum, această schimbare nu vine exclusiv cu avantaje. Creșterea spectaculoasă a productivității este însoțită, după cum bine știți, de noi provocări și compromisuri, pe care le vom discuta ulterior.

Un aspect important legat de LLM-uri, pe care aș dori să-l punctez, este viteza de adoptare a paradigmei cunoscute astăzi sub numele de "vibe coding". Deși este un termen relativ nou, conceptul din spate este unul simplu: programatorul descrie cerința în limbaj natural, iar LLM-ul generează implementarea. Dacă programarea "object-oriented", apariția framework-urilor moderne sau migrarea spre cloud s-au desfășurat gradual, LLM-urile au trecut de la statutul experimental la o unealtă folosită zilnic de milioane de oameni. O posibilă explicație pentru acest fenomen este, în opinia mea, simplă. Programatorii au fost întotdeauna receptivi la idei sau instrumente care reduc efortul necesar pentru rezolvarea unei probleme. Librăriile au eliminat nevoia de a rescrie funcționalități comune, în timp ce LLM-urile reduc timpul necesar pentru a transforma o idee într-o implementare concretă. Mai mult, aceste modele permit accesul la cunoștințe pentru oricine este interesat. Să vă închipuiți că un student aflat la început de drum poate, în acest moment, să dezvolte o aplicație cap-coadă: funcționalități, baze de date, infrastructură, securitate etc. Bineînțeles, rolul experienței intervine în momentul în care discutăm despre calitatea propriu-zisă a aplicației, dar mi se pare un avantaj major pentru cineva care, să zicem, ar dori să dezvolte doar partea de backend a unei aplicații și să utilizeze un model pentru a-i genera componenta de frontend. Astfel de scenarii nu mai sunt de domeniul science-fiction-ului. Developerul nu mai este nevoit să aloce timp pentru a deprinde tool-uri dedicate unei anumite componente a unui proiect sau a unei aplicații.

Un alt aspect interesant al conceptului de "vibe coding" îl reprezintă mutarea accentului de la implementarea propriu-zisă la formularea problemei. În trecut, și chiar în prezent, expertiza unui programator este asociată cu abilitatea de a transpune o cerință în cod într-un mod cât mai optim. Odată cu această trecere, atenția se mută de la implementarea propriu-zisă la modul de a formula sau de a defini corect problema și, respectiv, de a evalua într-un mod cât mai critic răspunsurile primite. Această schimbare devine și mai evidentă în momentul în care analizăm modul în care funcționează LLM-urile. Rezultatul este influențat direct de informațiile oferite ca input. Nu este întâmplător faptul că, în ultimii ani, au apărut termeni precum "prompt engineering". Ca o paranteză în acest sens, în trecut s-a simplificat aprofundarea unui limbaj de programare (de la Assembly la C, de la C la Python), în timp ce, în prezent, se pune accentul pe formularea unui context relevant. Deși, odată cu dezvoltarea hardware-ului, spațiul de "manevră" pentru LLM-uri a crescut, principiul de bază rămâne același: modelul poate lua decizii doar pe baza informațiilor pe care le vede. Astfel, abilitatea de a selecta și furniza un context cât mai relevant devine, în sine, o competență. Nu este de ajuns să formulezi cerințe vagi precum "construiește-mi o aplicație web care să facă x, y, z". În mod ideal, trebuie să descrii comportamentul dorit, arhitectura dorită, eventualele constrângeri tehnice, tehnologiile dorite, nivelul de securitate și multe altele. Din această perspectivă, aș argumenta că programarea asistată de LLM-uri nu elimină complexitatea dezvoltării software, ci, mai degrabă, o mută într-un alt loc.

Totuși, tocmai această mutare a complexității aduce cu sine unele dezavantaje. În trecut, programatorul era obligat să înțeleagă în detaliu mecanismele pe care le implementează (de preferat ar fi și în prezent), dar, prin "vibe coding", se creează uneori iluzia că aprofundarea nu mai este necesară. În realitate, codul generat nu este mai puțin complex doar prin simplul fapt că a fost generat. Astfel, un prim risc este reprezentat de scăderea nivelului de înțelegere asupra soluției tehnice. Este relativ banal să obții sute de linii de cod funcționale folosind un LLM, însă să poți explica modul în care funcționează este cu totul altceva. Eventualele limitări și vulnerabilități de funcționare sunt aspecte pe care ar trebui să le adresăm de fiecare dată, deoarece ele vor apărea inevitabil de-a lungul timpului. În mod normal, o aplicație reală trebuie menținută ani de zile, trebuie adăugate noi funcționalități și trebuie adaptată unor medii și situații imprevizibile.

Un alt dezavantaj îl reprezintă tendința modelelor de a produce soluții aparent corecte, dar fundamentate greșit. În timp ce un compilator semnalează explicit o eroare și nu îți permite să continui până ce aceasta nu este rezolvată, un LLM poate genera explicații de-a dreptul convingătoare, clase și metode care par valide la prima vedere, însă sunt nefolositoare sau chiar dăunătoare. Riscul este cu atât mai mare cu cât task-ul de implementat este mai complex. Din acest motiv, verificarea codului generat rămâne o responsabilitate care, în momentul de față, nu poate fi ignorată.

Mergând mai departe, nu putem să ignorăm costul asociat utilizării acestor modele. În cazul proiectelor mici sau ocazionale, acesta poate părea neglijabil. Totuși, pe măsură ce complexitatea crește, iar prompt-urile devin, la rândul lor, tot mai mari, costurile generate de tokeni pot crește semnificativ. Acest lucru este și mai evident în cazul proiectelor enterprise, unde discutăm despre mii de fișiere, documentații sau baze de cod de dimensiuni considerabile. Cu toate că, în ultimii ani, prețul per token a scăzut, acesta, asemenea infrastructurii unui proiect, rămâne o resursă care trebuie gestionată și optimizată pe cât de mult posibil.

Un ultim aspect la care mă gândesc este că, odată cu utilizarea LLM-urilor, se poate să apreciem viteza în detrimentul calității. Faptul că o aplicație poate fi dezvoltată într-o după-amiază nu denotă automat că aceasta a fost bine proiectată, securizată etc. În ultima perioadă, observăm tot mai des pe platforme precum LinkedIn postări de tipul "am construit o aplicație de management financiar în două ore" sau "am dezvoltat un calendar inteligent folosind modelul X". Deși astfel de realizări sunt, fără îndoială, impresionante din perspectiva vitezei de execuție, ele nu spun aproape nimic despre calitatea produsului rezultat. În realitate, diferența dintre un prototip și o aplicație pregătită pentru utilizare reală este considerabil mai mare decât pare la prima vedere.

Concluzie

Așadar, privind în urmă, observăm că evoluția programării nu a eliminat complexitatea, ci a mutat-o constant către niveluri tot mai ridicate de abstractizare. Librăriile și framework-urile au redus nevoia de a rescrie cod, IDE-urile au automatizat activități repetitive, iar LLM-urile au început să participe activ la procesul de dezvoltare. Cu toate acestea, fiecare progres tehnologic care a redus efortul mecanic a crescut, concomitent, și importanța discernământului uman.

Poate cea mai bună analogie este cea a șoferului și a mecanicului. Faptul că cineva conduce foarte bine o mașină nu înseamnă automat că înțelege și mecanismele din spatele ei. În mod similar, faptul că putem genera rapid o aplicație folosind un LLM nu garantează că îi înțelegem arhitectura, limitările sau implicațiile pe termen lung. Indiferent cât de performante vor deveni aceste unelte, responsabilitatea pentru produsul final rămâne, în continuare, a programatorului.

NUMĂRUL 166 - AI for Programmers

Sponsori

  • Banca Transilvania
  • Betfair
  • MHP
  • .msg systems
  • P3 group
  • Cognizant Softvision
  • BMW TechWorks Romania

INTERVIURI