ABONAMENTE VIDEO REDACȚIA
RO
EN
×
▼ LISTĂ EDIȚII ▼
Numărul 31
Abonament PDF

Diminuarea complexității cu TDD și Agile

Radu Ometită
Software engineer
@Fortech
PROGRAMARE


În ultimii ani complexitatea proiectelor a crescut încet până la punctul când modalitățile utilizate în trecut pentru diminuarea sa au devenit ineficiente. În prima parte a articolului vă voi împărtăși motivele pentru care consider complexitatea ca pe un aspect de care ne vom izbi timp îndelungat și despre care cred că va crește standardele a ceea ce înțelegem noi prin software acceptabil.

Complexitatea este legată în primul rând de legea lui Moore și de creșterea incredibilă a puterii computaționale de care se dispune în prezent. Aceasta le-a permis sistemelor software să înceapă să facă față cu mare ușurință problemei complexității crescute, în ciuda evoluției mai lente a paradigmelor de programare. Chiar și cu apariția procesării multicore abia am început să simțim saltul în complexitatea software-ului când e vorba de programare concurentă.

Întrucât legea lui Moore și-a atins platoul și se pare că nimeni nu e dispus să investească în creșterea puterii de procesare a nucleelor individuale (dacă nu chiar în eliminarea puterii pe care o au), ne confruntăm cu dilema dificultății scalării aplicațiilor noastre. Se pare că nu există o soluție facilă pentru această problemă, pe care să o putem aplica menținând în același timp stilul și modelul nostru actual folosit în dezvoltarea de software, potrivit pentru aplicații ce rulează pe un singur nucleu.

Puterea de procesare sporită din ultima decadă a dus la o creștere a așteptărilor cu privire la ceea ce trebuie să facă aplicațiile de business. Una dintre schimbările cele mai interesante remarcate de noi este ca software-ul trebuie să fie tot mai maleabil si ușor de îmbunătățit pentru a ține pasul cu ritmul schimbărilor. Aceasta e probabil principala forță motrice pentru Agile și cauza buclelor de feedback tot mai strânse.

Complexitatea are deci două surse. în primul rând, businessul are nevoie de timpi de răspuns mai scurți la solicitările de modificări, aspect ilustrat de acapararea majorității proiectelor și firmelor de software de către metodologiile Agile. Nu am întâlnit pe nimeni suficient de curajos pentru a încerca o metodologie Waterfall pe un proiect complex în prezent. Pe de altă parte, suntem nevoiți să facem față unor aplicații paralele care cresc semnificativ complexitatea elaborării software-ului din cauza căreia putem vedea această apariție a paradigmelor programării funcționale în majoritatea, dacă nu toate, limbajele principale.

Un cadru (framework) pentru complexitate

Pentru a înțelege de ce par să dea greș fără drept de apel abordările vechi când se aplică unor proiecte foarte dinamice e nevoie de o bună înțelegere a noțiunii de complexitate și a modului în care poate fi ea clasificată. Un astfel de model este cel utilizat de Cynefin Framework, care clasifică complexitatea în patru domenii, fiecare dintre ele cu propriile abordări. Merită menționat că nu tot ce funcționează pentru un domeniu de complexitate se poate aplica și altuia.

Modul în care se tratează o problemă din acest punct de vedere este prin aducerea ei de la un domeniu complex la unul simplu. Dar felul în care transformi o problemă complexă într-una cu un grad mai redus de dificultate este diferit de modul în care transformi o problemă complicată într-una simplă. În final este vorba de constrângeri. Constrângerea unui domeniu complex dă naștere unui domeniu mai puțin complex, iar constrângerea acestuia rezultă unui domeniu simplu.

Desigur, direcția poate fi și inversă (datorita unui eveniment ce proiectează problema în domeniul haotic, o schimbare în specificații, direcție sau toate cele menționate mai sus), caz în care ar trebui să urmăriți cu atenție semnele ce vă permit să încadrați corect problema în domeniul său de complexitate și să o abordați într-un mod potrivit.

Se va prezenta în continuare o scurtă descriere a domeniilor de complexitate, conform definiției Cynefin Framework.

Sursă fig. - quarterview.com/?p=1091

Simplu

