Laborator ​10 - Static & Dynamic Checking ​

1. Objectives ​

Verificarea automată a calității codului reprezintă unul dintre primii pași în garantarea corectitudinii codului. Așa cum ne putem imagina, un program formatat inconsistent și cu greșeli frecvente de structură e predispus la a avea probleme ulterioare, chit că inițial el e funcțional. De asemenea, mai ales în proiectele open-source, ne dorim ca programele noastre să fie cât mai prezentabile pentru a atrage contribuitori și utilizatori, cât și pentru a face contribuțiile mai consistente. Dacă fiecare contribuitor ar avea un stil de formatare al codului diferit, s-ar ajunge garantat la neînțelegeri.

Aceste idei sunt întărite când este vorba de limbaje interpretate precum Python. După cum știm, în Python tipul variabilelor este determinat la rulare, deci nu putem ști cu siguranță că avem un cod funcțional, până când nu rulăm cu succes pe toate căile posibile de execuție. Totodată, codul Python este bazat pe indentare și o formatare cât de cât corectă, deci fără să respectăm un minim de formatare, este posibil ca interpretorul de Python să refuze sa ne ruleze programul cu totul. Deoarece Python este mai lax în scriere, trebuie să folosim folosim aplicații terțe pentru a ne formata codul, comparat cu limbaje mult mai stricte ca Rust.

Dacă combinăm toate aceste idei cu o formatare automată și folosirea sistemelor de CI/CD, precum în laboratoarele precedente, putem să ne asigurăm că în proiectul nostru nu pătrunde cod de o calitate inferioară celei dorite.

1.1 Linters ​

Linterele, sau uneltele de verificare a calității codului, reprezintă prima metodă de verificare a codului. De multe ori acestea sunt integrate în editorul folosit de developeri, în așa fel încât codul este evidențiat, sau chiar formatat automat la fiecare salvare a fișierului.

Deoarece procesul de linting poate semnala multe probleme, este recomandat să fie integrat în dezvoltarea unui program cât mai devreme, pentru a evita schimbări fundamentale ale codului ulterior. Dificultatea principală în procesul de linting vine din stabilirea unui standard de formatare a codului care să corespundă cu așteptările tuturor contribuitorilor. În cazul nostru, configurările de bază sunt mai mult decât suficiente.

1.2 Static Checking ​

Static checkerele, sau unelte de verificare statică a codului, reprezintă o verificare mai amănunțită a codului. Acestea parcurg codul și încearcă să îl înțeleagă în amănunt pentru a deduce folosiri eronate ale anumitor funcții și/sau cod. Un bun exemplu de astfel de verificări sunt cele de securitate. Prin anumite static checkere putem descoperi automat parole, tokene, și alte date de autentificare uitate în cod, înainte de a face public codul și a ne compromite. Totodată, vom vedea că există și unelte mai amănunțite, ce realizează o detecție automată a tipurilor fiecărei variabile, pentru a ne garanta că nu există erori de runtime generate de acestea.

Static checkerele reprezintă, pe scurt, următorul pas al verificării codului de către developer, înainte de rulare și de publicarea efectivă a codului.

1.3 Dynamic Checking ​

Diferentța majoră între acest checking și cel anterior este timpul în care se fac verificările. Pentru Static Checking, uneltele principale de verificare sunt rulate înainte de a rula codul sursă. În cazul Dynamic Checking, sunt rulate odată cu codul sursă. De aceea, și din laboratoarele trecute, putem identifica câteva forme de Dynamic Checking, precum: Testarea Unitară, Testarea de Integrare, Fuzz Testing, etc..

Verificările dinamice rulează efectiv codul și observă comportamentul la execuție. Ele surprind erori de logică, condiții de margine și probleme de integrare care scapă analizei statice.

1.4 Static vs Dynamic

