web-dev-qa-db-ja.com

nullまたは空のコレクションを返す方が良いですか?

それは一種の一般的な質問です(ただし、私はC#を使用しています)、最善の方法は何ですか(ベストプラクティス)、戻り値の型としてコレクションを持つメソッドに対してnullまたは空のコレクションを返しますか?

382
Omu

空のコレクション。常に。

これは吸う:

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

コレクションまたは列挙型を返すときは、nullを返さないようにすることをお勧めします。 ALWAYS空の列挙型/コレクションを返します。これは、前述のナンセンスを防ぎ、同僚やクラスのユーザーによって車がエッグされるのを防ぎます。

プロパティについて話すときは、必ず一度プロパティを設定して忘れてください

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

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

.NET 4.6.1では、これを非常に多く凝縮できます。

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

列挙型を返すメソッドについて話すとき、null...の代わりに空の列挙型を簡単に返すことができます...

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

Enumerable.Empty<T>() を使用すると、たとえば新しい空のコレクションまたは配列を返すよりも効率的であると見なすことができます。

457
Will

Framework Design Guidelines 2nd Edition (pg。256)から:

コレクションプロパティまたはコレクションを返すメソッドからnull値を返さないでください。代わりに、空のコレクションまたは空の配列を返します。

Nullを返さないことの利点に関する別の興味深い記事があります(私はBrad Abramのブログで何かを見つけようとしていましたが、彼は記事にリンクしていました)。

Edit-Eric Lippertが元の質問にコメントしたので、私も 彼の素晴らしい記事へのリンク にしたいと思います。

148
RichardOD

contractおよびyourconcrete caseに依存します。一般的には空のコレクションを返すのが最善です、しかし時々(まれに):

  • nullは、より具体的なものを意味する場合があります。
  • aPI(契約)がnullを強制的に返す場合があります。

いくつかの具体的な例:

  • uIコンポーネント(コントロール外のライブラリから)は、空のコレクションが渡された場合は空のテーブルをレンダリングし、nullが渡された場合はテーブルをまったくレンダリングしません。
  • object-to-XML(JSON/whatever)では、nullは要素が欠落していることを意味しますが、空のコレクションは冗長(および場合によっては誤った)<collection />をレンダリングします
  • nullを返す/渡す必要があることを明示的に示すAPIを使用または実装している
86
Bozho

まだ言及されていないもう1つのポイントがあります。次のコードを検討してください。

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

このメソッドを呼び出すと、C#言語は空の列挙子を返します。したがって、言語設計と一致するように(したがって、プログラマーが期待するように)空のコレクションを返す必要があります。

36

空ははるかに消費者に優しい。

空の列挙型を作成する明確な方法があります。

Enumerable.Empty<Element>()
30
George Polevoy

どういう意味であれ、文脈上意味的に正しい値を返す必要があるように思えます。 「常に空のコレクションを返す」というルールは、少し単純化されているように思えます。

たとえば、病院のシステムに、過去5年間の以前のすべての入院のリストを返す機能があるとします。顧客が病院にいなかった場合、空のリストを返すことは理にかなっています。しかし、顧客がアドミタンスフォームのその部分を空白のままにするとどうなりますか? 「空のリスト」と「回答なし」または「わからない」を区別するには、異なる値が必要です。例外をスローすることもできますが、それは必ずしもエラー状態ではなく、必ずしも通常のプログラムフローから抜け出すわけではありません。

ゼロと無回答を区別できないシステムにイライラすることがよくあります。システムからいくつかの数値の入力を求められ、ゼロを入力すると、このフィールドに値を入力する必要があることを示すエラーメッセージが表示されることが何度もありました。私はちょうどやった:私はゼロを入力しました!しかし、無回答と区別できないため、ゼロは受け入れません。


サンダースへの返信:

はい、「人が質問に答えなかった」と「答えはゼロでした」との間に違いがあると仮定しています。それが私の答えの最後の段落のポイントでした。多くのプログラムは、「わからない」を空白またはゼロと区別することができません。これは、潜在的に深刻な欠陥のようです。たとえば、私は1年ほど前に家を買いました。私は不動産のウェブサイトに行きましたが、価格が0ドルの多くの家がリストされていました。私にはかなり良さそうに聞こえました:彼らはこれらの家を無料で配っています!しかし、悲しい現実は、彼らがただ価格を入力しなかったということだと確信しています。その場合、「まあ、明らかにゼロは、彼らが価格を入力しなかったことを意味します-誰も無料で家を譲るつもりはない」と言うかもしれません。しかし、このサイトには、さまざまな町の住宅の平均売買価格も記載されていました。平均にゼロが含まれていなかったため、一部の場所で誤って低い平均が得られていたのではないかと思いました。つまり、平均100,000ドルです。 120,000ドル。と「知らない」?技術的には、答えは「わからない」です。おそらく本当に見たいのは110,000ドルです。しかし、おそらく73,333ドルであり、これは完全に間違っています。また、ユーザーがオンラインで注文できるサイトでこの問題が発生した場合はどうなりますか? (不動産の場合はほとんどありませんが、他の多くの製品で行われているのを見たことがあると思います。)「価格がまだ指定されていない」を「無料」と解釈したいのでしょうか。

2つの独立した機能を持つRE、「何かありますか?」そして「もしそうなら、それは何ですか?」はい、あなたは確かにそれを行うことができますが、なぜしたいのですか?ここで、呼び出し側プログラムは1つではなく2つの呼び出しを行う必要があります。プログラマが「any」の呼び出しに失敗するとどうなりますか?そして、それは何ですか? ?プログラムは誤解を招くゼロを返しますか?例外を投げますか?未定義の値を返しますか?より多くのコード、より多くの作業、より多くの潜在的なエラーを作成します。

私が見る唯一の利点は、任意のルールに準拠できることです。このルールに従うのに苦労する価値があるこのルールには利点がありますか?そうでない場合、なぜわざわざ?


Jammycakesへの返信:

実際のコードがどのようになるかを検討してください。私は質問がC#と言ったのを知っていますが、Javaを書くならすみません。私のC#はあまりシャープではなく、原理は同じです。

戻り値がnullの場合:

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

別の機能を使用する場合:

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

実際には、nullリターンを含む1行または2行少ないコードなので、呼び出し側の負担は大きくありません。

DRYの問題がどのように発生するかわかりません。呼び出しを2回実行する必要はありません。リストが存在しないときに常に同じことをしたい場合は、呼び出し元に実行させるのではなく、get-list関数に処理をプッシュダウンすることができます。そのため、呼び出し元にコードを置くことはDRY違反。しかし、私たちはほぼ確実に同じことを常にしたくありません。処理するリストが必要な関数では、リストが見つからないとエラーが発生し、処理が停止する可能性があります。ただし、編集画面では、まだデータを入力していない場合は処理を停止したくありません。データを入力できるようにします。したがって、「リストなし」の処理は、何らかの方法で呼び出し元レベルで実行する必要があります。そして、null returnでそれを行うか、別の関数で行うかは、より大きな原理に違いはありません。

もちろん、呼び出し側がnullをチェックしない場合、プログラムはnullポインタ例外で失敗する可能性があります。しかし、別の「任意の」関数があり、呼び出し元がその関数を呼び出さず、盲目的に「リストの取得」関数を呼び出す場合、どうなりますか?例外をスローするか、そうでなければ失敗する場合、それは、それがnullを返し、それをチェックしなかった場合に起こることとほとんど同じです。空のリストが返される場合、それは間違っています。 「要素がゼロのリストがあります」と「リストがありません」を区別できません。ユーザーが価格を入力しなかったときに、価格にゼロを返すようなものです。それは単に間違っています。

コレクションに追加の属性を付加するとどのように役立つかわかりません。呼び出し元はまだ確認する必要があります。 nullをチェックするよりも優れているのでしょうか。繰り返しになりますが、起こりうる絶対的な最悪の事態は、プログラマーがそれをチェックするのを忘れて、誤った結果を出すことです。

Nullを返す関数は、プログラマーが「値を持たない」というnullの概念に精通していれば、驚くことではありません。別の機能を持つことは、「驚き」の問題だと思います。プログラマーがAPIに慣れていない場合、データなしでテストを実行すると、ときどきヌルが返されることがすぐにわかります。しかし、そのような機能が存在する可能性があり、ドキュメンテーションをチェックし、ドキュメンテーションが完全でわかりやすい場合を除き、どのようにして彼は別の機能の存在を発見しますか?両方を呼び出して、覚えておく必要がある2つの関数ではなく、常に意味のある応答を返す1つの関数が必要です。

18
Jay

空のコレクションが意味的に理にかなっている場合、それは私が返すことを好むものです。 GetMessagesInMyInbox()の空のコレクションを返すことは、「受信ボックスにメッセージが実際にない」ことを伝えますが、nullを返すことは、返されるリストがどのように見えるかを示すのに十分なデータがないことを伝えるのに役立ちます。

10
David Hedlund

Null Object Pattern の背後にある理由は、空のコレクションを返すことを支持する理由に似ていると主張できます。

6
Dan

新しいオブジェクトは作成されないため、nullを返す方が効率的です。ただし、多くの場合、nullチェック(または例外処理)も必要になります。

意味的に、nullと空のリストは同じことを意味しません。違いは微妙であり、特定の場合、1つの選択が他の選択よりも優れている場合があります。

選択に関係なく、混乱を避けるために文書化します。

6
Karmic Coder

状況に依存します。特殊な場合は、nullを返します。関数がたまたま空のコレクションを返す場合、明らかにそれを返すことは問題ありません。ただし、無効なパラメーターまたはその他の理由のために特別なケースとして空のコレクションを返すことは、特別なケースの条件をマスクしているため、良いアイデアではありません。

実際、この場合、私は通常、例外をスローして、それが本当に無視されないことを確認することを好みます:)

