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


420

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


5
ええと、CPerkinsではありません。 rads.stackoverflow.com/amzn/click/0321545613

3
@CPerkins-はい、あります。これは、Microsoft独自の.NETフレームワーク設計ガイドラインに明確に記載されています。詳細については、RichardODの回答を参照してください。
グレッグビーチ、

51
意味が「結果を計算できない」の場合のみ、nullを返します。Nullは「空」のセマンティクスを持つことはなく、「欠落」または「不明」のみを含む必要があります。テーマに関する私の記事で詳細:blogs.msdn.com/ericlippert/archive/2009/05/14/...
エリックリペット

3
Bozho:「どれでも」というのはひどい言語です。空のリストがnull値と完全に等しいCommon Lispはどうですか?:-)
Ken

9
実際、「複製」とは、コレクションではなくオブジェクトを返すメソッドに関するものです。それは、異なる答えを持つ異なるシナリオです。
GalacticCowboy

回答:


499

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

これは最悪です:

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

nullコレクションまたは列挙型を返すときに決して戻ることはベストプラクティスと見なされています。 常に空の列挙可能/コレクションを返します。それは前述のナンセンスを防ぎ、あなたの車があなたのクラスの同僚やユーザーに騙されるのを防ぎます。

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

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>()たとえば、新しい空のコレクションまたは配列を返すよりも、使用する方が効率的です。


24
私はウィルに同意しますが、「常に」は少し過剰だと思います。空のコレクションは「0アイテム」を意味するかもしれませんが、Nullを返すことは「まったくコレクションを持たない」ことを意味する可能性があります。HTMLを解析する場合、id = "foo"を含む<ul>を検索すると、<ul id = "foo"> </ ul>は空のコレクションを返す可能性があります。id = "foo"の<ul>がない場合は、nullを返すほうが適切です(例外を使用してこのケースを処理する場合を除く)
Patonza

30
「空の配列を簡単に返すことができるか」という問題ではなく、現在のコンテキストで空の配列が誤解を招く可能性があるかどうかが問題になります。空の配列は実際にはnullと同様に何かを意味します。nullではなく常に空の配列を返す必要があると言うのは、ブールメソッドが常にtrueを返す必要があると言っているのとほとんど同じです。両方の可能な値は意味を伝えます。
David Hedlund、

31
実際には、新しいFoo [0]ではなくSystem.Linq.Enumerable.Empty <Foo>()を返すことをお勧めします。それはより明示的で、メモリ割り当てを1つ節約します(少なくとも、インストールした.NET実装では)。
Trillian、

4
@Will:OP: " 戻り値の型としてコレクションを持つメソッドに対して、nullまたは空のコレクションを返しますか"。私はそれがだかどうか、この場合だと思うIEnumerableか、ICollectionそれほど問題ではありません。とにかく、あなたが何かのタイプを選択すると、ICollectionそれらも返されnullます...空のコレクションを返してもらいたいのですが、私はそれらに戻って出くわしたnullので、ここでそれについて言及したいと思いました。enumerableのコレクションのデフォルトはnullではなく空だと思います。そんなにデリケートなテーマだとは知りませんでした。
Matthijs Wessels 2011年


154

Frameworkの設計ガイドライン第2版(PG 256。):

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

nullを返さないことの利点に関する興味深い記事をもう1つ紹介します(私はBrad Abramのブログで何かを見つけようとしており、彼はその記事にリンクしています)。

編集 -Eric Lippertが元の質問にコメントしたので、彼の優れた記事にリンクしたいと思います


33
+1。非常に正当な理由がない限り、フレームワークの設計ガイドラインに従うことが常にベストプラクティスです。

@まあ、絶対に。それは私が従うものです。私は他に何かする必要を見つけたことはありません
RichardOD

うん、それはあなたが従うものです。しかし、あなたが依存しているAPIがそれに従わない場合、あなたは「罠にかけられます」;)
Bozho

@ Bozho-ええ。あなたの答えはそれらのフリンジケースのいくつかの素晴らしい答えを提供します。
RichardOD 09

90

