La magie en programmation n'est pas une métaphore, mais un terme précis désignant des valeurs (nombres, chaînes, drapeaux) dont le sens n'est pas évident dans le contexte et nécessite des connaissances externes pour être compris. Le type de magie le plus courant est celui des magic numbers : des constantes numériques écrites directement dans le code sans explication de pourquoi cette valeur particulière a été choisie. Selon le Rapport sur la qualité du code de SonarSource (2025), environ 8 pour cent de tous les avertissements des analyseurs statiques sont liés à des littéraux non expliqués. Les valeurs magiques rendent le code fragile : les modifier nécessite de trouver toutes les occurrences, et un nouveau développeur ne sait pas si le nombre peut être modifié ou s'il est critique pour le fonctionnement du système.
Points clés
Magie est toute valeur dans le code source dont le sens n'est pas évident sans connaissance supplémentaire du domaine. Le terme est établi dans la communauté : si un développeur regarde un nombre et ne peut pas dire d'où il vient — c'est de la magie.
La magie se présente sous plusieurs types : numérique (magic numbers), textuel (magic strings), booléen (magic flags) et de configuration (paramètres codés en dur qui devraient être dans les paramètres). Les quatre types partagent un problème : lorsqu'une exigence change, le développeur doit trouver chaque endroit où la valeur est utilisée et les remplacer manuellement. Manquer une seule occurrence entraîne un bogue.
Selon l'Enquête sur la qualité du code de JetBrains (2025), 73 pour cent des développeurs considèrent les magic numbers comme un indicateur de faible qualité de code, tandis que 41 pour cent admettent qu'ils les laissent occasionnellement. La raison principale est la précipitation : « Je mettrai la constante plus tard » — mais ce plus tard n'arrive jamais, et un mois plus tard, le nombre 0,85 reste au milieu d'un corps de méthode sans explication.
La règle clé : toute valeur littérale sauf 0, 1, true, false et chaîne vide doit être extraite dans une constante nommée. Exceptions : incrément de compteur (i + 1), zéros mathématiques (vérification de 0) et valeurs initiales d'accumulateurs. Tout le reste est candidat à la dénomination.
Un magic number est un littéral numérique dont la valeur n'est pas évidente dans le contexte. Un exemple classique : 86400 dans du code lié au timeout. Un développeur voit le nombre et doit deviner qu'il s'agit du nombre de secondes dans une journée. S'il se trompe et écrit 84600, le bogue sera difficile à détecter car le timeout se déclenchera 18 minutes plus tôt.
Pourquoi les magic numbers sont dangereux : premièrement, ils nuisent à la lisibilité. Le nombre 1024 peut signifier une taille de kilooctet, un seuil de pagination ou le nombre maximum d'éléments. Sans contexte — ce n'est qu'un nombre. Deuxièmement, ils créent des doublons : si 1024 est utilisé à cinq endroits, lorsque le seuil passe à 2048, le développeur doit trouver les cinq et les remplacer. Si un endroit est manqué, le système fonctionne incorrectement mais sans erreur explicite.
// before - magic in its pure form
fun calculateTimeout(base: Int): Int {
return base * 3 + 5000
}
// after - values replaced with constants
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
}
Le troisième danger est l'impossibilité de tester. Si une valeur seuil est codée en dur comme littéral, le test ne peut pas la surcharger pour vérifier les conditions limites. Une constante extraite dans un companion object ou un fichier de configuration rend le code testable : le test remplace une valeur différente et vérifie le comportement du système à la limite.
Développez une habitude : chaque fois que vous écrivez un nombre autre que 0, 1, 100 ou 2 — arrêtez-vous et demandez-vous s'il devrait être extrait dans une constante. Si le nombre est lié à la logique métier (limite, seuil, timeout, taille) — extrayez-le sans hésitation. Si le nombre est une constante mathématique (pi, e) — utilisez la bibliothèque standard (Math.PI, Math.E).
Les chaînes magiques sont des littéraux de chaîne intégrés dans le code sans être extraits dans des constantes ou des ressources. Exemples typiques : URLs d'endpoints, noms de clés SharedPreferences, Intent Actions, clés de bundle, noms de fichiers et requêtes SQL.
Le danger des chaînes magiques est l'absence de vérification à la compilation. Une faute de frappe dans la chaîne « user_prefs » ne sera détectée qu'à l'exécution. Si la chaîne est utilisée à dix endroits et que le développeur écrit « user_pref » (sans le s) à l'un d'eux — l'application ne plante pas, mais les données ne sont pas sauvegardées. Un tel bogue peut vivre en production pendant des mois car il ne provoque pas de plantage.
Pour les projets Android, les chaînes magiques doivent être extraites dans des ressources (strings.xml, arrays.xml) ou des constantes dans un companion object. Pour iOS — dans des ressources de chaîne (Localizable.strings) ou des constantes d'énumération. Pour le backend — dans des fichiers de configuration (.env, application.properties). Aucune clé, URL ou chemin ne doit apparaître dans le code comme littéral de chaîne.
// before - magic strings across the class
let prefs = UserDefaults.standard
prefs.set(token, forKey: "auth_token")
prefs.set(userId, forKey: "current_user_id")
// after - strings extracted to 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)
Portez une attention particulière aux chaînes qui sont dupliquées. Si la même clé « user_settings » apparaît dans trois fichiers — dans 99 pour cent des cas, une faute de frappe finira par apparaître dans l'un d'eux. L'extraction dans une énumération ou une constante garantit que toutes les références utilisent la même valeur.
Les magic flags sont des paramètres booléens dont le sens n'est pas évident dans le contexte d'appel. Un anti-patron classique : passer true ou false à une méthode sans expliquer ce que ce drapeau active ou désactive exactement.
Exemple : userDao.fetch(includeDeleted = false). Un développeur voit false et ne peut pas dire si cela signifie « ne pas inclure les supprimés » ou « ne pas inclure les actifs ». Un mois plus tard, false se transforme en true, et des enregistrements supprimés commencent à apparaître dans les résultats. Le bogue n'est découvert qu'en production.
La solution consiste à remplacer les drapeaux booléens par une énumération ou une classe scellée. Au lieu d'un paramètre Boolean, utilisez UserFilter.includeDeleted ou UserFilter.activeOnly. Ainsi, le code documente son intention et l'IDE suggère les options disponibles lors de l'autocomplétion.
Si un drapeau booléen est passé à travers plusieurs couches — c'est un autre signe que l'abstraction est erronée. Au lieu de traîner un drapeau à travers trois niveaux d'appels, demandez-vous si le choix du filtre ne devrait pas être effectué au niveau supérieur et passé comme une configuration prête. Moins il y a de drapeaux booléens dans le code — moins il y a de magie.
Adoptez une règle : aucun paramètre booléen n'est passé à une méthode sans argument nommé (si le langage prend en charge les arguments nommés). En Kotlin et Swift, cette exigence est automatique. En Java, utilisez Builder ou des constantes d'énumération au lieu de true/false.
La recherche de valeurs magiques est automatisée par des analyseurs statiques configurés pour détecter les littéraux dans des endroits inattendus. Chaque langage propose ses propres outils avec des exceptions personnalisables.
| Outil | Langages | Règle |
|---|---|---|
| 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 (opt-in) |
| PMD | Java, Apex, PLSQL | MagicNumber (liste autorisée configurable) |
| Inspections PhpStorm | PHP | NumericLiteralWithContext (inspection intégrée) |
La configuration des exceptions est cruciale — sans cela, l'analyseur signalera chaque incrément (-1, +1) et zéro mathématique. Pour SonarQube, la liste des nombres autorisés : 0, 1, -1, 2 (pour le doublement), 100 (pourcentages), 60 et 24 (temps). Pour toutes les autres valeurs — exigez une constante nommée avec le modificateur public static final (Java) ou const val (Kotlin).
Pour l'analyse au niveau CI, ajoutez une étape qui vérifie la magie comme avertissement mais ne bloque pas la compilation. La première exécution montrera des centaines d'avertissements dans le code hérité. Progressivement, ticket par ticket, migrez le code vers des constantes et augmentez le seuil de qualité. Lorsque le nombre de magic numbers devient inférieur à 10 — activez la règle comme erreur de compilation.
Le refactoring de la magie est l'une des opérations les plus sûres : remplacer un littéral par une constante ne change pas le comportement du code. Néanmoins, l'approche doit être systématique pour ne pas manquer des dépendances cachées (par exemple, si le même magic number est utilisé dans des contextes non liés mais a par coïncidence la même valeur).
Processus étape par étape : trouvez toutes les occurrences de la valeur magique, comprenez le contexte de chacune, répartissez-les dans différentes constantes (même si les valeurs coïncident — les contextes sont différents, et les constantes doivent avoir des noms différents), remplacez les littéraux par des constantes, vérifiez via des tests. L'erreur à l'étape 2 est la plus courante : deux concepts différents (un timeout en millisecondes et un seuil en octets) peuvent coïncider numériquement (par exemple, 5000), mais sémantiquement ce sont des quantités différentes et elles ne peuvent pas être combinées en une seule constante.
// before - same number in different contexts
public class Config {
public void setupCache() {
cache.setMaxSize(5000); // 5 MB
}
public void setupTimeout() {
client.setReadTimeout(5000); // 5 seconds
}
}
// after - different constants for different contexts
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
);
}
}
Pour le nouveau code, la règle est simple : tout littéral sauf 0, 1, -1, true, false, null et chaîne vide est extrait dans une constante. Exceptions : constantes mathématiques (toujours utiliser la bibliothèque standard), données de test (les littéraux peuvent rester dans les tests mais avec un nom de variable descriptif) et valeurs limites pour l'incrément (i + 1 dans une boucle est acceptable).
Questions fréquentes
Oui, 100 est aussi un magic number s'il est utilisé sans contexte. Au lieu de 100, écrivez MAX_PERCENT ou PROBABILITY_SCALE. Exception : lorsque 100 est évidemment un pourcentage dans le contexte (par exemple, dans une formule de calcul de pourcentage), mais même dans ce cas, une constante améliore la lisibilité.
Dans les tests, il est également préférable d'utiliser des variables nommées. Au lieu de assertEquals(42, result), écrivez val expected = 42; assertEquals(expected, result). Exception : les tests de valeurs limites (0, null, chaîne vide) — ils peuvent rester comme littéraux car ils sont lisibles dans le contexte du test.
Oui, les nombres liés à l'UI (tailles, marges, durée d'animation) doivent être dans les ressources (dimens.xml, integers.xml). Les constantes métier (timeouts, limites) — dans un companion object ou un fichier de configuration. Le critère principal : si un nombre peut changer sans modifier la logique — c'est une ressource.
Exécutez SonarQube avec la règle MagicNumber ou ESLint avec no-magic-numbers. Obtenez le rapport, triez par fréquence d'utilisation et commencez par les nombres qui apparaissent dans trois endroits ou plus. Ce sont les candidats les plus probables pour une extraction en constantes.
Non. Les littéraux acceptables : 0, 1, -1 (incrément/décrément, vérification de vide), true, false, null, chaîne vide. Tous les autres nécessitent une dénomination. Si le nombre 0 n'est pas utilisé comme vérification de vide (par exemple, 0 est l'ID de la catégorie racine), alors 0 doit aussi être une constante : ROOT_CATEGORY_ID = 0.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi