Si les valeurs nulles sont mauvaises, que faut-il utiliser lorsqu'une valeur peut être significativement absente?


26

C'est l'une des règles qui ne cessent de se répéter et qui me rendent perplexe.

Les nuls sont mauvais et doivent être évités autant que possible .

Mais, mais - de ma naïveté, permettez-moi de crier - parfois une valeur PEUT être significativement absente!

S'il vous plaît laissez-moi vous poser cette question sur un exemple qui provient de ce code horrible anti-modèle monté sur lequel je travaille en ce moment . Il s'agit, à la base, d'un jeu multijoueur au tour par tour sur le Web, où les tours des deux joueurs sont exécutés simultanément (comme dans Pokemon, et contrairement aux échecs).

Après chaque tour, le serveur diffuse une liste de mises à jour du code JS côté client. La classe Update:

public class GameUpdate
{
    public Player player;
    // other stuff
}

Cette classe est sérialisée en JSON et envoyée aux joueurs connectés.

La plupart des mises à jour ont naturellement un joueur associé - après tout, il est nécessaire de savoir quel joueur a fait quel mouvement ce tour-ci. Cependant, certaines mises à jour ne peuvent pas être associées de manière significative à un lecteur. Exemple: le jeu a été lié de force car la limite de tour sans action a été dépassée. Pour de telles mises à jour, je pense qu'il est significatif que le lecteur soit annulé.

Bien sûr, je pourrais "corriger" ce code en utilisant l'héritage:

public class GameUpdate
{
    // stuff
}

public class GamePlayerUpdate : GameUpdate
{
    public Player player;
    // other stuff
}

Cependant, je ne vois pas en quoi cela peut être amélioré, pour deux raisons:

  • Le code JS recevra désormais simplement un objet sans Player en tant que propriété définie, ce qui est le même que s'il était nul puisque les deux cas nécessitent de vérifier si la valeur est présente ou absente;
  • Si un autre champ nullable était ajouté à la classe GameUpdate, je devrais être en mesure d'utiliser l'héritage multiple pour continuer avec cette conception - mais MI est mauvais en soi (selon des programmeurs plus expérimentés) et plus important encore, C # ne le fait pas l'avoir donc je ne peux pas l'utiliser.

J'ai une idée que ce morceau de ce code est l'un des très nombreux où un bon programmeur expérimenté hurlerait d'horreur. En même temps, je ne vois pas comment, dans cet endroit, est-ce que cela peut nuire à quoi que ce soit et ce qui devrait être fait à la place.

Pourriez-vous m'expliquer ce problème?


2
Cette question est-elle spécifique à JavaScript? De nombreuses langues ont un type facultatif à cet effet, mais je ne sais pas si js en a un (bien que vous puissiez en créer un) ou s'il fonctionnerait avec json (bien que ce soit une partie des vérifications nulles désagréables plutôt que partout dans votre code
Richard Tingle

9
Cette règle est tout simplement fausse. Je suis un peu étonné que quelqu'un souscrive à cette notion. Il existe de bonnes alternatives à null dans certains cas. null est vraiment bon pour représenter une valeur manquante. Ne laissez pas des règles Internet aléatoires comme celle-ci vous empêcher de faire confiance à votre propre jugement raisonné.
usr

1
@usr - Je suis totalement d'accord. Null est votre ami. À mon humble avis, il n'y a pas de moyen plus clair de représenter une valeur manquante. Il peut vous aider à trouver des bogues, il peut vous aider à gérer les conditions d'erreur, il peut vous aider à demander pardon (essayer / attraper). Qu'est-ce qu'il ne faut pas aimer?!
Scuba Steve

4
l'une des premières règles de conception d'un logiciel est "Toujours vérifier la nullité". Pourquoi c'est même un problème est époustouflant pour moi.
MattE

1
@MattE - Absolument. Pourquoi devrais-je concevoir un modèle de conception alors que vraiment, Null est une bonne chose à avoir dans votre boîte à outils de base. Ce que Graham suggérait, à mon humble avis, me semble être une ingénierie excessive.
Scuba Steve

Réponses:


20

