Les types nullables sont-ils préférables aux nombres magiques?

22

J'ai eu un petit débat avec un collègue récemment. Nous utilisons spécifiquement C #, mais cela pourrait s'appliquer à n'importe quel langage avec des types nullables. Disons par exemple que vous avez une valeur qui représente un maximum. Cependant, cette valeur maximale est facultative. Je soutiens qu'un nombre annulable serait préférable. Mon collègue est favorable à l'utilisation de zéro, citant un précédent. Certes, des choses comme les sockets réseau ont souvent utilisé zéro pour représenter un délai illimité. Si je devais écrire du code traitant des sockets aujourd'hui, j'utiliserais personnellement une valeur nullable, car je pense qu'elle représenterait mieux le fait qu'il n'y a AUCUN timeout.

Quelle représentation est la meilleure? Les deux nécessitent une condition vérifiant la valeur signifiant «aucun», mais je crois qu'un type nullable transmet l'intention un peu mieux.

Matt H
la source
6
Si un nombre est utilisé, mettez-le dans une constante, pas directement dans le code.
Renato Dinhani
@ RenatoDinhaniConceição qui ne peut pas être une règle générale. Sinon, vous finissez par tout coder en douceur .
Simon Bergot

Réponses:

24

Considérer:

  • La langue,

  • Cadre,

  • Le contexte.

1. Langue

L'utilisation de ∞ peut être une solution pour un maximum.

  • JavaScript, par exemple, a une infinité. C # ne fait pas¹.

  • Ada, par exemple, a des plages. C # ne le fait pas.

En C #, il y en a int.MaxValue, mais vous ne pouvez pas l'utiliser dans votre cas. int.MaxValueest l'entier maximum, 2.147.483.647. Si dans votre code, vous avez une valeur maximale de quelque chose, comme une pression maximale acceptée avant que quelque chose explose, l'utilisation de 2147483647 n'a aucun sens.

2. Cadre

.NET Framework est plutôt incohérent sur ce point, et son utilisation des valeurs magiques peut être critiquée.

Par exemple, "Hello".IndexOf("Z")renvoie une valeur magique -1. Cela facilite peut-être (le fait-il?) La manipulation du résultat:

int position = "Hello".IndexOf("Z");
if (position > 0)
{
    DoSomething(position);
}

plutôt que d'utiliser une structure personnalisée:

SearchOccurrence occurrence = "Hello".IndexOf("Z");
if (occurrence.IsFound)
{
    DoSomething(occurrence.StartOffset);
}

mais n'est pas du tout intuitif. Pourquoi -1et non -123? Un débutant peut également penser à tort que cela 0signifie aussi "Introuvable" ou simplement taper du texte (position >= 0).

3. Contexte

Si votre code est lié à des délais d'expiration dans les sockets réseau, utiliser quelque chose qui a été utilisé par tout le monde pendant des décennies dans un souci de cohérence n'est pas une mauvaise idée . Surtout, 0car un timeout est très clair: c'est une valeur qui ne peut pas être nulle. L'utilisation d'une classe personnalisée dans ce cas peut rendre les choses plus difficiles à comprendre:

class Timeout
{
    // A value indicating whether there is a timeout.
    public bool IsTimeoutEnabled { get; set; }

    // The duration of the timeout, in milliseconds.
    public int Duration { get; set; }
}
  • Puis-je mettre Durationà 0 si IsTimeoutEnabledc'est vrai?
  • Si IsTimeoutEnabledest faux, que se passe-t-il si je mets Duration100?

Cela peut conduire à de multiples erreurs. Imaginez le morceau de code suivant:

this.currentOperation.Timeout = new Timeout
{
    // Set the timeout to 200 ms.; we don't want this operation to be longer than that.
    Duration = 200,
};

this.currentOperation.Run();

L'opération dure dix secondes. Pouvez-vous voir ce qui ne va pas avec ce code, sans lire la documentation de la Timeoutclasse?

Conclusion

  • nullexprime bien l'idée que la valeur n'est pas là. Ce n'est pas fourni. Indisponible. Ce n'est ni un nombre, ni une chaîne vide / nulle ou quoi que ce soit. Ne l'utilisez pas pour des valeurs maximales ou minimales.

  • int.MaxValueest fortement liée à la langue elle-même. Ne pas utiliser int.MaxValuepour une limite de vitesse maximale de Vehicleclasse ou une vitesse maximale acceptable pour un avion, etc.

  • Évitez les valeurs magiques comme -1dans votre code. Ils sont trompeurs et entraînent des erreurs de code.

  • Créez votre propre classe qui serait plus simple, avec les valeurs minimum / maximum spécifiées. Par exemple, VehicleSpeedpeut avoir VehicleSpeed.MaxValue.

  • Ne suivez aucune directive précédente et utilisez des valeurs magiques si c'est une convention générale depuis des décennies dans un domaine très spécifique, utilisé par la plupart des gens qui écrivent du code dans ce domaine.

  • N'oubliez pas de mélanger les approches. Par exemple:

    class DnsQuery
    {
        public const int NoTimeout = 0;
    
        public int Timeout { get; set; }
    }
    
    this.query.Timeout = 0; // For people who are familiar with timeouts set to zero.
    // or
    this.query.Timeout = DnsQuery.NoTimeout; // For other people.
    

