Как организовать проверку инвариантности времени с D-контрактами?

Например, я должен убедиться, что определенная функция для определенной системы реального времени работает в течение 20 мс или меньше. Я могу просто измерить время в начале и в конце функции, а затем утверждать, что разница удовлетворительна. И я делаю это на С++.

Но это очень похоже на контракт, за исключением того, что проверка времени является постусловием, а измерение времени в начале вообще не является условием. Было бы неплохо заключить его в контракт не только из-за его обозначения, но и из-за строительных соображений.

Вот мне интересно, можно ли использовать возможности контракта для проверки времени работы функции?


person akalenuk    schedule 22.10.2013    source источник


Ответы (1)


Вроде, но не совсем хорошо. Причина в том, что переменные, объявленные в блоке in{}, не видны в блоке out{}. (Были некоторые обсуждения об изменении этого, чтобы он мог проверять состояние до и после, создавая копию в блоке in, но ничего не было реализовано.)

Итак, это не сработает:

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

Переменная из in не будет перенесена в out, что приведет к ошибке неопределенного идентификатора. Но я говорю «вроде как», потому что есть потенциальный обходной путь:

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{} должен пройти. Таким образом, если бы у вас был другой вход {} вниз по цепочке, он мог бы забыть установить время, а затем, когда выход попытался бы вытолкнуть его, ваш стек опустился бы.

// 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{}, если только проверка с явными тестами удовлетворяет вашим требованиям (однако обратите внимание, что контракты, как и большинство утверждений в D, удаляются, если вы компилируете с ключом -release, поэтому они не будут на самом деле проверяйте и в версиях выпуска.Если вам нужен надежный сбой, создайте исключение, а не утверждение, поскольку это всегда будет работать в режимах отладки и выпуска.).

Или вы можете сделать это с помощью утверждения в функции, вспомогательной структуры или чего-то еще, как в 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 является локальной.

В итоге я бы сказал, что вам, вероятно, лучше всего делать это более или менее так же, как вы делали это на С++.

person Adam D. Ruppe    schedule 22.10.2013