66 votes

Combien null vérification est-elle suffisante?

Ce sont quelques lignes directrices pour quand il n'est pas nécessaire de vérifier pour une valeur nulle?

Beaucoup de l'héritage de code j'ai travaillé à partir de la fin a null-contrôles ad nauseam. Null contrôles sur trivial fonctions, null contrôles sur les appels d'API que l'état de non-null renvoie, etc. Dans certains cas, le null-vérifications sont raisonnables, mais dans de nombreux endroits, un nul n'est pas une attente raisonnable.

J'ai entendu un certain nombre d'arguments allant de "Vous ne pouvez pas faire confiance à d'autres code" à "TOUJOURS programme, sur la défensive" à "Jusqu'à ce que la langue des garanties moi une valeur non nulle, je suis toujours à la va vérifier." J'ai certainement d'accord avec bon nombre de ces principes jusqu'à un certain point, mais j'ai trouvé excessif null-vérification de la cause d'autres problèmes qui, généralement, contreviennent à ces principes. Est tenace null vérifier vraiment la peine?

Souvent, j'ai observé les codes avec un excès de nulle vérifier réellement être de moins bonne qualité, pas de qualité supérieure. Une grande partie du code semble être tellement concentré sur null-vérifie que le développeur a perdu de vue les autres qualités importantes, telles que la lisibilité, de l'exactitude, ou la gestion des exceptions. En particulier, je vois beaucoup de code ignorer les std::bad_alloc exception, mais faire un null-case sur un new.

En C++, je comprends dans une certaine mesure, le comportement imprévisible de déréférencement d'un pointeur null; null déréférencement est traitée avec plus de grâce en Java, C#, Python, etc. Ai-je vu de mauvaise exemples de vigilance null-la vérification ou l'est-il vraiment quelque chose à cela?

Cette question est destinée à être la langue agnostique, mais je m'intéresse principalement en C++, Java et C#.


Quelques exemples de null-vérification que j'ai vu qui semblent être excessive sont les suivants:


Cet exemple semble être de comptabilité pour les non-standard compilateurs C++ spec dit l'échec d'une nouvelle déclenche une exception. Sauf si vous explicitement soutenir non-conforme compilateurs, est-il logique? Cela fait-il de tout sens dans une langue comme le Java ou le C# (ou même en C++/CLR)?

try {
   MyObject* obj = new MyObject(); 
   if(obj!=NULL) {
      //do something
   } else {
      //??? most code I see has log-it and move on
      //or it repeats what's in the exception handler
   }
} catch(std::bad_alloc) {
   //Do something? normally--this code is wrong as it allocates
   //more memory and will likely fail, such as writing to a log file.
}


Un autre exemple est lorsque l'on travaille sur le code interne. En particulier, si c'est une petite équipe qui peuvent définir leurs propres pratiques en matière de développement, ce qui semble inutile. Sur certains projets ou code hérité de confiance de la documentation ne peut pas être raisonnable... mais pour le nouveau code que vous ou votre équipe de contrôle, est-ce vraiment nécessaire?

Si une méthode, que vous pouvez voir et peut mettre à jour (ou peut crier au développeur qui est responsable) a conclu un contrat, est-il encore nécessaire de vérifier les valeurs null?

//X is non-negative.
//Returns an object or throws exception.
MyObject* create(int x) {
   if(x<0) throw;
   return new MyObject();
}

try {
   MyObject* x = create(unknownVar);
   if(x!=null) {
      //is this null check really necessary?
   }
} catch {
   //do something
}


Lors de l'élaboration d'un privé ou autre fonction interne, est-il vraiment nécessaire de traiter explicitement une valeur null lorsque le contrat prévoit des valeurs non nulles seulement? Pourquoi un null-check-être préférable à une assertion?

(évidemment, sur votre API publique, null vérifications sont indispensables; il est considéré comme impoli de crier à vos utilisateurs de manière incorrecte à l'aide de l'API)

//Internal use only--non-public, not part of public API
//input must be non-null.
//returns non-negative value, or -1 if failed
int ParseType(String input) {
   if(input==null) return -1;
   //do something magic
   return value;
}

Par rapport à:

//Internal use only--non-public, not part of public API
//input must be non-null.
//returns non-negative value
int ParseType(String input) {
   assert(input!=null : "Input must be non-null.");
   //do something magic
   return value;
}

36voto

JoshBerke Points 34238

