質問

関数からデータを返すときのベストプラクティスとは何ですか。 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オブジェクトが役立つ場合があります。

戻り値の型が配列の場合は空の配列を返し、そうでない場合はnullを返します。

特定の契約が破られた場合にのみ例外をスローする必要があります。
既知のIDに基づいてUserEntityを要求する特定の例では、行方不明(削除済み)のユーザーが予想されるケースであるかどうかに応じて異なります。その場合、 null を返しますが、予期されたケースではない場合は、例外をスローします。
関数が UserEntity GetUserByName(string name)と呼ばれた場合、おそらくスローせずにnullを返すことに注意してください。どちらの場合も、空のUserEntityを返すことは役に立ちません。

文字列、配列、コレクションの場合、状況は通常異なります。メソッドは null を「空の」リストとして受け入れますが、 null ではなく長さゼロのコレクションを返すというMSのガイドラインを覚えています。文字列についても同じです。空の配列を宣言できることに注意してください: int [] arr = new int [0];

これはビジネス上の質問であり、特定のGUID IDを持つユーザーの存在がこの関数の予想される通常のユースケースであるか、このメソッドがどのような機能でもアプリケーションが正常に完了することを妨げる異常であるかどうかに依存しますユーザーオブジェクトの提供先...

「例外」の場合、そのIDを持つユーザーが存在しないと、アプリケーションが実行している機能を正常に完了できなくなります(製品を出荷した顧客の請求書を作成するとしますto ...)、この状況ではArgumentException(またはその他のカスタム例外)がスローされます。

行方不明のユーザーが大丈夫である場合(この関数を呼び出すことの潜在的な通常の結果の1つ)、nullを返します...

編集:(別の回答でAdamからのコメントに対処するため)

アプリケーションに複数のビジネスプロセスが含まれ、そのうちの1つ以上が正常に完了するためにユーザーを必要とし、1つ以上がユーザーなしで正常に完了する場合、コールスタックの上位に例外をスローする必要があります。ユーザーを必要とするビジネスプロセスがこの実行スレッドを呼び出している場所に近い。このメソッドとそのポイント(例外がスローされている)の間のメソッドは、ユーザーが存在しないことを通知する必要があります(null、boolean、何でも-これは実装の詳細です)。

しかし、アプリケーション内のすべてのプロセスがユーザーを必要にしても、このメソッドで例外をスローします...

私は個人的にnullを返します。これがDAL /リポジトリレイヤーの動作を期待するからです。

存在しない場合は、オブジェクトの取得に成功したと解釈できるものを返さないでください。 null はここでうまく機能します。

最も重要なことは、DAL / Reposレイヤー全体で一貫性を保つことです。その方法で混乱しないようにしてください。

傾向がある

  • 存在する必要があるかどうかが事前にわからない場合、オブジェクトIDが存在しない場合は、nullを返します
  • throw オブジェクトIDが存在しない場合、存在するはずである

これら3つのタイプのメソッドを使用して、これら2つのシナリオを区別します。 最初:

Boolean TryGetSomeObjectById(Int32 id, out SomeObject o)
{
    if (InternalIdExists(id))
    {
        o = InternalGetSomeObject(id);

        return true;
    }
    else
    {
        return false;
    }
}

2番目:

SomeObject FindSomeObjectById(Int32 id)
{
    SomeObject o;

    return TryGetObjectById(id, out o) ? o : null;
}

3番目:

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();
}

デザインの観点から、私はこのアプローチが本当に好きですが、ファーストクラスの機能を容易にサポートしない言語では呼び出しサイトをより大きくするという欠点があります。

CSLA.NETを使用しますが、データの取得に失敗すると「空」が返されると考えています。オブジェクト。これは、 obj == null ではなく、 obj.IsNew であるかどうかを確認する規則を要求するため、実際には非常に迷惑です。

前述のポスターで述べたように、 null戻り値はコードをすぐに失敗させ、空のオブジェクトが原因のステルス問題の可能性を減らします。

個人的には、 null の方がエレガントだと思います。

これは非常に一般的なケースであり、私はここの人々がそれに驚いているように見えることに驚いています:すべてのWebアプリケーションでは、クエリ文字列パラメーターを使用してデータがフェッチされることがよくあります。 " not found"。

これは次の方法で処理できます。

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を返します。

ただし、ここで言及した特定のインスタンスは、 ユーザーIDでユーザーを検索しています。これは並べ替えです そのユーザーへのキーの、その場合はおそらくしたい ユーザーインスタンスインスタンスがない場合に例外をスローする 見つかりました。

これは私が一般的に従うルールです:

  • 主キー操作による検索で結果が見つからない場合、 ObjectNotFoundExceptionをスローします。
  • 他の基準による検索で結果が見つからなかった場合、 nullを返します。
  • 複数のオブジェクトを返す可能性のある非キー基準による検索で結果が見つからない場合 空のコレクションを返します。

