Est-il préférable de renvoyer une collection nulle ou vide?

420

C'est une sorte de question générale (mais j'utilise C #), quelle est la meilleure façon (meilleure pratique), renvoyez-vous une collection nulle ou vide pour une méthode qui a une collection comme type de retour?

Omu
la source
5
Pas tout à fait CPerkins. rads.stackoverflow.com/amzn/click/0321545613
3
@CPerkins - Oui, il y en a. Il est clairement indiqué dans les propres directives de conception du framework .NET de Microsoft. Voir la réponse de RichardOD pour les détails.
Greg Beech
51
UNIQUEMENT si la signification est "Je ne peux pas calculer les résultats" si vous retournez null. Null ne devrait jamais avoir la sémantique de "vide", seulement "manquant" ou "inconnu". Plus de détails dans mon article sur le sujet: blogs.msdn.com/ericlippert/archive/2009/05/14/…
Eric Lippert
3
Bozho: "any", c'est énormément de langues. Qu'en est-il de Common Lisp, où la liste vide est exactement égale à la valeur nulle? :-)
Ken
9
En fait, le "doublon" concerne les méthodes qui retournent un objet, pas une collection. C'est un scénario différent avec des réponses différentes.
GalacticCowboy

Réponses:

499

Collection vide. Toujours.

Cela craint:

if(myInstance.CollectionProperty != null)
{
  foreach(var item in myInstance.CollectionProperty)
    /* arrgh */
}

Il est considéré comme une meilleure pratique de ne JAMAIS retourner nulllors du retour d'une collection ou d'un énumérable. TOUJOURS retourner une collection / énumérable vide. Il empêche les absurdités susmentionnées et empêche votre voiture d'être incitée par les collègues et les utilisateurs de vos classes.

Lorsque vous parlez de propriétés, définissez toujours votre propriété une fois et oubliez-la

public List<Foo> Foos {public get; private set;}

public Bar() { Foos = new List<Foo>(); }

Dans .NET 4.6.1, vous pouvez condenser cela beaucoup:

public List<Foo> Foos { get; } = new List<Foo>();

Lorsque vous parlez de méthodes qui renvoient des énumérateurs, vous pouvez facilement renvoyer un énumérateur vide au lieu de null...

public IEnumerable<Foo> GetMyFoos()
{
  return InnerGetFoos() ?? Enumerable.Empty<Foo>();
}

L'utilisation Enumerable.Empty<T>()peut être considérée comme plus efficace que le retour, par exemple, d'une nouvelle collection ou d'un tableau vide.

Ryan Lundy
la source
24
Je suis d'accord avec Will, mais je pense que "toujours" est un peu excessif. Alors qu'une collection vide peut signifier "0 élément", le retour de Null peut signifier "aucune collection du tout" - par exemple. si vous analysez du HTML, la recherche d'un <ul> avec id = "foo", <ul id = "foo"> </ul> pourrait retourner une collection vide; s'il n'y a pas de <ul> avec id = "foo" un retour nul serait mieux (sauf si vous voulez gérer ce cas avec une exception)
Patonza
30
il ne s'agit pas toujours de savoir si "vous pouvez facilement retourner un tableau vide", mais plutôt de savoir si un tableau vide peut ou non induire en erreur dans le contexte actuel. Un tableau vide signifie en fait quelque chose, tout comme null. Dire que vous devez toujours retourner un tableau vide plutôt que null, est presque aussi erroné que de vous dire qu'une méthode booléenne devrait toujours retourner true. Les deux valeurs possibles véhiculent un sens.
David Hedlund
31
Vous devriez en fait préférer retourner System.Linq.Enumerable.Empty <Foo> () au lieu d'un nouveau Foo [0]. Il est plus explicite et vous permet d'économiser une allocation de mémoire (au moins, dans mon implémentation .NET installée).
Trillian
4
@Will: OP: "renvoyez-vous une collection nulle ou vide pour une méthode qui a une collection comme type de retour". Dans ce cas, je pense que cela importe IEnumerableou ICollectionnon. Quoi qu'il en soit, si vous sélectionnez quelque chose de type, ICollectionils reviennent également null... J'aimerais qu'ils retournent une collection vide, mais je les ai rencontrés en retour null, alors j'ai pensé que je le mentionnerais ici. Je dirais que la valeur par défaut d'une collection d'énumérables est vide et non nulle. Je ne savais pas que c'était un sujet aussi sensible.
Matthijs Wessels
154

