Pourquoi «null» est-il présent dans C # et Java?

Nous avons remarqué que beaucoup de bogues de nos logiciels développés en C # (ou Java) provoquaient une exception NullReferenceException.

Y a-t-il une raison pour laquelle “null” a même été inclus dans la langue?

Après tout, s’il n’y avait pas de “null”, je n’aurais pas de bug, non?

En d’autres termes, quelle fonctionnalité dans la langue ne pourrait pas fonctionner sans null?

Anders Hejlsberg, “père C #”, vient de parler de ce point dans son interview Computerworld :

Par exemple, dans le système de types, nous n’avons pas de séparation entre les types valeur et référence et la possibilité de nullité des types. Cela peut paraître un peu compliqué ou un peu technique, mais en C #, les types de référence peuvent être nuls, tels que les chaînes de caractères, mais les types de valeur ne peuvent pas être nuls. Ce serait bien d’avoir des types de référence non-nullables, donc vous pourriez déclarer que “cette chaîne ne peut jamais être nulle, et je veux que le compilateur vérifie que je ne peux jamais bash un pointeur nul ici”.

50% des bogues rencontrés par les utilisateurs aujourd’hui, codés avec C # sur notre plate-forme, et il en va de même pour Java, sont probablement des exceptions de référence null. Si nous avions eu un système de type plus puissant qui vous permettrait de dire que “ce paramètre peut ne jamais être nul et que votre compilateur vérifie cela à chaque appel, en effectuant une parsing statique du code”. Nous aurions alors pu éliminer des classes de bogues.

Cyrus Najmabadi, un ancien ingénieur en conception de logiciels de l’équipe C # (qui travaille actuellement chez Google) discute à ce sujet sur son blog: ( 1er , 2ème , 3ème , 4ème ). Il semble que le plus grand obstacle à l’adoption de types non-nuls est que la notation perturbe les habitudes et la base de code des programmeurs. Quelque chose comme 70% des références des programmes C # sont susceptibles de se retrouver comme non-nullables.

Si vous voulez vraiment avoir un type de référence non nullable en C #, vous devriez essayer d’utiliser Spec # qui est une extension C # permettant l’utilisation de “!” comme un signe non nullable.

static ssortingng AcceptNotNullObject(object! s) { return s.ToSsortingng(); } 

La nullité est une conséquence naturelle des types de référence. Si vous avez une référence, elle doit faire référence à un object – ou être nul. Si vous deviez interdire la nullité, vous devriez toujours vous assurer que chaque variable a été initialisée avec une expression non NULL – et même dans ce cas, vous auriez des problèmes si des variables étaient lues pendant la phase d’initialisation.

Comment proposeriez-vous de supprimer le concept de nullité?

Comme beaucoup de choses dans la programmation orientée object, tout revient à ALGOL. Tony Hoare vient de l’appeler son “erreur d’un milliard de dollars”. Si quelque chose, c’est un euphémisme.

Voici une thèse très intéressante sur la façon de rendre la nullité non la valeur par défaut en Java. Les parallèles à C # sont évidents.

Null in C # est principalement un report de C ++, qui contenait des pointeurs qui ne indiquaient rien dans la mémoire (ou plutôt l’adresse 0x00 ). Dans cette interview , Anders Hejlsberg dit qu’il aurait aimé append des types de référence non nullables en C #.

Null a également une place légitime dans un système de type, cependant, comme quelque chose qui s’apparente au type inférieur (où l’ object est le type supérieur ). Dans lisp, le type de fond est NIL et dans Scala, il n’y a Nothing .

Il aurait été possible de concevoir C # sans aucun NULL, mais il faudrait alors trouver une solution acceptable pour les usages que les gens ont généralement pour null , tels que unitialized-value , not-found , default-value , undefined-value et None . Il y aurait probablement eu moins d’adoption parmi les programmeurs C ++ et Java s’ils y parvenaient. Au moins jusqu’à ce qu’ils aient vu que les programmes C # n’avaient jamais d’exception de pointeur nul.

Supprimer null ne résoudrait pas grand chose. Vous devez avoir une référence par défaut pour la plupart des variables définies sur init. Au lieu d’exceptions de référence null, vous obtiendrez un comportement inattendu car la variable pointe vers les objects incorrects. Au moins les références null échouent rapidement au lieu de provoquer un comportement inattendu.

