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


40

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

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

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

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

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


43
「誰かがこのメソッドを呼び出している場合、データを渡すつもりである」という仮定は正しいですか?たぶん、彼らは結果を処理するために手に入れたものを渡しているだけかもしれません。
SpaceTrucker 14年

7
ところで:通常、「変換」メソッドは「マップ」と呼ばれますが、.NET(および.NETが名前を取得したSQL)では一般に「選択」と呼ばれます。「変換」はC ++が呼び出すものですが、それは一般的な名前ではありません。
ヨルグWミットタグ14年

25
コレクションがnull空の場合ではなく、コレクションの場合はスローします。
CodesInChaos 14年

21
@NickUdell-常に空のコレクションをコードに渡します。コレクションに進む前にコレクションに何も追加できなかった場合、特別な分岐ロジックを記述するよりも、コードが同じ動作をする方が簡単です。
theMayer

7
コレクションが空の場合にチェックしてエラーをスローすると、空のコレクションをメソッドに渡さずに、呼び出し元にもチェックを強制します。検証はシステムの境界上で(そしてその上でのみ)行う必要があります。空のコレクションが気になる場合は、ユーティリティメソッドに到達するずっと前にそれらを既にチェックしておく必要があります。これで2つの冗長チェックができました。
ライライアン14年

回答:


175

ユーティリティメソッドは、空のコレクションではスローしないでください。APIクライアントはそれを嫌います。

コレクションは空にすることができます。「空でなくてはならないコレクション」は、概念的には作業がはるかに難しいものです。

空のコレクションを変換すると、明らかな結果が得られます。空のコレクションです。(パラメーター自体を返すことで、ゴミを節約することもできます。)

モジュールが何かで既に満たされているかもしれないし、そうでないかもしれないもののリストを維持する多くの状況があります。呼び出しのたびに空をチェックしなければならないのtransformは面倒で、単純でエレガントなアルゴリズムをい混乱に変える可能性があります。

効用メソッドは、常に入力が寛容で、出力が保守的であるように努める必要があります。

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


14
+1-(できればもっと)。null空のコレクションに合体することさえできるかもしれません。暗黙的な制限のある関数は苦痛です。
テラスティン14年

6
「あなたはすべきではない」...それらを受け入れるべきではないか、例外をスローすべきではないのですか?
ベンアーロンソン14年

16
@Andy-これらはユーティリティメソッドではありません。それらは、ビジネスメソッドまたは同様の具体的な動作です。
テラスティン

38
空のコレクションを返品のためにリサイクルすることは良い考えだとは思いません。ほんの少しのメモリしか保存していません。呼び出し元で予期しない動作が発生する可能性があります。少し工夫された例:Populate1(input); output1 = TransformNodes(input); Populate2(input); output2 = TransformNodes(input); Populate1がコレクションを空のままにして、最初のTransformNodes呼び出しでそれを返した場合、output1とinputは同じコレクションになり、Populaten2が呼び出されたときにコレクションにノードを入れた場合、2番目になりますoutput2の入力のセット。
ダン・ニーリー

6
@Andyそれでも、引数が空にならない場合-処理するデータがないことがエラーである場合- 引数を照合する場合、これをチェックするのは呼び出し側の責任であると感じています。このメソッドをより洗練された状態に保つだけでなく、より良いエラーメッセージ(より良いコンテキスト)を生成し、フェイルファーストでそれを発生させることができることを意味します。下流のクラスは...呼び出し側の不変量を確認することは意味がありません
アンジェイ・ドイル

26

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

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

1.有意義なリターン

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

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

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

2.スタイル

一般的なコード(またはプロジェクトのコードまたはチームのコード)は機能的なスタイルを好みますか?いいえの場合、例外が予想され、対処されます。はいの場合、Option Typeを返すことを検討してください。オプションタイプでは、意味のある回答またはNone / Nothingを返します。上記の3番目の例では、FPスタイルでは何も良い答えになりません。関数がオプションタイプを返すという事実は、意味のある答えよりも発信者に明確に信号を送ることができない可能性があり、発信者はそれに対処する準備をする必要があります。私はそれが発信者に多くのオプションを与えると思います(もしあなたがしゃれを許すなら)。

F#は、すべてのクールな.Netキッズがこのようなことを行う場所ですが、C# このスタイルをサポートしています。

tl; dr

他の誰かからの完全に予見可能な(そして合法的な)入力ではなく、あなた自身のコードパスにおける予期しないエラーの例外を保持してください。