Extrait des Framework Design Guidelines 2nd Edition (p. 256):

NE retournez PAS de valeurs nulles à partir des propriétés de la collection ou des méthodes renvoyant des collections. Renvoyez une collection vide ou un tableau vide à la place.

Voici un autre article intéressant sur les avantages de ne pas retourner de null (j'essayais de trouver quelque chose sur le blog de Brad Abram, et il avait un lien vers l'article).

Edit- comme Eric Lippert a maintenant commenté la question d'origine, je voudrais également faire un lien vers son excellent article .

RichardOD
la source
33
+1. Il est toujours préférable de suivre les directives de conception du cadre, sauf si vous avez une TRÈS bonne raison de ne pas le faire.
@Will, absolument. Voilà ce que je suis. Je n'ai jamais trouvé le besoin de faire autrement
RichardOD
yup, c'est ce que vous suivez. Mais dans les cas où les API sur lesquelles vous comptez ne le suivent pas, vous êtes «piégé»;)
Bozho
@ Bozho- yeap. Votre réponse fournit de belles réponses à ces cas marginaux.
RichardOD
90

Cela dépend de votre contrat et de votre cas concret . En règle générale, il est préférable de renvoyer les collections vides , mais parfois ( rarement ):

  • null pourrait signifier quelque chose de plus spécifique;
  • votre API (contrat) pourrait vous forcer à revenir null.

Quelques exemples concrets:

  • un composant d'interface utilisateur (d'une bibliothèque hors de votre contrôle), peut rendre une table vide si une collection vide est passée, ou aucune table du tout, si null est passé.
  • dans un Object-to-XML (JSON / autre), où nullcela signifierait que l'élément est manquant, tandis qu'une collection vide rendrait un élément redondant (et peut-être incorrect)<collection />
  • vous utilisez ou implémentez une API qui indique explicitement que null doit être retourné / passé
Bozho
la source
4
@ Bozho- quelques exemples intéressants ici.
RichardOD
1
Je verrais un peu votre point même si je ne pense pas que vous devriez construire votre code autour d'autres défauts et par définition, 'null' en c # ne signifie jamais quelque chose de spécifique. La valeur null est définie comme "aucune information" et donc dire qu'elle porte des informations spécifiques est un oxymoron. C'est pourquoi les directives .NET indiquent que vous devez renvoyer un ensemble vide si l'ensemble est effectivement vide. retourner null signifie: "Je ne sais pas où est allé le set attendu"
Rune FS
13
non, cela signifie "il n'y a pas d'ensemble" plutôt que "l'ensemble n'a pas d'éléments"
Bozho
J'avais l'habitude de le croire, puis j'ai dû écrire beaucoup de TSQL et j'ai appris que ce n'était pas toujours le cas, heheh.
1
La partie avec Object-To-Xml est
parfaite
36

Il y a un autre point qui n'a pas encore été mentionné. Considérez le code suivant:

    public static IEnumerable<string> GetFavoriteEmoSongs()
    {
        yield break;
    }

Le langage C # renverra un énumérateur vide lors de l'appel de cette méthode. Par conséquent, pour être cohérent avec la conception du langage (et donc les attentes du programmeur), une collection vide doit être retournée.

Jeffrey L Whitledge
la source
1
Je ne pense pas techniquement que cela renvoie une collection vide.
FryGuy
3
@FryGuy - Non, ce n'est pas le cas. Il renvoie un objet Enumerable, dont la méthode GetEnumerator () renvoie un Enumerator qui est (par analogie avec une collection) vide. Autrement dit, la méthode MoveNext () de l'énumérateur renvoie toujours false. L'appel de l'exemple de méthode ne renvoie pas null, ni ne renvoie un objet Enumerable dont la méthode GetEnumerator () renvoie null.
Jeffrey L Whitledge
31

Le vide est beaucoup plus convivial pour le consommateur.

Il existe une méthode claire pour créer un énumérable vide:

Enumerable.Empty<Element>()
George Polevoy
la source
2
Merci pour l'astuce soignée. J'instanciais une liste vide <T> () comme un idiot mais cela semble beaucoup plus propre et est probablement un peu plus efficace aussi.
Jacobs Data Solutions
18

Il me semble que vous devez renvoyer la valeur sémantiquement correcte dans son contexte, quelle qu'elle soit. Une règle qui dit "toujours retourner une collection vide" me semble un peu simpliste.

