Vaut-il mieux retourner la collection nulle ou vide?

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

362
demandé sur Mark Bell 2009-12-28 18:30:29

18 réponses

Vide de la collection. Toujours.

ça craint:

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

Il est considéré comme une meilleure pratique de ne JAMAIS revenir null lors du retour d'une collection ou énumérables. TOUJOURS retour d'un vide énumérable/collection. Il évite les absurdités susmentionnées, et empêche votre voiture se faire ovationner 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 l'oublier

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érables, vous pouvez facilement retourner un énumérable vide au lieu de null ...

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

utilisant Enumerable.Empty<T>() peut être considéré comme plus efficace que de retourner, par exemple, une nouvelle collection ou un nouveau tableau vide.

435
répondu Will 2016-05-18 20:54:38

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

ne pas retourner les valeurs nulles de propriétés de la collection ou des méthodes le retour des collections. De retour d'un vide collection ou un tableau vide à la place.

voici un autre article intéressant sur les avantages de ne pas rendre nulls (j'essayais de trouver quelque chose sur le blog de Brad Abram, et il lié à l'article).

Edit - comme Eric Lippert a maintenant commenté à la question originale, je voudrais également à lien à son excellent article .

144
répondu RichardOD 2017-11-04 04:41:58

dépend de votre contrat et votre caisse en béton . Généralement il est préférable de retourner les collections vides , mais parfois ( rarement ):

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

quelques exemples concrets:

  • un composant de L'interface utilisateur (d'une bibliothèque hors de votre contrôle), pourrait rendre une table vide si une collection vide est passée, ou aucune table du tout, si null est passé.
  • dans un objet-To-XML (JSON / whatever), où null signifierait que l'élément est manquant, tandis qu'une collection vide rendrait une redondance (et peut-être incorrecte) <collection />
  • vous utilisez ou implémentez une API qui explicitement indique que null doit être retourné /passé
84
répondu Bozho 2010-02-08 11:46:43

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

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

le langage C# retournera un recenseur 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 des programmeurs) une collection vide devrait être retournée.

33
répondu Jeffrey L Whitledge 2009-12-28 16:19:15

Vide est beaucoup plus convivial.

il y a une méthode claire pour composer un énumérable vide:

Enumerable.Empty<Element>()
29
répondu George Polevoy 2009-12-30 14:28:42

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

Supposons, par exemple, un système, un hôpital, nous avons une fonction qui est censé renvoyer une liste de toutes les hospitalisations pour les 5 dernières années. Si le client n'est pas allé à l'hôpital, il est logique de retourner une liste vide. Mais que faire si le le client a laissé cette partie du formulaire vierge? Nous avons besoin d'une valeur différente pour distinguer "liste vide" de "pas de réponse" ou "ne sait pas". Nous pourrions jeter une exception, mais ce n'est pas nécessairement une condition d'erreur, et cela ne nous pousse pas nécessairement hors du flux normal du programme.

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


réponse à Saunders:

Oui, je suppose qu'il y a une différence entre "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 blanc ou zéro, ce qui me semble un défaut potentiellement grave. Par exemple, il y a environ un an, je cherchais une maison. Je suis allé à un site web immobilier et il y avait de nombreuses maisons cotées avec un prix de demande de 0$. 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, évidemment zéro signifie qu'ils n'ont pas entré le prix -- Personne ne va donner maison gratuitement."Mais le site répertorie également les prix de vente et de vente moyens des maisons dans diverses villes. Je ne peux m'empêcher de me demander si la moyenne n'incluait pas les zéros, donnant ainsi une moyenne plus basse pour certains endroits. autrement dit, quelle est la moyenne de 100 000$; 120 000$; et "ne sait pas"? Techniquement, la réponse est"Je ne sais pas". Ce que nous voulons probablement voir, c'est 110 000$. Mais on aura probablement 73 333$, ce qui serait complètement faux. Et si on avait 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 avez vu fait pour beaucoup d'autres produits.) Voulons-nous vraiment que" prix non encore spécifié "soit interprété comme"gratuit"?

ayant deux fonctions distinctes, une "y en a-t-il?"et "si oui, quel est-il?"Oui, vous avez certainement pu le faire, mais pourquoi voudriez-vous? Maintenant, le programme appelant doit faire deux appels au lieu d'un. Qu'advient-il si un programmeur ne parvient pas à appeler le "tout?"et s'en va droit au "c'est quoi?"? Le programme de retour d'une erreur de zéro? Lever une exception? Retourner une valeur non définie? Il crée plus de code, plus de travail, et plus d'erreurs potentielles.

