Code propre - Dois-je changer le littéral 1 en une constante?

14

Pour éviter les nombres magiques, nous entendons souvent dire que nous devons donner à un littéral un nom significatif. Tel que:

//THIS CODE COMES FROM THE CLEAN CODE BOOK
for (int j = 0; j < 34; j++) {
    s += (t[j] * 4) / 5;
}

-------------------- Change to --------------------

int realDaysPerIdealDay = 4;
const int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for (int j = 0; j < NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realdays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

J'ai une méthode factice comme celle-ci:

Expliquez: je suppose que j'ai une liste de personnes à servir et par défaut, nous dépensons 5 $ pour acheter de la nourriture uniquement, mais lorsque nous avons plus d'une personne, nous devons acheter de l'eau et de la nourriture, nous devons dépenser plus d'argent, peut-être 6 $. Je vais changer mon code, veuillez vous concentrer sur le littéral 1 , ma question à ce sujet.

public int getMoneyByPersons(){
    if(persons.size() == 1){ 
        // TODO - return money for one person
    } else {
        // TODO - calculate and return money for people.
    }

}

Quand j'ai demandé à mes amis de revoir mon code, l'un a dit que donner un nom à la valeur 1 donnerait un code plus propre, et l'autre a dit que nous n'avons pas besoin d'un nom constant ici parce que la valeur est significative en soi.

Donc, ma question est: Dois-je donner un nom à la valeur littérale 1? Quand une valeur est-elle un nombre magique et quand ne l'est-elle pas? Comment distinguer le contexte pour choisir la meilleure solution?

Jack
la source
D'où personsvient-il et que décrit-il? Votre code n'a aucun commentaire, il est donc difficile de deviner ce qu'il fait.
Hubert Grzeskowiak
7
Cela ne devient pas plus clair que 1. Je changerais cependant "taille" en "nombre". Et moneyService est aussi stupide qu'il soit, il devrait pouvoir décider combien d'argent retourner en fonction de la collecte des personnes. Donc, je passais des personnes pour obtenir de l'argent et je laissais régler tous les cas exceptionnels.
Martin Maat
4
Vous pouvez également extraire l'expression logique de votre clause if vers une nouvelle méthode. Peut-être l'appeler IsSinglePerson (). De cette façon, vous extrayez un peu plus que cette seule variable, mais vous rendez également la clause if un peu plus lisible ...
selmaohneh
2
Peut-être que cette logique conviendrait mieux dans moneyService.getMoney ()? Y aurait-il un moment où vous auriez besoin d'appeler getMoney dans le cas d'une personne? Mais je suis d'accord avec le sentiment général que 1 est clair. Les nombres magiques sont des nombres où vous auriez à vous gratter la tête en demandant comment le programmeur est arrivé à ce nombre .. ieif(getErrorCode().equals(4095)) ...
Neil

Réponses:

23

Non. Dans cet exemple, 1 est parfaitement significatif.

Cependant, que se passe-t-il si persons.size () vaut zéro? Cela semble étrange qui persons.getMoney()fonctionne pour 0 et 2 mais pas pour 1.

user949300
la source
Je suis d'accord que 1 est significatif. Merci, au fait, j'ai mis à jour ma question. Vous pouvez le revoir pour avoir mon idée.
Jack
3
Une phrase que j'ai entendu plusieurs fois au fil des ans est que les nombres magiques sont des constantes non nommées autres que 0 et 1. Bien que je puisse potentiellement penser à des exemples où ces nombres devraient être nommés, c'est une règle assez simple de suivre 99% des temps.
Baldrickk
@Baldrickk Parfois, d'autres littéraux numériques ne doivent pas non plus être cachés derrière un nom.
Déduplicateur
@Deduplicator des exemples vous viennent à l'esprit?
Baldrickk
@Baldrickk Voir amon .
Déduplicateur
18

Pourquoi un morceau de code contient-il cette valeur littérale particulière?

  • Cette valeur a-t-elle une signification particulière dans le domaine problématique ?
  • Ou cette valeur n'est-elle qu'un détail d'implémentation , où cette valeur est une conséquence directe du code environnant?

Si la valeur littérale a une signification qui ne ressort pas clairement du contexte, alors oui, il est utile de donner un nom à cette valeur via une constante ou une variable. Plus tard, lorsque le contexte d'origine est oublié, le code avec des noms de variable significatifs sera plus facile à gérer. N'oubliez pas que le public de votre code n'est pas principalement le compilateur (le compilateur fonctionnera avec plaisir avec du code horrible), mais les futurs responsables de ce code - qui apprécieront si le code s'explique un peu.

  • Dans votre premier exemple, le sens de littéraux tels que 34, 4, 5ne ressort pas du contexte. Au lieu de cela, certaines de ces valeurs ont une signification particulière dans votre domaine problématique. Il était donc bon de leur donner des noms.

  • Dans votre deuxième exemple, la signification du littéral 1est très claire à partir du contexte. Introduire un nom n'est pas utile.

En fait, l'introduction de noms pour des valeurs évidentes peut également être mauvaise car elle masque la valeur réelle.

  • Cela peut masquer les bogues si la valeur nommée est modifiée ou était incorrecte, surtout si la même variable est réutilisée dans des morceaux de code non liés.

  • Un morceau de code peut également fonctionner correctement pour une valeur spécifique, mais peut être incorrect dans le cas général. En introduisant une abstraction inutile, le code n'est plus évidemment correct.

Il n'y a pas de limite de taille sur les littéraux «évidents» car cela dépend totalement du contexte. Par exemple, le littéral 1024peut être totalement évident dans le contexte du calcul de la taille du fichier, ou le littéral 31dans le contexte d'une fonction de hachage, ou le littéral padding: 0.5emdans le contexte d'une feuille de style CSS.

amon
la source
8

Il y a plusieurs problèmes avec ce morceau de code, qui, soit dit en passant, peut être raccourci comme ceci:

public List<Money> getMoneyByPersons() {
    return persons.size() == 1 ?
        moneyService.getMoneyIfHasOnePerson() :
        moneyService.getMoney(); 
}
  1. On ne sait pas pourquoi une personne est un cas spécial. Je suppose qu'il existe une règle commerciale spécifique qui dit qu'obtenir de l'argent d'une seule personne est radicalement différent de recevoir de l'argent de plusieurs personnes. Cependant, je dois aller voir à l'intérieur des deux getMoneyIfHasOnePersonet getMoney, en espérant comprendre pourquoi il y a des cas distincts.

  2. Le nom getMoneyIfHasOnePersonne semble pas correct. D'après le nom, je m'attendrais à ce que la méthode vérifie s'il y a une seule personne et, si c'est le cas, obtienne de l'argent de sa part; sinon, ne faites rien. D'après votre code, ce n'est pas ce qui se passe (ou vous faites la condition deux fois).

  3. Y a-t-il une raison de retourner une collection List<Money>plutôt qu'une collection?

Revenons à votre question, car il n'est pas clair pourquoi il existe un traitement spécial pour une personne, le chiffre un devrait être remplacé par une constante, sauf s'il existe un autre moyen de rendre les règles explicites. Ici, on n'est pas très différent de tout autre nombre magique. Vous pourriez avoir des règles commerciales indiquant que le traitement spécial s'applique à une, deux ou trois personnes, ou seulement à plus de douze personnes.

Comment distinguer le contexte pour choisir la meilleure solution?

Vous faites tout ce qui rend votre code plus explicite.

Exemple 1

Imaginez le morceau de code suivant:

if (sequence.size() == 0) {
    return null;
}

return this.processSequence(sequence);

Le zéro est-il ici une valeur magique? Le code est assez clair: s'il n'y a pas d'éléments dans la séquence, ne la traitons pas et retournons une valeur spéciale. Mais ce code peut également être réécrit comme ceci:

if (sequence.isEmpty()) {
    return null;
}

return this.processSequence(sequence);

Ici, pas plus constant, et le code est encore plus clair.

Exemple 2

Prenez un autre morceau de code:

const result = Math.round(input * 1000) / 1000;

Il ne faut pas trop de temps pour comprendre ce qu'il fait dans des langages tels que JavaScript qui n'ont pas de round(value, precision)surcharge.

Maintenant, si vous voulez introduire une constante, comment l'appellerait-elle? Le terme le plus proche que vous pouvez obtenir est Precision. Donc:

const precision = 1000;
const result = Math.round(input * precision) / precision;

Améliore-t-elle la lisibilité? Ça pourrait être. Ici, la valeur d'une constante est plutôt limitée et vous pouvez vous demander si vous avez vraiment besoin d'effectuer le refactoring. La bonne chose ici est que maintenant, la précision n'est déclarée qu'une seule fois, donc si elle change, vous ne risquez pas de faire une erreur telle que:

const result = Math.round(input * 100) / 1000;

changer la valeur dans un endroit et oublier de le faire dans l'autre.

Exemple 3

À partir de ces exemples, vous pouvez avoir l'impression que les nombres doivent être remplacés par des constantes dans tous les cas . Ce n'est pas vrai. Dans certaines situations, avoir une constante ne conduit pas à une amélioration du code.

Prenez le morceau de code suivant:

class Point
{
    ...
    public void Reset()
    {
        x, y = (0, 0);
    }
}

Si vous essayez de remplacer les zéros par une variable, la difficulté serait de trouver un nom significatif. Comment le nommeriez-vous? ZeroPosition? Base? Default? L'introduction d'une constante ici n'améliorerait en rien le code. Cela le rendrait légèrement plus long, et juste cela.

De tels cas sont cependant rares. Donc, chaque fois que vous trouvez un numéro dans le code, faites l'effort d'essayer de trouver comment le code peut être refactorisé. Demandez-vous s'il y a une signification commerciale au nombre. Si oui, une constante est obligatoire. Si non, comment nommeriez-vous le numéro? Si vous trouvez un nom significatif, c'est parfait. Sinon, vous avez probablement trouvé un cas où la constante n'est pas nécessaire.

Arseni Mourzenko
la source
J'ajouterais 3a: N'utilisez pas le mot argent dans les noms de variables, utilisez le montant, le solde ou similaire. Une collecte d'argent n'a pas de sens, contrairement à la collecte de montants ou de soldes. (D'accord, j'ai une petite collection d'argent étranger (ou plutôt de pièces), mais c'est une question différente de non-programmation).
Bent
3

Vous pouvez créer une fonction qui prend un seul paramètre et renvoie des temps quatre divisés par cinq qui offrent un alias propre dans ce qu'elle fait tout en utilisant le premier exemple.

Je propose juste ma propre stratégie habituelle mais peut-être que j'apprendrai aussi quelque chose.

Ce que je pense, c'est.

// Just an example name  
function normalize_task_value(task) {  
    return (task * 4) / 5;  // or to `return (task * 4) / NUMBER_OF_TASKS` but it really matters what logic you want to convey and there are reasons to many ways to make this logical. A comment would be the greatest improvement

}  

// Is it possible to just use tasks.length or something like that?  
// this NUMBER_OF_TASKS is the only one thats actually tricky to   
// understand right now how it plays its role.  

function normalize_all_task_values(tasks, accumulator) {  
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {  
        accumulator += normalize_task_value(tasks[i]);
    }
}  

Désolé si je suis hors de la base mais je suis juste un développeur javascript. Je ne sais pas quelle composition j'ai justifié, je suppose que tout ne doit pas être dans un tableau ou une liste, mais .longueur aurait beaucoup de sens. Et le * 4 ferait la bonne constante puisque son origine est nébuleuse.

etisdew
la source
5
Mais que signifient ces valeurs 4 et 5? Si l'un d'entre eux devait changer, pourriez-vous trouver tous les endroits où le numéro est utilisé dans un contexte similaire et mettre à jour ces emplacements en conséquence? C'est le nœud de la question d'origine.
Bart van Ingen Schenau
Je pense que le premier exemple était assez explicite pour que je ne puisse pas répondre. L'opération ne devrait pas changer à l'avenir, lorsque vous en aurez besoin, j'en rédigerai une nouvelle pour accomplir ce qui est nécessaire. Je pense que les commentaires seraient les plus utiles à cette fin. C'est un appel d'une ligne pour réduire dans ma langue. Dans quelle mesure est-il important de rendre tout à fait compréhensible pour le profane?
etisdew
@BartvanIngenSchenau La fonction entière est mal nommée. Je préférerais un meilleur nom de fonction, un commentaire avec une spécification de ce que la fonction est censée retourner, et un commentaire pourquoi * 4/5 y parvient. Les noms constants ne sont pas vraiment nécessaires.
gnasher729
@ gnasher729: Si les constantes apparaissent exactement une fois dans le code source d'une fonction bien nommée et bien documentée, il peut ne pas être nécessaire d'utiliser une constante nommée. Dès qu'une constante apparaît plusieurs fois avec la même signification, le fait de donner un nom à cette constante vous évite d'avoir à déterminer si toutes ces instances du littéral 42 signifient la même chose ou non.
Bart van Ingen Schenau
Au cœur de celui-ci, si vous vous attendez, vous devrez peut-être modifier la valeur oui. Je le changerais cependant en const pour représenter la variable. Je ne pense pas que ce soit le cas et cela devrait simplement être une variable intermédiaire résidant dans la portée au-dessus d'elle. Je ne veux pas me contenter de dire que tout est un paramètre d'une fonction, mais si cela peut changer, mais rarement, je le ferais comme un paramètre interne à cette fonction ou à la portée objet / structure où vous travaillez actuellement pour effectuer ce changement en laissant la portée mondiale seule.
etisdew
2

Ce numéro 1, pourrait-il être un numéro différent? Pourrait-il être 2 ou 3, ou y a-t-il des raisons logiques pour lesquelles il doit être 1? S'il doit être égal à 1, alors utiliser 1 suffit. Sinon, vous pouvez définir une constante. N'appelez pas cette constante UN. (J'ai vu cela fait).

60 secondes en une minute - avez-vous besoin d'une constante? Eh bien, il est de 60 secondes, pas 50 ou 70. Et tout le monde le sait. Cela peut donc rester un certain nombre.

60 éléments imprimés par page - ce nombre aurait pu facilement être 59, 55 ou 70. En fait, si vous changez la taille de la police, elle pourrait devenir 55 ou 70. Donc, ici, une constante significative est plus demandée.

C'est aussi une question de clarté du sens. Si vous écrivez "minutes = secondes / 60", c'est clair. Si vous écrivez "x = y / 60", ce n'est pas clair. Il doit y avoir certains noms significatifs quelque part.

Il y a une règle absolue: il n'y a pas de règles absolues. Avec de la pratique, vous saurez quand utiliser des nombres et quand utiliser des constantes nommées. Ne le faites pas parce qu'un livre le dit - jusqu'à ce que vous compreniez pourquoi il dit cela.

gnasher729
la source
"60 secondes dans une minute ... Et tout le monde le sait. Alors ça peut rester un chiffre." - Pas un argument valable pour moi. Que faire si j'ai 200 emplacements dans mon code où "60" est utilisé? Comment puis-je trouver les endroits où il est utilisé en quelques secondes par rapport à d'autres utilisations éventuellement "naturelles" de celui-ci? - En pratique, cependant, j'ose moi aussi utiliser minutes*60, parfois même hours*3600quand j'en ai besoin sans déclarer de constantes supplémentaires. Pendant des jours, j'écrirais probablement d*24*3600ou d*24*60*60parce que 86400c'est près du bord où quelqu'un ne reconnaîtra pas ce nombre magique d'un coup d'œil.
JimmyB
@JimmyB Si vous utilisez "60" sur 200 emplacements dans votre code, il est très probable que la solution consiste à refactoriser une fonction plutôt qu'à introduire une constante.
klutt
0

J'ai vu une bonne quantité de code comme dans l'OP (modifié) dans les récupérations de bases de données. La requête renvoie une liste, mais les règles métier indiquent qu'il ne peut y avoir qu'un seul élément. Et puis, bien sûr, quelque chose a changé pour «juste ce cas» en une liste avec plus d'un élément. (Ouais, j'ai dit pas mal de fois. C'est presque comme s'ils ... nm)

Donc, plutôt que de créer une constante, je créerais (dans une méthode de code propre) une méthode pour donner un nom ou préciser ce que le conditionnel est censé détecter (et résumer comment il le détecte):

public int getMoneyByPersons(){
  if(isSingleDepartmentHead()){ 
    // TODO - return money for one person
  } else {
    // TODO - calculate and return money for people.
  }
}

public boolean isSingleDepartmentHead() {
   return persons.size() == 1;
}
Kristian H
la source
Il est de loin préférable d'unifier la manipulation. Une liste de n éléments peut être traitée uniformément quel que soit n.
Déduplicateur
J'ai vu que ce n'était pas le cas, où le cas unique était, en fait, une valeur (marginale) différente de ce qu'elle aurait été si le même utilisateur avait fait partie d'une liste de taille n. Dans un OO bien fait, cela pourrait ne pas être un problème, mais, lorsqu'il s'agit de systèmes hérités, une bonne implémentation d'OO n'est pas toujours réalisable. Le meilleur que nous ayons obtenu est une façade OO raisonnable sur la logique DAO.
Kristian H