Supposons, par exemple, dans un système pour un hôpital, que nous ayons une fonction qui est censée renvoyer une liste de toutes les hospitalisations précédentes au cours des 5 dernières années. Si le client n'est pas allé à l'hôpital, il est judicieux de renvoyer une liste vide. Mais que se passe-t-il si le client laisse cette partie du formulaire d'admission vierge? Nous avons besoin d'une valeur différente pour distinguer "liste vide" de "pas de réponse" ou "ne sait pas". Nous pourrions lever une exception, mais ce n'est pas nécessairement une condition d'erreur, et cela ne nous exclut pas nécessairement du flux de programme normal.

J'ai souvent été frustré par des systèmes qui ne peuvent pas faire la distinction entre zéro et aucune réponse. J'ai eu un certain nombre de fois où un système m'a demandé d'entrer un certain nombre, j'entre zéro et je reçois un message d'erreur me disant que je dois entrer une valeur dans ce champ. Je viens de le faire: je suis entré à zéro! Mais il n'acceptera pas zéro car il ne peut le distinguer d'aucune réponse.


Répondre à Saunders:

Oui, je suppose qu'il y a une différence entre «La personne n'a pas répondu à la question» et «La réponse était zéro». C'était le point du dernier paragraphe de ma réponse. De nombreux programmes sont incapables de distinguer «ne sait pas» de vide ou zéro, ce qui me semble être une faille potentiellement grave. Par exemple, je cherchais une maison il y a environ un an. Je suis allé sur un site Web immobilier et il y avait beaucoup de maisons répertoriées avec un prix demandé de 0 $. Cela me semblait plutôt bien: ils donnent ces maisons gratuitement! Mais je suis sûr que la triste réalité était qu'ils n'avaient tout simplement pas entré le prix. Dans ce cas, vous pouvez dire: «Eh bien, bien sûr, zéro signifie qu'ils n'ont pas entré le prix - personne ne va céder une maison gratuitement». Mais le site a également répertorié les prix moyens de vente et de demande des maisons dans diverses villes. Je ne peux pas m'empêcher de me demander si la moyenne n'a pas inclus les zéros, donnant ainsi une moyenne incorrectement faible pour certains endroits. c'est-à-dire quelle est la moyenne de 100 000 $; 120 000 $; et "je ne sais pas"? Techniquement, la réponse est "ne sais pas". Ce que nous voulons probablement vraiment, c'est 110 000 $. Mais ce que nous obtiendrons probablement, c'est 73 333 $, ce qui serait complètement faux. Et si nous avions ce problème sur un site où les utilisateurs peuvent commander en ligne? (Peu probable pour l'immobilier, mais je suis sûr que vous l'avez vu pour de nombreux autres produits.) Voudrions-nous vraiment que "prix non spécifié pour le moment" soit interprété comme "gratuit"? donnant ainsi une moyenne incorrectement faible pour certains endroits. c'est-à-dire quelle est la moyenne de 100 000 $; 120 000 $; et "je ne sais pas"? Techniquement, la réponse est "ne sais pas". Ce que nous voulons probablement vraiment, c'est 110 000 $. Mais ce que nous obtiendrons probablement, c'est 73 333 $, ce qui serait complètement faux. Et si nous avions ce problème sur un site où les utilisateurs peuvent commander en ligne? (Peu probable pour l'immobilier, mais je suis sûr que vous l'avez vu pour de nombreux autres produits.) Voudrions-nous vraiment que "prix non spécifié pour le moment" soit interprété comme "gratuit"? donnant ainsi une moyenne incorrectement faible pour certains endroits. c'est-à-dire quelle est la moyenne de 100 000 $; 120 000 $; et "je ne sais pas"? Techniquement, la réponse est "ne sais pas". Ce que nous voulons probablement vraiment, c'est 110 000 $. Mais ce que nous obtiendrons probablement, c'est 73 333 $, ce qui serait complètement faux. Et si nous avions ce problème sur un site où les utilisateurs peuvent commander en ligne? (Peu probable pour l'immobilier, mais je suis sûr que vous l'avez vu pour de nombreux autres produits.) Voudrions-nous vraiment que "prix non spécifié pour le moment" soit interprété comme "gratuit"? ce qui serait complètement faux. Et si nous avions ce problème sur un site où les utilisateurs peuvent commander en ligne? (Peu probable pour l'immobilier, mais je suis sûr que vous l'avez vu pour de nombreux autres produits.) Voudrions-nous vraiment que "prix non spécifié pour le moment" soit interprété comme "gratuit"? ce qui serait complètement faux. Et si nous avions ce problème sur un site où les utilisateurs peuvent commander en ligne? (Peu probable pour l'immobilier, mais je suis sûr que vous l'avez vu pour de nombreux autres produits.) Voudrions-nous vraiment que "prix non spécifié pour le moment" soit interprété comme "gratuit"?

