如何验证对象的内部状态?

时间:2008-12-05 11:52:38

标签: c# c++ validation invariants

我很想听听您在操作过程中使用哪种技术来验证对象的内部状态,从它自己的角度来看,只会因内部状态不良或不变违规而失败。

我的主要关注点是C ++,因为在C#中,官方和流行的方式是抛出异常,而在C ++中,不仅有一种方式可以做到这一点(好吧,不是真的在C#中)或者,我知道。)

请注意,我谈论函数参数验证,但更像是类不变完整性检查。

例如,假设我们想要一个Printer对象以Queue异步打印作业。对于Printer的用户,该操作只能成功,因为异步队列结果会在另一时间到达。因此,没有相关的错误代码传达给调用者。

但是对于Printer对象,如果内部状态不好,则此操作可能会失败,即类不变量被破坏,这基本上意味着:一个错误。这个条件不一定是Printer对象的用户感兴趣的。

就个人而言,我倾向于混合三种内部状态验证方式,我无法确定哪一种是最好的,如果有的话,只有哪一种绝对是最差的。我想听听你对这些问题的看法,并且你也分享了自己在这方面的经验和想法。

我使用的第一种风格 - 以可控制的方式比损坏的数据更好地失败:

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.
}

我使用的第二种风格 - 比腐败数据更难以控制崩溃:

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.
}

我使用的第三种风格 - 比腐败的数据更好地默默地和防御性地纾困:

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. 如果它不是一个涉及不变量的NULL指针,那么我倾向于倾向于第一种风格。
  3. 我真的不喜欢第三种风格,因为它会隐藏很多错误,但我知道人们更喜欢它在生产代码中,因为它创造了一个不会崩溃的强大软件的幻觉(功能将停止运行,就像在破碎的Printer对象上排队一样)。
  4. 您是否更喜欢这些或者您是否有其他方法可以实现这一目标?

4 个答案:

答案 0 :(得分:6)

您可以将名为NVI(非虚拟接口)的技术与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,如上所示。

我真的不鼓励检查一个条件,但后来什么都不做。


在调用满足前置条件的方法之前,类的用户本身可以断言。所以一般来说,如果一个类负责某个状态,并且它发现一个状态无效,那么它应该断言。如果班级发现违反的条件不属于其职责范围,则应抛出。

答案 1 :(得分:2)

最好结合您测试软件的方式考虑这个问题。

重要的是在测试期间击中破坏的不变量作为高严重性错误归档,就像崩溃一样。可以使开发期间的测试构建停止并输出诊断信息。

添加防御性代码可能是合适的,就像你的风格3:你的DebugBreak会在测试版本中转储诊断,但只是开发人员的一个突破点。这种情况不太可能导致开发人员无法使用不相关代码中的错误。

可悲的是,我经常看到它完全相反,开发人员遇到了所有不便,但测试版本通过破坏的不变量。很多奇怪的行为错误都会被提起,其中实际上只有一个错误。

答案 2 :(得分:1)

这是一个很好且非常相关的问题。恕我直言,任何应用程序架构都应提供报告破坏不变量的策略。可以决定使用异常,使用“错误注册表”对象,或明确检查任何操作的结果。也许甚至还有其他策略 - 这不是重点。

根据可能的大声崩溃是一个坏主意:如果您不知道不变违规的原因,您无法保证应用程序将崩溃。如果没有,您仍然有损坏的数据。

来自litb的NonVirtual Interface解决方案是检查不变量的一种巧妙方法。

答案 3 :(得分:1)

这个问题很难:)

就我个人而言,我倾向于抛出异常,因为在实现内容时,我通常会过多地处理我正在做的事情,以照顾你的设计应该注意什么。通常这会回来并在以后咬我...

我个人经历的“做一些日志,然后不要做任何事情 - 更多”的策略是,它又回来咬你 - 特别是如果它像你的情况一样实施(没有全球战略,每个阶级都可能以不同的方式做到这一点。)

一旦发现这样的问题,我会做的就是与团队的其他成员交谈并告诉他们我们需要某种全局错误处理。处理将取决于您的产品(您不希望什么都不做,并在空中交通管制系统中以微妙的开发人员文件记录某些内容,但如果您正在制作驱动程序,它将正常工作,比如打印机:))。

我想我的意思是,即imho,这个问题是您应该在应用程序的设计级别而不是在实现级别上解决的问题。 - 遗憾的是,没有神奇的解决方案:(