Toate ghidurile

Cod curat: principiile care chiar contează

Nume clare, funcții mici, gestionarea erorilor, DRY, YAGNI și SOLID, cu exemple înainte și după, pentru cod ușor de citit și schimbat.


Codul se scrie o dată, dar se citește de zeci de ori: de colegi, de reviewer-i și de tine, peste șase luni, când nu-ți mai amintești nimic din el. „Cod curat” nu înseamnă cod perfect sau plin de design patterns, ci cod ușor de înțeles și ușor de schimbat. Ghidul acesta adună principiile cu cel mai mare impact în practică, cu exemple de tip „înainte și după”.

Ce înseamnă, de fapt, cod curat

Termenul a fost popularizat de Robert C. Martin prin cartea Clean Code (2008), dar ideea e mai veche și mai simplă: codul e curat atunci când un coleg nou îl poate înțelege și modifica fără să te întrebe pe tine și fără teama că strică altceva.

În practică, asta se traduce în câteva calități concrete:

  • Lizibil: îl citești de sus în jos și înțelegi intenția fără să sari prin zece fișiere.
  • Previzibil: funcțiile fac ce spune numele lor și nimic în plus.
  • Testabil: poți verifica fiecare bucată separat.
  • Ușor de schimbat: o cerință nouă atinge puține locuri.

Nu toate recomandările din cărțile clasice sunt acceptate universal. Unele, cum ar fi spargerea codului în funcții de câte trei rânduri, sunt contestate pe bună dreptate. Scopul nu e să respecți reguli, ci să reduci efortul celui care citește codul.

În proiectele reale, codul se scrie de regulă în engleză. Exemplele de mai jos folosesc nume în română doar pentru claritate.

Nume care își explică intenția

Numele bune sunt cea mai ieftină formă de documentație. Compară:

// Înainte
const d = 86400000;
function proc(l) {
  return l.filter((x) => Date.now() - x.t < d * 30);
}

// După
const MS_PE_ZI = 24 * 60 * 60 * 1000;
const ZILE_PERIOADA_ACTIVA = 30;

function utilizatoriActivi(utilizatori) {
  const limita = Date.now() - ZILE_PERIOADA_ACTIVA * MS_PE_ZI;
  return utilizatori.filter((u) => u.ultimaAutentificare > limita);
}

A doua variantă nu are nevoie de niciun comentariu. Câteva reguli utile:

  • Variabilele sunt substantive, funcțiile sunt verbe: factura, calculeazaTotal, trimiteFactura.
  • Valorile booleene sună a întrebare: esteActiv, areAcces (în engleză, isActive, hasAccess).
  • Fără numere magice: 30 sau 86400000 devin constante cu nume.
  • Fără abrevieri obscure: usrMgr nu economisește nimic, doar obligă cititorul să decodeze.
  • Vocabular consecvent: dacă într-un loc folosești fetch, nu folosi get și retrieve pentru același concept în altă parte.
  • Lungimea numelui crește cu aria de vizibilitate: i e perfect într-o buclă de trei rânduri, dar o variabilă folosită în tot modulul merită un nume complet.

Funcții mici, cu o singură responsabilitate

O funcție care validează, calculează, salvează și trimite emailuri e greu de citit, greu de testat și se schimbă din patru motive diferite.

# Înainte: o singură funcție face patru lucruri
def proceseaza_comanda(comanda):
    if not comanda["produse"]:
        raise ValueError("Comanda nu are produse")
    total = 0
    for p in comanda["produse"]:
        total += p["pret"] * p["cantitate"]
    if comanda.get("cupon") == "REDUCERE10":
        total *= 0.9
    db.execute(
        "INSERT INTO comenzi (client_id, total) VALUES (?, ?)",
        (comanda["client_id"], total),
    )
    email.trimite(comanda["email"], f"Comanda ta: {total} lei")
# După: funcția principală citește ca un rezumat
def proceseaza_comanda(comanda):
    valideaza(comanda)
    total = calculeaza_total(comanda["produse"], comanda.get("cupon"))
    salveaza_comanda(comanda["client_id"], total)
    trimite_confirmare(comanda["email"], total)

