2008-12-05 51 views
7

我有兴趣听到在操作期间使用什么技术来验证对象的内部状态,从操作本身的角度来看,由于内部状态不良或不变的违反,只能失败。因为在C#中,官方和普遍的方式是抛出一个异常,并且在C++中不仅有一个单一的方法来做到这一点(好吧,不是真的在C#中,我知道那)。你如何验证对象的内部状态?

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

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

但是对于Printer对象,如果内部状态不好,即该类不变被打破,这个操作可能会失败,这基本上意味着:一个错误。该条件对于Printer对象的用户不一定感兴趣。我个人倾向于将三种风格的内部状态验证混合在一起,并且我不能真正确定哪一个最好(如果有的话)只有哪一个绝对是最差的。我希望听到您对这些问题的看法,以及您在这个问题上分享您的任何经验和想法。

的第一个样式我用 - 比数据损坏可控的方式更好地失败 - :

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

我的commen ts的风格:

  1. 我想我更喜欢第二种风格,其中失败不隐藏,只要访问冲突实际上导致崩溃。
  2. 如果它不是一个NULL指针参与不变量,那么我倾向于倾向于第一种风格。
  3. 我真的不喜欢第三种风格,因为它会隐藏大量的错误,但我知道那些喜欢生产代码的人,因为它创造了一个强大的软件的幻觉,它不会崩溃(功能将停止运行,如在破碎的Printer对象上排队)。

你更喜欢这些吗?还是你有其他方法来实现这个目标?

回答

2

该问题最好与您如何测试软件结合考虑。

重要的是,在测试过程中击中一个不变的变量会被视为高度严重的错误,就像崩溃一样。开发过程中的测试版可以停止死机并输出诊断信息。

添加防御性代码比较合适,比如你的风格3:你的DebugBreak会在测试版本中转储诊断,但仅仅是开发者的一个折点。这使得开发人员无法通过无关代码中的错误工作的情况变得不太可能。令人遗憾的是,我经常看到它完成了相反的过程,开发人员因此而感到不便,但测试版本会穿过破损的不变式。很多奇怪的行为错误都会被提交,实际上一个错误就是原因。

6

您可以使用称为NVI(非虚拟接口)和template method模式的技术。这可能是我会怎么做(当然,这只是我个人的看法,这确实是值得商榷):如果一个派生类中重写DoQueue

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非虚,不变还是检查特殊处理。


对你的选择:我认为这取决于你想检查的条件。

如果它是一个内部的不变

如果它是不变的,它不应该 您 类的用户违反才有可能。该班级应该关心 关于其不变本身。因此, 我会assert(CheckInvariant());在 这样的情况。

这仅仅是一个方法的前提条件

如果它只是一个先决条件是 类的用户将 后必须 保证(比方说,只印刷打印机准备就绪),我会抛出 std::logic_error如上所示。

我真的不打算检查一个条件,但然后什么都不做。


该类的用户本身可以在调用一个方法之前声明它的前提条件满足。所以一般来说,如果一个类对某个状态负责,并且发现一个状态是无效的,它应该断言。如果班级发现违规的情况不属于其责任范围,则应予以排除。

+0

我实际上并不同意NVI是一个很好的解决方案,在我所述的具体情况下,如果Printer是基类,但是之前添加了接线明显的需求往往是徒劳的,如果我看到需要从Printer中派生出来,那么当时我会重构。 – 2008-12-18 10:28:57

1

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

根据可能发生的巨大碰撞是一个糟糕的主意:如果您不知道不变突破的原因,则无法保证应用程序会崩溃。如果没有,你仍然有损坏的数据。

来自litb的NonVirtual Interface解决方案是检查不变量的简便方法。

1

棘手的问题这一个:)

就个人而言,我倾向于只抛出一个异常,因为我平时进过多实现的东西时,要照顾的应该采取什么由照顾我在做什么你的设计。通常这会回来,后来咬我...

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

当我发现像这样的问题时,我会这样做,就是和我们团队的其他人说话,并告诉他们我们需要某种全局错误处理。处理将取决于你的产品(你不希望什么都不做,并且在空中交通控制器系统中的一个微妙的开发人员头脑中的文件中记录一些东西,但是如果你正在制作驱动程序,比如打印机:))。

我想我说的是,这个问题是你应该在你的应用程序的设计级别而不是在实现级别上解决的问题。 - 可悲的是没有神奇的解决方案:(