コンテキストに基づいて変化しますが、特定のオブジェクトを探している場合は(例のように)通常nullを返し、オブジェクトのセットを探しているが何もない場合は空のコレクションを返します。

コードに間違いを犯し、nullを返すとnullポインタ例外が発生する場合は、キャッチするのが早いほど良いです。空のオブジェクトを返す場合、最初の使用は機能しますが、後でエラーが発生する可能性があります。

この場合の最良の結果は、「null」を返します。そのようなユーザーがいない場合。また、メソッドを静的にします。

編集:

通常、このようなメソッドはいくつかの「ユーザー」のメンバーです。クラスおよびそのインスタンスメンバへのアクセス権がありません。この場合、メソッドは静的である必要があります。そうでない場合は、「ユーザー」のインスタンスを作成する必要があります。そして、別の" User"を返すGetUserByIdメソッドを呼び出します。インスタンス。これは混乱を招くことに同意します。ただし、GetUserByIdメソッドが一部の" DatabaseFactory"のメンバーである場合クラス-インスタンスメンバーとして残しても問題ありません。

個人的にオブジェクトのデフォルトのインスタンスを返します。その理由は、メソッドがゼロから多数またはゼロから1(メソッドの目的に応じて)を返すことを期待しているからです。このアプローチを使用して、あらゆる種類のエラー状態になる唯一の理由は、メソッドがオブジェクトを返さず、常に(1対多または単数のリターンに関して)期待される場合です。

これがビジネス領域の質問であるという仮定に関しては、方程式のその側面からは見ていません。戻り値の型の正規化は、有効なアプリケーションアーキテクチャの問題です。少なくとも、コーディング慣行の標準化の対象となります。 「シナリオXでnullを与えるだけ」と言うビジネスユーザーがいることは疑わしい。

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ページでエンティティを追加または編集するときに特に使用されます。

これにより、両方の長所が使用されるコンテキストで最高の状態になります。

私はフランスのIT学生ですので、英語が下手です。私たちのクラスでは、そのようなメソッドは決してnullも空のオブジェクトも返すべきではないと言われています。このメソッドのユーザーは、取得しようとする前に、探しているオブジェクトが存在することを最初に確認することになっています。

Javaを使用して、 assert exists(object)を追加するように求められます。"存在しないオブジェクトにアクセスしようとしないでください&quot ;; nullを返して、「前提条件」を表現できます。 (英語の単語が何なのかわかりません)。

IMOこれは実際に使用するのは簡単ではありませんが、それは私が使用しているもので、より良いものを待っています。

ユーザーが見つからないというケースが頻繁に発生し、状況に応じてさまざまな方法で対処したい場合(例外をスローしたり、空のユーザーを置き換えたりする場合)、F#に近いものを使用することもできます Option またはHaskellの Maybe タイプ。「no value」の場合と「found found!」を明示的に分離します。データベースアクセスコードは次のようになります。

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();
}

残念ながら、誰もがこのようなタイプを独自に展開しているようです。

通常、nullを返します。例外をスローすることなく、どこでも大量のtry / catchを使用することなく、何かが失敗したかどうかを検出するための迅速かつ簡単なメカニズムを提供します。

コレクションタイプの場合は空のコレクションを返しますが、他のすべてのタイプの場合は、戻り値のタイプと同じインターフェースを実装するオブジェクトを返すためにNullObjectパターンを使用することを好みます。パターンの詳細については、リンクテキスト

をご覧ください。

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 ""; } ...} 

他の人が言ったことをより悲惨な方法で言うには...

例外は例外的な状況のためです

このメソッドが純粋なデータアクセスレイヤーである場合、selectステートメントに含まれるパラメーターが与えられた場合、オブジェクトを構築する行が見つからないことが予想されるため、nullを返すことはこれはデータアクセスロジックであるため許容可能です。

一方、パラメータが主キーを反映することを期待している場合、 1つの行だけを戻す必要があり、複数戻った場合は例外をスローします。 0はnullを返しても問題ありませんが、2は返しません。

今、LDAPプロバイダーに対してチェックし、さらに詳細を取得するためにDBに対してチェックするログインコードがあり、それらが常に同期していると予想した場合、例外を投げることがあります。他の人が言ったように、それはビジネスルールです。

これは一般ルールです。あなたはそれを破りたい時があるかもしれません。ただし、C#(その多く)とJava(その少し)を使用した私の経験と実験により、条件付きで予測可能な問題を処理するよりも例外を処理する方がはるかにコストが高いことがわかりました論理。場合によっては2桁または3桁高価な曲に話をしています。そのため、コードがループになる可能性がある場合は、nullを返してテストすることをお勧めします。

擬似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;
}

それが私の一般的なルールです。これまでのところうまくいきました。

