Magi i programmering — är inte en metafor, utan en exakt term som betecknar värden (tal, strängar, flaggor) vars innebörd inte är uppenbar från sammanhanget och kräver extern kunskap för att förstå. Den mest utbredda typen av magi — magic numbers: numeriska konstanter inskrivna direkt i koden utan förklaring till varför just detta värde valdes. Enligt undersökningen SonarSource Code Quality Report (2025) är cirka 8 procent av alla varningar från statiska analysatorer relaterade till oförklarade literal. Magiska värden gör kod spröd: ändring kräver sökning efter alla förekomster och en ny utvecklare förstår inte om talet får röras eller om det är kritiskt för systemets funktion.
Huvudpunkter
Magi (magic) — är varje värde i källkoden vars innebörd inte är uppenbar utan ytterligare kunskap om domänen. Termen har etablerats i communityn: om en utvecklare tittar på ett tal och inte förstår var det kommer ifrån — är det magi.
Magi finns i flera typer: numerisk (magic numbers), sträng (magic strings), boolesk (magic flags) och konfigurationsmässig (hårdkodade parametrar som borde vara i inställningar). Alla fyra typer förenas av ett problem: när ett krav ändras måste utvecklaren hitta alla platser där värdet används och ersätta dem manuellt. Att missa även en förekomst leder till en bugg.
Enligt rapporten JetBrains Code Quality Survey (2025) anser 73 procent av utvecklarna att magic numbers är en indikator på låg kodkvalitet, medan 41 procent erkänner att de själva ibland lämnar kvar dem. Huvudorsaken — brådska: “Jag sätter in konstanten senare” — men senare kommer aldrig, och efter en månad förblir talet 0.85 utan förklaring i metodkroppen.
Nyckelregel: varje literalvärde, förutom 0, 1, true, false och tom sträng, bör extraheras till en namngiven konstant. Undantag: räknarökning (i + 1), matematiska nollor (kontroll på 0) och startvärden för ackumulatorer. Allt annat — kandidat för namngivning.
Magic number — är en numerisk literal vars värde inte är uppenbart från sammanhanget. Klassiskt exempel: 86400 i koden som ansvarar för timeout. Utvecklaren ser talet och måste gissa att det är antalet sekunder på en dag. Om han gör fel och sätter 84600 — kommer buggen att vara svår att hitta eftersom timeouten utlöses 18 minuter tidigare.
Varför magic numbers är farliga: för det första skadar de läsbarheten. Talet 1024 kan betyda storleken på en kilobyte, en pagineringströskel eller det maximala antalet element. Utan sammanhang — är det bara ett tal. För det andra skapar de duplicering: om 1024 används på fem ställen måste utvecklaren vid ändring av tröskeln till 2048 hitta alla fem och ersätta dem. Om ett ställe missas — fungerar systemet felaktigt, men utan uppenbart fel.
// före — magi i ren form
fun calculateTimeout(base: Int): Int {
return base * 3 + 5000
}
// efter — värden ersatta med konstanter
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
}
Den tredje faran — omöjlighet att testa. Om tröskelvärdet är inbäddat i koden som en literal kan testet inte åsidosätta det för att kontrollera gränsvillkor. En konstant extraherad till ett companion object eller konfigurationsfil gör koden testbar: testet sätter ett annat värde och kontrollerar systemets beteende vid gränsen.
Utveckla en vana: varje gång du skriver ett tal som inte är 0, 1, 100 eller 2 — stanna och fundera på om det är värt att extrahera det till en konstant. Om talet är relaterat till affärslogik (gräns, tröskel, timeout, storlek) — extrahera det obligatoriskt. Om talet är en matematisk konstant (pi, e) — använd standardbiblioteket (Math.PI, Math.E).
Magic strings — strängliteral inbäddade i koden utan extrahering till konstanter eller resurser. Typiska exempel: URL:er till slutpunkter, namn på SharedPreferences-nycklar, Intent Actions, bundle keys, filnamn och SQL-frågor.
Faran med magiska strängar ligger i avsaknaden av kontroll vid kompilering. Ett stavfel i strängen “user_prefs” upptäcks inte förrän vid körning. Om strängen används på tio ställen och utvecklaren på ett ställe har skrivit “user_pref” (utan s) — kraschar inte applikationen, men data sparas inte. En sådan bugg kan leva i produktion i månader eftersom den inte orsakar en krasch.
För Android-projekt måste magiska strängar extraheras till resurser (strings.xml, arrays.xml) eller till konstanter i companion object. För iOS — till strängresurser (Localizable.strings) eller enum-konstanter. För backend — till konfigurationsfiler (.env, application.properties). Ingen nyckel, URL eller sökväg bör finnas i koden som en strängliteral.
// före — magiska strängar i hela klassen
let prefs = UserDefaults.standard
prefs.set(token, forKey: "auth_token")
prefs.set(userId, forKey: "current_user_id")
// efter — strängar extraherade till 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)
Ägna särskild uppmärksamhet åt strängar som dupliceras. Om samma nyckel “user_settings” förekommer i tre filer — kommer det med 99 procents sannolikhet förr eller senare att uppstå ett stavfel i en av dem. Extrahering till enum eller konstant garanterar att alla referenser använder samma värde.
Magic flags — booleska parametrar vars värde inte är uppenbart från anropets sammanhang. Klassiskt anti-mönster: att skicka true eller false till en metod utan att förklara vad denna flagga exakt aktiverar eller inaktiverar.
Exempel: userDao.fetch(includeDeleted = false). Utvecklaren ser false och förstår inte om detta betyder “inkludera inte borttagna” eller “inkludera inte aktiva”. En månad senare förvandlas false till true och i resultaten börjar borttagna poster dyka upp. Buggen upptäcks först i produktion.
Lösning — ersättning av booleska flaggor med enum eller sealed class. Använd UserFilter.includeDeleted eller UserFilter.activeOnly istället för Boolean-parametern. På så sätt dokumenterar koden själv avsikten och IDE föreslår tillgängliga alternativ vid autokomplettering.
Om en boolesk flagga skickas genom flera lager — är detta ytterligare en signal att abstraktionen är felaktig. Istället för att dra flaggan genom tre anropsnivåer, fundera på om filtervalet borde göras på den övre nivån och skickas som en färdig konfiguration. Ju färre booleska flaggor i koden — desto mindre magi.
Inför en regel: ingen boolesk parameter skickas till en metod utan namngivet argument (om språket stöder named arguments). I Kotlin och Swift uppfylls detta krav automatiskt. I Java använd Builder eller enum-konstanter istället för true/false.
Sökningen efter magiska värden automatiseras av statiska analysatorer som är konfigurerade för att identifiera literal på oväntade platser. Varje språk erbjuder egna verktyg med konfigurerbara undantag.
| Verktyg | Språk | Regel |
|---|---|---|
| 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 (aktiverad opt-in) |
| PMD | Java, Apex, PLSQL | MagicNumber (lista över tillåtna kan konfigureras) |
| PhpStorm Inspections | PHP | NumericLiteralWithContext (inbyggd inspektion) |
Konfiguration av undantag är kritisk — utan den kommer analysatorn att utfärda varningar vid varje ökning (-1, +1) och matematisk nolla. För SonarQube är listan över tillåtna tal: 0, 1, -1, 2 (för dubblering), 100 (procent), 60 och 24 (tid). För alla andra värden — kräv en namngiven konstant med modifieraren public static final (Java) eller const val (Kotlin).
För analys på CI-nivå, lägg till ett steg med kontroll av magi som en varning, men inte blockera bygget. Den första körningen kommer att visa hundratals varningar i äldre kod. Gradvis, ticket för ticket, migrera koden till konstanter och höj kvalitetströskeln. När antalet magic numbers blir mindre än 10 — aktivera regeln som byggfel.
Refaktorisering av magi — en av de säkraste operationerna: att ersätta en literal med en konstant ändrar inte kodens beteende. Trots detta måste tillvägagångssättet vara systematiskt för att inte missa dolda beroenden (till exempel om samma magic number används i orelaterade sammanhang men råkar ha samma värde).
Steg-för-steg-process: hitta alla förekomster av det magiska värdet, förstå sammanhanget för varje, dela upp i olika konstanter (även om värdena är samma — sammanhangen är olika och konstanterna måste namnges olika), ersätt literalerna med konstanter, kontrollera genom tester. Felet i steg 2 — det vanligaste: två olika begrepp (timeout i millisekunder och tröskel i byte) kan numeriskt sammanfalla (till exempel 5000), men semantiskt är de olika storheter och kan inte förenas i en konstant.
// före — samma tal i olika sammanhang
public class Config {
public void setupCache() {
cache.setMaxSize(5000); // 5 MB
}
public void setupTimeout() {
client.setReadTimeout(5000); // 5 sekunder
}
}
// efter — olika konstanter för olika sammanhang
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
);
}
}
För ny kod är regeln enkel: varje literal, förutom 0, 1, -1, true, false, null och tom sträng, extraheras till en konstant. Undantag: matematiska konstanter (alltid via standardbiblioteket), testdata (literal kan lämnas i testet, men med ett förklarande variabelnamn) och gränsvärden för ökning (i + 1 i en loop — normalt).
Vanliga frågor
Ja, 100 är också ett magic number om det används utan sammanhang. Skriv MAX_PERCENT eller PROBABILITY_SCALE istället för 100. Undantag: när 100 är en uppenbar procentsats i sammanhanget (till exempel i en procentberäkningsformel), men även i detta fall förbättrar en konstant läsbarheten.
I tester är det också bättre att använda namngivna variabler. Skriv val expected = 42; assertEquals(expected, result) istället för assertEquals(42, result). Undantag: tester på gränsvärden (0, null, tom sträng) — de kan lämnas som literal eftersom de är läsbara i testsammanhanget.
Ja, tal relaterade till användargränssnittet (storlekar, marginaler, animeringstid) bör vara i resurser (dimens.xml, integers.xml). Affärskonstanter (timeouter, gränser) — i companion object eller konfigurationsfil. Huvudkriteriet: om talet kan ändras utan att ändra logiken — är det en resurs.
Kör SonarQube med regeln MagicNumber eller ESLint med no-magic-numbers. Få en rapport, sortera efter användningsfrekvens och börja med tal som förekommer på tre eller fler ställen. De är med största sannolikhet kandidater för extrahering till konstant.
Nej. Tillåtna literal: 0, 1, -1 (ökning/minskning, tomhetskontroll), true, false, null, tom sträng. Alla andra kräver namngivning. Om talet 0 används inte som tomhetskontroll (till exempel 0 — är ID för rotkategorin), då måste 0 också vara en konstant: ROOT_CATEGORY_ID = 0.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också