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 (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 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ă.
// î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).
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.
// î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 — 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.
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.
| Instrument | Limbaje | Regulă |
|---|---|---|
| SonarQube | Java, Kotlin, Swift, Python, JS | MagicNumber, HardcodedString |
| ESLint | JavaScript, TypeScript | no-magic-numbers, no-hardcoded-strings |
| Detekt | Kotlin | MagicNumber, ComplexCondition |
| SwiftLint | Swift | magic_number (activat opt-in) |
| PMD | Java, Apex, PLSQL | MagicNumber (se poate configura lista permisă) |
| PhpStorm Inspections | PHP | NumericLiteralWithContext (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.
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ă.
// î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
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.
Ș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.
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ă.
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.
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
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.
Citiți și