興味深い質問です。「正しい」ことはないと思います。答えは、常にコードの責任に依存するためです。見つかったデータに問題がないかどうか、メソッドは知っていますか?ほとんどの場合、答えは「いいえ」です。そして、それがnullを返し、呼び出し側に彼の状況を処理させるのが完璧な理由です。

スローするメソッドとnullを返すメソッドを区別するための良い方法は、チームで慣例を見つけることです。取得するものがない場合、例外がスローされます。 nullを返す可能性のあるメソッドの名前は、おそらく&quot; Find ...&quot;とは異なる場合があります。代わりに。

返されるオブジェクトが反復可能なものである場合、空のオブジェクトを返すため、最初に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のコレクションパイプラインを参照してください) )結果について。

nullではなくOptionを返すことでコードの複雑さを軽減する別の例を次に示します。循環的複雑さを軽減する方法:オプション機能タイプ

さらに挽くべき肉:私の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へのすべての呼び出しをラップする代わりに、Unhandled Exception Handlerが使用されます。

私のアプローチに明白な問題がある場合、私はいつも学ぶことを切望しているので私に知らせてください。他のポスターが言っているように、例外は高価なものであり、「最初に確認する」アプローチは良いが、例外はそれだけである必要があります-例外的。

この投稿を楽しんでいます。「依存する」ための良い提案がたくさんあります。シナリオ:-)

コードベースの健全性のために、関数はnullを返すべきではないと思います。いくつかの理由が考えられます:

null参照 if(f()!= null)を処理する大量のガード句があります。

null とは何ですか、受け入れられた答えですか、それとも問題ですか? nullは特定のオブジェクトの有効な状態ですか? (あなたがコードのクライアントであると想像してください)。すべての参照タイプをnullにすることができますが、そうすべきですか?

null をぶらぶらさせると、コードベースが大きくなると、ほとんどの場合、予期しないNullRef例外が時々発生します。

いくつかの解決策があります。 tester-doer pattern 、または関数型プログラミングから option type を実装します。

2つの方法が必要であると言う回答(Web全体)に困惑しています:&quot; IsItThere()&quot;メソッドと&quot; GetItForMe()&quot;メソッドであるため、これは競合状態につながります。 nullを返し、それを変数に割り当て、1回のテストでNullの変数をすべてチェックする関数の何が問題になっていますか?以前のCコードには

がちりばめられていました

if(NULL!=(variable = function(arguments ...))){

したがって、変数の値(またはnull)を取得し、結果を一度に取得します。このイディオムは忘れられましたか?なぜですか?

ここでは、 null の傾向があるほとんどの投稿に同意します。

私の推論では、nullできないプロパティを持つ空のオブジェクトを生成すると、バグが発生する可能性があります。たとえば、 int ID プロパティを持つエンティティの初期値は ID = 0 で、これは完全に有効な値です。そのオブジェクトが、ある状況下でデータベースに保存された場合、それは悪いことです。

イテレータを使用する場合は、空のコレクションを常に使用します。次のようなもの

foreach (var eachValue in collection ?? new List<Type>(0))

私の意見ではコードの匂いです。コレクションプロパティは決してnullにしないでください。

エッジケースは String です。多くの人は、 String.IsNullOrEmpty は必ずしも必要ではないと言いますが、空の文字列とnullを常に区別できるとは限りません。さらに、一部のデータベースシステム(Oracle)ではまったく区別されないため( '' DBNULL として保存されます)、それらを均等に処理する必要があります。その理由は、ほとんどの文字列値はユーザー入力または外部システムからのものであり、テキストボックスもほとんどの交換フォーマットも '' null の表現が異なるためです。したがって、ユーザーが値を削除したい場合でも、入力コントロールをクリアする以上のことはできません。また、DBMSがオラクルでない場合、null可能およびnull不可の nvarchar データベースフィールドの区別は疑いの余地がありません- '' を許可する必須フィールドは奇妙です、あなたのUIこれは許可されないため、制約はマップされません。 私の意見では、ここでの答えは、常に同等に処理することです。

例外とパフォーマンスに関する質問について: プログラムロジックで完全に処理できない例外をスローする場合は、ある時点でプログラムが実行していることを中止し、ユーザーに実行したことをやり直すように依頼する必要があります。その場合、 catch のパフォーマンスペナルティは本当に心配する必要はありません。ユーザーに尋ねる必要があるのは部屋の象です(つまり、UI全体を再レンダリングするか、HTMLを送信することを意味します)インターネット経由)。したがって、&quot; のアンチパターンに従わない場合例外のあるプログラムフロー&quot ;、迷惑をかけないでください。 「検証例外」などの境界線の場合でも、いずれにしても、ユーザーに再度質問する必要があるため、パフォーマンスは実際には問題ではありません。

非同期TryGetパターン:

同期メソッドの場合、 @Johann Gerellの answer は、すべての場合に使用する パターンです。

>

ただし、 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));
    }
}
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top