¹ Vous pouvez créer votre propre type qui inclut l'infini. Ici, je parle intuniquement de type natif .

Arseni Mourzenko
la source
1
"Utiliser quelque chose qui a été utilisé par tout le monde pendant des décennies dans un souci de cohérence n'est pas une mauvaise idée" / "Ne suivez aucune directive précédente et utilisez des valeurs magiques si c'est une convention générale depuis des décennies dans un domaine très spécifique, utilisé par la plupart des gens écrivent du code dans ce domaine. " - Il y a une faute de frappe quelque part, je pense?
deworde
1
@deworde Je pense que MainMa fait référence aux directives qu'il a lui-même données au-dessus de celle-ci.
Joshua Drake
1
Je ne suis pas d'accord sur l'exemple indexOf, car -1 est en dehors de la chaîne, ce qui est certainement Z.
Joshua Drake
5
"JavaScript, par exemple, a une infinité. C # n'en a pas." - hein?
BlueRaja - Danny Pflughoeft
+1 spécialement pour "Créez votre propre classe" qui est ce que j'aurais suggéré. Chaque fois qu'un élément nu intn'exprime pas suffisamment sur le type pour contraindre le problème, envisagez une nouvelle structure avec plus d'informations (des instances const de la structure qui représentent des valeurs magiques, par exemple, ou une énumération dessus pour indiquer). Ou envisagez la programmation de contrats ou d'autres solutions, mais je pense qu'une structure personnalisée est la plus simple.
CodexArcanum
12

Null n'est pas mieux qu'un nombre magique.

L'important est de NOMMER les valeurs qui ont des effets magiques, si vous devez avoir de telles valeurs, et de vous assurer que les définitions de ces noms sont quelque part qui seront vues par quiconque se heurtera à la valeur magique et aux wtf.

if (timeout == 4298435) ... // bad.
if (timeout == null) ... // bad.
if (timeout == NEVER_TIME_OUT) ... // yay! puppies and unicorns!
mjfgates
la source
2
Ok, peut-être que cela dépend plus du langage, mais en C #, vous feriez probablement: if (timeout.HasValue) au lieu d'une comparaison directe avec null.
Matt H
2
Null n'est pas pire que le nombre magique. Avec les nombres magiques, vous ne savez jamais quel est le nombre magique ... il pourrait être 0, -1 ou autre chose. null est juste null.
marco-fiset
9
Null signifie absence de valeur. C'est le concept que beaucoup de nombres magiques ont l'intention d'exprimer. Pouvoir utiliser null avec un type nullable est une bien meilleure solution que de choisir une valeur arbitraire dans la plage de valeurs possibles pour un type de données.
17 du 26
2
Utiliser "null" comme valeur magique, si votre type a "null", c'est bien. L'important est de le nommer, car bien sûr, en tirant, le prochain gars à venir ne saura pas ce que vous vouliez dire. Null peut signifier «infini», «pas encore spécifié», «bogue dans le code qui a créé la structure de données» ou un certain nombre d'autres choses. Seul un nom permet au codeur suivant de savoir que vous vouliez que cette valeur soit là, et quel comportement vous vouliez qu'elle déclenche.
mjfgates
1
@CodeInChaos: Je sais que vous pouvez faire les deux, mais je préfère HasValue. En fait, je ne suis pas un grand fan de null en général, mais les types nullables utilisant HasValue me semblent un peu plus proches d'un type Option / Peut-être, dont je suis fan.
Matt H
10

MAGIC_NUMBERle code doit absolument toujours être évité autant que possible. nullest une expression d'intention beaucoup plus claire.

DeadMG
la source
6

En C #, de nombreuses classes CLR ont un Emptymembre statique :

  • System.String.Empty
  • System.EventArgs.Empty
  • System.Guid.Empty
  • System.Drawing.Rectangle.Empty
  • System.Windows.Size.Empty

Cela vous évite d'avoir à vous rappeler s'il faut utiliser une valeur magique ou utiliser null pour construire un objet vide.

Mais que faire si vous avez affaire à un type de valeur simple comme un int? Dans ce cas, demandez-vous si vous êtes victime de l' obsession primitive . Il est tout à fait possible que votre propriété numérique apparemment simple bénéficie de sa propre classe ou structure, ce qui vous permettrait de spécifier le Emptymembre et également d'ajouter d'autres comportements spécifiques à ce type de valeur.

