web-dev-qa-db-ja.com

それらを反復するメソッドで空のコレクションを受け入れる必要がありますか?

すべてのロジックがメソッドのパラメーターを反復するforeachループ内で実行されるメソッドがあります。

public IEnumerable<TransformedNode> TransformNodes(IEnumerable<Node> nodes)
{
    foreach(var node in nodes)
    {
        // yadda yadda yadda
        yield return transformedNode;
    }
}

この場合、空のコレクションを送信すると空のコレクションになりますが、それが賢明ではないかと思います。

ここでの私の論理は、誰かがこのメソッドを呼び出している場合、データを渡すつもりであり、誤った状況では空のコレクションを私のメソッドにのみ渡すということです。

この動作をキャッチして例外をスローする必要がありますか、それとも空のコレクションを返すのがベストプラクティスですか?

40
Nick Udell

ユーティリティメソッドは空のコレクションをスローするべきではありません。 APIクライアントはそれを嫌います。

コレクションは空にすることができます。 「空にする必要のないコレクション」は、概念的には操作がはるかに困難です。

空のコレクションの変換には明らかな結果があります。空のコレクションです。 (パラメーター自体を返すことで、ガベージを節約することもできます。)

モジュールが、すでに何かで満たされているかどうかわからないもののリストを維持する多くの状況があります。 transformを呼び出す前に毎回空をチェックする必要があるのは面倒で、シンプルでエレガントなアルゴリズムを醜い混乱に変える可能性があります。

ユーティリティメソッドは常に、入力ではリベラルであり、出力では保守的であるように努力する必要があります。

これらすべての理由から、神のために、空のコレクションを正しく処理します。 考えるそれはあなたが何をしたいかよりもあなたが何が欲しいかを知っているヘルパーモジュールほど苛立たしいものはありません。

175
Kilian Foth

これに対する答えを決定する2つの重要な質問が表示されます。

  1. 空のコレクション(nullを含む)を渡すと、関数は意味のある論理的なものを返すことができますか?
  2. このアプリケーション/ライブラリ/チームの一般的なプログラミングスタイルは何ですか? (具体的には、FPお元気ですか?)

1。意味のあるリターン

基本的に、意味のあるものを返すことができる場合は、例外をスローしないでください。発信者に結果を処理させます。だからあなたの機能...

  • コレクション内の要素の数をカウントし、0を返します。これは簡単でした。
  • 特定の基準に一致する要素を検索し、空のコレクションを返します。何も投げないでください。呼び出し元はコレクションの膨大なコレクションを持っている可能性があります。呼び出し元は、任意のコレクション内の任意の一致する要素を必要としています。例外は、発信者の生活を困難にするだけです。
  • リスト内で最大/最小/基準に最適なものを探しています。おっとっと。スタイルの質問によっては、ここで例外をスローするか、nullを返すことができます。私はnullが嫌いです(FP person)ですが、ここでは間違いなく意味があり、独自のコード内の予期しないエラーの例外を予約できます。呼び出し側がnullをチェックしない場合、いずれにせよ、かなり明確な例外が発生しますが、それは彼らに任せてください。
  • コレクションのn番目のアイテム、または最初/最後のnアイテムを要求します。これは、まだ例外の最良のケースであり、呼び出し元に混乱や困難を引き起こす可能性が最も低いケースです。 nullについては、前のポイントで示したすべての理由により、あなたとあなたのチームがそれをチェックすることに慣れている場合でも、ケースを作成できますが、これはDudeYouKnowYouShouldCheckTheSizeFirst例外をスローするための、まだ最も有効なケースです。スタイルがより機能的である場合は、nullまたはmyStyle答えなさい。

一般に、my FPバイアスは「意味のある何かを返す」ことを示し、nullはこの場合有効な意味を持つことができます。

2。スタイル

一般的なコード(またはプロジェクトのコードまたはチームのコード)は、機能的なスタイルを支持していますか?いいえの場合、例外が予想され、処理されます。はいの場合、 オプションの種類 を返すことを検討してください。オプションタイプでは、意味のある答えを返すか、None/Nothingを返します。上記の3番目の例では、NothingがFPスタイルで適切な答えになります。関数が返す事実Optionタイプは、意味のある回答が不可能である可能性があることを発信者に明確に知らせ、発信者はそれに対処する準備をする必要があることを通知します。

F#は、すべてのクールな.Net子供がこの種のことを行う場所ですが、C#が行うsupport このスタイルです。

tl; dr

予期しないエラーの例外は、自分のコードパスで保持してください。他人からの予測可能な(そして合法的な)入力ではありません。

26
itsbruce

相変わらず、それは依存します。

matterコレクションは空ですか?
ほとんどのコレクション処理コードはおそらく「ノー」と言うでしょう。コレクションには任意の数のアイテムを含めることができます含むゼロ。

さて、中にアイテムがないことが「無効」であるようなコレクションがある場合、それは新しい要件であり、それに対して何をするかを決定する必要があります。

データベースの世界からいくつかのテストロジックを借用します:zero items、one itemおよびtwo itemsのテスト。これは、最も重要なケース(不適切な形式の内部結合またはデカルト結合の条件をすべて洗い流す)に対応します。

18
Phill W.

優れた設計の問題として、入力の変動をできるだけ実用的または可能な限り受け入れます。例外はonlyがスローされる必要があります(許容できない入力が提示されたOR処理中に予期しないエラーが発生した)ANDプログラムは予測可能な方法で続行できません結果として。

この場合、空のコレクションが表示され、コードはそれを処理する必要があります(すでに処理されています)。コードがここで例外をスローした場合、それはすべての違反になります。これは、数学で0を0で乗算するようなものです。これは冗長ですが、動作するためには絶対に必要です。

