web-dev-qa-db-fra.com

Les références nulles sont-elles vraiment une mauvaise chose?

J'ai entendu dire que l'inclusion de références nulles dans les langages de programmation est "l'erreur d'un milliard de dollars". Mais pourquoi? Bien sûr, ils peuvent provoquer des NullReferenceExceptions, mais alors quoi? Tout élément du langage peut être une source d'erreurs s'il est mal utilisé.

Et quelle est l'alternative? Je suppose qu'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? De toute façon, si vous oubliez de vérifier que le client existe, vous obtenez une exception.

Je suppose qu'une CustomerNotFoundException est un peu plus facile à déboguer qu'une NullReferenceException car elle est plus descriptive. Est-ce tout ce qu'il y a à faire?

165
Tim Goodman

null is evil

Il y a une présentation sur InfoQ sur ce sujet: Références Null: The Billion Dollar Mistake by Tony Hoare

Type d'option

L'alternative à la programmation fonctionnelle utilise un type d'option , qui peut contenir SOME value ou NONE.

Un bon article Le modèle "Option" qui traite du type d'option et en fournit une implémentation pour Java.

J'ai également trouvé un rapport de bogue pour Java à propos de ce problème: Ajouter des types d'options sympas à Java pour empêcher NullPointerExceptions . Le fonctionnalité demandée a été introduit dans Java 8.

93
Jonas

Le problème est que, parce qu'en théorie tout objet peut être nul 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 élégante des erreurs peut être fonctionnellement identique aux instructions if à vérification nulle. Mais que se passe-t-il quand quelque chose dont vous vous êtes convaincu ne pouvait pas peut-être être nul est, en fait, nul? Kerboom. Quoi qu'il arrive ensuite, je suis prêt à parier que 1) ce ne sera pas gracieux et 2) vous ne l'aimerez pas.

Et ne négligez pas la valeur de "facile à déboguer". Le code de production mature est une créature folle et tentaculaire; tout ce qui vous donne plus d'informations sur ce qui n'a pas fonctionné et où peut vous faire économiser des heures de fouille.

129
BlairHippo

Il y a plusieurs problèmes avec l'utilisation de références nulles dans le code.

Tout d'abord, il est généralement utilisé pour indiquer un état spécial . Plutôt que de définir une nouvelle classe ou constante pour chaque état comme les spécialisations sont normalement effectuées, l'utilisation d'une référence nulle utilise un type/valeur avec perte et massivement généralisé .

Deuxièmement, le débogage du code devient plus difficile lorsqu'une référence nulle apparaît et que vous tentez de déterminer ce qui l'a généré , quel état est en vigueur et sa cause même si vous peut tracer 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 comme états valides pour les paramètres ainsi que pour les valeurs de retour, la programmation défensive (pour les états causés par la conception) nécessite plus de vérification des références nulles à effectuer à divers endroits … juste au cas où.

Cinquièmement, le runtime du langage effectue déjà des vérifications de type lorsqu'il effectue une recherche de sélecteur sur la table de méthodes d'un objet. Vous dupliquez donc l'effort en vérifiant si le type de l'objet est valide/invalide, puis en demandant au runtime de vérifier le type de l'objet valide pour appeler sa méthode.

Pourquoi ne pas utiliser le modèle NullObject pour profiter de la vérification du runtime pour le faire invoquer NOP des méthodes spécifiques à cet état (conformes à l'interface de l'état normal) tout en éliminant toute vérification supplémentaire des références nulles dans votre base de code?

Cela implique plus de travail en créant une classe NullObject pour chaque interface avec laquelle vous souhaitez représenter un état spécial. Mais au moins la spécialisation est isolée à chaque état spécial, plutôt qu'au code dans lequel l'état peut être présent. IOW, le nombre de tests est réduit car vous avez moins de chemins d'exécution alternatifs dans vos méthodes.

50
Huperniketes

Les nuls ne sont pas si mauvais, sauf si vous ne vous y attendez pas. Vous devriez devez explicitement spécifier dans le code que vous attendez null, ce qui est un problème de conception de langage. Considère ceci:

Customer? c = Customer.GetByLastName("Goodman");
// note the question mark -- 'c' is either a Customer or null
if (c != null)
{
    // c is not null, therefore its type in this block is
    // now 'Customer' instead of 'Customer?'
    Console.WriteLine(c.FirstName + " " + c.LastName + " is awesome!");
}
else { Console.WriteLine("There was no customer named Goodman.  How lame!"); }