Beaucoup de choses valent mieux que null.

  • Une chaîne vide ("")
  • Une collection vide
  • Une monade "facultative" ou "peut-être"
  • Une fonction qui ne fait rien tranquillement
  • Un objet plein de méthodes qui ne font rien tranquillement
  • Une valeur significative qui rejette la prémisse incorrecte de la question
    (qui est ce qui vous a fait considérer comme nul en premier lieu)

L'astuce consiste à savoir quand le faire. Null est vraiment bon pour exploser dans votre visage, mais seulement lorsque vous le détachez sans le vérifier. Ce n'est pas bon pour expliquer pourquoi.

Lorsque vous n'avez pas besoin d'exploser et que vous ne voulez pas vérifier la valeur null, utilisez l'une de ces alternatives. Cela peut sembler bizarre de renvoyer une collection si elle ne contient que 0 ou 1 élément, mais elle est vraiment bonne pour vous permettre de traiter silencieusement les valeurs manquantes.

Les monades ont l'air fantaisistes, mais ici, tout ce qu'elles font, c'est de vous faire comprendre que les tailles valides pour la collection sont 0 et 1. Si vous les avez, considérez-les.

N'importe lequel de ceux-ci fait un meilleur travail pour clarifier votre intention que null. C'est la différence la plus importante.