Le seul avantage que je vois est qu'il permet de respecter une règle arbitraire. Y a-t-il un avantage à cette règle qui justifie la peine de l'obéir? Si non, pourquoi s'embêter?


réponse à Jammycakes:

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

Avec un retour null:

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

avec une fonction séparée:

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

en fait, c'est une ligne ou deux de moins avec la déclaration nulle, donc ce n'est pas plus de fardeau pour l'appelant, c'est moins.

je ne vois pas comment il crée un SÈCHE question. Ce n'est pas comme si nous devions exécuter l'appel deux fois. Si nous voulions toujours faire la même chose lorsque la liste n'existe pas, nous pourrions peut-être pousser la manipulation jusqu'à 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 sèche. Mais nous avons presque certainement ne veulent 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 edit screen, nous ne voulons sûrement pas arrêter le traitement s'ils n'ont pas encore entré les données: nous voulons les laisser entrer les données. Donc la manipulation "pas de liste" doit être faite à l'appelant niveau d'une façon ou d'une autre. Et que nous le fassions avec un retour nul ou une fonction séparée ne fait aucune différence pour le principe plus grand.

bien sûr, si l'appelant ne vérifie pas null, le programme pourrait échouer avec une exception null-pointeur. Mais s'il y a une fonction "got any" séparée et que l'appelant n'appelle pas cette fonction appelle aveuglément la fonction" get list", alors que se passe-t-il? Si elle jette une exception ou échoue, Eh bien, c'est à peu près la même chose que ce qui se passerait si elle est retournée nulle et n'a pas vérifié pour elle. S'il renvoie une liste vide, c'est mal. Vous ne faites pas la distinction entre "j'ai une liste avec zéro éléments" et "je n'ai pas de liste". C'est comme revenir à zéro pour le prix quand l'utilisateur n'a pas entré de prix: c'est juste mal.

je n'ai pas voir comment attacher un attribut supplémentaire à cette collection aide. L'appelant a encore à vérifier. Comment est-ce mieux que de vérifier pour la nulle? 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 signification "n'ont pas de valeur", que je pense que tout programmeur compétent devrait avoir entendu parler de, si il pense que c'est une bonne idée ou pas. Je pense qu'avoir une fonction séparée est plus un problème "surprise". Si un programmeur n'est pas familier avec L'API, lorsqu'il exécute un test sans aucune donnée, il découvrira rapidement que parfois il obtient un nul. Mais comment découvrirait-il l'existence d'une autre fonction à moins qu'il ne se soit rendu compte qu'il pourrait y avoir une telle fonction et qu'il vérifie la documentation, et la documentation est complète et compréhensible? Je préférerais avoir une fonction qui soit toujours me donne une réponse significative, plutôt que deux fonctions que je dois connaître et se rappeler d'appeler les deux.

18
répondu Jay 2014-10-21 06:41:17

si une collection vide a un sens sémantique, c'est ce que je préfère retourner. Retourner une collection vide pour GetMessagesInMyInbox() communique" vous n'avez vraiment pas de messages dans votre boîte de réception", tandis que retourner null pourrait être utile pour communiquer que les données disponibles sont insuffisantes pour dire ce que la liste qui pourrait être retourné devrait ressembler.

10
répondu David Hedlund 2009-12-28 15:35:12

retourner null pourrait être plus efficace, car aucun nouvel objet n'est créé. Toutefois, il faudrait souvent effectuer une vérification null (ou une vérification des exceptions).)

sémantiquement, null et une liste vide ne signifient pas la même chose. Les différences sont subtiles et un choix peut être meilleur que l'autre dans des cas précis.

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

6
répondu Karmic Coder 2009-12-28 15:39:17

on pourrait soutenir que le raisonnement derrière motif objet nul est similaire à celui en faveur de retourner la collection vide.

6
répondu Dan 2009-12-28 16:28:18

dépend de la situation. Si c'est un cas spécial, puis retourner la valeur null. Si la fonction retourne juste une collection vide, alors évidemment retourner qui est ok. Cependant, retourner une collection vide comme un cas spécial en raison de paramètres invalides ou d'autres raisons n'est pas une bonne idée, parce qu'il masque une condition de cas spécial.