あなたの契約とあなたの具体的なケースに依存します。通常は空のコレクションを返すのが最善ですが、場合によっては(まれに)次のようになります。

  • null より具体的なものを意味するかもしれません。
  • API(契約)により、強制的に戻る必要がある場合がありますnull

いくつかの具体的な例:

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

4
@ Bozho-ここにいくつかの興味深い例があります。
RichardOD 09

1
他の障害を中心にコードを構築する必要はないと私は思いますが、c#の「null」は特定のことを意味することはありません。値nullは「情報なし」と定義されているため、特定の情報が含まれているとはオキシモロンです。そのため、.NETガイドラインでは、セットが実際に空の場合は空のセットを返す必要があると規定されています。nullを返すと、「予想されるセットがどこに行ったのかわからない」
ルーンFS

13
いいえ、「セットに要素がない」ではなく「セットがない」を意味します
Bozho

以前はそれを信じていたので、TSQLをたくさん書く必要があり、いつもそうであるとは限らないことを学びました。

1
Object-To-Xmlのある部分が
注目さ

36

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

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

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


1
技術的には、これが空のコレクションを返すとは思いません。
FryGuy 2009

3
@FryGuy-いいえ、ありません。これは、Enumerableオブジェクトを返します。このオブジェクトのGetEnumerator()メソッドは、(コレクションの分析によって)空のEnumeratorを返します。つまり、列挙子のMoveNext()メソッドは常にfalseを返します。サンプルメソッドを呼び出してもnullは返されません。また、GetEnumerator()メソッドがnullを返すEnumerableオブジェクトも返されません。
Jeffrey L Whitledge、2009

31

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

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

Enumerable.Empty<Element>()

2
きちんとしたトリックをありがとう。私はばかのように空のList <T>()をインスタンス化していましたが、これはかなりきれいに見え、おそらくもう少し効率的でもあります。
ジェイコブスデータソリューション

18

私はあなたが文脈上意味的に正しい値を返すべきだと思っています。「常に空のコレクションを返す」というルールは、私には少し単純すぎるようです。

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

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


サンダースに返信:

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

REには2つの別個の機能があります。「何かありますか?」そして「もしそうなら、それは何ですか?」はい、確かにそれが可能ですが、なぜあなたはそれをしたいのですか?これで、呼び出し側プログラムは、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の戻り値で行うか別の関数で行うかは、より大きな原則に違いはありません。

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

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

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


4
「返答なし」と「ゼロ」の応答を同じ戻り値で組み合わせる必要があるのはなぜですか?代わりに、メソッドに「過去5年間の以前の入院」を返し、「以前の入院リストは記入されていませんでしたか?」これは、以前の入院で埋めリスト、およびに満たされていないリストの違いはあります前提としています。
ジョン・サンダース

2
しかし、nullを返すと、とにかく呼び出し元に余分な負担がかかります。呼び出し側はすべての戻り値のnullをチェックする必要があります-恐ろしいDRY違反です。「呼び出し元が質問に答えなかった」ことを個別に示す場合、追加のメソッド呼び出しを作成して事実を示す方簡単です。それか、またはDeclinedToAnswerの追加プロパティを持つ派生コレクション型を使用します。nullを返さないというルールは決して恣意的ではありません。それは最小サプライズの原則です。また、メソッドの戻り値の型は、その名前が示すとおりです。ヌルはほぼ間違いなくそうではありません。
jammycakes

2
getHospitalizationListが1つの場所からのみ呼び出されること、および/またはそのすべての呼び出し元が「応答なし」と「ゼロ」のケースを区別することを望んでいると想定しています。そこでしょうあなたは、これは必要ではないはずの場所ではnullチェックを追加するためにそれらを強制されているので、あなたの呼び出し側がその区別をする必要がある、としません(ほぼ確実に過半数)の場合も。これはコードベースに重大なリスクを追加します。人々はそうすることを簡単に忘れがちであり、コレクションではなくnullを返す正当な理由がほとんどないため、これはより可能性が高くなります。
jammycakes

