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

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

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

каково Ваше мнение об этом?

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 эта статья (текст ссылки) описывает метод, который может использоваться для выполнения утверждений во время компиляции.

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


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

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


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

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

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

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

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


Мне не нравится утверждает интенсивно. Но я бы не сказал, что они злые.

в основном assert будет делать то же самое, что и непроверенное исключение, единственным исключением является то, что 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'");


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


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

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

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

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

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

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


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

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

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

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

некоторые литература:

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

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


не столько зло, сколько вообще контрпродуктивно. Существует разделение между постоянной проверкой ошибок и отладкой. 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.
}