Faut-il vérifier la valeur null s'il ne s'attend pas à une valeur nulle?

113

La semaine dernière, nous avons eu un débat passionné sur la gestion des valeurs NULL dans la couche de service de notre application. La question se situe dans le contexte .NET, mais ce sera la même chose en Java et dans de nombreuses autres technologies.

La question était: devriez-vous toujours vérifier les nulls et faire fonctionner votre code à tout moment, ou laisser une exception bouillonner quand une null est reçue de manière inattendue?

D'un côté, vérifier la valeur null là où vous ne l'attendez pas (c'est-à-dire ne pas avoir d'interface utilisateur pour la gérer) est, à mon avis, identique à l'écriture d'un bloc try avec un catch vide. Vous ne faites que cacher une erreur. L'erreur peut être que quelque chose a changé dans le code et que null est maintenant une valeur attendue, ou il y a une autre erreur et un ID incorrect est transmis à la méthode.

En revanche, vérifier les valeurs nulles peut être une bonne habitude en général. De plus, s’il ya vérification, l’application peut continuer à fonctionner, seule une petite partie de la fonctionnalité n’ayant aucun effet. Ensuite, le client peut signaler un petit bogue du type "impossible de supprimer un commentaire" au lieu d'un bogue beaucoup plus grave tel que "impossible d'ouvrir la page X".

Quelle pratique suivez-vous et quels sont vos arguments pour ou contre l'une ou l'autre approche?

Mise à jour:

Je veux ajouter quelques détails sur notre cas particulier. Nous avons récupéré des objets de la base de données et les avons traités (construisons une collection, par exemple). Le développeur qui a écrit le code n'a pas prévu que l'objet puisse être null et n'a donc inclus aucune vérification. Lors du chargement de la page, une erreur s'est produite et la totalité de la page n'a pas été chargée.

Évidemment, dans ce cas, il aurait dû y avoir un contrôle. Nous nous sommes ensuite demandé si tous les objets traités devaient être vérifiés, même s'ils ne devaient pas être manquants, et si le traitement éventuel devait être interrompu en mode silencieux.

L'avantage hypothétique serait que la page continuera à fonctionner. Pensez à des résultats de recherche sur Stack Exchange dans différents groupes (utilisateurs, commentaires, questions). La méthode pourrait vérifier la valeur null et abandonner le traitement des utilisateurs (ce qui, en raison d'un bogue, a la valeur null), mais renvoyer les sections "comments" et "questions". La page continuerait à fonctionner sauf que la section "utilisateurs" serait manquante (ce qui est un bogue). Devrions-nous échouer tôt et casser toute la page ou continuer à travailler et attendre que quelqu'un remarque la section "utilisateurs" manquante?

Stilgar
la source
62
Principe de Fox Mulder: ne faites confiance à personne.
Yannis
14
@YannisRizos: ce principe est bon: il est tellement bon que les créateurs de Java et de C # ont laissé l'environnement d'exécution du langage le faire automatiquement. Donc, normalement, il n’est pas nécessaire de faire une vérification explicite de null partout dans votre code dans ces langues.
Doc Brown
4
doublon possible de Une méthode doit-elle valider ses paramètres?
Martin York
Je suis d'accord avec tdammers répondre. Je crois que la vérification de null est tout simplement fausse, car vous n’avez pas un moyen raisonnable de le gérer. Toutefois, dans votre scénario, vous pouvez gérer une section défaillante, telle que la validation d’un utilisateur (ou l’obtention de commentaires ou quoi que ce soit d'autre) et la présence d’une zone "Aucune erreur ne s’est produite". J'enregistre personnellement toutes les exceptions dans mon application et filtre certaines exceptions, telles que les 404 et la fermeture de la connexion, de façon inattendue
2
assert(foo != null, "foo is web control within the repeater, there's no reason to expect it to be null, etc, etc...");
zzzzBov

Réponses:

105

La question n'est pas tant de savoir si vous devez vérifier nullsi le moteur d'exécution génère une exception. c'est comment vous devriez réagir à une situation aussi inattendue.

Vos options sont alors:

  • Lancez une exception générique ( NullReferenceException) et laissez-la bouillonner; Si vous ne faites pas le nullcontrôle vous-même, c'est ce qui se produit automatiquement.
  • Lancez une exception personnalisée décrivant le problème à un niveau supérieur. ceci peut être réalisé soit en jetant le nullchèque, soit en attrapant un NullReferenceExceptionet en lançant l'exception plus spécifique.
  • Récupérez en substituant une valeur par défaut appropriée.

Il n'y a pas de règle générale pour déterminer laquelle est la meilleure solution. Personnellement, je dirais:

  • L'exception générique est préférable si la condition d'erreur signale un bogue grave dans votre code, c'est-à-dire que la valeur null ne devrait jamais être autorisée à y parvenir. Une telle exception peut alors remonter jusque dans la journalisation des erreurs que vous avez configurée, afin que quelqu'un soit averti et corrige le bogue.
  • La solution de valeur par défaut est bonne si la fourniture d'un résultat semi-utile est plus importante que l'exactitude, comme un navigateur Web qui accepte le code HTML techniquement incorrect et fait de son mieux pour le rendre correctement.
  • Sinon, j'irais avec l'exception spécifique et le gérer dans un endroit approprié.
