La meilleure façon de gérer les null en Java? [fermé]


21

J'ai du code qui échoue à cause de NullPointerException. Une méthode est appelée sur l'objet là où il n'existe pas.

Cependant, cela m'a amené à réfléchir à la meilleure façon de résoudre ce problème. Dois-je toujours coder de manière défensive pour les valeurs nulles afin que je code à l'épreuve du temps pour les exceptions de pointeur nul, ou dois-je corriger la cause de la valeur null afin qu'elle ne se produise pas en aval.

Quelles sont vos pensées?


5
Pourquoi ne "fixeriez-vous pas la cause du null"? Pouvez-vous expliquer pourquoi ce n'est pas le seul choix judicieux? De toute évidence, quelque chose doit vous éloigner de l'évidence. Qu'Est-ce que c'est? Pourquoi fixer la cause racine n'est-il pas votre premier choix?
S.Lott

Bien corriger la "cause première" du problème peut aider à court terme, mais si le code ne vérifie toujours pas les valeurs nulles de manière défensive et est réutilisé par un autre processus qui peut introduire une valeur nulle, je devrais le corriger à nouveau.
Shaun F

2
@Shaun F: Si le code est cassé, il est cassé. Je ne comprends pas comment il est possible que le code produise des null inattendus et ne soit pas corrigé. De toute évidence, il se passe des trucs de "gestion stupide" ou "politique". Ou quelque chose qui permet à un code erroné d'être en quelque sorte acceptable. Pouvez-vous expliquer comment le code erroné ne se corrige pas? Quel est le contexte politique qui vous amène à poser cette question?
S.Lott

1
"vulnérable à la création ultérieure de données contenant des valeurs nulles" Qu'est-ce que cela signifie même? Si «je peux réparer la routine de création de données», il n'y a plus de vulnérabilité. Je ne peux pas comprendre ce que peut signifier cette "création de données ultérieure qui contenait des valeurs nulles"? Logiciel buggy?
S.Lott

1
@ S.Lott, encapsulation et responsabilité unique. Tout votre code ne "sait" pas ce que fait tout votre autre code. Si une classe accepte les Froobinators, elle fait le moins d'hypothèses possible sur qui a créé le Froobinator et comment. Il peut provenir d'un code "externe", ou simplement provenir d'une autre partie de la base de code. Je ne dis pas que vous ne devriez pas corriger le code buggy; mais recherchez "programmation défensive" et vous trouverez à quoi fait référence l'OP.
Paul Draper du

Réponses:


45

Si null est un paramètre d'entrée raisonnable pour votre méthode, corrigez la méthode. Sinon, corrigez l'appelant. "Raisonnable" est un terme flexible, je propose donc le test suivant: Comment la méthode doit-elle suspendre une entrée nulle? Si vous trouvez plus d'une réponse possible, alors null n'est pas une entrée raisonnable.


1
C'est vraiment aussi simple que ça.
biziclop

3
La goyave a de très belles méthodes d'aide qui rendent la vérification nulle aussi simple que Precondition.checkNotNull(...). Voir stackoverflow.com/questions/3022319/…

Ma règle est de tout initialiser si possible par défaut et de se soucier des exceptions nulles par la suite.
davidk01

Thorbjørn: Il remplace donc une NullPointerException accidentelle par une NullPointerException intentionnelle? Dans certains cas, la vérification précoce peut être utile, voire nécessaire; mais je crains beaucoup de contrôles nuls insensés qui ajoutent peu de valeur et agrandissent simplement le programme.
user281377

6
Si null n'est pas un paramètre d'entrée raisonnable pour votre méthode mais que les appels de la méthode sont susceptibles de passer accidentellement null (en particulier si la méthode est publique), vous pouvez également souhaiter que votre méthode lève un IllegalArgumentExceptionlorsque null est donné. Cela signale aux appelants de la méthode que le bogue est dans leur code (plutôt que dans la méthode elle-même).
Brian

20

N'utilisez pas null, utilisez facultatif

Comme vous l'avez souligné, l'un des plus gros problèmes avec nullJava est qu'il peut être utilisé partout , ou du moins pour tous les types de référence.

Il est impossible de dire ce qui pourrait être nullet ce qui ne pourrait pas l'être.

Java 8 contient un bien meilleur motif: Optional.

Et un exemple d'Oracle:

String version = "UNKNOWN";
if(computer != null) {
  Soundcard soundcard = computer.getSoundcard();
  if(soundcard != null) {
    USB usb = soundcard.getUSB();
    if(usb != null) {
      version = usb.getVersion();
    }
  }
}

Si chacune d'elles peut ou non retourner une valeur réussie, vous pouvez changer les API en Optionals:

String name = computer.flatMap(Computer::getSoundcard)
    .flatMap(Soundcard::getUSB)
    .map(USB::getVersion)
    .orElse("UNKNOWN");

En encodant explicitement le caractère facultatif du type, vos interfaces seront bien meilleures et votre code sera plus propre.

Si vous n'utilisez pas Java 8, vous pouvez regarder com.google.common.base.Optionaldans Google Guava.

Une bonne explication par l'équipe de Guava: https://github.com/google/guava/wiki/UsingAndAvoidingNullExplained

Une explication plus générale des inconvénients à null, avec des exemples de plusieurs langues: https://www.lucidchart.com/techblog/2015/08/31/the-worst-mistake-of-computer-science/


@Nonnull, @Nullable

Java 8 ajoute ces annotations pour aider les outils de vérification de code comme les IDE à détecter les problèmes. Leur efficacité est assez limitée.


Vérifiez quand cela a du sens

N'écrivez pas 50% de votre code en vérifiant null, en particulier s'il n'y a rien de sensé que votre code puisse faire avec une nullvaleur.

D'un autre côté, s'il nullpourrait être utilisé et signifier quelque chose, assurez-vous de l'utiliser.


En fin de compte, vous ne pouvez évidemment pas supprimer nullde Java. Je recommande fortement de remplacer l' Optionalabstraction autant que possible et de vérifier nullces autres fois que vous pouvez faire quelque chose de raisonnable à ce sujet.


2
Normalement, les réponses aux questions de quatre ans finissent par être supprimées en raison de leur faible qualité. Bon travail pour expliquer comment Java 8 (qui n'existait pas à l'époque) peut résoudre le problème.

mais sans rapport avec la question d'origine bien sûr. Et que dire que l'argument est en effet optionnel?
jwenting

5
@jwenting C'est tout à fait pertinent pour la question d'origine. La réponse à "Quelle est la meilleure façon de enfoncer un clou avec un tournevis" est "Utilisez un marteau au lieu d'un tournevis".
Daenyth

@jwenting, je pense que j'ai couvert cela, mais pour plus de clarté: si l'argument n'est pas facultatif, je ne vérifierais généralement pas null. Vous aurez 100 lignes de contrôle nul et 40 lignes de substance. Recherchez null sur les interfaces les plus publiques, ou lorsque null est réellement logique (c'est-à-dire que l'argument est facultatif).
Paul Draper

4
@ J-Boss, comment, en Java, n'auriez-vous pas un décoché NullPointerException? Un NullPointerExceptionpeut se produire littéralement chaque fois que vous appelez une méthode d'instance en Java. Vous auriez throws NullPointerExceptiondans presque toutes les méthodes jamais.
Paul Draper

8

Il y a plusieurs façons de gérer cela, poivrer votre code if (obj != null) {}n'est pas idéal, c'est désordonné, cela ajoute du bruit lors de la lecture du code plus tard pendant le cycle de maintenance et est sujet aux erreurs, car il est facile d'oublier de faire cet emballage de plaque de chaudière.

Cela dépend si vous voulez que le code continue à s'exécuter en silence ou échoue. Est-ce nullune erreur ou une condition attendue.

Ce qui est nul

Dans chaque définition et chaque cas, Null représente le manque absolu de données. Les valeurs nulles dans les bases de données représentent l'absence de valeur pour cette colonne. un null Stringn'est pas la même chose qu'un vide String, un null intn'est pas la même chose que ZERO, en théorie. En pratique "ça dépend". Vide Stringpeut faire une bonne Null Objectimplémentation pour la classe String, car Integercela dépend de la logique métier.