Une chose de se rappeler que le code que vous écrivez aujourd'hui, alors qu'il peut être une petite équipe et vous pouvez avoir une bonne documentation, va se transformer en code de legs que quelqu'un d'autre devra maintenir. J'utilise les règles suivantes:

  1. Si je suis en train d'écrire une API publique qui sera exposé à d'autres, alors je vais faire null vérifie tous les paramètres de référence.

  2. Si je suis en train d'écrire un composant interne à ma demande, je vous écris nulle vérifie quand j'ai besoin de faire quelque chose de spécial quand un nul qui existe, ou quand je veux faire, il est très clair. Sinon, je ne dérange pas d'avoir la référence nulle exception, puisque c'est aussi assez claire de ce qui se passe.

  3. Lorsque vous travaillez avec des données de retour d'autres peuples cadres, je n'ai vérifier la valeur null lorsque c'est possible et valable pour avoir une valeur null est retourné. Si leur contrat dit qu'il ne la renvoie pas null, je ne vais pas faire le chèque.

19voto

Steve Jessop Points 166970

Notons d'abord que cette un cas spécial de contrat de vérification: vous écrivez du code qui n'est rien d'autre qu'à valider au moment de l'exécution d'un marché documenté est rencontré. Un échec signifie qu'un peu de code quelque part est défectueux.

Je suis toujours un peu dubitatif sur la mise en œuvre des cas particuliers d'une plus généralement concept utile. Contrat de vérification est utile, car il capture des erreurs de programmation la première fois qu'ils traversent une API limite. Quel est si spécial à propos des valeurs null, cela signifie qu'ils sont la seule partie du contrat que vous avez des soins à vérifier? Encore,

Sur le sujet de la validation des entrées:

la valeur null est spécial en Java: un grand nombre d'Api de Java sont écrits, tels que la valeur null est la seule valeur non valide qu'il est même possible de passer dans un appel de méthode. Dans de tels cas, null, cochez la case "entièrement valide" de l'entrée, de l'argument en faveur d'un contrat de vérification s'applique.

En C++, d'autre part, la valeur NULL est un seul de près de 2^32 (2^64 sur les nouvelles architectures) les valeurs non valides qu'un pointeur de paramètre pourrait prendre, étant donné que presque toutes les adresses ne sont pas des objets du type correct. Vous ne pouvez pas "pleinement valider", votre entrée, sauf si vous avez une liste quelque part de tous les objets de ce type.

La question devient alors: est NULLE suffisamment commun d'entrée non valide pour obtenir un traitement spécial qu' (foo *)(-1) n'est pas?

Contrairement à Java, de champs, de ne pas obtenir l'auto-initialisé à NULL, une poubelle valeur non initialisée est tout aussi plausible que la valeur NULL. Mais parfois, des objets en C++ ont pointeur de membres qui sont explicitement NULL-inited, qui signifie "je n'ai pas encore". Si votre interlocuteur n'est présent, alors il y a une importante classe d'erreurs de programmation qui peut être diagnostiqué par un NULL vérifier. Une exception peut être plus facile pour eux de débogage qu'une défaillance de page dans une bibliothèque, ils n'ont pas la source. Donc, si vous n'avez pas l'esprit de l'augmentation du code, il pourrait être utile. Mais il est votre interlocuteur vous devez penser, ce n'est pas vous - ce n'est pas défensive de codage, car il ne 'défend' contre NULL, non pas contre (foo *)(-1).

Si la valeur NULL n'est pas une entrée valide, vous pourriez envisager de prendre le paramètre par référence plutôt que le pointeur, mais beaucoup de codage styles de désapprouver les non-const paramètres de référence. Et si l'appelant passe vous *fooptr, où fooptr est NULLE, alors il l'a fait à personne bon de toute façon. Ce que vous essayez de faire est de presser un peu plus de la documentation à la signature de la fonction, dans l'espoir que votre interlocuteur est plus susceptibles de penser "hmm, peut-fooptr être null ici?" quand ils ont explicitement déréférencement d'elle, que si ils ont juste passer pour vous en tant que pointeur. Il ne va pas plus loin, mais comme il va, ça pourrait aider.

Je ne sais pas C#, mais je comprends que c'est comme Java dans que les références sont la garantie d'avoir des valeurs valides (code de sécurité, au moins), mais à la différence de Java dans que pas tous les types ont une valeur NULL. Donc je suppose que nulle vérifications il y a rarement de la peine: si vous êtes dans le code de sécurité, puis ne pas utiliser un type nullable, sauf si la valeur null est une entrée valide, et si vous êtes dans le code unsafe alors le même raisonnement s'applique, comme en C++.

Sur le sujet de la validation de sortie:

