web-dev-qa-db-ja.com

エラー変数はアンチパターンまたは優れた設計ですか?

実行を停止してはいけないいくつかのエラーを処理するために、クライアントがチェックして例外をスローするために使用できるerror変数があります。これはアンチパターンですか?これを処理するより良い方法はありますか?この動作の例については、PHPの mysqli APIをご覧ください。可視性の問題(アクセサ、パブリックスコープとプライベートスコープ、クラス内の変数かグローバルか)が正しく処理されていると仮定します。

45
Mikayla Maki

言語が本質的に例外をサポートしている場合は、例外をスローすることをお勧めします。クライアントが例外を失敗させたくない場合は、クライアントが例外をキャッチできます。実際、コードのクライアントは例外を想定しており、戻り値をチェックしないため、多くのバグが発生します。

選択肢があれば、例外を使用することにはいくつかの利点があります。

メッセージ

例外には、開発者がデバッグに使用したり、必要に応じてユーザーに表示したりできる、ユーザーが読み取り可能なエラーメッセージが含まれます。使用するコードが例外を処理できない場合は、常にログに記録できるため、開発者は他のすべてのトレースで停止して戻り値が何であるかを理解し、それをテーブルにマッピングして何が何であったかを理解する必要なく、ログを調べることができます。実際の例外。

戻り値を使用すると、追加情報を簡単に提供できません。一部の言語は、最後のエラーメッセージを取得するためのメソッド呼び出しの実行をサポートするため、この懸念は少し緩和されますが、呼び出し元は追加の呼び出しを行う必要があり、この情報を含む「特別なオブジェクト」へのアクセスが必要になる場合があります。

例外メッセージの場合は、次のようにできるだけ多くのコンテキストを提供します。

ユーザーのプロファイルで参照されているユーザー「bar」の名前「foo」のポリシーを取得できませんでした。

これを戻りコード-85と比較してください。どちらを選びますか?

コールスタック

例外には通常、詳細なコールスタックもあり、コードのデバッグをより迅速かつ迅速に行うことができます。また、必要に応じて、呼び出しコードによってログに記録することもできます。これにより、開発者は通常、問題を正確に特定できるため、非常に強力です。もう一度、これを戻り値(-85、101、0など)を含むログファイルと比較します。どちらを使用しますか?

高速バイアスアプローチの失敗

失敗した場所でメソッドが呼び出されると、例外がスローされます。呼び出し元のコードは、例外を明示的に抑制しないと失敗します。開発とテスト中に(そして本番環境でさえ)コードがすぐに失敗し、開発者に修正を強いるからです。戻り値の場合、戻り値のチェックを怠ると、エラーは無視され、バグは予期しない場所に現れます。通常、デバッグと修正にかなりのコストがかかります。

例外のラップとアンラップ

例外は他の例外の内側にラップされ、必要に応じてラップが解除されます。たとえば、コードがArgumentNullExceptionをスローする場合があります。これは、呼び出しコードで操作が失敗したため、呼び出しコードがUnableToRetrievePolicyException内にラップする場合があるためです。上記の例と同様のメッセージがユーザーに表示される場合がありますが、一部の診断コードは例外をラップ解除し、ArgumentNullExceptionが問題を引き起こしたことがわかります。これは、コンシューマーのコードのコーディングエラーであることを意味します。これによりアラートが発生し、開発者がコードを修正できるようになります。このような高度なシナリオは、戻り値を使用して実装するのは簡単ではありません。

コードの単純さ

これは説明が少し難しいですが、私はこのコーディングを通じて、戻り値と例外の両方を学びました。通常、戻り値を使用して記述されたコードは呼び出しを行い、戻り値が何であるかについて一連のチェックを行います。場合によっては、別のメソッドが呼び出され、そのメソッドからの戻り値について別の一連のチェックが行われます。例外がある場合、例外処理は、すべてではないにしてもほとんどの場合、はるかに単純です。あなたはtry/catch/finallyブロックがあり、ランタイムはfinallyブロックのコードを実行してクリーンアップするために最善を尽くしています。ネストされたtry/catch/finallyブロックでさえ、ネストされたif/elseおよび複数のメソッドからの関連する戻り値よりも、追跡および維持が比較的容易です。

