web-dev-qa-db-ja.com

結果オブジェクトと例外のスロー

別のモジュールにリクエストを送信して結果を期待するとき、「不幸なパス」を処理する方法は2つあるように見えます。

  • 例外を投げる
  • さまざまな結果(値やエラーなど)をラップする結果オブジェクトを返す

最初の方が一般的にはより良いように思えます。これにより、コードがクリーンで読みやすくなります。結果が正しいことが期待される場合は、例外をスローしてハッピーパスの相違を処理します。

しかし、結果がどうなるか見当がつかない場合はどうでしょうか?

たとえば、宝くじを検証するモジュールを呼び出す。幸せな道はあなたが勝ったということでしょうが、おそらく勝てません。 (@Ben Cottrellのコメントで指摘されているように、「勝つことはできません」も幸運な道です。

ハッピーパスがLotteryTicketValidatorから結果を取得し、チケットを処理できなかった場合の例外を処理することを検討する方が良いでしょうか?

もう1つは、ログイン時のユーザー認証です。ユーザーが正しい資格情報を入力したと想定して、資格情報が無効な場合に例外をスローできますか、または何らかのLoginResultオブジェクトを取得する必要がありますか?

61
dumazy

要するに:状況によります。

宝くじの例をとると:
bool isWinningTicket(const LotteryTicket& ticketToCheck)

(デフォルトでは)無効なチケットは当選チケットではないと主張することができるので、「検証サーバーに到達できない」シナリオを見てみましょう。その場合、関数は約束したものを提供できません-チケットが当選チケットである場合はtrue/false応答-したがって、例外をスローしてそれを認識させる必要があります。

どうして?のような代替案を考えてみましょう
bool checkIfTicketIsWinningTicket(const LotteryTicket& ticketToCheck, bool& isWinning)
true/false(チケットが勝った場合)の結果をアウトパラメーターbool& isWinningに格納し、関数を返すことで、関数自体の成功/失敗を示しますtrue/false、例外なし、面倒なし...を除いて(しゃれを許して)-誤って戻り値を確認するのを忘れて、関数が失敗したことを忘れて、誤検出/否定(それぞれさまざまな人々を不幸にします)。

最初の例で例外をキャッチするのを忘れた場合...実行時エラーが発生し、コードに問題があることがわかるはずです(catch(...) { /*do nothing*/ }とはいえ、とにかく誰もあなたを助けることはできません)。 2番目のケースでは、コードは黙って失敗し、怒っている宝くじの人々または怒っているチケット所有者のいずれかがあなたのドアをノックダウンしたときにのみ気づきます...

ログインの例について:関数void loginUser(User userToLogIn, Password passwordOfUser)-ログ記録を約束する場合valid users(つまり、人々は事前にisValidUser(User u, Password p)の後にブールloginUser()を呼び出すことが期待されます]が呼び出されると、ユーザーがログインします)。その後、無効なユーザー/パスワードの組み合わせの例外は正しいです。関数が「組み合わせが有効かどうかを確認し、有効であればログインする」という基準で機能する場合は、その例外をスローしないでください(関数に別の名前を付けます)。

0
CharonX

他のオプションがない限り、例外を返さないようにしてください。標準のエラー処理の問題は、上に何かを投げるだけであり、メソッドのユーザーは、関数のパラメーターを読み取ることによって失敗がオプションであることを知らされず、例外をキャッチしない場合の戻り値がわからないことです。 。

複数の戻り値がある場合にできる最善のことは、結果オブジェクトでそれをラップすることです。リスナーを作成するときは、実際の戻り値を取得するために、戻りオブジェクトを処理する必要があります。つまり、メソッドが呼び出されるたびに、ユーザーはさまざまな結果の状態と値をどのように処理するかを考える必要があります。したがって、迷惑なポップアップまたはバックグラウンドのどこかで目に見えない障害が発生してコンシューマに到達するまで、エラーはエンドレススタックでスローされません。

この種の結果処理は、Rustプログラミング言語の基本概念の1つであり、null参照型やその他の不要な動作を使用しません。c#でも同様のアプローチを使用しています。メソッドがResultオブジェクトを返すRustの結果。結果がOKの場合、値に到達できます。それ以外の場合は、エラーを処理する必要があります。このようにコーディングすることの良い点は、テストまたは実行します。

私のコードがどのように見えるかの例:

enum GetError { NotFound, SomeOtherError, Etc }

public Result<ValueObject, GetError> GetValue() {
  if (SomeError) 
    return new Err<ValueObject, GetError>(GetError.SomeError);

  else return new Ok<ValueObject, GetError>();
}

value = GetValue()
          .OnOk( // OnOk is used to get the ok value and use it
            (ok) => {
               // do something with the ok value here...
            }
          )
          .OnErr( // OnErr is used to get the error value and act on it
            (err) => {
               // do something with the err value here...
            }
          )
          .Finalize( // finalize is used to make a value out of the result.
            (err) => {
                // if the result is ok it returns the ok value
                // if the result is an error it needs to be handle here
                return new ValueObject();
            });

ご覧のとおり、問題が発生する可能性はあまりありません。欠点は、コードが場所で退屈になる可能性があるため、このような結果を使用することが常に最良の選択肢であるとは限らないことです。私が伝えようとしていることは、プログラマーとして、コードがどのように動作し、エラー状況を処理するかを決定することであり、その逆ではありません。