Debug.Assert против конкретных исключений

Я только начал снимать "Отладка приложений MS.Net 2.0" Джона Роббинса и смутился его евангелизмом для Debug.Assert(...).

Он указывает, что хорошо реализованные Asserts сохраняют состояние, несколько, условия ошибки, например:

Debug.Assert(i > 3, "i > 3", "This means I got a bad parameter");

Теперь, мне кажется, мне кажется безумным, что он так любит повторять свой тест без фактического разумного комментария "бизнес-логики", возможно, "i <= 3 никогда не должно происходить из-за процесса внедрения флабитиджама".

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

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

Debug.Assert(i > 3, "i must be greater than 3 because of the flibbity widgit status");
if (i <= 3)
{
    throw new ArgumentOutOfRangeException("i", "i must be > 3 because... i=" + i.ToString());
}

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

double interestAmount = loan.GetInterest();
Debug.Assert(debugInterestDoubleCheck(loan) == interestAmount, "Mismatch on interest calc");

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

Ответы

Ответ 1

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

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

Надеюсь, это поможет!

Ответ 2

Существует аспект связи для утверждения против исключения.

Скажем, у нас есть класс User с свойством Name и ToString.

Если ToString реализована следующим образом:

public string ToString()
{
     Debug.Assert(Name != null);
     return Name;
}

В нем указано, что имя никогда не должно быть нулевым, а в классе User есть ошибка.

Если ToString реализуется следующим образом:

public string ToString()
{
     if ( Name == null )
     {
          throw new InvalidOperationException("Name is null");
     }

     return Name;
}

В нем говорится, что вызывающий абонент неправильно использует ToString, если имя равно null и должно проверить, что перед вызовом.

Реализация с помощью

public string ToString()
{
     Debug.Assert(Name != null);
     if ( Name == null )
     {
          throw new InvalidOperationException("Name is null");
     }

     return Name;
}

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

Ответ 3

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

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

Хорошие эмпирические правила, к которым я пришел:

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

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

  • Если попытки отключения (либо в unit test, либо во время тестирования), класс прослушивается.

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

Что касается дублирования: я согласен. Я не понимаю, почему вы должны повторить проверку с помощью Debug.Assert и проверки исключения. Он не только добавляет некоторый шум в код и мутный воды о том, кто виноват, но это форма повторения.

Ответ 4

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

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

Ответ 5

Исключение можно поймать и усвоить, сделав ошибку невидимой для тестирования. Это не может случиться с Debug.Assert.

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

Также, когда исключение будет необработанным, еще лучше использовать мини-дамп. Одна область, где VB является более мощной, чем С#, заключается в том, что вы можете использовать фильтр исключений для привязки мини-дампа, когда исключение находится в полете, и оставить остальную обработку исключений без изменений. Сообщение в блоге Gregg Miskelly о применении фильтра исключений дает полезный способ сделать это из С#.

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

Ответ 6

ИМО это только потеря времени разработки. Правильно выполненное исключение дает вам четкое представление о том, что произошло. Я видел слишком много приложений, показывая неясное сообщение "Assertion failed: я < 10". Я считаю утверждение временным решением. По моему мнению, никаких утверждений не должно быть в окончательной версии программы. В своей практике я использовал утверждения для быстрых и грязных проверок. Окончательная версия кода должна учитывать ошибочную ситуацию и вести себя соответственно. Если что-то происходит плохо, у вас есть 2 варианта: обработайте его или оставьте. Функция должна генерировать исключение из значимого описания, если неверные параметры передавались. Я не вижу точек в дублировании логики проверки.

Ответ 7

Пример хорошего использования Assert:

Debug.Assert(flibbles.count() < 1000000, "too many flibbles"); // indicate something is awry
log.warning("flibble count reached " + flibbles.count()); // log in production as early warning

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

Ключевым компромиссом для меня является вопрос о том, хотите ли вы сбить систему Live/Production с Exception, чтобы избежать коррупции и облегчить устранение неполадок, или вы столкнулись с ситуацией, которая никогда не должна оставаться незамеченной в тестах/отладке но может быть разрешено продолжить производство (естественно, регистрируя предупреждение).

ср. http://c2.com/cgi/wiki?FailFast скопирован и изменен из java-вопроса: Исключение Vs Assertion

Ответ 8

Вот на 2 цента.

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

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

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

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