tdammers
la source
1
@Stilgar pour l'utilisateur final, il n'y a pas de différence. pour le développeur, une exception spécifique telle que "le serveur de base de données n'est pas accessible" est plus intuitive que le "pointeur nul excepté"
k3b
32
En cas d'échec rapide et précoce, il y a moins de risques que des problèmes tels que la mauvaise authentification, des données corrompues persistantes au stockage, des fuites d'informations et d'autres choses amusantes se produisent.
Lars Viklund
1
@Stilgar: Il y a deux préoccupations ici: le comportement sans surveillance et la maintenabilité. Alors que pour le comportement sans surveillance, il y a peu de différence entre une exception spécifique et une exception générique, pour la maintenabilité, cela peut faire la différence entre "oh, c'est pourquoi les choses explosent" et un débogage multi-heure.
tdammers
3
tdammers est tout à fait juste. La "référence d'objet non définie à une instance d'objet" est l'une des exceptions les plus gênantes que je vois le plus fréquemment. Je préférerais de beaucoup voir une exception spécifique, qu'il s'agisse d'une exception ArgumentNullException avec le nom du paramètre ou d'une exception personnalisée "Aucun enregistrement de publication n'existe pour l'utilisateur TK421."
Sean Chase
1
@ k3b Pour l'utilisateur final également, "le serveur de base de données n'est pas accessible" est très utile. Au moins, pour tout utilisateur final qui a une idée des problèmes courants - cela peut l’aider à trouver qu’un câble est lâche, le routeur doit être redémarré, etc. plutôt que de se fâcher contre un logiciel buggy.
Julia Hayward le
58

IMHO essayer de gérer des valeurs NULL que vous n'attendez pas conduit à un code trop compliqué. Si vous ne vous attendez pas à null, clarifiez-le en jetant ArgumentNullException. Je suis vraiment frustré lorsque les gens vérifient si la valeur est nulle et essaient ensuite d'écrire du code qui n'a aucun sens. Il en va de même pour l'utilisation SingleOrDefault(ou pire encore pour la collecte) lorsque quelqu'un s'attend vraiment, Singleet de nombreux autres cas où les gens ont peur (je ne sais pas vraiment de quoi) énoncer clairement leur logique.

empi
la source
Au lieu de ArgumentNullException, vous devriez utiliser des contrats de code (sauf s'il s'agit d'un code source antérieur à 2010 qui ne prend pas en charge les contrats de code).
Arseni Mourzenko
Je suis d'accord avec toi. Si la valeur null n'est pas attendue, elle ne doit pas être traitée de manière étrange mais uniquement signalée.
Giorgio
9
Si je lis la question correctement, lancer un ArgumentNullExceptioncompte comme "manipulant" la valeur NULL: cela empêche une exception de référence Null ultérieure.
KutuluMike
@ MichaelEdenfield: Je ne pense pas que cela compte comme une manipulation réelle en fonction de la question posée (notre objectif n'est pas de faire fonctionner votre code, quoi qu'il arrive ). Pour être honnête, j'oublie souvent d'écrire le bloc approprié qui sera lancé si null est passé. Cependant, je considère que lancer NullArgumentException est la meilleure pratique et la pire pratique à mon avis est d'écrire du code qui ne peut pas gérer null, mais au lieu de générer des retours d'exception, par exemple un autre null en tant que résultat de méthode (ou chaîne vide, ou collection vide, ou -1 , ou saute l’exécution de la méthode si le type de retour est nul, etc.).
empi
2
@Giorgio, ArgumentNullExceptionexplique très clairement quel est le problème et comment le résoudre. C # n'a pas tendance à utiliser "indéfini". Si vous appelez quelque chose d'une manière qui n'est pas définie, la façon dont vous l'appelez n'a pas de sens. Une exception est la bonne façon de l'indiquer. Maintenant, parfois, je crée des méthodes d'analyse qui renvoient null si le int(par exemple) ne peut pas être analysé. Mais c'est un cas où un résultat imparable est une possibilité légitime plutôt qu'une erreur d'utilisation. Voir aussi cet article .
Kyralessa
35

L'habitude de vérifier pour nullmon expérience vient des anciens C ou C ++ développeurs, dans les langues que vous avez une bonne chance de cacher une erreur grave lors de ne pas vérifier pour NULL. Comme vous le savez, en Java ou en C #, les choses sont différentes, utiliser une référence null sans rechercher nullune exception provoquera une exception. Ainsi, l'erreur ne sera pas cachée secrètement tant que vous ne l'ignorerez pas du côté de l'appelant. Ainsi, dans la plupart des cas, la recherche explicite de nulln'a pas beaucoup de sens et complique le code plus que nécessaire. C'est pourquoi je ne considère pas la vérification nulle comme une bonne habitude dans ces langues (du moins, pas en général).

Bien sûr, il y a des exceptions à cette règle, voici celles auxquelles je peux penser:

  • vous voulez un meilleur message d'erreur, lisible par l'homme, indiquant à l'utilisateur dans quelle partie du programme la mauvaise référence null s'est produite. Votre test pour null génère donc une exception différente, avec un texte d'erreur différent. De toute évidence, aucune erreur ne sera masquée de cette façon.

  • vous voulez que votre programme "échoue plus tôt". Par exemple, vous souhaitez vérifier la valeur null d'un paramètre de constructeur, où la référence de l'objet ne serait sinon stockée que dans une variable membre et utilisée ultérieurement.

  • vous pouvez gérer la situation d'une référence nulle en toute sécurité, sans risque de défaillance ultérieure et sans masquer une erreur grave.

  • vous voulez un type de signalisation d'erreur complètement différent (par exemple, renvoyer un code d'erreur) dans votre contexte actuel

