369

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


  • うーん、かなりCPerkinsではないrads.stackoverflow.com/amzn/click/0321545613 - Will
  • @CPerkins - はい、あります。 Microsoftの.NET Framework設計ガイドラインに明確に記載されています。詳細についてはRichardODの回答をご覧ください。 - Greg Beech
  • 意味が「結果を計算できない」の場合のみnullを返すべきですかNullは決して「空」の意味を持つべきではなく、「欠けている」だけです。または「不明」です。この件に関する私の記事の詳細:blogs.msdn.com/ericlippert/archive/2009/05/14/… - Eric Lippert
  • Bozho:" any"ひどい言語です。空のリストがnull値と正確に等しいCommon Lispについてはどうですか? :-) - Ken
  • 実際には、「重複」はコレクションではなくオブジェクトを返すメソッドに関するものです。回答が異なる別のシナリオです。 - GalacticCowboy

18 답변


443

空のコレクション常に。

これは吸う:

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


  • 私は意志に同意しますが、私は「いつも」と思います。やや過剰です。空のコレクションは「0項目」を意味しますが、Nullを返すことは「コレクションがまったくない」ことを意味します。 - 例えば。 HTMLを解析している場合は、< ul>を探します。 id = "foo"の場合、
      Patonza
    • 「空の配列を簡単に返すことができるかどうか」ではなく、現在のコンテキストで空の配列が誤解を招く可能性があるかどうかという問題ではありません。空の配列は、nullと同様に、実際には何かを意味します。 nullではなく常に空の配列を返すようにするということは、ブール型のメソッドが常にtrueを返すようにするのと同じくらい誤解されやすいです。両方の可能な値は意味を伝えます。 - David Hedlund
    • 新しいFoo [0]ではなく、System.Linq.Enumerable.Empty< Foo>()を返すことを実際にお勧めします。これはより明確で、(少なくとも、私がインストールした.NETの実装では)1つのメモリ割り当てを節約できます。 - Trillian
    • @Will:OP:"に対してnullまたは空のコレクションを返しますか方法これは、戻り型としてコレクションを持っています。この場合、問題ないかと思いますIEnumerableまたはICollectionそれほど重要ではありません。とにかく、あなたがタイプの何かを選択すればICollection彼らはまた戻るnull...私は彼らに空のコレクションを返させたいのですが、私は彼らに遭遇して戻りましたnullそのため、ここで言及するだけと思います。列挙型のコレクションのデフォルトはnullではなく空です。私はそれがそんなに敏感な話題であることを知りませんでした。 - Matthijs Wessels
    • 関連(CodeProject記事): 本当にnullの代わりに空のリストを返すほうが良いですか。 - Krumia

147

からフレームワーク設計ガイドライン第2版(256ページ):

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

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

編集 - Eric Lippertが最初の質問にコメントしたので、私もそうしたいと思います彼の優れた記事へのリンク


  • +1。あなたが非常に非常に良い理由がない限り、フレームワークデザインガイドラインに従うことは常にベストプラクティスです。 - Will
  • @なるほど、絶対に。それが私が従うものです。他に必要があることがわかりませんでした - RichardOD
  • うん、それがあなたが従うものだ。しかし、信頼していないAPIがそれに従わない場合、あなたはトラップされていることになります。 ;) - Bozho
  • @ Bozho - はい。あなたの答えはそれらのフリンジケースの良い答えをいくつか提供します。 - RichardOD

84

あなたに依存契約するそしてあなたのコンクリートケース。 一般に空のコレクションを返すのが最善です、 でも時々 (めったに):

  • nullより具体的な何かを意味するかもしれません。
  • あなたのAPI(契約)はあなたに戻ることを強制するかもしれませんnull

いくつかの具体例:

  • UIコンポーネント(コントロール外のライブラリから)は、空のコレクションが渡された場合は空のテーブルをレンダリングし、nullが渡された場合はまったくテーブルをレンダリングしない可能性があります。
  • Object-to-XML(JSON /その他)では、null空のコレクションは冗長になります(そしておそらく不正確になるでしょう)<collection />
  • nullを返す/渡すべきであると明示的に述べているAPIを使用または実装している


  • @ Bozho - ここにいくつかの興味深い例があります。 - RichardOD
  • 他の問題を回避してコードを作成する必要があるとは思わず、定義上はnullにしないでください。 C#で決して具体的な何かを意味します。値nullは、「情報なし」として定義されます。そしてそれゆえにそれが特定の情報を運ぶと言うことはオキシモロンです。そのため、.NETのガイドラインでは、セットが実際に空の場合は空のセットを返すように指定されています。 nullを返すことは、「予想されるセットがどこに移動したのかわからない」ということです。 - Rune FS
  • いいえ、「セットがありません」という意味です。 「セットに要素がありません」ではなく、 - Bozho
  • 私はそれを信じていました、それから私はたくさんのTSQLを書かなければなりませんでした、そして、それがいつもそうであるというわけではないことを学びました、heheh。 - Will
  • Object-To-Xmlのある部分がスポットです - Mihai Caracostea