Contextele simple sunt caracterizate de faptul că răspunsul corect la o problemă este evident. Unele exemple de astfel de contexte ar putea fi scrierea unui Java Bean sau a ceva care poate fi făcut într-un singur mod. Aceasta este zona ‘Best Practices’, unde cauzalitatea unei chestiuni este clar înțeleasă.

La acest nivel abordarea unei probleme începe prin evaluarea situației și includerea ei in categoria potrivită. Ulterior se trece la soluționarea ei într-un mod prestabilit.

Dacă ne referim la programare, problemele de acest gen pot fi sarcini pe care le poate îndeplini aproape orice persoană capabilă să urmeze o listă de verificare (checklist). Drept exemplu puteți considera un cod pe care-l poate scrie chiar și un copil sau non-programatori.

Complicated/Sofisticat

Întrucât în limba română complicat este apreciat ca sinonimul lui complex, traducerea lui complicated cu sofisticat ar acoperi mai bine definiția dată de Cyenfin Framework acestui nivel de dificultate.

Cynefin Framework utilizează termenul complicated. Domeniul complicated e ceva mai puțin restrictiv decât cel simplu și permite soluții alternative la o problemă. Acesta e domeniul cunoștințelor experte. Întrucât poți avea mai mult decât un răspuns bun la o întrebare, domeniul e de asemenea unul al Bunelor Practici. Există aici și o legătură cauzală, care este totuși destul de ascunsă vederii de multitudinea soluțiilor posibile și în mod cert nu e la fel de clară ca într-un domeniu simplu.

Pentru problemele din acest domeniu evaluăm prima dată situația și apoi ne bazăm pe experții, care trebuie să efectueze o analiză ale cărei concluzii le utilizăm pentru implementarea soluției agreate. Această abordare e necesară deoarece nu există o soluție care e în mod cert cea mai bună.

În ce privește activitățile de programare, putem include aici sarcini repetitive care cer totuși puțină analiză situațională. Un exemplu potrivit ar fi designul unei aplicații CRUD. Aceasta necesită cunoașterea câtorva framework-uri, baze de date, etc. .Există însă o mulțime de Bune Practici încetățenite care pot fi urmate.

La acest nivel cerințele sunt destul de stabile, permițând utilizarea abordării Waterfall. Un exemplu de sarcina corespunzătoare acestui nivel ar fi unul ce implică scriere de driver-e pentru dispozitive. Deși soluția poate necesita o experiență extensivă, există o mulțime de Bune Practici pentru a te ghida spre o soluție acceptabilă. Specificațiile nu se vor schimba foarte mult, ele fiind constrânse de interfațahardware.

Complex

Domeniul complex este unul pe care am început să-l întâlnim tot mai des de ceva vreme încoace. La acest nivel primim cerințe care se modifică rapid pentru a răspunde presiunii externe. Orice modificare în specificații va duce la dezvoltarea unei funcționalități, iar implementarea sa ar putea duce la alte schimbări ale cerințelor după analiza efectului pe care îl are ea asupra bazei de utilizatori sau a altor componente ale aplicației. Acesta e domeniul designului emergent și el ne permite să discutăm despre cauzalitate doar în retrospectivă.

Din acest punct abordarea Waterfall va deveni neputincioasă datorită buclelor sale de feedback foarte mari, iar metodologiile Agile încep să câștige teren. Suntem echipați acum pentru a face față specificațiilor dinamice, aflate într-o continuă schimbare.

Cea mai mare problemă ridicată de contextele complexe este elementul de noutate pe care-l aduc în procesele noastre. Întrucât abia am început să facem trecerea la ele, le înțelegem prea puțin și tindem să le considerăm doar complicate. Dovadă că lucrurile stau așa sunt încercările de a impune un document cu specificații cât mai exact, apariția unei mulțimi de reguli și regulamente care încearcă să controleze haosul aparent, frustrarea produsă de neînțelegerea modului în care se așteaptă să evolueze sistemele și lipsa controlului asupra mersului lucrurilor. În ciuda tuturor celor de mai sus, sistemele complexe cer experimentări și explorări, pe care trebuie să le includem în planurile noastre pentru a obține rezultatele dorite.

