Élimination des nombres magiques: quand est-il temps de dire «non»?


36

Nous sommes tous conscients que les nombres magiques (valeurs codées en dur) peuvent causer des ravages dans votre programme, en particulier lorsqu'il est temps de modifier une section de code sans commentaires, mais où tracez-vous la ligne?

Par exemple, si vous avez une fonction qui calcule le nombre de secondes entre deux jours, remplacez-vous

seconds = num_days * 24 * 60 * 60

avec

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

À quel moment décidez-vous qu'il est tout à fait évident de comprendre la valeur codée en dur et laissez-la tranquille?


2
Pourquoi ne pas remplacer ce calcul par une fonction ou une macro pour que votre code finisse par ressemblerseconds = CALC_SECONDS(num_days);
FrustratedWithFormsDesigner

15
TimeSpan.FromDays(numDays).Seconds;
Personne

18
@oosterwal: avec cette attitude ( HOURS_PER_DAY will never need to be altered), vous ne coderez jamais pour les logiciels déployés sur Mars. : P
FrustratedWithFormsDesigner

23
J'aurais réduit le nombre de constantes à seulement SECONDS_PER_DAY = 86400. Pourquoi calculer quelque chose qui ne changera pas?
JohnFx

17
Qu'en est-il des secondes intercalaires?
Jean

Réponses:


40

Il y a deux raisons d'utiliser des constantes symboliques au lieu de littéraux numériques:

  1. Pour simplifier la maintenance si les nombres magiques changent. Cela ne s'applique pas à votre exemple. Il est extrêmement improbable que le nombre de secondes dans une heure ou le nombre d'heures dans une journée change.

  2. Pour améliorer la lisibilité. L'expression "24 * 60 * 60" est assez évidente pour presque tout le monde. "SECONDS_PER_DAY" l'est également, mais si vous recherchez un bogue, vous devrez peut-être vérifier que SECONDS_PER_DAY a été défini correctement. Il y a de la valeur dans la brièveté.

Pour les nombres magiques qui apparaissent exactement une fois et qui sont indépendants du reste du programme, décider de créer un symbole pour ce nombre est une question de goût. En cas de doute, créez un symbole.

Ne faites pas cela:

public static final int THREE = 3;

3
+1 @kevin cline: Je partage votre point de vue sur la brièveté, une chasse aux bogues. L'avantage supplémentaire que je vois à utiliser une constante nommée, en particulier lors du débogage, est que s'il est découvert qu'une constante a été définie de manière incorrecte, il vous suffit de modifier un morceau de code plutôt que de rechercher dans un projet entier toutes les occurrences de la mise en œuvre incorrecte. valeur.
Oosterwal

40
Ou même pire:publid final int FOUR = 3;
gablin

3
Oh mon Dieu, vous avez dû travailler avec le même gars avec qui j'avais déjà travaillé.
Rapidement maintenant

2
@ gablin: Pour être juste, il est très utile que les pubs aient des couvercles.
Alan Pearce

10
J'ai vu ceci: public static int THREE = 3;... note - non final!
Stephen C

29

Je garderais la règle de ne jamais avoir de nombres magiques.

Tandis que

seconds = num_days * 24 * 60 * 60

Est parfaitement lisible la plupart du temps, après avoir codé 10 heures par jour pendant trois ou quatre semaines en mode crunch

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

est beaucoup plus facile à lire.

La suggestion de FrustratedWithFormsDesigner est meilleure:

seconds = num_days * DAYS_TO_SECOND_FACTOR

ou même mieux

seconds = CONVERT_DAYS_TO_SECONDS(num_days)

Les choses cessent d'être évidentes lorsque vous êtes très fatigué. Code défensivement .


13
Entrer dans un mode crunch tel que vous le décrivez est un anti-modèle contre-productif qui devrait être évité. Les programmeurs atteignent une productivité maximale soutenue d’environ 35 à 40 heures par semaine.
mardi

4
@btilly Je suis tout à fait d'accord avec vous. Mais cela arrive, souvent en raison de facteurs externes.
Vitor Py

3
Je définis en général les constantes pour seconde, minute, jour et heure. Si rien d'autre n'est '30 * MINUTE ', c'est vraiment facile à lire et je sais que c'est un temps sans avoir à y penser.
Zachary K

9
@btilly: le pic correspond à 35-40 heures ou à un taux d'alcoolémie compris entre 0,129% et 0,138%. Je l'ai lu sur XKCD , alors ça doit être vrai!
Oosterwal

1
Si je voyais une constante comme HOURS_PER_DAY, je la supprimerais et vous humilierais publiquement devant vos pairs. Ok, je renoncerais peut-être à l'humiliation publique, mais je la supprimerais probablement.
Ed S.

8