Les alternatives sont:

  1. Le Null Objectmotif. Créez une instance de votre objet qui représente l' nullétat et initialisez toutes les références à ce type avec une référence à l' Nullimplémentation. Ceci est utile pour les objets de type valeur simple qui n'ont pas beaucoup de références à d'autres objets qui pourraient également être nullet devraient être nullcomme un état valide.

  2. Utilisez des outils orientés aspect pour tisser des méthodes avec un Null Checkeraspect qui empêche les paramètres d'être nuls. C'est pour les cas où nullest une erreur.

  3. Utilisez assert()pas beaucoup mieux que le if (obj != null){}mais moins de bruit.

  4. Utilisez un outil d'application des contrats tel que Contracts For Java . Même cas d'utilisation que quelque chose comme AspectJ mais plus récent et utilise des annotations au lieu de fichiers de configuration externes. Le meilleur des deux œuvres d'Aspects et Asserts.

1 est la solution idéale lorsque les données entrantes sont connues nullet doivent être remplacées par une valeur par défaut afin que les consommateurs en amont n'aient pas à gérer tout le code passe-partout à vérification nulle. La vérification par rapport aux valeurs par défaut connues sera également plus expressive.

2, 3 et 4 ne sont que des générateurs d'exceptions alternatifs pratiques à remplacer NullPointerExceptionpar quelque chose de plus informatif, ce qui est toujours une amélioration.

À la fin

nullen Java est presque dans tous les cas une erreur logique. Vous devez toujours vous efforcer d'éliminer la cause profonde de NullPointerExceptions. Vous devez vous efforcer de ne pas utiliser les nullconditions comme logique métier. if (x == null) { i = someDefault; }effectuez simplement l'assignation initiale à cette instance d'objet par défaut.


Si possible, le modèle d'objet nul est le chemin vers, c'est la façon la plus gracieuse de gérer un cas nul. Il est rare que vous vouliez un null pour faire exploser des trucs en production!
Richard Miskin

@Richard: à moins que cela ne nullsoit inattendu, c'est une vraie erreur, alors tout devrait s'arrêter complètement.

Null dans les bases de données et dans le code de programmation est traité différemment sous un aspect important: en programmation null == null(même en PHP), mais dans les bases de données, null != nullcar dans les bases de données, il représente une valeur inconnue plutôt que "rien". Deux inconnues ne sont pas nécessairement égales, tandis que deux riens sont égaux.
Simon Forsberg

8

L'ajout de contrôles nuls peut rendre le test problématique. Voir cette grande conférence ...

Consultez la conférence Google Tech: "Les pourparlers sur le code propre - ne cherchez rien!" il en parle vers la minute 24

http://www.youtube.com/watch?v=RlfLCWKxHJ0&list=PL693EFD059797C21E

La programmation paranoïaque consiste à ajouter des contrôles nuls partout. Cela semble être une bonne idée au début, mais du point de vue des tests, cela rend votre test de type de vérification nulle difficile à gérer.

De plus, lorsque vous créez une condition préalable à l'existence d'un objet tel que

class House(Door door){

    .. null check here and throw exception if Door is NULL
    this.door = door
}

cela vous empêche de créer House car vous allez lever une exception. Supposons que vos cas de test créent des objets fictifs pour tester autre chose que Door, eh bien, vous ne pouvez pas le faire car Door est requis

Ceux qui ont souffert de l'enfer de la création factice sont bien conscients de ces types de désagréments.

En résumé, votre suite de tests doit être suffisamment robuste pour tester les portes, les maisons, les toits ou autre sans avoir à être paranoïaque à ce sujet. Sérieusement, à quel point est-il difficile d'ajouter un test de vérification nulle pour des objets spécifiques dans vos tests :)

Vous devriez toujours préférer les applications qui fonctionnent parce que vous avez plusieurs tests qui PROUVENT que cela fonctionne, plutôt que d'espérer que cela fonctionne simplement parce que vous avez tout un tas de vérifications nulles pré-conditionnelles partout


4

tl; dr - il est bon de vérifier les nulls inattendus mais MAUVAIS pour une application pour essayer de les rendre bons.

Détails

De toute évidence, il existe des situations où nullune entrée ou une sortie est valide pour une méthode, et d'autres où elle ne l'est pas.

Règle 1:

Le javadoc d'une méthode qui autorise un nullparamètre ou renvoie une nullvaleur doit clairement documenter cela et expliquer ce que cela nullsignifie.

Règle n ° 2:

Une méthode API ne doit pas être spécifiée comme acceptant ou renvoyant, nullsauf s'il existe une bonne raison de le faire.

Étant donné une spécification claire du «contrat» d'une méthode vis-à-vis des nulls, c'est une erreur de programmation de passer ou de renvoyer un nulloù vous ne devriez pas.