În domeniile complexe trebuie să încurajezi experimentarea și explorarea. Aceasta înseamnă că trebuie să setezi proiectul, aplicația și procesele astfel încât ele să permită eșecuri multiple ieftine în încercarea de a obține rezultatul dorit. La acest stadiu e vorba despre lansarea unui experiment și evaluarea rezultatelor post-experiment, urmate de integrarea soluției în aplicație în cazul unor rezultate satisfăcătoare.

Haotic

Domeniul haotic este cel al circumstanțelor excepționale. El apare destul de rar și poate fi deosebit de periculos pentru aplicația și afacerea ta. Haosul ar putea fi cauzat, printre altele, de o eroare mare în modulul de securitate apărută în sistemul tău aflat în funcțiune.

Prima măsură pe care trebuie s-o iei în astfel de circumstanțe este să controlezi daunele. Aceasta înseamnă să închizi serverele, să deconectezi cablurile de rețea, să-ți chemi avocații și orice altceva poate minimiza impactul incidentului.

Ca follow-up la trecerea pe context haotic, presupunând că firma supraviețuiește, pot urma fără rezistență îmbunătățirile proceselor și inovarea, întrucât toată lumea e predispusă în astfel de momente să accepte ușor schimbarea pentru a preveni repetarea unor asemenea evenimente.

Evoluția practicilor Agile

Debutul

La începuturile lor, practicile Agile aveau rolul de a reduce impactul unor probleme ridicate de domeniile complicate și într-o mai mică măsură de cele complexe. Metodologia preferată atunci era întrucâtva similară celei Waterfall, care începea să-și arate limitele în contextul creșterii complexității software-ului dezvoltat (presupunând ca a funcționat vreodată).

Principalele probleme cu Waterfall au fost ‘specificarea completă a software-ului’ folosită în dezvoltarea software și ciclurile relativ lungi de release. În vreme ce acestea pot da în mod cert rezultate pentru domeniile simple și complicate, bucla de feedback mare ar preveni implicarea clientului, conducând la rescrieri majore ale softului de la o versiune la alta.

Metoda Waterfall a fost și încă este o abordare foarte confortabilă și intuitivă, care creează un sentiment de securitate artificial, întrucât toate lumea își vede de treabă, cu rezultate acceptabile, în conformitate cu specificațiile. Problemele apar doar la ora deadline-ului, când se constată ca softul nu e exact cel visat de client și fiecare își găsește o scuză. Procesul Waterfall este confortabil tocmai pentru că nu permite tragerea la răspundere. Nimeni nu răspunde pentru propriile greșeli.

În paralel, echipele Agile au început să rezolve probleme cu grad sporit de complexitate. Ciclurile de feedback mai scurte au permis o implicare mai mare și totodată puțin mai stresantă din partea clientului, dar echipele au fost în măsură să se adapteze mult mai rapid la modificările cerințelor.

Succesul inițial avut cu Agile a condus uneori la delăsare și iluzia că procesul e unul simplu. Pentru domeniile simple și complicate este într-adevăr așa, pentru că în aceste contexte ne putem baza pe cerințe care evoluează încet și conexiuni cauză-efect întrucâtva clare.

Această delăsare a creat un context ce a transformat Agile într-un set de Bune Practici, ducând la relaxarea tuturor sub impresia că înțelegeau Agile. Trebuie doar să ai ‘stand ups’ și retrospectiva sprint-ului și putem uita totul despre forțele care au dus la apariția Agile.

Incidental, același lucru s-a întâmplat cu TDD. Datorită naturii simple sau complicate a proiectelor, TDD a fost redus la cerința de a avea un set de teste ‘unit’ pentru a obține un grad acceptabil de certitudine cu privire la corectitudinea codului tău. Şi a devenit chiar ușor. Trebuie să ai doar (aproape) 100% acoperire. Nu e nevoie să te gândești prea mult la cum structurezi codul de producție sau testare.

Această abordare părea profesională și totul era bine și frumos în lume, până când a intervenit schimbarea…

Nu mai suntem în Kansas

Puteți vedea această schimbare deoarece multe persoane au început să afirme că metoda Agile și TDD și toate acele practici mărunte și drăguțe care ne-au făcut să ne simțim foarte profesionali au început să nu mai funcționeze. Ce se întâmplă?

