Утверждает ли это зло? [закрыто]


199

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

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

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


4
касательная: Go - это язык с необычным мнением. Это не обязательно плохо. Тем не менее, это означает, что вы должны принять его мнение с большей долей соли. Это также означает, что если вы не согласны, вы будете скрежетать зубами при использовании языка. В качестве доказательства того, как Go цепляется за свои мнения, несмотря на реальность, следует учитывать, что вам нужно прибегнуть к магии размышлений, чтобы определить, равны ли две коллекции.
allyourcode

@allyourcode Если вы имеете в виду reflect.DeepEqual, вам это точно не нужно . Это удобно, но за счет производительности (юнит-тесты - хороший пример использования). В противном случае вы можете реализовать любую проверку на равенство, подходящую для вашей «коллекции», без особых проблем.
Игорь Дубинский

1
Нет, я не об этом. Нет такой вещи, как slice1 == slice2 без отражения. Все остальные языки имеют эквивалент этой супер основной операции. Единственная причина, по которой Го не делает, это предубеждение.
allyourcode

Вы можете сравнить два среза без отражения, используя forцикл в Go (как в C). Было бы неплохо иметь общие операции срезов, хотя сравнение усложняется, когда задействованы указатели и структуры.
Кболино

Ответы:


321

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

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

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

109

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

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


как использовать с gotoумом?
ar2015

1
@ ar2015 Найдите один из абсурдно вымышленных шаблонов, которые некоторые люди рекомендуют избегать gotoпо чисто религиозным причинам, а затем просто используйте, gotoа не запутывайте то, что вы делаете. Другими словами: если вы можете доказать, что вы действительно нуждаетесь goto, и единственная альтернатива состоит в том, чтобы ввести груз бессмысленных лесов, который в конечном итоге делает то же самое, но без изменения полиции Гото ... тогда просто используйте goto. Конечно, предварительным условием этого является то, что «если вы можете доказать, что вам действительно нужно goto». Часто люди этого не делают. Это по-прежнему не означает, что по своей сути это плохо.
underscore_d

2
gotoиспользуется в ядре Linux для очистки кода
малат

61

По этой логике контрольные точки тоже злые.

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

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

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


40

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

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

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


31

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

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

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


30

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

При правильном использовании они не являются злом.


13

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

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). Отбирая утверждения в производственной сборке, я исключаю риск того, что кто-то использует их как костыль.

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

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


5

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

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


5

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

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

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

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

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


4
90% процентов статистики, используемой в аргументах, являются ложными.
Жоао Портела

5

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

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

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

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


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


3

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

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

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

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 .

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


2

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

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

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

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

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

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

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


1

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


1

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

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


1
Не так уж плохо :-) Исключения или нет, утверждения или нет, поклонники Go все еще говорят о том, насколько короткими являются коды.
Моше Рева

1

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

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

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


1

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


Если вы посмотрите на мой ответ. Я использую для дифференциации «исключения» (утверждения), от которых я хочу избавиться, которые используются для отладки, против исключений, которые я храню. Зачем мне от них избавляться? потому что без них работа не была бы полной. Например, если я занимаюсь 3 случаями, а 4 - делом. Я могу легко найти assert, чтобы найти их в коде и узнать его неполное, а не использовать исключение, которое может случайно быть поймано (другим программистом) или трудно определить, является ли это исключением или проверкой логики, которую я должен решить в коде.

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

Я решил, что примеры сценариев являются лучшими. Вот простой. int func (int i) {if (i> = 0) {console.write ("Число положительное {0}", i); } else {assert (false); // лень делать негативы ATM} return i * 2; <- Как бы я сделал это без утверждений и действительно ли исключение лучше? и помните, этот код будет реализован перед выпуском.

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

В этом суть, вы ДОЛЖНЫ сказать, что пользователь не хочет делать. Приложение должно завершиться с сообщением об ошибке, и тот, кто отлаживает, запомнит, что он ДОЛЖЕН БЫТЬ СДЕЛАН, но это не так. Вы НЕ хотите, чтобы это было обработано. Вы хотите расторжения и напоминание, что вы не рассматривали это дело. и это очень легко увидеть, какие дела остались, так как все, что вам нужно сделать, это найти утверждение в коде. Обработка ошибки была бы неправильной, так как код должен быть в состоянии сделать это, когда он будет готов к работе. Позволить программисту поймать это и сделать что-то еще неправильно.

1

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

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

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

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

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

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


0

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


5
assert хорош для объявления предварительных условий в верхней части функции и, если четко написано, действует как часть документации функции.
Питер Кордес

0

я никогда не использую 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.
}

14
newникогда не возвращается nullptr, он бросает.
Дэвид Стоун

Обратите внимание, что для этого вы можете использовать std :: nothrow .
Markand
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.