Doc Brown
la source
3
"vous voulez un meilleur message d'erreur lisible par l'homme" - c'est la meilleure raison de le faire, à mon avis. "Référence d'objet non définie sur une instance d'objet" ou "Exception de référence nulle" n'est jamais aussi utile que "Produit non instancié".
pdr
@pdr: Une autre raison de vérifier est de s'assurer qu'il est toujours détecté.
Giorgio
13
"Anciens développeurs C", peut-être. "Anciens développeurs C ++" uniquement si ceux-ci récupèrent eux-mêmes des développeurs C. En tant que développeur C ++ moderne, je me méfierais beaucoup du code qui utilise des pointeurs nus ou une allocation dynamique imprudente. En fait, je serais tenté de renverser l'argument et de dire que C ++ ne souffre pas du problème décrit par l'OP, car ses variables ne possèdent pas la propriété supplémentaire spéciale null-ness que Java et C # ont.
Kerrek SB
7
Votre premier paragraphe ne représente que la moitié de l'histoire: oui, si vous utilisez une référence null en C #, vous obtiendrez une exception, tandis qu'en C ++, vous obtiendrez un comportement indéfini. Mais en C # bien écrit, vous êtes bloqué avec beaucoup plus de références nullables qu'en C ++ bien écrit.
James McNellis
Plus l'encapsulation est bonne, meilleure sera la réponse.
radarbob
15

Utilisez des assertions pour tester et indiquer les pré / post-conditions et les invariants de votre code.

Il est tellement plus facile de comprendre ce que le code attend et ce que l’on peut s’attendre à gérer.

Une affirmation, IMO, est un chèque approprié car il s’agit de:

  • efficace (généralement pas utilisé dans la libération),
  • bref (généralement une seule ligne) et
  • clear (condition préalable violée, il s'agit d'une erreur de programmation).

Je pense que le code auto-documenté est important, donc avoir une vérification est bien. La programmation défensive, rapide après échec, facilite beaucoup la maintenance, où l’on passe généralement le plus de temps possible.

Mise à jour

Wrt votre élaboration. Il est généralement utile de donner à l'utilisateur final une application qui fonctionne bien sous les bogues, c'est-à-dire autant que possible. La robustesse est bonne, car le ciel ne sait que ce qui se brisera à un moment critique.

Cependant, les développeurs et les testeurs doivent être conscients des bogues dès que possible, vous souhaiterez donc probablement une infrastructure de journalisation avec un point d'ancrage pouvant afficher une alerte à l'utilisateur en cas de problème. L'alerte devrait probablement être affichée pour tous les utilisateurs, mais peut probablement être adaptée différemment en fonction de l'environnement d'exécution.

Macke
la source
affirme que ne pas être présent dans la publication est un gros point négatif pour moi, cette dernière est l'heure exacte à laquelle vous souhaitez obtenir les informations supplémentaires
jk.
1
Eh bien, sentez-vous libre d’utiliser une fonction de lancement conditionnelle qui est active dans la version aussi si vous en avez besoin. J'ai roulé le mien assez souvent.
Macke
7

Échouer tôt, échouer souvent. nullest l’une des meilleures valeurs inattendues que vous puissiez obtenir, car vous pouvez rapidement échouer dès que vous essayez de l’utiliser. D'autres valeurs inattendues ne sont pas si faciles à détecter. Comme cela nulléchoue automatiquement pour vous chaque fois que vous essayez de l’utiliser, je dirais que cela ne nécessite pas de vérification explicite.

DeadMG
la source
28
J'espère que vous vouliez dire: échouer tôt, échouer vite, pas souvent ...
empi
12
Le problème est que, sans vérifications explicites, une valeur null peut parfois se propager assez loin avant de provoquer une exception. Il peut alors être très difficile de savoir d'où elle provient.
Michael Borgwardt
@MichaelBorgwardt N'est-ce pas ce à quoi servent les traces de pile?
R0MANARMY
6
@ R0MANARMY: les traces de pile ne sont d'aucune aide lorsqu'une valeur null est enregistrée dans un champ et que l'exception NullPointerException est levée beaucoup plus tard.
Michael Borgwardt
@ R0MANARMY même si vous pouviez faire confiance au numéro de ligne dans le suivi de la pile (dans de nombreux cas, vous ne le pouvez pas), certaines lignes contiennent plusieurs variables pouvant être nulles.
Ohad Schneider
6
  • Rechercher une valeur nulle devrait être votre seconde nature

  • Votre API sera utilisée par d'autres personnes et leurs attentes seront probablement différentes des vôtres.

  • Les tests unitaires approfondis mettent normalement en évidence la nécessité d'une vérification de la référence nulle

  • La vérification des valeurs NULL n'implique pas d'instructions conditionnelles. Si vous craignez que la vérification null rend votre code moins lisible, vous pouvez envisager des contrats de code .NET.

  • Pensez à installer ReSharper (ou similaire) pour rechercher dans votre code les vérifications de références nulles manquantes.

