Magia în programare — ce este, de ce sunt periculoase magic numbers și înlocuirea

Autor: IT Sectr Publicat: 2026-07-27 Timp de citire: 10 min

Magia în programare — nu este o metaforă, ci un termen precis care desemnează valori (numere, șiruri, flaguri) al căror sens nu este evident din context și necesită cunoștințe externe pentru a fi înțeles. Cel mai răspândit tip de magie — magic numbers: constante numerice scrise direct în cod fără explicații de ce a fost aleasă exact această valoare. Conform studiului SonarSource Code Quality Report (2025), aproximativ 8 procente din toate avertizările analizoarelor statice sunt legate de literali neexplicați. Valorile magice fac codul fragil: modificarea necesită căutarea tuturor aparițiilor, iar un nou dezvoltator nu înțelege dacă poate atinge numărul sau dacă este critic pentru funcționarea sistemului.

Principalele

  • Magia — numere, șiruri și flaguri implicite în cod, al căror sens este ascuns cititorului.
  • Magic numbers — literali numerici fără nume: 86400, 3.14, 0.85, 1024.
  • Șiruri magice — hardcodarea căilor, cheilor, URL-urilor fără extragerea în constante.
  • Instrumente de căutare: SonarQube (regula MagicNumber), ESLint (no-magic-numbers), Detekt.
  • Soluția: extrageți fiecare valoare magică într-o constantă denumită cu un nume explicativ.

Ce este magia în programare?

Magia (magic) — este orice valoare în codul sursă al cărei sens nu este evident fără cunoștințe suplimentare despre domeniu. Termenul s-a consacrat în comunitate: dacă un dezvoltator se uită la un număr și nu înțelege de unde provine — este magie.

Magia poate fi de mai multe tipuri: numerică (magic numbers), de șir (magic strings), booleană (magic flags) și de configurare (parametri hardcodați care ar trebui să fie în setări). Toate cele patru tipuri sunt unite de o problemă comună: atunci când o cerință se schimbă, dezvoltatorul trebuie să găsească toate locurile în care valoarea este utilizată și să le înlocuiască manual. Omiterea chiar și a unei singure apariții duce la o eroare.

Conform raportului JetBrains Code Quality Survey (2025), 73 la sută dintre dezvoltatori consideră magic numbers un indicator al codului de calitate scăzută, iar 41 la sută recunosc că ei înșiși le lasă ocazional. Motivul principal — graba: „Voi pune constanta mai târziu” — dar mai târziu nu vine, iar după o lună numărul 0.85 rămâne fără explicație în corpul metodei.

Regula cheie: orice valoare literală, cu excepția 0, 1, true, false și a șirului gol, trebuie extrasă într-o constantă denumită. Excepții: incrementarea contorului (i + 1), zerouri matematice (verificarea pentru 0) și valorile inițiale ale acumulatorilor. Orice altceva — este candidat pentru denumire.

Magic numbers și de ce sunt periculoase

Magic number — este un literal numeric a cărui valoare nu este evidentă din context. Exemplu clasic: 86400 în codul responsabil pentru timeout. Dezvoltatorul vede numărul și trebuie să ghicească că acesta este numărul de secunde dintr-o zi. Dacă greșește și pune 84600 — eroarea va fi greu de depistat, deoarece timeout-ul va declanșa cu 18 minute mai devreme.

De ce sunt periculoase magic numbers: în primul rând, ele afectează lizibilitatea. Numărul 1024 poate însemna dimensiunea unui kilooctet, pragul de paginare sau numărul maxim de elemente. Fără context — este doar un număr. În al doilea rând, ele creează duplicare: dacă 1024 este utilizat în cinci locuri, la schimbarea pragului la 2048, dezvoltatorul trebuie să le găsească pe toate cinci și să le înlocuiască. Dacă un loc este omis — sistemul funcționează incorect, dar fără o eroare evidentă.

Exemplu magic numbers înainte și după

kotlin
// înainte — magia în formă pură
fun calculateTimeout(base: Int): Int {
    return base * 3 + 5000
}

// după — valorile înlocuite cu constante
private const val RETRY_MULTIPLIER = 3
private const val BASE_TIMEOUT_MS = 5000

fun calculateTimeout(base: Int): Int {
    return base * RETRY_MULTIPLIER + BASE_TIMEOUT_MS
}

Al treilea pericol — imposibilitatea testării. Dacă valoarea de prag este încorporată în cod ca literal, testul nu o poate suprascrie pentru a verifica condițiile limită. O constantă extrasă în companion object sau într-un fișier de configurare face codul testabil: testul pune o altă valoare și verifică comportamentul sistemului la limită.

Dezvoltați obiceiul: de fiecare dată când scrieți un număr diferit de 0, 1, 100 sau 2 — opriți-vă și gândiți-vă dacă merită să-l extrageți într-o constantă. Dacă numărul este legat de logica de afaceri (limită, prag, timeout, dimensiune) — extrageți obligatoriu. Dacă numărul este o constantă matematică (pi, e) — folosiți biblioteca standard (Math.PI, Math.E).

