Am discutat la evenimentul de lansare TSM 113 cu invitații noștri despre arta programării și bineînțeles clean code. Alături de noi au fost online:
Alen Treznai - Senior Software Engineer @ ThoughtWorks,
Daniel Costea: Lucrez în IT de douăzeci de ani și am schimbat multe tehnologii. Când ajungem la stadiul de platou, realizăm că ori îmbrățișăm schimbarea, ori nu mai suntem. Mă refer la faptul că, dacă stai pe tușă în IT chiar și cinci ani, devii ușor depășit în anumite domenii. Lucrez în special pe tehnologii backend, web și command line tools. În ultimul an, am scris niște parsere mutând niște date din data lakes în baze de date relaționale. Lucrurile se complică repede când vorbim de volum mare de date. În ultimii doi ani am obținut o distincție de la Microsoft. E vorba de Microsoft MVP și sper să o merit în continuare. Deși nici în acest moment nu vorbesc din țară, sunt aproape de voi cu fiecare ocazie.
Cât de important este să avem Clean Code? Au fost situații când te-ai împotmolit și a trebuit să rescrii o mare parte din cod?
Daniel Costea: Este extrem de important. Să ne reamintim pentru cine scriem cod. Pentru computer sau pentru om? Codul se scrie mereu pentru doi actori, computerul și omul. Cu computerul e ușor. Dacă compilează și execută înseamnă că a înțeles ce ai scris, dar mai rămâne să înțeleagă și omul ce ai scris. Programarea se face în echipă, de multe ori Agile, ceea ce înseamnă ca avem un cod viu pe care îl scriem, îl testăm, îl extindem. Ceea ce scrii azi, peste șase luni, tu, om cu peste 500 ore de experiență nu îți mai recunoști propriul cod, așa că îl scrii și pentru tine. Eu sunt o persoană vizuală, iar, dacă acel cod nu stă drept, nu mai pot gândi eficient. Pentru confortul meu mental, înainte de a mă uita peste cod, trebuie să îl aranjez.
Alin Treznai: Precum colegul, lucrez de peste douăzeci de ani în IT, cu experiență în backend și Big Data, monoliți legacy și microservicii. Momentan, lucrez pe o platformă care se adresează firmelor de avocatură. Este un exemplu bun de Clean Code. În principiu, evit rescrierile. Sunt adeptul abordării iterative, adică văd o problemă, o rezolv, apoi dezvolt o funcționalitate nouă, mai văd o problemă și o rezolv. La rescriere, e un hazard mare. Mulți se supraapreciază și consideră că vor rezolva din prima toate problemele.
Ce reguli trebuie respectate pentru a avea Clean Code?
Alin Treznai: Ar trebui să avem o serie de procese care să fie urmărite cu multă seriozitate. Aș propune Test-Driven Development, Code Review (pentru a prinde problemele în fază incipientă), dar și tooluri precum SonarQube sau ceva similar.
Daniel Costea: La un moment dat, am pregătit un training pentru începători și am împărțit partea de Clean Code în Basic și Advanced. În ceea ce privește secțiunea Basic, am explicat că trebuie creat cod self-descriptive care să nu necesite linii de comentarii, deoarece codul prin denumirea liniilor, a claselor și a variabilelor ar trebui să fie evident. Sunt și excepții. Dacă scriem o ecuație pui și un comentariu, conform regulii celor 10 secunde. Dacă îți ia mai mult de 10 secunde să înțelegi linia, atunci un comentariu este binevenit. Referitor la Advanced, cel mai subestimat criteriu este DRY (Do Not Repeat Yourself). Primul FOR sau WHILE scris vreodată a fost o implementare a acestui principiu. Se aplică de la semnături complicate la design patterns unde nu vrei să repeți același tip de problemă. Cartea lui Uncle Bob, invitat anul trecut la IT Days, este o biblie.
Faceți Unit Testing? Avem nevoie de coverage de 100%?
Alin Treznai: Aceste teste sunt necesare, iar coverage depinde de tipul de aplicație. Dacă avem o aplicație unde anumiți algoritmi sau tipuri de date vrem să fie corecte, atunci procentul trebuie să fie cât mai mare. Dacă este o chestie simplă, poate un CRUD application, un 70% sau 80% ar fi suficient.
Daniel Costea: Sunt de acord cu colegul meu. Adaug că un cod, cu cât e mai probabil că va fi modificat, cu atât mai legitim este să scrii Unit Tests pentru el. Am făcut parte dintr-o echipă mare de 25 de persoane unde doar partea de backend era de cinci persoane. Pentru că era o aplicație scrisă de la zero și pentru că nu s-au scris Unit Tests la timp, în doar nouă luni de zile, rezolvam bugul cu numărul 1000 și am avut o revelație. Până la acel moment, nu avusesem disciplina scrierii de cod. Vocea mea a contat prea puțin și am început să scriem Unit Tests după multe alte repetări. Când vrei să rescrii o clasă sau o metodă, nu poți începe fără Unit Tests, deoarece vei pierde din funcționalitate. Nu sunt purist și nu sunt un partizan al Test-Driven Development, însă cred că în cod nu există democrație și nu toate clasele sunt egale. Sunt clase care se recomandă a nu fi testate, cum sunt cele generate automat sau clasele statice sau protected. Aș testa părțile scrise integral de noi. Cu cât avem o clasă care conține mai multă logică de business, cu atât mai mult ar trebui să testăm.
Ce părere aveți de testele de integrare?
Alin Treznai: Sunt un fan al lor, deoarece pot găsi multe probleme. Singura problemă la care trebuie să ne gândim este cum să le facem suficient de performante. Am avut proiecte cu teste de integrare ce rulau 1 oră, 2 ore, 3 ore. Soluția este simplă uneori: treci pe microservicii, schimbi arhitectura.
Cine scrie testele?
Alin Treznai: La proiectele la care lucrez, programatorii scriu toate testele, Unit Tests, de integrare. La orice funcționalitate nouă, se așteaptă să vii cu testele în pull request.
Daniel Costea: Și la noi, Unit Tests cad în zona programatorului. Când trebuie scris cod pentru un task greu de testat, un programator ar și trebui să înceapă să scrie Unit Tests, decât să testeze ceva în n feluri. De exemplu, eu, lucrând recent pe niște parsere, am constatat că datele sunt foarte multe cu mii de proprietăți și fără teste de integrare. Iar testerii din echipa noastră ar putea testa doar cât să nu ia foc aplicația.
Cum funcționează procesele?
Alin Treznai: Programatorul are un task. Face un pull request care vine cu cod și teste. Apoi doi oameni fac code review. După aprobare, merge. E important ca doi oameni să realizeze code review, dacă se poate din echipe diferite sau arii diferite ale produsului. Este o modalitate ca toată lumea să știe ce face toată lumea.
Daniel Costea: Noi, fiind o echipă de trei oameni, doi oameni fac code review. Acesta se face pe logica a ceea ce citești. Tot programatorul este responsabil să își testeze funcțional codul. Avem pipeline-uri diferite pentru build.
Cum faceți update la librării? Mereu există o problemă cu versionarea?
Daniel Costea: Este o bună practică să îți faci update-urile când apar, dar nu vineri după-masa la ora 5, deoarece atunci ajungem în iadul programatorilor și se coboară pe tine toate blestemele. Momentul oportun pentru update-uri este la început de sprint. Pentru un control strict al release-urilor, folosim GitFlow care sunt niște meta-comenzi peste comenzile de Git și respectăm anumite practici ca să facem deployment. Bugurile le fixăm pe release, iar hotfixes pe production branch.
Alin Treznai: Unde putem, să folosim tooluri deja existente. De exemplu, GitHub are Dependabot care îți scanează toate dependințele și face upgrade când îi zici tu.
Cum generați documentația în cod? Folosiți API docs, Wiki sau rămâneți la comentarii în cod?
Alin Treznai: Toate modalitățile sunt complementare. Cele mai importante aspecte ale proiectului ar trebui explicate în fișierul ReadMe al proiectului. Când vine un programator nou, poate afla din ReadMe cum face build, cum își setează environmentul. Pentru lucruri ce țin de business/produs, ajută Wiki, iar pentru documentație, folosești un JavaDoc și le generezi direct. Pentru API documentation se poate folosi Swagger.
Daniel Costea: La noi există un standard care zice că nu punem nimic, nici măcar în testare, fără să aibă partea de Swagger populată cu descrieri și cu documentarea tuturor tipurilor de răspuns. Mai menținem și niște documente prin Confluence, dar, ca de obicei, acolo nu sunt ultimele modificări, iar acestea vin cu întârziere. De aceea, Swagger e baza. E chiar un standard internațional. Orice proiect faci în .NET 5 are Swagger inclus.
Cum formatați codul?
Daniel Costea: Un programator aflat la început de drum trebuie să studieze convențiile limbajelor cu care lucrează. Peste acele convenții, vin excepțiile culturii organizaționale. E greu de urmărit un cod care nu respectă convențiile. Dacă ai un ReSharper care te trage de mânecă la orice bucățică de cod e și mai bine. Visual Studio are partea sa de Clean Code, adică poți să chemi dintr-un shortcut sugestii suplimentare de îmbunătățire a codului.
Alin Treznai: Eu propun folosirea de tooluri care formatează codul automat. Limbaje precum Go sau Rust vin cu tooluri de formatare automată. Fiecare limbaj adaptează un design pattern. Design pattern îți prezintă în linii mari cum arată abordarea, dar unele tipare îți arată și care sunt unele deficiențe de limbaj. Nu țin la folosirea anumitor patterns. Momentan, lucrez cu Python. Anterior, am lucrat cu Scala, Java, C++, C#.
Daniel Costea: La începuturi, am lucrat cu Pascal și Delphi. Cât despre design patterns, nu ai cum să eviți în .NET 5 un Abstract Factory, multe integrate în limbaj și pe care le faci în partea de business layer. În afară de asta, un pattern destul de mult neglijat ar fi Null Object pattern, fiindcă ArgumentNullException este de departe cea mai răspândită excepție. Când s-a inventat valoarea Null pentru un câmp în care nu ai fost pregătit să introduci date, a apărut și denumirea de "eroare de 1 miliard de dolari". În .NET, în variantele de C# mai noi există posibilitatea de a da enable la null check și atunci nu mai poți avea variabile nullable neinițializate.
Avem și o întrebare din public. Ce părere aveți de prezența interfețelor la nivel de modele?
Daniel Costea: Eu am predat vreo patru ani, la Școala Informală, la începători. Este foarte greu să predai cuiva care nu are background de ce este nevoie de interfețe. Mi-am dat seama că cel mai util lucru cu o interfață este să faci disciplină în cod. Dacă ai un junior și îi spui să facă o metodă sau o clasă, de unde știi că respectă ce intră și ce iese? Atunci, creezi control creând o interfață și spunând "moștenește din interfața asta". Lucrurile vor arăta mult mai bine, deoarece va trebui să facă ce spune interfața. Interfețele pentru modele nu sunt necesare, dar, dacă vrei să te asiguri că toate vor arăta la fel, o să prezinte aceleași proprietăți și aceleași metode, atunci cu o interfață te-ai asigurat total. În ceea ce privește data models, o consider utilă doar dacă aș vrea niște clase sau logică de business unde să injectez niște dependințe.
Alin Treznai: Sunt de acord cu Daniel.
Ați folosit tooluri ca să determinați technical debt, iar dacă da, cât de mult v-a influențat?
Alin Treznai: Pentru Java, SonarQube e numărul 1. Dacă la fiecare commit ar rula SonarQube pentru a vedea dacă crește sau scade tech debt te ajută. Evident, este nevoie să îți folosești filtrul gândirii și să îți notezi dacă unele probleme sunt reale sau false.
Daniel Costea: Și eu am folosit acest tool și țin minte că primeam zilnic rapoarte. SonarQube, împreună cu niște loguri bine scrise care să captureze contextul și stiva de apel este tot ce trebuie pentru a face debug offline.
Cât de mult mai folosiți bazele de date relaționale față de NoSQL?
Daniel Costea: Nu au dispărut. Se completează. Pentru situațiile în care schema datelor nu se schimbă, bazele de date relaționale sunt cele mai potrivite. Dacă ai o schemă dinamică, atunci NoSQL este ceea ce trebuie ales. De acolo începe ramificația. Dacă vrei să faci graphuri între date nu poți face acest lucru în baze de date relaționale. Nici ierarhiile de date nu sunt potrivite. Poți alege un Neo4j care e folosit pentru a lega noduri de date. Elasticsearch e super performant la citire, dar are și o schemă dinamică unde poți defini proprietăți în mod dinamic și unde te poți abate de la schemă. La agenția pentru care lucrez folosim Elasticsearch, deoarece face față la un număr mare de date și scalează foarte bine prin modificări aduse configurărilor.
Alin Treznai: De acord cu Daniel. Doresc să adaug că modul de accesare a datelor determină care este soluția cea mai bună. Volumul de date este și el important. Arhitectura sistemului este de o luat, de asemenea, în considerare. La un proiect la care am lucrat, veneau zilnic necomprimate 120GB de date. Inițial, am lucrat cu Cassandra, iar când Cassandra nu a mai făcut față, am migrat pe Google BigQuery, ceea ce s-a dovedit a fi o soluție inspirată.
Într-o lume în care producem foarte repede și vrem ca totul să fie foarte bine realizat, care sunt sfaturile voastre pentru programatori?
Alin Treznai: Să fixăm iterativ, în stadiu incipient. Începătorii ar trebui să citească cât mai mult despre Technical Debt și Clean Code.
Daniel Costea: Să țină o agendă cu lucrurile pe care vor să le studieze în viitor și să le prioritizeze. De exemplu, eu, știind că am mult de lucru în viitorul apropiat pe partea de DevOps, mi-am căutat niște titluri și stau în așteptare. Sunt pasionat și de Machine Learning, deci am și acolo niște lucruri în așteptare. Consum cât mai mult din listă în ordinea priorităților. Dacă nu citesc, pățesc ce am mai pățit acum vreo zece ani când am renunțat la a mă mai conecta la nou și, din păcate, nu mi-am dat seama. Atunci am primit un duș rece de la cineva din echipă și de atunci nu m-am oprit. Am un sfat mare și un sfat mic pentru programatori. Sfatul mare este de a scrie cod testabil, chiar dacă nu scrieți Unit Tests. Când vei dori să injectezi dependințe, va trebui să te pui la punct cu principiile SOLID, iar ca să te pui la punct cu acestea trebuie să știi OOP pe care nu îl poți stăpâni dacă nu stăpânești limbajul. Sfatul mic este de a scrie cod curat ușor de citit și înțeles. Să citiți și să lucrați cu muzică ambientală.