web-dev-qa-db-ja.com

入力が技術的には有効であるが、満足できない場合の例外と空の結果セット

公開リリースを目的としたライブラリを開発しています。これには、オブジェクトのセットを操作するためのさまざまなメソッド(セットの生成、検査、分割、および新しいフォームへの投影)が含まれています。必要に応じて、それはNuGetパッケージとしてリリースされるIEnumerableのLINQスタイルの拡張機能を含むC#クラスライブラリです。

このライブラリの一部のメソッドには、満足できない入力パラメーターが与えられる可能性があります。たとえば、組み合わせメソッドには、mアイテムのソースセットから構築できるnアイテムのすべてのセットを生成するメソッドがあります。たとえば、次のセットがあるとします。

1、2、3、4、5

2の組み合わせを要求すると、次のようになります。

1、2
1、3
1、4
等...
5、3
5、4

これで、3つのアイテムのセットを与え、4つのアイテムの組み合わせを要求し、各アイテムを1回しか使用できないというオプションを設定するなど、実行できないことを求めることは明らかに可能です。

このシナリオでは、各パラメーターは個別に有効です。

  • ソースコレクションはnullではなく、アイテムが含まれています
  • 要求された組み合わせのサイズは正のゼロ以外の整数です
  • 要求されたモード(各アイテムを一度だけ使用)は有効な選択です

ただし、パラメーターの状態一緒にした場合は問題を引き起こします。

このシナリオでは、メソッドが例外(InvalidOperationExceptionなど)をスローするか、空のコレクションが返されることを期待しますか?どちらも私には有効だと思われます:

  • nアイテムの組み合わせからmアイテムの組み合わせを生成することはできません。ただし、各アイテムの使用のみが許可されている場合はn> mしたがって、この操作は不可能と見なされる可能性があるため、InvalidOperationExceptionとなります。
  • サイズの組み合わせのセットnn> mが空のセットの場合、mアイテムから生成できます。組み合わせは作成できません。

空のセットの引数

私の最初の懸念は、サイズが不明である可能性のあるデータセットを処理しているときに、例外がメソッドの慣用的なLINQスタイルの連鎖を防ぐことです。言い換えれば、次のようなことをしたいと思うかもしれません:

_var result = someInputSet
    .CombinationsOf(4, CombinationsGenerationMode.Distinct)
    .Select(combo => /* do some operation to a combination */)
    .ToList();
_

入力セットが可変サイズの場合、このコードの動作は予測できません。 someInputSetの要素数が4未満のときに.CombinationsOf()が例外をスローする場合、このコードはときどき事前チェックなしで実行時に失敗します。上記の例では、このチェックは簡単ですが、LINQのより長いチェーンの途中で呼び出す場合は、面倒になる可能性があります。空のセットが返された場合、resultは空になります。

例外の引数

私の2番目の懸念は、空のセットを返すことで問題が隠される可能性があることです。このメソッドをLINQのチェーンの途中で呼び出し、静かに空のセットを返す場合、後でいくつかの手順で問題が発生するか、空の状態になることがあります結果セット。これは、入力セットに間違いなく何かがあったとしても、それがどのように起こったかは明らかではないかもしれません。

あなたは何を期待しますか、そしてそれに対するあなたの議論は何ですか?

111
anaximander

空のセットを返す

私は空のセットを期待します:

各数字を1回しか使用できない場合、3つのセットから4つの数字の0の組み合わせがあります。

145
Ewan

疑問がある場合は、誰かに尋ねてください。

あなたのサンプル関数は、Pythonで非常によく似たものです:itertools.combinations。それがどのように機能するか見てみましょう:

>>> import itertools
>>> input = [1, 2, 3, 4, 5]
>>> list(itertools.combinations(input, 2))
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), (2, 5), (3, 4), (3, 5), (4, 5)]
>>> list(itertools.combinations(input, 5))
[(1, 2, 3, 4, 5)]
>>> list(itertools.combinations(input, 6))
[]

そして、それは私には完璧に感じます。反復できる結果を期待していたので、1つ取得しました。

しかし、明らかに、あなたが愚かなことを尋ねるなら:

>>> list(itertools.combinations(input, -1))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: r must be non-negative