RE ayant deux fonctions distinctes, un "y en a-t-il?" et un "si oui, qu'est-ce que c'est?" Oui, vous pourriez certainement le faire, mais pourquoi voudriez-vous? Maintenant, le programme appelant doit passer deux appels au lieu d'un. Que se passe-t-il si un programmeur ne parvient pas à appeler le "tout?" et va directement au "qu'est-ce que c'est?" ? Le programme renverra-t-il un zéro erroné? Jetez une exception? Renvoyer une valeur indéfinie? Il crée plus de code, plus de travail et plus d'erreurs potentielles.

Le seul avantage que je vois est qu'il vous permet de vous conformer à une règle arbitraire. Y a-t-il un avantage à cette règle qui vaut la peine de lui obéir? Sinon, pourquoi s'embêter?


Répondre à Jammycakes:

Considérez à quoi ressemblerait le code réel. Je sais que la question a dit C # mais excusez-moi si j'écris Java. Mon C # n'est pas très net et le principe est le même.

Avec un retour nul:

HospList list=patient.getHospitalizationList(patientId);
if (list==null)
{
   // ... handle missing list ...
}
else
{
  for (HospEntry entry : list)
   //  ... do whatever ...
}

Avec une fonction distincte:

if (patient.hasHospitalizationList(patientId))
{
   // ... handle missing list ...
}
else
{
  HospList=patient.getHospitalizationList(patientId))
  for (HospEntry entry : list)
   // ... do whatever ...
}

C'est en fait une ligne ou deux de moins avec le retour nul, donc ce n'est pas plus de fardeau pour l'appelant, c'est moins.

Je ne vois pas comment cela crée un problème SEC. Ce n'est pas comme si nous devions exécuter l'appel deux fois. Si nous avons toujours voulu faire la même chose lorsque la liste n'existe pas, nous pourrions peut-être pousser la gestion vers la fonction get-list plutôt que de demander à l'appelant de le faire, et donc mettre le code dans l'appelant serait une violation DRY. Mais nous ne voulons presque certainement pas toujours faire la même chose. Dans les fonctions où nous devons avoir la liste à traiter, une liste manquante est une erreur qui pourrait bien arrêter le traitement. Mais sur un écran d'édition, nous ne voulons certainement pas arrêter le traitement s'ils n'ont pas encore entré de données: nous voulons les laisser entrer des données. Ainsi, la gestion de "pas de liste" doit se faire au niveau de l'appelant d'une manière ou d'une autre. Et que nous le fassions avec un retour nul ou une fonction séparée ne fait aucune différence avec le principe plus large.

Bien sûr, si l'appelant ne vérifie pas null, le programme peut échouer avec une exception de pointeur nul. Mais s'il y a une fonction séparée "got any" et que l'appelant n'appelle pas cette fonction mais appelle aveuglément la fonction "get list", alors que se passe-t-il? S'il lance une exception ou échoue, eh bien, c'est à peu près la même chose que ce qui se passerait s'il retournait null et ne le vérifiait pas. S'il renvoie une liste vide, c'est tout simplement faux. Vous ne parvenez pas à faire la distinction entre "J'ai une liste avec zéro élément" et "Je n'ai pas de liste". C'est comme retourner zéro pour le prix lorsque l'utilisateur n'a entré aucun prix: c'est juste faux.

Je ne vois pas en quoi l'attachement d'un attribut supplémentaire à la collection aide. L'appelant doit encore le vérifier. Comment est-ce mieux que de vérifier null? Encore une fois, la pire chose qui puisse arriver est que le programmeur oublie de le vérifier et donne des résultats incorrects.