Il peut être utile de comprendre pourquoi vous revenez du tout plutôt que de simplement lever une exception. Les exceptions sont essentiellement un moyen de rejeter une hypothèse (ce n'est pas le seul moyen). Lorsque vous demandez le point où deux lignes se croisent, vous supposez qu'elles se croisent. Ils pourraient être parallèles. Devriez-vous lever une exception "lignes parallèles"? Eh bien, vous pourriez mais maintenant vous devez gérer cette exception ailleurs ou la laisser arrêter le système.

Si vous préférez rester où vous êtes, vous pouvez transformer le rejet de cette hypothèse en une sorte de valeur qui peut exprimer des résultats ou des rejets. Ce n'est pas si étrange. Nous le faisons tout le temps en algèbre . L'astuce consiste à rendre cette valeur significative afin que nous puissions comprendre ce qui s'est passé.

Si la valeur retournée peut exprimer des résultats et des rejets, elle doit être envoyée à un code qui peut gérer les deux. Le polymorphisme est vraiment puissant à utiliser ici. Plutôt que de simplement échanger des chèques nuls contre des isPresent()chèques, vous pouvez écrire du code qui se comporte bien dans les deux cas. Soit en remplaçant les valeurs vides par des valeurs par défaut, soit en ne faisant rien.

Le problème avec null est qu'il peut signifier beaucoup trop de choses. Cela finit donc par détruire des informations.


Dans mon expérience de pensée nulle préférée, je vous demande d'imaginer une machine Rube Goldbergienne complexe qui signale les messages codés en utilisant la lumière colorée en ramassant les ampoules colorées d'un tapis roulant, en les vissant dans une prise et en les alimentant. Le signal est contrôlé par les différentes couleurs des ampoules placées sur la bande transporteuse.

On vous a demandé de rendre cette chose horriblement chère conforme à la RFC3.14159 qui stipule qu'il devrait y avoir un marqueur entre les messages. Le marqueur ne doit pas être du tout lumineux. Cela devrait durer exactement une impulsion. La même quantité de temps qu'une lumière colorée unique brille normalement.

Vous ne pouvez pas simplement laisser des espaces entre les messages car l'engin déclenche des alarmes et s'arrête s'il ne trouve pas une ampoule à mettre dans la douille.

Tous ceux qui connaissent ce projet frissonnent à l'idée de toucher la machine ou son code de contrôle. Ce n'est pas facile de changer. Que faire? Eh bien, vous pouvez plonger et commencer à casser des choses. Peut-être mettre à jour ce design hideux. Ouais tu pourrais faire tellement mieux. Ou vous pouvez parler au concierge et commencer à ramasser les ampoules grillées.

Voilà la solution polymorphe. Le système n'a même pas besoin de savoir que quelque chose a changé.


C'est vraiment bien si vous pouvez retirer cela. En encodant un rejet significatif d'une hypothèse en une valeur, vous n'avez pas à forcer la question à changer.

Combien de pommes me devrez-vous si je vous donne 1 pomme? -1. Parce que je te devais 2 pommes d'hier. Ici, l'hypothèse de la question "vous me devez" est rejetée avec un nombre négatif. Même si la question était erronée, des informations significatives sont encodées dans cette réponse. En la rejetant de manière significative, la question n'est pas obligée de changer.

Cependant, changer la question est parfois la voie à suivre. Si l'hypothèse peut être rendue plus fiable, tout en restant utile, envisagez de le faire.

Votre problème est que, normalement, les mises à jour proviennent des joueurs. Mais vous avez découvert le besoin d'une mise à jour qui ne vient pas du joueur 1 ou du joueur 2. Vous avez besoin d'une mise à jour indiquant que le temps est écoulé. Aucun joueur ne dit cela, alors que devez-vous faire? Laisser le joueur nul semble si tentant. Mais cela détruit l'information. Vous essayez de coder la connaissance dans un trou noir.

Le champ du joueur ne vous dit pas qui vient de bouger. Vous pensez que oui, mais ce problème prouve que c'est un mensonge. Le champ du joueur vous indique d'où vient la mise à jour. Votre mise à jour expirée provient de l'horloge du jeu. Ma recommandation est de donner à l'horloge le crédit qu'elle mérite et de changer le nom du playerchamp en quelque chose comme source.

De cette façon, si le serveur s'arrête et doit suspendre le jeu, il peut envoyer une mise à jour qui signale ce qui se passe et se répertorie comme source de la mise à jour.

Est-ce à dire que vous ne devez jamais laisser de côté quelque chose? Non. Mais vous devez considérer dans quelle mesure rien ne peut vraiment être considéré comme signifiant une seule bonne valeur par défaut connue. Rien n'est vraiment facile à utiliser. Soyez donc prudent lorsque vous l'utilisez. Il y a une école de pensée qui embrasse en ne favorisant rien . C'est ce qu'on appelle la convention sur la configuration .

J'aime ça moi-même, mais seulement lorsque la convention est clairement communiquée d'une manière ou d'une autre. Ce ne devrait pas être un moyen de brouiller les débutants. Mais même si vous l'utilisez, null n'est toujours pas la meilleure façon de le faire. Null est un rien dangereux . Null prétend être quelque chose que vous pouvez utiliser jusqu'à ce que vous l'utilisiez, puis il fait sauter un trou dans l'univers (brise votre modèle sémantique). À moins de faire un trou dans l'univers, c'est le comportement dont vous avez besoin, pourquoi jouez-vous avec ça? Utilisez autre chose.


9
"retourner une collection si elle ne contient que 0 ou 1 élément" - je suis désolé mais je ne pense pas l'avoir :( De mon POV faire ceci (a) ajoute des états invalides inutiles à la classe (b) nécessite de documenter que la collection doit avoir au plus 1 élément (c) ne semble pas résoudre le problème de toute façon, car vérifier si la collection contient son seul élément avant d'y accéder est de toute façon nécessaire, sinon une exception sera levée?
gaazkam

2
@gaazkam voyez? Je vous ai dit que ça dérangeait les gens. Pourtant, c'est exactement ce que vous avez fait chaque fois que vous utilisez la chaîne vide.
candied_orange

16
Que se passe-t-il lorsqu'une valeur valide se trouve être une chaîne ou un ensemble vide?
Blrfl

5
Quoi qu'il en soit @gaazkam, vous ne vérifiez pas explicitement si la collection contient des éléments. La boucle (ou le mappage) sur une liste vide est une action sûre qui n'a aucun effet.
RubberDuck

3
Je sais que vous répondez à la question de savoir ce que quelqu'un devrait faire au lieu de renvoyer null, mais je ne suis vraiment pas d'accord avec beaucoup de ces points. Cependant, comme nous ne sommes pas spécifiques à une langue et qu'il y a une raison d'enfreindre toutes les règles, je refuse de voter contre
Liath

15

nullce n'est pas vraiment le problème ici. Le problème est de savoir comment la plupart des langages de programmation actuels gèrent les informations manquantes; ils ne prennent pas ce problème au sérieux (ou du moins ne fournissent pas le support et la sécurité dont la plupart des terriens ont besoin pour éviter les pièges). Cela conduit à tous les problèmes que nous avons appris à craindre (Javas NullPointerException, Segfaults, ...).

Voyons comment mieux gérer ce problème.

D'autres ont suggéré le modèle Null-Object . Bien que je pense que cela s'applique parfois, il existe de nombreux scénarios où il n'y a tout simplement pas de valeur par défaut unique. Dans ces cas, les consommateurs des informations éventuellement absentes doivent pouvoir décider eux-mêmes quoi faire si ces informations sont manquantes.

Il existe des langages de programmation qui offrent une sécurité contre les pointeurs nuls (ce qu'on appelle la sécurité nulle) au moment de la compilation. Pour donner quelques exemples modernes: Kotlin, Rust, Swift. Dans ces langues, le problème des informations manquantes a été pris au sérieux et l'utilisation de null est totalement indolore - le compilateur vous empêchera de déréférencer les nullpointers.
En Java, des efforts ont été faits pour établir les annotations @ Nullable / @ NotNull avec les plugins compilateur + IDE pour atteindre le même effet. Ce n'était pas vraiment réussi mais c'est hors de portée pour cette réponse.

Un type générique explicite qui dénote une absence possible est une autre façon de le gérer. Par exemple, en Java, il y en a java.util.Optional. Cela peut fonctionner: au niveau d'
un projet ou d'une entreprise, vous pouvez établir des règles / directives

  • utiliser uniquement des bibliothèques tierces de haute qualité
  • utilisez toujoursjava.util.Optional au lieu denull

Votre communauté / écosystème de langues a peut-être trouvé d'autres moyens de résoudre ce problème mieux que le compilateur / interprète.


Pourriez-vous nous dire en quoi les options Java sont meilleures que les valeurs nulles? En lisant la documentation , essayer d'obtenir une valeur de l'option produit une exception si la valeur est absente; il semblerait donc que nous soyons au même endroit: un contrôle est nécessaire et si on en oublie un, on est foutu?
gaazkam

3
@gaazkam Vous avez raison, il ne fournit pas de sécurité au moment de la compilation. En outre, l'option facultative elle-même pourrait être nulle, un autre problème. Mais il est explicite (par rapport aux variables pouvant être des commentaires null / doc). Cela ne présente un réel avantage que si, pour l'ensemble de la base de code, une règle de codage est mise en place. Vous ne pouvez pas mettre les choses à zéro. Si des informations peuvent être absentes, utilisez Facultatif. Cela force l'explicitation. Les contrôles nuls habituels deviennent alors des appels àOptional.isPresent
marstato

8
@marstato remplacer les contrôles nuls par isPresent() n'est pas l'utilisation recommandée de Facultatif
candied_orange

10

Je ne vois aucun mérite dans l'affirmation selon laquelle null est mauvais. J'ai vérifié les discussions à ce sujet et elles ne font que m'impatienter et m'énerver.

Null peut être mal utilisé, en tant que «valeur magique», alors qu'il signifie réellement quelque chose. Mais il faudrait faire une programmation assez tordue pour y arriver.

Null devrait signifier «pas là», qui n'est pas créé (encore) ou (déjà) disparu ou non fourni s'il s'agit d'un argument. Ce n'est pas un état, le tout manque. Dans un environnement OO où les choses sont créées et détruites tout le temps, c'est une condition valable et significative différente de tout état.

Avec null, vous êtes giflé au moment de l'exécution où vous êtes giflé au moment de la compilation avec une sorte d'alternative null de type sécurisé.

Pas entièrement vrai, je peux obtenir un avertissement pour ne pas vérifier la valeur null avant d'utiliser la variable. Et ce n'est pas pareil. Je ne veux peut-être pas épargner les ressources d'un objet qui n'a aucun but. Et plus important encore, je devrai vérifier l'alternative tout de même afin d'obtenir le comportement souhaité. Si j'oublie de le faire, je préfère obtenir une exception NullReferenceException au moment de l'exécution plutôt qu'un comportement non défini / involontaire.

Il y a un problème à prendre en compte. Dans un contexte multithread, vous devez vous protéger contre les conditions de concurrence en attribuant à une variable locale avant d'effectuer la vérification de null.

Le problème avec null est qu'il peut signifier beaucoup de choses. Cela finit donc par détruire des informations.

Non, cela ne signifie / ne devrait rien signifier donc il n'y a rien à détruire. Le nom et le type de la variable / argument diront toujours ce qui manque, c'est tout ce qu'il faut savoir.

Modifier:

Je suis à mi-chemin de la vidéo candied_orange liée à l'une de ses autres réponses sur la programmation fonctionnelle et c'est très intéressant. Je peux en voir l'utilité dans certains scénarios. L'approche fonctionnelle que j'ai vue jusqu'à présent, avec des morceaux de code volant autour, me fait généralement grincer des dents lorsqu'il est utilisé dans un environnement OO. Mais je suppose qu'il a sa place. Quelque part. Et je suppose qu'il y aura des langages qui feront du bon travail en cachant ce que je perçois comme un désordre déroutant chaque fois que je le rencontre en C #, des langages qui fournissent à la place un modèle propre et réalisable.

Si ce modèle fonctionnel est votre état d'esprit / cadre, il n'y a vraiment pas d'autre alternative que de pousser les incidents en tant que descriptions d'erreurs documentées et finalement de laisser l'appelant gérer les ordures accumulées qui ont été traînées tout le long du chemin jusqu'au point d'initiation. Apparemment, c'est exactement comme cela que fonctionne la programmation fonctionnelle. Appelez cela une limitation, appelez-le un nouveau paradigme. Vous pouvez considérer que c'est un hack pour les disciples fonctionnels qui leur permet de continuer un peu plus loin sur le chemin profane, vous pouvez être ravi de cette nouvelle façon de programmer qui ouvre de nouvelles possibilités passionnantes. Quoi qu'il en soit, il me semble inutile d'entrer dans ce monde, de revenir à la façon traditionnelle de faire les choses en OO (oui, nous pouvons lever des exceptions, désolé), de choisir un concept à partir de cela,

Tout concept peut être utilisé dans le mauvais sens. D'où je me tiens, les "solutions" présentées ne sont pas des alternatives pour une utilisation appropriée de null. Ce sont des concepts d'un autre monde.


9
Null détruit la connaissance du genre de rien qu'il représente. Il y a le genre de rien qui devrait être tranquillement ignoré. Le genre de rien qui doit arrêter le système pour éviter un état invalide. Le genre de rien qui signifie que le programmeur a foiré et doit réparer le code. Le genre de rien qui signifie que l'utilisateur a demandé quelque chose qui n'existe pas et doit être poliment dit. Non désolé. Tous ces éléments ont été codés comme nuls. Pour cette raison, si vous encodez l'un de ces éléments comme étant nul, vous ne devriez pas être surpris si personne ne sait ce que vous voulez dire. Vous nous forcez à deviner à partir du contexte.
candied_orange

3
@candied_orange Vous pouvez faire exactement le même argument à propos de tout type facultatif: Nonereprésente…
Déduplicateur

3
@candied_orange Je ne comprends toujours pas ce que vous voulez dire. Différents types de rien? Si vous utilisez null alors que vous auriez dû utiliser une énumération peut-être? Il n'y a qu'une seule sorte de rien. La raison pour laquelle quelque chose n'est rien peut varier, mais cela ne change pas le néant ni la réponse appropriée à quelque chose qui n'est rien. Si vous attendez une valeur dans un magasin qui n'est pas là et qui est remarquable, vous lancez ou vous connectez au moment où vous interrogez et n'obtenez rien, vous ne passez pas null comme argument à une méthode, puis dans cette méthode, essayez de comprendre pourquoi tu es devenu nul. Null ne serait pas une erreur.
Martin Maat

2
Null est l'erreur parce que vous avez eu la chance de coder le sens du rien. Votre compréhension du rien n'exige pas un traitement immédiat du rien. 0, null, NaN, chaîne vide, ensemble vide, void, objet null, non applicable, exception non implémentée, rien ne se présente sous de nombreuses formes. Vous n'avez pas à oublier ce que vous savez maintenant simplement parce que vous ne voulez pas gérer ce que vous savez maintenant. Si je vous demande de prendre la racine carrée de -1, vous pouvez soit lever une exception pour me faire savoir que c'est impossible et tout oublier, soit inventer des nombres imaginaires et conserver ce que vous savez.
candied_orange

1
@MartinMaat vous donnez l'impression qu'une fonction doit lever une exception chaque fois que vos attentes sont violées. Ce n'est tout simplement pas vrai. Il y a souvent des cas de coin à traiter. Si vous ne pouvez vraiment pas voir cela, lisez ceci
candied_orange

7

Ta question.

Mais, mais - de ma naïveté, permettez-moi de crier - parfois une valeur PEUT être significativement absente!

Entièrement à bord avec vous là-bas. Cependant, il y a quelques mises en garde que j'entrerai dans une seconde. Mais d'abord:

Les nuls sont mauvais et doivent être évités autant que possible.

Je pense que c'est une déclaration bien intentionnée mais trop généralisée.

Les nuls ne sont pas mauvais. Ils ne sont pas un contre-modèle. Ils ne doivent pas être évités à tout prix. Cependant, les valeurs nulles sont sujettes à erreur (faute de vérifier la valeur nulle).

Mais je ne comprends pas comment le fait de ne pas vérifier null est en quelque sorte la preuve que null est intrinsèquement mauvais à utiliser.

Si null est mauvais, les index de tableau et les pointeurs C ++ le sont aussi. Ils ne sont pas. Ils sont faciles à tirer avec le pied, mais ils ne sont pas censés être évités à tout prix.

Pour le reste de cette réponse, je vais adapter l'intention de "les nuls sont mauvais" à un plus nuancé "les nuls devraient être traités de manière responsable ".


Votre solution.

Bien que je partage votre opinion sur l'utilisation de null comme règle globale; Je ne suis pas d'accord avec votre utilisation de null dans ce cas particulier.

Pour résumer les commentaires ci-dessous: vous comprenez mal le but de l'héritage et du polymorphisme. Bien que votre argument pour utiliser null soit valide, votre "preuve" supplémentaire de la raison pour laquelle l'autre manière est mauvaise est basée sur une mauvaise utilisation de l'héritage, rendant vos points quelque peu hors de propos pour le problème nul.

La plupart des mises à jour ont naturellement un joueur associé - après tout, il est nécessaire de savoir quel joueur a fait quel mouvement ce tour-ci. Cependant, certaines mises à jour ne peuvent pas être associées de manière significative à un lecteur.

Si ces mises à jour sont significativement différentes (ce qu'elles sont), vous avez besoin de deux types de mise à jour distincts.

Prenons l'exemple des messages. Vous avez des messages d'erreur et des messages de chat IM. Mais bien qu'ils puissent contenir des données très similaires (un message de chaîne et un expéditeur), ils ne se comportent pas de la même manière. Ils doivent être utilisés séparément, en tant que classes différentes.

Ce que vous faites maintenant, c'est différencier efficacement deux types de mise à jour en fonction de la nullité de la propriété Player. Cela revient à décider entre un message instantané et un message d'erreur basé sur l'existence d'un code d'erreur. Cela fonctionne, mais ce n'est pas la meilleure approche.

  • Le code JS recevra désormais simplement un objet sans Player en tant que propriété définie, ce qui est le même que s'il était nul puisque les deux cas nécessitent de vérifier si la valeur est présente ou absente;

Votre argument repose sur des erreurs de polymorphisme. Si vous avez une méthode qui renvoie un GameUpdateobjet, elle ne devrait généralement pas se soucier de faire la différence entre GameUpdate, PlayerGameUpdateou NewlyDevelopedGameUpdate.

Une classe de base doit avoir un contrat pleinement fonctionnel, c'est-à-dire que ses propriétés sont définies sur la classe de base et non sur la classe dérivée (cela étant dit, la valeur de ces propriétés de base peut bien sûr être remplacée dans les classes dérivées).

Cela rend votre argument théorique. Dans la bonne pratique, vous ne devriez jamais vous soucier que votre GameUpdateobjet ait ou non un joueur. Si vous essayez d'accéder à la Playerpropriété d'un GameUpdate, vous faites quelque chose d'illogique.

Les langages typés tels que C # ne vous permettraient même pas d'essayer d'accéder à la Playerpropriété d'un GameUpdatecar GameUpdateil n'a tout simplement pas la propriété. Javascript est considérablement plus laxiste dans son approche (et il ne nécessite pas de précompilation), il ne prend donc pas la peine de vous corriger et le fait exploser au moment de l'exécution.

  • Si un autre champ nullable était ajouté à la classe GameUpdate, je devrais être en mesure d'utiliser l'héritage multiple pour continuer avec cette conception - mais MI est mauvais en soi (selon des programmeurs plus expérimentés) et plus important encore, C # ne le fait pas l'avoir donc je ne peux pas l'utiliser.

Vous ne devriez pas créer de classes basées sur l'ajout de propriétés nullables. Vous devez créer des classes basées sur des types de mises à jour de jeu fonctionnellement uniques .


Comment éviter les problèmes avec null en général?

Je pense qu'il est pertinent de préciser comment vous pouvez exprimer de manière significative l'absence de valeur. Il s'agit d'un ensemble de solutions (ou de mauvaises approches) sans ordre particulier.

1. Utilisez null de toute façon.

C'est l'approche la plus simple. Cependant, vous vous inscrivez pour une tonne de vérifications nulles et (en cas d'échec) de dépannage des exceptions de référence null.

2. Utilisez une valeur vide de sens non nulle

Voici un exemple simple indexOf():

"abcde".indexOf("b"); // 1
"abcde".indexOf("f"); // -1

Au lieu de null, vous obtenez -1. Sur le plan technique, il s'agit d'une valeur entière valide. Cependant, une valeur négative est une réponse absurde car les indices devraient être des entiers positifs.

Logiquement, cela ne peut être utilisé que dans les cas où le type de retour autorise plus de valeurs possibles que celles qui peuvent être renvoyées de manière significative (indexOf = entiers positifs; int = nombres négatifs et positifs)

Pour les types de référence, vous pouvez toujours appliquer ce principe. Par exemple, si vous avez une entité avec un ID entier, vous pouvez renvoyer un objet factice avec son ID défini sur -1, par opposition à null.
Vous devez simplement définir un "état factice" par lequel vous pouvez mesurer si un objet est réellement utilisable ou non.

Notez que vous ne devez pas vous fier à des valeurs de chaîne prédéterminées si vous ne contrôlez pas la valeur de chaîne. Vous pouvez envisager de définir le nom d'un utilisateur sur "null" lorsque vous souhaitez renvoyer un objet vide, mais vous rencontrez des problèmes lorsque Christopher Null s'inscrit à votre service .

3. Lancez une exception à la place.

Non, juste non. Les exceptions ne doivent pas être gérées pour le flux logique.

Cela étant dit, dans certains cas, vous ne souhaitez pas masquer l'exception (nullreference), mais plutôt afficher une exception appropriée. Par exemple:

var existingPerson = personRepository.GetById(123);

Cela peut lancer un PersonNotFoundException(ou similaire) quand il n'y a personne avec ID 123.

De manière assez évidente, cela n'est acceptable que dans les cas où le fait de ne pas trouver un article rend impossible tout autre traitement. S'il est impossible de trouver un élément et que l'application peut continuer, vous ne devez JAMAIS lever une exception . Je ne peux insister assez sur ce point.


5

La raison pour laquelle les valeurs NULL sont mauvaises n'est pas que la valeur manquante est codée comme nulle, mais que toute valeur de référence peut être nulle. Si vous avez C #, vous êtes probablement perdu de toute façon. Je ne vois pas un point ro utiliser certains facultatifs, car un type de référence est déjà facultatif. Mais pour Java, il y a des annotations @Notnull, que vous semblez pouvoir configurer au niveau du package , puis pour les valeurs qui peuvent être manquées, vous pouvez utiliser l'annotation @Nullable.


Oui! Le problème est un défaut insensé.
Déduplicateur

Je ne suis pas d'accord. La programmation dans un style qui évite null conduit à moins de variables de référence étant nulles, conduisant à moins de variables de référence qui ne devraient pas être nulles étant nulles. Ce n'est pas 100% mais c'est peut-être 90%, ce qui en vaut la peine IMO.
Rétablir Monica le

1

Les nuls ne sont pas mauvais, il n'y a en réalité aucun moyen de mettre en œuvre l'une des alternatives sans à un moment donné traiter quelque chose qui ressemble à un nul.

Les nuls sont cependant dangereux . Comme toute autre construction de bas niveau, si vous vous trompez, il n'y a rien en dessous pour vous attraper.

Je pense qu'il y a beaucoup d'arguments valides contre null:

  • Que si vous êtes déjà dans un domaine de haut niveau, il existe des modèles plus sûrs que l'utilisation du modèle de bas niveau.

  • À moins que vous n'ayez besoin des avantages d'un système de bas niveau et que vous soyez prêt à être prudent, vous ne devriez peut-être pas utiliser un langage de bas niveau.

  • etc ...

Ceux-ci sont tous valides et ne pas avoir à faire l'effort d'écrire des getters et setters, etc. est considéré comme une raison courante de ne pas avoir suivi ces conseils. Il n'est pas vraiment surprenant que beaucoup de programmeurs soient parvenus à la conclusion que les valeurs nulles sont dangereuses et paresseuses (une mauvaise combinaison!), Mais comme tant d'autres bits de conseils "universels", elles ne sont pas aussi universelles qu'elles sont formulées.

Les bonnes personnes qui ont écrit la langue ont pensé qu'elles seraient utiles à quelqu'un. Si vous comprenez les objections et pensez toujours qu'elles vous conviennent, personne d'autre ne saura mieux.


Le fait est que les «conseils universels» ne sont généralement pas formulés comme «universellement» comme le prétendent les gens. Si vous trouvez la source originale de ces conseils, ils parlent généralement des mises en garde que connaissent les programmeurs expérimentés. Ce qui se passe, c'est que lorsque quelqu'un d'autre lit le conseil, il a tendance à ignorer ces mises en garde et à ne se souvenir que de la partie "conseil universel". .
Nicol Bolas

1
@NicolBolas, vous avez raison, mais la source d'origine n'est pas le conseil que la plupart des gens citent, c'est le message relayé. Le point que je voulais faire était simplement que beaucoup de programmeurs se sont fait dire «ne fais jamais X, X est mauvais / mauvais / quoi que ce soit» et, exactement comme vous le dites, il manque beaucoup de mises en garde. Peut-être qu'ils ont été abandonnés, peut-être qu'ils n'étaient jamais présents, le résultat final est le même.
drjpizzle

0

Java a une Optionalclasse avec un ifPresent+ lambda explicite pour accéder à n'importe quelle valeur en toute sécurité. Cela peut aussi être fait dans JS:

Voir cette question StackOverflow .

Soit dit en passant: beaucoup de puristes trouveront cet usage douteux, mais je ne le pense pas. Des fonctionnalités optionnelles existent.


Une référence à un java.lang.Optionalêtre ne peut-elle pas être nullaussi?
Deduplicator

@Deduplicator Non, Optional.of(null)lancerait une exception, Optional.ofNullable(null)donneraitOptional.empty() - la valeur pas-là.
Joop Eggen

Et Optional<Type> a = null?
Deduplicator

@Deduplicator true, mais là, vous devez utiliser Optional<Optional<Type>> a = Optional.empty();;) - En d'autres termes, dans JS (et dans d'autres langues), de telles choses ne seront pas réalisables. Scala avec des valeurs par défaut ferait l'affaire. Java peut-être avec une énumération. JS Je doute: Optional<Type> a = Optional.empty();.
Joop Eggen

Ainsi, nous sommes passés d'une indirection et potentielle nullou vide, à deux plus quelques méthodes supplémentaires sur l'objet interposé. C'est tout un exploit.
Deduplicator

0

La RCA Hoare a déclaré nulle son "erreur d'un milliard de dollars". Cependant, il est important de noter qu'il pensait que la solution n'était pas "pas de null nulle part" mais plutôt "des pointeurs non nullables comme valeur par défaut et des null uniquement là où ils sont sémantiquement requis".

Le problème avec null dans les langues qui répètent son erreur est que vous ne pouvez pas dire qu'une fonction attend des données non nullables; c'est fondamentalement inexprimable dans la langue. Cela oblige les fonctions à inclure un grand nombre de plates-formes inutiles de gestion des null, et oublier une vérification nulle est une source courante de bogues.

Pour contourner ce manque fondamental d'expressivité, certaines communautés (par exemple la communauté Scala) n'utilisent pas null. Dans Scala, si vous voulez une valeur nullable de type T, vous prenez un argument de type Option[T], et si vous voulez une valeur non nullable vous prenez juste un T. Si quelqu'un passe un null dans votre code, votre code explosera. Cependant, il est considéré que le code appelant est le code buggy. Optionest un assez bon remplacement pour null, car les modèles courants de gestion des valeurs nulles sont extraits dans des fonctions de bibliothèque comme .map(f)ou .getOrElse(someDefault).

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.