Caracteristică Static Checking Dynamic Testing
Momentul analizei Înainte de execuție În timpul rulării testelor sau în mediul de dev
Detectează Erori de stil, tipuri, vulnerabilități de securitate Erori de logică, edge cases, probleme de integrare
Exemplu de problemă String concatenat cu int, importuri inutile Funcție care aruncă excepție pentru input neprevăzut
Viteză Rapid (nu rulează codul) Mai lent (rulează efectiv cod)
Acoperire Nu poate evalua toate căile de execuție Poate acoperi doar căile pentru care există teste
Cost de scriere dat de Configurare inițială și tunare a regulilor Scriere de teste și date de testare

2. Exercises ​

În exercițiile din laborator vom explora cinci unelte de linting și static checking. Pentru fiecare va trebui să o rulați și să rezolvați (pe cât se poate) problemele semnalate. Vom folosi un repository open source pentru a rula exercițiile. Din acesta va trebui să alegeți 1-2 mini-proiecte pe care să rulați uneltele și să rezolvați problemele.

Dacă proiectul ales nu mai are destule probleme pentru a fi util, sau ați rezolvat deja toate problemele, puteți încerca altul.

2.0 Environment Setup

1. Instalăm pip dacă lipsește: https://pip.pypa.io/en/stable/installation

	$ python3 -m ensurepip --upgrade

2. Pentru început va trebui să instalăm tool-urile ce le vom folosi:

	$ pip3 install pylint ruff black bandit pytype
	$ pip3 install pytest typeguard coverage hypothesis atheris

3. Apoi clonăm repository-ul peste care o sa rulăm uneltele:

	$ git clone https://github.com/Ingineria-Calculatoarelor-ACS-UPB/python-mini-project.git

Rulați comenzile din timp, eventual la început de laborator, deoarece instalarea poate să dureze.

2.1 Pylint - Basic Checks

Pylint este unul dintre cele mai cunoscute unelte de linting. Acesta oferă informații simple, cât și avansate în legătură cu codul pe care îl analizează, și apoi generează un raport cu toate erorile și acordă o notă acestuia. Acesta are și integrare cu VSCode, precum și alte editoare, ce ușurează folosirea lui.

Observăm că fiecare linie din raport menționează un cod de verificare, prin care e identificată fiecare problemă. Putem avea probleme de tip error, warning, comment, etc.

 1. Rulați Pylint și observați problemele semnalate de acesta.
 2. Rezolvați problemele semnalate pentru proiectul ales.

2.2 Ruff - Advanced Checks

De cele mai multe ori, un singur linter nu poate prinde toate erorile ce le avem în cod, de aceea suntem nevoiți să folosim mai multe. Ruff este o suită de verificatoare de cod ce este des folosită în proiectele mari. Aceasta ne poate ajuta să descoperim probleme ce au fost ratate de Pylint. De asemenea, înglobează verificări de la diferite alte unelte.

 1. Rulați Ruff și observați problemele semnalate de acesta.
 2. Rezolvați problemele semnalate pentru proiectul ales.

2.3 Black - Automatic Checks

Am observat că este destul de obositor să facem atatea modificări și timpul consumat pe formatarea codului ar fi putut să fie investit în adăugarea de funcționalități noi. Black vine cu o paradigmă nouă, cea de cedare a controlului asupra linting-ului către linter. Pe lângă semnalarea problemelor, acesta le și rezolvă după un standard propus de dezvoltatori. Desigur, noi putem să rulăm fără a aplica modificările.

 1. Rulați Black în mod de diff color precizând argumentele --diff și --color la rulare și observați problemele.
 2. Rulați Black fără argumente adiționale pentru a aplica schimbările.
 3. Rezolvați problemele rămase semnalate pentru proiectul ales (dacă mai există).

2.4 Bandit - Security Checks

Bandit este o unealtă de static checking concentrată pe partea de securitate. Aceasta se asigură că programul nostru nu poate fi exploatat de alte persoane și că este sigur în timpul folosirii. Pe lângă verificarea existenței unor parole/tokene uitate, acesta analizează și cod ce ar putea fi exploatat în anumite circumstanțe.