Une fonction qui renvoie null n'est pas une surprise si le programmeur est familier avec le concept de null signifiant "ne pas avoir de valeur", ce que je pense que tout programmeur compétent aurait dû entendre, qu'il pense que c'est une bonne idée ou non. Je pense qu'avoir une fonction séparée est plus un problème de "surprise". Si un programmeur n'est pas familier avec l'API, lorsqu'il exécute un test sans données, il découvre rapidement que parfois il récupère une valeur nulle. Mais comment pourrait-il découvrir l'existence d'une autre fonction à moins qu'il ne lui vienne à l'esprit qu'il pourrait y avoir une telle fonction et qu'il vérifie la documentation, et que la documentation soit complète et compréhensible? Je préfère de loin avoir une fonction qui me donne toujours une réponse significative, plutôt que deux fonctions que je dois connaître et me rappeler d'appeler les deux.

Geai
la source
4
Pourquoi est-il nécessaire de combiner les réponses «pas de réponse» et «zéro» dans la même valeur de retour? Au lieu de cela, la méthode doit-elle renvoyer «toutes les hospitalisations antérieures au cours des cinq dernières années» et avoir une méthode distincte qui demande «la liste des hospitalisations précédentes a-t-elle déjà été remplie?». Cela suppose qu'il y a une différence entre une liste remplie sans hospitalisation antérieure et une liste non remplie.
John Saunders
2
Mais si vous retournez null, vous placez déjà un fardeau supplémentaire sur l'appelant! L'appelant doit vérifier chaque valeur de retour pour null - une horrible violation DRY. Si vous souhaitez indiquer séparément "l'appelant n'a pas répondu à la question", il est plus simple de créer un appel de méthode supplémentaire pour indiquer le fait. Soit cela, soit utilisez un type de collection dérivé avec une propriété supplémentaire de DeclinedToAnswer. La règle de ne jamais retourner nul n'est pas arbitraire du tout, c'est le principe de la moindre surprise. De plus, le type de retour de votre méthode doit signifier ce que son nom indique. Null ne le fait certainement pas.
jammycakes
2
Vous supposez que votre getHospitalizationList n'est appelée qu'à partir d'un seul endroit et / ou que tous ses appelants voudront faire la distinction entre les cas «sans réponse» et «zéro». Il y aura des cas (presque certainement une majorité) où votre appelant n'a pas besoin de faire cette distinction, et vous les forcez donc à ajouter des contrôles nuls aux endroits où cela ne devrait pas être nécessaire. Cela ajoute un risque important à votre base de code car les gens peuvent trop facilement oublier de le faire - et parce qu'il y a très peu de raisons légitimes de retourner null au lieu d'une collection, ce sera beaucoup plus probable.
jammycakes
3
RE le nom: Aucun nom de fonction ne peut décrire complètement ce que fait la fonction à moins qu'elle ne soit aussi longue que la fonction, et donc extrêmement impraticable. Mais dans tous les cas, si la fonction renvoie une liste vide quand aucune réponse n'a été donnée, alors par le même raisonnement, ne devrait-elle pas être appelée "getHospitalizationListOrEmptyListIfNoAnswer"? Mais voudriez-vous vraiment que la fonction Java Reader.read soit renommée readCharacterOrReturnMinusOneOnEndOfStream? Que ResultSet.getInt devrait vraiment être "getIntOrZeroIfValueWasNull"? Etc.
Jay
4
RE chaque appel veut distinguer: Eh bien, oui, je suppose que, ou du moins que l'auteur d'un appelant devrait prendre une décision consciente qu'il ne se soucie pas. Si la fonction renvoie une liste vide pour "ne sait pas" et que les appelants traitent aveuglément ce "aucun", cela pourrait donner des résultats très inexacts. Imaginez si la fonction était "getAllergicReactionToMedicationList". Un programme qui a traité aveuglément "la liste n'a pas été entrée" car "le patient n'a pas de réactions allergiques connues" pourrait littéralement entraîner la mort d'un paitent. Vous obtiendriez des résultats similaires, quoique moins spectaculaires, dans de nombreux autres systèmes. ...
Jay
10

Si une collection vide a un sens sémantique, c'est ce que je préfère retourner. Renvoyer une collection vide pour les GetMessagesInMyInbox()communications "vous n'avez vraiment aucun message dans votre boîte de réception", alors que le retour nullpeut être utile pour indiquer que les données sont insuffisantes pour dire à quoi devrait ressembler la liste qui pourrait être retournée.