次に、nullコレクション引数に移ります。この場合、nullコレクションはプログラミングの誤りです。プログラマーが変数を割り当てるのを忘れていました。これは例外をスローする可能性のあるケースです。例外を出力に有意義に処理できず、そうしようとすると予期しない動作が発生します。これは数学でゼロで割ることに似ています-それは完全に無意味です。

11
theMayer

関数を単独で見ているだけでは、正しい解決策を見つけるのははるかに困難です。関数を より大きな問題 の一部と見なしてください。その例の1つの可能な解決策は次のようになります(Scalaの場合):

_input.split("\\D")
.filterNot (_.isEmpty)
.map (_.toInt)
.filter (x => x >= 1000 && x <= 9999)
_

最初に、文字列を非数字で分割し、空の文字列をフィルターで除外し、文字列を整数に変換してから、4桁の数字のみを保持するようにフィルターします。あなたの関数はパイプラインのmap (_.toInt)かもしれません。

パイプラインの各ステージは空の文字列または空のコレクションを処理するだけなので、このコードはかなり単純です。最初に空の文字列を入れると、最後に空のリストが表示されます。すべての呼び出しの後にnullまたは例外を停止して確認する必要はありません。

もちろん、これは空の出力リストが複数の意味を持たないことを前提としています。空の入力が原因の空の出力と変換自体が原因の出力を区別する必要がある必要がある場合は、状況を完全に変更します。

10
Karl Bielefeldt

この質問は実際には例外についてです。そのように見て、実装の詳細として空のコレクションを無視すると、答えは簡単です:

1)続行できない場合、メソッドは例外をスローする必要があります。指定されたタスクを実行できないか、適切な値を返します。

2)メソッドは、失敗にもかかわらず続行できる場合、例外をキャッチする必要があります。

したがって、ヘルパーメソッドは「役立つ」べきではなく、itが空のコレクションでの作業を実行できない場合を除き、例外をスローします。呼び出し元に、結果を処理できるかどうかを判断させます。

空のコレクションを返すかnullを返すかは少し難しいですが、それほど多くはありません。null可能なコレクションは、可能であれば避けてください。 null可能なコレクションの目的は、(SQLのように)情報がないことを示すことです-たとえば、誰かが持っているかどうかはわからないが、あなたが知らない場合、子のコレクションはnullになる可能性があります。彼らが知らないことを知っています。しかし、それが何らかの理由で重要である場合、それを追跡するために追加の変数を使用する価値があります。

2
jmoreno

メソッドの名前はTransformNodesです。空のコレクションを入力として使用する場合、空のコレクションを取得するのは自然で直感的であり、数学的には非常に理にかなっています。

メソッドの名前がMaxで、最大の要素を返すように設計されている場合、空のコレクションに対してNoSuchElementExceptionをスローするのは自然なことです。

メソッドの名前がJoinSqlColumnNamesで、SQLクエリで使用するために要素がコンマで結合されている文字列を返すように設計されている場合、空のコレクションでIllegalArgumentExceptionをスローするのは理にかなっています。呼び出し側は、さらにチェックせずにSQLクエリで文字列を直接使用し、返された空の文字列をチェックする代わりに、空のコレクションを実際にチェックする必要がある場合、最終的にSQLエラーが発生します。

1
janos

一歩下がって、値の配列の算術平均を計算する別の例を使用してみましょう。

入力配列が空(またはnull)の場合、呼び出し元の要求を合理的に満たすことができますか?いいえ。あなたのオプションは何ですか?さて、あなたはできる:

  • エラーを表示/返却/スローします。そのエラーのクラスに対してコードベースの規約を使用します。
  • ゼロなどの値が返されることをドキュメント
  • 指定された無効な値が返されることを文書化します(例:NaN)
  • 魔法の値が返されることを文書化します(例:タイプの最小値または最大値、またはうまくいけば指標となる値)
  • 結果が指定されていないことを宣言します
  • アクションが未定義であることを宣言します
  • 等.

無効な入力があり、リクエストを完了できない場合は、エラーを表示すると言います。プログラムの要件を理解してもらうために、初日からのハードエラーを意味します。結局のところ、関数は応答する立場にありません。操作couldが失敗した場合(ファイルのコピーなど)、APIは処理できるエラーを提供する必要があります。

これにより、ライブラリが不正なリクエストや失敗する可能性のあるリクエストを処理する方法を定義できます。

コードがこれらのクラスのエラーを処理する方法に一貫性があることは非常に重要です。

次のカテゴリは、ライブラリがナンセンスリクエストを処理する方法を決定することです。あなたに似た例に戻りましょう-パスにファイルが存在するかどうかを判別する関数を使用してみましょう:bool FileExistsAtPath(String)。クライアントが空の文字列を渡す場合、このシナリオをどのように処理しますか? void SaveDocuments(Array<Document>)に渡される空またはnullの配列はどうですか?ライブラリ/コードベースを決定し、一貫性がある。私はたまたまこれらのケースのエラーを考慮し、クライアントにエラーとしてフラグを立てることによって(アサーションを介して)ナンセンスなリクエストを行うことを禁止します。一部の人々はその考え/行動に強く抵抗します。このエラー検出は非常に役立ちます。問題のあるプログラムへの局所性が高いため、プログラム内の問題を特定するのに非常に適しています。プログラムはより明確で正確であり(コードベースの進化を考慮して)、何もしない関数内でサイクルを焼き付けないでください。コードはこのように小さく/きれいになり、チェックは通常、問題が発生する可能性のある場所にプッシュされます。

0
justin