正しいエラー処理戦略の選択

以前の投稿を引用すると、「うまくいかないことがある」何かがうまくいかない場合は、それに対処する必要があります。しかし、どのように?

基本的な戦略には、回復可能なエラー処理 (例外、エラー リターン コード、ハンドラー関数) と回復不可能なエラー処理 (07) の 2 種類があります。 、 16 ).いつ、どちらを使用しますか?

エラーの種類

エラーにはさまざまな理由が考えられます:ユーザーが奇妙な入力を入力した、オペレーティング システムがファイル ハンドルを提供できない、または一部のコードが 24 を逆参照している .ここでのこれらのエラーはそれぞれ異なり、異なる処理が必要です。エラー ソースの 3 つの主なカテゴリは次のとおりです。

    <リ>

    ユーザー エラー:ここでの「ユーザー」とは、API を使用しているプログラマーではなく、コンピューターの前に座って実際にプログラムを「使用している」人間を意味します。ユーザーが何か間違ったことをすると、ユーザー エラーが発生します。

    <リ>

    システム エラー:システム エラーは、OS が要求を実行できない場合に発生します。一言で言えば、システム API への呼び出しが失敗したために失敗するすべてのものは、システム エラーです。システム エラーにはグレー ゾーンがあります。そのうちのいくつかは、プログラマーがシステム コールに不正なパラメータを渡したために発生します。これはプログラミングに近いものです。システム エラーよりもエラーです。

    <リ>

    プログラミング エラー:プログラマーは、API または言語の前提条件を確認していません。API で 38 を呼び出してはならないことが指定されている場合 46 で 最初のパラメーターとして、あなたはそうします-これはプログラマーのせいです.たとえユーザーが 55 を入力したとしても 67 に渡されたもの 、プログラマーはそれをチェックするコードを書いていないため、プログラマーの責任です.

各カテゴリは異なり、それぞれ特別な扱いが必要なので、それらを見てみましょう.

ユーザー エラー

私は非常に大胆な声明を出すつもりです.ユーザーエラーは実際にはエラーではありません.

人間の入力を扱うプログラマーは、入力が悪いことを想定する必要があります。最初に行うべきことは、有効性を確認し、間違いをユーザーに報告して、新しい入力を要求することです。

したがって、何らかの形式のエラー処理戦略を使用してユーザー エラーに対処することは、実際には意味がありません。単純にユーザー エラーの発生を防ぐために、入力はできるだけ早く検証する必要があります。

もちろん、これは毎回可能というわけではありません.入力を検証するのに非常にコストがかかる場合もあれば、コード設計と関心の分離が適切にそれを妨げる場合もあります.しかし、エラー処理は間違いなく回復可能である必要があります-バックスペースを押したためにオフィスプログラムがクラッシュした場合を想像してください空のドキュメントで、または空の武器で撃とうとしたためにゲームが中止された場合。

また、例外が回復可能な処理戦略として好まれている場合は、注意してください:例外は例外的のためのものです 状況のみ - 悪いユーザー入力のほとんどは例外ではありません。私が使用するすべてのプログラムは、これが標準であるとさえ主張します.外部コードのコールスタックの奥深くでユーザーエラーが検出された場合にのみ使用し、まれにしか発生しません.それ以外の場合は、リターン コードがエラーを報告する適切な方法です。

システム エラー

システム エラーは (通常は) 予測できません。さらに、それらは決定論的ではなく、以前の実行で動作したプログラムで発生する可能性があります。入力のみに依存するユーザー エラーとは異なり、それらは真のエラーです。

しかし、回復可能または回復不可能なエラー処理戦略を使用していますか?

場合によります。

メモリ不足は回復不可能なエラーであると主張する人もいます.多くの場合、エラーを処理するためのメモリさえありません!したがって、プログラムをただちに終了する必要があります.

しかし、OS がソケットを提供できなかったためにクラッシュするのは、あまりユーザーフレンドリーではありません。 プログラムを正常に終了します。

例外をスローすることは、選択する正しい回復可能な戦略であるとは限りません。

失敗した操作を再試行する場合は、関数を 86 でラップします -96 ループ内は遅い .その場合、エラー コードを返すのが正しい選択であり、戻り値が正常になるまでループします。

API 呼び出しを自分用に作成する場合は、状況に応じて必要な方法を選択して実行するだけで済みます。しかし、ライブラリを作成すると、ユーザーが何を望んでいるのかわかりません。回復不可能なエラーの可能性については、「例外ハンドラ」を使用できます。その他のエラーについては、2 つのバリアントを提供する必要があります。

プログラミング エラー

プログラミング エラーは最悪の種類のエラーです。エラー処理の目的で、関数呼び出しで発生するプログラミング エラーに限定します。不正なパラメーターです。その他の種類のプログラミング エラーは、コードに散りばめられた (デバッグ) アサーション マクロの助けを借りて実行時にのみ検出できます。

不適切なパラメーターを処理するには、2 つの戦略があります:定義済みの動作または未定義の動作を与えることです。