CodeART
la source
L'utilisation de contrats de code pour les conditions préalables est simplement une instruction conditionnelle glorifiée.
un CVn
Oui, je suis d'accord, mais je pense aussi que le code a l'air plus net quand on utilise des contrats de code, c'est-à-dire que sa lisibilité est améliorée.
CodeART
4

Je considérerais la question du point de vue de la sémantique, c'est-à-dire me demander ce que représente un pointeur NULL ou un argument de référence nul.

Si une fonction ou une méthode foo () a un argument x de type T, x peut être obligatoire ou facultatif .

En C ++, vous pouvez passer un argument obligatoire en tant que

  • Une valeur, par exemple void foo (T x)
  • Une référence, par exemple void foo (T & x).

Dans les deux cas, vous n'avez pas le problème de vérifier un pointeur NULL. Ainsi, dans de nombreux cas, vous n'avez pas besoin d' une vérification de pointeur NULL à tous pour les arguments obligatoires.

Si vous transmettez un argument facultatif , vous pouvez utiliser un pointeur et utiliser la valeur NULL pour indiquer qu'aucune valeur n'a été fournie:

void foo(T* x)

Une alternative consiste à utiliser un pointeur intelligent comme

void foo(shared_ptr<T> x)

Dans ce cas, vous voudrez probablement vérifier le pointeur dans le code, car la méthode foo () fait la différence si x contient une valeur ou aucune valeur.

Le troisième et dernier cas est que vous utilisez un pointeur pour un argument obligatoire . Dans ce cas, appeler foo (x) avec x == NULL est une erreur et vous devez décider comment le gérer (de la même manière qu'avec un index hors limite ou toute entrée non valide):

  1. Pas de vérification: s'il y a un bogue, laissez-le tomber en panne et espérez que ce bogue apparaisse suffisamment tôt (pendant les tests). Si ce n'est pas le cas (si vous échouez suffisamment tôt), espérez que cela n'apparaîtra pas dans un système de production, car l'expérience de l'utilisateur sera de voir le blocage de l'application.
  2. Vérifiez tous les arguments au début de la méthode et signalez les arguments NULL invalides, par exemple, émettez une exception avec foo () et laissez une autre méthode la gérer. La meilleure chose à faire est probablement de montrer à l'utilisateur une fenêtre de dialogue indiquant que l'application a rencontré une erreur interne et va être fermée.

Mis à part une façon plus agréable d’échouer (en donnant à l’utilisateur plus d’informations), la deuxième approche présente un avantage: il est plus probable que le bogue soit trouvé: le programme échouera chaque fois que la méthode est appelée avec les arguments incorrects approche 1, le bogue n'apparaît que si une instruction de déréférencement du pointeur est exécutée (par exemple, si la branche droite d'une instruction if est entrée). Donc, l'approche 2 offre une forme plus forte d '"échec précoce".

En Java, vous avez moins de choix, car tous les objets sont transmis à l'aide de références qui peuvent toujours être nulles. De nouveau, si la valeur null représente la valeur NONE d'un argument facultatif, la vérification de cette valeur fera (probablement) partie de la logique d'implémentation.

Si la valeur null est une entrée non valide, vous avez une erreur et j'appliquerais les mêmes considérations que pour le cas des pointeurs C ++. Comme en Java vous pouvez intercepter des exceptions de pointeur nul, l'approche 1 ci-dessus est équivalente à l'approche 2 si la méthode foo () déréférence tous les arguments d'entrée dans tous les chemins d'exécution (ce qui se produit probablement très souvent pour les petites méthodes).