Kyralessa
la source
3

Dans ce cas, la valeur nulle est un excellent moyen d'indiquer qu'il n'y a pas de maximum. Généralement, lorsque le cas spécial signifie que la valeur en question ne s'applique pas, que vous ne voulez tout simplement pas la fonctionnalité qu'il configure, null est une bonne indication de cela.

Un problème avec l'utilisation de null pour représenter des cas spéciaux est qu'il n'y a qu'une seule valeur null et qu'il peut y avoir plusieurs cas spéciaux. Dans ce cas, je passerais une énumération comme paramètre supplémentaire, ce qui peut indiquer un cas spécial, ou pour utiliser la valeur int normalement. (C'est essentiellement ce que Nullable <> fait pour vous, bien qu'il utilise un booléen au lieu d'une énumération et combine les paramètres en une seule structure.)

JGWeissman
la source
3

Dans ce cas, je pense qu'un type nullable est parfaitement logique.

Null signifie absence de valeur. Il s'agit d'un concept nettement différent d'un nombre ayant une valeur de 0.

Si vous voulez dire "Si je ne vous donne pas de valeur, utilisez le maximum", alors passer null est la façon exacte d'exprimer cela.

17 sur 26
la source
1

Null: valeur d'erreur commune, non spécifiée, nulle ou absence de valeur.

Zéro: une valeur réelle, mais pas nécessairement logique ou intuitive (dans ce contexte). Également une valeur commune à l'initialisation.

Dans le contexte de votre problème, la timeoutInMillisecondspropriété est facultative et il n'y a aucune mention que la surcharge de cette approche la disqualifierait en option.

Conclusion: il existe des exceptions et les solutions varient selon les langues et les domaines; dans ce cas, je choisirais Null. Là où (je crois) certaines personnes se trompent, c'est quand elles ne séparent pas bien les données de l'interface. Ils s'attendent à ce que tout client lise la documentation (ou l'implémentation) pour déterminer comment ces valeurs spéciales doivent être utilisées / gérées - les cas spéciaux s'infiltrent dans le programme du client et cela peut être assez peu clair. En ajoutant une bonne couche d'abstraction, l'utilisation peut être beaucoup plus claire.

Justin
la source
0

Null est pire à utiliser que MagicNumber. Null représente mieux l'idée exprimée, mais il n'est pas cohérent sur toutes les plates-formes dans son comportement, en utilisant MagicNumbertoujours le même, ce qui est bénéfique.

selon l'environnement / la langue utilisée, null pourrait

  • être simplement 0
  • peut ne pas avoir une valeur légale
  • peut produire des résultats inattendus en raison de la logique à trois voies

MagicNumber se comporte toujours de la même manière.

Ryathal
la source
0

Si vous oubliez de vérifier le nombre magique (cela se produira correctement), un nombre magique continuera pendant un petit moment avec des données absurdes. Il vaut mieux avoir un null qui provoque une exception dès que possible.

Tom Hawtin - sellerie
la source
-1

Null n'est pas la seule alternative à un nombre magique.

public static int NO_TIMEOUT = 0;  // javaish

Null est mal. Dans l'exemple ci-dessus, vous pourrez peut-être vous en sortir car le code serait évidemment capable de gérer un null. Mais en général, ce qui se passe lorsque vous commencez à transmettre des valeurs nulles, c'est que tôt ou tard, vous obtenez une exception de pointeur nul. Cela peut ne pas se produire lors de la première écriture du code, mais le code est conservé bien plus longtemps que la première version. Il est souvent maintenu par des personnes qui ne connaissent pas autant le système que les développeurs d'origine.

Scala (par exemple) a une belle alternative dans la classe Option. La classe Option a l'une des deux valeurs, Some - qui encapsule la valeur que vous voulez vraiment et None - qui n'a pas de valeur.

Cela rend évident pour tout développeur qu'il peut ne pas y avoir de valeur et que vous aviez un meilleur code pour cela. Eh bien, cela devrait être évident de toute façon.

Et tous les nombres magiques ne sont pas un problème. Selon le contexte 0, 1, 1024, etc., tout peut être évident. 347? Ouais, celui que tu devrais éviter. :-)

Jon Strayer
la source
4
-1: Justifiez "null is evil".
deworde
4
La définition d'un alias pour un nombre ne change pas le fait qu'il s'agit toujours d'un nombre magique.
17 du 26
Eh bien, vous avez peut-être une définition du nombre magique différente de la mienne. Veuillez consulter en.wikipedia.org/wiki/…
Jon Strayer
1
Je suis d'accord avec Jon Strayer ici. Null est un exemple d'ADT dans une langue qui ne prend pas réellement en charge les ADT. L'OP peut probablement s'en tirer ici, mais en général, je considère que tout langage qui a nullement échoué est légèrement ses programmeurs.
Jeremy Wall