7

操作中にオブジェクトの内部状態を検証するために使用している手法に興味があります。オブジェクト自体の観点からは、内部状態が悪いか不変の違反が原因でのみ失敗する可能性があります。

私の主な焦点はC++です。これは、C#では例外をスローすることが公式で一般的な方法であり、C ++ではこれを行う方法が1つだけではないためです(C#でも実際にはそうではありません)。

私は関数パラメーターの検証について話しているのではなく、クラス不変の整合性チェックについて話していることに注意してください。

たとえば、PrinterオブジェクトをQueue印刷ジョブに非同期的に送信したいとします。のユーザーにとってPrinter、非同期キューの結果は別の時間に到着するため、その操作は成功するだけです。したがって、発信者に伝える関連のエラーコードはありません。

しかし、Printerオブジェクトにとって、内部状態が悪い場合、つまりクラス不変条件が壊れている場合、この操作は失敗する可能性があります。これは基本的にバグを意味します。Printerこの条件は、オブジェクトのユーザーにとって必ずしも重要ではありません。

個人的には、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.
}

スタイルへの私のコメント:

  1. アクセス違反が実際にクラッシュを引き起こすという条件で、失敗が隠されていない2番目のスタイルを好むと思います。
  2. 不変条件に関係するNULLポインターでない場合、私は最初のスタイルに傾く傾向があります。
  3. 3番目のスタイルは多くのバグを隠すので本当に嫌いですが、クラッシュしない堅牢なソフトウェアのような錯覚を引き起こすため、本番コードでそれを好む人は知っています(機能は機能を停止するだけです)。壊れたPrinterオブジェクトのキューイング)。

これらのいずれかを好みますか、それともこれを達成する他の方法がありますか?

4

4 に答える 4

6

パターンと一緒にNVI ( Non-Virtual-Interfacetemplate 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上記のようにスローします。

条件をチェックすることは本当に思いとどまりますが、その後は何もしません。


クラスのユーザーは、メソッドを呼び出す前に、その事前条件が満たされていることをアサートできます。したがって、一般に、クラスが何らかの状態を担当していて、その状態が無効であることがわかった場合は、アサートする必要があります。クラスがその責任に該当しない違反条件を見つけた場合、クラスはスローする必要があります。

于 2008-12-05T12:05:24.767 に答える
2

この質問は、ソフトウェアのテスト方法と組み合わせて検討するのが最適です。

テスト中に壊れた不変式にヒットした場合は、クラッシュと同様に重大度の高いバグとして報告することが重要です。開発中のテスト用のビルドは、デッドを停止して診断を出力するように作成できます。

スタイル 3 のように、防御的なコードを追加することが適切な場合があります。DebugBreakテスト ビルドで診断をダンプしますが、開発者にとってはブレーク ポイントになるだけです。これにより、関係のないコードのバグによって開発者が作業できなくなるという状況が起こりにくくなります。

悲しいことに、開発者がすべての不都合を被り、テスト ビルドが壊れた不変条件を通過するという逆の方法で行われるのをよく見てきました。多くの奇妙な動作のバグが記録されますが、実際には 1 つのバグが原因です。

于 2008-12-05T12:13:03.643 に答える
1

それは素晴らしく、非常に関連性のある質問です。IMHO、アプリケーション アーキテクチャは、壊れた不変条件を報告するための戦略を提供する必要があります。例外を使用するか、「エラー レジストリ」オブジェクトを使用するか、アクションの結果を明示的にチェックするかを決定できます。他の戦略もあるかもしれませんが、それは重要ではありません。

おそらく大音量のクラッシュに依存することは悪い考えです。不変違反の原因がわからない場合、アプリケーションがクラッシュすることを保証することはできません。そうでない場合でも、データが破損しています。

litbのNonVirtual Interfaceソリューションは、不変条件をチェックする優れた方法です。

于 2008-12-05T13:21:37.343 に答える
1

これは難しい質問です:)

個人的には、私は通常、設計で処理する必要があるものを処理するために何かを実装するときに自分がしていることに夢中になっているため、例外をスローする傾向があります。通常、これは戻ってきて、後で私を噛みます...

「ログを記録してから何もしない」戦略に関する私の個人的な経験では、特にあなたの場合のように実装されている場合(グローバル戦略はありません) 、すべてのクラスが潜在的に異なる方法で行うことができます)。

このような問題を発見したらすぐに、チームの他のメンバーと話し、ある種のグローバルなエラー処理が必要であることを伝えます。処理が何を行うかは、製品によって異なります (何もせずに、航空管制システムの開発者向けの微妙なファイルに何かを記録したくはありませんが、たとえば、プリンター:))。

私が言っているのは、私見ですが、この質問は、実装レベルではなく、アプリケーションの設計レベルで解決する必要があるということだと思います。- そして悲しいことに、魔法の解決策はありません:(

于 2008-12-05T17:09:40.237 に答える