34

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

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

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


  • 空のお気に入りのエモ曲の場合は+1。 - Jesse C. Slicer
  • +1。ジェフリーいい例です。 - RichardOD
  • この例は私をとても悲しくします... - Ken
  • 技術的には、これが空のコレクションを返すとは思わない。 - FryGuy
  • @FryGuy - いいえ、違います。これはEnumerableオブジェクトを返します。そのGetEnumerator()メソッドは、(コレクションとの比較で)空のEnumeratorを返します。つまり、EnumeratorのMoveNext()メソッドは常にfalseを返します。この例のメソッドを呼び出してもnullは返されず、GetEnumerator()メソッドがnullを返すEnumerableオブジェクトも返されません。 - Jeffrey L Whitledge

29

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

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

Enumerable.Empty<Element>()


  • きちんとしたトリックをありがとう。私はバカのように空のList< T>()をインスタンス化していましたが、これはずっときれいに見え、おそらくもう少し効率的です。 - Repo Man

18

それが何であれ、文脈上意味的に正しい値を返すべきだと私には思えます。 「常に空のコレクションを返す」という規則は、私には少し単純に思えます。

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

私はしばしばゼロと無回答を見分けることができないシステムに失望しました。私は何度かシステムが私にいくつかの数を入力するように頼んだ、私はゼロを入力する、そして私は私がこのフィールドに値を入力しなければならないことを伝えるエラーメッセージを得る。私はちょうどしました:私はゼロに入りました!しかし、無回答と区別できないため、ゼロは受け入れられません。


Saundersへの返信:

はい、「質問に答えなかった」と「答えはゼロでした」の間に違いがあると思います。それが私の答えの最後の段落の要点でした。多くのプログラムでは、「わからない」と空白またはゼロを区別できません。これは私にとって潜在的に深刻な欠陥です。たとえば、私は1年ほど前に家のために買い物をしていました。私は不動産ウェブサイトに行きました、そして、0ドルの提示価格でリストされた多くの家がありました。私にはかなり良いと思いました:彼らはこれらの家を無料で配っています!しかし、悲しい現実は、彼らがただ価格を入力していなかったということでした。そのような場合、「まあ、明らかにゼロは彼らが価格を入力しなかったことを意味します - 誰もが無料で家を譲るつもりはありません」と言うかもしれません。しかし、このサイトには、さまざまな町の住宅の平均売買価格も記載されています。平均にゼロが含まれていなかったのではないかと思いますが、平均が誤って低い場所もあります。つまり、平均$ 100,000です。 120,000ドル。そして「知らない」?技術的には、答えは「わからない」です。私たちが本当に見たいと思うものは11万ドルです。しかし、私たちがおそらく得るのは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 ...
}

これは実際には1行または2行少ない戻り値のコードなので、呼び出し側にはそれ以上の負担はかかりません。

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

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

コレクションに追加の属性を追加することがどのように役立つのかわかりません。呼び出し側はまだそれをチェックしなければなりません。それはnullをチェックするよりもどうですか?繰り返しますが、起こり得る絶対的な最悪の事態は、プログラマーがそれをチェックするのを忘れて間違った結果を出すことです。

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


  • 「回答なし」を組み合わせる必要があるのはなぜですか。および「ゼロ」同じ戻り値で応答?代わりに、メソッドに「過去5年間の過去の入院歴」を返し、別のメソッドで「以前の入院リストはいっぱいになったことがあるかどうか」と尋ねるようにします。前回の入院なしで記入されたリストと記入されていないリストの間に違いがあることを前提としています。 - John Saunders
  • しかし、もしあなたがnullを返すなら、あなたはとにかくすでに呼び出し側に余分な負担をかけています!呼び出し側はすべての戻り値にnullがないかどうかをチェックする必要があります。これはひどいDRY違反です。 "発信者が質問に回答しなかった"を指定したい場合は、別に、それです事実を示すために追加のメソッド呼び出しを作成する方が簡単です。どちらか、またはDeclinedToAnswerの追加プロパティを持つ派生コレクション型を使用します。決してnullを返さないというルールはまったく恣意的なものではありません、それは最小サプライズの原則です。また、メソッドの戻り値の型は、その名前が示すとおりの意味になります。 Nullはほぼ確実にそうではありません。 - jammycakes
  • あなたは、あなたのgetHospitalizationListが一箇所からしか呼ばれていないこと、および/またはそのすべての発呼者が"返事なし"を区別したいと思うだろう。および「ゼロ」ケース。そこ意志あなたの呼び出し側がその区別をする必要がないケース(ほとんど間違いなく大多数)であるので、あなたはこれが必要ではないはずの場所にnullチェックを追加することを強制しています。これはあなたのコードベースに重大なリスクを追加します。正当なコレクションの代わりにnullを返す理由は、これがはるかに可能性があります。 - jammycakes
  • RE the name:関数と同じ長さでない限り、関数名がその関数が何をするのかを完全に記述することはできません。したがって、実際的には実用的ではありません。しかし、いずれにせよ、答えが与えられなかったときに関数が空のリストを返す場合、同じ理由で、それが「getHospitalizationListOrEmptyListIfNoAnswer」と呼ばれるべきではありませんか?しかし、実際には、Java Reader.read関数の名前をreadCharacterOrReturnMinusOneOnEndOfStreamに変更する必要があると主張しますか。そのResultSet.getIntは、実際には「getIntOrZeroIfValueWasNull」になります。等。 - Jay
  • すべての呼び出しで区別することを望んでいます。まあ、はい、少なくとも発信者の作者は気にしないという意識的な決定を下すべきだと思います。関数が「わからない」のために空のリストを返し、呼び出し側がこの「なし」を盲目的に扱うと、真に不正確な結果をもたらす可能性があります。関数が" getAllergicReactionToMedicationList"であると想像してください。 「リストに盲目的に対処したプログラムは入力されませんでした」。 「患者には既知のアレルギー反応はありません」文字通りpaitentを殺すことになる可能性があります。それほど劇的な結果でなければ、他の多くのシステムでも同様の結果が得られます。 ... - Jay