Șiruri și căi magice

Magic strings — literali de șir încorporați în cod fără extragerea în constante sau resurse. Exemple tipice: URL-uri ale endpoint-urilor, nume de chei SharedPreferences, Intent Actions, bundle keys, nume de fișiere și interogări SQL.

Pericolul șirurilor magice constă în absența verificării la compilare. O greșeală de tipar în șirul „user_prefs” nu va fi detectată până la runtime. Dacă șirul este utilizat în zece locuri, iar dezvoltatorul într-unul a scris „user_pref” (fără s) — aplicația nu crapă, dar datele nu se salvează. O astfel de eroare poate trăi în producție luni de zile, deoarece nu provoacă un crash.

Pentru proiectele Android, șirurile magice trebuie extrase în resurse (strings.xml, arrays.xml) sau în constante în companion object. Pentru iOS — în resurse de șir (Localizable.strings) sau constante enum. Pentru backend — în fișiere de configurare (.env, application.properties). Nici o cheie, URL sau cale nu trebuie să fie prezentă în cod ca literal de șir.

swift
// înainte — șiruri magice în întreaga clasă
let prefs = UserDefaults.standard
prefs.set(token, forKey: "auth_token")
prefs.set(userId, forKey: "current_user_id")

// după — șirurile extrase în enum
enum PrefKeys: String {
    case authToken = "auth_token"
    case currentUserId = "current_user_id"
}

prefs.set(token, forKey: PrefKeys.authToken.rawValue)
prefs.set(userId, forKey: PrefKeys.currentUserId.rawValue)

Acordați o atenție deosebită șirurilor care se repetă. Dacă aceeași cheie „user_settings” apare în trei fișiere — cu o probabilitate de 99 la sută, mai devreme sau mai târziu, într-unul dintre ele va apărea o greșeală de tipar. Extragerea în enum sau constantă garantează că toate referințele folosesc aceeași valoare.

Magic flags și parametri booleeni

Magic flags — parametri booleeni a căror valoare nu este evidentă din contextul apelului. Anti-pattern clasic: trimiterea true sau false către o metodă fără a explica ce anume activează sau dezactivează acest flag.

Exemplu: userDao.fetch(includeDeleted = false). Dezvoltatorul vede false și nu înțelege dacă aceasta înseamnă „nu include șterse” sau „nu include active”. Peste o lună, false se transformă în true, iar în rezultate încep să apară înregistrări șterse. Eroarea este descoperită abia în producție.

Soluția — înlocuirea flagurilor booleene cu enum sau sealed class. În loc de parametrul Boolean, utilizați UserFilter.includeDeleted sau UserFilter.activeOnly. Astfel, codul își documentează singur intenția, iar IDE-ul sugerează opțiunile disponibile la autocompletare.

Dacă un flag boolean este transmis prin mai multe straturi — acesta este un alt semnal că abstractizarea este greșită. În loc să trageți flagul prin trei niveluri de apeluri, gândiți-vă dacă alegerea filtrării nu ar trebui să fie luată la nivelul superior și transmisă ca o configurație gata făcută. Cu cât sunt mai puține flaguri booleene în cod — cu atât mai puțină magie.

Introduceți o regulă: niciun parametru boolean nu este transmis unei metode fără un argument denumit (dacă limbajul suportă named arguments). În Kotlin și Swift, această cerință este îndeplinită automat. În Java, utilizați Builder sau constante enum în loc de true/false.

Instrumente de detectare a magiei

Căutarea valorilor magice este automatizată de analizoare statice care sunt configurate pentru a identifica literali în locuri neașteptate. Fiecare limbaj oferă propriile instrumente cu excepții configurabile.

InstrumentLimbajeRegulă
SonarQubeJava, Kotlin, Swift, Python, JSMagicNumber, HardcodedString
ESLintJavaScript, TypeScriptno-magic-numbers, no-hardcoded-strings
DetektKotlinMagicNumber, ComplexCondition
SwiftLintSwiftmagic_number (activat opt-in)
PMDJava, Apex, PLSQLMagicNumber (se poate configura lista permisă)
PhpStorm InspectionsPHPNumericLiteralWithContext (inspecție încorporată)

Configurarea excepțiilor este critică — fără ea, analizorul va emite avertismente la fiecare incrementare (-1, +1) și zero matematic. Pentru SonarQube, lista numerelor permise: 0, 1, -1, 2 (pentru dublare), 100 (procente), 60 și 24 (timp). Pentru toate celelalte valori — cereți o constantă denumită cu modificatorul public static final (Java) sau const val (Kotlin).

Pentru analiza la nivel de CI, adăugați un pas cu verificarea magiei ca avertisment, dar care să nu blocheze construirea. Prima rulare va arăta sute de avertismente în codul moștenit. Treptat, ticket după ticket, migrați codul la constante și ridicați pragul de calitate. Când numărul de magic numbers devine mai mic de 10 — activați regula ca eroare de build.