Règle n ° 3:

Une application ne doit pas tenter de "corriger" les erreurs de programmation.

Si une méthode détecte une nullqui ne devrait pas exister, elle ne doit pas tenter de résoudre le problème en la transformant en autre chose. Cela cache simplement le problème au programmeur. Au lieu de cela, cela devrait permettre au NPE de se produire et provoquer une défaillance afin que le programmeur puisse déterminer la cause principale et la corriger. Espérons que l'échec sera remarqué lors des tests. Sinon, cela en dit long sur votre méthodologie de test.

Règle n ° 4:

Dans la mesure du possible, écrivez votre code pour détecter précocement les erreurs de programmation.

Si vous avez des bogues dans votre code qui entraînent de nombreux NPE, la chose la plus difficile peut être de déterminer d'où nullviennent les valeurs. Une façon de faciliter le diagnostic consiste à écrire votre code afin qu'il nullsoit détecté le plus tôt possible. Souvent, vous pouvez le faire en conjonction avec d'autres vérifications; par exemple

public setName(String name) {
    // This also detects `null` as an (intended) side-effect
    if (name.length() == 0) {
        throw new IllegalArgumentException("empty name");
    }
}

(Il y a évidemment des cas où les règles 3 et 4 doivent être tempérées par la réalité. Par exemple (règle 3), certains types d'applications doivent tenter de continuer après avoir détecté probablement des erreurs de programmation. Et (règle 4) trop de vérification de mauvais paramètres peut avoir un impact sur les performances.)


3

Je recommanderais de fixer la méthode comme défensive. Par exemple:

String go(String s){  
    return s.toString();  
}

Devrait être plus dans le sens de ceci:

String go(String s){  
    if(s == null){  
       return "";  
    }     
    return s.toString();  
}

Je me rends compte que c'est absolument trivial, mais si l'invocateur attend un objet, donnez-lui un objet par défaut qui ne fera pas passer un null.


10
Cela a un effet secondaire désagréable dans la mesure où l'appelant (programmeur qui code contre cette API) peut développer l'habitude de passer null en paramètre pour obtenir un comportement "par défaut".
Goran Jovic

2
S'il s'agit de Java, vous ne devriez pas utiliser new String()du tout.
biziclop

1
@biziclop est en fait beaucoup plus important que la plupart ne le pensent.
Woot4Moo

1
À mon avis, il est plus logique de placer une assertion et de lever une exception si l'argument est nul, car c'est clairement l'erreur de l'appelant. Ne «corrigez» pas silencieusement les erreurs des appelants; au lieu de cela, informez-les d'eux.
Andres F.

1
@ Woot4Moo Alors, écrivez votre propre code qui lève alors une exception. L'important est d'informer l'appelant que ses arguments passés sont faux et de lui dire le plus tôt possible. Corriger silencieusement nulls est la pire option possible, pire que de lancer un NPE.
Andres F.

2

Les règles générales suivantes concernant null m'ont beaucoup aidé jusqu'à présent:

  1. Si les données proviennent de l'extérieur de votre contrôle, vérifiez systématiquement les valeurs nulles et agissez de manière appropriée. Cela signifie soit lever une exception qui a du sens pour la fonction (cochée ou décochée, assurez-vous simplement que le nom de l'exception vous indique exactement ce qui se passe.). Mais ne craignez JAMAIS une valeur lâche dans votre système qui peut potentiellement porter des surprises.

  2. Si Null se trouve dans le domaine des valeurs appropriées pour votre modèle de données, traitez-le de manière appropriée.

  3. Lorsque vous retournez des valeurs, essayez de ne pas renvoyer de valeurs nulles autant que possible. Préférez toujours les listes vides, les chaînes vides, les modèles d'objets nuls. Conservez les valeurs nulles comme valeurs renvoyées lorsqu'il s'agit de la meilleure représentation possible des données pour un cas d'utilisation donné.

  4. Probablement le plus important de tous ... Tests, tests et test à nouveau. Lorsque vous testez votre code, ne le testez pas en tant que codeur, testez-le en tant que dominatrice psychopathe nazie et essayez d'imaginer toutes sortes de façons de torturer l'enfer hors de ce code.