結論

使用しているプラ​​ットフォームが例外をサポートしている場合(Javaまたは.NETなど))、これらのプラットフォームにはスローするガイドラインがあるため、例外をスローする以外に他の方法はないと想定してください。例外が発生すると、クライアントが期待します。ライブラリを使用している場合は、例外がスローされることを期待しているので、戻り値を確認する必要はありません。これが、これらのプラットフォームの世界です。

ただし、C++の場合、リターンコードを含む大規模なコードベースが既に存在し、例外ではなく多数の開発者が値を返すように調整されているため(たとえば、WindowsはHRESULTでいっぱいです)、決定するのは少し難しいでしょう。 。さらに、多くのアプリケーションでは、それもパフォーマンスの問題になる可能性があります(または少なくとも認識されています)。

66
Omer Iqbal

エラーを通知する方法はいくつかあります。

エラー変数の問題は、チェックを忘れやすいことです。

例外の問題は、実行の隠されたパスが作成されることです。try/ catchは簡単に作成できますが、catch句で適切なリカバリを確実に実行することは非常に困難です(型システム/コンパイラのサポートなし)。

条件ハンドラーの問題は、うまく構成できないことです。動的なコード実行(仮想関数)がある場合、どの条件を処理するかを予測することは不可能です。さらに、同じ条件が複数の場所で発生する可能性がある場合、毎回均一な溶液を適用できるとは言えず、すぐに乱雑になります。

ポリモーフィックリターン(Either a b(Haskellで)これまでのところ、私のお気に入りのソリューションです。

  • 明示的:隠された実行パスがない
  • 明示的:関数型シグネチャで完全に文書化(驚きなし)
  • 無視するのは難しい:目的の結果を得るためにパターンマッチングを行い、エラーのケースを処理する

唯一の問題は、過剰なチェックにつながる可能性があることです。それらを使用する言語には、それらを使用する関数の呼び出しをチェーンするイディオムがありますが、それでも、もう少しタイピング/クラッターが必要になる場合があります。 Haskellでは、これは monads ;になります。ただし、これは思ったよりはるかに恐ろしいです。Railway Oriented Programmingを参照してください。

21
Matthieu M.

エラー変数は、例外が利用できなかったCなどの言語の遺物です。今日、Cプログラム(または例外処理のない同様の言語)から使用される可能性のあるライブラリーを作成している場合を除いて、それらを回避する必要があります。

もちろん、「警告」として分類できる種類のエラーがある場合(=ライブラリは有効な結果を提供でき、呼び出し元が重要ではないと考える場合は警告を無視できます)、フォームのステータスインジケーター変数を使用することは、例外のある言語でも意味があります。しかし注意してください。ライブラリの呼び出し元は、そうするべきではない場合でも、このような警告を無視する傾向があります。したがって、そのような構成をライブラリに導入する前によく考えてください。

21
Doc Brown

ひどいと思います。私は現在、Java例外の代わりに戻り値を使用するアプリをリファクタリングしています。Javaをまったく使用していないかもしれませんが、それでもそれは当てはまると思います。

次のようなコードになります。

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

またはこれ:

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

アクション自体が例外をスローするようにしたいので、次のような結果になります。

x.doActionA();
x.doActionB();

これをtry-catchでラップして、例外からメッセージを取得するか、すでに削除されている可能性のあるものを削除する場合など、例外を無視することを選択できます。スタックトレースがある場合は、それも保持します。メソッド自体も簡単になります。例外を自分で処理するのではなく、問題の原因を投げるだけです。

現在の(恐ろしい)コード:

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

新しくなり改善された:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

Strackトレースは保持され、メッセージは、役に立たない「問題が発生しました!」ではなく、例外で使用できます。

もちろん、より適切なエラーメッセージを提供できます。しかし、私が取り組んでいる現在のコードは苦痛であり、同じことをするべきではないため、この投稿はここにあります。

12
mrjink

他の人が使用するパターンを使用している限り、このパターンまたはそのパターンの使用には何の問題もありません。 Objective-C 開発では、呼び出されるメソッドがNSErrorオブジェクトを配置できる場所にポインターを渡すのが最も好ましいパターンです。例外はプログラミングエラー用に予約されており、クラッシュにつながります(Javaまたは 。NET プログラマーが最初のiPhoneアプリを作成している場合を除く)。これは非常にうまく機能します。

5
gnasher729

「発生する可能性のあるいくつかのエラーを処理するために、実行が停止することはありません。」

エラーによって現在の関数の実行が停止することはないが、何らかの方法で呼び出し元に報告される必要がある場合は、実際には言及されていないオプションがいくつかあります。このケースは実際にはエラーというより警告です。スロー/リターンは、現在の機能を終了するオプションではありません。単一のエラーメッセージパラメータまたはリターンでは、これらのエラーの多くても1つしか発生しません。

私が使用した2つのパターンは次のとおりです。

  • 渡されるか、メンバー変数として保持されるエラー/警告コレクション。どれを追加して処理を続けるか。私は個人的には、この方法が発信者に力を与えないと感じているので、あまり好きではありません。

  • エラー/警告ハンドラーオブジェクトを渡す(またはメンバー変数として設定する)。そして、各エラーはハンドラーのメンバー関数を呼び出します。このようにして、呼び出し側は、このような非終了エラーをどう処理するかを決定できます。

これらのコレクション/ハンドラーに渡すものには、エラーを「正しく」処理するための十分なコンテキストが含まれている必要があります。通常、文字列は小さすぎ、例外のインスタンスを渡すことは賢明ですが、(例外の乱用として)時には不快になります。 。

エラーハンドラを使用した典型的なコードは次のようになります

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}
5