David Hedlund
la source
6
Dans l'exemple que vous donnez, il semble que la méthode devrait probablement lever une exception si elle ne peut pas répondre à la demande plutôt que de simplement retourner null. Les exceptions sont beaucoup plus utiles pour diagnostiquer les problèmes que les valeurs nulles.
Greg Beech
eh bien oui, dans l'exemple de la boîte de réception, une nullvaleur ne semble certainement pas raisonnable, je pensais en termes plus généraux à celle-ci. Les exceptions sont également importantes pour communiquer le fait que quelque chose a mal tourné, mais si les "données insuffisantes" auxquelles il est fait référence sont parfaitement attendues, le fait de lever une exception entraînerait une mauvaise conception. Je pense plutôt à un scénario où c'est parfaitement possible et sans erreur du tout pour que la méthode ne puisse parfois pas calculer une réponse.
David Hedlund
6

Renvoyer null pourrait être plus efficace, car aucun nouvel objet n'est créé. Cependant, cela nécessiterait également souvent une nullvérification (ou une gestion des exceptions).

Sémantiquement, nullet une liste vide ne signifie pas la même chose. Les différences sont subtiles et un choix peut être meilleur que l'autre dans des cas spécifiques.

Quel que soit votre choix, documentez-le pour éviter toute confusion.

Codeur karmique
la source
8
L'efficacité ne devrait presque jamais être un facteur lorsque l'on considère l'exactitude de la conception d'une API. Dans certains cas très spécifiques tels que les primitives graphiques, il peut en être ainsi, mais lorsque je traite de listes et de la plupart des autres choses de haut niveau, j'en doute fortement.
Greg Beech
D'accord avec Greg, d'autant plus que le code que l'utilisateur de l'API doit écrire pour compenser cette "optimisation" peut être plus inefficace que si une meilleure conception était utilisée en premier lieu.
Craig Stuntz
D'accord, et dans la plupart des cas, cela ne vaut tout simplement pas l'optimisation. Les listes vides sont pratiquement gratuites avec une gestion de la mémoire moderne.
Jason Baker,
6

On pourrait faire valoir que le raisonnement derrière Null Object Pattern est similaire à celui en faveur du retour de la collection vide.

Dan
la source
4

Dépend de la situation. S'il s'agit d'un cas spécial, retournez null. Si la fonction arrive juste à renvoyer une collection vide, alors évidemment, c'est ok. Cependant, renvoyer une collection vide en tant que cas spécial en raison de paramètres non valides ou d'autres raisons n'est PAS une bonne idée, car il masque une condition de cas spécial.

En fait, dans ce cas, je préfère généralement lever une exception pour m'assurer qu'elle n'est VRAIMENT pas ignorée :)

Dire que cela rend le code plus robuste (en renvoyant une collection vide) car ils n'ont pas à gérer la condition nulle est mauvais, car il masque simplement un problème qui devrait être géré par le code appelant.

Larry Watanabe
la source
4

Je dirais que ce nulln'est pas la même chose qu'une collection vide et vous devez choisir celle qui représente le mieux ce que vous retournez. Dans la plupart des cas, il nulln'y a rien (sauf en SQL). Une collection vide est quelque chose, quoique quelque chose de vide.

Si vous devez choisir l'un ou l'autre, je dirais que vous devriez tendre vers une collection vide plutôt que null. Mais il y a des moments où une collection vide n'est pas la même chose qu'une valeur nulle.

Jason Baker
la source
4

Pensez toujours en faveur de vos clients (qui utilisent votre API):

Le retour de 'null' pose très souvent des problèmes avec les clients qui ne gèrent pas correctement les vérifications nulles, ce qui provoque une NullPointerException pendant l'exécution. J'ai vu des cas où une telle vérification nulle manquante a forcé un problème de production prioritaire (un client utilisé pour chaque (...) sur une valeur nulle). Pendant le test, le problème ne s'est pas produit, car les données opérées étaient légèrement différentes.

manuel aldana
la source
3

J'aime donner des explications ici, avec un exemple approprié.

Considérons un cas ici ..

int totalValue = MySession.ListCustomerAccounts()
                          .FindAll(ac => ac.AccountHead.AccountHeadID 
                                         == accountHead.AccountHeadID)
                          .Sum(account => account.AccountValue);

Considérons ici les fonctions que j'utilise ..

1. ListCustomerAccounts() // User Defined
2. FindAll()              // Pre-defined Library Function

Je peux facilement utiliser ListCustomerAccountet FindAllau lieu de.,