Cela tend un peu du côté paranoïaque en ce qui concerne les null conduisant souvent à des façades et à des mandataires qui interfacent les systèmes avec le monde extérieur et des valeurs strictement contrôlées à l'intérieur avec une redondance abondante. Le monde extérieur signifie ici à peu près tout ce que je n'ai pas codé moi-même. Il supporte un coût d'exécution mais jusqu'à présent, j'ai rarement eu à l'optimiser en créant des "sections null safe" de code. Je dois dire cependant que je crée principalement des systèmes de longue durée pour les soins de santé et la dernière chose que je veux, c'est que le sous-système d'interface transportant vos allergies à l'iode au scanner CT se bloque à cause d'un pointeur nul inattendu parce que quelqu'un d'autre dans un autre système n'a jamais réalisé que les noms pouvaient contiennent des apostrophes ou des caractères comme 但 耒耨。

de toute façon .... mes 2 cents


1

Je proposerais d'utiliser le modèle Option / Some / None à partir des langages fonctionnels. Je ne suis pas un spécialiste Java, mais j'utilise intensivement ma propre implémentation de ce modèle dans mon projet C #, et je suis sûr qu'il pourrait être converti en monde Java.

L'idée derrière ce modèle est: s'il s'agit logiquement d'avoir une situation où il y a possibilité d'absence d'une valeur (par exemple lors de la récupération de la base de données par id) vous fournissez un objet de type Option [T], où T est une valeur possible . I cas d'absence d'une valeur objet de classe None [T] retourné, dans le cas si existence de la valeur - objet de Some [T] retourné, contenant la valeur.

Dans ce cas, vous devez gérer la possibilité d'absence de valeur, et si vous effectuez un examen du code, vous pourriez facilement trouver un placec de manipulation incorrecte. Pour vous inspirer de l'implémentation du langage C #, reportez-vous à mon référentiel bitbucket https://bitbucket.org/mikegirkin/optionsomenone

Si vous renvoyez une valeur nulle et qu'elle est logiquement équivalente à une erreur (par exemple, aucun fichier n'existe ou n'a pas pu se connecter), vous devez lever une exception ou utiliser un autre modèle de gestion des erreurs. L'idée derrière cela, encore une fois, vous vous retrouvez avec une solution, lorsque vous devez gérer la situation d'absence de valeur, et que vous pouvez facilement trouver les endroits de mauvaise manipulation dans le code.


0

Eh bien, si l'un des résultats possibles de votre méthode est une valeur nulle, vous devez coder de manière défensive pour cela, mais si une méthode est censée renvoyer une valeur non nulle, mais je ne le corrigerais pas à coup sûr.

Comme d'habitude, cela dépend du cas, comme pour la plupart des choses de la vie :)



0
  1. N'utilisez pas le type facultatif, à moins qu'il ne soit vraiment facultatif, la sortie est souvent mieux gérée comme exception, à moins que vous ne vous attendiez vraiment à ce que null soit une option, et non parce que vous écrivez régulièrement du code bogué.

  2. Le problème n'est pas nul en tant que type utilisé, comme le souligne l'article de Google. Le problème est que les valeurs nulles doivent être vérifiées et gérées, souvent elles peuvent être fermées avec élégance.

  3. Il existe un certain nombre de cas nuls qui représentent des conditions non valides dans des zones en dehors du fonctionnement de routine du programme (entrée utilisateur non valide, problèmes de base de données, panne de réseau, fichiers manquants, données corrompues), c'est à cela que servent les exceptions vérifiées, en les gérant, même si c'est juste pour l'enregistrer.

  4. La gestion des exceptions est autorisée dans la JVM selon différentes priorités et privilèges opérationnels, par opposition à un type, comme Facultatif, qui tient compte de la nature exceptionnelle de leur occurrence, y compris la prise en charge précoce du chargement différé prioritaire du gestionnaire en mémoire, car vous ne le faites pas. besoin de traîner tout le temps.

  5. Vous n'avez pas besoin d'écrire des gestionnaires nuls partout, seulement là où ils sont susceptibles de se produire, partout où vous accédez potentiellement à un service de données non fiable, et puisque la plupart d'entre eux se divisent en quelques modèles généraux qui peuvent facilement être résumés, vous n'avez vraiment besoin que un appel de gestionnaire, sauf dans de rares cas.

Je suppose donc que ma réponse serait de l'envelopper dans une exception vérifiée et de la gérer, ou de corriger le code non fiable s'il est à votre portée.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.