つまり、すべてのパラメータが検証されても、結果が空のセットで空のセットを返す場合、それを行うのはあなただけではありません。


コメントの @ Bakuri で述べたように、これはSELECT <columns> FROM <table> WHERE <conditions>のようなSQLクエリでも同じです。 <columns><table><conditions>が正しい形式であり、既存の名前を参照している限り、互いに除外する一連の条件を構築できます。結果のクエリは、InvalidConditionsErrorをスローする代わりに、行を生成しません。

80

簡単に言えば:

  • エラーがある場合は、例外を発生させる必要があります。これには、エラーが発生した場所を正確に把握するために、単一の連鎖呼び出しではなく、段階的に処理を行うことが含まれる場合があります。
  • エラーはないが結果のセットが空の場合、例外を発生させず、空のセットを返します。空のセットは有効なセットです。
71

Ewanの答え に同意しますが、具体的な理由を追加したいと思います。

あなたは数学的な操作を扱っているので、同じ数学的な定義に固執することは良いアドバイスかもしれません。数学的な観点から、rの数-nのセット- set(つまりnCr)はすべてのr> n> = 0に対して明確に定義されています。これはゼロです。したがって、空のセットを返すことは、数学的な観点から予想されるケースです。

54
sigy

例外を使用するかどうかを判断する良い方法は、トランザクションに関与している人々を想像することです。

例としてファイルの内容を取得する場合:

  1. 「does n't exist.txt」というファイルの内容を取得してください

    a。 「これが内容です:空の文字コレクション」

    b。 「えーと、問題があり、そのファイルは存在しません。どうすればいいのかわかりません!」

  2. 「存在するが空です.txt」というファイルの内容を取得してください

    a。 「これが内容です:空の文字コレクション」

    b。 「えーと、問題があり、このファイルには何もありません。どうすればいいのかわかりません!」

間違いなく一部は同意しないでしょうが、ほとんどの人にとって、ファイルが存在しない場合は「えーと、問題があります」というのは理にかなっており、ファイルが空の場合は「空の文字のコレクション」を返します。

だからあなたの例に同じアプローチを適用する:

  1. {1, 2, 3}の4項目のすべての組み合わせを教えてください

    a。何もありません。空のセットがあります。

    b。問題があります。どうすればよいかわかりません。

繰り返しますが、「null」がアイテムのセットとして提供されている場合、「問題があります」は理にかなっていますが、「ここは空のセットです」は上記の要求に対する賢明な応答のようです。

空の値を返すと問題がマスクされる場合(たとえば、ファイルが見つからない、null)、通常は代わりに例外を使用する必要があります(選択した言語がoption/maybeタイプをサポートしている場合を除き、 )。それ以外の場合、空の値を返すと、コストが単純化され、最小の驚きの原則に準拠しやすくなります。

24
David Arno

汎用ライブラリなので、本能はエンドユーザーが選択できるようにします

Parse()TryParse()を使用できるように、関数から必要な出力に応じて使用するオプションを指定できます。関数の単一のバージョンを選択することを主張するよりも、関数ラッパーを記述して維持し、例外をスローする時間を短縮できます。

9
James Snell

関数が呼び出されるときに提供される引数を検証する必要があります。そして実際のところ、無効な引数の処理方法を知りたいと思っています。複数の引数が相互に依存しているという事実は、引数を検証するという事実を補うものではありません。

したがって、何が問題だったかをユーザーが理解するために必要な情報を提供するArgumentExceptionに投票します。

例として、Linqのpublic static TSource ElementAt<TSource>(this IEnumerable<TSource>, Int32)関数を確認します。インデックスが0未満であるか、ソース内の要素数以上である場合、ArgumentOutOfRangeExceptionをスローします。したがって、インデックスは、呼び出し元によって提供された列挙型に関して検証されます。

4
sbecker