int totalValue = 0; 
List<CustomerAccounts> custAccounts = ListCustomerAccounts();
if(custAccounts !=null ){
  List<CustomerAccounts> custAccountsFiltered = 
        custAccounts.FindAll(ac => ac.AccountHead.AccountHeadID 
                                   == accountHead.AccountHeadID );
   if(custAccountsFiltered != null)
      totalValue = custAccountsFiltered.Sum(account => 
                                            account.AccountValue).ToString();
}

REMARQUE: Étant donné que AccountValue ne l'est pas null, la fonction Sum () ne retournera pas null. Par conséquent, je peux l'utiliser directement.

Muthu Ganapathy Nathan
la source
2

Nous avons eu cette discussion au sein de l'équipe de développement au travail il y a environ une semaine, et nous avons presque unanimement opté pour la collecte vide. Une personne a voulu retourner null pour la même raison que Mike spécifiée ci-dessus.

Henric
la source
2

Collection vide. Si vous utilisez C #, l'hypothèse est que la maximisation des ressources système n'est pas essentielle. Bien que moins efficace, le retour de Empty Collection est beaucoup plus pratique pour les programmeurs impliqués (pour la raison décrite ci-dessus).

mothis
la source
2

Le retour d'une collection vide est préférable dans la plupart des cas.

La raison en est la commodité de la mise en œuvre de l'appelant, un contrat cohérent et une mise en œuvre plus facile.

Si une méthode renvoie null pour indiquer un résultat vide, l'appelant doit implémenter un adaptateur de vérification null en plus de l'énumération. Ce code est ensuite dupliqué dans différents appelants, alors pourquoi ne pas mettre cet adaptateur dans la méthode afin qu'il puisse être réutilisé.

Une utilisation valide de null pour IEnumerable peut être une indication de résultat absent ou d'échec d'une opération, mais dans ce cas, d'autres techniques doivent être envisagées, telles que la levée d'une exception.

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

namespace StackOverflow.EmptyCollectionUsageTests.Tests
{
    /// <summary>
    /// Demonstrates different approaches for empty collection results.
    /// </summary>
    class Container
    {
        /// <summary>
        /// Elements list.
        /// Not initialized to an empty collection here for the purpose of demonstration of usage along with <see cref="Populate"/> method.
        /// </summary>
        private List<Element> elements;

        /// <summary>
        /// Gets elements if any
        /// </summary>
        /// <returns>Returns elements or empty collection.</returns>
        public IEnumerable<Element> GetElements()
        {
            return elements ?? Enumerable.Empty<Element>();
        }

        /// <summary>
        /// Initializes the container with some results, if any.
        /// </summary>
        public void Populate()
        {
            elements = new List<Element>();
        }

        /// <summary>
        /// Gets elements. Throws <see cref="InvalidOperationException"/> if not populated.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>.</returns>
        public IEnumerable<Element> GetElementsStrict()
        {
            if (elements == null)
            {
                throw new InvalidOperationException("You must call Populate before calling this method.");
            }

            return elements;
        }

        /// <summary>
        /// Gets elements, empty collection or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with zero or more elements, or null in some cases.</returns>
        public IEnumerable<Element> GetElementsInconvenientCareless()
        {
            return elements;
        }

        /// <summary>
        /// Gets elements or nothing.
        /// </summary>
        /// <returns>Returns <see cref="IEnumerable{T}"/> of <see cref="Element"/>, with elements, or null in case of empty collection.</returns>
        /// <remarks>We are lucky that elements is a List, otherwise enumeration would be needed.</remarks>
        public IEnumerable<Element> GetElementsInconvenientCarefull()
        {
            if (elements == null || elements.Count == 0)
            {
                return null;
            }
            return elements;
        }
    }

    class Element
    {
    }

    /// <summary>
    /// http://stackoverflow.com/questions/1969993/is-it-better-to-return-null-or-empty-collection/
    /// </summary>
    class EmptyCollectionTests
    {
        private Container container;

        [SetUp]
        public void SetUp()
        {
            container = new Container();
        }

        /// <summary>
        /// Forgiving contract - caller does not have to implement null check in addition to enumeration.
        /// </summary>
        [Test]
        public void UseGetElements()
        {
            Assert.AreEqual(0, container.GetElements().Count());
        }

        /// <summary>
        /// Forget to <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void WrongUseOfStrictContract()
        {
            container.GetElementsStrict().Count();
        }

        /// <summary>
        /// Call <see cref="Container.Populate"/> and use strict method.
        /// </summary>
        [Test]
        public void CorrectUsaOfStrictContract()
        {
            container.Populate();
            Assert.AreEqual(0, container.GetElementsStrict().Count());
        }

