Qu'est-ce qu'un nombre magique?
Pourquoi faut-il l'éviter?
Y a-t-il des cas où cela est approprié?
Qu'est-ce qu'un nombre magique?
Pourquoi faut-il l'éviter?
Y a-t-il des cas où cela est approprié?
Réponses:
Un nombre magique est une utilisation directe d'un nombre dans le code.
Par exemple, si vous avez (en Java):
public class Foo {
public void setPassword(String password) {
// don't do this
if (password.length() > 7) {
throw new InvalidArgumentException("password");
}
}
}
Cela devrait être refactorisé pour:
public class Foo {
public static final int MAX_PASSWORD_SIZE = 7;
public void setPassword(String password) {
if (password.length() > MAX_PASSWORD_SIZE) {
throw new InvalidArgumentException("password");
}
}
}
Il améliore la lisibilité du code et est plus facile à maintenir. Imaginez le cas où j'ai défini la taille du champ de mot de passe dans l'interface graphique. Si j'utilise un nombre magique, chaque fois que la taille maximale change, je dois changer dans deux emplacements de code. Si j'en oublie un, cela conduira à des incohérences.
Le JDK est plein d'exemples comme dans Integer
, Character
et les Math
classes.
PS: Des outils d'analyse statique comme FindBugs et PMD détectent l'utilisation de nombres magiques dans votre code et suggèrent le refactoring.
TRUE
/ FALSE
)
Un nombre magique est une valeur codée en dur qui peut changer à un stade ultérieur, mais qui peut donc être difficile à mettre à jour.
Par exemple, supposons que vous ayez une page qui affiche les 50 dernières commandes dans une page de présentation «Vos commandes». 50 est le nombre magique ici, car il n'est pas défini par une norme ou une convention, c'est un nombre que vous avez composé pour les raisons décrites dans la spécification.
Maintenant, ce que vous faites, c'est que vous avez les 50 à différents endroits - votre script SQL ( SELECT TOP 50 * FROM orders
), votre site Web (vos 50 dernières commandes), votre identifiant de commande ( for (i = 0; i < 50; i++)
) et éventuellement de nombreux autres endroits.
Maintenant, que se passe-t-il lorsque quelqu'un décide de passer de 50 à 25? ou 75? ou 153? Vous devez maintenant remplacer le 50 dans tous les endroits, et vous risquez de le manquer. Rechercher / Remplacer peut ne pas fonctionner, car 50 peut être utilisé pour d'autres choses, et le remplacement aveugle de 50 par 25 peut avoir d'autres effets secondaires néfastes (par exemple, votre Session.Timeout = 50
appel, qui est également défini sur 25 et les utilisateurs commencent à signaler des délais d'attente trop fréquents).
En outre, le code peut être difficile à comprendre, c'est-à-dire " if a < 50 then bla
" - si vous rencontrez cela au milieu d'une fonction compliquée, d'autres développeurs qui ne connaissent pas le code peuvent se demander "WTF is 50 ???"
C'est pourquoi il est préférable d'avoir de tels nombres ambigus et arbitraires à exactement 1 endroit - " const int NumOrdersToDisplay = 50
", car cela rend le code plus lisible (" if a < NumOrdersToDisplay
", cela signifie également que vous n'avez besoin de le changer qu'à 1 endroit bien défini.
Les endroits où les nombres magiques sont appropriés sont tout ce qui est défini par une norme, c'est-à-dire SmtpClient.DefaultPort = 25
ou TCPPacketSize = whatever
(je ne sais pas si cela est normalisé). De plus, tout ce qui n'est défini que dans 1 fonction peut être acceptable, mais cela dépend du contexte.
SmtpClient.DefaultPort = 25
est peut - être clair er que SmtpClient.DefaultPort = DEFAULT_SMTP_PORT
.
25
toute l'application et vous assurer que vous ne modifiez que les occurrences du 25
port SMTP, pas les 25 qui sont par exemple la largeur d'une colonne de tableau ou le nombre d'enregistrements à afficher sur une page.
IANA
.
Avez-vous jeté un œil à l'entrée Wikipedia pour le nombre magique?
Il va dans un peu de détails sur toutes les façons dont la référence magique est faite. Voici une citation sur le nombre magique comme une mauvaise pratique de programmation
Le terme nombre magique fait également référence à la mauvaise pratique de programmation consistant à utiliser des nombres directement dans le code source sans explication. Dans la plupart des cas, cela rend les programmes plus difficiles à lire, à comprendre et à maintenir. Bien que la plupart des guides fassent une exception pour les nombres zéro et un, c'est une bonne idée de définir tous les autres nombres dans le code en tant que constantes nommées.
Magie: sémantique inconnue
Constante symbolique -> Fournit à la fois un contexte sémantique et un contexte corrects à utiliser
Sémantique: le sens ou le but d'une chose.
"Créez une constante, nommez-la d'après la signification et remplacez-la par le nombre." - Martin Fowler
Premièrement, les nombres magiques ne sont pas seulement des nombres. Toute valeur de base peut être "magique". Les valeurs de base sont des entités manifestes telles que des entiers, des réels, des doubles, des flottants, des dates, des chaînes, des booléens, des caractères, etc. Le problème n'est pas le type de données, mais l'aspect "magique" de la valeur telle qu'elle apparaît dans notre texte de code.
Qu'entendons-nous par «magie»? Pour être précis: Par «magie», nous entendons pointer la sémantique (sens ou finalité) de la valeur dans le contexte de notre code; qu'il est inconnu, inconnaissable, peu clair ou déroutant. C'est la notion de "magie". Une valeur de base n'est pas magique lorsque sa signification sémantique ou son but d'être-là est rapidement et facilement connu, clair et compris (ne prêtant pas à confusion) à partir du contexte environnant sans mots auxiliaires spéciaux (par exemple, constante symbolique).
Par conséquent, nous identifions les nombres magiques en mesurant la capacité d'un lecteur de code à connaître, à être clair et à comprendre la signification et le but d'une valeur de base à partir de son contexte environnant. Moins le lecteur est connu, moins clair et confus, plus la valeur de base est "magique".
Nous avons deux scénarios pour nos valeurs de base magiques. Seul le second est de première importance pour les programmeurs et le code:
Une dépendance globale de la «magie» est la façon dont la valeur de base isolée (par exemple, le nombre) n'a pas de sémantique connue (comme Pi), mais a une sémantique connue localement (par exemple, votre programme), qui n'est pas entièrement claire du contexte ou pourrait être abusée dans un bon ou un mauvais contexte.
La sémantique de la plupart des langages de programmation ne nous permettra pas d'utiliser des valeurs de base isolées, sauf (peut-être) comme données (c'est-à-dire des tableaux de données). Lorsque nous rencontrons des «nombres magiques», nous le faisons généralement dans un contexte. Par conséquent, la réponse à
"Dois-je remplacer ce nombre magique par une constante symbolique?"
est:
"En combien de temps pouvez-vous évaluer et comprendre la signification sémantique du nombre (sa raison d'être là) dans son contexte?"
Avec cette pensée à l'esprit, nous pouvons rapidement voir comment un nombre comme Pi (3.14159) n'est pas un "nombre magique" lorsqu'il est placé dans le contexte approprié (par exemple 2 x 3.14159 x rayon ou 2 * Pi * r). Ici, le nombre 3,14159 est mentalement reconnu Pi sans l'identifiant de constante symbolique.
Pourtant, nous remplaçons généralement 3.14159 par un identificateur de constante symbolique comme Pi en raison de la longueur et de la complexité du nombre. Les aspects de longueur et de complexité de Pi (couplés à un besoin de précision) signifient généralement que l'identifiant symbolique ou la constante est moins sujet aux erreurs. La reconnaissance de "Pi" comme nom est simplement un bonus pratique, mais ce n'est pas la principale raison d'avoir la constante.
En mettant de côté les constantes communes comme Pi, concentrons-nous principalement sur les nombres ayant des significations spéciales, mais dont ces significations sont limitées à l'univers de notre système logiciel. Un tel nombre peut être "2" (comme valeur entière de base).
Si j'utilise le chiffre 2 seul, ma première question pourrait être: que signifie «2»? Le sens de "2" en lui-même est inconnu et inconnaissable sans contexte, laissant son utilisation peu claire et déroutante. Même si le fait de n'avoir que "2" dans notre logiciel ne se produira pas à cause de la sémantique du langage, nous voulons voir que "2" en lui-même n'a aucune sémantique particulière ou objectif évident étant seul.
Mettons notre seul "2" dans un contexte de:, padding := 2
où le contexte est un "GUI Container". Dans ce contexte, la signification de 2 (en pixels ou autre unité graphique) nous offre une estimation rapide de sa sémantique (signification et objectif). Nous pourrions nous arrêter ici et dire que 2 est correct dans ce contexte et qu'il n'y a rien d'autre que nous devons savoir. Cependant, peut-être que dans notre univers logiciel, ce n'est pas toute l'histoire. Il y a plus, mais "padding = 2" en tant que contexte ne peut pas le révéler.
Supposons en outre que 2 en tant que remplissage de pixels dans notre programme est de la variété "default_padding" dans tout notre système. Par conséquent, écrire l'instruction padding = 2
n'est pas suffisant. La notion de "défaut" n'est pas révélée. Ce n'est que lorsque j'écris: en padding = default_padding
tant que contexte, puis ailleurs: default_padding = 2
que je réalise pleinement une signification meilleure et plus complète (sémantique et objectif) de 2 dans notre système.
L'exemple ci-dessus est assez bon car "2" en soi pourrait être n'importe quoi. Ce n'est que lorsque nous limitons la plage et le domaine de compréhension à «mon programme», où 2 est default_padding
dans les parties GUI UX de «mon programme», que nous avons finalement un sens de «2» dans son contexte approprié. Ici "2" est un nombre "magique", qui est factorisé en une constante symbolique default_padding
dans le contexte de l'interface utilisateur graphique de "mon programme" afin de le faire utiliser comme default_padding
il est compris rapidement dans le contexte plus large du code englobant.
Ainsi, toute valeur de base, dont le sens (sémantique et finalité) ne peut être suffisamment et rapidement compris est un bon candidat pour une constante symbolique à la place de la valeur de base (par exemple le nombre magique).
Les nombres sur une échelle peuvent également avoir une sémantique. Par exemple, faites comme si nous faisions un jeu D&D, où nous avons la notion de monstre. Notre objet monstre a une fonctionnalité appelée life_force
, qui est un entier. Les nombres ont des significations qui ne sont pas connaissables ou claires sans mots pour fournir un sens. Ainsi, nous commençons par dire arbitrairement:
À partir des constantes symboliques ci-dessus, nous commençons à obtenir une image mentale de la vivacité, de la mort et des "morts-vivants" (et des ramifications ou conséquences possibles) pour nos monstres dans notre jeu D&D. Sans ces mots (constantes symboliques), il ne nous reste que les nombres allant de -10 .. 10
. Juste la plage sans les mots nous laisse dans un endroit de confusion possible et potentiellement avec des erreurs dans notre jeu si différentes parties du jeu ont des dépendances sur ce que cette plage de nombres signifie pour diverses opérations comme attack_elves
ou seek_magic_healing_potion
.
Par conséquent, lorsque vous recherchez et envisagez de remplacer des "nombres magiques", nous voulons poser des questions très précises sur les nombres dans le contexte de notre logiciel et même sur la façon dont les nombres interagissent sémantiquement entre eux.
Passons en revue les questions que nous devons poser:
Vous pourriez avoir un nombre magique si ...
Examinez les valeurs de base constantes manifestes autonomes dans votre texte de code. Posez chaque question lentement et avec réflexion sur chaque instance d'une telle valeur. Considérez la force de votre réponse. Souvent, la réponse n'est pas en noir et blanc, mais a des nuances de sens et de but mal compris, de vitesse d'apprentissage et de vitesse de compréhension. Il est également nécessaire de voir comment il se connecte à la machine logicielle qui l'entoure.
En fin de compte, la réponse au remplacement est de répondre à la mesure (dans votre esprit) de la force ou de la faiblesse du lecteur pour établir la connexion (par exemple, «obtenez-le»). Plus ils comprennent rapidement le sens et le but, moins vous avez de «magie».
CONCLUSION: Remplacez les valeurs de base par des constantes symboliques uniquement lorsque la magie est suffisamment grande pour provoquer des bogues difficiles à détecter résultant de confusions.
Un nombre magique est une séquence de caractères au début d'un format de fichier, ou échange de protocole. Ce numéro sert de test de santé mentale.
Exemple: ouvrez n'importe quel fichier GIF, vous verrez au tout début: GIF89. "GIF89" étant le nombre magique.
D'autres programmes peuvent lire les premiers caractères d'un fichier et identifier correctement les GIF.
Le danger est que les données binaires aléatoires puissent contenir ces mêmes caractères. Mais c'est très peu probable.
Quant à l'échange de protocoles, vous pouvez l'utiliser pour identifier rapidement que le «message» actuel qui vous est transmis est corrompu ou non valide.
Les nombres magiques sont toujours utiles.
En programmation, un "nombre magique" est une valeur à laquelle il faut donner un nom symbolique, mais qui a plutôt été glissé dans le code en tant que littéral, généralement à plusieurs endroits.
C'est mauvais pour la même raison que SPOT (Single Point of Truth) est bon: si vous voulez changer cette constante plus tard, vous devrez parcourir votre code pour trouver chaque instance. Il est également mauvais car il peut ne pas être clair pour les autres programmeurs ce que ce nombre représente, d'où la "magie".
Les gens poussent parfois l'élimination des nombres magiques plus loin, en déplaçant ces constantes dans des fichiers séparés pour servir de configuration. Ceci est parfois utile, mais peut également créer plus de complexité qu'il n'en vaut la peine.
(foo[i]+foo[i+1]+foo[i+2]+1)/3
peut être évaluée beaucoup plus rapidement qu'une boucle. Si l'on devait remplacer le 3
sans réécrire le code en boucle, quelqu'un qui a vu ITEMS_TO_AVERAGE
défini comme 3
pourrait comprendre qu'il pourrait le changer 5
et avoir le code en moyenne plus d'éléments. En revanche, quelqu'un qui a regardé l'expression avec le littéral se 3
rendrait compte que le 3
représente le nombre d'éléments à additionner.
Un nombre magique peut également être un nombre avec une sémantique spéciale codée en dur. Par exemple, j'ai vu une fois un système dans lequel les ID d'enregistrement> 0 étaient traités normalement, 0 lui-même était "nouvel enregistrement", -1 était "c'est la racine" et -99 était "cela a été créé à la racine". 0 et -99 obligerait le WebService à fournir un nouvel ID.
Ce qui est mauvais, c'est que vous réutilisez un espace (celui des entiers signés pour les ID d'enregistrement) pour des capacités spéciales. Peut-être que vous ne voudrez jamais créer un enregistrement avec l'ID 0, ou avec un ID négatif, mais même si ce n'est pas le cas, chaque personne qui regarde le code ou la base de données peut tomber dessus et être confuse au début. Il va sans dire que ces valeurs spéciales n'étaient pas bien documentées.
On peut dire que 22, 7, -12 et 620 comptent aussi comme des nombres magiques. ;-)
Un problème qui n'a pas été évoqué avec l'utilisation des nombres magiques ...
Si vous en avez beaucoup, les chances sont raisonnablement bonnes que vous ayez deux objectifs différents pour lesquels vous utilisez des nombres magiques, où les valeurs se trouvent être les mêmes.
Et puis, bien sûr, vous devez changer la valeur ... pour un seul but.
Je suppose que c'est une réponse à ma réponse à votre question précédente. En programmation, un nombre magique est une constante numérique intégrée qui apparaît sans explication. S'il apparaît à deux emplacements distincts, cela peut entraîner des circonstances dans lesquelles une instance est modifiée et non une autre. Pour ces deux raisons, il est important d'isoler et de définir les constantes numériques en dehors des endroits où elles sont utilisées.
J'ai toujours utilisé le terme «nombre magique» différemment, comme une valeur obscure stockée dans une structure de données qui peut être vérifiée comme un contrôle de validité rapide. Par exemple, les fichiers gzip contiennent 0x1f8b08 comme leurs trois premiers octets, les fichiers de classe Java commencent par 0xcafebabe, etc.
Vous voyez souvent des nombres magiques intégrés dans les formats de fichiers, car les fichiers peuvent être envoyés de manière plutôt promiscuité et perdre toutes les métadonnées sur la façon dont ils ont été créés. Cependant, les nombres magiques sont également parfois utilisés pour les structures de données en mémoire, comme les appels ioctl ().
Une vérification rapide du nombre magique avant de traiter le fichier ou la structure de données permet de signaler les erreurs tôt, plutôt que de passer à travers un traitement potentiellement long afin d'annoncer que l'entrée était complète.
Il convient de noter que, parfois, vous voulez des numéros «codés en dur» non configurables dans votre code. Il existe un certain nombre de célèbres, dont 0x5F3759DF qui est utilisé dans l'algorithme optimisé de racine carrée inverse.
Dans les rares cas où je trouve la nécessité d'utiliser de tels numéros magiques, je les définis comme const dans mon code et je documente pourquoi ils sont utilisés, comment ils fonctionnent et d'où ils viennent.
Qu'en est-il de l'initialisation d'une variable en haut de la classe avec une valeur par défaut? Par exemple:
public class SomeClass {
private int maxRows = 15000;
...
// Inside another method
for (int i = 0; i < maxRows; i++) {
// Do something
}
public void setMaxRows(int maxRows) {
this.maxRows = maxRows;
}
public int getMaxRows() {
return this.maxRows;
}
Dans ce cas, 15000 est un nombre magique (selon CheckStyles). Pour moi, définir une valeur par défaut est correct. Je ne veux pas avoir à faire:
private static final int DEFAULT_MAX_ROWS = 15000;
private int maxRows = DEFAULT_MAX_ROWS;
Cela rend-il la lecture plus difficile? Je n'ai jamais envisagé cela avant d'avoir installé CheckStyles.
static final
constantes sont exagérées lorsque vous les utilisez dans une seule méthode. Une final
variable déclarée en haut de la méthode est plus lisible à mon humble avis.
@ eed3si9n: Je dirais même que «1» est un nombre magique. :-)
Un principe lié aux nombres magiques est que chaque fait traité par votre code doit être déclaré exactement une fois. Si vous utilisez des nombres magiques dans votre code (comme l'exemple de longueur de mot de passe donné par @marcio, vous pouvez facilement finir par dupliquer ce fait, et lorsque votre compréhension de ce fait change, vous avez un problème de maintenance.
factorial n = if n == BASE_CASE then BASE_VALUE else n * factorial (n - RECURSION_INPUT_CHANGE); RECURSION_INPUT_CHANGE = 1; BASE_CASE = 0; BASE_VALUE = 1
Qu'en est-il des variables de retour?
Je trouve particulièrement difficile la mise en œuvre de procédures stockées .
Imaginez la prochaine procédure stockée (mauvaise syntaxe, je sais, juste pour montrer un exemple):
int procGetIdCompanyByName(string companyName);
Il renvoie l'ID de l'entreprise s'il existe dans une table particulière. Sinon, il renvoie -1. D'une certaine manière, c'est un nombre magique. Certaines des recommandations que j'ai lues jusqu'à présent disent que je devrai vraiment faire quelque chose comme ça:
int procGetIdCompanyByName(string companyName, bool existsCompany);
Soit dit en passant, que devrait-il retourner si l'entreprise n'existe pas? Ok: il définira existesCompany comme false , mais renverra également -1.
L'option Antoher consiste à créer deux fonctions distinctes:
bool procCompanyExists(string companyName);
int procGetIdCompanyByName(string companyName);
Une condition préalable pour la deuxième procédure stockée est donc que la société existe.
Mais j'ai peur de la simultanéité, car dans ce système, une entreprise peut être créée par un autre utilisateur.
En fin de compte, que pensez-vous de l'utilisation de ce type de «nombres magiques» qui sont relativement connus et sûrs pour dire que quelque chose échoue ou que quelque chose n'existe pas?
Un autre avantage de l'extraction d'un nombre magique en tant que constante donne la possibilité de documenter clairement les informations commerciales.
public class Foo {
/**
* Max age in year to get child rate for airline tickets
*
* The value of the constant is {@value}
*/
public static final int MAX_AGE_FOR_CHILD_RATE = 2;
public void computeRate() {
if (person.getAge() < MAX_AGE_FOR_CHILD_RATE) {
applyChildRate();
}
}
}
const myNum = 22; const number = myNum / 11;
ce moment, mes 11 pourraient être des gens ou des bouteilles de bière ou quelque chose comme ça, je changerais donc 11 en constante comme les habitants.