Null条件を処理する必要がないため、(空のコレクションを返すことで)コードをより堅牢にするというのは悪いことです。これは、呼び出し元のコードで処理する必要がある問題を単純にマスクしているためです。

4
Larry Watanabe

nullは空のコレクションと同じものではないので、返されるものを最も適切に表すものを選択する必要があります。ほとんどの場合、nullは何もありません(SQLを除く)。空のコレクションは空のコレクションですが、空のコレクションです。

どちらかを選択する必要がある場合は、nullではなく空のコレクションを使用する傾向があります。ただし、空のコレクションがnull値と同じではない場合があります。

4
Jason Baker

クライアント(APIを使用しているクライアント)を常に優先して考えてください。

「null」を返すと、クライアントがnullチェックを正しく処理しないという問題が非常に多く発生し、実行時にNullPointerExceptionが発生します。私は、そのような欠落したnullチェックが優先的な生産問題を強制するケースを見てきました(クライアントがnull値でforeach(...)を使用しました)。操作中のデータがわずかに異なるため、テスト中に問題は発生しませんでした。

4
manuel aldana

ここで、適切な例を挙げて説明します。

ここのケースを考えてみてください。

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

ここで私が使用している機能を考えてみてください..

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

代わりにListCustomerAccountFindAllを簡単に使用できます。

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();
}