「ベストフィット」など:何らかの基準に一致するコレクション内のベストフィットオブジェクトを見つけるメソッドがあります。そのメソッドは、空でないコレクションに対しても同じ問題を抱えます。何も基準に適合しない可能性があるため、空の選択について心配する必要がないからです。
gnasher729

1
いいえ、だからこそ、「適合」ではなく「最適」と言ったのです。このフレーズは、完全一致ではなく、最も近い近似を意味します。最大/最小オプションが手がかりになっているはずです。「最適」のみが要求された場合、1つ以上のメンバーを持つコレクションはメンバーを返すことができます。メンバーが1つだけの場合は、それが最適です。
itsbruce

1
ほとんどの場合、「null」を返すのは、例外をスローするよりも悪いです。ただし、空のコレクションを返すことには意味があります。
イアン14年

1
単一のアイテム(最小/最大/頭/尾)を返さなければならない場合ではありません。nullに関しては、私が言ったように、私はそれを嫌います(そしてそれを持たない言語を好む)が、言語がそれを持っている場合、あなたはそれを扱い、それを手渡すコードを持たなければなりません。ローカルコーディング規則によっては、適切な場合があります。
itsbruce 14年

あなたの例は、空の入力とは何の関係もありません。それらは、答えが「何もない」かもしれない状況の特別なケースです。これは、空のコレクションを「処理する」方法に関するOPの質問に答える必要があります。
djechlin 14年

18

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

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

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

データベースの世界からいくつかのテストロジックを借用します。ゼロアイテム、1アイテム、2アイテムをテストします。それは最も重要なケースに対応します(不正な形式の内部またはデカルト結合条件をすべて消去します)。


コレクションにアイテムがないことが無効な場合、理想的には引数はでなくjava.util.Collectionカスタムcom.foo.util.NonEmptyCollectionクラスであり、この不変式を一貫して保持し、最初から無効な状態にならないようにすることができます。
アンドジェジドイル14年

