ライブラリに実装する「良い数」の例外とは何ですか?


20

私はいつも、ソフトウェアのさまざまな部分にいくつの異なる例外クラスを実装してスローすべきかと考えていました。私の特定の開発は通常C ++ / C#/ Java関連ですが、これはすべての言語の問題だと思います。

スローする例外の良い数と、開発者コミュニティが良いライブラリに期待するものを理解したいと思います。

私が見るトレードオフには以下が含まれます:

  • より多くの例外クラスにより、APIユーザーの非常に細かいレベルのエラー処理が可能になります(ユーザー構成やデータエラー、またはファイルが見つからない)
  • 例外クラスを追加すると、単なる文字列メッセージやエラーコードではなく、エラー固有の情報を例外に埋め込むことができます
  • 例外クラスが増えると、コードのメンテナンスが増えます
  • 例外クラスが増えると、APIがユーザーに近づきにくくなる可能性があります

例外の使用法を理解したいシナリオは次のとおりです。

  • ファイルのロードやパラメーターの設定を含む「構成」段階
  • ライブラリがタスクを実行し、おそらく別のスレッドで何らかの作業を行う「操作」タイプのフェーズ中

例外を使用しない、またはより少ない例外を(比較として)使用しないエラー報告の他のパタ​​ーンには、次のものがあります。

  • 例外は少ないが、ルックアップとして使用できるエラーコードを埋め込む
  • エラーコードとフラグを関数から直接返す(スレッドからは不可能な場合がある)
  • エラー時にイベントまたはコールバックシステムを実装(スタックの巻き戻しを回避)

開発者として、あなたは何を見たいですか?

多数の例外がある場合、とにかくそれらを個別に処理するエラーを気にしますか?

操作の段階に応じて、エラー処理タイプを優先しますか?



5
「APIはユーザーにとって使いにくい」-すべてがAPIの共通の基本クラスから継承するいくつかの例外クラスを持つことで対処できます。次に、開始するときと同様に、すべての詳細について心配することなく、例外がどのAPIからのものであるかを確認できます。APIを使用して最初のプログラムを完了するまでに、エラーに関する詳細情報が必要な場合は、さまざまな例外の実際の意味を調べ始めます。もちろん、使用している言語の標準の例外階層をうまく操作する必要があります。
スティーブジェソップ

適切なポイントスティーブ
ファズ

回答:


18

シンプルにします。

ライブラリには、std ::: runtime_errorから拡張された基本例外タイプがあります(他の言語に適切に適用されるC ++のものです)。この例外は、ログに記録できるようにメッセージ文字列を受け取ります。すべてのスローポイントには、一意のメッセージ(通常は一意のID)があります。

それについてです。

