Разве утверждать зло? [закрытый]


на Go создатели языка написать:

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

каково Ваше мнение по этому поводу?

21 190

21 ответ:

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

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

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

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

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

по этой логике, точки останова-это тоже зло.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

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

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 можно злоупотреблять. Я делаю это все время.

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

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

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

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

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

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

да, утверждает зло.

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

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

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

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

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

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

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

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

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

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

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

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

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

Я никогда не использовать 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.
}