Vous pouvez regarder le modèle d’object nul pour résoudre une partie de ce problème

Null est une fonctionnalité extrêmement puissante. Que faites-vous si vous avez une absence de valeur? C’est NULL!

Une école de pensée est de ne jamais retourner nul, une autre est de toujours. Par exemple, certains disent que vous devez retourner un object valide mais vide.

Je préfère null car pour moi c’est une indication plus vraie de ce que c’est réellement. Si je ne peux pas récupérer une entité de ma couche de persistance, je veux une valeur nulle. Je ne veux pas de valeur vide. Mais c’est moi.

Il est particulièrement pratique avec les primitives. Par exemple, si j’ai true ou false, mais qu’il est utilisé sur un formulaire de sécurité, une autorisation peut être Allow, Deny ou non. Eh bien, je veux que ce ne soit pas mis à zéro. Donc je peux utiliser bool?

Il y a beaucoup d’autres choses sur lesquelles je pourrais continuer, mais je vais en restr là.

Après tout, s’il n’y avait pas de “null”, je n’aurais pas de bug, non?

La réponse est NON . Le problème n’est pas que C # autorise null, le problème est que vous avez des bogues qui se manifestent avec l’exception NullReferenceException. Comme cela a déjà été dit, les valeurs nulles ont pour but d’indiquer un type de référence “vide” ou une non-valeur (vide / rien / inconnu).

Null ne provoque pas les NullPointerExceptions …

Les programmeurs provoquent des exceptions NullPointerExceptions.

Sans null, nous revenons à l’utilisation d’une valeur arbitraire réelle pour déterminer que la valeur de retour d’une fonction ou d’une méthode est invalide. Vous devez toujours vérifier la valeur -1 (ou peu importe), la suppression des valeurs nulles ne résoudra pas comme par magie la paresse, mais la masquera légèrement.

La question peut être interprétée comme “Est-il préférable d’avoir une valeur par défaut pour chaque type de référence (comme Ssortingng.Empty) ou null?”. Dans cette perspective, je préférerais avoir des valeurs nulles, car;

  • Je ne voudrais pas écrire un constructeur par défaut pour chaque classe que j’écris.
  • Je ne voudrais pas que de la mémoire inutile soit allouée pour de telles valeurs par défaut.
  • Vérifier si une référence est nulle est plus économique que les comparaisons de valeur.
  • Il est hautement possible d’avoir plus de bogues plus difficiles à détecter, au lieu de NullReferanceExceptions. C’est une bonne chose d’avoir une telle exception qui indique clairement que je fais (en supposant) quelque chose de mal.

“Null” est inclus dans le langage car nous avons des types de valeur et des types de référence. C’est probablement un effet secondaire, mais bon, je pense. Cela nous donne beaucoup de pouvoir sur la façon dont nous gérons efficacement la mémoire.

Pourquoi avons-nous null? …

Les types de valeur sont stockés dans la “stack”, leur valeur se trouve directement dans cette partie de la mémoire (c.-à-d. Que int x = 5 signifie que l’emplacement de mémoire pour cette variable contient “5”).

Les types de référence ont en revanche un “pointeur” sur la stack pointant sur la valeur réelle du tas (c.-à-d. Chaîne x = “ello” signifie que le bloc mémoire sur la stack ne contient qu’une adresse pointant sur la valeur réelle du tas ).

Une valeur nulle signifie simplement que notre valeur sur la stack ne pointe sur aucune valeur réelle sur le tas – c’est un pointeur vide.

J’espère que je l’ai bien expliqué.

Si vous obtenez une ‘NullReferenceException’, vous continuez peut-être à faire référence aux objects qui n’existent plus. Ce n’est pas un problème avec ‘null’, c’est un problème avec votre code pointant vers des adresses inexistantes.

Il existe des situations dans lesquelles null est un bon moyen de signifier qu’une référence n’a pas été initialisée. Ceci est important dans certains scénarios.

