これは一般的な質問のようなものですが(私はC#を使用しています)、最良の方法は何ですか(ベストプラクティス)、戻り型としてコレクションを持つメソッドに対してnullまたは空のコレクションを返しますか?
空のコレクション常に。
これは吸う:
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>()
たとえば、新しい空のコレクションまたは配列を返すよりも効率的であると見なすことができます。
IEnumerable
またはICollection
それほど重要ではありません。とにかく、あなたがタイプの何かを選択すればICollection
彼らはまた戻るnull
...私は彼らに空のコレクションを返させたいのですが、私は彼らに遭遇して戻りましたnull
そのため、ここで言及するだけと思います。列挙型のコレクションのデフォルトはnullではなく空です。私はそれがそんなに敏感な話題であることを知りませんでした。 - Matthijs Wessels
からフレームワーク設計ガイドライン第2版(256ページ):
からnull値を返さないでください。 コレクションプロパティまたはメソッドから コレクションを返す空を返す 代わりにコレクションまたは空の配列。
これはnullを返さないことの利点に関するもう一つの興味深い記事です(私はBrad Abramのブログで何かを見つけようとしていました、そして彼はその記事にリンクしました)。
編集 - Eric Lippertが最初の質問にコメントしたので、私もそうしたいと思います彼の優れた記事へのリンク。
あなたに依存契約するそしてあなたのコンクリートケース。 一般に空のコレクションを返すのが最善です、 でも時々 (めったに):
null
より具体的な何かを意味するかもしれません。null
。いくつかの具体例:
null
空のコレクションは冗長になります(そしておそらく不正確になるでしょう)<collection />
まだ言及されていないもう一つの点があります。次のコードを見てください。
public static IEnumerable<string> GetFavoriteEmoSongs()
{
yield break;
}
このメソッドを呼び出すと、C#言語は空の列挙子を返します。したがって、言語設計(したがってプログラマの期待)と一致するように、空のコレクションを返す必要があります。
空ははるかに消費者にやさしいです。
空の列挙型を構成する明確な方法があります。
Enumerable.Empty<Element>()
それが何であれ、文脈上意味的に正しい値を返すべきだと私には思えます。 「常に空のコレクションを返す」という規則は、私には少し単純に思えます。
たとえば、病院のシステムで、過去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つの関数を用意したほうがよいでしょう。
空のコレクションが意味的に意味をなさない場合、それが私が返すことを好みますの空のコレクションを返すGetMessagesInMyInbox()
「実際には受信トレイにメッセージがありません」と伝達しますnull
返される可能性のあるリストがどのようなものであるべきかを示すために利用可能なデータが不十分であることを伝達するのに役立つかもしれません。
null
値は確かに合理的に見えない、私はその1つのより一般的な用語で考えていました。例外は、何かがうまくいっていないという事実を伝えるのにも最適です。と言われることは完全に期待されている、そして例外を投げることは貧弱なデザインであろう。私はむしろ、完全に可能であり、そして時々応答を計算することができない方法に関して全くエラーがないシナリオを考えている。 - David Hedlund
新しいオブジェクトが作成されないため、nullを返す方が効率的です。ただし、それはまた頻繁に要求しますnull
チェック(または例外処理)
意味的には、null
空のリストが同じことを意味するわけではありません。違いは微妙であり、特定の例ではどちらか一方が他方より優れているかもしれません。
選択に関係なく、混乱を避けるために文書化してください。
状況によります。特別な場合はnullを返します。関数がたまたま空のコレクションを返す場合は、当然それを返しても問題ありません。ただし、無効なパラメータなどの理由で空のコレクションを特別なケースとして返すことは、特別なケース条件を隠しているため、お勧めできません。
実際には、この場合私は通常それが本当に無視されていないことを確認するために例外をスローすることを好む:)
それは単に呼び出し元のコードによって処理されるべき問題を覆い隠しているので、null条件を処理する必要がないのでそれがコードをより堅牢にする(空のコレクションを返すことによって)と言うのは悪いことです。
私はそれを主張するでしょうnull
空のコレクションと同じことではありません。あなたが返すものを最もよく表すものを選択する必要があります。ほとんどの場合null
何もありません(SQLを除く)。空のコレクションは、空のものではありますが何かです。
どちらかを選択する必要がある場合は、nullではなく空のコレクションを選択する必要があります。しかし、空のコレクションがnull値と同じではない場合があります。
あなたのクライアント(あなたのAPIを使用している)を支持して常に考えてください:
'null'を返すと、クライアントがnullチェックを正しく処理しないという問題が頻繁に発生します。これにより、実行時にNullPointerExceptionが発生します。私はそのような欠けているnull-checkが優先生産問題を余儀なくさせたケースを見ました(クライアントはnull値にforeach(...)を使いました)。テスト中に操作されたデータがわずかに異なるため、問題は発生しませんでした。
ここで、適切な例を挙げて説明します。
ここでケースを考えてみましょう。
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
それで、私はそれを直接使うことができます。
私たちは1週間ほど前に開発チームの間でこの議論をしていましたが、ほぼ全員一致で空のコレクションに行きました。マイクが上記で指定したのと同じ理由で、1人の人がNULLを返したかったのです。
空のコレクションC#を使用している場合、システムリソースを最大限に活用することは重要ではないと想定されます。効率的ではありませんが、Empty Collectionを返すほうが、関係するプログラマにとってははるかに便利です(上記の理由から)。
ほとんどの場合、空のコレクションを返すことをお勧めします。
その理由は、呼び出し側の実装の便利さ、一貫した契約、そしてより簡単な実装です。
空の結果を示すためにメソッドが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());
}
}
}
私はそれを自分の億ドルの間違いと呼んでいます…その当時、私はオブジェクト指向言語での参照用に最初の包括的な型システムを設計していました。私の目標は、コンパイラが自動的にチェックを実行しながら、参照の使用をすべて絶対に安全にすることです。しかし、実装が非常に簡単だったという理由だけで、null参照を入れようという誘惑には耐えられませんでした。これにより、無数のエラー、脆弱性、およびシステムクラッシュが発生し、過去40年間でおそらく数十億ドルもの痛みと損傷が発生しています。 - ALGOL W.の発明者であるTony Hoare
見るここに巧妙なたわごとの嵐のためにnull
一般に。私はその声明に同意しません。undefined
別ですnull
しかし、それはまだ読む価値があります。そしてそれは説明します、なぜあなたは避けるべきですnull
あなたが尋ねた場合だけでなく、まったく。本質は、null
どんな言語でも特別な場合です。あなたは考えなければなりませんnull
例外として。undefined
未定義の動作を扱うコードは、ほとんどの場合、単なるバグです。 Cや他のほとんどの言語にも未定義の動作がありますが、それらのほとんどにはその言語の識別子がありません。
ソフトウェアエンジニアリングの主要目的である複雑さを管理するという観点から、私たちは伝播を避けたいと思います。不要APIのクライアントにとっては循環的な複雑さ。クライアントにnullを返すことは、別のコードブランチの循環的な複雑さのコストを返すようなものです。
(これは単体テストの負担に相当します。空のコレクションの戻りケースに加えて、nullの戻りケースのテストを書く必要があります。)