如何validation对象的内部状态?

我有兴趣听听您在操作期间使用什么技术来validation对象的内部状态,从它自己的角度来看,只有因为内部状态不良或不变的违规才能失败。

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

请注意,我不是在讨论函数参数validation,而是更像是类不变完整性检查。

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

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

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

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

 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. 我真的不喜欢第三种风格,因为它会隐藏很多错误,但我知道人们更喜欢它在生产代码中,因为它创造了一个不会崩溃的强大软件的错觉(function将停止运行,如在在打破的Printer对象上排队)。

您是否更喜欢这些或者您是否有其他方法可以实现这一目标?

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

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

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

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

您可以将名为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以进行特殊处理,仍会检查不变量。


您可以选择:我认为这取决于您要检查的条件。

如果是内部不变量

如果它是不变量,则您的class级用户不应该违反它。 class级应该关心它的不变性。 因此,我会assert(CheckInvariant()); 在这种情况下。

它只是方法的先决条件

如果它仅仅是该类用户必须保证的前提条件(例如,仅在打印机准备就绪后打印),我将抛出std::logic_error如上所示。

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


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

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

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

litb的非虚拟接口解决方案是检查不变量的一种巧妙方法。

这个问题很难:)

就个人而言,我倾向于抛出一个例外,因为我在实施工作时通常会对我正在做的事情做太多考虑,以照顾你的设计应该注意什么。 通常这会回来并在以后咬我…

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

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

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