A apărut nevoia ca software-ul să rezolve probleme complexe și pentru prima dată am avut hardware-ul care poate să facă acest lucru. Dezavantajul este că a lipsit o înțelegere clară a modelului complexității din spatele nevoilor noastre și presupunând că dacă am face ‘mai mult’ din lucrurile pe care le-am făcut pentru rezolvarea problemelor complicate, ar putea funcționa. Dar așa ceva nu avea cum să funcționeze.

Problemele complexe au natură diferită față de problemele din sfera complicated datorită schimbării în lanțul cauzalității. Putem vedea cauzalitatea doar în retrospectivă în probleme complexe. În timp ce acest lucru pare clar acum, nu era evident atunci. Era ca și încercarea de atingere a curcubeului. Eșuezi un proiect și înveți lecția, stabilești un cod de bune practici care să prevină același lanț cauzal și încerci din nou. Din păcate, natura problemelor complexe nu garantează sub nici o formă că o astfel de abordare ar funcționa, creând o frustrare nemărginită.

Într-un mod ironic, practicile Agile și TDD ar fi putut fi folosite pentru a ajuta la rezolvarea unor probleme complexe în forma lor originală, mai puțin instituționalizată, dacă forțele datorită cărora ele funcționează ar fi fost înțelese.

De ce nu mai funcționează practicile TDD?

Să analizăm care este principala problemă pe care oamenii o au cu practica TDD. Dacă aveți o acoperire completă, folosind unit tests ale softului, atunci schimbarea comportamentului softului va determina eșuarea testelor. Câte vor eșua? Depinde de schimbare și de numărul de teste pe care le-ai scris. Rețineți că în domeniile simple și complicated unit tests sunt indicate, deoarece aceste domenii sunt aproape imune la schimbare și solidificarea codului de bază este o idee bună deși aceste teste trebuie văzute mai mult ca un mijloc de descurajare a schimbării decât ca o verificare a corectitudinii.

Atunci când softul încearcă să rezolve o problemă complexă, în special o problemă care nu este chiar clară la acest moment, codul trebuie să fie schimbat foarte frecvent. Este necesar ca experimentarea și eșecul să fie ieftine. De ce ar folosi cineva unit tests? Singurul răspuns plauzibil este acela că din cauza celor mai bune practici, care nu se aplică evident domeniilor complexe.

TDD nu a fost inițial despre unit tests. Accentul, în special pentru un software nou, a fost pus pe testare funcțională (care din punctul meu de vedere este testare funcțională). Aceste teste sunt magice în sensul că ele nutestează unități de cod ci mai degrabă comportamente ale sistemului complet. Mă întreb cum ar putea funcționa acest lucru pentru probleme complexe. Crearea de teste funcționale furnizează barierele de care ai nevoie în dezvoltarea softului dintr-un domeniu complex. Testele sunt uneori denumite specificații executabile.

Testele funcționale nu împiedică schimbări la baza codului, ci mai degrabă încurajează acest aspect. Aceste teste se asigură că acest cod respectă funcționalitatea care a fost convenită până în prezent (problema schimbării frecvente a softului a fost unul dintre obiectivele TDD, care acum pare să fi fost uitat).

Scrierea testelor înaintea implementării oferă o structură inițială (barieră) a funcțiilor pe care încerci să le implementezi. Natura imprevizibilă a ciclurilor Roșu / Verde / Refactorizare se potrivește ca o mănușă “practicilor emergente”.

Partea de Refactor a ciclului TDD este ceea ce de obicei se amână în cele mai multe echipe, prin urmare avem ’’sprinturi de consolidare’’. Aceasta este o greșeală. Codul trebuie să fie în cea mai bună formă și extras corespunzător în componente. Cum poți să faci experimente ieftine dacă codul tău arată ca un bol de spaghete?

Cu siguranță TDD pare că ar fi destul de potrivit pentru domeniul nostru complex. Dar nu poți trata TDD ca un regulament și să îl utilizezi ca atare și să aștepți rezultate în acest domeniu. Este necesar să lucrezi cu TDD, în același timp să fi conștient de cauzele ce au dus la crearea sa și ce anume vrea strategia ta de testare să realizeze. Întotdeauna trebuie să ai o strategie de testare.

