J'ai entendu dire que l'inclusion de références nulles dans les langages de programmation est une "erreur d'un milliard de dollars". Mais pourquoi? Bien sûr, ils peuvent provoquer des exceptions NullReferenceExceptions, mais alors quoi? Tout élément de la langue peut être une source d’erreurs s’il n’est pas utilisé correctement.
Et quelle est l'alternative? Je suppose au lieu de dire ceci:
Customer c = Customer.GetByLastName("Goodman"); // returns null if not found
if (c != null)
{
Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman. How lame!"); }
Vous pourriez dire ceci:
if (Customer.ExistsWithLastName("Goodman"))
{
Customer c = Customer.GetByLastName("Goodman") // throws error if not found
Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman. How lame!"); }
Mais comment est-ce mieux? Dans les deux cas, si vous oubliez de vérifier que le client existe, vous obtenez une exception.
Je suppose qu’une exception CustomerNotFoundException est un peu plus facile à déboguer qu’une exception NullReferenceException car elle est plus descriptive. Est-ce tout ce qu'il y a à faire?
language-design
exceptions
pointers
null
Tim Goodman
la source
la source
Réponses:
null est le mal
Il existe une présentation sur InfoQ à ce sujet: Références nulles: L’erreur du milliard de dollars de Tony Hoare
Type d'option
L'alternative à la programmation fonctionnelle consiste à utiliser un type Option pouvant contenir
SOME value
ouNONE
.Un bon article Le modèle «Option» qui traite du type Option et fournit une implémentation de celui-ci pour Java.
J'ai également trouvé un rapport de bogue pour Java sur ce problème: Ajoutez des types d'option Nice à Java pour empêcher NullPointerExceptions . La fonctionnalité demandée a été introduite dans Java 8.
la source
String
type n'est pas vraiment une chaîne. C'est unString or Null
type. Les langues qui adhèrent pleinement à l'idée de types d'options n'autorisent pas les valeurs NULL dans leur système de types, ce qui garantit que si vous définissez une signature de type qui nécessite uneString
(ou toute autre chose), elle doit avoir une valeur. LeOption
type est là pour donner une représentation au niveau du type de choses qui peuvent ou non avoir une valeur, ainsi vous savez où vous devez gérer explicitement ces situations.Le problème est qu’en théorie, tout objet pouvant avoir la valeur null et lancer une exception lorsque vous essayez de l’utiliser, votre code orienté objet est essentiellement une collection de bombes non explosées.
Vous avez raison de dire que la gestion des erreurs sans erreur peut être fonctionnellement identique aux
if
instructions à vérification nulle . Mais ce qui se passe quand quelque chose que vous vous ne pouvait pas convaincu peut - être une valeur nulle est, en fait, un nul? Kerboom. Quoi qu'il arrive, je suis prêt à parier que 1) cela ne sera pas gracieux et 2) que cela ne vous plaira pas.Et ne rejetez pas la valeur de "facile à déboguer". Le code de production mature est une créature folle et tentaculaire; tout ce qui vous donne une idée plus précise de ce qui ne va pas et de ce qui peut vous faire économiser des heures de fouilles.
la source
public Foo doStuff()
ne signifie pas "faire des choses et renvoyer le résultat Foo", cela signifie "faire des choses et renvoyer le résultat Foo, OU renvoyer null, mais vous ne savez pas si cela se produira ou non". Le résultat est que vous devez vérifier NUL PARTOUT pour être certain. Vous pouvez également supprimer les valeurs NULL et utiliser un type ou un modèle spécial (comme un type de valeur) pour indiquer une valeur sans signification.L'utilisation de références null dans le code pose plusieurs problèmes.
Premièrement, il est généralement utilisé pour indiquer un état spécial . Plutôt que de définir une nouvelle classe ou une constante pour chaque état au fur et à mesure que les spécialisations sont effectuées, l'utilisation d'une référence null consiste à utiliser un type / valeur avec perte et généralisé .
Deuxièmement, le code de débogage devient plus difficile lorsqu'une référence null apparaît et que vous tentez de déterminer ce qui l'a généré , son état et sa cause, même si vous pouvez suivre son chemin d'exécution en amont.
Troisièmement, les références nulles introduisent des chemins de code supplémentaires à tester .
Quatrièmement, une fois que les références nulles sont utilisées en tant qu'états valides pour les paramètres ainsi que pour les valeurs renvoyées, la programmation défensive (pour les états causés par la conception) nécessite davantage de vérifications de références nulles à divers endroits … au cas où.
Cinquièmement, le langage d' exécution effectue déjà des vérifications de type lorsqu'il effectue une recherche par sélecteur sur la table de méthodes d'un objet. Vous dupliquez donc vos efforts en vérifiant si le type de l'objet est valide / invalide, puis en demandant au moteur d'exécution de vérifier le type de l'objet valide pour appeler sa méthode.
Pourquoi ne pas utiliser le modèle NullObject pour tirer parti de la vérification du moteur d'exécution afin qu'il appelle des méthodes NOP spécifiques à cet état (conformes à l'interface de l'état normal) tout en éliminant toute vérification supplémentaire des références null dans votre base de code?
Cela implique plus de travail en créant une classe NullObject pour chaque interface avec laquelle vous voulez représenter un état spécial. Mais au moins la spécialisation est isolée pour chaque état spécial, plutôt que le code dans lequel l'état pourrait être présent. IOW, le nombre de tests est réduit car vos méthodes utilisent moins de chemins d'exécution alternatifs .
la source
[self.navigationController.presentingViewController dismissViewControllerAnimated: YES completion: nil];
le moteur d'exécution, il la traite comme une séquence de non-opérations, la vue n'est pas supprimée de l'écran et vous pouvez vous asseoir devant Xcode pour vous gratter la tête pendant des heures et tenter de comprendre pourquoi.Les nullités ne sont pas si mauvaises, à moins que vous ne les attendiez pas. Vous devriez avoir besoin de spécifier explicitement dans le code que vous attendez la valeur null, ce qui est un problème de conception de langage. Considère ceci:
Si vous essayez d'appeler des méthodes sur un
Customer?
, vous devriez obtenir une erreur de compilation. La raison pour laquelle d’autres langages ne font pas cela (IMO) est qu’ils ne fournissent pas le type de variable à modifier en fonction de la portée de celle-ci. Si le langage peut le gérer, le problème peut être entièrement résolu dans le système de type.Il existe également un moyen fonctionnel de gérer ce problème, en utilisant des types d'options et
Maybe
, mais je ne le connais pas aussi bien. Je préfère cette méthode car un code correct ne devrait théoriquement nécessiter qu'un seul caractère ajouté pour être compilé correctement.la source
GetByLastName
renvoie null si non trouvé (par opposition à une exception) - je peux juste voir si elle retourneCustomer?
ouCustomer
.Maybe
- AMaybe Customer
est soitJust c
(oùc
est aCustomer
) ouNothing
. Dans d'autres langues, cela s'appelleOption
- voir d'autres réponses à cette question.Customer.FirstName
est de typeString
(par opposition àString?
). C'est l'avantage réel: seules les variables / propriétés qui ont une valeur rien significative doivent être déclarées nullables.Il y a une foule d'excellentes réponses qui couvrent les symptômes malheureux de
null
, alors j'aimerais présenter un argument alternatif: Null est une faille dans le système de types.Le but d'un système de types est de s'assurer que les différents composants d'un programme "s'emboîtent" correctement; un programme bien typé ne peut pas "dérober" un comportement indéfini.
Considérons un dialecte hypothétique de Java, ou quel que soit votre langage préféré à typage statique, dans lequel vous pouvez affecter la chaîne
"Hello, world!"
à n'importe quelle variable de n'importe quel type:Et vous pouvez vérifier les variables comme suit:
Il n’ya rien d’impossible à cela: quelqu'un pourrait concevoir un tel langage s’il le souhaite. La valeur spéciale ne doit pas nécessairement être
"Hello, world!"
- cela aurait pu être le nombre 42, le tuple(1, 4, 9)
, ou, disonsnull
. Mais pourquoi feriez-vous cela? Une variable de typeFoo
ne devrait contenir queFoo
s - c'est tout l'intérêt du système de types!null
n'est pas unFoo
plus que ce"Hello, world!"
n'est. Pire, cenull
n'est une valeur d' aucun type, et vous ne pouvez rien y faire!Le programmeur ne peut jamais être sûr qu'une variable détient réellement un
Foo
, et le programme non plus; afin d'éviter tout comportement indéfini, il doit vérifier les variables"Hello, world!"
avant de les utiliser en tant queFoo
s. Notez que faire la vérification de chaîne dans le fragment précédent ne propage pas le fait que foo1 est vraiment unFoo
-bar
aura probablement aussi sa propre vérification, juste pour être sûr.Comparez cela à l'utilisation d'un
Maybe
/Option
type avec un motif correspondant:À l'intérieur de la
Just foo
clause, vous et le programme savez à coup sûr que notreMaybe Foo
variable contient réellement uneFoo
valeur: cette information est propagée dans la chaîne d'appels etbar
ne nécessite aucune vérification. Parce queMaybe Foo
est un type distinct deFoo
, vous êtes obligé de gérer la possibilité qu'il pourrait contenirNothing
, de sorte que vous ne pouvez jamais être pris au dépourvu par unNullPointerException
. Vous pouvez raisonner beaucoup plus facilement sur votre programme et le compilateur peut omettre les vérifications nulles en sachant que toutes les variables de typeFoo
contiennent réellement desFoo
s. Tout le monde gagne.la source
my Int $variable = Int
, oùInt
est la valeur null de typeInt
?this type only contains integers
par opposition àthis type contains integers and this other value
, vous aurez un problème chaque fois que vous ne voulez entiers.??
,?.
etc.) pour des langages comme haskell qui implémentent des fonctions de bibliothèque. Je pense que c'est beaucoup plus soigné.null
/Nothing
propagation n'est en aucun cas "spécial", alors pourquoi devrions-nous apprendre plus de syntaxe pour l'avoir?(Jeter mon chapeau sur le ring pour une vieille question;))
Le problème spécifique de null est qu'il interrompt le typage statique.
Si j'ai un
Thing t
, alors le compilateur peut garantir que je peux appelert.doSomething()
. Eh bien, sauf sit
null est à l'exécution. Maintenant, tous les paris sont ouverts. Le compilateur a dit que c'était OK, mais je découvre beaucoup plus tard que cet
n'est pas le casdoSomething()
. Donc, plutôt que de pouvoir faire confiance au compilateur pour intercepter les erreurs de type, je dois attendre le temps d'exécution pour les intercepter. Je pourrais aussi bien utiliser Python!Donc, dans un sens, null introduit le typage dynamique dans un système à typage statique, avec les résultats attendus.
La différence entre une division par zéro ou un log de négatif, etc. est que lorsque i = 0, il s'agit toujours d'un entier. Le compilateur peut toujours garantir son type. Le problème est que la logique applique mal cette valeur d'une manière qui n'est pas permise par la logique ... mais si la logique le permet, c'est à peu près la définition d'un bogue.
(Le compilateur peut résoudre certains de ces problèmes, BTW. Par exemple, marquer des expressions comme
i = 1 / 0
au moment de la compilation. Mais vous ne pouvez pas vous attendre à ce que le compilateur suive une fonction et veille à ce que tous les paramètres soient cohérents avec sa logique.)Le problème pratique est que vous faites beaucoup de travail supplémentaire et que vous ajoutez des contrôles nuls pour vous protéger au moment de l'exécution, mais que se passe-t-il si vous en oubliez un? Le compilateur vous empêche d'attribuer:
Alors, pourquoi devrait-il permettre l’affectation à une valeur (null) qui interrompra les dé-références
s
?En mélangeant "aucune valeur" avec des références "typées" dans votre code, vous mettez une charge supplémentaire sur les programmeurs. Et lorsque NullPointerExceptions se produit, les rechercher peut prendre encore plus de temps. Plutôt que de compter sur le typage statique pour dire «ceci est une référence à quelque chose d’attendu», vous laissez le langage dire «cela pourrait bien être une référence à quelque chose d’attendu».
la source
*int
identifie unint
, ouNULL
. De même en C ++, une variable de type*Widget
identifieWidget
une chose dont le type dériveWidget
, ouNULL
. Le problème avec Java et les dérivés tels que C # est qu’ils utilisent l’emplacement de stockageWidget
pour signifier*Widget
. La solution n’est pas de prétendre qu’un*Widget
ne devrait pas pouvoir tenirNULL
, mais bien d’avoir unWidget
type d’emplacement de stockage approprié .Un
null
pointeur est un outilpas un ennemi. Vous devez juste les utiliser correctement.
Pensez juste au temps qu'il faut pour trouver et résoudre un bogue de pointeur invalide typique , par rapport à la recherche et à la correction d'un bogue de pointeur nul. Il est facile de vérifier un pointeur contre
null
. C'est un PITA qui vérifie si un pointeur non nul pointe sur des données valides.Si vous n'êtes toujours pas convaincu, ajoutez une bonne dose de multithreading à votre scénario, puis réfléchissez à nouveau.
Morale de l'histoire: Ne jetez pas les enfants avec de l'eau. Et ne blâmez pas les outils pour cet accident. L'homme qui a inventé le chiffre zéro il y a longtemps était assez malin, puisque ce jour-là, vous pouviez nommer le "rien". Le
null
pointeur n'est pas si loin de ça.EDIT: Bien que le
NullObject
modèle semble être une meilleure solution que les références pouvant être nulles, il introduit lui-même des problèmes:Une référence tenant un
NullObject
devrait (selon la théorie) ne fait rien quand une méthode est appelée. Ainsi, des erreurs subtiles peuvent être introduites en raison de références non attribuées par erreur, qui sont maintenant garanties non nulles (yehaa!) Mais exécutent une action non désirée: rien. Avec un NPE, le problème est presque évident. Avec unNullObject
comportement correct (mais faux), nous introduisons le risque que des erreurs soient détectées (trop) tardivement. Ce n'est pas accidentellement similaire à un problème de pointeur invalide et a des conséquences similaires: la référence pointe vers quelque chose qui ressemble à des données valides, mais ne le fait pas, introduit des erreurs de données et peut être difficile à localiser. Pour être honnête, dans ces cas, je préférerais sans hésiter un NPE qui échoue immédiatement, maintenant,sur une erreur logique / de données qui me frappe soudainement plus tard sur la route. Vous souvenez-vous de l'étude d'IBM sur le coût des erreurs en fonction du moment où elles sont détectées?La notion de ne rien faire quand une méthode est appelée sur un
NullObject
ne tient pas, quand un getter de propriété ou une fonction est appelée, ou quand la méthode renvoie des valeurs viaout
. Disons que la valeur de retour est unint
et nous décidons que "ne rien faire" signifie "retourner 0". Mais comment pouvons-nous être sûrs que 0 est le droit "rien" à être (non) retourné? Après tout, leNullObject
ne devrait rien faire, mais lorsqu'on lui demande une valeur, il doit réagir d'une manière ou d'une autre. L'échec n'est pas une option: nous utilisons leNullObject
pour empêcher le NPE, et nous ne le négocierons sûrement pas contre une autre exception (non mis en œuvre, diviser par zéro, ...), n'est-ce pas? Alors, comment ne retournez-vous rien correctement lorsque vous devez retourner quelque chose?Je ne peux pas aider, mais quand quelqu'un essaie d'appliquer le
NullObject
motif à chaque problème, cela ressemble plus à essayer de réparer une erreur en en faisant une autre. C'est sans aucun doute une solution utile et utile dans certains cas, mais ce n'est certainement pas la solution miracle pour tous les cas.la source
null
devrait exister? Il est possible de traiter à la main n'importe quel défaut de langue avec "ce n'est pas un problème, mais ne commettez pas d'erreur".Maybe T
éventuellement, qui peut contenir l'uneNothing
ou l' autre ou uneT
valeur. Le point clé ici est que vous êtes obligé de vérifier si unMaybe
produit contient vraiment un motT
avant que vous ne soyez autorisé à l'utiliser, et de fournir une ligne de conduite au cas où ce ne serait pas le cas. Et comme vous avez interdit les pointeurs non valides, vous ne devez plus jamais vérifier ce qui n’est pas unMaybe
.t.Something()
? Ou a-t-il un moyen de savoir que vous avez déjà effectué le contrôle et de ne pas vous le faire refaire? Et si vous faisiez la vérification avant de passert
à cette méthode? Avec le type Option, le compilateur sait si vous avez déjà effectué la vérification en regardant le type det
. Si c'est une option, vous ne l'avez pas cochée, alors que si c'est la valeur non enveloppée, alors vous l'avez.Les références nulles sont une erreur car elles autorisent un code non sensuel:
Il existe des alternatives si vous utilisez le système de types:
L'idée générale est de mettre la variable dans une boîte, et la seule chose à faire est de la déballer, en recourant de préférence à l'aide du compilateur comme celle proposée pour Eiffel.
Haskell l'a depuis le début (
Maybe
), en C ++, vous pouvez en tirer partiboost::optional<T>
mais vous pouvez toujours obtenir un comportement indéfini ...la source
Maybe
ne se construit pas dans la langue de base, la définition se trouve être dans le prélude standard:data Maybe t = Nothing | Just t
.T
divisées par d'autres valeurs de typeT
, vous limiteriez l'opération aux valeurs de typeT
divisées par les valeurs de typeNonZero<T>
.Types facultatifs et correspondance de modèle. Puisque je ne connais pas le C #, voici un morceau de code rédigé dans un langage fictif appelé Scala # :-)
la source
Le problème avec les valeurs nulles est que les langages qui leur permettent de vous forcer à programmer en défensive contre elles. Il faut beaucoup d’efforts (bien plus que d’utiliser des blocs de défense défensifs) pour s’assurer que
Ainsi, en effet, les valeurs nulles deviennent une opération coûteuse.
la source
La question est de savoir dans quelle mesure votre langage de programmation tente de prouver l'exactitude de votre programme avant de l'exécuter. Dans un langage typé statiquement, vous prouvez que vous avez les types corrects. En passant aux valeurs par défaut des références non nullables (avec des références facultatives Nullable), vous pouvez éliminer bon nombre des cas où la valeur null a été passée et elle ne devrait pas l'être. La question qui se pose est de savoir si l’effort supplémentaire de traitement des références non nullables vaut l’avantage en termes de correction du programme.
la source
Le problème n'est pas tellement nul, c'est que vous ne pouvez pas spécifier un type de référence non nul dans beaucoup de langues modernes.
Par exemple, votre code pourrait ressembler à
Lorsque la référence de classe est transmise, la programmation défensive vous incite à vérifier à nouveau tous les paramètres, même si vous venez juste de les vérifier à l’avance, en particulier lors de la construction d’unités réutilisables à tester. La même référence pourrait facilement être vérifiée à zéro 10 fois dans la base de code!
Ce ne serait pas mieux si vous pouviez avoir un type nullable normal quand vous obtenez la chose, traitez null puis, puis passez-le comme un type non nullable à toutes vos petites fonctions et classes d’aide sans chercher null pour null temps?
C’est l’intérêt de la solution Option, non pas de vous permettre d’activer les états d’erreur, mais de permettre la conception de fonctions qui, implicitement, ne peuvent pas accepter les arguments nuls. Que ce soit ou non l'implémentation "par défaut" des types est nullable ou non-nullable est moins important que la flexibilité d'avoir les deux outils disponibles.
http://twistedoakstudios.com/blog/Post330_non-nullable-types-vs-c-fixing-the-billion-dollar-mistake
Ceci est également classé comme la fonctionnalité # 2 la plus votée pour C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c
la source
None
chaque étape du processus.Null est le mal. Cependant, l'absence d'un nul peut être un plus grand mal.
Le problème est que dans le monde réel, vous vous retrouvez souvent dans une situation où vous ne disposez pas de données. Votre exemple de la version sans null peut encore exploser - soit vous avez commis une erreur de logique et oublié de vérifier Goodman, soit peut-être que Goodman s'est marié entre vous lorsque vous avez vérifié et que vous l'avez vérifiée. (Il est utile d’évaluer la logique si vous pensez que Moriarty surveille chaque partie de votre code et tente de vous faire trébucher.)
Que fait la recherche de Goodman quand elle n'est pas là? Sans null, vous devez retourner une sorte de client par défaut - et maintenant vous vendez des choses à ce défaut.
Fondamentalement, il s’agit de savoir s’il est plus important que le code fonctionne, quel qu’il soit ou qu’il fonctionne correctement. Si un comportement inapproprié est préférable à aucun comportement, vous ne voulez pas de valeurs NULL. (Pour un exemple de ce qui pourrait être le bon choix, considérons le premier lancement de l’Ariane V. Une erreur non capturée / 0 a provoqué un virage serré de la fusée et l’autodestruction a été tirée lorsqu’elle s’est effondrée à cause de cela. La valeur il essayait de calculer en fait ne servait plus à rien dès l'instant où le booster était allumé - il aurait été mis en orbite malgré la routine de retour des ordures.)
Au moins 99 fois sur 100, je choisirais d'utiliser des valeurs nulles. Je voudrais sauter de joie à la note à la version de soi d'eux, cependant.
la source
null
c'est le seul (voire même préférable) moyen de modéliser l'absence de valeur.Optimiser pour le cas le plus commun.
Il est fastidieux de devoir vérifier tout le temps la valeur null - vous voulez pouvoir obtenir simplement l'objet Client et le manipuler.
Dans le cas normal, cela devrait fonctionner correctement: vous effectuez la recherche, récupérez l'objet et utilisez-le.
Dans le cas exceptionnel , où vous recherchez (au hasard) un client par son nom, sans savoir si cet enregistrement / cet objet existe, vous avez besoin d'une indication indiquant que cela a échoué. Dans cette situation, la solution consiste à lever une exception RecordNotFound (ou à laisser le fournisseur SQL inférieur le faire pour vous).
Si vous ne savez pas si vous pouvez faire confiance aux données qui arrivent (le paramètre), peut-être parce qu'elles ont été entrées par un utilisateur, vous pouvez également fournir le «TryGetCustomer (nom, client)». modèle. Référence croisée avec int.Parse et int.TryParse.
la source
Les exceptions ne sont pas eval!
Ils sont là pour une raison. Si votre code fonctionne mal, il existe un motif de génie, appelé exception , qui vous indique que quelque chose ne va pas.
En évitant d'utiliser des objets nuls, vous masquez une partie de ces exceptions. Je ne parle pas de l'exemple OP où il convertit l'exception du pointeur null en une exception bien typée. Cela pourrait en fait être une bonne chose, car cela améliore la lisibilité. Cependant, lorsque vous prenez la solution de type Option , comme l'a souligné @Jonas, vous cachez des exceptions.
Que dans votre application de production, au lieu d'exception à être levée lorsque vous cliquez sur le bouton pour sélectionner l'option vide, rien ne se passe. Au lieu d'exception de pointeur nul, une exception serait générée et nous obtiendrions probablement un rapport de cette exception (comme dans de nombreuses applications de production, nous avons un tel mécanisme), et nous pourrions le réparer.
Rendre votre code à l'épreuve des balles en évitant les exceptions est une mauvaise idée, vous obliger à coder à l'épreuve des balles en corrigeant une exception, voilà le chemin que je choisirais.
la source
None
à quelque chose qui n'est pas une option une erreur de compilation, et d'utiliser également unOption
comme s'il avait une valeur sans vérifier au préalable qu'il s'agit d'une erreur de compilation. Les erreurs de compilation sont certainement plus agréables que les exceptions d'exécution.s: String = None
, vous devez dires: Option[String] = None
. Et sis
est une option, vous ne pouvez pas le dires.length
, mais vous pouvez vérifier qu’elle a une valeur et extraire la valeur en même temps, avec correspondance de motif:s match { case Some(str) => str.length; case None => throw RuntimeException("Where's my value?") }
s: String = null
, mais c’est le prix de l’interopérabilité avec Java. Nous interdisons généralement ce genre de chose dans notre code Scala.Nulls
sont problématiques car elles doivent être explicitement vérifiées, mais le compilateur ne peut pas vous avertir que vous avez oublié de les vérifier. Seule une analyse statique fastidieuse peut vous le dire. Heureusement, il existe plusieurs bonnes alternatives.Prenez la variable hors de la portée. Bien trop souvent,
null
est utilisé comme remplaçant lorsqu'un programmeur déclare une variable trop tôt ou la garde trop longtemps. La meilleure approche consiste simplement à supprimer la variable. Ne le déclarez pas avant d'avoir une valeur valide à y inscrire. Ce n'est pas une restriction aussi difficile que vous ne le pensez et cela rend votre code beaucoup plus propre.Utilisez le modèle d'objet null. Parfois, une valeur manquante est un état valide pour votre système. Le modèle d'objet nul est une bonne solution ici. Cela évite le recours à des contrôles explicites constants, mais vous permet néanmoins de représenter un état nul. Certaines personnes prétendent qu'il ne s'agit pas de "masquer une condition d'erreur", car dans ce cas, un état nul est un état sémantique valide. Cela étant dit, vous ne devriez pas utiliser ce modèle lorsqu'un état null n'est pas un état sémantique valide. Vous devriez juste prendre votre variable hors de portée.
Utilisez une option / peut-être. Tout d’abord, cela permet au compilateur de vous avertir que vous devez rechercher une valeur manquante, mais cela ne se limite pas à remplacer un type de contrôle explicite par un autre. En utilisant
Options
, vous pouvez chaîner votre code et poursuivre comme si votre valeur existait, sans avoir besoin de vérifier réellement jusqu'à la dernière minute. Dans Scala, votre exemple de code ressemblerait à quelque chose comme:Sur la deuxième ligne, où nous construisons le message génial, nous ne vérifions pas explicitement si le client a été trouvé, et la beauté est que nous n’avons pas besoin de le faire . Ce n'est pas avant la toute dernière ligne, qui peut être plusieurs lignes plus tard, ou même dans un module différent, où nous nous intéressons explicitement à ce qui se passe si le
Option
estNone
. Non seulement cela, si nous avions oublié de le faire, il n'aurait pas vérifié le type. Même alors, cela se fait de manière très naturelle, sansif
déclaration explicite .Comparez cela à un
null
, où il vérifie parfaitement le type, mais où vous devez effectuer un contrôle explicite à chaque étape ou lorsque votre application entière explose au moment de l'exécution, si vous avez de la chance lors de votre exercice de tests unitaires. Cela ne vaut tout simplement pas la peine.la source
Le problème central de NULL est qu’il rend le système peu fiable. En 1980, Tony Hoare dans le journal dédié à son prix Turing écrivait:
Le langage ADA a beaucoup changé depuis, mais de tels problèmes persistent en Java, en C # et dans de nombreux autres langages populaires.
Les développeurs ont le devoir de créer des contrats entre un client et un fournisseur. Par exemple, en C #, comme en Java, vous pouvez
Generics
réduire l’impact de laNull
référence en créant en lecture seuleNullableClass<T>
(deux options):et ensuite l'utiliser comme
ou utilisez deux styles avec les méthodes d'extension C #:
La différence est significative. En tant que client d'une méthode, vous savez à quoi vous attendre. Une équipe peut avoir la règle:
Si une classe n'est pas de type NullableClass, son instance ne doit pas être nulle .
L’équipe peut renforcer cette idée en utilisant Conception par contrat et vérification statique au moment de la compilation, par exemple avec les conditions préalables suivantes:
ou pour une ficelle
Cette approche peut considérablement augmenter la fiabilité des applications et la qualité du logiciel. Design by Contract est un cas de logique Hoare , qui a été peuplé par Bertrand Meyer dans son célèbre livre Construction de logiciels orientés objet et en langage Eiffel en 1988, mais il n’est pas utilisé de manière non valide dans l’élaboration de logiciels modernes.
la source
Non.
L'échec d'une langue à prendre en compte une valeur spéciale, comme
null
c'est le problème de la langue. Si vous examinez des langages comme Kotlin ou Swift , qui obligent le rédacteur à traiter explicitement de la possibilité d'une valeur nulle à chaque rencontre, il n'y a pas de danger.Dans des langages comme celui en question, le langage permet
null
d’être une valeur de tout type -ish , mais ne fournit aucune construction permettant de le reconnaître ultérieurement, ce qui conduit à des événementsnull
inattendus (surtout lorsqu’ils vous sont transmis d’autre part), et ainsi vous obtenez des accidents. C’est le mal: vous laisser utilisernull
sans vous en préoccuper.la source
En gros, l'idée centrale est que les problèmes de référence de pointeurs nuls doivent être interceptés au moment de la compilation plutôt qu'à l'exécution. Donc, si vous écrivez une fonction qui prend un objet et que vous ne voulez pas que quelqu'un l'appelle avec des références nulles, le système de type devrait vous permettre de spécifier cette exigence afin que le compilateur puisse l'utiliser pour garantir que l'appel de votre fonction ne serait jamais effectué. compiler si l'appelant ne répond pas à vos besoins. Cela peut être fait de différentes manières, par exemple en décorant vos types ou en utilisant des types d’options (également appelés types nullables dans certaines langues), mais c’est évidemment beaucoup plus de travail pour les concepteurs de compilateur.
Je pense qu'il est compréhensible que dans les années 60 et 70, le concepteur de compilateur ne se soit pas plié en bloc pour mettre en œuvre cette idée, car les machines étaient limitées en ressources, les compilateurs devaient rester relativement simples et personne ne savait vraiment à quel point 40 années sur la ligne.
la source
J'ai une simple objection contre
null
:Il casse complètement la sémantique de votre code en introduisant une ambiguïté .
Souvent, vous vous attendez à ce que le résultat soit d'un certain type. Ceci est sémantiquement correct. Dites, vous avez demandé à la base de données pour un utilisateur avec un certain
id
, vous vous attendez à ce que le résultat soit d'un certain type (=user
). Mais que se passe-t-il s'il n'y a pas d'utilisateur de cet identifiant? Une (mauvaise) solution consiste à: ajouter unenull
valeur. Le résultat est donc ambigu : soit c'est un,user
soit c'est çanull
. Maisnull
n'est pas le type attendu. Et c'est là que commence l' odeur du code .En évitant
null
, votre code est sémantiquement clair.Il y a toujours moyen de contourner le
null
problème: refactoriser les collectionsNull-objects
,optionals
etc.la source
S'il est sémantiquement possible d'accéder à un emplacement de stockage de type référence ou pointeur avant l'exécution du code pour en calculer la valeur, il n'y a pas de choix parfait quant à ce qui devrait se passer. Avoir de tels emplacements par défaut des références de pointeur nulles qui peuvent être librement lues et copiées, mais qui présenteront une défaillance si elles sont déréférencées ou indexées, est un choix possible. Les autres choix incluent:
Le dernier choix pourrait avoir un certain intérêt, en particulier si un langage incluait à la fois des types nullable et non nullable (on pourrait appeler les constructeurs de tableaux spéciaux pour n’importe quel type, mais il ne serait nécessaire de les appeler que pour créer des tableaux de types non nullables), mais n'aurait probablement pas été réalisable à l'époque où des pointeurs nuls ont été inventés. Parmi les autres choix, aucun ne semble plus attrayant que de permettre la copie de pointeurs nuls, mais pas de déréférencement ou d’indexation. L’approche n ° 4 pourrait être une option pratique et devrait être relativement peu coûteuse à mettre en œuvre, mais elle ne devrait certainement pas être la seule option. Exiger que les pointeurs pointent par défaut sur un objet valide particulier est bien pire que d'avoir des pointeurs sur une valeur nulle pouvant être lue ou copiée mais non déréférencée ou indexée.
la source
Oui, NULL est une conception terrible , dans un monde orienté objet. En un mot, l'utilisation de NULL conduit à:
Consultez ce billet de blog pour une explication détaillée: http://www.yegor256.com/2014/05/13/why-null-is-bad.html
la source