Résumant

  • En C ++ et en Java, vérifier si les arguments optionnels sont nuls fait partie de la logique du programme.
  • En C ++, je vérifiais toujours les arguments obligatoires pour m'assurer qu'une exception appropriée était générée afin que le programme puisse la gérer de manière robuste.
  • En Java (ou C #), je vérifierais les arguments obligatoires s’il existe des chemins d’exécution qui ne seront pas générés afin d’avoir une forme plus forte d’échec précoce.

MODIFIER

Merci à Stilgar pour plus de détails.

Dans votre cas, il semble que vous ayez null comme résultat d'une méthode. Encore une fois, je pense que vous devriez d’abord clarifier (et corriger) la sémantique de vos méthodes avant de pouvoir prendre une décision.

Donc, vous avez la méthode m1 () appelant la méthode m2 () et m2 () renvoie une référence (en Java) ou un pointeur (en C ++) à un objet.

Quelle est la sémantique de m2 ()? M2 () doit-il toujours renvoyer un résultat non nul? Si tel est le cas, un résultat NULL est une erreur interne (en m2 ()). Si vous vérifiez la valeur de retour dans m1 (), la gestion des erreurs peut être plus robuste. Si vous ne le faites pas, vous aurez probablement une exception, tôt ou tard. Peut être pas. J'espère que l'exception est levée pendant les tests et non après le déploiement. Question : si m2 () ne doit jamais renvoyer null, pourquoi renvoie-t-il null? Peut-être devrait-il lancer une exception à la place? m2 () est probablement buggy.

L’alternative est que le retour de null fait partie de la sémantique de la méthode m2 (), c’est-à-dire qu’il a un résultat optionnel, qui devrait être documenté dans la documentation Javadoc de la méthode. Dans ce cas, il est correct d'avoir une valeur nulle. La méthode m1 () devrait la vérifier comme toute autre valeur: il n'y a pas d'erreur ici, mais la vérification probable que le résultat est null fait simplement partie de la logique du programme.

Giorgio
la source
Dans le cas présent, l'argument n'était pas nul. Nous avons lancé un appel de base de données pour quelque chose qui devait être présent, mais en pratique, il n'était pas présent. La question est de savoir si nous devrions vérifier que chaque objet existe ou si nous nous attendons à ce qu'il soit manquant.
Stilgar
Le résultat renvoyé par une méthode était donc une référence à un objet et null était la valeur utilisée pour représenter le résultat: NOT FOUND. Est-ce que je comprends bien?
Giorgio
J'ai mis à jour la question avec des détails supplémentaires.
Stilgar
3

Mon approche générale est de ne jamais rechercher une condition d'erreur que vous ne savez pas comment gérer . La question à laquelle il faut répondre dans chaque cas particulier devient alors: pouvez-vous faire quelque chose de raisonnable en présence de la nullvaleur inattendue ?

Si vous pouvez continuer en toute sécurité en substituant une autre valeur (une collection vide, par exemple, ou une valeur ou instance par défaut), alors ne vous gênez pas. @ L'exemple de Shahbaz, tiré de World of Warcraft, consistant à remplacer une image par une autre tombe dans cette catégorie; l'image elle-même n'est probablement qu'une décoration et n'a aucun impact fonctionnel . (Dans une version de débogage, j'utiliserais une couleur criarde pour attirer l'attention sur le fait qu'une substitution s'est produite, mais cela dépend grandement de la nature de l'application.) Consignez les détails de l'erreur, cependant, afin que vous sachiez qu'elle se produit; sinon, vous cachez une erreur que vous souhaitez probablement connaître. Le cacher à l'utilisateur est une chose (encore une fois, en supposant que le traitement puisse continuer en toute sécurité); le cacher au développeur en est une autre.

