ここでおすすめを探しています。戻り値が存在しないか、または判別できない場合に、メソッドからNULLまたは空の値を返す方がよいかと悩んでいます。
例として、次の2つの方法を取り上げます。
_string ReverseString(string stringToReverse) // takes a string and reverses it.
Person FindPerson(int personID) // finds a Person with a matching personID.
_
ReverseString()
では、戻り値の型が文字列であるため、空の文字列を返すので、呼び出し元はそれを期待しています。また、この方法では、呼び出し側はNULLが返されたかどうかを確認する必要がありません。
FindPerson()
では、NULLを返す方が適しているようです。 NULLまたは空のPersonオブジェクト(new Person()
)が返されるかどうかに関係なく、呼び出し元は、何かを行う前にPersonオブジェクトがNULLまたは空かどうかを確認する必要があります(UpdateName()
)。では、なぜここでNULLを返さず、呼び出し側はNULLをチェックするだけでよいのでしょうか。
他の誰かがこれで苦労していますか?どんな助けや洞察もありがたいです。
StackOverflowは、この正確なトピックについてこの Q&A で良い議論をしています。最高評価の質問で、クロノズは次のように述べています:
データが利用できないことを示す場合は、通常、nullを返すのが最善の方法です。
空のオブジェクトはデータが返されたことを意味しますが、nullを返すと、何も返されなかったことを明確に示します。
さらに、オブジェクトのメンバーにアクセスしようとすると、nullを返すとnull例外が発生します。これはバグのあるコードを強調表示するのに役立ちます。何もないメンバーにアクセスしようとしても意味がありません。空のオブジェクトのメンバーへのアクセスは失敗せず、バグが発見されない可能性があります。
個人的には、文字列を返す関数に対して空の文字列を返し、配置する必要のあるエラー処理の量を最小限に抑えたいと思っています。ただし、作業するグループが同じ規則に従うことを確認する必要があります。そうしないと、この決定の利点が実現されません。
ただし、SO回答の投稿者が指摘したように、データが返されているかどうかに疑いがないようにオブジェクトが予期される場合は、おそらくnullが返されるはずです。
結局、物事を行うための最良の方法は1つではありません。チームのコンセンサスを構築すると、最終的にはチームのベストプラクティスが促進されます。
私が書くすべてのコードでは、関数からnull
を返すことを避けています。 クリーンコード でそれを読みました。
null
を使用する際の問題は、_not null
_参照型がないため、インターフェイスを使用している人がnull
が考えられる結果かどうか、またそれを確認する必要があるかどうかを知らないことです。
F#では、option
型を返すことができます。これは、some(Person)
またはnone
のいずれかになるため、呼び出し側が確認する必要があることは明らかです。
類似のC#(アンチ)パターンは_Try...
_メソッドです。
_public bool TryFindPerson(int personId, out Person result);
_
hate_Try...
_パターンが出力パラメーターを持つことは純粋な関数のアイデアを壊すので、私は人々が彼らを言ったことを知っていますが、それは実際にはありません異なります:
_class FindResult<T>
{
public FindResult(bool found, T result)
{
this.Found = found;
this.Result = result;
}
public bool Found { get; private set; }
// Only valid if Found is true
public T Result { get; private set;
}
public FindResult<Person> FindPerson(int personId);
_
...そして正直に言うと、every.NETプログラマーは_Try...
_パターンを。フレームワーク。つまり、彼らはドキュメントを読んで理解する必要がないことを意味します。これは、純粋な関数の見方に固執するよりも私にとって重要です( result
はout
パラメーターではなくref
パラメーターであることを理解してください)。
それで、それを見つけることができないのは完全に正常であるとあなたが示しているように見えるので、TryFindPerson
を使用します。
一方、存在しないpersonId
を呼び出し元が提供する論理的な理由がない場合は、おそらく次のようにします。
_public Person GetPerson(int personId);
_
...そして、それが無効な場合、例外をスローします。 _Get...
_接頭辞は、呼び出し元が成功する必要があることを知っていることを意味します。
エンタープライズアプリケーションアーキテクチャのパターン から、Martin Fowlerの 特別なケースパターン を試すことができます。
Nullは、ポリモーフィズムを無効にするため、オブジェクト指向プログラムでは扱いにくいものです。通常、アイテムが正確なタイプであるかサブクラスであるかを気にすることなく、特定のタイプの変数参照でfooを自由に呼び出すことができます。強く型付けされた言語を使用すると、呼び出しが正しいことをコンパイラにチェックさせることもできます。ただし、変数にnullが含まれる可能性があるため、nullでメッセージを呼び出すとランタイムエラーが発生する可能性があります。
変数がnullになる可能性がある場合は、変数をnullテストコードで囲むことを忘れないようにする必要があります。これにより、nullが存在する場合でも正しいことができます。多くの場合、正しいことは多くの場合同じであるため、多くの場所で同様のコードを作成することになります-コード重複の罪を犯します。
NULLはそのような問題の一般的な例であり、他の問題は定期的に発生します。数体系では、無限大に対処する必要があります。無限大には、実数の通常の不変条件を破る加算などの特別なルールがあります。ビジネスソフトウェアでの私の最初の経験の1つは、「居住者」と呼ばれる、完全には知られていない公益事業の顧客との経験でした。これらはすべて、型の通常の動作を変更することを意味します。
Nullまたは奇妙な値を返す代わりに、呼び出し元が期待するものと同じインターフェイスを持つ特別なケースを返します。
ReverseString()
は逆の文字列を返し、IllegalArgumentException
に渡された場合はNull
をスローすると思います。
FindPerson()
は NullObject Pattern に従う必要があるか、または常に何かを見つけることができる場合は、何かを見つけられないという未チェックの例外を発生させる必要があると思います。
Null
に対処する必要があることは避けなければなりません。言語でNull
を持つことは、発明者によって 10億ドルの間違い と呼ばれています!
私はそれを10億ドルの間違いと呼んでいます。それは1965年にnull参照の発明でした。そのとき、私はオブジェクト指向言語(ALGOL W)での参照用の最初の包括的な型システムを設計していました。
私の目標は、コンパイラーによって自動的に実行されるチェックにより、参照のすべての使用が絶対的に安全であることを確認することでした。しかし、実装が非常に簡単だったという理由だけで、null参照を挿入するという誘惑に抵抗できませんでした。
これにより、無数のエラー、脆弱性、システムクラッシュが発生し、おそらく過去40年間で10億ドルの苦痛と損害を引き起こしています。
近年では、MicrosoftのPREfixやPREfastなどの多くのプログラムアナライザーを使用して参照をチェックし、nullでない可能性があるリスクがある場合に警告を出します。 Spec#などの最近のプログラミング言語では、null以外の参照の宣言が導入されています。これが私が1965年に却下した解決策です。
トニー・ホアレ
オプション を返します。 NullPointerExceptionのリスクなしに、異なる無効な値を返すことのすべての利点(コレクションに空の値を含めることができるなど)。
私はこの議論の両面を見ており、コードをクリーンに保つため、余分なエラー処理ブロックを回避するために、nullを返さないことを支持するいくつかの影響力のある声(Fowlerなど)を理解しています。
ただし、私はnullを返すことを支持する傾向があります。メソッドを呼び出す際に重要な違いがあることに気づき、それはデータがないで応答し、この空の文字列で応答します。
Personクラスを参照するディスカッションの一部を見てきましたので、クラスのインスタンスを検索しようとするシナリオを考えてみてください。 Finder属性(IDなど)を渡した場合、クライアントはすぐにnullをチェックして、値が見つからなかったかどうかを確認できます。これは必ずしも例外ではありません(したがって例外は必要ありません)が、それも明確に文書化する必要があります。はい、これはクライアントの側である程度の厳密さが必要です。いいえ、それは悪いことではないと私は思います。
次に、有効なPersonオブジェクトを返す別の方法を考えてみましょう。これには何も含まれていません。すべての値(名前、住所、favoriteDrink)にnullを入れますか、それとも、有効だが空のオブジェクトをそれらに入力しますか?クライアントはどのようにして、実際の人物が見つからなかったと判断しますか?名前がnullではなく空の文字列かどうかを確認する必要がありますか?この種のことは、nullをチェックして先に進んだ場合よりも、実際にはコードの混乱や条件ステートメントにつながるのではないでしょうか。
繰り返しますが、この議論のどちらかに同意できる点がありますが、これはほとんどの人にとって最も理にかなっている(コードをより保守しやすくする)と私は思います。
アイテムが存在するかどうかを知る必要がある場合はnullを返します。それ以外の場合は、予期されるデータ型を返します。これは、アイテムのリストを返す場合に特に当てはまります。通常、呼び出し元がリストを必要としている場合は、リストを反復処理したいと考えるのが安全です。多くの(ほとんど?すべて?)言語は、nullを反復しようとすると失敗しますが、空のリストを反復すると失敗します。
このようなコードを試して使用するのは、失敗するだけでイライラします。
for thing in get_list_of_things() {
do_something_clever(thing)
}
thingsがない場合のために、特別なケースを追加する必要はありません。
それはメソッドのセマンティクスに依存します。指定することもできますが、メソッドは「Not null」のみを受け入れます。 Javaでは、いくつかのメタデータアノテーションを使用して次のように宣言できます。
string ReverseString(@NotNull String stringToReverse)
どういうわけか、戻り値を指定する必要があります。宣言すると、NotNull値のみを受け入れ、空の文字列を返すようにバインドされます(入力が空の文字列の場合も)。
2番目のケースは、そのセマンティクスが少し複雑です。このメソッドが一部の人を主キーで返す(そしてその人が存在することが期待される)場合、より良い方法は例外をスローすることです。
Person FindPerson(int personID)
推測されたID(奇妙に思われる)で人物を検索する場合は、そのメソッドを@Nullableとして宣言することをお勧めします。
可能な限りNull Objectパターンを使用することをお勧めします。メソッドを呼び出すときにコードを簡略化し、次のような醜いコードを読むことができません
if (someObject != null && someObject.someMethod () != whateverValue)
たとえば、あるパターンに一致するオブジェクトのコレクションをメソッドが返す場合、空のコレクションを返すことはnull
を返すよりも意味があり、この空のコレクションを反復してもパフォーマンスは実質的に低下しません。もう1つのケースは、データのログに使用されるクラスインスタンスを返すメソッドであり、null
ではなくNullオブジェクトを返す方が、私の意見では、返される参照がnull
。
null
を返すのが理にかなっている場合(findPerson ()
メソッドを呼び出すなど)、オブジェクトが存在する場合に返されるメソッド(personExists (int personId)
を少なくとも提供することを試みます。たとえば)、別の例はJavaのMap
のcontainsKey ()
メソッドです)。目的のオブジェクトが使用できない可能性があることを簡単に確認できるため、呼び出し元のコードがよりクリーンになります(人が存在せず、キーがマップに存在しません)。参照がnull
であるかどうかを絶えずチェックすると、私の意見ではコードが難読化されます。
次の条件が当てはまる場合にのみ、nullを返すのが最善です。
さらに、事実を文書化関数がnullを返すことができることを確認してください。
これらのルールに従う場合、ヌルはほとんど無害です。 nullのチェックを忘れた場合、通常は直後にNullPointerExceptionが発生することに注意してください。これは通常、修正が非常に簡単なバグです。このフェイルファーストアプローチは、システムの周りに静かに伝播される偽の戻り値(たとえば、空の文字列)を使用するよりもはるかに優れています。例外を投げます。
最後に、質問にリストされている2つの関数にこれらのルールを適用すると、次のようになります。
私の意見では、NULLを返すこと、空の結果(空の文字列や空のリストなど)を返すこと、例外をスローすることには違いがあります。
私は通常、次のアプローチをとります。関数またはメソッドf(v1、...、vn)呼び出しを関数のアプリケーションと見なします
f : S x T1 x ... x Tn -> T
ここで、Sは「世界の状態」T1、...、Tnは入力パラメーターのタイプ、Tは戻りタイプです。
まず、この関数を定義してみます。関数が部分的の場合(つまり、定義されていない入力値がある場合)、これを示すためにNULLを返します。これは、計算を正常に終了させ、要求した関数が特定の入力で定義されていないことを通知するためです。たとえば、戻り値として空の文字列を使用すると、関数が入力で定義され、空の文字列が正しい結果である可能性があるため、あいまいになります。
呼び出しコードでのNULLポインターの追加チェック必須を使用しているのは、部分的な関数を適用していて、指定された関数が定義されていない場合に関数が定義されているかどうかを通知するのが、呼び出されたメソッドのタスクだからです。入力。
計算の実行が許可されていないエラー(つまり、答えを見つけることができなかった)には、例外を使用することを好みます。
たとえば、Customerクラスがあり、メソッドを実装したいとします。
Customer findCustomer(String customerCode)
アプリケーションデータベース内の顧客をコードで検索します。この方法では、
Nullの追加チェック(例:.
Customer customer = findCustomer("...");
if (customer != null && customer.getOrders() > 0)
{
...
}
私がやっていることのセマンティクスの一部であり、コードを読みやすくするために「スキップ」するだけではありません。コードを単純化するためだけに、目前の問題のセマンティクスを単純化することは良い習慣ではないと思います。
もちろん、nullのチェックは非常に頻繁に発生するので、言語がそのための特別な構文をサポートしているとよいでしょう。
クラスのnullオブジェクトを他のすべてのオブジェクトから区別できる限り、(Lafによって提案された)Null Objectパターンの使用も検討します。
人々がMaybe
型コンストラクタについてすでに言ったことに追加します:
NPEを危険にさらさないこととは別に、もう1つの大きな利点はセマンティックなものです。純粋な関数を扱う関数の世界では、適用したときにそれらの戻り値と完全に等しいである関数を作成しようとしています。 String.reverse()
の場合を考えてみます。これは、特定の文字列が与えられたときに、その文字列の逆バージョンを表す関数です。すべての文字列を逆にすることができるため、この文字列が存在することがわかります(文字列は基本的に順序付けられた文字のセットです)。
さて、findCustomer(int id)
はどうですか?この関数は、指定されたIDを持つ顧客を表します。これは存在する場合と存在しない場合があります。ただし、関数には単一の戻り値があるため、「この関数は指定されたIDの顧客を返す[〜#〜] or [〜#〜]はnull
。
これは、null
を返すこととnullオブジェクトを返すことの両方に関する私の主な問題です。彼らは誤解を招くものです。 null
は顧客ではなく、実際には「顧客の不在」でもありません。それは何もないことです。それは単にNOTHINGへの型なしのポインタです。非常に低レベルで、非常に醜く、エラーが発生しやすい。 nullオブジェクトもここではかなり誤解を招くと思います。 nullの顧客IS顧客です。顧客の不在ではなく、要求されたIDを持つ顧客ではないため、返却は単に間違っています。これは私が要求した顧客ではありませんが、それでも顧客を返品したと主張します。IDが間違っています。明らかにこれはバグです。メソッド名とシグネチャによって提案された契約に違反します。クライアントコードは、設計に関することを想定したり、ドキュメントを読んだりする必要があります。
これらの問題はどちらもMaybe Customer
によって解決されます。突然、「この関数は指定されたIDを持つ顧客を表す」とは言えません。 「この関数は、指定されたIDを持つ顧客を表します存在する場合。これで、機能が非常に明確になりました。存在しないIDを持つ顧客を要求すると、はNothing
を受け取ります。これはどのようにnull
より優れていますか?NPEリスクだけでなく、Nothing
のタイプがMaybe
だけではないためです。それはMaybe Customer
です。意味的に意味があります。具体的には、「顧客の不在」を意味し、そのような顧客が存在しないことを示します。
Nullのもう1つの問題は、あいまいであることです。お客様が見つからなかったのですか、それともデータベース接続エラーがあったのですか、それとも私はその顧客に会うことを許可されていませんか?
このようないくつかのエラーケースを処理する必要がある場合は、それらのバージョンの例外をスローするか、(そして私がこの方法を好む)Either CustomerLoadError Customer
を返すことができます。 Maybe Customer
のすべての利点を備えながら、何が問題になるかを指定することもできます。
私が求めている主なことは、関数のコントラクトをシグネチャに取り込むことです。物事を仮定しないでください、つかの間の慣習に依存しないでください、明示的に。
1)関数のセマンティクスが何も返さないというものである場合、呼び出し元はそれをテストする必要があります。あなたのお金を取り、誰にもそれを与えません。
2)意味的に常に何かを返す(またはスローする)関数を作成するのは良いことです。
loggerを使用すると、ロガーが定義されていない場合にログを記録しないロガーを返すことは理にかなっています。コレクションを返す場合、空のセットはセットではなくセットであるため、何も返さないことはほとんどありません(コレクション自体がデータであり、コレクション自体がデータである「十分に低いレベル」を除いて)。
personの例では、2つの関数を持ち、1つはnullを返し、もう1つはそれを返す「休止状態」の方法(get vs load、IIRCですが、遅延読み込みのプロキシオブジェクトによって複雑になります)投げます。
コレクションを返すメソッドは空を返す必要がありますが、他の場合はnullを返す可能性があります。これは、コレクションの場合、オブジェクトは存在するがその中に要素がないため、呼び出し元が両方のサイズだけを検証するためです。
私には2つのケースがあります。なんらかのリストを返す場合は、何があっても常にリストを返す必要があります。何も入力できない場合は空にしてください。
私が議論を見る唯一のケースは、あなたが単一のアイテムを返すときです。私は、物事を速く失敗させることに基づいて、そのような状況で失敗した場合にnullを返すことを好むことに気づきました。
Nullオブジェクトを返し、呼び出し元が実際のオブジェクトを必要とする場合、それらは先に進み、nullオブジェクトを使用しようとすると、予期しない動作が発生する可能性があります。彼らが状況に対処するのを忘れたなら、ルーティンがブームを起こすほうが良いと思います。何か問題がある場合は、できるだけ早く例外を設定します。この方法でバグを出荷する可能性は低くなります。