Si vous essayez d'appeler des méthodes sur un Customer?, vous devriez obtenir une erreur de compilation. La raison pour laquelle plus de langues ne le font pas (IMO) est qu'elles ne prévoient pas le type d'une variable à changer en fonction de l'étendue dans laquelle elle se trouve. Si la langue peut gérer cela, le problème peut être résolu entièrement dans le système de type.

Il y a aussi la manière fonctionnelle de gérer ce problème, en utilisant les types d'options et Maybe, mais je ne le connais pas aussi bien. Je préfère cette façon parce que le code correct ne devrait théoriquement avoir besoin que d'un seul caractère ajouté pour compiler correctement.

Il y a beaucoup d'excellentes réponses qui couvrent les symptômes malheureux de null, donc je voudrais présenter un argument alternatif: Null est une faille dans le système de type.

Le but d'un système de type est de s'assurer que les différentes composantes d'un programme "s'emboîtent" correctement; un programme bien typé ne peut pas "sortir des rails" dans un comportement indéfini.

Considérez un dialecte hypothétique de Java, ou quel que soit votre langage de type statique préféré, où vous pouvez affecter la chaîne "Hello, world!" à toute variable de tout type:

Foo foo1 = new Foo();  // Legal
Foo foo2 = "Hello, world!"; // Also legal
Foo foo3 = "Bonjour!"; // Not legal - only "Hello, world!" is allowed

Et vous pouvez vérifier des variables comme ceci:

if (foo1 != "Hello, world!") {
    bar(foo1);
} else {
    baz();
}

Il n'y a rien d'impossible à cela - quelqu'un pourrait concevoir un tel langage s'il le voulait. La valeur spéciale n'a pas besoin d'être "Hello, world!" - ça aurait pu être le numéro 42, le Tuple (1, 4, 9) ou, disons, null. Mais pourquoi feriez-vous cela? Une variable de type Foo ne devrait contenir que Foos - c'est tout l'intérêt du système de types! null n'est pas un Foo pas plus que "Hello, world!" est. Pire, null n'est pas une valeur de de type , et vous ne pouvez rien y faire!

Le programmeur ne peut jamais être sûr qu'une variable contient réellement un Foo, et le programme non plus; afin d'éviter un comportement indéfini, il doit vérifier les variables pour "Hello, world!" avant de les utiliser comme Foos. Notez que la vérification de la chaîne dans l'extrait précédent ne propage pas le fait que foo1 est vraiment un Foo - bar aura probablement sa propre vérification également, juste pour être sûr.

Comparez cela à l'utilisation d'un type Maybe/Option avec une correspondance de modèle:

case maybeFoo of
 |  Just foo => bar(foo)
 |  Nothing => baz()

À l'intérieur de Just foo clause, vous et le programme savez à coup sûr que notre Maybe Foo la variable contient vraiment une valeur Foo - cette information est propagée le long de la chaîne d'appel, et bar n'a pas besoin de faire de vérification. Parce que Maybe Foo est un type distinct de Foo, vous êtes obligé de gérer la possibilité qu'il puisse contenir Nothing, donc vous ne pouvez jamais en aveugle par un NullPointerException. Vous pouvez raisonner votre programme beaucoup plus facilement et le compilateur peut omettre les vérifications nulles sachant que toutes les variables de type Foo contiennent vraiment Foos. Tout le monde y gagne.

20
Doval

Un pointeur null est un outil

pas un ennemi. Vous devez juste les utiliser correctement.

Pensez une minute au temps nécessaire pour trouver et corriger un bogue typique pointeur invalide, par rapport à la localisation et à la correction d'un bogue de pointeur nul. Il est facile de comparer un pointeur avec null. Il s'agit d'un PITA pour vérifier si un pointeur non nul pointe vers des données valides.

Si vous n'êtes toujours pas convaincu, ajoutez une bonne dose de multithreading à votre scénario, puis détrompez-vous.

Morale de l'histoire: ne jetez pas les enfants avec l'eau. Et ne blâmez pas les outils pour l'accident. L'homme qui a inventé le nombre zéro il y a longtemps était assez intelligent, car ce jour-là, vous pouviez nommer le "rien". Le pointeur null n'est pas si loin de cela.


