ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
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 167
Abonamente

Controlul se preia nu se generează: despre AI, viteză și design

Sorina Băleanu
Java Software Engineer @ BMW TechWorks Romania



PROGRAMARE

Nu este o noutate pentru nimeni că dezvoltarea software a fost schimbată permanent de instrumentele de IA pentru generarea de cod. Sunt, fără exagerare, cel mai mare salt de productivitate pe care l-am văzut în cariera mea și cred cu tărie că pot fi folosite în avantajul nostru. Dar tocmai pentru că sunt atât de eficiente, viteza pe care o oferă poate scăpa ușor de sub control.

Ceea ce am început să remarc este o schimbare subtilă în modul nostru de lucru când începem să integrăm aceste instrumente foarte puternice fără să înțelegem cum să le folosim și fără o strategie clară. Ne concentrăm tot mai mult pe fișiere izolate în loc să urmărim fluxuri end-to-end, începem fiecare implementare cu o generare de cod în loc să schițăm o soluție și amânăm deciziile de design, preferând să facem totul din mers.

Din experiența proprie, vă pot spune că efectul devine cel mai vizibil într-un sistem construit de la zero cu IA. Cerințele de la început sunt simple și vin una câte una. Doar că totul se implementează mult prea rapid, făcând dificilă anticiparea modului în care vor interacționa componentele și tipurile de edge case-uri care vor apărea dincolo de fluxurile inițiale. Rezultatul este că fiecare piesă pare corectă individual, dar sistemul pierde din coerență la nivel de ansamblu. Controlul se poate recupera, dar cu cât se acumulează mai multe straturi de inconsistență, cu atât costul crește.

Vestea bună este că aceleași tooluri care accelerează implementarea pot fi folosite și pentru a preveni problemele dacă știm cum să le ghidăm. Pentru a explora acest fenomen, mi-am structurat observațiile într-un scenariu cu trei acte: accelerarea, inconsistența și recuperarea controlului. Povestea este inspirată din experiențe reale, dar construită într-o formă fictivă, simplificată și exagerată pe alocuri pentru claritate.

Acestea fiind spuse, vă rog să vă imaginați următorul cadru:

Personajele:

Echipa: dezvoltatori software, cu acces pentru prima dată la cele mai bune tooluri de IA disponibile;

IA-ul: un partener de implementare rapid, care face exact ce i se cere;

Designul: absent din distribuție, nu apare în primele două acte.

Decorul: Un proiect nou. La început: un frontend și un backend. Pe parcurs, backendul inițial evoluează într-un microserviciu de tip orchestrator când se adaugă un nou microserviciu downstream.

Se ridică cortina.

Actul 1: Prompt după prompt - viteză fără direcție

La început, totul era simplu. Un frontend, un backend, având comunicare directă între ele. Cerințele vin vagi, cum vin la începutul oricărui proiect, dar echipa tocmai a descoperit un tool de generare de cod și este fascinant. Este promițător. Cerințele de început prind culoare extrem de repede: un formular, un endpoint, un interceptor care prinde erorile și le afișează într-un pop-up. Elegant, simplu și funcțional. În câteva minute, totul este cod funcțional.

Apoi arhitectura crește. Apare un microserviciu specializat: downstreamul. Backendul inițial devine un microserviciu de tip orchestrator: primește cereri de la frontend, apelează downstreamul, agregă răspunsurile. Acum sunt două microservicii, fiecare cu propriile endpointuri, propriul model de erori, propria structură de răspuns.

Cu timpul, cerințele devin mai complexe și fiecare serviciu ajunge să funcționeze după propriile sale reguli. Frontendul are interceptorul lui, logica lui de retry și de afișare a erorilor. Orchestratorul are retry-ul lui, cache-ul lui, modul lui de a traduce erorile. Noul microserviciu are structura lui de răspuns, timeout-urile lui. Toate sunt generate corect, fiecare în izolarea propriului fișier. Dar nimeni nu a scris: "Așa arată un flux de eroare end-to-end." Nimeni nu a decis: "Retry-ul se face într-un singur loc." Nimeni nu a stabilit: "Formatul de eroare este acesta, peste tot."