注:AccountValueはnullではないため、Sum()関数はnullを返しません。したがって、直接使用できます。

私はそれを10億ドルの間違いと呼んでいます…その時、私はオブジェクト指向言語での参照のための最初の包括的な型システムを設計していました。私の目標は、コンパイラによって自動的に実行されるチェックを使用して、参照のすべての使用が絶対に安全であることを保証することでした。しかし、実装が非常に簡単だったという理由だけで、null参照を挿入する誘惑に抵抗することはできませんでした。これにより、無数のエラー、脆弱性、およびシステムクラッシュが発生し、過去40年で数十億ドルの痛みと損害を引き起こしたと考えられます。 –トニー・ホア、ALGOL Wの発明者。

一般的にnullに関する手の込んだたわごとについては、 here を参照してください。 undefinedが別のnullであるというステートメントには同意しませんが、それでも読む価値はあります。そして、あなたが尋ねた場合だけでなく、なぜnullを避けるべきなのかを説明しています。本質は、nullはどの言語でも特別な場合です。 nullを例外として考える必要があります。 undefinedはその点で異なり、未定義の動作を処理するコードはほとんどの場合、単なるバグです。 Cおよび他のほとんどの言語にも未定義の動作がありますが、ほとんどの言語にはその言語の識別子がありません。

2
ceving

私たちは1週間ほど前に職場の開発チームでこの議論を行い、ほぼ満場一致で空のコレクションを探しました。ある人は、マイクが上記で指定したのと同じ理由でnullを返したいと思っていました。

2
Henric

空のコレクション。 C#を使用している場合、システムリソースの最大化は必須ではないという前提があります。空のコレクションを返すことは、効率は劣りますが、関係するプログラマーにとってははるかに便利です(上記で概説した理由により)。

2
mothis

ほとんどの場合、空のコレクションを返すことをお勧めします。

その理由は、呼び出し元の実装の利便性、一貫した契約、およびより簡単な実装です。

メソッドがnullを返して空の結果を示す場合、呼び出し元は列挙に加えてnullチェックアダプターを実装する必要があります。このコードはさまざまな呼び出し元で複製されるため、このアダプターをメソッド内に配置して再利用できるようにしてください。

IEnumerableのnullの有効な使用法は、結果の欠如または操作の失敗を示している可能性がありますが、この場合、例外をスローするなど、他の手法を検討する必要があります。

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());
        }
    }
}
2
George Polevoy

ソフトウェアエンジニアリングの主要な目的である複雑さを管理するという観点から、APIのクライアントに不要循環的複雑さを伝播することは避けたいと思います。クライアントにnullを返すことは、別のコードブランチの循環的複雑性コストをクライアントに返すようなものです。

(これは単体テストの負担に相当します。空のコレクションリターンケースに加えて、ヌルリターンケースのテストを記述する必要があります。)

1
dthal