Si vous ne pouvez pas continuer en toute sécurité en présence d'une valeur NULL, échouez avec une erreur sensible; en général, je préfère échouer dès que possible lorsqu'il est évident que l'opération ne peut pas aboutir, ce qui implique de vérifier les conditions préalables. Il y a des moments où vous ne voulez pas échouer immédiatement, mais différer l'échec le plus longtemps possible; cette considération se pose par exemple dans les applications liées à la cryptographie, où les attaques par canal latéral pourraient sinon divulguer des informations que vous ne voulez pas connaître. À la fin, laissez l’erreur remonter jusqu’à un gestionnaire d’erreurs général, qui enregistre à son tour les détails pertinents et affiche un beau message d’erreur (même s'il peut être gentil) à l’utilisateur.

Il convient également de noter que la réponse appropriée peut très bien différer entre les versions test / QA / débogage et les versions production / édition. Dans les versions de débogage, j'accepterais l'échec précoce et difficile avec des messages d'erreur très détaillés, afin de rendre tout à fait évident qu'il y a un problème et de permettre à un post-mortem de déterminer ce qu'il faut faire pour le résoudre. Les versions de production, confrontées à des erreurs récupérables en toute sécurité , devraient probablement favoriser la récupération.

un CVn
la source
Bien sûr, il y a un gestionnaire d'erreur général en haut de la chaîne. Le problème avec les journaux est qu’il n’ya peut-être personne pour les vérifier pendant très longtemps.
Stilgar
@Stilgar, s'il n'y a personne pour vérifier les journaux, alors ce n'est pas un problème avec l'existence ou l'absence de contrôles nuls.
un CVn
2
Il ya un problème. Les utilisateurs finaux peuvent ne pas remarquer que le système ne fonctionne pas correctement pendant longtemps s'il existe des contrôles nuls qui masquent simplement l'erreur et l'écrivent dans les journaux.
Stilgar
@Stilgar, cela dépend entièrement de la nature de l'erreur. Comme je l’ai dit dans le post, ce n’est que si vous pouvez continuer en toute sécurité que l’inattendu null doit être remplacé / ignoré. L'utilisation d'une image plutôt que d'une autre dans une version validée (dans les versions debug, échouez le plus tôt possible et le plus difficilement possible pour que le problème devienne évident) est une bête très différente de celle qui renvoie des résultats non valides pour un calcul, par exemple. (Réponse modifiée pour inclure cela.)
un CVn
1
J'irais avec un échec rapide et précoce, même en production, à moins que vous ne puissiez vous connecter et signaler l'erreur (sans trop vous fier à des utilisateurs avertis). Cacher vos bogues déployés aux utilisateurs est une chose - les cacher à vous-même est dangereux. Permettre également à l'utilisateur de vivre avec des bugs subtils peut éroder sa confiance en votre application. Parfois, il est préférable de mordre la balle et leur faire savoir qu'il y avait un bogue et que vous l'avez corrigé rapidement.
Merlyn Morgan-Graham
2

Bien sûr, ce NULLn'est pas prévu , mais il y a toujours des bugs!

Je crois que vous devriez vérifier NULLsi vous ne vous y attendez pas ou non. Laissez-moi vous donner des exemples (bien qu'ils ne soient pas en Java).

  • Dans World of Warcraft, en raison d'un bug, l'image de l'un des développeurs apparaissait sur un cube pour de nombreux objets. Imaginez si le moteur graphique ne s'attendait pas à desNULL pointeurs, il y aurait eu un crash, alors que cela deviendrait une expérience peu fatale (même drôle) pour l'utilisateur. Le moins est que vous n’auriez pas perdu de façon inattendue votre connexion ou l’un de vos points de sauvegarde.

    Voici un exemple où la vérification de la valeur NULL a empêché un blocage et le traitement silencieux de l’affaire.

  • Imaginez un programme de caissier de banque. L'interface effectue certaines vérifications et envoie les données d'entrée à une partie sous-jacente pour effectuer les transferts d'argent. Si la partie principale s'attend à ne pas recevoir NULL, alors un bogue dans l'interface peut causer un problème dans la transaction, éventuellement de l'argent perdu au milieu (ou pire, dupliqué).

    Par "un problème", j'entends un crash (comme dans un crash (disons que le programme est écrit en C ++), pas une exception de pointeur nul). Dans ce cas, la transaction doit être annulée si NULL est rencontré (ce qui ne serait bien entendu pas possible si vous ne compariez pas les variables à NULL!)

Je suis sûr que vous avez l'idée.

Contrôler la validité des arguments de vos fonctions n’encombre pas votre code, c’est deux vérifications au sommet de votre fonction (non réparties au milieu) et il augmente considérablement la robustesse.

Si vous devez choisir entre "le programme indique à l'utilisateur qu'il a échoué et qu'il s'arrête ou revient à un état cohérent, créant éventuellement un journal des erreurs" et "Violation d'accès", ne choisissez-vous pas le premier? Cela signifie que chaque fonction doit être préparée pour être appelée par un code de bug plutôt que de s'attendre à ce que tout soit correct, tout simplement parce que les bogues existent toujours.

Shahbaz
la source
Votre deuxième exemple réfute votre argument. Dans une transaction bancaire, si quelque chose ne va pas, la transaction doit être annulée. C'est précisément lorsque vous couvrez l'erreur que de l'argent peut être perdu ou dupliqué. Rechercher null serait comme "Le client envoie de l'argent nul ... eh bien, je vais juste envoyer 10 $ (l'équivalent de l'image du développeur)"
Stilgar
@Stilgar, le contraire! La vérification de la valeur NULL serait annulée avec un message. Ne pas vérifier NULL serait un crash . Désormais, le crash au début de la transaction n'est peut-être pas grave, mais au centre, il peut être catastrophique. (Je n'ai pas dit que le virement bancaire devrait traiter l'erreur comme le jeu! Juste le fait qu'il devrait le gérer)
Shahbaz
2
Le point essentiel d’une "transaction" est qu’elle ne peut être complétée à moitié :) Si une erreur se produit, elle est annulée.
Stilgar
C'est un conseil terrible pour tout ce qui nécessite une sémantique transactionnelle. Toutes vos transactions doivent être atomiques et idempotentes, sinon toute erreur de votre part garantirait des ressources perdues ou dupliquées (argent). Si vous devez gérer des opérations non atomiques ou non-idempotentes, vous devez les envelopper très soigneusement dans des couches garantissant un comportement atomique et idempotent (même si vous devez écrire ces couches vous-même). Pensez toujours: que se passera-t-il si un météore frappait le serveur Web au moment où cette transaction est en cours?
Merlyn Morgan-Graham
1
@ MerlynMorgan-Graham, je l'ai fait. J'espère que ça dissipe la confusion.
Shahbaz
2

Comme indiqué dans les autres réponses Si vous ne vous attendez pas NULL, précisez-le en jetant ArgumentNullException. À mon avis, lorsque vous déboguez le projet, cela vous aide à découvrir plus tôt les erreurs dans la logique de votre programme.

Donc, vous allez libérer votre logiciel, si vous restaurez ces NullRefrencesvérifications, vous ne manquerez rien de la logique de votre logiciel, cela fera double-ment la certitude qu'un bug grave ne surviendra pas.

