オブジェクトの内部状態をどのように検証しますか?
-
19-08-2019 - |
質問
操作中にオブジェクトの内部状態を検証するために使用している手法を聞くことに興味があります。それは、独自の観点からは、内部状態が悪いか不変の違反が原因で失敗するだけです。
C#での主な焦点は、C#では公式で一般的な方法が例外をスローすることであり、C ++ではこれを行うための1つの単一の方法がないことです(実際、C#ではありません)どちらか、私はそれを知っています)。
関数パラメータの検証については ではなく、クラス不変の整合性チェックのようです。
たとえば、Printer
オブジェクトが非同期で印刷ジョブをQueue
したいとします。 <=>のユーザーにとっては、非同期キューの結果が別の時間に到着するため、その操作は成功するだけです。そのため、呼び出し元に伝える関連するエラーコードはありません。
ただし、<=>オブジェクトでは、内部状態が悪い場合、つまりクラス不変式が壊れている場合、この操作は失敗する可能性があります。これは基本的にはバグです。この条件は、<=>オブジェクトのユーザーにとって必ずしも重要ではありません。
個人的に、私は3つのスタイルの内部状態検証を混在させる傾向があり、どれが最高か、もしあれば、どれが絶対に最悪かを決定することはできません。これらについてのあなたの意見を聞きたいと思います。また、この問題に関するあなた自身の経験や考えを共有してください。
最初に使用するスタイル-破損したデータよりも制御可能な方法で失敗する方が良い:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in both release and debug builds.
// Never proceed with the queuing in a bad state.
if(!IsValidState())
{
throw InvalidOperationException();
}
// Continue with queuing, parameter checking, etc.
// Internal state is guaranteed to be good.
}
私が使用する2番目のスタイル-破損したデータよりも制御不能なクラッシュの方が優れています:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in debug builds only.
// Break into the debugger in debug builds.
// Always proceed with the queuing, also in a bad state.
DebugAssert(IsValidState());
// Continue with queuing, parameter checking, etc.
// Generally, behavior is now undefined, because of bad internal state.
// But, specifically, this often means an access violation when
// a NULL pointer is dereferenced, or something similar, and that crash will
// generate a dump file that can be used to find the error cause during
// testing before shipping the product.
}
私が使用する3番目のスタイル-破損したデータよりも静かに、防御的に救済する方が良い:
void Printer::Queue(const PrintJob& job)
{
// Validate the state in both release and debug builds.
// Break into the debugger in debug builds.
// Never proceed with the queuing in a bad state.
// This object will likely never again succeed in queuing anything.
if(!IsValidState())
{
DebugBreak();
return;
}
// Continue with defenestration.
// Internal state is guaranteed to be good.
}
スタイルに対する私のコメント:
- アクセス違反が実際にクラッシュを引き起こす場合、失敗が隠されていない2番目のスタイルを好むと思います。
- 不変式に含まれるNULLポインターでない場合、最初のスタイルに傾く傾向があります。
- 多くのバグを隠すため、3番目のスタイルは本当に嫌いです。しかし、クラッシュしない堅牢なソフトウェアのような錯覚を作り出すため、プロダクションコードでそれを好む人を知っています(機能は機能しなくなります) 、壊れた<=>オブジェクトのキューイングのように)。
これらのいずれかを好むか、これを達成する他の方法がありますか?
解決
質問は、ソフトウェアのテスト方法と組み合わせて検討するのが最適です。
テスト中に壊れた不変式にヒットすることは、クラッシュと同様に、重大度の高いバグとして提出することが重要です。開発中にテスト用のビルドを作成して、デッドを停止し、診断を出力できます。
スタイル3のように防御的なコードを追加するのが適切な場合があります。DebugBreak
はテストビルドで診断をダンプしますが、開発者にとってはブレークポイントになります。これにより、開発者が無関係なコードのバグによって作業を妨げられる可能性が低くなります。
残念なことに、開発者はすべての不便さを感じますが、テストビルドは壊れた不変条件を通り抜けるという逆の方法で見られることがよくあります。多くの奇妙な動作のバグが報告されますが、実際には単一のバグが原因です。
他のヒント
NVI( Non-Virtual-Interface )と呼ばれる手法とtemplate method
パターンを併用できます。これはおそらく私がそれをする方法です(もちろん、それは私の個人的な意見であり、実際に議論の余地があります):
class Printer {
public:
// checks invariant, and calls the actual queuing
void Queue(const PrintJob&);
private:
virtual void DoQueue(const PringJob&);
};
void Printer::Queue(const PrintJob& job) // not virtual
{
// Validate the state in both release and debug builds.
// Never proceed with the queuing in a bad state.
if(!IsValidState()) {
throw std::logic_error("Printer not ready");
}
// call virtual method DoQueue which does the job
DoQueue(job);
}
void Printer::DoQueue(const PrintJob& job) // virtual
{
// Do the actual Queuing. State is guaranteed to be valid.
}
Queue
は非仮想であるため、派生クラスが特別な処理のためにDoQueue
をオーバーライドする場合、不変式は引き続きチェックされます。
オプション:確認したい条件に依存すると思います。
内部不変式である場合
不変式である場合、 クラスのユーザーが可能である それに違反する。クラスは気にする必要があります その不変式自体について。そのため、 私は
assert(CheckInvariant());
だろう そのような場合。
これは単にメソッドの前提条件です
それが単に前提条件である場合 クラスのユーザーは 保証(たとえば、 プリンターの準備ができています)、私は投げます
std::logic_error
上記のように。
条件をチェックすることは本当に思いませんが、その後は何もしません。
クラスのユーザーは、メソッドの呼び出し前に、クラスの前提条件が満たされていることをアサートできます。そのため、一般的に、クラスが何らかの状態を担当し、状態が無効であると判断した場合、アサートする必要があります。クラスは、責任を負わない違反条件を見つけた場合、スローする必要があります。
これは、すばらしい関連性の高い質問です。私見、アプリケーションアーキテクチャは、壊れた不変式を報告する戦略を提供する必要があります。例外の使用、「エラーレジストリ」オブジェクトの使用、またはアクションの結果の明示的なチェックを決定できます。他にも戦略があるかもしれません-それはポイントではありません。
大規模なクラッシュに依存することは悪い考えです。不変の違反の原因がわからない場合、アプリケーションがクラッシュすることを保証できません。そうでない場合でも、破損したデータが残っています。
litbの NonVirtual Interface ソリューションは不変条件をチェックするきちんとした方法。
これは難しい質問です:)
個人的に、私は例外を投げる傾向があります。なぜなら、私は通常、あなたのデザインで世話をするべきものを世話するために、ものを実装するとき、私がしていることに過度に集中しているからです。通常、これは戻ってきて後で噛みつきます...
<!> quot; Do-some-logging-and-the-don-do-do-anything-more <!> quot;-戦略に関する私の個人的な経験は、それもあなたに噛み付くように戻ってくることです-特にあなたの場合のように実装されている場合(グローバル戦略はありません。すべてのクラスが異なる方法で実行できる可能性があります)。
このような問題を発見したらすぐに、チームの他のメンバーと話し、何らかのグローバルなエラー処理が必要であることを伝えます。処理が行われるかどうかは製品によって異なります(何もせずに、Air Traffic Controllerシステムの開発者向けの微妙なファイルに何かを記録したくはありませんが、ドライバーを作成していれば問題なく動作しますが、たとえば、プリンター:))。
私が言っていることは、私見では、この質問は、実装レベルではなく、アプリケーションの設計レベルで解決すべきものだと思います。 -そして悲しいことに、魔法の解決策はありません:(