Refactorizare: înlocuim magia cu constante

Refactorizarea magiei — una dintre cele mai sigure operații: înlocuirea unui literal cu o constantă nu schimbă comportamentul codului. Cu toate acestea, abordarea trebuie să fie sistematică pentru a nu rata dependențe ascunse (de exemplu, dacă același magic number este utilizat în contexte fără legătură, dar are accidental aceeași valoare).

Procesul pas cu pas: găsiți toate aparițiile valorii magice, înțelegeți contextul fiecăreia, separați în constante diferite (chiar dacă valorile coincid — contextele sunt diferite, iar constantele trebuie denumite diferit), înlocuiți literalii cu constante, verificați prin teste. Eroarea la pasul 2 — cea mai frecventă: două concepte diferite (timeout în milisecunde și pragul în octeți) pot coincide numeric (de exemplu, 5000), dar semantic sunt mărimi diferite și nu pot fi unite într-o singură constantă.

java
// înainte — același număr în contexte diferite
public class Config {
    public void setupCache() {
        cache.setMaxSize(5000); // 5 MB
    }
    public void setupTimeout() {
        client.setReadTimeout(5000); // 5 secunde
    }
}

// după — constante diferite pentru contexte diferite
public class Config {
    private static final int CACHE_MAX_SIZE_MB = 5;
    private static final int READ_TIMEOUT_SECONDS = 5;

    public void setupCache() {
        cache.setMaxSize(CACHE_MAX_SIZE_MB * 1024 * 1024);
    }
    public void setupTimeout() {
        client.setReadTimeout(
            READ_TIMEOUT_SECONDS * 1000
        );
    }
}

Pentru codul nou, regula este simplă: orice literal, cu excepția 0, 1, -1, true, false, null și a șirului gol, este extras într-o constantă. Excepții: constante matematice (întotdeauna prin biblioteca standard), date de test (se poate lăsa literalul în test, dar cu un nume de variabilă explicativ) și valori limită pentru incrementare (i + 1 într-o buclă — normal).

Întrebări frecvente

Este 100 un magic number dacă reprezintă 100 la sută?

Da, 100 este, de asemenea, magic number dacă este utilizat fără context. În loc de 100, scrieți MAX_PERCENT sau PROBABILITY_SCALE. Excepție: când 100 este un procent evident în context (de exemplu, în formula de calcul a procentului), dar chiar și în acest caz, o constantă îmbunătățește lizibilitatea.

Ce facem cu numerele în teste?

Și în teste este mai bine să folosiți variabile denumite. În loc de assertEquals(42, result) scrieți val expected = 42; assertEquals(expected, result). Excepție: testele pentru valori limită (0, null, șir gol) — pot fi lăsate ca literali, deoarece sunt lizibile în contextul testului.

Merită să extragem numerele în resurse Android?

Da, numerele legate de UI (dimensiuni, margini, durata animației) ar trebui să fie în resurse (dimens.xml, integers.xml). Constantele de afaceri (timeout-uri, limite) — în companion object sau fișier de configurare. Criteriul principal: dacă numărul se poate schimba fără a modifica logica — este o resursă.

Cum găsim magic numbers într-un proiect moștenit?

Rulați SonarQube cu regula MagicNumber sau ESLint cu no-magic-numbers. Obțineți raportul, sortați după frecvența de utilizare și începeți cu numerele care apar în trei sau mai multe locuri. Acestea au cea mai mare probabilitate de a fi candidate pentru extragerea în constante.

Trebuie fiecare număr din cod să fie extras într-o constantă?

Nu. Literali permis: 0, 1, -1 (incrementare/decrementare, verificare de gol), true, false, null, șir gol. Toate celelalte necesită denumire. Dacă numărul 0 este utilizat nu ca verificare de gol (de exemplu, 0 — este ID-ul categoriei rădăcină), atunci și 0 trebuie să fie o constantă: ROOT_CATEGORY_ID = 0.

Concluzii

  • Magia — literali fără explicație: numere, șiruri, flaguri, al căror sens este ascuns cititorului codului.
  • Magic numbers — constante numerice fără nume (86400, 1024, 0.85, 5000), care necesită cunoștințe de domeniu pentru a fi înțelese.
  • Magic strings — hardcodarea cheilor, URL-urilor și căilor, invizibile pentru compilator și care duc la erori de runtime.
  • Magic flags — parametri booleeni a căror valoare nu este evidentă (true/false în apelul metodei).
  • Instrumente: SonarQube, ESLint, Detekt, SwiftLint, PMD — toate suportă regula MagicNumber.
  • Soluția: fiecare literal (cu excepția 0, ±1, true, false, null, „”) este extras într-o constantă denumită cu un nume explicativ.
  • Contexte diferite — constante diferite: 5000 ca timeout și 5000 ca dimensiune a cache-ului — sunt entități diferite.

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și