Un autre point est que si la NULLvérification concerne les paramètres d'une fonction, vous pouvez alors décider si la fonction est le bon endroit pour le faire ou non. sinon, trouve toutes les références à la fonction, puis avant de passer un paramètre à la fonction, examinez les scénarios probables pouvant conduire à une référence nulle.

Ahmad
la source
1

Chaque nullsystème de votre système est une bombe à retardement à découvrir. Recherchez nullles limites où votre code interagit avec du code que vous ne contrôlez pas et interdisez-le nulldans votre propre code. Cela vous évite le problème sans faire naïvement confiance à un code étranger. Utilisez des exceptions ou une sorte de Maybe/ Nothingtype à la place.

Doval
la source
0

Bien qu'une exception de pointeur nul soit éventuellement levée, elle le sera souvent loin du point où vous avez obtenu le pointeur nul. Faites-vous une faveur et raccourcissez le temps nécessaire à la résolution du bogue en enregistrant au moins le fait que vous générez un pointeur nul dès que possible.

Même si vous ne savez pas quoi faire maintenant, la journalisation de l'événement vous fera gagner du temps. Et peut-être que le gars qui recevra le billet pourra utiliser le temps gagné pour trouver la bonne réponse.

Jon Strayer
la source
-1

Comme Fail early, fail fastindiqué par @DeadMG (@empi) n'est pas possible, car l'application Web doit bien fonctionner sous les bogues, vous devez appliquer la règle.

pas d'exception attraper sans se connecter

en tant que développeurs, vous êtes donc au courant des problèmes potentiels en inspectant les journaux.

Si vous utilisez une structure de journalisation telle que log4net ou log4j, vous pouvez configurer une sortie de journalisation spéciale supplémentaire (appelée appender) qui vous enverra des erreurs et des erreurs fatales. afin que vous restiez informé .

[mise à jour]

si l'utilisateur final de la page ne doit pas être conscient de l'erreur, vous pouvez configurer un éditeur qui vous enverra des erreurs et des erreurs fatales qui vous informeront.

s'il est correct que l'utilisateur final de la page voie des messages d'erreur cryptiques, vous pouvez configurer un générateur d'appels qui place le journal des erreurs du traitement de la page à la fin de la page.

Peu importe la manière dont vous utilisez la journalisation si vous avalez l'exception, le programme continue avec des données qui ont du sens et la déglutition n'est plus silencieuse.

k3b
la source
1
La question est ce qui arrive à la page?
Stilgar
Comment savons-nous que les données ont du sens et que le système est dans un état cohérent? Après tout, l’erreur était inattendue, nous ne savons donc pas quel en a été l’impact.
Stilgar
"Depuis Fail early, échouer vite comme indiqué par @DeadMG (@empi) n'est pas possible car l'application Web doit bien fonctionner sous les bogues, vous devez appliquer la règle": On pourrait toujours intercepter toutes les exceptions (catch (Throwable t)) et rediriger vers une page d'erreur. Cela ne serait-il pas possible?
Giorgio
-1

Il y a déjà de bonnes réponses à cette question, peut-être y at-il un ajout: je préfère les assertions dans mon code. Si votre code ne peut fonctionner qu'avec des objets valides, mais non avec null, vous devez affirmer la valeur null.

Les assertions dans ce genre de situation laisseraient n'importe quel test unitaire et / ou d'intégration échouer avec le message exact. Vous pourrez ainsi résoudre le problème assez rapidement avant la production. Si une telle erreur se glisse entre les tests et passe en production (où les assertions doivent être désactivées), vous recevrez un NpEx, et un bogue grave, mais il vaut mieux qu'un bogue mineur qui est beaucoup plus flou et apparaît quelque part dans le fichier. code, et serait plus coûteux à réparer.

De plus, si quelqu'un doit travailler avec vos assertions de code l'informe de vos hypothèses de conception / écriture de votre code (dans ce cas: Hey mec, cet objet doit être présenté!), Ce qui facilite sa maintenance.

Obtenir
la source
Pourriez-vous s'il vous plaît écrire quelque chose pour voter à la baisse? J'apprécierais une discussion. Thanx
GeT
Je n'ai pas voté à la baisse et je suis d'accord avec votre idée générale. La langue a besoin de travail cependant. Vos assertions définissent le contrat de votre API et vous échouez rapidement / rapidement si ces contrats sont violés. Faire cela à la peau de votre application permettra aux utilisateurs de votre code de savoir qu'ils ont mal agi dans leur code. S'ils voient une trace de pile dans les renfoncements de votre code, ils penseront probablement que votre code est défectueux - et vous pouvez le penser aussi jusqu'à ce que vous obteniez une repro solide et le déboguiez.
Merlyn Morgan-Graham
-1

Je vérifie normalement les valeurs NULL, mais je «gère» les valeurs NULL inattendues en lançant une exception qui donne un peu plus de détails sur l'emplacement exact de la valeur NULL.

Edit: Si vous obtenez une valeur nulle alors que vous ne vous attendez pas à quelque chose, le programme devrait disparaître.

