web-dev-qa-db-fra.com

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

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 puis-je distinguer le contexte pour choisir le meilleure solution?

14
Jack

Non. Dans cet exemple, 1 est parfaitement significatif.

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

23
user949300

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 est-ce que cette valeur est juste 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 variables significatifs sera plus facile à gérer. Rappelez-vous 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 de lui-même.

  • Dans votre premier exemple, la signification de littéraux tels que 34, 4, 5 ne 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 1 est très clair d'après le 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 1024 peut être totalement évident dans le contexte du calcul de la taille du fichier, ou le littéral 31 dans le contexte d'une fonction de hachage, ou le littéral padding: 0.5em dans le contexte d'une feuille de style CSS.

18
amon

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 particulier. Je suppose qu'il existe une règle commerciale spécifique qui dit qu'obtenir de l'argent d'une personne est radicalement différent de recevoir de l'argent de plusieurs personnes. Cependant, je dois aller voir à la fois getMoneyIfHasOnePerson et getMoney, en espérant comprendre pourquoi il y a des cas distincts.

  2. Le nom getMoneyIfHasOnePerson ne 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 un List<Money> Plutôt qu'une collection?

Revenons à votre question, parce que on ne sait pas pourquoi il existe un traitement spécial pour une personne, le chiffre un devrait être remplacé par une constante, sauf il y a une autre façon de faire 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 surcharge de round(value, precision).

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 chaque 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.

8
Arseni Mourzenko

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 la longueur aurait beaucoup de sens. Et le * 4 ferait la bonne constante puisque son origine est nébuleuse.

3
etisdew

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, utiliser 1 suffit. Sinon, vous pouvez définir une constante. N'appelez pas cela constant UN. (J'ai vu cela fait).

60 secondes en une minute - avez-vous besoin d'une constante? Eh bien, cela est 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.

2
gnasher729

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 stipulent 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;
}
0
Kristian H