Cで書かれた暗号ライブラリのエラー処理フレームワークを設計しています。
私たちが取っているアプローチは、ほとんどの場合ユーザーがそれらについて何もできないため、比較的少数のエラーがユーザーに伝搬されるというものです(注目に値する例外:パラメーター検証エラー。もちろん、呼び出しが成功したかどうかをユーザーに通知します。また、障害調査用のログエントリ。)要するに、ライブラリ内のどこかにエラー状態があるからといって、パブリック(ユーザー向け)エラーコードが必要であるという意味ではありません。
また、過去の経験から、エラーコードを必要とするエラー状態のかなりの部分はコードの非常に狭い領域に関連すると考えられ、特定のエラーが1つの関数からのみ返される(または1つの関数によって設定される)場合がよくあります。
したがって、可能な限り低いレベルでエラーコードを定義したいと思います。たとえば、1つの関数だけが特定のエラーコードを返す/設定できる場合は、関数のプロトタイプが宣言されている.hファイルでそのコードを定義します。
ただし、そのような場合、エラーコードの競合が発生する可能性があります。異なるエラーに対して同じエラーコード値が返されます。誰かがそのような衝突を回避するためのエレガントなアプローチを提案してくれませんか?または、少なくともそのような競合をチェックする自動化されたメカニズム(手動で修正できます)?
関数のプロトタイプが宣言されている.hファイルでそのコードを定義します。
番号付けの競合が発生する可能性を減らすためのスペースがないため、これはお勧めしません。
代わりに、クライアントアプリケーションとサーバーアプリケーションの両方にエラーコードを提供する共通のヘッダーファイルを使用してください。別の方法として、クライアントファイルをサーバーエラーコードファイルのサブセットにすることもできます。
共通ヘッダーファイル内では、アプリケーションの各主要領域に一連のエラー番号が表示されます。また、各主要領域内から、エラーコード範囲のサブセットをそのレイヤー内のモジュールに割り当てることができます。
たとえば、サーバーアプリケーションにセッションマネージャー、ビジネスロジック、およびデータベースアクセスレイヤーがあるとします。ヘッダーファイルは次のように分解される可能性があります。
/*
* our error.h file
*/
/* Common Errors 0 - 99 */
#define SUCCESS 0
#define GENERIC_ERROR 1
/* Session Manager errors 100 - 199 */
/* Login issues 100 - 110 */
#define BAD_PASSWORD 101
/* session issues 120 - 125 */
/* other session issues 150 - 162 */
#define SOMETHING_ELSE 150
/* Business logic errors 200 - 299 */
/* Database errors 300 - 399 */
#define INVALID_CONNECTION 300
このアプローチの利点は、番号付けの競合が発生する可能性がある全体的なスペースを削減することです。潜在的な競合は、2人以上の開発者が同時に作業している領域に限定されます。
共通のエラーヘッダーファイルを用意することで、開発者は必要に応じてファイルをチェックアウトして新しいエラーを作成し、その時点で未使用の番号を要求していることを確認できます。
このアプローチの潜在的な落とし穴は、開発者がエラーヘッダーのローカルコピーを頻繁にコミットしない場合です。しかし、それは、それらの開発者が数回それに対処しなければならなくなった場合に通常うまくいくプロセスの問題です。また、#define
を通じてエラー番号を参照しているため、それほど大きな変更ではありません。
別の落とし穴は、レイヤーまたはサブモジュールに必要な範囲を誤って判断する場合です。これを回避する最も簡単な方法は、各レイヤーとサブモジュールに非常に大きな範囲を事前に割り当てることです。そして、そこから、あるサブモジュールからトリムして、より広い範囲を別のサブモジュールに与えることができるはずです。そして、範囲が連続している必要があると言うものは何もありません。
@ GlenH7とまったく同じ一般的なエラーコードファイルを使用することをお勧めしますが、グローバルエラーファイルをまったく必要としない自己完結型の関数またはモジュールを作成することもお勧めします。たとえば、 `connect´関数が失敗するだけの場合(または失敗しない場合)は、ブールインジケーターを返します。その関数の呼び出し元は、ブール戻り値をグローバル定数にマップすることを決定できますが、接続関数自体はマップしません。
success = connect(parameters);
if(!success)
return INVALID_CONNECTION;
success = check_password(paramaters);
if(!success)
return BAD_PASSWORD;
//...
return SUCCESS;
多くの異なるモジュールまたはライブラリで構成されるより大きなソフトウェアの場合、異なるチームによって開発された可能性があるため、グローバルエラーコードをまったく回避し、代わりにローカルのモジュールごとまたはライブラリごとのエラーコードのみを使用する方が良いことがよくあります。たとえば、中央のログファイルでこれらを区別するには、常にモジュール名またはある種のモジュールIDをエラーコードに追加する必要があります。ロガーAPIをそのように設計することで、これら2つの値の使用を強制できます。「log」関数では、1つのエラーコードだけでなく、モジュールIDとエラーコードをパラメーターとして想定しています。プログラム全体でエラーコードをグローバルに区別できる方法で渡す必要がある場合は、2つの属性「ModuleID」と「LocalErrorCode」を持つ構造体「ErrorCode」を作成できます。そうすることで、グローバルエラーコードをまったく必要としないようにすることができます。