J'ai entendu des voix dire que vérifier une valeur nulle renvoyée par les méthodes est une mauvaise conception. J'aimerais entendre quelques raisons à cela.
pseudocode:
variable x = object.method()
if (x is null) do something
J'ai entendu des voix dire que vérifier une valeur nulle renvoyée par les méthodes est une mauvaise conception. J'aimerais entendre quelques raisons à cela.
pseudocode:
variable x = object.method()
if (x is null) do something
Réponses:
La justification de ne pas renvoyer null est que vous n'avez pas à le vérifier et que votre code n'a donc pas besoin de suivre un chemin différent en fonction de la valeur de retour. Vous voudrez peut-être consulter le modèle d'objet nul qui fournit plus d'informations à ce sujet.
Par exemple, si je devais définir une méthode en Java qui retournait une collection, je préférerais généralement renvoyer une collection vide (c'est-à-dire Collections.emptyList()
) plutôt que null car cela signifie que mon code client est plus propre; par exemple
Collection<? extends Item> c = getItems(); // Will never return null.
for (Item item : c) { // Will not enter the loop if c is empty.
// Process item.
}
... qui est plus propre que:
Collection<? extends Item> c = getItems(); // Could potentially return null.
// Two possible code paths now so harder to test.
if (c != null) {
for (Item item : c) {
// Process item.
}
}
null
est un "conteneur", qui contient zéro ou un pointeur vers des objets. (C'est certainement ainsi qu'il est explicitement modélisé par Haskell Maybe
dans ces cas, et c'est d'autant mieux pour le faire.)
Voici la raison.
Dans Clean Code de Robert Martin, il écrit que renvoyer null est une mauvaise conception lorsque vous pouvez à la place retourner, disons, un tableau vide. Puisque le résultat attendu est un tableau, pourquoi pas? Cela vous permettra de parcourir le résultat sans aucune condition supplémentaire. Si c'est un entier, peut-être que 0 suffira, si c'est un hachage, un hachage vide. etc.
Le principe est de ne pas forcer le code d'appel à traiter immédiatement les problèmes. Le code d'appel peut ne pas vouloir se préoccuper d'eux. C'est aussi pourquoi, dans de nombreux cas, les exceptions valent mieux que nul.
Bonnes utilisations du retour de null:
Mauvaises utilisations: essayer de remplacer ou de masquer des situations exceptionnelles telles que:
Dans ces cas, lever une exception est plus adéquat car:
Cependant, les exceptions ne doivent pas être utilisées pour gérer les conditions normales de fonctionnement du programme telles que:
boolean login(String,String)
semble bien et aussiAuthenticationContext createAuthContext(String,String) throws AuthenticationException
Oui, renvoyer NULL est une conception terrible , dans un monde orienté objet. En un mot, l'utilisation NULL conduit à:
Consultez ce billet de blog pour une explication détaillée: http://www.yegor256.com/2014/05/13/why-null-is-bad.html . Plus dans mon livre Objets élégants , section 4.1.
bool TryGetBlah(out blah)
ou FirstOrNull()
ou MatchOrFallback(T fallbackValue)
.
isEmpty()
) et seulement si c'est vrai alors appelez la méthode. Les gens s'opposent à la deuxième affirmant que ses performances sont moins bonnes - mais comme le dit la philosophie Unix, «valorise le temps humain par rapport au temps machine» (c'est-à-dire que des performances trivialement plus lentes perdent moins de temps que les développeurs déboguant du code qui donne des erreurs fallacieuses).
Qui a dit que c'était une mauvaise conception?
La vérification des valeurs nulles est une pratique courante, même encouragée, sinon vous courez le risque de NullReferenceExceptions partout. Il est préférable de gérer l'erreur avec élégance plutôt que de lever des exceptions lorsque vous n'en avez pas besoin.
D'après ce que vous avez dit jusqu'à présent, je pense qu'il n'y a pas suffisamment d'informations.
Renvoyer null depuis une méthode CreateWidget () semble mauvais.
Renvoyer null depuis une méthode FindFooInBar () semble correct.
Create...
retourne une nouvelle instance, ou lance ; Get...
renvoie une instance existante attendue, ou jette ; GetOrCreate...
renvoie une instance existante, ou une nouvelle instance s'il n'y en a pas, ou renvoie ; Find...
renvoie une instance existante, si elle existe, ounull
. Pour les requêtes de collection - Get...
renvoie toujours une collection, qui est vide si aucun élément correspondant n'est trouvé.
Son inventeur dit que c'est une erreur d'un milliard de dollars!
Cela dépend de la langue que vous utilisez. Si vous êtes dans un langage comme C # où la manière idiomatique d'indiquer l'absence de valeur est de retourner null, alors renvoyer null est une bonne conception si vous n'avez pas de valeur. Alternativement, dans des langages tels que Haskell qui utilisent idiomatiquement la monade Maybe dans ce cas, alors retourner null serait une mauvaise conception (si c'était même possible).
null
dans des langages tels que C # et Java est souvent surchargée et donne un sens dans le domaine. Si vous recherchez le null
mot - clé dans les spécifications de langue, cela signifie simplement "un pointeur non valide". Cela ne signifie probablement rien dans aucun domaine problématique.
null
ou "non initialisée"null
Si vous lisez toutes les réponses, il devient clair que la réponse à cette question dépend du type de méthode.
Premièrement, lorsque quelque chose d'exceptionnel se produit (IOproblem, etc.), des exceptions sont logiquement levées. Quand exactement quelque chose est exceptionnel, c'est probablement quelque chose pour un sujet différent.
Chaque fois qu'une méthode ne devrait pas donner de résultats, il existe deux catégories:
Jusqu'à ce que nous ayons un moyen officiel de désigner qu'une fonction peut ou ne peut pas retourner null, j'essaie d'avoir une convention de dénomination pour le désigner.
Tout comme vous avez la convention Try Something () pour les méthodes qui devraient échouer, je nomme souvent mes méthodes Safe Something () lorsque la méthode renvoie un résultat neutre au lieu de null.
Je ne suis pas encore tout à fait d'accord avec le nom, mais je ne pourrais rien trouver de mieux. Donc je cours avec ça pour le moment.
J'ai une convention dans ce domaine qui m'a bien servi
Pour les requêtes sur un seul élément:
Create...
renvoie une nouvelle instance ou lanceGet...
renvoie une instance existante attendue, ou lanceGetOrCreate...
renvoie une instance existante, ou une nouvelle instance s'il n'y en a pas, ou renvoieFind...
renvoie une instance existante, si elle existe, ounull
Pour les requêtes de collection:
Get...
renvoie toujours une collection, qui est vide si aucun élément [1] correspondant n'est trouvé[1] étant donné certains critères, explicites ou implicites, donnés dans le nom de la fonction ou en paramètres.
Get
quand je m'attends à ce qu'il soit là, de sorte que s'il n'y est pas, alors c'est une erreur et je lance - je n'ai jamais besoin de vérifier la valeur de retour. J'utilise Find
si je ne sais vraiment pas si c'est là ou non - alors je dois vérifier la valeur de retour.
Les exceptions concernent des circonstances exceptionnelles .
Si votre fonction est destinée à trouver un attribut associé à un objet donné et que cet objet n'a pas d'attribut de ce type, il peut être approprié de renvoyer null. Si l'objet n'existe pas, lever une exception peut être plus approprié. Si la fonction est censée renvoyer une liste d'attributs et qu'il n'y en a aucun à renvoyer, renvoyer une liste vide a du sens - vous renvoyez tous les attributs nuls.
C'est bien de retourner null si cela est significatif d'une certaine manière:
public String getEmployeeName(int id){ ..}
Dans un cas comme celui-ci, il est significatif de renvoyer null si l'ID ne correspond pas à une entité existante, car cela vous permet de distinguer le cas où aucune correspondance n'a été trouvée d'une erreur légitime.
Les gens peuvent penser que c'est mauvais parce qu'il peut être abusé en tant que valeur de retour "spéciale" qui indique une condition d'erreur, ce qui n'est pas si bon, un peu comme renvoyer des codes d'erreur à partir d'une fonction mais déroutant car l'utilisateur doit vérifier le retour pour null, au lieu d'attraper les exceptions appropriées, par exemple
public Integer getId(...){
try{ ... ; return id; }
catch(Exception e){ return null;}
}
Ce n'est pas nécessairement une mauvaise conception - comme pour beaucoup de décisions de conception, cela dépend.
Si le résultat de la méthode est quelque chose qui n'aurait pas un bon résultat en utilisation normale, renvoyer null est très bien:
object x = GetObjectFromCache(); // return null if it's not in the cache
S'il doit toujours y avoir un résultat non nul, il peut être préférable de lever une exception:
try {
Controller c = GetController(); // the controller object is central to
// the application. If we don't get one,
// we're fubar
// it's likely that it's OK to not have the try/catch since you won't
// be able to really handle the problem here
}
catch /* ... */ {
}
Optional<>
Pour certains scénarios, vous souhaitez remarquer un échec dès qu'il se produit.
Vérifier NULL et ne pas affirmer (pour les erreurs du programmeur) ou lancer (pour les erreurs de l'utilisateur ou de l'appelant) dans le cas d'échec peut signifier que les plantages ultérieurs sont plus difficiles à localiser, car le cas étrange d'origine n'a pas été trouvé.
De plus, ignorer les erreurs peut conduire à des failles de sécurité. Peut-être que la nullité est venue du fait qu'un tampon a été écrasé ou similaire. Maintenant, vous ne plantez pas , ce qui signifie que l'exploitant a une chance de s'exécuter dans votre code.
Quelles alternatives voyez-vous pour renvoyer null?
Je vois deux cas:
Dans ce cas, nous pourrions: Renvoyer Null ou lancer une exception (cochée) (ou peut-être créer un élément et le renvoyer)
Dans ce cas, nous pourrions retourner Null, renvoyer une liste vide ou lancer une exception.
Je pense que return null peut être moins bon que les alternatives car il oblige le client à se souvenir de vérifier la nullité, les programmeurs oublient et codent
x = find();
x.getField(); // bang null pointer exception
En Java, lever une exception vérifiée, RecordNotFoundException, permet au compilateur de rappeler au client de traiter le cas.
Je trouve que les recherches retournant des listes vides peuvent être très pratiques - il suffit de remplir l'écran avec tout le contenu de la liste, oh c'est vide, le code "fonctionne juste".
Parfois, renvoyer NULL est la bonne chose à faire, mais plus particulièrement lorsque vous avez affaire à des séquences de différentes sortes (tableaux, listes, chaînes, ce que vous avez), il est probablement préférable de renvoyer une séquence de longueur nulle, car elle conduit à un code plus court et, espérons-le, plus compréhensible, sans nécessiter beaucoup plus d'écriture de la part de l'implémenteur d'API.
Faites-leur appeler une autre méthode après coup pour savoir si l'appel précédent était nul. ;-) Hé, c'était assez bien pour JDBC
L'idée de base derrière ce fil est de programmer de manière défensive. Autrement dit, code contre l'inattendu. Il existe un éventail de réponses différentes:
Adamski suggère de regarder Null Object Pattern, cette réponse étant votée pour cette suggestion.
Michael Valenty suggère également une convention de dénomination pour dire au développeur ce à quoi on peut s'attendre. ZeroConcept suggère une utilisation correcte d'Exception, si c'est la raison de NULL. Et d'autres.
Si nous établissons la «règle» selon laquelle nous voulons toujours faire de la programmation défensive, nous pouvons voir que ces suggestions sont valables.
Mais nous avons 2 scénarios de développement.
Classes "créées" par un développeur: l'auteur
Classes "consommées" par un autre (peut-être) développeur: le développeur
Indépendamment du fait qu'une classe renvoie NULL pour les méthodes avec une valeur de retour ou non, le développeur devra tester si l'objet est valide.
Si le développeur ne peut pas faire cela, alors cette classe / méthode n'est pas déterministe. Autrement dit, si "l'appel de méthode" pour obtenir l'objet ne fait pas ce qu'il "annonce" (par exemple, getEmployee), il a rompu le contrat.
En tant qu'auteur d'une classe, je veux toujours être aussi gentil et défensif (et déterministe) lors de la création d'une méthode.
Donc, étant donné que NULL ou NULL OBJECT (par exemple if (employee as NullEmployee.ISVALID)) doit être vérifié et que cela peut devoir se produire avec une collection d'employés, alors l'approche d'objet nul est la meilleure approche.
Mais j'aime aussi la suggestion de Michael Valenty de nommer la méthode qui DOIT retourner null, par exemple getEmployeeOrNull.
Un auteur qui lève une exception supprime le choix du développeur de tester la validité de l'objet, ce qui est très mauvais sur une collection d'objets, et oblige le développeur à gérer les exceptions lors de la création de branches de son code consommateur.
En tant que développeur consommant la classe, j'espère que l'auteur me donnera la possibilité d'éviter ou de programmer pour la situation nulle à laquelle leur classe / méthodes peuvent être confrontées.
Donc, en tant que développeur, je programmerais de manière défensive contre NULL à partir d'une méthode. Si l'auteur m'a donné un contrat qui renvoie toujours un objet (NULL OBJECT le fait toujours) et que cet objet a une méthode / propriété par laquelle tester la validité de l'objet, alors j'utiliserais cette méthode / propriété pour continuer à utiliser l'objet , sinon l'objet n'est pas valide et je ne peux pas l'utiliser.
L'essentiel est que l'auteur de la classe / des méthodes doit fournir des mécanismes qu'un développeur peut utiliser dans sa programmation défensive. Autrement dit, une intention plus claire de la méthode.
Le développeur doit toujours utiliser une programmation défensive pour tester la validité des objets renvoyés par une autre classe / méthode.
Cordialement
GregJF
D'autres options à cela, sont: renvoyer une valeur qui indique le succès ou non (ou le type d'une erreur), mais si vous avez juste besoin d'une valeur booléenne qui indiquera le succès / échec, renvoyer null en cas d'échec et un objet pour le succès ne le ferait pas être moins correct, puis renvoyer vrai / faux et obtenir l'objet via le paramètre.
Une autre approche consisterait à utiliser l'exception pour indiquer les échecs, mais ici - il y a en fait beaucoup plus de voix, qui disent que c'est une mauvaise pratique (car l'utilisation d'exceptions peut être pratique mais présente de nombreux inconvénients).
Donc, personnellement, je ne vois rien de mal à renvoyer null pour indiquer que quelque chose s'est mal passé, et à le vérifier plus tard (pour savoir si vous avez réussi ou non). De plus, penser aveuglément que votre méthode ne retournera pas NULL, puis basera votre code dessus, peut entraîner d'autres erreurs, parfois difficiles à trouver (bien que dans la plupart des cas, cela plantera simplement votre système :), comme vous le ferez référence à 0x00000000 tôt ou tard).
Des fonctions nulles involontaires peuvent survenir pendant le développement d'un programme complexe, et comme du code mort, de telles occurrences indiquent de graves défauts dans les structures du programme.
Une fonction ou méthode nulle est souvent utilisée comme comportement par défaut d'une fonction révisable ou d'une méthode remplaçable dans une structure d'objets.
Eh bien, cela dépend bien sûr du but de la méthode ... Parfois, un meilleur choix serait de lever une exception. Tout dépend d'un cas à l'autre.
Si le code est quelque chose comme:
command = get_something_to_do()
if command: # if not Null
command.execute()
Si vous avez un objet factice dont la méthode execute () ne fait rien, et que vous retournez cela au lieu de Null dans les cas appropriés, vous n'avez pas à vérifier le cas Null et pouvez simplement faire:
get_something_to_do().execute()
Donc, ici, le problème n'est pas entre la vérification de NULL et une exception, mais plutôt entre le fait que l'appelant doit gérer différemment les non-cas spéciaux (de quelque manière que ce soit) ou non.
Pour mon cas d'utilisation, j'avais besoin de renvoyer une méthode Map from, puis de rechercher une clé spécifique. Mais si je retourne une carte vide, cela mènera à NullPointerException et alors ce ne sera pas très différent de renvoyer null au lieu d'une carte vide. Mais à partir de Java8, nous pourrions utiliser Optional . Ce qui précède est la raison même pour laquelle le concept facultatif a été introduit.
G'day,
Renvoyer NULL lorsque vous ne parvenez pas à créer un nouvel objet est une pratique standard pour de nombreuses API.
Pourquoi diable c'est un mauvais design, je n'en ai aucune idée.
Edit: Ceci est vrai pour les langages où vous n'avez pas d'exceptions comme C où c'est la convention depuis de nombreuses années.
HTH
'Avahappy,