Transcriere
Hai să vedem ce reguli de Clean Code putem aplica în metode. Se spune că orice metodă e indicat să aibă cel mult trei niveluri de imbricare, adică un for în if în while și pe aici mă opresc, nu am voie să am mai mult de trei niveluri de imbricare. Și asta poartă numele de arrow code pentru că pe măsură ce merg cu acoladele și apoi mă întorc cu ele să le închid se formează o săgeată. Acea săgeată nu e o ok să fie foarte adâncă, foarte ascuțită pentru că asta înseamnă că încalc principiul de care sper că vă amintiți și anume că pot reține doar 7 niveluri. Trei niveluri imbricate înseamnă că mă duc trei niveluri, mă duc spre vârful săgeții 3 niveluri și mă întorc spre baza săgeții încă trei, adică am deja 6 lucruri de reținut. Dacă merg mai mult de aceste 6 chestii deja ajung la 7 chestii de reținut și trec de capacitate umana de a reține, trebuie să dau scroll să văd ce naiba se întâmpla în primul for sau în primul if.
O altă regulă de clean code pe care și eu am învățat-o de curând și e foarte utilă (mai degrabă m-a forțat ReSharper-ul să o folosesc), ieșirea cât mai rapidă din funcție. Adică, de ce să validez, să fac if(userValid), scriu cod pe acolo și apoi else throw exception? De ce să nu fac if(!userValid) throw exception, adică primele linii dintr-o funcție întotdeauna trebuie să fie return-uri de la validări sau aruncări de excepții. Adică primele linii în funcții fac validările (sau în metodă). Validez, nu e chestia asta valabilă, arunc excepție, nu e chestia asta valabilă, returnez null, returnez 0, returnez ce trebuie să returneze funcția respectivă. Și ca să dau un exemplu pe funcția mea, respectiv, metoda aia statică calculează care de fapt era suma elementelor unui ArrayList și trebuia să verific, e ArrayList-ul null? Da. Atunci arunc excepție de null. Am elemente in el, arunc excepție că e gol. Nu știu, mă trezesc că elementele din el nu sunt întregi arunc excepție că nu am element întreg. Nu fac la început if toate-s ok, fac prelucrări și apoi în else văd ce-a ieșit.
Apoi, încercați pe cât posibil să declarați variabile, în mediile în care variabilele se declară într-adevăr, cât mai aproape de utilizarea lor, mai ales variabilele locale. Adică am un metodă în care folosesc o variabilă, ar fi ok să o declar exact deasupra ultimei folosiri, adică să nu o declar ca și constantele, deasupra pe toate pentru că: 1. programatorul uită, spuneam că reținem șapte lucruri simultan, 2. îmi e destul de greu să fac scroll până sus să văd cu ce naiba inițializam variabila aia, dau scroll uit unde am rămas, dau scroll, caut iar ce căutam, găsesc în sfârșit, uit ce era în variabilă. Mie chiar mi s-a întâmplat chestia asta și e destul de enervant.
Ok, apoi ca și regulă pentru programele puternic orientate obiect, bine e și cazul C++. Încercați pe cât posibil folosirea this și stabilirea unei convenții de nume pentru parametrii constructorului. Și aici vorbesc despre studenții mei de la Programare Orientată Obiect care, tot timpul, nu iși dau seama că dacă dau constructorului cu parametri, dacă dau parametrilor aceleași nume ca și atributele din clasă vor face inițializare tot pe parametri. De exemplu am clasa Student care are un întreg matricol, un float medie și un string nume. În constructorul cu parametri primesc trei parametri numiți exact așa, adică int matricol, float medie, string nume și dacă fac apoi nume=nume, matricol=matricol și medie=medie o să mă trezesc că în atributele din clasa am exact nimic. De ce? Pentru că am spus întotdeauna în C#, în Java, în C++ local scope bate global scope, adică variabile locale au întotdeauna prioritate în fața variabilelor globale. Variabila locală e parametrul, va avea prioritate în fața variabilei globale care e atributul și o să fac numele din parametru egal cu el însuși. Ca să evit chestia asta scriu this săgeată sau punct, în funcție de limbaj, egal cu nume și am evitat chestia asta. ReSharper-ul folosește o altă abordare și anume parametrilor constructorului le pune un underscore în față. Adică folosește _matricol, _medie, _nume ca să evite problemele.
O altă regulă de clean code în metode: să evităm metodele cu mai mult de doi parametri. De ce? Sunt dificil de reținut și nu prea avem nevoie. De obicei trebuie să folosesc tipuri încapsulate ca și parametri în metode, vedem puțin mai târziu detalii. Apoi trebuie să evit metodele foarte lungi care au mai mult de 20 de linii de cod. De ce? Pentru că sunt dificil de reținut și ies din ecran. Adică pentru mai mult de 20 de linii de cod de obicei trebui să dau scroll. Când dau scroll înseamnă că am uitat ceea ce scria mai sus. Adică dacă stau suficient timp în partea de jos a scroll-ului o să uit se scria în partea de sus. Mai există și un principiu care spune că ar trebui ca complexitatea unei metode să fie invers proporțională cu numărul de linii de cod. Adică e metoda foarte complicată, foarte complexă, trebuie să aibă un număr scăzut de linii de cod. Într-adevăr în acest număr scăzut pot să am și apel de alte metode, dar un număr mic de linii de cod. E metoda destul simplă, am voie să am 20 de linii de cod sau aproape 20 de linii de cod.
Mare atenție în C# și Java la ordinea în care tratați excepțiile. Adică dacă pun o excepție mai generală deasupra unei excepții mai particulare, în excepția particulară nu o să intre niciodată. De exemplu dacă pun catch(Exception e) deasupra lui catch(NullException e), chiar dacă am NullException, chiar dacă am orice altă excepție, va fi prinsă de primul catch. În al doilea catch nu o să ajung niciodată. De ce? Pentru că Exception e clasă superioară în ierarhie lui NullException. Adică NullException e derivată din Exception. Așa că Exception va prinde toate erorile și le va ignora pe restul. Deci catch-ul general îl punem la sfârșit.
Legat de regulile de clean code în clase: toate metodele dintr-o clasă trebuie să aibă legătură cu acea clasă. Și aici oarecum revenim la Single Responsibility Principle și anume o clasă trebuie să facă prelucrări doar pentru un anumite lucru și toate metodele ei și toate atributele trebuie să aibă legătură cu acel lucru. Trebuie să evităm folosirea claselor generale și să mutăm prelucrările respective ca metode statice în clasele aferente. De ce? Așa cum spuneam, clasele generale sunt un magnet pentru programatorii leneși. O să ne trezim acolo cu tot felul de chestii de la alți programator care nu au nicio treabă cu ce am gândit noi. De exemplu am o clasă genică în care fac sortari, poate ar fi mai bine ca acele sortări să le pun ca și metode statice în clasele respective. De exemplu am o funcție care sortează clienți, ar fi ok să fie funcție statică în clasa Client.
Apoi sunt de evitat primitivele ca parametri și sunt preferate oricând obiectele ca tipuri încapsulate. De exemplu, vreau să adaug într-o listă un nou client, nu am de ce ca la metodă adaugaClient să îi dau ca parametru id-ul clientului, numele, adresa, mai știu eu ce alte prostii. O să dau o instanță de Client, un client ca și parametru, un obiect încapsulat. Asta e și ideea Programării Orientate Obiect să nu lucrez cu primitive. Și mare atenție la primitive pentru că, cel puțin în C# și Java, ele nu sunt thread safe. Adică nu sunt sigure pentru lucrul cu mai multe fire de execuție, de asta se recomandă clasele wrapper. Clasele Wrapper sunt clase, nu primitive, ce au aceeași funcționalitate ca și primitivele. Și de exemplu pentru int în Java avem Integer, pentru boolean avem Boolean, și așa mai departe. În C# e o abordare puțin diferită și anume tipurile de bază sunt structuri, sunt tipuri valorice, iar clasele sunt timpuri referențiale, clasele și interfețele (mai precis). Și aici e de discutat legat de tipurile valorice și anume de obicei trebuie să le înlocuim și pe ele cu clasele Wrapper corespunzătoare.
Apoi, e recomandat să folosim fișiere de resurse pentru stringurile sau șirurile de caractere din interfața utilizator. De ce? 1. Pentru că nu voi risca să scriu greșit, să mănânc litere când scriu. 2. Am Intellisense, am autocompletarea, ele fiind salvate într-un singur fișier de resurse. 3. Pot face aplicația foarte ușor disponibilă în mai multe limbi, schimb doar fișierul de resurse și atât. 4. Nu dau copy-paste. Conform principiului DRY nu trebuie să ne repetăm, nu trebuie să dăm copy-paste, e o practică urâtă.
Următoarea regulă e destul de greu de implementat în C# și în Java, dar e de preferat ca acele clase ce conlucrează să fie așezate una lângă alta. Spun că e destul de greu pentru că de obicei în C# și în Java se preferă ca o clasă să fie într-un singur fișier.
Atunci când identificați o situație de tipul de design patterns, folosiți design patterns. Ce sunt Design Patterns? Sunt niște specificații despre cum ar trebui scrisă o clasă ca să implementeze un anumit comportament. Uite, un design pattern este Singleton. Singleton înseamnă că acea clasă va da o singură instanță de obiect. De exemplu, conexiunea la o bază de date. Nu am nevoie să deschid conexiunea în cadrul unei sesiuni de lucru de mai multe ori. De fiecare dată când un obiect vrea conexiunea la baza de date, îi dau aceeași instanță deschisă odată. Deci clasa asta Singleton va face astfel: dacă nu există încă o instanță, o creează, dacă există o dă pe cea deja creată. Deci tot timpul lucrează cu o singură instanță. Mai există totodată Factory. Factory e o clasă ce are înglobată o metodă ce produce obiecte de tipul respectiv. Deci e o fabrică de obiecte, de aici și denumirea. Mai există Iterator ca Design Pattern pe care poate l-ați întâlnit în C++ la STL, care reține un pointer la un obiect și are metode de tipul next, previous și așa mai departe. Ok, deci folosim Design Patterns atunci când identificăm o situație corespunzătoare lor.
0 comentarii