Is it better to return null or empty collection?

前端 未结 18 1620
我在风中等你
我在风中等你 2020-11-22 07:56

That\'s kind of a general question (but I\'m using C#), what\'s the best way (best practice), do you return null or empty collection for a method that has a collection as a

相关标签:
18条回答
  • 2020-11-22 08:25

    Think always in favor of your clients (which are using your api):

    Returning 'null' very often makes problems with clients not handling null checks correctly, which causes a NullPointerException during runtime. I have seen cases where such a missing null-check forced a priority production issue (a client used foreach(...) on a null value). During testing the problem did not occur, because the data operated on was slightly different.

    0 讨论(0)
  • 2020-11-22 08:26

    Returning an empty collection is better in most cases.

    The reason for that is convenience of implementation of the caller, consistent contract, and easier implementation.

    If a method returns null to indicate empty result, the caller must implement a null checking adapter in addition to enumeration. This code is then duplicated in various callers, so why not to put this adapter inside the method so it could be reused.

    A valid usage of null for IEnumerable might be an indication of absent result, or an operation failure, but in this case other techniques should be considered, such as throwing an 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());
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 08:26

    I call it my billion-dollar mistake…At that time, I was designing the first comprehensive type system for references in an object-oriented language. My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. – Tony Hoare, inventor of ALGOL W.

    See here for an elaborate shit storm about null in general. I do not agree with the statement that undefined is another null, but it is still worth reading. And it explains, why you should avoid null at all and not just in the case you have asked. The essence is, that null is in any language a special case. You have to think about null as an exception. undefined is different in that way, that code dealing with undefined behavior is in most cases just a bug. C and most other languages have also undefined behavior but most of them have no identifier for that in the language.

    0 讨论(0)
  • 2020-11-22 08:27

    Returning null could be more efficient, as no new object is created. However, it would also often require a null check (or exception handling.)

    Semantically, null and an empty list do not mean the same thing. The differences are subtle and one choice may be better than the other in specific instances.

    Regardless of your choice, document it to avoid confusion.

    0 讨论(0)
  • 2020-11-22 08:27

    I would argue that null isn't the same thing as an empty collection and you should choose which one best represents what you're returning. In most cases null is nothing (except in SQL). An empty collection is something, albeit an empty something.

    If you have have to choose one or the other, I would say that you should tend towards an empty collection rather than null. But there are times when an empty collection isn't the same thing as a null value.

    0 讨论(0)
  • 2020-11-22 08:33

    We had this discussion among the development team at work a week or so ago, and we almost unanimously went for empty collection. One person wanted to return null for the same reason Mike specified above.

    0 讨论(0)
提交回复
热议问题