10

空のコレクションが意味的に意味をなさない場合、それが私が返すことを好みますの空のコレクションを返すGetMessagesInMyInbox()「実際には受信トレイにメッセージがありません」と伝達しますnull返される可能性のあるリストがどのようなものであるべきかを示すために利用可能なデータが不十分であることを伝達するのに役立つかもしれません。


  • あなたが与える例では、メソッドが単にnullを返すのではなく要求を満たすことができない場合、おそらくメソッドが例外を投げるべきであるように思えます。例外は、nullよりも問題の診断にはるかに役立ちます。 - Greg Beech
  • はい、受信箱の例ではnull値は確かに合理的に見えない、私はその1つのより一般的な用語で考えていました。例外は、何かがうまくいっていないという事実を伝えるのにも最適です。と言われることは完全に期待されている、そして例外を投げることは貧弱なデザインであろう。私はむしろ、完全に可能であり、そして時々応答を計算することができない方法に関して全くエラーがないシナリオを考えている。 - David Hedlund

6

新しいオブジェクトが作成されないため、nullを返す方が効率的です。ただし、それはまた頻繁に要求しますnullチェック(または例外処理)

意味的には、null空のリストが同じことを意味するわけではありません。違いは微妙であり、特定の例ではどちらか一方が他方より優れているかもしれません。

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


  • APIの設計の正確さを考慮すると、効率はほとんど問題にならないはずです。グラフィックスプリミティブのような非常に特殊なケースではそうなるかもしれませんが、リストや他のほとんどの高レベルのものを扱うとき、私はそれをとても疑います。 - Greg Beech
  • 特にこの「最適化」を補うためにAPIユーザーが記述しなければならないコードを考えると、Gregに同意してください。そもそもより良いデザインが使用された場合よりも非効率的かもしれません。 - Craig Stuntz
  • 同意しました。ほとんどの場合、最適化するだけの価値はありません。空のリストは現代のメモリ管理では事実上無料です。 - Jason Baker

6

推論の背後にあると主張することができるヌルオブジェクトパターン空のコレクションを返すという点で似ています。


4

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

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

それは単に呼び出し元のコードによって処理されるべき問題を覆い隠しているので、null条件を処理する必要がないのでそれがコードをより堅牢にする(空のコレクションを返すことによって)と言うのは悪いことです。


4

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

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


4

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

'null'を返すと、クライアントがnullチェックを正しく処理しないという問題が頻繁に発生します。これにより、実行時にNullPointerExceptionが発生します。私はそのような欠けているnull-checkが優先生産問題を余儀なくさせたケースを見ました(クライアントは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は違いますのでnullSum()関数は  戻るnullそれで、私はそれを直接使うことができます。


2

私たちは1週間ほど前に開発チームの間でこの議論をしていましたが、ほぼ全員一致で空のコレクションに行きました。マイクが上記で指定したのと同じ理由で、1人の人がNULLを返したかったのです。


2

空のコレクションC#を使用している場合、システムリソースを最大限に活用することは重要ではないと想定されます。効率的ではありませんが、Empty Collectionを返すほうが、関係するプログラマにとってははるかに便利です(上記の理由から)。


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

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

見るここに巧妙なたわごとの嵐のためにnull一般に。私はその声明に同意しません。undefined別ですnullしかし、それはまだ読む価値があります。そしてそれは説明します、なぜあなたは避けるべきですnullあなたが尋ねた場合だけでなく、まったく。本質は、nullどんな言語でも特別な場合です。あなたは考えなければなりませんnull例外として。undefined未定義の動作を扱うコードは、ほとんどの場合、単なるバグです。 Cや他のほとんどの言語にも未定義の動作がありますが、それらのほとんどにはその言語の識別子がありません。


1

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

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

リンクされた質問


最近の質問