Mai mult, trebuie să înceapă să îți placă Roșu/Verde și în special etapa de Refactor. Acesta este singurul motiv pentru care codul va fi mai ușor de scris. Nu poate rezolva niciodată toate problemele; dacă apare o schimbare arhitecturală majoră va dura un timp până va fi implementată. Dacă este un model pentru schimbare sau când apare un model nou, codul tău îl va oglindi precis,iar implementarea experimentelor va deveni mult mai ușoară.

Ce putem spune despre practicile Agile?

De la începuturile lor, practicile Agile s-au făcut pe cod testabil și testat. Dacă folosești un proces Agile și cod netestat înseamnă că nu ești foarte Agile (vezi flaccid scrum), iar proiectul e mai degrabă nou sau se situează în domeniul simplu sau poate complicat.

Una dintre plângerile recente adresate practicilor Agile a sunat în felul următor: ‘programatorii ar trebui să scrie cod și nu să-și piardă timpul în întâlniri standup lipsite de sens’. Aceasta presupune ca aplici procesul aferent unei probleme simple sau complicate, întrucât scrierea de cod o poți face doar dacă știi ce ar trebui să facă acel cod.

Aceasta nu se întâmplă în cazul domeniului complex, care necesită eșecuri ieftine și are cerințele modificate în funcție de succesul sau eșecul diferitelor experimente. Un domeniu complex cere de asemenea structurarea codului în părți reutilizabile. Cum poți ști că nu scrii cod pentru același tip de funcționalitate ca unul dintre membrii echipei tale în lipsa comunicării în cadrul echipei?

În domenii complexe comunicarea ar trebui încurajată puternic; întâlniri zilnice de 20 de minute ale membrilor echipei optimizează procesele, ele micșorând nevoia întreruperilor repetate la intervale aleatorii pe parcursul întregii zile.

Când întâlnirile stand-up se țin doar pentru că sunt prevăzute în regulament și desconsideri problema pe care ele încearcă să o rezolve ele devin cu adevărat o pierdere de vreme.

O altă explicație a eșecului metodelor Agile în cazul domeniilor complexe o reprezintă sesiunea de planificare a sprint-ului în care trebuie să estimezi timpul necesar implementării unei funcționalități. Întrucât domeniile complexe cer experimentarea cu codul, estimările par întrucâtva contraintuitive (nu poți face previziuni precise despre funcționalități în acest context). Problema acestea nu cred totuși că e una relevantă în practică. Cred mai degrabă ca planificarea sprint-urilor ar trebui să creeze o imagine întrucâtva precisă a punctului actual și al celui pe care dorim să-l atingem până la următoarea întâlnire, obiectiv pe care îl îndeplinește în mod admirabil.

Concluzii

Nu ne mai putem permite să credem în magia cuvintelor sacre precum TDD și Agile. Pe măsură ce crește complexitatea software-ului, întreaga echipă de dezvoltare trebuie să înțeleagă forțele care au dus la apariția TDD și Agile și cum să le aplice în conformitate cu rolurile lor și cu o atenție permanentă.

Avem un model pentru complexitate care explică de ce apar aceste probleme și motivele pentru care nu vom putea replica succesele noastre trecute în prezent folosind de fiecare dată aceleași tehnici. Este acest model precis sau nu? Încă nu știm foarte bine, dar modul în care a fost el primit și utilizat indică niște corelații interesante.

Plafonarea datorată succeselor trecute îți creează tot felul de probleme, la fel ca și industriei IT. Nu te relaxa prea mult în lumea ta și încearcă să înveți în permanență ceva nou, preferabil ceva ce te sperie. Dacă vrei să te alături noii generații de programatori și de paradigme de programare, lasă deoparte plafonarea și începe chiar acum să înveți.

LANSAREA NUMĂRULUI 87

Prezentări articole și
Panel: Project management

Marți, 24 Septembrie, ora 18:00
Impact Hub, București

Înregistrează-te

Facebook Meetup

Conferință

Sponsori

  • ntt data
  • 3PillarGlobal
  • Betfair
  • Telenav
  • Accenture
  • Siemens
  • Bosch
  • FlowTraders
  • MHP
  • Connatix
  • UIPatj
  • MetroSystems
  • Globant
  • Colors in projects