次のいずれかを実行する必要があります(ただし、負の数の組み合わせなどの基本的な問題を常に発生させ続けます)。

  1. 2つの実装を提供します。1つは入力が無意味な場合に空のセットを返し、もう1つはスローします。 CombinationsOfおよびCombinationsOfWithInputCheckと呼んでください。またはあなたが好きなもの。これを逆にすると、入力チェックの方が短い名前になり、リストの方がCombinationsOfAllowInconsistentParametersになります。

  2. Linqメソッドの場合、概説した正確な前提で空のIEnumerableを返します。次に、これらのLinqメソッドをライブラリに追加します。

    public static class EnumerableExtensions {
       public static IEnumerable<T> ThrowIfEmpty<T>(this IEnumerable<T> input) {
          return input.IfEmpty<T>(() => {
             throw new InvalidOperationException("An enumerable was unexpectedly empty");
          });
       }
    
       public static IEnumerable<T> IfEmpty<T>(
          this IEnumerable<T> input,
          Action callbackIfEmpty
       ) {
          var enumerator = input.GetEnumerator();
          if (!enumerator.MoveNext()) {
             // Safe because if this throws, we'll never run the return statement below
             callbackIfEmpty();
          }
          return EnumeratePrimedEnumerator(enumerator);
       }
    
       private static IEnumerable<T> EnumeratePrimedEnumerator<T>(
          IEnumerator<T> primedEnumerator
       ) {
          yield return primedEnumerator.Current;
          while (primedEnumerator.MoveNext()) {
             yield return primedEnumerator.Current;
          }
       }
    }
    

    最後に、次のように使用します。

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .ThrowIfEmpty()
       .Select(combo => /* do some operation to a combination */)
       .ToList();
    

    またはこのように:

    var result = someInputSet
       .CombinationsOf(4, CombinationsGenerationMode.Distinct)
       .IfEmpty(() => _log.Warning(
          $@"Unexpectedly received no results when creating combinations for {
             nameof(someInputSet)}"
       ))
       .Select(combo => /* do some operation to a combination */)
       .ToList();
    

    Linqチェーンが作成されたときに列挙されるときではなく、作成されたときにスローまたはアクションの動作が発生するためには、パブリックメソッドとは異なるプライベートメソッドが必要であることに注意してください。あなたがすぐに投げたい.

    ただし、アイテムがあるかどうかを判断するには、少なくとも最初のアイテムを列挙する必要があります。これは、将来の視聴者がThrowIfEmptyメソッドhasで少なくとも1つのアイテムを列挙することを非常に簡単に推論できるため、そうではありませんそうすることに驚かされます。しかし、あなたは決して知りません。これをより明確にすることができますThrowIfEmptyByEnumeratingAndReEmittingFirstItem。しかし、それは巨大な過剰のようです。

#2は素晴らしいと思います。これで、呼び出し元のコードに力が加わり、コードの次のリーダーは、コードが何をしているかを正確に理解し、予期しない例外に対処する必要がなくなります。

3
ErikE

両方のユースケースの引数を見ることができます-ダウンストリームコードがデータを含むセットを期待する場合、例外は素晴らしいです。一方、これが予想される場合は、単に空のセットで十分です。

これがエラーであるか、許容できる結果であるかは、呼び出し側の期待に依存すると思います。そのため、選択を呼び出し側に転送します。多分オプションを導入しますか?

.CombinationsOf(4, CombinationsGenerationMode.Distinct, Options.AllowEmptySets)

2
Christian Sauer

明確な答えがないかどうかを判断するには、2つの方法があります。

  • 最初に1つのオプションを想定し、次に他のオプションを想定してコードを記述します。どちらが実際に最もよく機能するかを検討してください。

  • 「厳密な」ブールパラメータを追加して、パラメータを厳密に検証するかどうかを示します。たとえば、JavaのSimpleDateFormatにはsetLenientメソッドがあり、形式と完全に一致しない入力の解析を試みます。もちろん、デフォルトを決定する必要があります。

2
JollyJoker

あなた自身の分析に基づいて、空のセットを返すことは明らかに正しいようです-あなたはそれを一部のユーザーが実際に望むかもしれないものとして特定し、ユーザーがこれを使いたいと想像することはできないので、いくつかの使用を禁止する罠に陥っていません。そのように。

一部のユーザーが空ではない戻りを強制したい場合があると本当に感じた場合は、全員に強制するのではなく、その動作に対してaskを実行する方法を提供してください。たとえば、次のようにします。

  • ユーザーのアクションを実行しているオブジェクトの構成オプションにします。
  • ユーザーがオプションで関数に渡すことができるフラグにします。
  • チェーンに入れることができるAssertNonemptyチェックを提供します。
  • 空でないことを表明する関数と、空でないことを表明する関数の2つの関数を作成します。