注1:誰かが例外をキャッチして、例外を修正し、アクションを再開できる場合。遠隔地で一意に修正できる可能性のあるものについて、派生した例外を追加します。しかし、これは非常にまれです(キャッチャーがスローポイントの近くにいる可能性は低いため、問題の修正は難しいでしょう(ただし、すべては状況に依存します)。

注2:時々、ライブラリは非常に単純であるため、独自の例外を指定する価値がない場合があり、std :: runtime_errorが実行します。それをstd :: runtime_errorと区別する能力がユーザーにそれで何かをするのに十分な情報を与えることができる場合にのみ例外を持つことが重要です。

注3:クラス内では、通常、エラーコードを好みます(ただし、これらはクラスのパブリックAPIを超えてエスケープすることはありません)

あなたのトレードオフを見て:

私が見るトレードオフには以下が含まれます:

より多くの例外クラスにより、APIユーザーの非常に細かいレベルのエラー処理が可能になります(ユーザー構成やデータエラー、またはファイルが見つからない)

より多くの例外を使用すると、よりきめ細かな制御が可能になりますか?問題は、キャッチコードが例外に基づいて実際にエラーを修正できるかどうかになります。私はそのような状況があると確信しており、これらの場合には別の例外が必要です。ただし、上に挙げた唯一の有用な修正は、大きな警告を生成してアプリケーションを停止することです。

例外クラスを追加すると、単なる文字列メッセージやエラーコードではなく、エラー固有の情報を例外に埋め込むことができます

これは例外を使用する大きな理由です。ただし、情報は、キャッシュする人にとって有用でなければなりません。彼らはその情報を使用して是正措置を実行できますか?オブジェクトがライブラリの内部にあり、APIに影響を与えるために使用できない場合、情報は役に立ちません。投げられた情報がそれを捕まえることができる人にとって有用な値を持っていることを非常に具体的にする必要があります。通常、それをキャッチする人はパブリックAPIの外部にいるので、パブリックAPIで使用できるように情報を調整します。

例外をログに記録するだけであれば、大量のデータではなく、エラーメッセージをスローすることをお勧めします。通常、キャッチャーはデータとともにエラーメッセージを作成します。エラーメッセージを作成すると、すべてのキャッチャーで一貫性が保たれます。キャッチャーにエラーメッセージの作成を許可すると、呼び出し元とキャッチ元によって異なるエラーが報告される可能性があります。

例外は少ないが、ルックアップとして使用できるエラーコードを埋め込む

エラーコードを有意義に使用できるように、天気を判断する必要があります。できる場合は、独自の例外が必要です。それ以外の場合、ユーザーはそこでcatchステートメント内にswitchステートメントを実装する必要があります(これは、catchが自動的に処理するという点全体を無効にします)。

それができない場合、例外でエラーメッセージを使用しない理由(コードとメッセージを分割する必要がないため、検索するのが面倒です)。

エラーコードとフラグを関数から直接返す(スレッドからは不可能な場合がある)

内部的にはエラーコードを返すのは素晴らしいことです。そこでバグを修正し、すべてのエラーコードを修正し、それらを説明する必要があります。ただし、パブリックAPIを介してそれらをリークすることはお勧めできません。問題は、プログラマーがエラー状態のチェックを忘れることが多いことです(少なくとも例外を除いて、未チェックのエラーはアプリケーションが未処理のエラーを強制的に終了させ、一般的にすべてのデータを破壊します)。

エラー時にイベントまたはコールバックシステムを実装(スタックの巻き戻しを回避)

このメソッドは、多くの場合、他のエラー処理メカニズムと組み合わせて使用​​されます(代替手段としてではありません)。Windowsプログラムを考えてください。ユーザーは、メニュー項目を選択してアクションを開始します。これにより、イベントキューでアクションが生成されます。イベントキューは、最終的にアクションを処理するスレッドを割り当てます。スレッドはアクションを処理し、最終的にスレッドプールに戻り、別のタスクを待機することになっています。ここで、例外は、ジョブでタスクを処理するスレッドによってベースでキャッチする必要があります。例外をキャッチした結果、通常、メインループに対してイベントが生成され、最終的にユーザーにエラーメッセージが表示されます。

ただし、例外に直面して続行できない限り、スタックは(少なくともスレッドに対して)巻き戻されます。


+1; Tho「それ以外の場合、ユーザーはそこでcatchステートメント内にswitchステートメントを実装する必要があります(これは、catchが自動的に処理するという点全体を無効にします)。-コールスタックの巻き戻し(まだ使用できる場合)および強制エラー処理は、依然として大きな利点です。ただし、途中でキャッチして再スローする必要がある場合、コールスタックのアンワインドを実行する機能を損ないます。
マーリンモーガングラハム

9

私は通常以下から始めます:

  1. 引数バグの例外クラス。例えば、「nullを許可しない引数」、「引数は正でなければなりません」など。JavaとC#には、これらのクラスが事前定義されています。C ++では、通常、std :: exceptionから派生したクラスを1つだけ作成します。
  2. 前提条件バグの例外クラス。これらは、「インデックスはサイズよりも小さくなければならない」など、より複雑なテスト用です。
  3. アサーションバグの例外クラス。これらは、一貫性のある中間メソッドの状態をチェックするためのものです。たとえば、リストを反復して負、ゼロ、または正の要素をカウントする場合、最後にこれら3つの要素がサイズに加算されます。
  4. ライブラリ自体の1つの基本例外クラス。最初は、このクラスをスローします。必要になった場合にのみ、サブクラスの追加を開始してください。
  5. 私は例外をラップしたくないのですが、この点について意見が異なることは知っています(そして使用されている言語と非常に相関しています)。ラップする場合は、追加のラッピング例外クラスが必要になります

最初の3つのケースのクラスはデバッグ支援であるため、コードによって処理されることを意図していません。代わりに、ユーザーが情報をコピーして開発者に貼り付けることができるように(または、さらに良いのは「レポートを送信」ボタンを押して)情報を表示する最上位ハンドラーによってのみキャッチする必要があります。そのため、開発者に役立つ情報を含めます。ファイル、関数、行番号、およびどのチェックが失敗したかを明確に特定するメッセージです。

最初の3つのケースは各プロジェクトで同じであるため、C ++では通常、前のプロジェクトからコピーするだけです。多くがまったく同じことをしているため、C#とJavaの設計者は、これらのケースの標準クラスを標準ライブラリに追加しました。[更新:]怠zyなプログラマーの場合:1つのクラスで十分であり、運が良ければ標準ライブラリにはすでに適切な例外クラスがあります。ファイル名や行番号などの情報を追加したいのですが、C ++のデフォルトクラスでは提供されていません。[更新終了]

ライブラリに応じて、4番目のケースは1つのクラスのみを持つことも、クラスの一部になることもあります。私は、必要に応じてサブクラスを追加してシンプルに始めるアジャイルアプローチを好んでいます。

4番目のケースに関する詳細な議論については、Loki Astariの回答を参照してください。私は彼の詳細な答えに完全に同意します。


+1; フレームワークでの書き込み例外の記述方法とアプリケーションでの例外の記述方法には明確な違いがあります。区別は少しあいまいですが、あなたはそれについて言及します(ライブラリの場合はLokiに任せます)。
マーリンモーガングラハム
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.