関数からデータを返す場合のベストプラクティスとは何ですか。 Nullまたは空のオブジェクトを返す方が良いですか?そして、なぜ一方が他方を上回らなければならないのですか?
このことを考慮:
public UserEntity GetUserById(Guid userId)
{
//Imagine some code here to access database.....
//Check if data was returned and return a null if none found
if (!DataExists)
return null;
//Should I be doing this here instead?
//return new UserEntity();
else
return existingUserEntity;
}
このプログラムには、そのGUIDを使用してデータベースにユーザー情報が存在しないという有効なケースがあると想定します。この場合、例外をスローするのは適切ではないと思いますか?また、例外処理はパフォーマンスを低下させる可能性があるという印象を受けています。
使用可能なデータがないことを示す場合は、通常、nullを返すことをお勧めします。
空のオブジェクトはデータが返されたことを意味しますが、nullを返すことは何も返されなかったことを明確に示します。
さらに、オブジェクトのメンバーにアクセスしようとすると、nullを返すとnull例外が発生します。これは、バグのあるコードを強調表示するのに役立ちます。空のオブジェクトのメンバーへのアクセスは失敗しません。つまり、バグが発見されない可能性があります。
それはあなたのケースにとって最も意味のあるものに依存します。
Nullを返すことは理にかなっていますか? 「そのようなユーザーは存在しません」?
または、デフォルトのユーザーを作成するのは理にかなっていますか?これは、ユーザーが存在しない場合、呼び出し元のコードはユーザーが要求したときに存在することを意図していると安全に想定できる場合に最も意味があります。
または、呼び出し元のコードが無効なIDを持つユーザーを要求している場合、例外(「FileNotFound」など)をスローすることは意味がありますか?
ただし、懸念の分離/ SRPの観点から、最初の2つはより正確です。そして、技術的に最初のものが最も正しい(しかし髪だけで)-GetUserByIdは1つのことだけに責任があるべきです-ユーザーの取得。他の何かを返すことによって、独自の「ユーザーが存在しない」場合を処理することは、SRPの違反である可能性があります。別のチェックに分離する-例外をスローすることを選択した場合、bool DoesUserExist(id)
が適切です。
以下の広範なコメントに基づく:これがAPIレベルの設計の質問である場合、このメソッドは「OpenFile」または「ReadEntireFile」に類似している可能性があります。あるリポジトリからユーザーを「オープン」し、結果のデータからオブジェクトをハイドレートしています。この場合、例外が適切である可能性があります。そうではないかもしれませんが、そうかもしれません。
すべてのアプローチが受け入れられます-API /アプリケーションのより大きなコンテキストに基づいて、それはただ依存します。
個人的には、NULLを使用しています。返すデータがないことを明確にします。ただし、 Null Object が役立つ場合があります。
戻り値の型が配列の場合は空の配列を返し、そうでない場合はnullを返します。
特定の契約が破られた場合にのみ、例外をスローする必要があります。
既知のIDに基づいてUserEntityを要求する特定の例では、行方不明(削除)ユーザーが予想されるケースであるかどうかに依存します。そうである場合、null
を返しますが、予想されるケースではない場合は、例外をスローします。
関数がUserEntity GetUserByName(string name)
と呼ばれた場合、おそらくスローせずnullを返すことに注意してください。どちらの場合も、空のUserEntityを返すことは役に立ちません。
文字列、配列、コレクションの場合、状況は通常異なります。メソッドはnull
を「空の」リストとして受け入れますが、null
ではなく長さがゼロのコレクションを返すというMSのガイドラインを覚えています。文字列についても同じです。空の配列を宣言できることに注意してください:int[] arr = new int[0];
これはビジネス上の質問であり、特定のGUID IDを持つユーザーの存在がこの機能の予想される通常のユースケースであるか、このメソッドがユーザーに提供する機能をアプリケーションが正常に完了することを妨げる異常なのかによって異なりますオブジェクト...
「例外」の場合、そのIDを持つユーザーが存在しないと、アプリケーションが実行中の機能を正常に完了できなくなります(製品を出荷した顧客の請求書を作成するとします... )、この状況ではArgumentException(またはその他のカスタム例外)がスローされます。
行方不明のユーザーが大丈夫な場合(この関数を呼び出す潜在的な通常の結果の1つ)、nullを返します。
編集:(別の回答でアダムからのコメントに対処するため)
アプリケーションに複数のビジネスプロセスが含まれ、そのうちの1つ以上が正常に完了するためにユーザーを必要とし、そのうちの1つ以上がユーザーなしで正常に完了できる場合、例外は呼び出しスタックのさらに上の場所にスローされますユーザーを必要とするビジネスプロセスは、この実行スレッドを呼び出しています。このメソッドとそのポイント(例外がスローされている)の間のメソッドは、ユーザーが存在しないことを通知する必要があります(null、boolean、何でも-これは実装の詳細です)。
しかし、アプリケーション内のすべてのプロセスrequireユーザーの場合、このメソッドで例外をスローします...
個人的にはnullを返します。これは、DAL /リポジトリレイヤーが機能することを期待する方法だからです。
存在しない場合は、オブジェクトの取得に成功したと解釈できるものは何も返さないでください。null
はここでうまく機能します。
最も重要なことは、DAL/Reposレイヤー全体で一貫性を保つことです。そうすれば、使用方法について混乱することはありません。
私はする傾向があります
return null
オブジェクトIDが存在しない場合、それが事前にわからない場合should存在するかどうか。throw
オブジェクトIDが存在しない場合should存在する場合。これらの3つのタイプの方法で、これら2つのシナリオを区別します。最初:
Boolean TryGetSomeObjectById(Int32 id, out SomeObject o)
{
if (InternalIdExists(id))
{
o = InternalGetSomeObject(id);
return true;
}
else
{
return false;
}
}
第二:
SomeObject FindSomeObjectById(Int32 id)
{
SomeObject o;
return TryGetObjectById(id, out o) ? o : null;
}
三番:
SomeObject GetSomeObjectById(Int32 id)
{
SomeObject o;
if (!TryGetObjectById(id, out o))
{
throw new SomeAppropriateException();
}
return o;
}
さらに別のアプローチでは、値を操作するコールバックオブジェクトまたはデリゲートを渡す必要があります。値が見つからない場合、コールバックは呼び出されません。
public void GetUserById(Guid id, UserCallback callback)
{
// Lookup user
if (userFound)
callback(userEntity); // or callback.Call(userEntity);
}
これは、コード全体でnullチェックを避けたい場合や、値が見つからない場合でもエラーにならない場合に有効です。特別な処理が必要な場合は、オブジェクトが見つからない場合のコールバックを提供することもできます。
public void GetUserById(Guid id, UserCallback callback, NotFoundCallback notFound)
{
// Lookup user
if (userFound)
callback(userEntity); // or callback.Call(userEntity);
else
notFound(); // or notFound.Call();
}
単一のオブジェクトを使用する同じアプローチは次のようになります。
public void GetUserById(Guid id, UserCallback callback)
{
// Lookup user
if (userFound)
callback.Found(userEntity);
else
callback.NotFound();
}
設計の観点から、私はこのアプローチが本当に好きですが、一流の機能を容易にサポートしない言語では呼び出しサイトをより大きくするという不利な点があります。
Null結合演算子(??
)と互換性があるので、null
を好みます。
空のオブジェクトの代わりにnullを返します。
しかし、ここで言及した特定のインスタンスは、ユーザーIDでユーザーを検索していますが、これはそのユーザーのキーの一種です。その場合、ユーザーインスタンスインスタンスが見つからない場合はおそらく例外をスローしたいと思います。
これは私が一般的に従うルールです:
CSLA.NETを使用しますが、データの取得に失敗すると「空の」オブジェクトが返されるはずだという見方をしています。これは、obj.IsNew
ではなくobj == null
であるかどうかを確認する規則を要求するため、実際には非常に迷惑です。
前のポスターで述べたように、nullの戻り値はコードをすぐに失敗させ、空のオブジェクトによって引き起こされるステルス問題の可能性を減らします。
個人的には、null
はよりエレガントだと思います。
それは非常に一般的なケースであり、私はここの人々がそれに驚いているように見えることに驚いています:Webアプリケーションでは、クエリ文字列パラメーターを使用してデータがフェッチされることがよくあります。 「。
これは次の方法で処理できます。
if(User.Exists(id)){ this.User = User.Fetch(id); } else { Response.Redirect( "〜 /notfound.aspx"); }
...しかし、それは毎回データベースへの余分な呼び出しであり、トラフィックの多いページで問題になる可能性があります。一方、
this.User = User.Fetch(id); if(this.User == null){ Response.Redirect( "〜/ notfound。 aspx "); }
... 1回の呼び出しのみが必要です。
コンテキストによって異なりますが、特定のオブジェクトを探している場合は(例のように)通常nullを返し、オブジェクトのセットを探しているが何もない場合は空のコレクションを返します。
コードに誤りを犯し、nullを返すとnullポインタ例外が発生する場合は、すぐにそれを捕まえることをお勧めします。空のオブジェクトを返す場合、最初の使用は機能する可能性がありますが、後でエラーが発生する可能性があります。
この場合のベストは、そのようなユーザーがいない場合に「null」を返します。また、メソッドを静的にします。
編集:
通常、このようなメソッドは「ユーザー」クラスのメンバーであり、そのインスタンスメンバーへのアクセス権はありません。この場合、メソッドは静的である必要があります。そうでない場合は、「User」のインスタンスを作成してから、別の「User」インスタンスを返すGetUserByIdメソッドを呼び出す必要があります。これは混乱を招くことに同意します。ただし、GetUserByIdメソッドが何らかの「DatabaseFactory」クラスのメンバーである場合、インスタンスメンバーとして残しても問題ありません。
私はフランス人のIT学生なので、英語が下手です。私たちのクラスでは、そのようなメソッドは決してnullも空のオブジェクトも返すべきではないと言われています。このメソッドのユーザーは、取得しようとする前に、探しているオブジェクトが存在することを最初に確認することになっています。
Javaを使用して、「前提条件」を表現するために、nullを返す可能性のあるメソッドの先頭にassert exists(object) : "You shouldn't try to access an object that doesn't exist";
を追加するように求められます(英語のWordが何なのかわかりません)。
IMOこれは実際に使用するのは簡単ではありませんが、それは私が使用しているもので、より良いものを待っています。
ユーザーが見つからないというケースが頻繁に発生し、状況に応じてさまざまな方法で対処したい場合(例外をスローしたり、空のユーザーを置き換えたりする場合)、F#のOption
またはHaskellのMaybe
タイプ。'novalue 'ケースを' found something! 'から明示的に分離します。データベースアクセスコードは次のようになります。
public Option<UserEntity> GetUserById(Guid userId)
{
//Imagine some code here to access database.....
//Check if data was returned and return a null if none found
if (!DataExists)
return Option<UserEntity>.Nothing;
else
return Option.Just(existingUserEntity);
}
そして、このように使用されます:
Option<UserEntity> result = GetUserById(...);
if (result.IsNothing()) {
// deal with it
} else {
UserEntity value = result.GetValue();
}
残念ながら、誰もがこのようなタイプを独自に展開しているようです。
Business Objectsには、2つの主要なGetメソッドがあります。
コンテキストで物事をシンプルに保つか、あなたが疑問に思うのは:
// Returns null if user does not exist
public UserEntity GetUserById(Guid userId)
{
}
// Returns a New User if user does not exist
public UserEntity GetNewOrExistingUserById(Guid userId)
{
}
最初の方法は特定のエンティティを取得するときに使用され、2番目の方法はWebページでエンティティを追加または編集するときに特に使用されます。
これにより、使用されるコンテキストで両方の長所を活用できます。
個人的にオブジェクトのデフォルトのインスタンスを返します。その理由は、メソッドが(メソッドの目的に応じて)0から多または0から1を返すことを期待しているからです。このアプローチを使用して、あらゆる種類のエラー状態になる唯一の理由は、メソッドがオブジェクトを返さず、常に(1対多または単数のリターンに関して)予想される場合です。
これがビジネス領域の質問であるという仮定に関しては、方程式のその側面からは見えません。戻り値の型の正規化は、有効なアプリケーションアーキテクチャの質問です。少なくとも、コーディングプラクティスの標準化の対象となります。 「シナリオXでは、nullを与えるだけ」と言うビジネスユーザーがいることを疑います。
通常、nullを返します。例外をスローしたり、大量のtry/catchをあちこち使用したりせずに、何かがめちゃくちゃになったかどうかを検出するための迅速かつ簡単なメカニズムを提供します。
興味深い質問であり、「正しい」答えはありません。コードの責任に常に依存するからです。見つかったデータに問題がないかどうか、メソッドは知っていますか?ほとんどの場合、答えは「いいえ」です。そのため、nullを返し、呼び出し側に状況を処理させることは完璧です。
スローするメソッドとnullを返すメソッドを区別するための適切なアプローチは、チーム内で慣習を見つけることです。 nullを返す可能性のあるメソッドの名前は、おそらく「Find ...」とは異なる場合があります。
コレクション型の場合は空のコレクションを返し、他のすべての型の場合は、戻り値の型と同じインターフェイスを実装するオブジェクトを返すためにNullObjectパターンを使用することを好みます。パターンチェックアウトの詳細については、 link text
NullObjectパターンを使用すると、これは次のようになります。
public UserEntity GetUserById(Guid userId)
{//ここにデータベースにアクセスするコードを想像してください.....
//Check if data was returned and return a null if none found
if (!DataExists)
return new NullUserEntity(); //Should I be doing this here instead? return new UserEntity();
else
return existingUserEntity;
}
class NullUserEntity: IUserEntity { public string getFirstName(){ return ""; } ...}
擬似php/codeを許してください。
結果の使用目的に本当に依存すると思います。
戻り値を編集/変更して保存する場合は、空のオブジェクトを返します。これにより、同じ関数を使用して、新規または既存のオブジェクトにデータを入力できます。
主キーとデータの配列を受け取り、行にデータを入力し、結果のレコードをdbに保存する関数があるとします。どちらの方法でもオブジェクトにデータを入力するつもりなので、空のオブジェクトをゲッターから取得することは大きな利点になります。そうすれば、どちらの場合でも同じ操作を実行できます。ゲッター関数の結果を使用します。
例:
function saveTheRow($prim_key, $data) {
$row = getRowByPrimKey($prim_key);
// Populate the data here
$row->save();
}
ここでは、同じ一連の操作がこのタイプのすべてのレコードを操作することがわかります。
ただし、戻り値の最終目的がデータの読み取りとデータの処理である場合、nullを返します。これにより、データが返されなかったかどうかを非常に迅速に判断し、適切なメッセージをユーザーに表示できます。
通常、データを取得する関数で例外をキャッチし(エラーメッセージなどを記録できるように...)、キャッチから直接nullを返します。通常、エンドユーザーにとって問題が何であるかは問題ではないため、データを取得する関数にエラーログ/処理を直接カプセル化するのが最善であることがわかります。大企業で共有コードベースを維持している場合は、最も怠laなプログラマーでも適切なエラーロギング/処理を強制できるため、これは特に有益です。
例:
function displayData($row_id) {
// Logging of the error would happen in this function
$row = getRow($row_id);
if($row === null) {
// Handle the error here
}
// Do stuff here with data
}
function getRow($row_id) {
$row = null;
try{
if(!$db->connected()) {
throw excpetion("Couldn't Connect");
}
$result = $db->query($some_query_using_row_id);
if(count($result) == 0 ) {
throw new exception("Couldn't find a record!");
}
$row = $db->nextRow();
} catch (db_exception) {
//Log db conn error, alert admin, etc...
return null; // This way I know that null means an error occurred
}
return $row;
}
それが私の一般的なルールです。これまでのところうまくいきました。
他の人が言ったことをもっと悲惨な方法で...
例外は例外的な状況のためのものです
このメソッドが純粋なデータアクセスレイヤーである場合、selectステートメントに含まれるパラメーターが与えられた場合、オブジェクトを構築する行が見つからないことが予想されるため、nullを返すことは許容されると考えられます。データアクセスロジックです。
一方、パラメータが主キーを反映することを期待し、one row backのみを取得する必要がある場合、2つ以上取得すると例外をスローします。 0はnullを返しても問題ありませんが、2はnullを返しません。
ここで、LDAPプロバイダーに対してチェックし、さらに詳細を取得するためにDBに対してチェックするログインコードがあり、それらが常に同期していることを期待していた場合、例外を投げることができます。他の人が言ったように、それはビジネスルールです。
これはgeneralルールだと言います。あなたはそれを破りたい時があるかもしれません。ただし、C#(その多く)とJava(その少し)を使用した私の経験と実験から、muchより高いパフォーマンスが得られることがわかりました。条件付きロジックを介して予測可能な問題を処理するよりも、例外に対処するため。私は、場合によっては2桁または3桁高価な曲を話しています。そのため、コードがループになる可能性がある場合は、nullを返してテストすることをお勧めします。
返されるオブジェクトが反復可能なものである場合、空のオブジェクトを返すため、最初にnullをテストする必要はありません。
例:
bool IsAdministrator(User user)
{
var groupsOfUser = GetGroupsOfUser(user);
// This foreach would cause a run time exception if groupsOfUser is null.
foreach (var groupOfUser in groupsOfUser)
{
if (groupOfUser.Name == "Administrators")
{
return true;
}
}
return false;
}
どのメソッドからもnullを返さず、代わりにOption機能タイプを使用するのが好きです。結果を返さないメソッドは、nullではなく空のOptionを返します。
また、結果を返さないようなメソッドは、名前でそれを示す必要があります。通常、メソッドの名前の先頭にTryまたはTryGetまたはTryFindを追加して、空の結果が返される可能性があることを示します(TryFindCustomer、TryLoadFileなど)。
これにより、呼び出し側は、コレクションパイプライン(Martin Fowlerの Collection Pipeline を参照)などのさまざまな手法を結果に適用できます。
次に、nullではなくOptionを返すことでコードの複雑さを軽減する別の例を示します。 循環的複雑さを軽減する方法:Option Functional Type
コードベースの健全性のために、関数はnullを返すべきではないと思います。いくつかの理由が考えられます。
Null参照if (f() != null)
を扱う大量のガード句があります。
null
とは何ですか、受け入れられた答えですか、それとも問題ですか? nullは特定のオブジェクトの有効な状態ですか? (あなたがコードのクライアントであると想像してください)。すべての参照型がnullになる可能性がありますが、そうすべきですか?
null
がぶらぶらしていると、コードベースが大きくなるにつれて、ほとんどの場合、予期しないNullRef例外が時々発生します。
tester-doer pattern
または関数型プログラミングからoption type
を実装するいくつかのソリューションがあります。
Grindの詳細:私のDALがGetPersonByIDに対してNULLを返すとしましょう。 NULLを受け取った場合、(かなり薄い)BLLは何をすべきですか?そのNULLを渡して、最終消費者に心配させてください(この場合、ASP.Netページ)。 BLLに例外をスローさせるのはどうですか?
BLLはASP.NetとWin App、または別のクラスライブラリで使用されている可能性があります-メソッドGetPersonByIDがnullを返すことを最終消費者が本質的に「知る」ことを期待するのは不公平だと思います(nullタイプが使用されていない限り、 )。
私の考え(価値がある)は、何も見つからない場合、DALはNULLを返すということです。一部のオブジェクトについては、それは大丈夫です-それは0:多くの物のリストである可能性がありますので、何も持たないことは問題ありません(例えば、お気に入りの本のリスト)。この場合、BLLは空のリストを返します。単一のエンティティ(ユーザー、アカウント、請求書など)がない場合、それは間違いなく問題であり、コストのかかる例外がスローされます。ただし、アプリケーションによって以前に与えられた一意の識別子でユーザーを取得すると、常にユーザーが返されるはずであり、例外は例外として「適切な」例外です。 BLL(ASP.Net、f'rinstance)の最終消費者は、物事がおかしなことを期待するだけなので、try-catchブロックでGetPersonByIDへのすべての呼び出しをラップする代わりに、未処理の例外ハンドラーが使用されます。
私のアプローチに明白な問題がある場合、私は常に学びたいと思っているので私に知らせてください。他のポスターが言っているように、例外は高価なものであり、「最初にチェックする」アプローチは良いですが、例外はまさにそれであるべきです-例外的。
私はこの投稿を楽しんでいます。「依存する」シナリオに関する多くの良い提案があります:-)
null
に向かう傾向があるここのほとんどの投稿に同意します。
私の理由は、nullを許可しないプロパティを持つ空のオブジェクトを生成するとバグが発生する可能性があるということです。たとえば、int ID
プロパティを持つエンティティの初期値はID = 0
で、これは完全に有効な値です。そのオブジェクトが、ある状況下でデータベースに保存された場合、それは悪いことです。
イテレータを使用する場合は、常にalways空のコレクションを使用します。何かのようなもの
foreach (var eachValue in collection ?? new List<Type>(0))
私の意見ではコードの匂いです。コレクションプロパティはnullであってはなりません。
エッジケースはString
です。 String.IsNullOrEmpty
は必ずしも必要ではありませんが、空の文字列とnullを常に区別できるとは限りません。さらに、一部のデータベースシステム(Oracle)ではまったく区別されないため(''
はDBNULL
として格納されます)、それらを均等に処理する必要があります。その理由は、ほとんどの文字列値はユーザー入力または外部システムから取得されますが、テキストボックスもほとんどの交換フォーマットも''
とnull
の表現が異なるためです。したがって、ユーザーが値を削除したい場合でも、入力コントロールをクリアする以上のことはできません。また、nullableとnonnullable nvarchar
データベースフィールドの区別は疑わしいものではありません。DBMSがOracleではない場合-''
を許可する必須フィールドは奇妙で、UIはこれを許可しないため、制約はマップされません。私の意見では、ここでの答えは、常に等しく扱うことです。
例外とパフォーマンスに関する質問について:プログラムロジックで完全に処理できない例外をスローした場合、ある時点でプログラムが実行していることを中止し、ユーザーに実行したことをやり直すように依頼する必要があります。その場合、catch
のパフォーマンスペナルティは、実際にあなたの心配の最小です-ユーザーに尋ねる必要があるのは部屋の象です(つまり、UI全体を再レンダリングするか、インターネット経由でHTMLを送信することを意味します) )。したがって、「 例外を伴うプログラムフロー 」のアンチパターンに従わない場合は、気にせず、意味がある場合は1つだけ投げてください。 「Validation Exception」などの境界線の場合でも、いずれにしてもユーザーに再度質問する必要があるため、パフォーマンスは実際には問題になりません。
「IsItThere()」メソッドと「GetItForMe()」メソッドの2つのメソッドが必要であると言う答え(Web全体)に困惑しているため、競合状態になります。 nullを返し、変数に割り当て、1回のテストでNullの変数をすべてチェックする関数の何が問題になっていますか?私の以前のCコードは
if(NULL!=(variable = function(arguments ...))){
そのため、変数の値(またはnull)と結果を一度に取得します。このイディオムは忘れられましたか?どうして?
同期メソッドの場合、 @ Johann Gerell'sanswer はパターンであり、すべての場合に使用するものです。
ただし、out
パラメーターを指定したTryGetパターンは、非同期メソッドでは機能しません。
C#7のTuple Literalsを使用すると、これを行うことができます。
async Task<(bool success, SomeObject o)> TryGetSomeObjectByIdAsync(Int32 id)
{
if (InternalIdExists(id))
{
o = await InternalGetSomeObjectAsync(id);
return (true, o);
}
else
{
return (false, default(SomeObject));
}
}