Quelles sont les lignes directrices pour déterminer quand il est pas nécessaire de vérifier une valeur nulle ?
Une grande partie du code hérité sur lequel j'ai travaillé récemment comporte des vérifications de null à n'en plus finir. Des vérifications de null sur des fonctions triviales, des vérifications de null sur des appels d'API qui indiquent des retours non nuls, etc. Dans certains cas, les vérifications de null sont raisonnables, mais dans de nombreux cas, une valeur nulle n'est pas une attente raisonnable.
J'ai entendu plusieurs arguments allant de "On ne peut pas se fier aux autres codes" à "TOUJOURS programmer de manière défensive" en passant par "Tant que le langage ne me garantit pas une valeur non nulle, je vais toujours vérifier". Je suis d'accord avec bon nombre de ces principes jusqu'à un certain point, mais j'ai constaté que des vérifications excessives de null causent d'autres problèmes qui vont à l'encontre de ces principes. Est-ce que la vérification acharnée de null en vaut vraiment la peine ?
Fréquemment, j'ai observé que des codes avec des vérifications excessives de null sont en réalité de moindre qualité, pas de qualité supérieure. Une grande partie du code semble tellement centrée sur les vérifications de null que le développeur a perdu de vue d'autres qualités importantes, telles que la lisibilité, la correction, ou la gestion des exceptions. En particulier, je vois beaucoup de code ignorer l'exception std::bad_alloc, mais faire une vérification de null sur un new
.
En C++, je comprends cela dans une certaine mesure en raison du comportement imprévisible du déréférencement d'un pointeur nul ; le déréférencement nul est géré de manière plus efficace en Java, C#, Python, etc. Ai-je simplement vu de mauvais exemples de vérification de null vigilante ou y a-t-il vraiment quelque chose à cela ?
Cette question est censée être agnostique par rapport au langage, bien que je m'intéresse principalement au C++, Java et C#.
Quelques exemples de vérifications de null que j'ai vus qui semblent être excessives comprennent les suivants :
Cet exemple semble tenir compte des compilateurs non standards car la spécification C++ indique qu'un new échoué lance une exception. À moins que vous ne souteniez explicitement les compilateurs non conformes, est-ce logique ? Est-ce sensé dans un langage géré comme Java ou C# (ou même C++/CLR) ?
try {
MyObject* obj = new MyObject();
if(obj!=NULL) {
//faire quelque chose
} else {
//??? la plupart du code que je vois le consigne dans un journal et continue
//ou il répète ce qui se trouve dans le gestionnaire d'exceptions
}
} catch(std::bad_alloc) {
//Faire quelque chose ? généralement -- ce code est erroné car il alloue
//plus de mémoire et échouera probablement, comme écrire dans un fichier journal.
}
Un autre exemple est lors du travail sur du code interne. Particulièrement, si c'est une petite équipe qui peut définir ses propres pratiques de développement, cela semble inutile. Sur certains projets ou du code hérité, faire confiance à la documentation peut ne pas être raisonnable... mais pour du code récent que vous ou votre équipe contrôlez, est-ce vraiment nécessaire ?
Si une méthode, que vous pouvez voir et modifier (ou réprimander le développeur responsable), a un contrat, est-il toujours nécessaire de vérifier les valeurs nulles ?
//X est non négatif.
//Retourne un objet ou lance une exception.
MyObject* create(int x) {
if(x<0) throw;
return new MyObject();
}
try {
MyObject* x = create(unknownVar);
if(x!=null) {
//cette vérification de null est-elle vraiment nécessaire ?
}
} catch {
//faire quelque chose
}
Lors du développement d'une fonction privée ou interne, est-il vraiment nécessaire de gérer explicitement une valeur nulle lorsque le contrat demande des valeurs non nulles uniquement ? Pourquoi une vérification de null serait-elle préférable à une assertion ?
(évidemment, dans votre API publique, les vérifications de null sont vitales car il est considéré impoli de gronder vos utilisateurs pour une mauvaise utilisation de l'API)
//Uniquement pour un usage interne--non public, pas partie de l'API publique
//l'entrée ne doit pas être nulle.
//retourne une valeur non négative, ou -1 en cas d'échec
int ParseType(String input) {
if(input==null) return -1;
//faire quelque chose de magique
return valeur;
}
Comparé à :
//Uniquement pour un usage interne--non public, pas partie de l'API publique
//l'entrée ne doit pas être nulle.
//retourne une valeur non négative
int ParseType(String input) {
assert(input!=null : "L'entrée ne doit pas être nulle.");
//faire quelque chose de magique
return valeur;
}