EDIT: Bien que le modèle NullObject semble être une meilleure solution que les références qui peuvent être nulles, il introduit lui-même des problèmes:

  • Une référence contenant un NullObject ne devrait (selon la théorie) rien faire quand une méthode est appelée. Ainsi, des erreurs subtiles peuvent être introduites en raison de références non affectées en erreur, qui sont maintenant garanties d'être non nulles (yehaa!) Mais effectuent une action indésirable: rien. Avec un NPE, il est presque évident où se situe le problème. Avec un NullObject qui se comporte d'une manière ou d'une autre (mais incorrect), nous introduisons le risque que des erreurs soient détectées (trop) tardivement. Ce n'est pas par accident semblable à 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 qui ne l'est pas, introduit des erreurs de données et peut être difficile à localiser. Pour être honnête, dans ces cas, je préférerais sans un clin d'œil un NPE qui échoue immédiatement, maintenant, à une erreur logique/de données qui me frappe soudainement plus tard en bas de la route. Vous vous souvenez de l'étude d'IBM sur le coût des erreurs en fonction de leur stade de détection?

  • La notion de ne rien faire lorsqu'une méthode est appelée sur un NullObject ne tient pas, lorsqu'un getter de propriété ou une fonction est appelée, ou lorsque la méthode retourne des valeurs via out. Disons que la valeur de retour est un int et nous décidons que "ne rien faire" signifie "retourner 0". Mais comment être sûr que 0 est le bon "rien" à (ne pas) renvoyer? Après tout, le NullObject 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 le NullObject pour empêcher le NPE, et nous ne le commercialiserons certainement pas contre une autre exception (non implémentée, divisée par zéro, ...), n'est-ce pas? Alors, comment renvoyez-vous correctement rien lorsque vous devez retourner quelque chose?

Je ne peux pas aider, mais lorsque quelqu'un essaie d'appliquer le modèle NullObject à chaque problème, cela ressemble plus à essayer de réparer une erreur en faisant une autre. C'est sans aucun doute une bonne et utile solution dans certains cas, mais ce n'est sûrement pas la solution miracle pour tous les cas.

15
JensG

(Jeter mon chapeau dans le ring pour une vieille question;))

Le problème spécifique avec null est qu'il rompt le typage statique.

Si j'ai un Thing t, Alors le compilateur peut garantir que je peux appeler t.doSomething(). Eh bien, UNLESS t est null au moment de l'exécution. Maintenant, tous les paris sont désactivés. Le compilateur a dit que c'était OK, mais je découvre beaucoup plus tard que t ne fait PAS doSomething(). Donc, plutôt que de pouvoir faire confiance au compilateur pour détecter les erreurs de type, je dois attendre l'exécution pour les détecter. Je pourrais aussi bien utiliser Python!

Donc, dans un sens, null introduit le typage dynamique dans un système typé statiquement, avec les résultats attendus.

La différence entre une division par zéro ou un log négatif, etc. est que lorsque i = 0, c'est toujours un int. 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 autorisée par la logique ... mais si la logique fait cela, c'est à peu près la définition d'un bogue.