関数の前提条件で、不正なパラメーターを渡してはならないことが示されている場合、それは「未定義の動作」であり、関数自体ではなく呼び出し元によってチェックされる必要はありません。関数は単にデバッグ アサーションを実行する必要があります。

一方、不適切なパラメーターが前提条件の一部ではなく、代わりに関数のドキュメントで 103 をスローすることが指定されている場合 不正なパラメータを渡す場合、不正なパラメータを渡すと明確に定義された動作が行われます (例外またはその他の回復可能な がスローされます)。 エラー処理戦略) であり、関数は常にそれをチェックする必要があります。

例として 111 を考えてみましょう アクセサ関数:126の仕様 インデックスが有効な範囲内になければならないことを指定しますが、130 インデックスが有効な範囲内にない場合、関数が例外をスローすることを指定します。さらに、ほとんどの標準ライブラリの実装は、143 のインデックスをチェックするデバッグ モードを提供します。 ですが、技術的にはこれは未定義の動作であり、チェックする必要はありません。

パラメータを定義済みにし、未定義の動作にするのはいつですか?つまり、デバッグ アサーションでのみチェックするのはいつですか?常にチェックするのはいつですか?

悲しいことに、満足のいく答えはありません。これは状況に大きく依存します。API を設計するときに従う経験則しかありません。それは、前提条件を確認するのは呼び出し元の責任であり、呼び出し先の責任ではないという観察に基づいています。 .したがって、前提条件は呼び出し元によって「チェック可能」である必要があります。パラメーター値を常に正しくする操作を実行することが容易な場合、前提条件も「チェック可能」です。これがパラメーターに対して可能である場合、それは前提条件であり、したがってデバッグ アサーションを介してのみチェックされます (チェックが高価な場合はまったくチェックされません)。

しかし、決定は他の多くの要因に依存するため、一般的な決定を下すことは非常に困難です。デフォルトでは、UB にしてアサーションのみを使用する傾向があります。標準ライブラリは 155 で行います と 162 .

175 に関する注意 階層

回復可能なエラー処理戦略として例外を使用している場合は、新しいクラスを作成し、標準ライブラリの例外クラスの 1 つから継承することをお勧めします。

さまざまなクラスから、これら 4 つのクラスのいずれかからのみ継承することをお勧めします:

    <リ>

    183 :割り当て失敗の場合

    <リ>

    193 :一般的な実行時エラーの場合。

    <リ>

    208 (216 から派生) ):エラー コード付きのシステム エラーの場合

    <リ>

    225 :動作が定義されているプログラミング エラー用

標準ライブラリには、ロジック (プログラミングなど) と ランタイム の区別があることに注意してください。 エラー。ランタイム エラーはシステムエラーよりも広い.標準を引用すると、「プログラムの実行時にのみ検出可能な」エラーに使用されます.これは実際にはあまり役に立ちません.ユーザー エラーが原因で発生することもありますが、それはコール スタックの奥深くでのみ検出されます。 、これは後で適切なレベルでキャッチされ、ログ出力になります.しかし、私はこのクラスを他の方法ではあまり使用しません. .

最終ガイドライン

エラーを処理するには 2 つの方法があります:

    <リ>

    回復可能 戦略は例外または戻り値を使用します (状況/宗教によって異なります)

    <リ>

    回復不能 戦略はエラーをログに記録し、プログラムを中止します

アサーションは回復不能の特別な方法です 戦略はデバッグ モードでのみ使用できます。

また、エラーには 3 つの主な原因があり、それぞれ異なる方法で対処する必要があります。

    <リ>

    ユーザー エラーは、上位レベルのプログラム部分でエラーとして扱われるべきではありません。ユーザーからのすべてのエラーをチェックして、適切に処理する必要があります。ユーザーと直接対話しない低レベル部分でのみ、適切な回復可能なエラー処理で処理できます。

    <リ>

    システム エラーは、エラーの種類と重大度に応じて、回復可能なエラー処理戦略と回復不可能なエラー処理戦略の両方で処理できます。ライブラリは、シリーズのパート 2 で概説した手法を使用して、可能な限り柔軟になるよう努める必要があります。

    <リ>

    プログラミングエラー、つまり不正なパラメーターは、関数がデバッグアサーションのみを使用してチェックするか、完全に定義された動作をチェックする必要がある前提条件によって禁止することができます。この場合、関数は適切な方法でエラーを通知する必要があります。デフォルトでは、呼び出し元によるチェックが非常に困難な場合にのみ、関数がパラメーターをチェックするように定義します。

次は?

これは、コードや実際のアドバイスのない非常にドライな部分でしたが、これは不可能です。しかし、次の投稿の紹介として自分の考えを書き留めておくのは理にかなっていると思いました.

これらの投稿では、エラーに対処するための具体的な戦略について概説します.

第 2 部 (既に公開済み) では、システム エラーを可能な限り柔軟に処理するための手法について説明します。次の第 3 部では、アサーションの実装について説明します。第 4 部では、インターフェースを順番に設計する方法について説明します。前提条件を最小限に抑えるため、それらを楽しみにしています!