Ceea ce se întâmplă este că, atunci când folosim AI pentru implementare, fără o viziune de ansamblu, modul de lucru devine un prompt izolat într-un fișier. Această granularitate se mapează perfect pe taskuri izolate, dar prost pe responsabilități care nu aparțin unui singur fișier: error handling, retry policies, caching strategies. Sunt exact tipul de decizii pe care arhitectura software le numește cross-cutting concerns, iar un astfel de prompt cu un context limitat nu le poate rezolva. IA-ul a răspuns exact la ce i s-a cerut, dar problema este că nu i s-a cerut niciodată coerență la nivel de sistem.

Actul 2: Fix după fix - coerență locală, inconsistență globală

Problemele apar una câte una, deghizate în buguri banale. Și exact cum a venit implementarea, așa vin de obicei și fixurile: un fișier, un context parțial, o soluție locală.

Primul semnal: pop-upul de eroare apare gol sau cu un mesaj generic. Echipa investighează. Orchestratorul propagă erorile de la downstream, dar cele două microservicii folosesc structuri diferite. Fiecare format era rezonabil în izolare. Nimeni nu verificase că sunt compatibile, pentru că nimeni nu documentase un contract comun. Se aliniază, se fixează structura și merge. Dar fixul deschide următorul strat. Acum că toate erorile ajung corect în frontend, se descoperă că retry-urile din frontend nu s-au executat niciodată pentru că interceptorul, extins de mai multe ori ca răspuns la cerințe punctuale, re-arunca anumite erori fără să păstreze structura originală. După ce se fixează interceptorul, brusc retry-urile pornesc toate, și cele din frontend, și cele din orchestrator, iar cu un singur click sistemul generează 9, 12 requesturi către downstream.

Și tot așa vă puteți imagina cum continuă povestea: dezactivezi retry-ul din frontend și descoperi că orchestratorul păstrează în cache erori vechi. Fixezi cache-ul și descoperi că timeouturile nu sunt corelate. Fiecare strat corectat expune un strat pe care nu-l știai. Fiecare investigație durează.

Cum de nimeni nu a observat mai devreme?

Cercetătorii numesc asta "automation complacency", adică tendința de a reduce vigilența când un sistem automatizat pare să funcționeze. Nu s-a verificat fluxul end-to-end, nu din neglijență, ci pentru că fiecare piesă individuală trecuse peste reviewuri ca fiind corectă. Riscul crește când cei care folosesc toolul nu au încă experiența necesară pentru a-i evalua critic outputul. Un dezvoltator experimentat poate recunoaște că o soluție este corectă local dar problematică în context. Unul la început de drum, confruntat cu un tool care pare atât de sigur pe sine, pierde discernământul. Iar odată ce problemele încep să apară, investigația devine tot mai adâncă.

Rabbit hole-ul nu are fund, pentru că nu mai trebuie să depanezi un bug, ci trebuie să depanezi lipsa unui design. Un LLM generează cod pe baza unei ferestre de context limitate, fundamental diferită de înțelegerea unui sistem. Studiile recente arată că există un decalaj semnificativ între ce așteaptă dezvoltatorii de la codul generat de IA și ce primes. Aceleași inconsistențe ar fi putut apărea și fără IA. Diferența e că IA-ul comprimă timeline-ul atât de mult încât straturile se acumulează înainte ca cineva să le observe.

Actul 3: Recuperarea controlului - design înainte de prompt

Ceea ce am descris în primele două acte se poate întâmpla oricui. Nu este o poveste despre o echipă care a greșit, ci este despre un mod de lucru care devine natural când metrica cea mai importantă devine viteza de implementare, fără o pauză deliberată pentru design și revizuire. Cu fiecare zi care trece, codul acceptat fără o decizie conștientă devine mai greu de gestionat. Nu pentru că cineva a uitat rațiunea din spate, ci pentru că nu a existat niciodată una. Sistemul crește pe fundații pe care nimeni nu le-a înțeles și validat corespunzător.

Tehnicile care urmează sunt cele pe care echipa din povestea noastră le-a adoptat ca să recupereze controlul și să folosească instrumentele în avantajul lor. Dar nu trebuie să aștepți să te lovești de un zid ca să le aplici. Folosește-le când începi o implementare nouă a unui feature complex, când adaugi ceva pe un flow existent și de fiecare dată când o schimbare traversează mai mult de un serviciu sau mai mult de un layer.

Analizează întregul flux și definește contractele.