(Le compilateur peut détecter certains de ces problèmes, BTW. Comme marquer des expressions comme i = 1 / 0 Au moment de la compilation. Mais vous ne pouvez pas vraiment attendez-vous à ce que le compilateur suive une fonction et s'assure que les paramètres sont tous cohérents avec la logique de la fonction)

Le problème pratique est que vous faites beaucoup de travail supplémentaire et ajoutez des contrôles nuls pour vous protéger au moment de l'exécution, mais que faire si vous en oubliez un? Le compilateur vous empêche d'affecter:

Chaîne s = nouvel entier (1234);

Alors pourquoi devrait-il autoriser l'affectation à une valeur (nulle) qui rompra les dé-références à s?

En mélangeant "aucune valeur" avec des références "typées" dans votre code, vous mettez un fardeau supplémentaire sur les programmeurs. Et lorsque des exceptions NullPointerException se produisent, leur localisation peut être encore plus longue. Plutôt que de compter sur une saisie statique pour dire "ceci est une référence à quelque chose d’attendu", vous laissez le langage dire "ceci peut-être bien une référence à quelque chose d’attendu. "

14
Rob

Et quelle est l'alternative?

Types et correspondance de motifs facultatifs. Comme je ne connais pas C #, voici un morceau de code dans un langage fictif appelé Scala # :-)

Customer.GetByLastName("Goodman")    // returns Option[Customer]
match
{
    case Some(customer) =>
    Console.WriteLine(customer.FirstName + " " + customer.LastName + " is awesome!");

    case None =>
    Console.WriteLine("There was no customer named Goodman.  How lame!");
}
8
fredoverflow

Les références nulles sont une erreur car elles autorisent un code non sensible:

foo = null
foo.bar()

Il existe des alternatives, si vous exploitez le système de type:

Maybe<Foo> foo = null
foo.bar() // error{Maybe<Foo> does not have any bar method}

L'idée générale est de mettre la variable dans une boîte, et la seule chose que vous pouvez faire est de la déballer, de préférence en demandant l'aide du compilateur comme proposé pour Eiffel.

Haskell l'a à partir de zéro (Maybe), en C++, vous pouvez tirer parti de boost::optional<T> mais vous pouvez toujours obtenir un comportement indéfini ...

8
Matthieu M.

Le problème avec les valeurs nulles est que les langages qui les autorisent vous obligent à la programmation défensive contre lui. Il faut beaucoup d'efforts (bien plus que d'essayer d'utiliser des blocs if défensifs) pour s'assurer que

  1. les objets que vous attendez d'eux pas comme étant nuls ne sont en effet jamais nuls, et
  2. que vos mécanismes défensifs traitent effectivement tous les NPE potentiels de manière efficace.

Donc, en effet, les valeurs nulles finissent par être une chose coûteuse à avoir.

6
luis.espinal

La question de savoir dans quelle mesure votre langage de programmation tente de prouver l'exactitude de votre programme avant de l'exécuter. Dans une langue typée statiquement, vous prouvez que vous avez les bons types. En passant à la valeur par défaut des références non nullables (avec des références nullables facultatives), vous pouvez éliminer la plupart des cas où null est passé et ce ne devrait pas être le cas. La question est de savoir si l'effort supplémentaire dans la gestion des références non annulables en vaut la peine en termes d'exactitude du programme.

4
Winston Ewert

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 à

public void MakeCake(Egg egg, Flour flour, Milk milk)
{
    if (Egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    Egg.Crack();
    MixingBowl.Mix(Egg, flour, milk);
    // etc
}

// inside Mixing bowl class
public void Mix(Egg egg, Flour flour, Milk milk)
{
    if (Egg == null) { throw ... }
    if (flour == null) { throw ... }
    if (milk == null) { throw ... }

    //... etc
}

Lorsque les références de classe sont transmises, la programmation défensive vous encourage à vérifier à nouveau tous les paramètres pour null, même si vous venez de les vérifier juste à l'avance, en particulier lors de la construction d'unités réutilisables en cours de test. La même référence pourrait facilement être vérifiée 10 fois nulle dans la base de code!

Ne serait-il pas préférable que vous puissiez avoir un type nullable normal lorsque vous obtenez la chose, traitez null puis et là, puis passez-le en tant que type non nullable à toutes vos petites fonctions et classes d'assistance sans vérifier null toutes les temps?

C'est le point de la solution Option - non pas pour vous permettre d'activer les états d'erreur, mais pour permettre la conception de fonctions qui implicitement ne peuvent pas accepter d'arguments nuls. Que l'implémentation "par défaut" des types soit annulable ou non annulable 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 répertorié comme la fonctionnalité n ° 2 la plus votée pour C # -> https://visualstudio.uservoice.com/forums/121579-visual-studio/category/30931-languages-c

4
Michael Parker

Null est mal. Cependant, l'absence d'un nul peut être un plus grand mal.

Le problème est que dans le monde réel, vous avez souvent une situation où vous n'avez pas de données. Votre exemple de la version sans null peut encore exploser - soit vous avez fait une erreur de logique et avez oublié de vérifier Goodman, soit peut-être que Goodman s'est marié entre le moment où vous avez vérifié et le moment où vous l'avez recherchée. (Cela aide à évaluer la logique si vous pensez que Moriarty regarde chaque bit de votre code et essaie de vous faire trébucher.)

Que fait la recherche de Goodman lorsqu'elle n'est pas là? Sans null, vous devez renvoyer 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 quoi qu'il arrive ou qu'il fonctionne correctement. Si un comportement incorrect est préférable à aucun comportement, alors vous ne voulez pas de null. (Pour un exemple de la façon dont cela pourrait être le bon choix, considérons le premier lancement de l'Ariane V.Une erreur non capturée/0 a provoqué un virage dur de la fusée et l'autodestruction a déclenché lorsqu'elle s'est détachée à cause de cela. La valeur il essayait de calculer ne servait plus à rien à 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 sauterais de joie en notant leur version personnelle.

3
Loren Pechtel

Optimiser pour le cas le plus courant.

Devoir vérifier la nullité tout le temps est fastidieux - vous voulez pouvoir simplement saisir l'objet Client et travailler avec lui.

Dans le cas normal, cela devrait très bien fonctionner - vous recherchez, récupérez l'objet et l'utilisez.

Dans le cas exceptionnel, où vous recherchez (au hasard) un client par son nom, sans savoir si cet enregistrement/objet existe, vous auriez besoin d'une indication que cela a échoué. Dans cette situation, la réponse est de lever une exception RecordNotFound (ou de laisser le fournisseur SQL ci-dessous le faire pour vous).

Si vous êtes dans une situation où vous ne savez pas si vous pouvez faire confiance aux données entrant (le paramètre), peut-être parce qu'elles ont été saisies par un utilisateur, vous pouvez également fournir le `` TryGetCustomer (nom, client sortant) '' modèle. Référence croisée avec int.Parse et int.TryParse.

1
JBRWilkinson

Les exceptions ne sont pas valables!

Ils sont là pour une raison. Si votre code fonctionne mal, il existe un modèle génial, appelé exception, et il vous indique que quelque chose ne va pas.

En évitant d'utiliser des objets nuls, vous cachez une partie de ces exceptions. Je ne pointe pas sur l'exemple OP où il convertit une exception de pointeur nul en une exception bien typée, cela pourrait en fait être une bonne chose car cela augmente la lisibilité. Cependant, lorsque vous prenez la solution Type d'option comme l'a souligné @Jonas, vous cachez des exceptions.

Que dans votre application de production, au lieu d'exception à lever lorsque vous cliquez sur le bouton pour sélectionner une option vide, rien ne se passe. Au lieu d'une exception de pointeur nul, une exception serait levée et nous obtiendrions probablement un rapport de cette exception (comme dans de nombreuses applications de production, nous avons un tel mécanisme), et que nous pourrions réellement le corriger.

Rendre votre code à l'épreuve des balles en évitant les exceptions est une mauvaise idée, vous rendre le code à l'épreuve des balles en fixant les exceptions, et bien c'est le chemin que je choisirais.

0
Ilya Gazman

Non.

L'échec d'une langue à prendre en compte une valeur spéciale comme null est le problème de la langue. Si vous regardez des langues comme Kotlin ou Swift , qui forcent l'écrivain à traiter explicitement la possibilité d'une valeur nulle à chaque rencontre, alors il n'y a pas de danger.

Dans des langues comme celle en question, la langue permet à null d'être une valeur de tout-ish tapez, mais ne fournit aucune construction pour reconnaître cela plus tard, ce qui conduit à ce que les choses soient null de manière inattendue (en particulier lorsqu'elles vous sont transmises ailleurs), et vous obtenez donc des plantages. C'est le mal: vous permettre d'utiliser null sans vous obliger à y faire face.

0
Ben Leggiero

Nulls sont problématiques car ils doivent être explicitement vérifiés, mais le compilateur ne peut pas vous avertir que vous avez oublié de les vérifier. Seule une analyse statique chronophage peut vous le dire. Heureusement, il existe plusieurs bonnes alternatives.

Prenez la variable hors de portée. Trop souvent, null est utilisé comme espace réservé lorsqu'un programmeur déclare une variable trop tôt ou la garde trop longtemps. La meilleure approche consiste simplement à se débarrasser de la variable. Ne le déclarez pas avant d'avoir une valeur valide à y mettre. Ce n'est pas une restriction aussi difficile qu'on pourrait le penser, et cela rend votre code beaucoup plus propre.

tilisez le modèle d'objet nul. Parfois, une valeur manquante est un état valide pour votre système. Le modèle d'objet nul est une bonne solution ici. Il évite la nécessité de contrôles explicites constants, mais vous permet toujours de représenter un état nul. Il ne s'agit pas de "supprimer une condition d'erreur", comme certains le prétendent, car dans ce cas, un état nul est un état sémantique valide. Cela étant dit, vous ne devez pas utiliser ce modèle lorsqu'un état nul n'est pas un état sémantique valide. Vous devez simplement prendre votre variable hors de portée.

tiliser une Peut-être/Option. Tout d'abord, cela permet au compilateur de vous avertir que vous devez vérifier une valeur manquante, mais cela fait plus que remplacer un type de vérification explicite par un autre. En utilisant Options, vous pouvez enchaîner votre code et continuer comme si votre valeur existe, sans avoir besoin de vérifier jusqu'à la dernière minute absolue. Dans Scala, votre exemple de code ressemblerait à quelque chose comme:

val customer = customerDb getByLastName "Goodman"
val awesomeMessage =
  customer map (c => s"${c.firstName} ${c.lastName} is awesome!")
val notFoundMessage = "There was no customer named Goodman.  How lame!"
println(awesomeMessage getOrElse notFoundMessage)

Sur la deuxième ligne, où nous créons le message génial, nous ne vérifions pas explicitement si le client a été trouvé, et la beauté est nous n'avons pas besoin de . Ce n'est qu'à la toute dernière ligne, qui pourrait ê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 est None. Non seulement cela, si nous avions oublié de le faire, il n'aurait pas été vérifié. Même alors, cela se fait de manière très naturelle, sans instruction if explicite.

Comparez cela avec un null, où le type vérifie très bien, mais où vous devez effectuer une vérification explicite à chaque étape du processus ou votre application entière explose au moment de l'exécution, si vous êtes chanceux lors d'une utilisation cas votre exercice de tests unitaires. Cela ne vaut tout simplement pas la peine.

0
Karl Bielefeldt

Le problème central de NULL est qu'il rend le système peu fiable. En 1980, Tony Hoare dans le journal consacré à son prix Turing écrivait:

Et donc, le meilleur de mes conseils aux créateurs et concepteurs d'ADA a été ignoré. …. Ne laissez pas ce langage dans son état actuel être utilisé dans des applications où la fiabilité est critique, c'est-à-dire les centrales nucléaires, les missiles de croisière, les systèmes d'alerte précoce, les systèmes de défense antimissile antiballistique. La prochaine fusée à s'égarer à la suite d'une erreur de langage de programmation peut ne pas être une fusée spatiale exploratoire lors d'un voyage inoffensif à Vénus: il peut s'agir d'une ogive nucléaire explosant au-dessus d'une de nos propres villes. Un langage de programmation peu fiable générant des programmes non fiables constitue un risque beaucoup plus grand pour notre environnement et pour notre société que les voitures dangereuses, les pesticides toxiques ou les accidents dans les centrales nucléaires. Soyez vigilant pour réduire le risque, pas pour l'augmenter.

Le langage ADA a beaucoup changé depuis, mais de tels problèmes existent toujours en Java, C # et dans de nombreux autres langages populaires.

Il est du devoir du développeur de créer des contrats entre un client et un fournisseur. Par exemple, en C #, comme en Java, vous pouvez utiliser Generics pour minimiser l'impact de la référence Null en créant en lecture seule NullableClass<T> (deux options):

class NullableClass<T>
{
     public HasValue {get;}
     public T Value {get;}
}

puis l'utiliser comme

NullableClass<Customer> customer = dbRepository.GetCustomer('Mr. Smith');
if(customer.HasValue){
  // one logic with customer.Value
}else{
  // another logic
} 

ou utilisez deux options de style avec les méthodes d'extension C #:

customer.Do(
      // code with normal behaviour
      ,
      // what to do in case of null
) 

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 alors son instance ne doit pas être nulle .

L'équipe peut renforcer cette idée en utilisant Design by Contract et la vérification statique au moment de la compilation, par exemple avec une condition préalable:

function SaveCustomer([NotNullAttribute]Customer customer){
     // there is no need to check whether customer is null 
     // it is a client problem, not this supplier
}

ou pour une chaîne

function GetCustomer([NotNullAndNotEmptyAttribute]String customerName){
     // there is no need to check whether customerName is null or empty 
     // it is a client problem, not this supplier
}

Ces approches peuvent augmenter considérablement la fiabilité des applications et la qualité des logiciels. Design by Contract est un cas de Hoare logic , qui a été peuplé par Bertrand Meyer dans son célèbre livre Object-Oriented Software Construction et le langage Eiffel en 1988, mais il n'est pas utilisé de manière invalide dans la conception de logiciels modernes.

0
Artru