Magie v programování — není metafora, ale přesný termín označující hodnoty (čísla, řetězce, příznaky), jejichž význam není z kontextu zřejmý a vyžaduje vnější znalosti k pochopení. Nejrozšířenějším druhem magie jsou magic numbers: číselné konstanty vepsané přímo do kódu bez vysvětlení, proč byla zvolena právě tato hodnota. Podle studie SonarSource Code Quality Report (2025) souvisí přibližně 8 procent všech varování statických analyzátorů s nevysvětlenými literály. Magické hodnoty činí kód křehkým: změna vyžaduje hledání všech výskytů a nový vývojář nechápe, zda se čísla může dotknout nebo je kritické pro fungování systému.
Hlavní
Magie (magic) — je jakákoli hodnota ve zdrojovém kódu, jejíž význam není zřejmý bez dodatečných znalostí o doméně. Termín se v komunitě ustálil: pokud se vývojář podívá na číslo a nechápe, odkud se vzalo — je to magie.
Magie může být několika typů: numerická (magic numbers), řetězcová (magic strings), booleovská (magic flags) a konfigurační (hardcoded parametry, které by měly být v nastavení). Všechny čtyři typy spojuje jeden problém: při změně požadavku musí vývojář najít všechna místa, kde se hodnota používá, a ručně je nahradit. Vynechání byť jen jednoho výskytu vede k chybě.
Podle zprávy JetBrains Code Quality Survey (2025) považuje 73 procent vývojářů magic numbers za indikátor nízké kvality kódu, přičemž 41 procent přiznává, že je sami občas zanechávají. Hlavní důvod — spěch: „Konstantu dám později” — ale později nepřichází a po měsíci zůstává číslo 0.85 bez vysvětlení v těle metody.
Klíčové pravidlo: každá literální hodnota, kromě 0, 1, true, false a prázdného řetězce, by měla být vyjmuta do pojmenované konstanty. Výjimky: inkrementace čítače (i + 1), matematické nuly (kontrola na 0) a počáteční hodnoty akumulátorů. Vše ostatní — kandidát na pojmenování.
Magic number — je číselný literál, jehož hodnota není z kontextu zřejmá. Klasický příklad: 86400 v kódu odpovědném za timeout. Vývojář vidí číslo a musí uhodnout, že se jedná o počet sekund v dni. Pokud udělá chybu a zadá 84600 — chyba bude těžko k odhalení, protože timeout se spustí o 18 minut dříve.
Proč jsou magic numbers nebezpečné: zaprvé naršují čitelnost. Číslo 1024 může znamenat velikost kilobajtu, práh stránkování nebo maximální počet prvků. Bez kontextu — je to jen číslo. Zadruhé vytvářejí duplicitu: pokud je 1024 použito na pěti místech, při změně prahu na 2048 musí vývojář najít všech pět a nahradit je. Pokud je jedno místo vynecháno — systém pracuje nesprávně, ale bez zřejmé chyby.
// před — magie v čisté formě
fun calculateTimeout(base: Int): Int {
return base * 3 + 5000
}
// po — hodnoty nahrazeny konstantami
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
}
Třetí nebezpečí — nemožnost testování. Pokud je prahová hodnota vložena do kódu jako literál, test ji nemůže přepsat pro kontrolu okrajových podmínek. Konstanta vyjmutá do companion object nebo konfiguračního souboru činí kód testovatelným: test dosadí jinou hodnotu a zkontroluje chování systému na hranici.
Vytvořte si návyk: pokaždé, když napíšete číslo jiné než 0, 1, 100 nebo 2 — zastavte se a přemýšlejte, zda stojí za to ho vyjmout do konstanty. Pokud číslo souvisí s obchodní logikou (limit, práh, timeout, velikost) — vyjměte ho povinně. Pokud je číslo matematickou konstantou (pi, e) — použijte standardní knihovnu (Math.PI, Math.E).
Magic strings — řetězcové literály vložené do kódu bez vyjmutí do konstant nebo zdrojů. Typické příklady: URL endpointů, názvy klíčů SharedPreferences, Intent Actions, bundle keys, názvy souborů a SQL dotazy.
Nebezpečí magických řetězců spočívá v absenci kontroly během kompilace. Překlep v řetězci „user_prefs” nebude odhalen až do runtime. Pokud je řetězec použit na deseti místech a vývojář na jednom místě napsal „user_pref” (bez s) — aplikace nespadne, ale data se neukládají. Taková chyba může žít v produkci měsíce, protože nezpůsobuje pád.
Pro Android projekty musí být magické řetězce vyjmuty do zdrojů (strings.xml, arrays.xml) nebo do konstant v companion object. Pro iOS — do řetězcových zdrojů (Localizable.strings) nebo enum konstant. Pro backend — do konfiguračních souborů (.env, application.properties). Žádný klíč, URL ani cesta by neměly být v kódu přítomny jako řetězcový literál.
// před — magické řetězce napříč třídou
let prefs = UserDefaults.standard
prefs.set(token, forKey: "auth_token")
prefs.set(userId, forKey: "current_user_id")
// po — řetězce vyjmuty do 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)
Věnujte zvláštní pozornost řetězcům, které se opakují. Pokud se stejný klíč „user_settings” vyskytuje ve třech souborech — s pravděpodobností 99 procent dříve nebo později v jednom z nich vznikne překlep. Vyjmutí do enum nebo konstanty zaručuje, že všechny reference používají stejnou hodnotu.
Magic flags — booleovské parametry, jejichž hodnota není z kontextu volání zřejmá. Klasický anti-vzor: předávání true nebo false metodě bez vysvětlení, co přesně tento příznak zapíná nebo vypíná.
Příklad: userDao.fetch(includeDeleted = false). Vývojář vidí false a nechápe, zda to znamená „nzahrnout smazané” nebo „nzahrnout aktivní”. Za měsíc se false změní na true a ve výsledcích se začnou objevovat smazané záznamy. Chyba je odhalena až v produkci.
Řešení — nahrazení booleovských příznaků enumem nebo sealed class. Místo parametru Boolean použijte UserFilter.includeDeleted nebo UserFilter.activeOnly. Tak kód sám dokumentuje záměr a IDE nabízí dostupné možnosti při automatickém doplňování.
Pokud je booleovský příznak předáván přes několik vrstev — to je další signál, že abstrakce je chybná. Místo vlečení příznaku přes tři úrovně volání přemýšlejte, zda by výběr filtrování neměl být proveden na horní úrovni a předán jako hotová konfigurace. Čím méně booleovských příznaků v kódu — tím méně magie.
Zaveďte pravidlo: žádný booleovský parametr není předáván metodě bez pojmenovaného argumentu (pokud jazyk podporuje named arguments). V Kotlin a Swift je tento požadavek splněn automaticky. V Java použijte Builder nebo enum konstanty místo true/false.
Vyhledávání magických hodnot je automatizováno statickými analyzátory, které jsou nakonfigurovány k identifikaci literálů na neočekávaných místech. Každý jazyk nabízí vlastní nástroje s konfigurovatelnými výjimkami.
| Nástroj | Jazyky | Pravidlo |
|---|---|---|
| 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 (zapnuto opt-in) |
| PMD | Java, Apex, PLSQL | MagicNumber (lze nakonfigurovat seznam povolených) |
| PhpStorm Inspections | PHP | NumericLiteralWithContext (vestavěná inspekce) |
Konfigurace výjimek je kriticky důležitá — bez ní bude analyzátor vydávat varování při každé inkrementaci (-1, +1) a matematické nule. Pro SonarQube je seznam povolených čísel: 0, 1, -1, 2 (pro zdvojnásobení), 100 (procenta), 60 a 24 (čas). Pro všechny ostatní hodnoty — vyžadujte pojmenovanou konstantu s modifikátorem public static final (Java) nebo const val (Kotlin).
Pro analýzu na úrovni CI přidejte krok s kontrolou magie jako varování, ale ne blokující sestavení. První spuštění ukáže stovky varování v legacy kódu. Postupně, ticket po ticketu, převádějte kód na konstanty a zvyšujte práh kvality. Když počet magic numbers klesne pod 10 — zapněte pravidlo jako chybu sestavení.
Refaktorování magie — jedna z nejbezpečnějších operací: nahrazení literálu konstantou nemění chování kódu. Nicméně přístup musí být systematický, aby nedošlo k přehlédnutí skrytých závislostí (například pokud je stejné magic number použito v nesouvisejících kontextech, ale náhodou má stejnou hodnotu).
Postup krok za krokem: najděte všechny výskyty magické hodnoty, pochopte kontext každého, rozdělte do různých konstant (i když se hodnoty shodují — kontexty jsou různé a konstanty musí být pojmenovány odlišně), nahraďte literály konstantami, zkontrolujte pomocí testů. Chyba v kroku 2 — nejčastější: dva různé pojmy (timeout v milisekundách a práh v bajtech) mohou číselně souhlasit (například 5000), ale sémanticky jde o různé veličiny a nelze je sloučit do jedné konstanty.
// před — stejné číslo v různých kontextech
public class Config {
public void setupCache() {
cache.setMaxSize(5000); // 5 MB
}
public void setupTimeout() {
client.setReadTimeout(5000); // 5 sekund
}
}
// po — různé konstanty pro různé kontexty
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
);
}
}
Pro nový kód je pravidlo jednoduché: každý literál, kromě 0, 1, -1, true, false, null a prázdného řetězce, se vyjímá do konstanty. Výjimky: matematické konstanty (vždy přes standardní knihovnu), testovací data (literál může zůstat v testu, ale s vysvětlujícím názvem proměnné) a okrajové hodnoty pro inkrementaci (i + 1 ve smyčce — normální).
Často kladené otázky
Ano, 100 je také magic number, pokud se používá bez kontextu. Místo 100 napište MAX_PERCENT nebo PROBABILITY_SCALE. Výjimka: když je 100 zřejmým procentem v kontextu (například ve vzorci pro výpočet procenta), ale i v tomto případě konstanta zlepšuje čitelnost.
V testech je také lepší používat pojmenované proměnné. Místo assertEquals(42, result) napište val expected = 42; assertEquals(expected, result). Výjimka: testy na okrajové hodnoty (0, null, prázdný řetězec) — lze je ponechat jako literály, protože jsou čitelné v kontextu testu.
Ano, čísla související s UI (velikosti, okraje, doba trvání animace) by měla být ve zdrojích (dimens.xml, integers.xml). Obchodní konstanty (timeouty, limity) — v companion object nebo konfiguračním souboru. Hlavní kritérium: pokud se číslo může změnit bez změny logiky — je to zdroj.
Spusťte SonarQube s pravidlem MagicNumber nebo ESLint s no-magic-numbers. Získejte zprávu, seřaďte podle četnosti použití a začněte s čísly, která se vyskytují na třech nebo více místech. Ty jsou s největší pravděpodobností kandidáty na vyjmutí do konstanty.
Ne. Povolené literály: 0, 1, -1 (inkrementace/dekrementace, kontrola prázdnoty), true, false, null, prázdný řetězec. Všechny ostatní vyžadují pojmenování. Pokud je číslo 0 použito ne jako kontrola prázdnoty (například 0 — je ID kořenové kategorie), pak i 0 musí být konstantou: ROOT_CATEGORY_ID = 0.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také