Как организовать проверку неизменности по времени с D-контрактами?
Например, я должен заверить, что определенная функция для определенной системы реального времени работает в течение 20 мс или меньше. Я могу просто измерить время в начале и в конце функции, а затем утверждать, что разница будет удовлетворительной. И я делаю это в C++.
Но это похоже на контракт, за исключением того, что проверка времени является постусловием, а измерение времени в начале вовсе не является условием. Было бы неплохо заключить договор не только для обозначения, но и по причинам строительства.
Поэтому мне интересно, могу ли я использовать возможности контракта для проверки времени работы функции?
1 ответ
Вроде, но не очень хорошо. Причина в том, что переменные, объявленные в блоке in{}, не видны в блоке out{}. (Были некоторые обсуждения об изменении этого, так что он может проверить предварительное состояние после публикации, сделав копию в блоке in, но ничего не было реализовано.)
Итак, это не будет работать:
void foo()
in { auto before = Clock.currTime(); }
out { assert(Clock.currTime - before < dur!"msecs"(20)); }
body { ... }
Переменная from 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{} должны пройти. Так что, если у вас есть другой вход {} в цепочке, он может забыть потянуть время, а затем, когда 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{}, если бы только проверка с помощью явных тестов соответствовала вашим требованиям (однако, обратите внимание, что контракты, как и большинство утверждений в D, удаляются, если вы компилируете с ключом -release, поэтому они не будут на самом деле проверяться и в версиях релиза. Если вам нужно, чтобы он надежно завершился с ошибкой, выведите исключение, а не утверждение, так как это всегда будет работать в режимах отладки и выпуска.).
Или вы можете сделать это с помощью assert в функции или вспомогательной структуры или чего-либо подобного 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++.