Магија у програмирању — шта је то, зашто су опасни magic numbers и замена

Аутор: IT Sectr Објављено: 2026-07-27 Време читања: 10 мин

Магија у програмирању — није метафора, већ прецизан термин који означава вредности (бројеве, стрингове, заставице), чије значење није очигледно из контекста и захтева спољно знање за разумевање. Најраспрострањенији вид магије — magic numbers: нумеричке константе уписане директно у код без објашњења зашто је баш та вредност изабрана. Према истраживању SonarSource Code Quality Report (2025), око 8 посто свих упозорења статичких анализатора повезано је са необјашњеним литералима. Магијске вредности чине код крхким: промена захтева тражење свих појављивања, а нови програмер не разуме да ли може да дира број или је он критичан за рад система.

Главно

  • Магија — нејавни бројеви, стрингови и заставице у коду, чије је значење скривено од читаоца.
  • Magic numbers — нумерички литерали без имена: 86400, 3.14, 0.85, 1024.
  • Магијски стрингови — хардкод путања, кључева, URL-ова без издвајања у константе.
  • Алати за претрагу: SonarQube (правило MagicNumber), ESLint (no-magic-numbers), Detekt.
  • Решење: издвојити сваку магијску вредност у именовану константу с објашњавајућим називом.

Шта је магија у програмирању?

Магија (magic) — је свака вредност у изворном коду чије значење није очигледно без додатног знања о домену. Термин се усталио у заједници: ако програмер погледа број и не разуме одакле је дошао — то је магија.

Магија може бити неколико врста: нумеричка (magic numbers), стринг (magic strings), Булова (magic flags) и конфигурациона (хардкодовани параметри који би требало да буду у подешавањима). Све четири врсте обједињује један проблем: при промени захтева програмер мора пронаћи сва места где се вредност користи и ручно их заменити. Пропуштање чак и једног појављивања доводи до грешке.

Према извештају JetBrains Code Quality Survey (2025), 73 посто програмера сматра magic numbers индикатором ниског квалитета кода, док 41 посто признаје да их и сами повремено остављају. Главни разлог — журба: „Ставићу константу касније“ — али касније не стиже, и после месец дана број 0.85 остаје без објашњења у телу методе.

Кључно правило: свака литерална вредност, осим 0, 1, true, false и празног стринга, мора бити издвојена у именовану константу. Изузеци: инкремент бројача (i + 1), математичке нуле (провера на 0) и почетне вредности акумулатора. Све остало — кандидат за именовање.

Magic numbers и зашто су опасни

Magic number — нумерички литерал чија вредност није очигледна из контекста. Класичан пример: 86400 у коду одговорном за timeout. Програмер види број и мора погодити да је то број секунди у дану. Ако погреши и стави 84600 — грешку ће бити тешко ухватити, јер ће timeout радити 18 минута раније.

Зашто су magic numbers опасни: прво, они нарушавају читљивост. Број 1024 може означавати величину килобајта, праг пагинације или максимални број елемената. Без контекста — то је само број. Друго, они стварају дуплирање: ако се 1024 користи на пет места, при промени прага на 2048 програмер мора пронаћи свих пет и заменити их. Ако је једно место пропуштено — систем ради неисправно, али без јасне грешке.

Пример magic numbers пре и после

kotlin
// пре — магија у чистом облику
fun calculateTimeout(base: Int): Int {
    return base * 3 + 5000
}

// после — вредности замењене константама
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
}

Трећа опасност — немогућност тестирања. Ако је гранична вредност уграђена у код као литерал, тест је не може прегазити да би проверио граничне услове. Константа издвојена у companion object или конфигурациони фајл чини код тестирабилним: тест поставља другу вредност и проверава понашање система на граници.

Изградите навику: сваки пут када напишете број који није 0, 1, 100 или 2 — зауставите се и размислите да ли га вреди издвојити у константу. Ако је број повезан са пословном логиком (лимит, праг, timeout, величина) — обавезно га издвојите. Ако је број математичка константа (pi, e) — користите стандардну библиотеку (Math.PI, Math.E).

Магијски стрингови и путање