3
この質問にはタグが付けられています[c#]
Nick Udell 14年

@NickUdellは、C#がオブジェクト指向プログラミングまたはネストされた名前空間の概念をサポートしていませんか?TIL。
ジョン・ドヴォルザーク

2
します。Andrzejが混乱しているに違いないので、私のコメントは明確化されました(他の理由で、この質問が参照しない言語に名前空間を指定する努力をするのでしょうか?)。
ニック・Udell

-1は、「ほぼ確実にイエス」よりも「依存する」ことを強調したためです。冗談はありません-間違ったことを伝えることはここで損害を与えます。
djechlin 14年

11

適切な設計の問題として、入力のバリエーションをできるだけ実用的または可能な限り受け入れます。例外は(受け入れられない入力が提示されるか、処理中に予期しないエラーが発生する)、プログラムが予測可能な方法で結果として続行できない場合にのみスローされます。

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

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


あなたの大胆な発言は、完全に一般的な非常に悪いアドバイスです。ここでは真実だと思いますが、この状況の特別な点を説明する必要があります。(サイレント障害を促進するため、一般的には悪いアドバイスです。)
djechlin 14年

@AAA-明らかに、ここでソフトウェアを設計する方法についての本を書いているわけではありませんが、私が言っていることを本当に理解しているなら、それは悪いアドバイスではないと思います。私のポイントは、不正な入力があいまいなまたは任意の出力を生成した場合、例外をスローする必要があるということです。出力が完全に予測可能な場合(ここでのように)、例外のスローは任意です。重要なのは、予測できない動作が発生する場所であるため、プログラムでin意的な決定を行わないことです。
theMayer

しかし、それはあなたの最初の文であり、唯一の強調された文です...あなたが言っていることを誰もまだ理解していません。(私は、ISが書き込みに学び、通信し、私がキーポイントの精度の損失が有害であることを行う行うには私たちがここにいる事のひとつ、ここであまりにも積極的になろうとしていないよ。)
djechlin

10

機能を分離して見ているだけでは、適切なソリューションを見つけるのははるかに困難です。あなたの機能をより大きな問題の一部と考えてください。その例の1つの可能な解決策は、次のようになります(Scalaで)。

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

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

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

もちろん、空の出力リストには複数の意味がないことを前提としています。空の入力によって引き起こされる空の出力と、変換自体によって引き起こされる空の出力を区別する必要がある場合、それは物事を完全に変えます。


+1(非常に効果的な例)(他の良い答えが欠けています)。
djechlin 14年

2

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

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

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

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

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


1

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

メソッドに名前が付けられMax、最大要素を返すように設計されている場合、NoSuchElementException空のコレクションをスローするのは自然なことです。最大値は数学的意味がないためです。

メソッドが名前付けJoinSqlColumnNamesされ、SQLクエリで使用するために要素がコンマで結合された文字列を返すように設計されている場合、IllegalArgumentException空のコレクションをスローすることは理にかなっています。 SQLクエリの文字列は、さらにチェックすることなく直接返されます。返された空の文字列をチェックする代わりに、空のコレクションを実際にチェックする必要があります。


Max多くの場合、負の無限大はありません。
djechlin

0

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

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

  • エラーを示す/返す/投げる そのクラスのエラーに対するコードベースの規則を使用します。
  • ゼロなどの値が返されることを文書化する
  • 指定された無効な値が返されることを文書化する(例:NaN)
  • マジック値が返されることを文書化する(例えば、型の最小値または最大値、または希望的に示す値)
  • 結果が指定されていないことを宣言する
  • アクションが未定義であることを宣言する

無効な入力が行われ、リクエストを完了できない場合は、エラーを返すと言います。彼らはあなたのプログラムの要件を理解するので、私は初日からハードエラーを意味します。結局のところ、あなたの機能は応答する立場にありません。操作失敗する可能性がある場合(たとえば、ファイルをコピーする場合)、APIはそれらに対処できるエラーを与える必要があります。

そのため、不正なリクエストや失敗する可能性のあるリクエストをライブラリがどのように処理するかを定義できます。

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

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


6
最後の例では、何がナンセンスかを判断するのはその機能の仕事ではありません。したがって、空の文字列はfalseを返す必要があります。実際、それはFile.Existsがとる正確なアプローチです
theMayer 14年

@ rmayer06それはその仕事です。参照される関数は、null、空の文字列を許可し、文字列内の無効な文字をチェックし、文字列の切り捨てを実行し、ファイルシステム呼び出しを行う必要があり、環境を照会する必要がある場合があります。高コストであり、間違いなく正確性の線(IMO)を曖昧にします。私はたくさんのif (!FileExists(path)) { ...create it!!!... }バグを見てきました-それらの多くはコミットする前にキャッチされるでしょうが、正確さの線はぼやけていませんでした。
ジャスティン14年

2
関数の名前がFile.ThrowExceptionIfPathIsInvalidである場合、私はあなたに同意しますが、そのような関数でそのような関数を呼び出すのは誰ですか?
theMayer

関数がどのように定義されているのかという理由だけで、平均0個のアイテムは既にNaNを返すか、ゼロ除算例外をスローします。存在しない可能性のある名前のファイルは、存在しないか、すでにエラーを引き起こしています。これらのケースを特別に処理する説得力のある理由はありません。
cHao 14年

とにかく、ファイルを読み書きする前にファイルの存在をチェックするという概念全体に欠陥があると主張することさえできます。(固有の競合状態があります。)先に進んで、読み取りのためにファイルを開くか、書き込み用に作成専用の設定でファイルを開くことをお勧めします。ファイル名が無効であるか、存在しない場合(または書き込みの場合は存在する場合)、すでに失敗します。
cHao 14年

-3

経験則として、標準関数は入力の最も広範なリストを受け入れ、それについてフィードバックを提供できるはずです。プログラマーが設計者が計画しなかった方法で関数を使用する多くの例があります。空のコレクションだけでなく、幅広い入力タイプを受け入れて、入力に対して実行された操作のエラーオブジェクトであっても、正常にフィードバックを返すことができる必要があります...


コレクションが常に渡されると想定し、コレクションを繰り返し処理することを設計者が想定することは絶対に間違っています。受け取った引数が期待されるデータ型に準拠していることを確認するためにチェックを行う必要があります...
Clement Mark-Aaba

3
「広範囲の入力タイプ」はここでは無関係に見えます。質問には、強く型付けされた言語であるc#がタグ付けされています。すなわち、入力がコレクションであることを、コンパイラの保証である
ブヨ

親切に「経験則」をグーグル、私の答えは...あなたが不慮または誤った出来事のための部屋を作るプログラマとして、これらのいずれかが誤って関数の引数を渡すことができるという一般注意した
クレメントマークAABA

1
これは間違っているか、トートロジー的です。writePaycheckToEmployee入力として負の数を受け入れるべきではありません...しかし、「フィードバック」が「次に起こるかもしれない何かをする」ことを意味する場合、はい、すべての関数は次に行うことをします。
djechlin 14年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.