如何使用 D 协定组织时间不变检查



例如,我必须确保某个实时系统的某个函数可以工作 20 毫秒或更短的时间。我可以简单地在函数的开头和结束时测量时间,然后断言差异令人满意。我在C++这样做。

但这看起来很像合同,除了时间检查是一个后置条件,而开始时的时间测量根本不是一个条件。最好将其放入合同中,不仅是为了它的符号,也是出于建筑原因。

所以我想知道,我可以使用合约功能来检查函数的工作时间吗?

有点,但不是很好。原因是在 in{} 块中声明的变量在 out{} 块中不可见。(有一些关于改变它的讨论,所以它可以通过在 in 块中制作副本来检查前后状态,但什么都没有实现。

所以,这是行不通的:

void foo()
in { auto before = Clock.currTime(); }
out { assert(Clock.currTime - before < dur!"msecs"(20)); }
body { ... }

从 in 到外的变量不会延续到输出,从而产生未定义的标识符错误。但是,我说"有点",因为有一个潜在的解决方法:

import std.datetime;
struct Foo {
    SysTime test_before;
    void test()
    in {
        test_before = Clock.currTime();
    }
    out {
        assert(Clock.currTime - test_before < dur!"msecs"(20));
    }
    body {
    }
}

将变量声明为结构的常规成员。但这意味着每个函数都会有很多无用的变量,不能使用递归,只会污染成员命名空间。

我的一部分是想你可以把你自己的堆栈放到一边,让 in{} 推动时间,然后 out{} 弹出它并检查......但快速测试表明,一旦涉及继承,它就很容易被破坏。如果每次都重复 in{} 块,它可能会起作用。但这让我觉得非常脆弱。合约继承的规则是继承树的所有 out{} 块都需要传递,但只有任何一个 in{} 块需要传递。因此,如果你在链上有一个不同的 in{},它可能会忘记推动时间,然后当 out 试图弹出它时,你的堆栈会溢出。

// just for experimenting.....
SysTime[] timeStack; // WARNING: use a real stack here in production, a plain array will waste a *lot* of time reallocating as you push and pop on to it
 class Foo {
    void test()
      in {
        timeStack ~= Clock.currTime();
      }
      out {
         auto start = timeStack[$-1];
         timeStack = timeStack[0 .. $-1];
         assert(Clock.currTime - start < dur!"msecs"(20));
         import std.stdio;
         // making sure the stack length is still sane
         writeln("stack length ", timeStack.length);
       }
    body { }
}
class Bar : Foo {
 override void test()
  in {
     // had to repeat the in block on the child class for this to work at all
    timeStack ~= Clock.currTime();
  }
  body {
    import core.thread;
    Thread.sleep(10.msecs); // bump that up to force a failure, ensuring the test is actually run
  }
}

似乎有效,但我认为这比它的价值更麻烦。我希望随着程序变大,它会以某种方式中断,如果您的测试破坏了您的程序,那有点违背了目的。

我可能会把它作为一个 unittest{},如果只有使用显式测试进行检查才能满足你的要求(但是,请注意,如果你使用 -release 开关编译,合约就像 D 中的大多数断言一样,会被移除,所以它们实际上也不会在发布版本中被检查。如果需要它可靠地失败,请抛出异常而不是断言,因为这在调试和发布模式下始终有效。

或者,您可以使用函数中的断言或帮助程序结构或其他任何东西来执行此操作,类似于C++。我会使用范围防护:

void test() {
    auto before = Clock.currTime();
    scope(exit) assert(Clock.currTime - before < dur!"msecs"(20)); // or import std.exception; and use enforce instead of assert if you want it in release builds too
    /* write the rest of your function */
}

当然,在这里你也必须在子类中复制它,但似乎无论如何你都必须使用 in{} 块来做到这一点,所以嗯,至少 before 变量是局部的。

底线,我想说你最好或多或少地按照你在C++的方式去做。

最新更新