Funcția de sus spune ce se întâmplă; detaliile despre cum stau un nivel mai jos. Fiecare bucată poate fi testată separat, iar logica de calcul poate fi refolosită, de exemplu în coșul de cumpărături.

Câteva semnale că o funcție face prea mult:

  • Ai nevoie de cuvântul „și” ca să-i descrii scopul.
  • Are mai multe niveluri de indentare imbricate.
  • Primește mai mult de trei-patru parametri. Grupează-i într-un obiect cu nume clar.
  • Primește un parametru boolean care îi schimbă complet comportamentul, de exemplu exporta(date, true). Ce înseamnă true? Două funcții separate sau o opțiune cu nume ({ format: "csv" }) sunt mai clare.

Returnează devreme

Condițiile imbricate împing logica principală spre dreapta și te obligă să ții minte toate ramurile. Clauzele de gardă (guard clauses) tratează cazurile speciale la început și lasă drumul principal liber:

// Înainte
function pretLivrare(comanda) {
  if (comanda) {
    if (comanda.produse.length > 0) {
      if (comanda.total < 200) {
        return 19.99;
      } else {
        return 0;
      }
    } else {
      throw new Error("Comandă goală");
    }
  } else {
    throw new Error("Comandă lipsă");
  }
}

// După
const PRAG_LIVRARE_GRATUITA = 200;
const COST_LIVRARE = 19.99;

function pretLivrare(comanda) {
  if (!comanda) throw new Error("Comandă lipsă");
  if (comanda.produse.length === 0) throw new Error("Comandă goală");

  return comanda.total >= PRAG_LIVRARE_GRATUITA ? 0 : COST_LIVRARE;
}

Fără surprize: efecte secundare ascunse

O funcție numită gasesteUtilizator nu ar trebui să scrie în baza de date, să trimită emailuri sau să modifice obiectul primit ca argument. Când numele și comportamentul nu se potrivesc, bug-urile apar exact acolo unde nu te uiți.

  • Separă comenzile de interogări: o funcție fie schimbă starea, fie întoarce o informație, nu amândouă.
  • Nu modifica argumentele primite: întoarce o valoare nouă în loc să schimbi lista sau obiectul apelantului.
  • Preferă funcțiile pure acolo unde se poate: pentru aceleași intrări, aceeași ieșire, fără efecte în exterior. Sunt cele mai ușor de testat și de înțeles.

Comentarii: de ce, nu ce

Un comentariu care repetă codul e zgomot și, în timp, devine minciună, pentru că codul se schimbă, iar comentariul rămâne. Comentariile valoroase explică de ce, adică informații care nu reies din cod:

// Rău: repetă ce se vede deja
// verifică dacă utilizatorul are cel puțin 18 ani
if (varsta >= 18) {
  // ...
}

// Bine: explică un motiv care nu reiese din cod
// Procesatorul de plăți respinge sumele cu mai mult de două zecimale,
// așa că rotunjim înainte de trimitere, nu doar la afișare.
const suma = Math.round(total * 100) / 100;

Două reguli suplimentare: șterge codul comentat, pentru că Git îl păstrează oricum în istoric, și leagă fiecare TODO de un tichet, altfel rămâne acolo pentru totdeauna.

Gestionarea erorilor

  • Nu înghiți excepțiile. Un bloc catch gol ascunde problema și o mută într-un loc unde e mult mai greu de găsit.
  • Eșuează devreme, cu mesaje clare. „Eroare” nu ajută pe nimeni; „Comanda 42 nu are adresă de livrare” ajută.
  • Validează la granițe. Datele care vin de la utilizator, din API-uri externe sau din fișiere se verifică la intrare. În interiorul sistemului poți apoi lucra cu date deja validate.
  • Respectă convențiile limbajului. În Python și Java se folosesc excepții, în Go erorile sunt valori returnate. Codul curat arată „nativ” în limbajul în care e scris.