Magic strings — стринг литерали уграђени у код без издвајања у константе или ресурсе. Типични примери: URL-ови ендпоинта, називи SharedPreferences кључева, Intent Actions, bundle keys, имена фајлова и SQL упити.

Опасност магијских стрингова је у недостатку провере у фази компилације. Грешку у куцању у стрингу „user_prefs“ неће бити откривена до runtime-а. Ако се стринг користи на десет места, а програмер је на једном написао „user_pref“ (без s) — апликација не пада, али подаци се не чувају. Таква грешка може живети у продукцији месецима, јер не изазива crash.

За Android пројекте магијски стрингови морају бити издвојени у ресурсе (strings.xml, arrays.xml) или у константе у companion object. За iOS — у стринг ресурсе (Localizable.strings) или константе enum. За backend — у конфигурационе фајлове (.env, application.properties). Ниједан кључ, URL или путања не смеју бити присутни у коду као стринг литерал.

swift
// пре — магијски стрингови широм класе
let prefs = UserDefaults.standard
prefs.set(token, forKey: "auth_token")
prefs.set(userId, forKey: "current_user_id")

// после — стрингови издвојени у 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)

Посебну пажњу посветите стринговима који се дуплирају. Ако се исти кључ „user_settings“ појављује у три фајла — са вероватноћом од 99 посто ће пре или касније у једном од њих настати грешка у куцању. Издвајање у enum или константу гарантује да све референце користе исту вредност.

Magic flags и Булови параметри

Magic flags — Булови параметри чија вредност није очигледна из контекста позива. Класичан анти-образац: прослеђивање true или false методи без објашњења шта тачно та заставица укључује или искључује.

Пример: userDao.fetch(includeDeleted = false). Програмер види false и не разуме да ли то значи „не укључуј обрисане“ или „не укључуј активне“. После месец дана false се претвара у true, а у резултатима почињу да се појављују обрисани записи. Грешка се открива тек у продукцији.

Решење — замена Булових заставица enum-ом или sealed class-ом. Уместо параметра Boolean користите UserFilter.includeDeleted или UserFilter.activeOnly. Тако код сам документује намеру, а IDE предлаже доступне опције при аутоматском довршавању.

Ако се Булова заставица прослеђује кроз неколико слојева — то је још један сигнал да је апстракција погрешна. Уместо да провлачите заставицу кроз три нивоа позива, размислите да ли избор филтрирања треба да буде донет на горњем нивоу и прослеђен као готова конфигурација. Што је мање Булових заставица у коду — то мање магије.

Уведите правило: ниједан Булов параметар се не прослеђује методи без именованог аргумента (ако језик подржава named arguments). У Kotlin и Swift-у овај захтев се испуњава аутоматски. У Java-и користите Builder или enum константе уместо true/false.

Алати за откривање магије

Претрага магијских вредности се аутоматизује статичким анализаторима који су подешени да откривају литерале на неочекиваним местима. Сваки језик нуди сопствене алате са подесивим изузецима.

АлатЈезициПравило
SonarQubeJava, Kotlin, Swift, Python, JSMagicNumber, HardcodedString
ESLintJavaScript, TypeScriptno-magic-numbers, no-hardcoded-strings
DetektKotlinMagicNumber, ComplexCondition
SwiftLintSwiftmagic_number (укључен opt-in)
PMDJava, Apex, PLSQLMagicNumber (може се подесити листа дозвољених)
PhpStorm InspectionsPHPNumericLiteralWithContext (уграђена инспекција)

Подешавање изузетака је критично важно — без њега ће анализатор издавати упозорења на сваки инкремент (-1, +1) и математичку нулу. За SonarQube листа дозвољених бројева: 0, 1, -1, 2 (за удвостручавање), 100 (проценти), 60 и 24 (време). За све остале вредности — захтевати именовану константу са модификатором public static final (Java) или const val (Kotlin).

За анализу на CI нивоу додајте корак са провером магије као упозорење, али не блокирајући изградњу. Прво покретање ће показати стотине упозорења у legacy коду. Постепено, тикет по тикет, преводите код на константе и подижите праг квалитета. Када број magic numbers постане мањи од 10 — укључите правило као грешку изградње.