Un problème similaire se pose: en Java, vous pouvez "valider complètement" la sortie par la connaissance de son type, et que la valeur n'est pas nulle. En C++, vous ne pouvez pas valider complètement" la sortie avec une valeur NULL check - pour tout ce que vous savez de la fonction renvoyée un pointeur vers un objet de sa propre pile, qui vient d'être démantelée. Mais si la valeur NULL est une commune de retour non valide en raison de l'constructions typiquement utilisé par l'auteur de l'appel de code, puis de vérifier, il va l'aider.

Dans tous les cas:

Utilisez des affirmations plutôt que de "vrai" code de vérifier les contrats, si possible une fois que votre application fonctionne, vous ne voulez probablement pas le code de la météorisation de chaque destinataire de l'appel de la vérification de toutes ses entrées, et chaque appelant la vérification de ses valeurs de retour.

Dans le cas de l'écriture de code qui est portable pour non-standard C++ implémentations, alors au lieu de le code dans la question qui vérifie la valeur null et attire aussi l'exception, j'aurais probablement une fonction comme ceci:

template<typename T>
static inline void nullcheck(T *ptr) { 
    #if PLATFORM_TRAITS_NEW_RETURNS_NULL
        if (ptr == NULL) throw std::bad_alloc();
    #endif
}

Puis, comme un élément de la liste de choses à faire lorsque le transfert à un nouveau système, vous devez définir PLATFORM_TRAITS_NEW_RETURNS_NULL (et peut être quelques autres PLATFORM_TRAITS) correctement. Évidemment, vous pouvez écrire un en-tête qui le fait pour tous les compilateurs que vous connaissez. Si quelqu'un prend votre code et le compile sur un non-standard C++ mise en œuvre que vous ne connaissez rien au sujet, ils sont fondamentalement sur leur propre pour plus de raisons que cela, donc ils doivent faire eux-mêmes.

10voto

MetroidFan2002 Points 11413

Si vous écrivez du code et de son contrat, vous êtes responsable de l'aide en termes de son contrat et d'assurer que le contrat est correct. Si vous dites "renvoie une valeur non nulle" x, alors le visiteur ne doit pas vérifier la valeur null. Si une exception de pointeur null puis se produit avec cette référence/pointeur, c'est votre contrat qui est incorrect.

Null vérification ne doit aller à l'extrême lors de l'utilisation d'une bibliothèque qui n'est pas fiable, ou n'a pas un bon contrat. Si c'est votre équipe de développement du code, le stress que les contrats ne doivent pas être brisés, et de retracer la personne qui utilise le contrat de manière incorrecte lorsque les bugs se produisent.

7voto

djuth Points 700

Une partie de cela dépend de la façon dont le code est utilisé, s'il s'agit d'une méthode disponible uniquement au sein d'un projet par rapport à un API publique, par exemple. Erreur de l'API de vérification exige quelque chose de plus fort qu'une affirmation.

Ainsi, alors que c'est bien dans un projet où il est pris en charge avec des tests unitaires et des trucs comme ça:

internal void DoThis(Something thing)
{
    Debug.Assert(thing != null, "Arg [thing] cannot be null.");
    //...
}

dans une méthode où vous n'avez pas de contrôle sur les personnes qui l'appelle, quelque chose comme ceci est peut-être mieux:

public void DoThis(Something thing)
{
    if (thing == null)
    {
        throw new ArgumentException("Arg [thing] cannot be null.");
    }
    //...
}

7voto

fizzer Points 8193

Cela dépend de la situation. Le reste de ma réponse suppose C++.

  • Je n'ai jamais tester la valeur de retour de nouveau depuis toutes les implémentations je utiliser jeter bad_alloc en cas d'échec. Si Je voir un héritage de test pour les nouvelles de retour null n'importe quel code je travaille, j' découpez-le et ne vous embêtez pas à le remplacer par quoi que ce soit.
  • À moins d'étroitesse d'esprit des normes de codage l'interdire, j'affirme documenté conditions préalables. De code cassé qui viole un marché publié besoins l'échec immédiatement et façon spectaculaire.
  • Si le nul se pose à partir d'un moteur d'exécution l'échec, ce qui n'est pas due à des ruptures de code, je le jette. fopen échec et malloc échec (même si j'ai rarement si jamais les utiliser en C++) serait tomber dans cette catégorie.
  • Je ne tente pas de les récupérer à partir de l'échec d'allocation. Bad_alloc obtient pris dans main().
  • Si le nul de test est pour un objet qui est collaborateur de ma classe, je réécriture le code de la prendre par référence.
  • Si le collaborateur ne pourrait pas vraiment existent, j'utilise l' Objet Null modèle de conception pour créer un espace réservé à l'échec dans bien définies façons.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X