en fait, dans ce cas, je préfère généralement jeter une exception pour s'assurer QU'il est vraiment pas ignoré :)

disant qu'il rend le code plus robuste (en retournant une collection vide) car ils n'ont pas à gérer la condition nulle est mauvais, car il est tout simplement masquant un problème qui devrait être traité par le code appelant.

4
répondu Larry Watanabe 2009-12-28 15:46:21

je dirais que null n'est pas la même chose qu'une collection vide et vous devriez choisir celui qui représente le mieux ce que vous retournez. Dans la plupart des cas null n'est rien (sauf en SQL). Une collection vide est quelque chose, bien que 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 nulle. Mais il ya des moments où un regroupement vide n'est pas la même chose comme une valeur null.

4
répondu Jason Baker 2009-12-28 15:48:28

pensez toujours en faveur de vos clients (qui utilisent votre api):

retourner "null" pose très souvent des problèmes avec les clients qui ne gèrent pas correctement les vérifications null, ce qui provoque une NullPointerException pendant l'exécution. J'ai vu des cas où une telle absence de contrôle null a forcé un problème de production prioritaire (un client a utilisé foreach(...) sur une valeur nulle). Au cours des essais, le problème ne s'est pas produit, car les données étaient légèrement différentes.

4
répondu manuel aldana 2009-12-28 20:39:33

j'aime expliquer ici, avec l'exemple approprié.

considérez un cas ici..

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

ici considérer les fonctions que j'utilise ..

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

je peux facilement utiliser ListCustomerAccount et FindAll au 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();
}

NOTE: puisque AccountValue n'est pas null , la fonction Sum () ne sera pas retournez null . Donc je peux l'utiliser directement.

3
répondu Muthu Ganapathy Nathan 2014-10-21 06:38:51

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

2
répondu Henric 2009-12-28 15:35:36

Vide De La Collection. Si vous utilisez C#, l'hypothèse est que maximiser les ressources du système n'est pas essentiel. Bien que moins efficace, retourner la Collection vide est beaucoup plus pratique pour les programmeurs impliqués (pour la raison mentionnée ci-dessus).

2
répondu mothis 2009-12-28 15:39:59

retourner 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, le contrat uniforme, et la 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 du dénombrement. Ce code est ensuite dupliqué dans différents appelants, alors pourquoi ne pas mettre cet adaptateur à l'intérieur de la méthode afin qu'il puisse être réutilisé.

un usage valide de null pour IEnumerable pourrait être une indication de résultat absent, ou une défaillance de l'opération, mais dans ce cas, d'autres techniques doivent être considérées, comme jeter 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>
    /// /q/is-it-better-to-return-null-or-empty-collection-36484/"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());
        }
    }
}
2
répondu George Polevoy 2016-02-25 11:46:32

Je l'appelle mon erreur d'un milliard de dollars...à l'époque, je concevais le premier système de type complet pour les références dans un langage orienté objet. Mon but était de m'assurer que toute utilisation de références devrait être absolument sûre, avec une vérification effectuée automatiquement par le compilateur. Mais je n'ai pas pu résister à la tentation de mettre une référence nulle, simplement parce que c'était si facile à mettre en œuvre. Cela a conduit à d'innombrables erreurs, vulnérabilités, et des accidents de 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 élaborer une tempête de merde à propos de null en général. Je ne suis pas d'accord avec l'affirmation selon laquelle undefined est un autre null , mais cela vaut quand même la peine d'être lu. Et il explique, pourquoi vous devriez éviter null du tout et pas seulement dans le cas où vous avez demandé. L'essentiel est que null est dans n'importe quelle langue un cas particulier. Vous devez penser à null comme une exception. undefined est différent de cette façon, ce code traitant du comportement non défini est dans la plupart des cas juste un bug. C et la plupart des autres langues ont également un comportement non défini, mais la plupart d'entre eux n'ont pas d'identificateur pour cela dans la langue.

2
répondu ceving 2017-12-04 16:27:09

du point de vue de la gestion de la complexité, un objectif de génie logiciel primaire, nous voulons éviter de propager inutile complexité cyclomatique aux clients d'une API. Retourner un null au client est comme leur retourner le coût de la complexité cyclomatique d'une autre branche de code.

(cela correspond à une charge d'essai unitaire. Vous devez écrire un test pour le cas de retour nul, en plus de la déclaration de collecte vide cas.)

1
répondu dthal 2018-06-20 18:03:18