質問はすでに答えられていますが、私は自分自身を助けることはできません。

Exceptionがすべてのユースケースのソリューションを提供することを期待することはできません。誰かハンマー?

たとえば、メソッドがリクエストを受け取り、渡されたすべてのフィールドを検証する責任があり、最初のフィールドだけではない場合など、例外がすべてではなくすべてである場合があります。複数のフィールドのエラーの原因を示します。検証の性質上、ユーザーがそれ以上進めないかどうかを示すこともできます。その例は、強力でないパスワードです。入力したパスワードはそれほど強力ではないが十分に強力であることを示すメッセージをユーザーに表示できます。

これらの検証はすべて、検証モジュールの最後で例外としてスローされる可能性があると主張できますが、名前以外のエラーコードになる可能性があります。

したがって、ここでの教訓は次のとおりです。例外には、エラーコードと同様に、場所があります。賢く選んだ。

4

エラーコードが例外よりも望ましいユースケースがあります。

エラーが発生してもコードは続行できるが、レポートが必要な場合は、例外によってフローが終了するため、例外を選択することはできません。たとえば、データファイルを読み取っているときに、ターミナル以外の不良データが含まれていることがわかった場合は、完全に失敗するのではなく、ファイルの残りの部分を読み取ってエラーを報告する方が良いでしょう。

その他の回答では、一般的にエラーコードよりも例外を優先する理由が説明されています。

4
Jack Aidley

例外が適切でない場合に例外を使用しないことには、間違いなく何も問題はありません。

コードの実行が中断されないようにする必要がある場合(たとえば、コンパイルするプログラムや処理するフォームなど、複数のエラーを含む可能性のあるユーザー入力に作用する場合)、has_errorserror_messagesなどのエラー変数でエラーを収集すると、はるかに洗練されたデザインになることがわかります最初のエラーで例外をスローするよりも。これにより、ユーザーに不必要な再送信を強制することなく、ユーザー入力のすべてのエラーを見つけることができます。

2
ikh

一部の動的プログラミング言語では、エラー値例外処理の両方を使用できます。これは、通常の戻り値の代わりにunthrown exceptionオブジェクトを返すことによって行われます。これは、エラー値のように確認できますが、そうでない場合は例外をスローしますチェックされていません。

Perl 6では、failを介して行われます。これは、no fatal;スコープを使用しない場合、特別なスローされない例外Failureオブジェクト。

Perl 5では Contextual :: Return を使用できます。これはreturn FAILで実行できます。

1
Jakub Narębski