        /// <summary>
        /// Inconvenient contract - needs a local variable.
        /// </summary>
        [Test]
        public void CarefulUseOfCarelessMethod()
        {
            var elements = container.GetElementsInconvenientCareless();
            Assert.AreEqual(0, elements == null ? 0 : elements.Count());
        }

        /// <summary>
        /// Inconvenient contract - duplicate call in order to use in context of an single expression.
        /// </summary>
        [Test]
        public void LameCarefulUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless() == null ? 0 : container.GetElementsInconvenientCareless().Count());
        }

        [Test]
        public void LuckyCarelessUseOfCarelessMethod()
        {
            // INIT
            var praySomeoneCalledPopulateBefore = (Action)(()=>container.Populate());
            praySomeoneCalledPopulateBefore();

            // ACT //ASSERT
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Excercise <see cref="ArgumentNullException"/> because of null passed to <see cref="Enumerable.Count{TSource}(System.Collections.Generic.IEnumerable{TSource})"/>
        /// </summary>
        [Test]
        [ExpectedException(typeof(ArgumentNullException))]
        public void UnfortunateCarelessUseOfCarelessMethod()
        {
            Assert.AreEqual(0, container.GetElementsInconvenientCareless().Count());
        }

        /// <summary>
        /// Demonstrates the client code flow relying on returning null for empty collection.
        /// Exception is due to <see cref="Enumerable.First{TSource}(System.Collections.Generic.IEnumerable{TSource})"/> on an empty collection.
        /// </summary>
        [Test]
        [ExpectedException(typeof(InvalidOperationException))]
        public void UnfortunateEducatedUseOfCarelessMethod()
        {
            container.Populate();
            var elements = container.GetElementsInconvenientCareless();
            if (elements == null)
            {
                Assert.Inconclusive();
            }
            Assert.IsNotNull(elements.First());
        }

        /// <summary>
        /// Demonstrates the client code is bloated a bit, to compensate for implementation 'cleverness'.
        /// We can throw away the nullness result, because we don't know if the operation succeeded or not anyway.
        /// We are unfortunate to create a new instance of an empty collection.
        /// We might have already had one inside the implementation,
        /// but it have been discarded then in an effort to return null for empty collection.
        /// </summary>
        [Test]
        public void EducatedUseOfCarefullMethod()
        {
            Assert.AreEqual(0, (container.GetElementsInconvenientCarefull() ?? Enumerable.Empty<Element>()).Count());
        }
    }
}
George Polevoy
la source
2

Je l'appelle mon erreur d'un milliard de dollars… À cette époque, je concevais le premier système de type complet pour les références dans un langage orienté objet. Mon objectif était de garantir que toute utilisation des références soit absolument sûre, la vérification étant effectuée automatiquement par le compilateur. Mais je n'ai pas pu résister à la tentation de mettre une référence nulle, tout simplement parce qu'elle était si facile à mettre en œuvre. Cela a conduit à d'innombrables erreurs, vulnérabilités et plantages du système, qui ont probablement causé un milliard de dollars de douleur et de dommages au cours des quarante dernières années. - Tony Hoare, inventeur d'ALGOL W.

Voir ici pour une tempête de merde élaborée sur nullen général. Je ne suis pas d'accord avec la déclaration qui en undefinedest une autre null, mais elle vaut quand même la peine d'être lue. Et cela explique pourquoi vous devriez éviter nulldu tout et pas seulement dans le cas que vous avez demandé. L'essence est, c'est nulldans n'importe quelle langue un cas spécial. Vous devez penser à nullune exception. undefinedest différent dans ce sens, ce code traitant d'un comportement indéfini n'est dans la plupart des cas qu'un bogue. C et la plupart des autres langues ont également un comportement non défini, mais la plupart d'entre elles n'ont pas d'identifiant pour cela dans la langue.

ceving
la source
1

Du point de vue de la gestion de la complexité, objectif principal de l'ingénierie logicielle, nous voulons éviter de propager une complexité cyclomatique inutile aux clients d'une API. Renvoyer un null au client, c'est comme lui renvoyer le coût de complexité cyclomatique d'une autre branche de code.

(Cela correspond à une charge de test unitaire. Vous devez écrire un test pour le cas de retour nul, en plus du cas de retour de la collection vide.)

dthal
la source