テストされていない戻り値をキャプチャする

以前に翻訳された記事「C ++エラー処理のためのエラーコードオブジェクトの使用」では、著者の発想は別のドキュメント「テストされていない戻り値のキャプチャ」から来たと述べたので、この記事を次のように翻訳し直しました。コントラストは一連の記事と見なすことができます。

序文

関数の戻り値は通常、関数がエラーなしで実行されたかどうかを示すために使用されます。ただし、呼び出し元がこの情報を適切に使用することを保証することは困難です(戻り値を参照)。たぶん、いくつかの商用ツールで十分ですが、特に小規模なプロジェクトでは、必ずしも購入ライセンスを取得できるとは限りません。あなたはおそらくこのことわざを聞いたでしょう:「私はあなたを信じます、あなたはそのような間違いをしないでしょう」。

ここで私が提唱したアイデアは、数週間前のプロジェクトでの間違いに触発されました。本番環境でのみ発生し、特定の環境の初期化ルーチンの失敗が原因であることが判明するまでに数日かかりました。実際、このルーチンを呼び出したコードは、その戻りコードをテストしていませんでした。

責任のサインを追加

私の経験では(長い間ではありませんが)、関数の戻り値がタイプごとに列挙にグループ化されることがよくあります。図1に示すように、呼び出し元はこのような戻り値を多少とも無視できます。戻り値の処理を制御するために、これらの値を直接返すのではなく、ErrorCodeクラスのインスタンスを返します。これには2つのメンバー変数が含まれています。値(enValue_)は関数のエラーコードを示し、責任フラグ(PboResp_)は次のとおりです。

class ErrorCode
{
private:
   ErrorCodeValue enValue_;
   bool * PboResp_;
public:
   // some code
}

責任フラグの目的は、ErrorCodeオブジェクトの値に対して責任があるかどうかを確認することです(確認する必要があるかどうかを指します)。ErrorCodeオブジェクトがコピーコンストラクターまたは割り当て関数を使用してオブジェクトをコピーすると、値(enValue_)がコピーされ、責任フラグPboResp_に含まれる責任が「転送」される必要があります。いわゆる「転送」とは、ソースインスタンスからターゲットインスタンスに責任を転送するコピー操作を指します。コピー後、ソースインスタンスはその内容に関与しなくなります(つまり、呼び出し元はチェックする必要がなくなります)。(実際、コピーコンストラクターと代入関数のセマンティクスは少し異なりますが、一般的な考え方は同じです。詳細は以下で説明します。)

コピーコンストラクターと割り当て関数のパラメーターはconst修飾子を使用するため、ブール値ではなくブール値へのポインターとして責任フラグPboResp_を実装することを選択しました。これにより、コピー機能は、パラメーターとして渡されたソースインスタンスの責任フラグPboResp_を変更できます。operator =制約に適用できるもう1つがあります。パラメーターErrorCodeオブジェクトの責任フラグPboResp_がtrueの場合、以前の値enValue_は失われ、記録する必要があります(出力ログを参照)。(ここで説明する責任の譲渡は、auto_ptrで発生するコピーと同様です)

ErrorCodeオブジェクトも==および!=演算子を使用します。これらの演算子は、関数から返される一時的なErrorCodeオブジェクトと比較するときに必要です。これらの一時的なErrorCodeオブジェクトは、特定のエラーステータス(成功など)を示すために作成されています。ご想像のとおり、これらの演算子関数は2つのErrorCodeオブジェクト内の値enValue_を比較します。さらに、これらのErrorCodeオブジェクトの責任フラグPboResp_もfalseに設定して、「予期しないエラーコード」のログレコードが存在しないようにする必要があります。必要に応じて、他のテスト演算子(<、>)も実装できます。

最後に、デストラクタは、インスタンスがまだエラーコード値の原因であるかどうか(責任フラグPboResp_がtrue)を確認し、存在する場合はそれを記録する必要があります。

既存のコードとの統合

私の意見では、このテクノロジーの成功は、既存のプログラムに簡単に統合できるかどうかにかかっています。図1は状況を示しています。これは、統合方法を示すための良い出発点です。

まず、クラス定義で、以前に定義された列挙の名前をクラス名として使用します(つまり、古い列挙名はErrorCodeで、新しいクラス名はErrorCodeです)。したがって、プログラムが再コンパイルされている限り、以前に列挙値を返していたすべての関数がエラーコードオブジェクトになります。また、列挙名ErrorCodeをErrorCodeValueに変更しました。このテクノロジーを本当に効果的にするには、ErrorCodeValueをパラメーターとして持つコンストラクターも定義する必要があります。このコンストラクターは、2つの状況で暗黙的に呼び出されます。1つ目は、前の関数がErrorCodeValueを返し、ErrorCodeクラスのオブジェクトではなかった場合です。次に、ErrorCodeオブジェクトとErrorCodeValueが比較されます(演算子==または!=を使用)。この場合、ErrorCodeValueを使用して一時的なErrorCodeオブジェクトが作成され、比較演算子のパラメーターとして使用されます。前述のように、比較演算子は2つのオブジェクトの責任フラグも「オフ」にします(責任フラグPboResp_がtrueに設定されています)。

実装する

図2は、ErrorCodeクラスを使用するエラー処理の新しい実装を示しています。ErrorCodeクラスが既存のファイルErrorCodes.hに追加されます。さらに、ErrorCodeクラスのメンバー関数を実装するために、ErrorCodes.cppファイルも作成する必要があります。

上記のように、コピーコンストラクターと代入演算子は、エラーコードの責任をそれらのターゲットオブジェクトに転送する必要があります。しかし、これだけでは十分ではなく、関数は常にErrorCodeオブジェクトである必要があります。したがって、コピーコンストラクターとErrorCodeValueを受け入れるコンストラクターは、オブジェクトを構築するときに責任フラグPboResp_をtrueに設定します(無条件に構築します)。これがrfコンストラクターと代入演算子の違いです。代入演算子は責任フラグをソースオブジェクトからターゲットインスタンスにコピーし、コピーコンストラクターは責任フラグをtrueに設定する必要があります。

最後に、デフォルトコンストラクターは、責任フラグをfalseに初期化するため、他のすべてのコンストラクターとは異なります。デフォルトで構築されたErrorCodeオブジェクトは、テストされていないエラーコードを表すものではなく、それを処理する方法を決定するためにそれを作成したプログラマーに完全に依存しています。

ErrorCodeクラスの拡張バージョンでは、デストラクタおよび代入演算子関数にアサーションを追加して、開発およびテスト中に「エラーリーク」を遮断(つまり、チェックされていないエラーコードを認識)できます。

新しいエラーコードクラスは図1のプログラムに実装されており、実行後、次のメッセージが標準エラー出力に出力されます。

Destruction of untested error code: 
value 1
Untested error code (value 0) erased by 
new value 2

おわりに

このコーディング手法は、特定の種類のエラーを検出するのに非常に役立つと思います。この方法ではエラーが解消されないことに注意してください。問題が発生した後、テストされていない戻りコードを報告してください。既存のプロジェクトでも簡単に実装できると思います。最初に、テストされていない多数の戻りコードを含むログファイルが表示される場合があります。したがって、このプロジェクトでは、コードを少しクリーンアップする必要がありました。

私のパブリックアカウント[Brother Linのプログラミングノート]に注意を払い、感謝します。ありがとうございます。

おすすめ

転載: www.cnblogs.com/qinwanlin/p/12681178.html