【问题标题】:How do you validate an object's internal state?如何验证对象的内部状态?
【发布时间】:2010-09-25 12:13:13
【问题描述】:

我很想听听您在操作期间使用什么技术来验证对象的内部状态,从它自己的角度来看,这些技术只会因内部状态不佳或不变的破坏而失败。

我的主要关注点是 C++,因为在 C# 中,官方和流行的方式是抛出异常,而在 C++ 中,并没有一种single 方法可以做到这一点(好吧,不是真的在 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 对象上的排队中)。

您更喜欢其中任何一种还是您有其他方法可以实现这一点?

【问题讨论】:

    标签: c# c++ validation invariants


    【解决方案1】:

    您可以将一种称为 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如上图。

    我真的不鼓励检查条件,但什么也不做。


    类的用户可以在调用一个方法之前断言它的先决条件得到满足。所以一般来说,如果一个类负责某个状态,并且它发现一个状态是无效的,它应该断言。如果该类发现违反了不属于其责任的条件,则应该抛出。

    【讨论】:

    • 我实际上完全不同意 NVI 在我所说的特定情况下是一个很好的解决方案。如果 Printer 是一个基类会很好,但是在需要明显之前添加接线通常是徒劳的。如果我看到需要从 Printer 派生,那么我会在那个时候重构。
    【解决方案2】:

    最好结合您如何测试您的软件来考虑这个问题。

    重要的是,在测试期间遇到损坏的不变量会被列为高严重性错误,就像崩溃一样。可以在开发期间构建用于测试的构建以停止死机并输出诊断信息。

    添加防御性代码可能是合适的,就像您的风格 3:您的 DebugBreak 会在测试构建中转储诊断,但对于开发人员来说只是一个断点。这降低了开发人员因不相关代码中的错误而无法工作的情况。

    遗憾的是,我经常看到它以相反的方式进行,开发人员会遇到所有不便,但测试构建会通过损坏的不变量。提交了许多奇怪的行为错误,实际上一个错误是原因。

    【讨论】:

      【解决方案3】:

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

      依赖于可能响亮的崩溃是一个坏主意:如果您不知道不变违规的原因,您不能保证应用程序会崩溃。如果没有,您仍然有损坏的数据。

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

      【讨论】:

        【解决方案4】:

        这是一个棘手的问题:)

        就我个人而言,我倾向于只抛出一个异常,因为在实现某些东西时我通常会过多地关注我正在做的事情,以照顾你的设计应该照顾的东西。通常这会在以后回来咬我……

        我对“做一些记录,然后不再做任何事情”策略的个人经验是,它也会反过来咬你 - 特别是如果它像你的情况那样实施(没有全局策略,每个班级都有可能采用不同的方式)。

        一旦发现此类问题,我会立即与团队的其他成员交谈,并告诉他们我们需要某种全局错误处理。处理将做什么取决于您的产品(您不想什么都不做,并将某些内容记录在空中交通管制系统中的一个微妙的开发人员意识文件中,但如果您正在制作驱动程序,它会工作得很好,比如说,一台打印机 :) )。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-05-06
          • 2011-08-23
          • 1970-01-01
          • 1970-01-01
          • 2022-11-26
          • 2021-11-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多