web-dev-qa-db-fra.com

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

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 futur 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?

21
Shaun F

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, donc je propose 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 est pas une entrée raisonnable.

45
user281377

N'utilisez pas null, utilisez facultatif

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

Il est impossible de dire que cela pourrait être null et ce qui ne pourrait pas l'être.

Java 8 introduit un modèle bien meilleur: 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 renvoyer 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 consulter com.google.common.base.Optional dans Google Guava.

Une bonne explication de l'équipe 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 valeur null.

D'un autre côté, si null peut être utilisé et signifie quelque chose, assurez-vous de l'utiliser.


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

21
Paul Draper

Il y a plusieurs façons de gérer cela, poivrer votre code avec 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 pour 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. null est-il une erreur ou une condition attendue?.

Qu'est-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 String n'est pas la même chose qu'un String vide, un null int n'est pas la même chose que ZERO, en théorie. En pratique "ça dépend". String vide peut faire une bonne implémentation Null Object Pour la classe String, pour Integer cela dépend de la logique métier.

Les alternatives sont:

  1. Le modèle Null Object . Créez une instance de votre objet qui représente l'état null et initialisez toutes les références à ce type avec une référence à l'implémentation Null. 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 null et devraient être null comme état valide.

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

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

  4. Utilisez un outil d'application des contrats tel que Contrats pour 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 pour être null et 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 pour remplacer NullPointerException par quelque chose de plus informatif, ce qui est toujours une amélioration.

À la fin

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

8
user7519

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.

En outre, 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 quoi que ce soit 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

8
Constantin

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

Détails

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

Règle n ° 1:

Le javadoc d'une méthode qui autorise un paramètre null ou renvoie une valeur null doit clairement documenter cela et expliquer ce que le null signifie.

Règle n ° 2:

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

Etant donné une spécification claire du "contrat" ​​d'une méthode vis-à-vis de nulls, c'est une erreur de programmation pour passer ou retourner un null où 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 un null qui ne devrait pas être là, elle ne devrait pas tenter de résoudre le problème en le 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ù viennent les valeurs null. Une façon de faciliter le diagnostic consiste à écrire votre code afin que le null soit détecté dès que 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 détectant probablement des erreurs de programmation. Et (règle 4) trop de vérification de mauvais paramètres peut avoir un impact sur les performances.)

4
Stephen C

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.

3
Woot4Moo

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 en raison 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

2
Newtopian

Je proposerais d'utiliser le modèle Option/Some/None à partir des langages fonctionnels. Je ne suis pas un spécialiste de 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 Java monde.

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 faites 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 pouvez facilement trouver les endroits de manipulation incorrecte dans le code.

1
Hedin
  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.

Donc, je suppose 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.

0
J-Boss

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 avec la plupart des choses dans la vie :)

0
jonezy

Les bibliothèques Apache Commons lang fournissent un moyen de gérer les valeurs nulles

la méthode defaultIfNull dans la classe ObjectUtils vous permet de renvoyer une valeur par défaut si l'objet passé est nul

0
Mahmoud Hossam