3
名前の再説明:関数名は、関数と同じ長さでない限り、関数の機能を完全に表すことができないため、非常に実用的ではありません。しかし、いずれにしても、答えが与えられなかったときに関数が空のリストを返す場合、同じ理由で、それは「getHospitalizationListOrEmptyListIfNoAnswer」と呼ばれるべきではありませんか?しかし、実際には、Java Reader.read関数の名前をreadCharacterOrReturnMinusOneOnEndOfStreamに変更する必要があると主張しますか?そのResultSet.getIntは本当に「getIntOrZeroIfValueWasNull」である必要がありますか?その他
Jay

4
REすべての呼び出しは区別したい:まあ、はい、私は、少なくとも呼び出し元の作成者は彼が気にしない意識的な決定を行うべきであると想定しています。関数が「わからない」の空のリストを返し、呼び出し側がこの「なし」を盲目的に扱う場合、深刻な不正確な結果が生じる可能性があります。関数が「getAllergicReactionToMedicationList」だったと想像してください。「患者はアレルギー反応が知られていない」として盲目的に「リストには入れられなかった」と扱ったプログラムは文字通り患者を殺すことになるかもしれない。他の多くのシステムでも、それほど劇的な結果ではない場合でも、同様の結果が得られます。...
ジェイ

10

空のコレクションが意味的に意味をなす場合は、それを返すことをお勧めします。空のコレクションを返すGetMessagesInMyInbox()ことは「本当に受信トレイにメッセージがない」ことを伝えますが、返すnullことは、返される可能性のあるリストがどのように見えるべきかを示すのに十分なデータが利用できないことを伝えるのに役立ちます。


6
あなたが与える例では、nullを返すだけでなく、リクエストを実行できない場合、メソッドはおそらく例外をスローするはずです。例外は、nullよりも問題の診断にはるかに役立ちます。
グレッグブナ

ええ、null確かに、受信トレイの例では、値は確かに妥当ではないように見えます。私はその値についてより一般的な用語で考えていました。例外は、何かがおかしいという事実を伝えるのにも最適ですが、参照された「不十分なデータ」が完全に予期される場合、例外をスローすると、設計が不十分になります。私はむしろ、メソッドが応答を計算できないこともあり、完全に可能でエラーがまったくないシナリオを考えています。
David Hedlund、

6

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

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

選択に関係なく、混乱を避けるために文書化してください。


8
APIの設計の正確さを検討する場合、効率が要因になることはほとんどありません。グラフィックスプリミティブなどの非常に特殊なケースではそうなるかもしれませんが、リストや他のほとんどの高レベルのものを扱うときは、私はそれを非常に疑っています。
グレッグビーチ、

特にこの「最適化」を補正するためにAPIユーザーが作成する必要があるコードが、最初により良いデザインが使用された場合よりも非効率的である可能性があることを考えると、Gregに同意します。
Craig Stuntz、2009

同意し、ほとんどの場合、最適化する価値はありません。空のリストは、最新のメモリ管理で事実上無料です。
Jason Baker、


4

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

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

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


4

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

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


4

常にあなたのクライアント(あなたのAPIを使用しています)を支持して考えてください:

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


3

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

ここでケースを検討してください。

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

私は簡単に使用することができますListCustomerAccountし、FindAll代わりに、

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。を返さないため、直接使用できます。


2

1週間ほど前に開発チームの間でこの議論があり、ほぼ全員が空のコレクションを求めていました。Mikeが上記で指定したのと同じ理由で、1人がnullを返したいと考えました。


2

空のコレクション。C#を使用している場合は、システムリソースの最大化は必須ではないと想定されます。効率は落ちますが、関係するプログラマにとっては空のコレクションを返す方がはるかに便利です(理由は上記で概説します)。


2

ほとんどの場合、空のコレクションを返す方が適切です。

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

メソッドが空の結果を示すために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

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

一般的なことについての精巧なたわごとの嵐については、ここを参照してくださいnull。私undefinedは別の声明に同意しませんnullが、それはまだ読む価値があります。そして、それは、nullあなたが尋ねた場合だけでなく、なぜあなたがまったく避けるべきなのかを説明しています。本質は、nullどの言語でも特別なケースです。あなたはnull例外として考える必要があります。undefinedその点で異なり、未定義の動作を処理するコードはほとんどの場合単なるバグです。Cと他のほとんどの言語にも未定義の動作がありますが、それらのほとんどは言語の中でそれを識別するためのものではありません。


1

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.