Bezdepbonusu7Программирование: статьи и ответы

Вопрос

Assert () против принудительного (): что выбрать?

Автор вопроса: user541686Ответов: 3

Мне трудно выбрать, следует ли мне "принудительно применять" условие или "утверждать" условие в D. (Однако это не зависит от языка.)

Теоретически я знаю, что вы используете утверждения для поиска ошибок и применяете другие условия для проверки нетипичных условий. Например. вы могли бы сказать assert(count >= 0) в качестве аргумента вашего метода, потому что это указывает на то, что в вызывающей программе есть ошибка, и что вы бы сказали enforce(isNetworkConnected), потому что это не ошибка, это просто то, что вы предполагаете, что вполне может быть неправдой в законной ситуации вне вашего контроля.

Кроме того, утверждения могут быть удалены из кода в качестве оптимизации без побочных эффектов, но ограничения нельзя удалить, поскольку они должны всегда выполнять свой код условия. Следовательно, если я реализую контейнер с ленивым заполнением, который заполняется при первом обращении к любому из его методов, я говорю enforce(!empty()) вместо assert(!empty()), потому что проверка на empty() должна происходить всегда, так как он лениво выполняет код внутри.

Так что я думаю, что знаю, что они должны означать. Но теория легче практики, и мне трудно применять концепции.

Рассмотрим следующее:

Я создаю диапазон (похожий на итератор), который перебирает два других диапазона и добавляет результаты. (Для функциональных программистов: я знаю, что вместо этого я могу использовать map!("a + b"), но пока игнорирую это, так как это не иллюстрирует вопрос.) Итак, у меня есть код, который выглядит так в псевдокоде:

void add(Range range1, Range range2)
{
    Range result;
    while (!range1.empty)
    {
        assert(!range2.empty);   //Should this be an assertion or enforcement?
        result += range1.front + range2.front;
        range1.popFront();
        range2.popFront();
    }
}

Это должно быть утверждением или принуждением? (Является ли вина вызывающей стороны в том, что диапазоны не опустошаются одновременно? Возможно, он не имеет контроля над тем, откуда был взят диапазон - он мог быть получен от пользователя - но опять же, он по-прежнему выглядит как ошибка, не так ли?)

Или вот еще пример псевдокода:

uint getFileSize(string path)
{
    HANDLE hFile = CreateFile(path, ...);
    assert(hFile != INVALID_HANDLE_VALUE); //Assertion or enforcement?
    return GetFileSize(hFile); //and close the handle, obviously
}
...

Должно ли это быть утверждением или принуждением? Путь может исходить от пользователя, так что это может не быть ошибкой, но предварительным условием этого метода по-прежнему является правильность пути. Я утверждаю или принуждаю?

Спасибо!

assertlanguage-agnosticenforcementassertions

Ответы

Ответ 1 · рейтинг 1 · Steve Jessop

Я не уверен, что это полностью нейтральный язык. Ни в одном из языков, которые я использую, нет enforce(), и если бы я столкнулся с таким, я бы хотел использовать assert и enforce так, как они предназначены, что может быть идиоматично для этого языка.

Например, assert в C или C++ останавливает программу в случае сбоя, она не генерирует исключение, поэтому ее использование может отличаться от того, о чем вы говорите. Вы не используете assert в C++, если только не считаете, что либо вызывающая сторона уже допустила настолько серьезную ошибку, что на нее нельзя положиться при исправлении (например, передача отрицательного числа), либо какой-то другой код в другом месте сделал ошибку. ошибка настолько серьезная, что программа должна считаться находящейся в неопределенном состоянии (например, ваша структура данных выглядит поврежденной). Тем не менее, С++ различает ошибки времени выполнения и логические ошибки, которые могут примерно соответствовать, но я думаю, что в основном речь идет о предотвратимых и неизбежных ошибках.

В случае add вы бы использовали логическую ошибку, если намерение автора состоит в том, что программа, которая предоставляет несоответствующие списки, имеет ошибки и нуждается в исправлении, или исключение времени выполнения, если это всего лишь одна из тех вещей, которые могут произойти. Например, если бы ваша функция обрабатывала произвольные генераторы, которые не обязательно имеют средства сообщения об их длине, за исключением деструктивной оценки всей последовательности, вы, скорее всего, сочли бы это неизбежным условием ошибки.

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

Называть это ошибкой времени выполнения означает, что "разумно" (если это ненормально) передавать списки разной длины, за исключением того, что это произошло в данном случае. Следовательно, я думаю, что это принуждение, а не утверждение.

В случае filesize: для существования файла вы должны, если возможно, рассматривать это как потенциально исправимый сбой (принудительное применение), а не как ошибку (утверждение). Причина просто в том, что вызывающая сторона не может быть уверена в том, что файл существует - всегда есть кто-то с большими привилегиями, кто может удалить его или размонтировать всю файловую систему в промежутке между проверкой существования и вызовом filesize. Следовательно, это не обязательно логический недостаток в вызывающем коде, если его не существует (хотя конечный пользователь мог бы выстрелить себе в ногу). Из-за этого факта, вероятно, будут вызывающие абоненты, которые могут рассматривать это как одну из тех вещей, которые происходят, неизбежную ошибку. Создание дескриптора файла также может завершиться ошибкой из-за нехватки памяти, что является еще одной неизбежной ошибкой в большинстве систем, хотя и не обязательно исправимой, если, например, включена чрезмерная фиксация.

Другой пример для рассмотрения - operator[] против at() для вектора C++. at() выдает out_of_range, логическую ошибку, не потому, что немыслимо, что вызывающая сторона может захотеть восстановиться, или потому, что вы должны быть каким-то тупицей, чтобы совершить ошибку доступа к массиву за пределами диапазона, используя at(), а потому, что ошибка полностью можно избежать, если вызывающая сторона этого хочет - вы всегда можете проверить size() перед доступом, если у вас нет другого способа узнать, хорош ли ваш индекс или нет. Итак, operator[] вообще не гарантирует никаких проверок, и во имя эффективности доступ вне диапазона имеет неопределенное поведение.

Ответ 2 · рейтинг 1 · Norbert Nemec

assert следует рассматривать как "комментарий, проверенный во время выполнения", указывающий на предположение, которое делает программист в данный момент. assert является частью реализации функции. Ошибка assert всегда должна считаться ошибкой в том месте, где делается неправильное предположение, то есть в месте кода утверждения. Чтобы исправить ошибку, используйте соответствующие средства, чтобы избежать ситуации.

Надлежащим средством предотвращения неверных входных данных функции являются контракты, поэтому пример функции должен иметь входной контракт, который проверяет, что диапазон2 не меньше диапазона1. Утверждение внутри реализации может остаться на месте. Особенно в более длинных и сложных реализациях такое утверждение может повысить понятность.

enforce - это ленивый подход к генерации исключений во время выполнения. Это хорошо для быстрого и грязного кода, потому что лучше иметь там проверку, чем молча игнорировать возможность плохого состояния. Для производственного кода его следует заменить соответствующим механизмом, который выдает более осмысленное исключение.

Ответ 3 · Vinod R

Я полагаю, что вы частично ответили на свой вопрос сами. Утверждения обязательно разорвут поток. Если ваше утверждение неверно, вы не согласитесь продолжать что-либо. Если вы навязываете что-то, вы принимаете решение позволить чему-то произойти в зависимости от ситуации. Если вы обнаружите, что условия не выполняются, вы можете принудительно запретить вход в конкретный раздел.

Перевод вопроса и ответов сообщества Stack Overflow, лицензия CC BY-SA. Оригинал на Stack Overflow.

Похожие материалы