Le temps de dire non est presque toujours. Il est parfois plus facile d'utiliser des nombres codés en dur, par exemple, dans la disposition de l'interface utilisateur: créer une constante pour le positionnement de chaque contrôle sur la fiche devient très fastidieux et fatigant, et si ce code est généralement géré par un concepteur d'interface utilisateur, il Peu importe. ... sauf si l'interface utilisateur est agencée de manière dynamique, ou utilise des positions relatives par rapport à une ancre ou est écrite à la main. Dans ce cas, je dirais qu'il est préférable de définir des constantes significatives pour la mise en page. Et si vous avez besoin d’un facteur de fudge ici ou là pour aligner / positionner quelque chose de "juste", vous devez également le définir.

Mais dans votre exemple, je pense que remplacer 24 * 60 * 60par, DAYS_TO_SECONDS_FACTORc'est mieux.


J'admets que les valeurs codées en dur conviennent également lorsque le contexte et l'utilisation sont parfaitement clairs. Ceci, cependant, est un appel de jugement ...

Exemple:

Comme @rmx l'a fait remarquer, utiliser 0 ou 1 pour vérifier si une liste est vide ou peut-être dans les limites d'une boucle est un exemple de cas où l'objectif de la constante est très clair.


2
Il est généralement bon d’utiliser 0ou 1je pense. if(someList.Count != 0) ...est meilleur que if(someList.Count != MinListCount) .... Pas toujours, mais en général.
Personne

2
@Dima: VS Form Designer gère tout cela. S'il veut créer des constantes, ça me va. Mais je ne vais pas dans le code généré et remplacer toutes les valeurs codées en dur avec des constantes.
FrustratedWithFormsDesigner

4
Ne confondons pas le code destiné à être généré et géré par un outil avec un code écrit pour la consommation humaine cependant.
biziclop

1
@FrustratedWithFormsDesigner, comme l'a souligné @biziclop, le code généré est un animal totalement différent. Les constantes nommées doivent absolument être utilisées dans du code lu et modifié par des personnes. Le code généré, du moins dans le cas idéal, ne devrait pas être modifié du tout.
Dima

2
@FrustratedWithFormsDesigner: que se passe-t-il lorsque vous avez une valeur bien connue codée en dur dans des dizaines de fichiers de votre programme qui doivent soudainement être modifiés? Par exemple, vous codez en dur la valeur représentant le nombre de ticks d'horloge par microseconde pour un processeur intégré, puis vous êtes invité à transférer votre logiciel vers une conception comportant un nombre différent de ticks d'horloge par microsecondes. Si votre valeur avait été quelque chose de commun, comme 8, effectuer une recherche / remplacement sur des dizaines de fichiers risquerait de causer plus de problèmes.
Oosterwal

8

Arrêtez-vous lorsque vous ne parvenez pas à donner un sens ou un but au nombre.

seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE

est beaucoup plus facile à lire que juste en utilisant les chiffres. (Bien que cela puisse être rendu plus lisible avec une seule SECONDS_PER_DAYconstante, mais c'est une question complètement distincte.)

Supposons qu'un développeur qui examine le code puisse voir ce qu'il fait. Mais ne supposez pas qu’ils savent aussi pourquoi. Si votre constante vous aide à comprendre le pourquoi, allez-y. Si non, ne le faites pas.

Si vous vous retrouvez avec trop de constantes, comme suggéré par une réponse, envisagez plutôt d'utiliser un fichier de configuration externe, car avoir des dizaines de constantes dans un fichier n'améliore pas vraiment la lisibilité.


7

Je dirais probablement "non" à des choses comme:

#define HTML_END_TAG "</html>"

Et sans aucun doute serait dire « non » à:

#define QUADRATIC_DISCRIMINANT_COEF 4
#define QUADRATIC_DENOMINATOR_COEF  2

7

L'un des meilleurs exemples que j'ai trouvé pour promouvoir l'utilisation de constantes pour des raisons évidentes HOURS_PER_DAYest le suivant:

Nous étions en train de calculer combien de temps les choses restaient dans la file d'attente d'une personne. Les exigences ont été définies de manière vague et le programmeur a été codé 24en dur à plusieurs endroits. Finalement, nous avons réalisé qu'il n'était pas juste de punir les utilisateurs pour s'être assis sur un problème pendant 24 heures alors que cela ne fonctionnait réellement que 8 heures par jour. Quand la tâche est venue de résoudre ce problème ET de voir quels autres rapports pourraient avoir le même problème, il était assez difficile de passer par grep / rechercher dans le code 24 aurait été beaucoup plus facile à rechercherHOURS_PER_DAY


oh non, les heures par jour varient donc selon que vous vous référez aux heures de travail par jour (7,5 BTW) ou aux heures de la journée. Pour changer le sens de la constante comme ça, vous voudriez en remplacer le nom par quelque chose d'autre. Votre remarque sur la recherche facile est cependant valable.
gbjbaanb

2
Il semble que dans ce cas, HOURS_PER_DAY n’était pas non plus la constante souhaitée. Mais pouvoir le rechercher par son nom est un avantage énorme, même si (ou surtout si) vous devez le remplacer par autre chose dans de nombreux endroits.
David K

4

Je pense que tant que le nombre est complètement constant et n’a aucune possibilité de changer, il est parfaitement acceptable. Donc, dans votre cas, tout seconds = num_days * 24 * 60 * 60va bien (en supposant bien sûr que vous ne faites pas quelque chose de stupide comme faire ce genre de calcul dans une boucle) et sans doute mieux pour la lisibilité que seconds = num_days * HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE.

C'est quand tu fais des choses comme ça que c'est mauvais:

lineOffset += 24; // 24 lines to a page

Même si vous ne pouvez plus insérer de lignes sur la page ou si vous n'avez pas l'intention de la modifier, utilisez plutôt une variable constante, car un jour, elle reviendra vous hanter. En fin de compte, le point essentiel est la lisibilité et non l’économie de 2 cycles de calcul sur le CPU. Ce n'est plus en 1978, lorsque de précieux octets ont été compressés pour toute leur valeur.


2
Diriez-vous qu'il est acceptable de coder en dur la valeur immuable 86400 au lieu d'utiliser la constante nommée SECONDS_PER_DAY? Comment pourriez-vous vérifier que toutes les occurrences de la valeur sont correctes et qu'il ne manque pas un 0 ou remplace le 6 ad 4, par exemple?
Oosterwal

Alors pourquoi pas: secondes = num_days * 86400? Cela ne changera pas non plus.
JeffO

2
J'ai fait la chose SECONDS_PER_DAY dans les deux sens - en utilisant le nom et le numéro. Lorsque vous revenez au code deux ans plus tard, le numéro nommé a TOUJOURS plus de sens.
Rapidement maintenant

2
secondes = num_days * 86400 n'est pas clair pour moi. C'est ce qui compte finalement. Si je vois "secondes = num_days * 24 * 60 * 60", mis à part le fait que les noms de variable donnent assez bien le sens dans ce cas, je me demande immédiatement pourquoi je les ai séparés et le sens devient évident car je suis parti en tant que nombres (donc constants), pas de variables pour lesquelles une enquête plus poussée nécessiterait que je comprenne leurs valeurs et si elles sont constantes.
Neil

1
Ce que les gens ne réalisent pas souvent: si vous changez la valeur de lineOffset de 24 à 25, vous devrez parcourir tout votre code pour voir où 24 a été utilisé et s'il doit être modifié, puis tous les calculs en jours en heures multiplier par 24 vraiment entrer dans votre chemin.
gnasher729

3
seconds = num_days * 24 * 60 * 60

Est parfaitement bien. Ce ne sont pas vraiment des nombres magiques car ils ne changeront jamais.

Tous les nombres qui peuvent raisonnablement changer ou qui n’ont pas de signification évidente doivent être intégrés aux variables. Ce qui signifie à peu près tous.


2
Serait seconds = num_days * 86400toujours acceptable? Si une telle valeur était utilisée plusieurs fois dans de nombreux fichiers, comment pourriez-vous vérifier que quelqu'un n'a pas saisi accidentellement seconds = num_days * 84600à un ou deux endroits?
Oosterwal

1
L'écriture 86400 est très différente de l'écriture 24 * 60 * 60.
Carra

4
Bien sûr ça va changer. Chaque jour compte 86 400 secondes. Prenons, par exemple, l'heure avancée. Une fois par an, certains endroits ont seulement 23 heures par jour, et un autre jour ils auront 25 poof vos numéros sont cassés.
Dave DeLong

1
@ Dave, excellent point. Il existe des secondes

Bon point. L'ajout d'une fonction constituerait une garantie si vous devez intercepter ces exceptions.
Carra

3

J'éviterais de créer des constantes (valeurs magiques) pour convertir une valeur d'une unité à une autre. En cas de conversion, je préfère un nom de méthode parlante. Dans cet exemple, cela serait par exemple DayToSeconds(num_days)interne, la méthode n'a pas besoin de valeurs magiques car la signification de "24" et "60" est claire.

Dans ce cas, je n’utiliserais jamais secondes / minutes / heures. Je voudrais seulement utiliser TimeSpan / DateTime.


1

Utiliser le contexte comme paramètre pour décider

Par exemple, vous avez une fonction appelée "CalculateSecondsBetween: aDay et: anotherDay", vous n'avez pas besoin de beaucoup expliquer ce que font ces nombres, car le nom de la fonction est assez représentatif.

Et une autre question est, quelles sont les possibilités de le calculer différemment? Parfois, il y a beaucoup de façons de faire la même chose, alors pour guider les futurs programmeurs et leur montrer quelle méthode vous avez utilisée, la définition de constantes pourrait aider à le comprendre.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.