Loren Pechtel
la source
-1

Cela dépend de la mise en œuvre linguistique des exceptions. Les exceptions vous permettent de prendre des mesures pour éviter d'endommager les données ou pour libérer des ressources proprement dans l'éventualité où votre système entrerait dans un état ou une condition imprévu. Cela diffère de la gestion des erreurs qui sont des conditions que vous pouvez anticiper. Ma philosophie est que vous devriez avoir le moins possible de gestion des exceptions. C'est du bruit. Votre méthode peut-elle faire quelque chose de raisonnable en gérant l'exception? Peut-il récupérer? Peut-il réparer ou prévenir d'autres dommages? Si vous attrapez simplement l'exception et revenez à la méthode ou si vous échouez d'une autre manière inoffensive, il était alors inutile de capturer l'exception. Vous n'auriez ajouté que du bruit et de la complexité à votre méthode, et vous pourriez cacher la source d'une faille dans votre conception. Dans ce cas, je laisserais la faute bouillonner et être attrapée par une boucle externe. Si vous pouvez faire quelque chose comme réparer un objet ou le marquer comme corrompu ou libérer une ressource critique telle qu'un fichier de verrouillage ou en fermant proprement un socket, il s'agit d'un bon usage des exceptions. Si vous vous attendez à ce que la valeur NULL apparaisse fréquemment comme un cas d'extrémité valide, vous devez le gérer dans le flux logique normal avec des instructions if-the-else ou switch-case ou quoi que ce soit, mais pas avec un traitement d'exception. Par exemple, un champ désactivé dans un formulaire peut être défini sur NULL, ce qui est différent d'une chaîne vide représentant un champ laissé vide. C'est un domaine dans lequel vous devez faire preuve de jugement et de bon sens. Je n'ai jamais entendu parler de bonnes règles empiriques concernant la manière de traiter ce problème dans toutes les situations. Si vous pouvez faire quelque chose comme réparer un objet ou le marquer comme corrompu ou libérer une ressource critique telle qu'un fichier de verrouillage ou en fermant proprement un socket, il s'agit d'un bon usage des exceptions. Si vous vous attendez à ce que la valeur NULL apparaisse fréquemment comme un cas d'extrémité valide, vous devez le gérer dans le flux logique normal avec des instructions if-the-else ou switch-case ou quoi que ce soit, mais pas avec un traitement d'exception. Par exemple, un champ désactivé dans un formulaire peut être défini sur NULL, ce qui est différent d'une chaîne vide représentant un champ laissé vide. C'est un domaine dans lequel vous devez faire preuve de jugement et de bon sens. Je n'ai jamais entendu parler de bonnes règles empiriques concernant la manière de traiter ce problème dans toutes les situations. Si vous pouvez faire quelque chose comme réparer un objet ou le marquer comme corrompu ou libérer une ressource critique telle qu'un fichier de verrouillage ou en fermant proprement un socket, il s'agit d'un bon usage des exceptions. Si vous vous attendez à ce que la valeur NULL apparaisse fréquemment comme un cas d'extrémité valide, vous devez le gérer dans le flux logique normal avec des instructions if-the-else ou switch-case ou quoi que ce soit, mais pas avec un traitement d'exception. Par exemple, un champ désactivé dans un formulaire peut être défini sur NULL, ce qui est différent d'une chaîne vide représentant un champ laissé vide. C'est un domaine dans lequel vous devez faire preuve de jugement et de bon sens. Je n'ai jamais entendu parler de bonnes règles empiriques concernant la manière de traiter ce problème dans toutes les situations.

Java échoue dans sa mise en œuvre de gestion des exceptions avec des "exceptions vérifiées", dans lesquelles toute exception pouvant être déclenchée par des objets utilisés dans une méthode doit être interceptée ou déclarée dans la clause "throws" de la méthode. Le est l'opposé de C ++ et de Python, où vous pouvez choisir de gérer les exceptions comme bon vous semble. En Java, si vous modifiez le corps d'une méthode pour inclure un appel susceptible de déclencher une exception, vous devez choisir d'ajouter une gestion explicite à une exception qui ne vous intéresse pas, ce qui ajoute du bruit et de la complexité à votre code. Ajoutez cette exception à la clause "Throws" de la méthode que vous modifiez, ce qui non seulement ajoute du bruit et de l'encombrement, mais modifie également la signature de votre méthode. Vous devez ensuite utiliser un code de modification chaque fois que votre méthode est utilisée pour gérer la nouvelle exception que votre méthode peut maintenant générer ou ajouter une exception à la clause "Throws" de ces méthodes, ce qui déclenche un effet de domino des modifications de code. La "Catch or Specify Requirement" est l'un des aspects les plus gênants de Java. L'exception exception pour RuntimeExceptions et la rationalisation Java officielle pour cette erreur de conception est boiteuse.

Ce dernier paragraphe avait peu à voir avec la réponse à votre question. Je déteste juste Java.

- Noé

Noah Spurrier
la source
Cet article est plutôt difficile à lire (mur de texte). Pourriez - vous l' esprit modifier ing dans une meilleure forme?
moucher