Par exemple:

 MyResource resource; try { resource = new MyResource(); // // Do some work // } finally { if (resource != null) resource.Close(); } 

Ceci est dans la plupart des cas accompli par l’utilisation d’une instruction using . Mais le modèle est encore largement utilisé.

En ce qui concerne votre exception NullReferenceException, la cause de ces erreurs est souvent facile à réduire en implémentant une norme de codage où tous les parameters sont vérifiés. Selon la nature du projet, je trouve que dans la plupart des cas, il suffit de vérifier les parameters sur les membres exposés. Si les parameters ne se trouvent pas dans la plage attendue, une exception ArgumentException est générée ou un résultat d’erreur est renvoyé, en fonction du modèle de traitement des erreurs utilisé.

La vérification des parameters ne supprime pas en elle-même les bogues, mais les bogues qui surviennent sont plus faciles à localiser et à corriger pendant la phase de test.

En guise de note, Anders Hejlsberg a mentionné le manque d’application non nulle comme l’une des plus grandes erreurs de la spécification C # 1.0 et que l’inclure maintenant est “difficile”.

Si vous pensez toujours qu’une valeur de référence non nulle imposée statiquement est d’une grande importance, vous pouvez vérifier le langage spec # . C’est une extension de C # où les références non nulles font partie du langage. Cela garantit qu’une référence NULL ne peut jamais être atsortingbuée à une référence marquée comme non nulle.

Une réponse mentionnait qu’il y avait des valeurs NULL dans les bases de données. C’est vrai, mais ils sont très différents des null en C #.

En C #, les null sont des marqueurs pour une référence qui ne fait référence à rien.

Dans les bases de données, les valeurs NULL sont des marqueurs pour les cellules de valeur qui ne contiennent pas de valeur. Par cellules de valeur, j’entends généralement l’intersection d’une ligne et d’une colonne dans une table, mais le concept de cellules de valeur pourrait être étendu au-delà des tables.

La différence entre les deux semble banale, au premier abord. Mais ce n’est pas.

Null, tel qu’il est disponible en C # / C ++ / Java / Ruby, est mieux perçu comme une singularité d’un passé obscur (Algol) qui a survécu jusqu’à aujourd’hui.

Vous l’utilisez de deux manières:

  • Pour déclarer des références sans les initialiser (mauvaises).
  • Pour indiquer une option (OK).

Comme vous l’avez deviné, 1) est ce qui nous cause des problèmes sans fin dans les langages impératifs communs et qui aurait dû être banni il y a longtemps, 2) est la véritable caractéristique essentielle.

Il y a des langues là-bas qui évitent 1) sans prévenir 2).

Par exemple, OCaml est un tel langage.

Une fonction simple renvoyant un nombre entier toujours croissant à partir de 1:

 let counter = ref 0;; let next_counter_value () = (counter := !counter + 1; !counter);; 

Et en ce qui concerne les options:

 type dissortingbuted_computation_result = NotYetAvailable | Result of float;; let print_result r = match r with | Result(f) -> Printf.printf "result is %f\n" f | NotYetAvailable -> Printf.printf "result not yet available\n";; 

Je ne peux pas parler de votre problème spécifique, mais il semble que le problème n’est pas l’existence de null. Nul existe dans les bases de données, vous avez besoin d’un moyen de le comptabiliser au niveau de l’application. Je ne pense pas que ce soit la seule raison pour laquelle il existe dans .net, remarquez. Mais je pense que c’est l’une des raisons.

Je suis surpris que personne n’ait parlé de bases de données pour leur réponse. Les bases de données ont des champs nullables et tout langage qui recevra des données d’un DB doit gérer cela. Cela signifie avoir une valeur nulle.

En fait, c’est tellement important que pour les types de base comme int, vous pouvez les rendre nullables!

Considérez également les valeurs de retour des fonctions, et si vous vouliez avoir une fonction diviser un nombre de nombres et le dénominateur pourrait être 0? La seule réponse “correcte” dans un tel cas serait nulle. (Je sais, dans un exemple aussi simple, une exception serait probablement une meilleure option … mais il peut y avoir des situations où toutes les valeurs sont correctes mais des données valides peuvent produire une réponse invalide ou non calculable. cas …)