Рефакторинг: замењујемо магију константама

Рефакторинг магије — једна од најсигурнијих операција: замена литерала константом не мења понашање кода. Ипак, приступ мора бити систематичан да се не пропусте скривене зависности (на пример, ако се исти magic number користи у неповезаним контекстима, али случајно има исту вредност).

Процес корак по корак: пронађите сва појављивања магијске вредности, разумите контекст сваког, раздвојите у различите константе (чак и ако су вредности исте — контексти су различити и константе морају имати различите називе), замените литерале константама, проверите кроз тестове. Грешка на кораку 2 — најчешћа: два различита појма (timeout у милисекундама и праг у бајтовима) могу нумерички бити исти (на пример 5000), али семантички су то различите величине и не могу се објединити у једну константу.

java
// пре — исти број у различитим контекстима
public class Config {
    public void setupCache() {
        cache.setMaxSize(5000); // 5 MB
    }
    public void setupTimeout() {
        client.setReadTimeout(5000); // 5 секунди
    }
}

// после — различите константе за различите контексте
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
        );
    }
}

За нови код правило је једноставно: сваки литерал, осим 0, 1, -1, true, false, null и празног стринга, издваја се у константу. Изузеци: математичке константе (увек преко стандардне библиотеке), тест подаци (литерал се може оставити у тесту, али са објашњавајућим именом променљиве) и граничне вредности за инкремент (i + 1 у петљи — нормално).

Често постављана питања

Да ли је 100 magic number ако представља 100 посто?

Да, 100 је такође magic number ако се користи без контекста. Уместо 100 напишите MAX_PERCENT или PROBABILITY_SCALE. Изузетак: када је 100 очигледан проценат у контексту (на пример, у формули за израчунавање процента), али чак и у том случају константа побољшава читљивост.

Шта радити с бројевима у тестовима?

У тестовима је такође боље користити именоване променљиве. Уместо assertEquals(42, result) напишите val expected = 42; assertEquals(expected, result). Изузетак: тестови на граничне вредности (0, null, празан стринг) — могу се оставити као литерали, јер су читљиви у контексту теста.

Да ли вреди издвајати бројеве у Android ресурсе?

Да, бројеви везани за UI (величине, маргине, трајање анимације) треба да буду у ресурсима (dimens.xml, integers.xml). Пословне константе (timeout-и, лимити) — у companion object или конфигурационом фајлу. Главни критеријум: ако се број може променити без промене логике — то је ресурс.

Како пронаћи magic numbers у legacy пројекту?

Покрените SonarQube с правилом MagicNumber или ESLint с no-magic-numbers. Добијте извештај, сортирајте по учесталости коришћења и почните с бројевима који се појављују на три или више места. Они су с највећом вероватноћом кандидати за издвајање у константу.

Да ли сваки број у коду треба издвојити у константу?

Не. Дозвољени литерали: 0, 1, -1 (инкремент/декремент, провера празнине), true, false, null, празан стринг. Сви остали захтевају именовање. Ако се број 0 користи не као провера празнине (на пример, 0 — ID коренске категорије), онда и 0 мора бити константа: ROOT_CATEGORY_ID = 0.

Закључци

  • Магија — литерали без објашњења: бројеви, стрингови, заставице, чије је значење скривено од читаоца кода.
  • Magic numbers — нумеричке константе без имена (86400, 1024, 0.85, 5000) које захтевају доменско знање за разумевање.
  • Magic strings — хардкод кључева, URL-ова и путања, невидљиви за компајлер и који доводе до runtime грешака.
  • Magic flags — Булови параметри чија вредност није очигледна (true/false у позиву методе).
  • Алати: SonarQube, ESLint, Detekt, SwiftLint, PMD — сви подржавају правило MagicNumber.
  • Решење: сваки литерал (осим 0, ±1, true, false, null, „“) се издваја у именовану константу с објашњавајућим називом.
  • Различити контексти — различите константе: 5000 као timeout и 5000 као величина кеша — различити су ентитети.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође