Является ли утверждение злом?

Создатели языка Go пишут:

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

Что вы думаете об этом?

Ответ 1

Нет, нет ничего плохого в assert, если вы используете его как предполагалось.

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

  • Утверждение: сбой в самой программе.
  • Обработка ошибок: ошибочный вход или состояние системы не из-за ошибки в программе.

Ответ 2

Нет, ни goto, ни assert не являются злыми. Но оба могут быть неправильно использованы.

Утверждение для проверки работоспособности. Вещи, которые должны убить программу, если они неверны. Не для проверки или замены для обработки ошибок.

Ответ 3

По этой логике точки останова тоже злы.

Заявления должны использоваться как вспомогательная помощь, и ничего больше. "Зло" - это когда вы пытаетесь использовать их вместо обработки ошибок.

Ассистенты помогут вам, программисту, обнаружить и исправить проблемы, которые не должны существовать, и убедитесь, что ваши предположения остаются верными.

Они не имеют никакого отношения к обработке ошибок, но, к сожалению, некоторые программисты злоупотребляют ими как таковыми, а затем объявляют их "злыми".

Ответ 4

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

Из-за утверждений я трачу гораздо меньше времени на программы кодирования/отладки.

Я также заметил, что утверждения помогают мне думать о многих вещах, которые могут нарушить мои программы.

Ответ 5

Они должны использоваться для обнаружения ошибок в программе. Неплохой пользовательский ввод.

Если они используются правильно, это не зло.

Ответ 6

В качестве дополнительной информации go обеспечивает встроенную функцию panic. Это можно использовать вместо assert. Например.

if x < 0 {
    panic("x is less than 0");
}

panic будет печатать трассировку стека, поэтому она имеет целью assert.

Ответ 7

Это очень много, и я думаю, что одна проблема, которая заставляет задуматься над утверждениями, заключается в том, что они часто основаны на проверке аргументов. Поэтому рассмотрим этот другой пример, когда вы можете использовать утверждение:

build-sorted-list-from-user-input(input)

    throw-exception-if-bad-input(input)

    ...

    //build list using algorithm that you expect to give a sorted list

    ...

    assert(is-sorted(list))

end

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

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

Ответ 8

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

Лично мне нравится использовать утверждения, потому что они документируют предположения, которые я мог бы сделать при написании кода. Если эти допущения нарушаются при сохранении кода, проблема может быть обнаружена во время теста. Тем не менее, я делаю все возможное, чтобы удалить все утверждения из моего кода при создании сборки (т.е. Используя #ifdefs). Уничтожив утверждения в сборке, я исключаю риск того, что кто-то неправильно использует их в качестве костыля.

Существует еще одна проблема с утверждениями. Утверждения проверяются только во время выполнения. Но часто бывает, что проверка, которую вы хотели бы выполнить, могла быть выполнена во время компиляции. Предпочтительно обнаруживать проблему во время компиляции. Для программистов на С++ boost обеспечивает BOOST_STATIC_ASSERT, который позволяет вам это делать. Для программистов C эта статья (текст ссылки) описывает метод, который можно использовать для выполнения утверждений во время компиляции.

Итак, основное правило, которым я придерживаюсь, следующее: Не используйте утверждения в сборке и, если возможно, используйте только утверждения для вещей, которые не могут быть проверены во время компиляции (т.е. должны быть проверены во время выполнения).

Ответ 9

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

Они особенно полезны, если вы хотите следовать принципу "Crash Early". Например, предположим, что вы используете механизм подсчета ссылок. В определенных местах вашего кода вы знаете, что refcount должен быть равен нулю или одному. А также предположим, что если refcount ошибочен, программа не будет немедленно сбой, а во время следующего цикла сообщения, в какой момент будет трудно выяснить, почему все пошло не так. Утверждение было бы полезно при обнаружении ошибки ближе к ее началу.

Ответ 10

Мне не нравится, интенсивно. Я бы не сказал, что они злые, хотя.

В принципе, утверждение будет делать то же самое, что и неконтролируемое исключение, единственным исключением является то, что утверждение (обычно) не должно сохраняться для конечного продукта.

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

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

Ответ 11

Я предпочитаю избегать кода, который делает разные вещи в отладке и выпуске.

Ломать в отладчике условие и иметь всю информацию о файле/строке полезно, хотя и точное выражение и точное значение.

Если утверждать, что "оценивать условие только при отладке", может быть оптимизация производительности и, как таковая, полезно только в 0,0001% программ, где люди знают, что они делают. Во всех других случаях это вредно, так как выражение может фактически изменить состояние программы:

assert(2 == ShroedingersCat.GetNumEars()); заставит программу делать разные вещи в отладке и выпуске.

Мы разработали набор макросов assert, которые генерируют исключение, и делают это как в отладочной, так и в выпускной версии. Например, THROW_UNLESS_EQ(a, 20); генерирует исключение из сообщения what(), имеющего как файл, строку, так и фактические значения , и т.д. Для этого есть только макрос. Отладчик может быть настроен на разрыв "throw" конкретного типа исключения.

Ответ 12

Короткий ответ: Нет, я считаю, что утверждения могут быть полезны

Ответ 13

Недавно я начал добавлять некоторые подтверждения в свой код, и именно так я это делал:

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

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

void Class::f (int value) {
    assert (value < end);
    member = value;
}

и функция, которая получает входную информацию от сети, может считываться как таковая:

void Class::g (InMessage & msg) {
    int const value = msg.read_int();
    if (value >= end)
        throw InvalidServerData();
    f (value);
}

Это дает мне два уровня чеков. Все, где данные определяются во время выполнения, всегда получает исключение или немедленную обработку ошибок. Однако эта дополнительная проверка Class::f с оператором assert означает, что если какой-либо внутренний код когда-либо называет Class::f, у меня все еще есть проверка работоспособности. Мой внутренний код может не передать действительный аргумент (потому что я, возможно, вычислил value из некоторой сложной серии функций), поэтому мне нравится, чтобы в функции настройки было указано, что независимо от того, кто вызывает функцию, value не должно быть больше или равно end.

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

Ответ 14

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

С другой стороны, очень легко злоупотреблять assert.

int quotient(int a, int b){
    assert(b != 0);
    return a / b;
}

Правильная, правильная версия будет примерно такой:

bool quotient(int a, int b, int &result){
    if(b == 0)
        return false;

    result = a / b;
    return true;
}

Итак... в конечном итоге... в общей картине... Я должен согласиться с тем, что assert можно злоупотреблять. Я делаю это все время.

Ответ 15

Да, утверждения - это зло.

Часто они используются в местах, где необходимо использовать правильную обработку ошибок. Приступайте к написанию правильной обработки ошибок качества продукции с самого начала!

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

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

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

Я написал об этом больше в своем блоге в 2005 году здесь: http://www.lenholgate.com/blog/2005/09/assert-is-evil.html

Ответ 16

assert используется для обработки ошибок, потому что он меньше печатает.

Так как разработчики языка, они должны видеть, что правильная обработка ошибок может быть выполнена с еще меньшим набором символов. Исключая assert, потому что ваш механизм исключения многословен, это не решение. О, подождите, у Go тоже нет исключений. Слишком плохо:)

Ответ 17

Мне казалось, что я ударил автора в голову, когда увидел это.

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

Исключения также легче сочетаются с производственным кодом, который мне не нравится. Утверждение легче заметить, чем throw new Exception("Some generic msg or 'pretend i am an assert'");

Ответ 18

Моя проблема с этими ответами, защищающими assert, - это не то, что четко указывает, что отличает ее от обычной фатальной ошибки и почему утверждение не может быть подмножеством исключения. Теперь, с этим сказанным, что, если исключение никогда не поймает? Означает ли это утверждение по номенклатуре? И почему вы когда-либо хотели бы наложить ограничение на языке, что может быть создано исключение, которое/ничего/может обрабатывать?

Ответ 19

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

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

Некоторые альтернативы утверждения:

  • Использование отладчика
  • Консоль/база данных/другое ведение журнала
  • Исключения
  • Другие типы обработки ошибок

Некоторые ссылки:

Даже люди, которые выступают за утверждение, думают, что их следует использовать только в развитии, а не в производстве:

Этот человек говорит, что утверждения должны использоваться, когда модуль имеет потенциально поврежденные данные, которые сохраняются после исключения: http://www.advogato.org/article/949.html. Разумеется, это разумный момент, однако внешний модуль никогда не должен предписывать, насколько важны поврежденные данные для вызывающей программы (путем выхода из них "для" ). Правильный способ справиться с этим - это выбросить исключение, дающее понять, что программа теперь может находиться в противоречивом состоянии. А поскольку хорошие программы в основном состоят из модулей (с небольшим кодом клея в основном исполняемом файле), утверждения почти всегда ошибочны.

Ответ 20

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

Ответ 21

Я никогда не использую assert(), примеры обычно показывают что-то вроде этого:

int* ptr = new int[10];
assert(ptr);

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

CMonster* ptrMonsters = new CMonster[10];
if(ptrMonsters == NULL) // or u could just write if(!ptrMonsters)
{
    // we failed allocating monsters. log the error e.g. "Failed spawning 10 monsters".
}
else
{
    // initialize monsters.
}