Înainte de a deschide un prompt, oprește-te și privește sistemul ca pe un întreg. Stabilește fluxul complet al unui request: tot ce ar trebui să se întâmple de la clickul utilizatorului până la răspunsul afișat pe ecran. Nu codul, nu fișierele, ci fluxul de date. Din acest flux extrage contractele explicite: un singur format de eroare, peste tot. Retry doar într-un singur loc. Nu sunt decizii de implementare, sunt decizii de arhitectură. Spre exemplu dacă folosești Github Copilot, scrie-le în fișiere de tip copilot instructions sau custom instructions care descriu regulile transversale ale proiectului. Acestea devin contextul de bază pe care IA-ul îl primește la fiecare interacțiune, indiferent de prompt, indiferent de cine promptează.

Specification-driven prompting

Cea mai concretă lecție este, totuși, că niciun prompt nu ar trebui să existe în vid. Designul stabilit în pasul anterior trebuie reîntărit în fiecare prompt, la fiecare interacțiune cu IA-ul. Modul de promptare ar trebui schimbat de la orientat pe simptome la orientat pe specificații.

Symptom-driven:

"Pop-up-ul de eroare apare gol. Repară interceptorul."

Specification-driven:

"Refactorizează interceptorul conform contractului comun de eroare: obiect cu câmpurile message, code, source. Nu prinde erori care nu respectă schema ApiError, ci lasă-le să se propage."

Diferența nu este doar de formulare ci și de mentalitate. Un astfel de model de promptare constrânge outputul IA-ului la spațiul de design definit de om și elimină cel mai periculos mod de eșec, anume IA-ul rezolvând o problemă diferită de cea reală.

Validare prin plan

Înainte de a executa, revizuiește. Copilot oferă moduri de planificare care permit vizualizarea schimbărilor propuse înainte de generarea codului. Dacă planul include retry într-un serviciu frontend, iar specificația spune că retry-urile aparțin orchestratorului, dezalinierea este vizibilă înainte de a scrie o linie de cod.

Folosește acest pas și pentru verificarea coerenței: "Iată contractul de eroare. Iată interceptorul din frontend. Iată handlerul din orchestrator. Sunt consistente? Ce scenarii ar putea produce un comportament neașteptat?" IA-ul este excelent la acest tip de analiză, atâta timp cât îi dai toate piesele.

Testarea ca plasa de siguranță

Planul arată intenția, dar codul generat trebuie verificat și în execuție. Testele unitare, de integrare și end-to-end sunt unele dintre cele mai eficiente metode de a valida codul generat de IA. Aceasta le poate genera rapid, dar aici apare aceeași capcană: dacă testele sunt generate pe baza implementării, ele validează ce face codul, nu ceea ce ar trebui să facă. Testele nu trebuie neapărat să vină înainte de implementare, dar principiul din Test-Driven Development rămâne valabil: testele trebuie scrise pornind de la specificații și contracte. Nevoia de design nu este eliminată, iar testele scrise corect devin gardieni ai deciziilor luate conștient.

Concluzie

Instrumentele de IA sunt cel mai bun partener de implementare pe care l-am avut vreodată. Dar implementarea nu este dezvoltare software, aceasta reprezintă doar o parte dintr-un proces mult mai vast. Deciziile de design, contractele între componente, responsabilitățile per layer rămân în continuare responsabilitatea noastră. Și va rămâne, indiferent cât de capabile devin toolurile.

Lecția pe care am extras-o nu este spectaculoasă. Este, de fapt, un principiu vechi reformulat pentru un context nou: planifică înainte de a construi. Desenează fluxul înainte de a deschide fișierul. Stabilește contractele înainte de a scrie promptul. Spune-i IA-ului toate regulile jocului înainte de a-l pune să joace.

Bibliografie

[1] R. Parasuraman and D. Manzey, "Complacency and bias in human use of automation," 2010.

[2] P. Vaithilingam, Z. Tianyi and E. Glassman, "Expectation vs. Experience: Evaluating the Usability of Code Generation Tools Powered by Large Language Models.," in CHI '22, 2022.

[3] "Adding repository custom instructions for GitHub Copilot," [Online]. Available: https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions.

[4] "Best practices for GitHub Copilot CLI," [Online]. Available: https://docs.github.com/en/copilot/how-tos/copilot-cli/cli-best-practices.

Conferință TSM

NUMĂRUL 166 - AI for Programmers

Sponsori

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

INTERVIURI