- Stefanescu Mihai a postat in Paginare in PHP
- johhny a postat in Paginare in PHP
- Stefanescu Mihai a postat in Cum pot afisa eroarea cand utilizatorul a scris un username gresit sau o parola gresita?
- madalin a postat in Cum pot afisa eroarea cand utilizatorul a scris un username gresit sau o parola gresita?
- Stefanescu Mihai a postat in Featureuri site
Complexitatea cognitiva si complexitatea cyclomatica
Atat timp cat scriem cod in mod profesional, metricile sunt foarte importante pentru a determina daca scriem cod de calitate ce este usor de testat, inteles si intretinut pe termen lung. Cum fiecare dezvoltator are stilul lui de a scrie devine important sa setam un standard dupa care sa masuram daca acel cod este sau nu de calitate. Doua dintre cele mai relevante metrici pe care ar trebui sa le avem in calcul sunt complexitatea cognitiva si cyclomatica.
Complexitatea Cyclomatica si fratele ei mai mic, Complexitatea Cognitiva
Conceptul de complexitate cognitiva a fost adus de SonarQube. Ai vrut sa introduca un mod de masurare a complexitati codului mai contextual. Stiu, exista mai multe surse din care puteti citi despre complexitatea cognitiva, inclusiv acest whitepaper de la sonar. In schimb, in acest articol am sa incerc sa povestesc cat mai simplu ca sa fie cat se poate de usor de inteles.
Complexitatea Cyclomatica
Aceasta metrica masoara cat de greu este de testat codul. Mai exact, calculeaza numarul de test case-uri distincte de care ai nevoie pentru a avea acoperire de 100% in testele unitare.
Complexitatea Cognitiva
Masoara cat de greu este de citit si inteles codul.
Hai sa incerc sa explic cu o bucata de cod:
switch ($input) { case 1: // rulam o anumita logica de business break; default: // rulam alta logica de business break; }
Complexitatea cyclomatica a acestui cod este de 2. Ajungem la aceasta concluzie pentru ca ai 2 testcase-uri, cate unul pentru fiecare case din switch. Desi acest exemplu este simplist, extrapoland putem intelege ca impreuna cu restul codului din functia in care este acest switch si codul din case-uri acest rezultat poate creste in mod liniar.
Am sa las mai jos un tabel cu un guideline al complexitatilor si riscurilor modificarii codului ulterior:
Scor | Complexitate Cyclomatica | Risk |
de la 1 la 10 | Simpla | Fara riscuri |
de la 11 la 20 | Complexa | Anumite riscuri |
de la 21 la 50 | Mult prea complexa | Risc mediu, necesita atentie |
mai mult de 50 | Mult prea complexa | Prea multe cazuri de testare, risc mare |
Desi avem o complexitate cyclomatica de 2, complexitatea cognitiva pentru acest cod este de 1 (sa ne amintim ca complexitatea cognitiva indica cat de usor de inteles este codul), deci sa citesti un switch cu 2 case-uri este relativ usor de citit si inteles.
Hai sa mai luam cateva exemple care sa ne ajute sa intelegem mai bine diferenta dintre cele 2. Am sa iau ca exemplu un program simplu care va decide daca un input dat este sau nu un an bisect sau nu.
Pentru a decide daca un an este bisect sau nu trebuie sa vedem daca este divizibil cu 4, de asemenea, daca este divizibil cu 100, dar nu cu 400, iar apoi, daca este divizibil cu 400.
Testaces-urile noastre sunt urmatoarele:
An bisesc : 1980, 1996, 2000, 2400 etc.
An care nu este bisect : 1981, 1995, 1700, 1900 etc.
Prima varianta a acestui cod:
public function isLeapYear(int $year) { $response = false; if ($year >= 1000 && $year <= 9999) { // anul este divizibil cu 400, ex: 1600, 2000, etc if ($year % 400 == 0){ $response = true; // anul este divizibil cu 100, dar nu cu 400, ex: 1700, 1900, etc } elseif (($year % 100 == 0) && ($year % 400 != 0)) { $response = false; // in cele din urma, daca primele 2 conditii nu sunt indeplinite // verificam daca este divizibil cu 4 }elseif ($year % 4 == 0) { $response = true; } } else { throw new Exception("Anul data ca parametru trebuie sa fie intre 1000 si 9999"); } return $response; }
Complexitatea cyclomatica pentru aceasta functie este de 7, decide asta uitandu-se la fiecare bucata de cod subliniata cu rosu din imaginea de mai jos
In schimb, pentru complexitatea cognitiva masuratoarea se face altfel:
Pentru fiecare conditie mai creste complexitatea cu 1, daca exista o despartire a conditiei, de exemplu && si || nou complexitatea este crescuta. Daca Avem 5 conditii ce contin &&, atunci scorul va fi de 1, in schimb daca avem combinatii de && si ||, pentru fiecare dintre ele scorul va fi crescut.
Deci, ce putem face pentru a scade acest scor? Daca ne uitam mai atent la cod, observa, ca avem operatii care se repeta si in urma carora luam o decizie. Deci, daca facem o cunftie noua checkDivisibility() care sa contina aceasta logica, am putea reduce numarul de conditii.
pubic function checkDivisibility(int $year, int $dividedBy, bool $cont) { $response = false; // ne folosim de $cont pentru a determina daca conditia precedenta a fost indeplinita // si mergem mai departe doar daca a fost indeplinita if ($cont) { $remainder = $year % $dividedBy; if ($remainder == 0) { $response = true; } } return $response; }
Acum, in functia care determina daca anul este sau nu bisect putem face urmatoarele modificari:
public function isLeapYear(int $year) { $response = false; if ($year >= 1000 && $year <= 9999) { $isDivisibleByFour = $this->checkDivisibility($year, 4, true); $isDivisibleByOneHundred = $this->checkDivisibility($year, 100, $isDivisibleByFour); $isDivisibleByFourHundred = $this->checkDivisibility($year, 400, $isDivisibleByOneHundred); $response = ($isDivisibleByFour && !($isDivisibleByOneHundred)) || $isDivisibleByFourHundred; } else { throw new Exception("Anul data ca parametru trebuie sa fie intre 1000 si 9999"); } return $response; }
Acum, putem observa cum am reusit sa reducem complexitatea cyclomatica (liniile rosii) cat si pe cea cognitiva (liniile verzi)
Ba chiar mai mult, putem reduce complexitatea cyclomatica si mai mult mutand verificarea range-ului de ani (1000 - 9999) intr-o functie separata.
public function validateRange(int $year) { if ($year >= 1000 && $year <= 9999) { return true; } return false; }
iar functia care principala va deveni:
public function isLeapYear(int $year) { if (!$this->validateRange($year)) { throw new Exception("Anul data ca parametru trebuie sa fie intre 1000 si 9999"); } $isDivisibleByFour = $this->checkDivisibility($year, 4, true); $isDivisibleByOneHundred = $this->checkDivisibility($year, 100, $isDivisibleByFour); $isDivisibleByFourHundred = $this->checkDivisibility($year, 400, $isDivisibleByOneHundred); return ($isDivisibleByFour && !($isDivisibleByOneHundred)) || $isDivisibleByFourHundred; }
Comentarii
Inca nu au fost postate comentarii, fii primul care posteaza un comentariu!