それは一種の一般的な質問ですが(C#を使用しています)、最善の方法(ベストプラクティス)は何ですか?戻り値の型としてコレクションを持つメソッドに対してnullまたは空のコレクションを返しますか?
それは一種の一般的な質問ですが(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
ので、ここでそれについて言及したいと思いました。enumerableのコレクションのデフォルトはnullではなく空だと思います。そんなにデリケートなテーマだとは知りませんでした。
Frameworkの設計ガイドライン第2版(PG 256。):
コレクションプロパティまたはコレクションを返すメソッドからnull値を返さないでください。代わりに空のコレクションまたは空の配列を返します。
nullを返さないことの利点に関する興味深い記事をもう1つ紹介します(私はBrad Abramのブログで何かを見つけようとしており、彼はその記事にリンクしています)。
編集 -Eric Lippertが元の質問にコメントしたので、彼の優れた記事にもリンクしたいと思います。
あなたの契約とあなたの具体的なケースに依存します。通常は空のコレクションを返すのが最善ですが、場合によっては(まれに)次のようになります。
null
より具体的なものを意味するかもしれません。null
。いくつかの具体的な例:
null
、要素が欠落していることを意味しますが、空のコレクションは冗長になります(そしておそらく正しくありません)。<collection />
まだ言及されていない点がもう1つあります。次のコードを検討してください。
public static IEnumerable<string> GetFavoriteEmoSongs()
{
yield break;
}
このメソッドを呼び出すと、C#言語は空の列挙子を返します。したがって、言語設計と一致するように(したがって、プログラマーの期待どおりに)するには、空のコレクションを返す必要があります。
空ははるかに消費者に優しいです。
空の列挙型を作成する明確な方法があります。
Enumerable.Empty<Element>()
私はあなたが文脈上意味的に正しい値を返すべきだと思っています。「常に空のコレクションを返す」というルールは、私には少し単純すぎるようです。
たとえば、病院向けのシステムで、過去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つの関数が欲しいです。
空のコレクションが意味的に意味をなす場合は、それを返すことをお勧めします。空のコレクションを返すGetMessagesInMyInbox()
ことは「本当に受信トレイにメッセージがない」ことを伝えますが、返すnull
ことは、返される可能性のあるリストがどのように見えるべきかを示すのに十分なデータが利用できないことを伝えるのに役立ちます。
null
確かに、受信トレイの例では、値は確かに妥当ではないように見えます。私はその値についてより一般的な用語で考えていました。例外は、何かがおかしいという事実を伝えるのにも最適ですが、参照された「不十分なデータ」が完全に予期される場合、例外をスローすると、設計が不十分になります。私はむしろ、メソッドが応答を計算できないこともあり、完全に可能でエラーがまったくないシナリオを考えています。
新しいオブジェクトは作成されないため、nullを返す方が効率的です。ただし、null
チェック(または例外処理)も必要になることがよくあります。
意味的にはnull
、空のリストは同じことを意味しません。違いは微妙であり、特定の状況では1つの選択肢が他の選択肢よりも優れている場合があります。
選択に関係なく、混乱を避けるために文書化してください。
Null Object Patternの背後にある理由は、空のコレクションを返すことを支持する理由と似ていると主張することができます。
ここで、適切な例を挙げて説明します。
ここでケースを検討してください。
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
。を返さないため、直接使用できます。
ほとんどの場合、空のコレクションを返す方が適切です。
その理由は、呼び出し元の実装の利便性、一貫した契約、およびより簡単な実装です。
メソッドが空の結果を示すために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());
}
}
}
私はそれを10億ドルの間違いと呼んでいます。そのとき、私はオブジェクト指向言語での参照用の最初の包括的な型システムを設計していました。私の目標は、コンパイラーによって自動的に実行されるチェックにより、参照のすべての使用が絶対的に安全であることを確認することでした。しかし、実装が非常に簡単だったという理由だけで、null参照を挿入するという誘惑に抵抗できませんでした。これにより、無数のエラー、脆弱性、システムクラッシュが発生し、おそらく過去40年間で10億ドルの苦痛と損害を引き起こしています。– ALGOL Wの発明者、Tony Hoare氏
一般的なことについての精巧なたわごとの嵐については、ここを参照してくださいnull
。私undefined
は別の声明に同意しませんnull
が、それはまだ読む価値があります。そして、それは、null
あなたが尋ねた場合だけでなく、なぜあなたがまったく避けるべきなのかを説明しています。本質は、null
どの言語でも特別なケースです。あなたはnull
例外として考える必要があります。undefined
その点で異なり、未定義の動作を処理するコードはほとんどの場合単なるバグです。Cと他のほとんどの言語にも未定義の動作がありますが、それらのほとんどは言語の中でそれを識別するためのものではありません。