Outre TOUTES les raisons déjà mentionnées, NULL est nécessaire lorsque vous avez besoin d’un espace réservé pour un object non encore créé. Par exemple. Si vous avez une référence circulaire entre une paire d’objects, vous avez besoin de null car vous ne pouvez pas instancier les deux simultanément.

 class A { B fieldb; } class B { A fielda; } A a = new A() // a.fieldb is null B b = new B() { fielda = a } // b.fielda isnt a.fieldb = b // now it isnt null anymore 

Edit: Vous pourrez peut-être sortir un langage qui fonctionne sans null, mais ce ne sera certainement pas un langage orienté object. Par exemple, prolog n’a pas de valeurs nulles.

Si vous créez un object avec une variable d’instance faisant référence à un object, quelle valeur proposeriez-vous à cette variable avant de lui atsortingbuer une référence d’object?

Je propose:

  1. Ban Null
  2. Étendre les booléens: True, False et FileNotFound

Communément – NullReferenceException signifie que certaines méthodes n’ont pas aimé ce qu’elles ont reçu et ont renvoyé une référence nulle, qui a ensuite été utilisée sans vérifier la référence avant utilisation.

Cette méthode aurait pu donner lieu à des exceptions plus détaillées au lieu de renvoyer null, ce qui est conforme au mode de reflection rapide .

Ou bien la méthode peut renvoyer null pour vous permettre d’écrire si au lieu d’ essayer d’ éviter la “surcharge” d’une exception.

  • Annulation explicite, bien que cela soit rarement nécessaire. On peut peut-être y voir une forme de programmation défensive.
  • Utilisez-le (ou la structure nullable (T) pour les valeurs non nullables) en tant qu’indicateur pour indiquer un champ manquant lors du mappage de champs d’une source de données (stream d’octets, table de firebase database) à un object. Il peut être très compliqué de créer un indicateur booléen pour chaque champ Nullable possible, et il peut être impossible d’utiliser des valeurs sentinelles telles que -1 ou 0 lorsque toutes les valeurs de cette plage sont valides. Ceci est particulièrement pratique quand il y a beaucoup de champs.

Que ces cas d’utilisation ou d’abus soient subjectifs, je les utilise parfois.

Désolé d’avoir répondu avec quatre ans de retard, je suis étonné qu’aucune des réponses n’ait jusqu’à présent répondu à la question originale:

Les langages tels que C # et Java , comme C et les autres langages avant eux, ont une valeur null sorte que le programmeur peut écrire du code rapide et optimisé en utilisant des pointeurs de manière efficace.


  • Vue bas niveau

Un peu d’histoire d’abord. La raison pour laquelle null été inventé est pour l’efficacité. Lorsque vous effectuez une programmation de bas niveau dans l’assemblage, il n’y a pas d’abstraction, vous avez des valeurs dans les registres et vous voulez en tirer le meilleur parti. Définir zéro comme valeur de pointeur non valide est une excellente stratégie pour représenter un object ou rien .

Pourquoi gaspiller la plupart des valeurs possibles d’un bon mot de mémoire, alors que vous pouvez avoir une implémentation très rapide du modèle de valeur facultatif ? C’est pourquoi null est si utile.

  • Vue de haut niveau.

Sémantiquement, null n’est en aucun cas nécessaire aux langages de programmation. Par exemple, dans les langages fonctionnels classiques comme Haskell ou dans la famille ML, il n’y a pas de null, mais plutôt des types nommés Maybe ou Option. Ils représentent le concept plus élevé de valeur facultative sans être concerné de quelque manière que ce soit par le code assembleur généré (ce sera le travail du compilateur).

Et cela est très utile aussi, car cela permet au compilateur d’ attraper plus de bogues , ce qui signifie moins d’exceptions NullReferenceExceptions.

  • Les rassemblant

Contrairement à ces langages de programmation de très haut niveau, C # et Java autorisent une valeur nulle pour chaque type de référence (qui est un autre nom pour le type qui sera implémenté à l’aide de pointeurs ).

Cela peut sembler être une mauvaise chose, mais ce qui est bien, c’est que le programmeur peut utiliser la connaissance de la façon dont cela fonctionne sous le capot, pour créer un code plus efficace (même si le langage possède un nettoyage de la mémoire).

C’est la raison pour laquelle null existe encore dans les langues de nos jours: un compromis entre le besoin d’un concept général de valeur optionnelle et le besoin constant d’efficacité.

La fonctionnalité qui ne pourrait pas fonctionner sans null est de pouvoir représenter “l’absence d’un object”.

L’absence d’un object est un concept important. Dans la programmation orientée object, nous en avons besoin pour représenter une association entre objects qui est facultative: l’object A peut être associé à un object B ou A peut ne pas avoir d’object B. Sans null, nous pouvons toujours émuler ceci: par exemple nous pourrions utiliser une liste d’objects pour associer B à A. Cette liste pourrait contenir un élément (un B) ou être vide. Ceci est un peu gênant et ne résout pas vraiment quoi que ce soit. Le code qui suppose qu’il y a un B, tel que aobj.blist.first().method() va exploser de la même manière qu’une exception de référence null: (si blist est vide, quel est le comportement de blist.first() ?)

En parlant de listes, null vous permet de terminer une liste liée. Un ListNode peut contenir une référence à un autre ListNode qui peut être null. On peut en dire autant des autres structures dynamics telles que les arbres. Null vous permet d’avoir un arbre binary ordinaire dont les noeuds feuilles sont marqués par des références enfants nulles.

Les listes et les arbres peuvent être construits sans null, mais ils doivent être circulaires ou infinis / paresseux. Cela serait probablement considéré comme une contrainte inacceptable par la plupart des programmeurs, qui préféreraient avoir le choix de concevoir des structures de données.

Les douleurs associées aux références nulles, telles que les références nulles survenues accidentellement à cause de bogues et provoquant des exceptions, sont en partie une conséquence du système de type statique, qui introduit une valeur nulle dans chaque type: …

Dans un langage typé dynamicment, il peut y avoir un seul object null, qui a son propre type. Le résultat de ceci est que vous avez tous les avantages de représentation de null, plus une plus grande sécurité. Par exemple, si vous écrivez une méthode qui accepte un paramètre Ssortingng, vous êtes assuré que le paramètre sera un object ssortingng, et non null. Il n’y a pas de référence null dans la classe Ssortingng: un object connu pour être une chaîne ne peut pas être l’object null. Les références n’ont pas de type dans un langage dynamic. Un emplacement de stockage tel qu’un membre de classe ou un paramètre de fonction contient une valeur pouvant être une référence à un object. Cet object a un type, pas la référence.

Donc, ces langages fournissent un modèle propre, plus ou moins purement mathématique de “null”, et ensuite les modèles statiques en font un peu un monstre de Frankenstein.

Si un framework autorise la création d’un tableau de quelque type sans spécifier ce qu’il convient de faire avec les nouveaux éléments, ce type doit avoir une valeur par défaut. Pour les types qui implémentent une sémantique de référence mutable (*), il n’y a dans le cas général aucune valeur par défaut sensible. Je considère comme une faiblesse du framework .NET qu’il n’existe aucun moyen de spécifier qu’un appel de fonction non virtuel doit supprimer toute vérification de null. Cela permettrait aux types immuables comme Ssortingng de se comporter comme des types de valeur, en renvoyant des valeurs sensibles pour des propriétés telles que Length.

(*) Notez que dans VB.NET et C #, la sémantique de référence mutable peut être implémentée par les types class ou struct; un type de structure implémenterait une sémantique de référence mutable en agissant en tant que proxy pour une instance enveloppée d’un object de classe auquel elle renferme une référence immuable.

Il serait également utile de pouvoir spécifier qu’une classe doit avoir une sémantique de type valeur mutable non nullable (ce qui implique – au minimum – que l’instanciation d’un champ de ce type crée une nouvelle instance d’object en utilisant un constructeur par défaut, et que copier un champ de ce type créerait une nouvelle instance en copiant l’ancienne (en manipulant de manière récursive toute classe de type valeur nestede).

Cependant, il est difficile de savoir exactement combien de soutien devrait être intégré dans ce cadre. Le fait que le framework reconnaisse lui-même les distinctions entre les types de valeurs mutables, les types de référence mutables et les types immuables permettrait aux classes qui détiennent elles-mêmes des références à un mélange de types mutables et immuables

Null est une exigence essentielle de toute langue OO. Toute variable d’object à laquelle une référence d’object n’a pas été atsortingbuée doit être nulle.