2
user122173

それは本当にユーザーが何を期待するかに依存します。コードが除算を実行する(やや無関係な)例の場合、ゼロで除算すると、例外をスローするか、InfまたはNaNを返すことができます。ただし、どちらも正しいか間違っていますか。

  • PythonライブラリでInfを返すと、エラーを非表示にするために人々があなたを攻撃します
  • matlabライブラリでエラーを発生させると、値が欠落しているデータの処理に失敗したとして人々が攻撃します

あなたの場合、私はエンドユーザーにとって最も驚くべき解決策を選びます。セットを処理するライブラリを開発しているので、空のセットはユーザーが処理することを期待しているように見えるため、それを返すことは賢明なことのように思えます。しかし、私は誤解しているかもしれません。ここでは他の誰よりもコンテキストをよく理解しているため、ユーザーがセットが常に空ではないことに依存していると予想される場合は、すぐに例外をスローする必要があります。

ユーザーが選択できる(「厳密な」パラメーターを追加するような)の解決策は、元の質問を新しい同等の質問で置き換えるため、決定的ではありません:「strictのどの値デフォルトですか?」

1

(数学では)一般的な概念ですが、セット上で要素を選択すると要素が見つからないため、空のセットが取得されます。もちろん、この方法を使用する場合は、数学と一貫している必要があります。

一般的な設定ルール:

  • Set.Foreach(述語); //空のセットの場合は常にtrueを返します
  • Set.Exists(述語); //空のセットの場合は常にfalseを返します

あなたの質問は非常に微妙です:

  • 関数の入力にcontract を尊重する)がある可能性があります:その場合、無効な入力では例外が発生するはずです。つまり、関数は通常どおり機能していませんパラメーター。

  • 関数の入力はsetとまったく同じように動作する必要があるため、空のセットを返すことができるはずです。

今私があなたの中にいるなら、私は「セット」の方法を取りますが、大きな「BUT」で。

「仮説による」には女子学生だけが含まれるべきコレクションがあるとします。

class FemaleClass{

    FemaleStudent GetAnyFemale(){
        var femaleSet= mySet.Select( x=> x.IsFemale());
        if(femaleSet.IsEmpty())
            throw new Exception("No female students");
        else
            return femaleSet.Any();
    }
}

これで、契約があるため、コレクションは「純粋なセット」ではなくなりました。したがって、例外を指定して契約を実施する必要があります。

純粋な方法で「セット」関数を使用する場合、空のセットの場合に例外をスローするべきではありませんが、「純粋なセット」ではないコレクションがある場合は、適切な例外をスローする

あなたは常により自然で一貫性のある感じをするべきです。私にとって、セットはセットのルールに従うべきですが、セットではないものは適切に考えられたルールを持つべきです。

あなたの場合、そうするのは良い考えのようです:

List SomeMethod( Set someInputSet){
    var result = someInputSet
        .CombinationsOf(4, CombinationsGenerationMode.Distinct)
        .Select(combo => /* do some operation to a combination */)
        .ToList();

    // the only information here is that set is empty => there are no combinations

    // BEWARE! if 0 here it may be invalid input, but also a empty set
    if(result.Count == 0)  //Add: "&&someInputSet.NotEmpty()"

    // we go a step further, our API require combinations, so
    // this method cannot satisfy the API request, then we throw.
         throw new Exception("you requsted impossible combinations");

    return result;
}

しかし、それは本当に良い考えではありません。実行時にランダムな瞬間に発生する可能性がある無効な状態になりましたが、それは問題に暗黙的に含まれているため、削除できません。例外を一部の内部に移動できることを確認してください ユーティリティメソッド (まったく同じコードであり、別の場所に移動されます)ですが、それは誤りであり、基本的には、 通常の規則セット を使用するのが最善です。

Linqクエリのメソッドを記述できることを示すためだけに新しい複雑さを加えることは、実際には問題の価値がない、OPがそのドメインの詳細を教えてくれれば、おそらく例外のある場所を見つけることができると確信しています)本当に必要な場合(問題があったとしても、問題に例外はまったく必要ない可能性があります)。

0
CoffeDeveloper