În cazul de față, avem o singură problemă, aceea că descărcăm o pagină fără a menționa un timp de oprire. Un utilizator malițios poate să apeleze funcția cu un url invalid și să blocheze execuția programului.

 1. Rulați Bandit și observați problemele.
 2. Rezolvați problemele semnalate pentru proiectul ales.

2.5 Pytype - Type Checks

Pytype este un tool de static checking mai avansat față de cele precedente. Se folosește de un engine de analiză statică pentru a garanta corectitudinea tipurilor variabilelor din codul nostru. Acesta ideal este folosit în timpul dezvoltării programului, sau atunci când avem bug-uri. Este foarte probabil ca acesta să nu întoarcă nimic, în cazul proiectelor analizate în cadrul laboratorului, deoarece acestea sunt deja funcționale.

Fără a rula codul, acesta a analizat operațiile realizate și a detectat adunarea unui întreg cu un șir de caractere. În Python astfel de problemă ar fi apărut doar la rulare, și dacă nu am testat acel caz posibil să treacă nedetectată și să ajungă în mediu de producție.

 1. Rulați Pytype și observați problemele.
 2. Rezolvați problemele semnalate pentru proiectul ales (dacă există).

2.6 Coverage Reports

Coverage este un tool testare al acoperirii testelor unitare și de integrare în cadrul codului vostru. De aceea, pentru a rezolva acest exercițiu, recomandăm reamintirea conceptelor de Teste Unitare. Printr-un raport ușor de descifrat, vă arată destul de clar unde sunt lipsuri și ce puteți îmbunătăți.

Rulând comanda:

$ coverage run -m pytest
$ coverage report

pe orice proiect în Pyhton care deține o suită de teste unitare sau de integrare, veți putea obține raportul care poate fi deschis și accesând fișierele noi generate.

 1. Rulați Coverage și observați problemele.
 2. Încercați să creșteți test coverage-ul prin mai multe teste unitare.

2.7 Typeguard Assertions

Typeguard Assertions este un tip de verificare al tipurilor odată cu rularea codului sursă. Este un tip de dynamic checking ce ajută la observarea problemelor de tip ce nu pot fi identificate fără a interpreta codul.

În fișierul vostru Python, importați și aplicați decoratorul @typechecked pe funcțiile critice:

from typeguard import typechecked
 
@typechecked
def process_data(data: dict, limit: int) -> list:
    assert limit > 0, "Limit trebuie să fie pozitiv"
    # … restul implementării …
    return result_list

Apoi rulați:

$ pytest --typeguard-packages=.
 1. Rulați Typeguard și observați problemele.
 2. Rezolvați problemele semnalate pentru proiectul ales (dacă există).

3.0 CI Pipeline

Creați un fișier python-checks.yaml în calea .github/workflows și completați conform specificației următoare:

  • Verificarea trebuie să se realizeze pe tot codul.
  • Verificarea trebuie sa pornească atunci când este deschis un nou Pull Request.
  • Workflow-ul trebuie să aibă 5 pași, corespunzător fiecărei unelte: Pylint, Ruff, Black, Bandit, Pytype. (Black poate sa aplice automat modificările).
  • Workflow-ul trebuie să eșueze atunci când oricare dintre cele 5 tool-uri a detectat erori, dar să își continue execuția.
  • Workflow-ul este complet doar atunci când niciun tool nu a semnalat erori.

Aveți grijă să rulați uneltele în mod neinteractiv (dacă este disponibil), și să verificați codurile de eroare întoarse de acestea.

3.1 Bonus

Încercați să rulați workflow-ul pe un fork al proiectului actual.

4. Resources

tsc/laboratoare/laborator-10.txt · Last modified: 2025/05/17 13:33 by andrei.napruiu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0