DRY, KISS și YAGNI, cu bun-simț

  • DRY (Don't Repeat Yourself) se referă la cunoaștere, nu la text care arată la fel. Două funcții aproape identice, dar care se schimbă din motive diferite, nu sunt duplicare reală. O regulă practică des folosită: abstractizează abia la a treia repetare.
  • KISS (Keep It Simple): alege cea mai simplă soluție care rezolvă problema de azi.
  • YAGNI (You Aren't Gonna Need It): nu construi pentru cerințe ipotetice. Configurările, pluginurile și parametrii „pentru viitor” complică codul acum, iar viitorul arată de obicei altfel decât ți-l imaginai.

Sandi Metz a formulat foarte bine riscul abstractizării premature: duplicarea este mult mai ieftină decât abstracțiunea greșită. O abstracțiune greșită, odată folosită în zece locuri, e mult mai greu de desfăcut decât zece bucăți de cod similar.

SOLID pe scurt

SOLID e un set de cinci principii de design orientat pe obiecte. Sunt utile ca semnale de alarmă, nu ca reguli de aplicat mecanic.

PrincipiuPe scurtSemnal că e încălcat
Single ResponsibilityUn modul are un singur motiv să se schimbeO clasă ManagerUtilizatori care face autentificare, emailuri și rapoarte
Open/ClosedExtinzi comportamentul fără să modifici codul existentUn switch uriaș pe tip, editat la fiecare tip nou
Liskov SubstitutionO subclasă poate înlocui clasa de bază fără surprizeO subclasă care aruncă „not implemented” pentru metode moștenite
Interface SegregationInterfețe mici și specificeClase care implementează metode goale doar ca să satisfacă o interfață
Dependency InversionLogica de business depinde de abstracții, nu de detaliiLogica de business creează direct conexiunea la baza de date

Lasă tool-urile să se ocupe de formatare

Discuțiile despre tab-uri, ghilimele sau lungimea liniilor nu au ce căuta în code review. Le rezolvi o singură dată, cu un formatter și un linter configurate la nivel de proiect:

LimbajFormatterLinter
JavaScript / TypeScriptPrettier sau BiomeESLint sau Biome
PythonBlack sau RuffRuff
Gogofmtgo vet
RustrustfmtClippy

Rulează-le automat la salvare în editor, într-un pre-commit hook și în pipeline-ul de CI, ca nimeni să nu poată introduce cod neformatat.

Refactorizare în pași mici

Codul nu devine curat dintr-odată, ci prin îmbunătățiri mici și constante. Regula cercetașului spune să lași codul puțin mai curat decât l-ai găsit: redenumești o variabilă neclară, extragi o funcție, ștergi un import nefolosit.

Pentru refactorizări sigure:

  1. Asigură-te că ai teste care acoperă comportamentul pe care îl atingi.
  2. Fă pași mici și rulează testele după fiecare.
  3. Folosește refactorizările automate din IDE (rename, extract function, inline variable); sunt mai sigure decât editarea de mână.
  4. Separă commit-urile de refactorizare de cele care schimbă comportamentul. Reviewer-ul îți va mulțumi.

Catalogul lui Martin Fowler, Refactoring, descrie zeci de astfel de transformări, cu nume și pași clari.

O listă scurtă pentru code review

Când citești codul cuiva (sau propriul cod, înainte de pull request), întreabă-te:

  • Înțeleg ce face fiecare funcție doar din nume și semnătură?
  • Există valori magice, cod duplicat sau cod mort?
  • Erorile sunt tratate explicit sau ignorate?
  • Ce s-ar strica dacă cerința s-ar schimba puțin?
  • Există teste pentru comportamentul nou?

Pașii următori

  • Ia un proiect vechi de-al tău și aplică trei transformări: redenumește variabilele neclare, extrage o funcție dintr-una lungă și înlocuiește condițiile imbricate cu clauze de gardă.
  • Configurează un formatter și un linter în proiect și rulează-le automat la commit.
  • Pentru principiile de design la scară mai mare (module, straturi, servicii), continuă cu roadmap-ul Software Architect.
  • Lipește o funcție de-a ta în tutorul AI și cere-i sugestii concrete de îmbunătățire. Apoi decide tu care dintre ele chiar fac codul mai clar.