L'utilisation requireNonNull()
en tant que premières déclarations d'une méthode permettent d'identifier immédiatement/rapidement la cause de l'exception.
Le suivi de la pile indique clairement que l'exception a été levée dès l'entrée de la méthode parce que l'appelant n'a pas respecté les exigences/le contrat. Transmission d'un null
à une autre méthode peut provoque en effet une exception à un moment donné, mais la cause du problème peut être plus compliquée à comprendre, car l'exception sera levée lors d'une invocation spécifique de la fonction null
qui peut être beaucoup plus éloigné.
Voici un exemple concret et réel qui montre pourquoi il faut privilégier l'échec rapide en général et plus particulièrement l'utilisation d'un système de gestion des risques. Object.requireNonNull()
ou tout autre moyen d'effectuer une vérification de l'absence de nullité sur des paramètres conçus pour ne pas l'être null
.
Supposons qu'un Dictionary
qui compose un LookupService
et un List
de String
représentant les mots contenus dans. Ces champs sont conçus pour ne pas être null
et l'un d'entre eux est transmis dans le Dictionary
constructeur.
Supposons maintenant qu'une "mauvaise" implémentation de Dictionary
sans null
vérifier dans l'entrée de la méthode (ici c'est le constructeur) :
public class Dictionary {
private final List<String> words;
private final LookupService lookupService;
public Dictionary(List<String> words) {
this.words = this.words;
this.lookupService = new LookupService(words);
}
public boolean isFirstElement(String userData) {
return lookupService.isFirstElement(userData);
}
}
public class LookupService {
List<String> words;
public LookupService(List<String> words) {
this.words = words;
}
public boolean isFirstElement(String userData) {
return words.get(0).contains(userData);
}
}
Invoquons maintenant la fonction Dictionary
avec un constructeur null
référence pour le words
paramètre :
Dictionary dictionary = new Dictionary(null);
// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");
La JVM lance le NPE à cette déclaration :
return words.get(0).contains(userData);
Exception in thread "main" java.lang.NullPointerException
at LookupService.isFirstElement(LookupService.java:5)
at Dictionary.isFirstElement(Dictionary.java:15)
at Dictionary.main(Dictionary.java:22)
L'exception est déclenchée dans le LookupService
alors que l'origine de celle-ci est bien antérieure (la classe Dictionary
). Cela rend l'analyse globale du problème beaucoup moins évidente.
Est words
null
? Est-ce que words.get(0) null
? Les deux ? Pourquoi l'un, l'autre ou peut-être les deux ? null
? S'agit-il d'une erreur de codage dans Dictionary
(constructeur ? méthode invoquée ?) ? S'agit-il d'une erreur de codage dans LookupService
? (constructeur ? méthode invoquée ?) ?
Enfin, nous devrons inspecter plus de code pour trouver l'origine de l'erreur et, dans une classe plus complexe, peut-être même utiliser un débogueur pour comprendre plus facilement ce qui s'est passé.
Mais pourquoi une chose simple (l'absence de vérification de la nullité) devient-elle un problème complexe ?
Parce que nous avons laissé le bogue initial/le manque identifiable sur un composant spécifique fuir sur les composants inférieurs.
Imaginez que LookupService
n'était pas un service local mais un service distant ou une bibliothèque tierce avec peu d'informations de débogage ou imaginez que vous n'ayez pas 2 couches mais 4 ou 5 couches d'invocations d'objets avant que la fonction null
peuvent-ils être détectés ? Le problème serait encore plus complexe à analyser.
Le moyen de favoriser est donc :
public Dictionary(List<String> words) {
this.words = Objects.requireNonNull(words);
this.lookupService = new LookupService(words);
}
De cette manière, pas de prise de tête : nous obtenons l'exception levée dès qu'elle est reçue :
// exception thrown early : in the constructor
Dictionary dictionary = new Dictionary(null);
// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
Exception in thread "main" java.lang.NullPointerException
at java.util.Objects.requireNonNull(Objects.java:203)
at com.Dictionary.(Dictionary.java:15)
at com.Dictionary.main(Dictionary.java:24)
Notez que j'ai illustré le problème avec un constructeur, mais qu'une invocation de méthode pourrait avoir la même contrainte de vérification non nulle.
40 votes
fr.wikipedia.org/wiki/Fail-fast
4 votes
Duplicata possible de Pourquoi lancer explicitement une NullPointerException plutôt que de la laisser se produire naturellement ?
2 votes
Cette approche de la vérification des arguments vous permet de tricher lorsque vous écrivez des tests unitaires. Spring dispose également d'utilitaires de ce type (voir docs.spring.io/spring/docs/current/javadoc-api/org/ ). Si vous avez un "if" et que vous voulez avoir une couverture de test unitaire élevée, vous devez couvrir les deux branches : lorsque la condition est remplie, et lorsque la condition n'est pas remplie. Si vous utilisez
Objects.requireNonNull
votre code n'a pas de ramification, donc un seul passage d'un test unitaire vous permettra d'obtenir une couverture de 100% :-)0 votes
TOCTOU
0 votes
Toutes les explications fournies ci-dessous sont valables mais, à mon avis, je chercherais à savoir pourquoi la méthode reçoit un résultat nul en premier lieu. Je ne pense pas qu'il s'agisse d'une bonne pratique que de vérifier défensivement si les entrées sont nulles, il vaut mieux, à mon avis, s'assurer que les appelants ne transmettent pas de null.
0 votes
@javing Comment allez-vous vous assurer que personne ne passe null sans faire en sorte que de mauvaises choses se produisent lorsqu'ils essaient ?
0 votes
Je suis ici parce qu'IntelliJ conseille d'utiliser
requireNonNull
partout. Si les exemples ci-dessous peuvent avoir un sens, je dois admettre que les corrections conseillées par IntelliJ ne font pas toujours l'affaire.