Я только начал бегло просматривать «Отладка приложений MS .Net 2.0» Джона Роббинса и был сбит с толку его проповедью Debug.Assert (...).
Он указывает, что хорошо реализованные Asserts в некоторой степени хранят состояние ошибки, например:
Debug.Assert(i > 3, "i > 3", "This means I got a bad parameter");
Лично мне кажется безумием, что ему так нравится повторять свой тест без реального разумного комментария «бизнес-логики», возможно, «i <= 3 никогда не должно происходить из-за процесса виджитификации flobittyjam».
Итак, я думаю, что я получаю утверждения как своего рода низкоуровневую вещь типа «Давайте защитим мои предположения» ... предполагая, что кто-то чувствует, что это тест, который нужно делать только при отладке - то есть вы защищаете себя от коллег и будущие программисты, и надеются, что они действительно что-то тестируют.
Но чего я не понимаю, так это то, что он далее говорит, что вы должны использовать утверждения в дополнение к обычной обработке ошибок; теперь я представляю себе что-то вроде этого:
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, так и в сборках Release) ... или нет. Что мне не хватает?





ИМО, это только потеря времени на разработку. Правильно реализованное исключение дает вам четкое представление о том, что произошло. Я видел, как приложения слишком много показывали непонятные ошибки типа «Assertion failed: i <10». Я рассматриваю утверждение как временное решение. На мой взгляд, в окончательной версии программы не должно быть никаких утверждений. В своей практике я использовал утверждения для быстрой и грязной проверки. Окончательная версия кода должна учитывать ошибочную ситуацию и вести себя соответствующим образом. Если случится что-то плохое, у вас есть 2 варианта: справиться с этим или оставить это. Функция должна генерировать исключение с осмысленным описанием, если переданы неправильные параметры. Я не вижу смысла в дублировании логики проверки.
Утверждения не предназначены для проверки параметров. Всегда следует выполнять проверку параметров (и точно в соответствии с тем, какие предварительные условия указаны в вашей документации и / или спецификации), а ArgumentOutOfRangeException запускать по мере необходимости.
Утверждения предназначены для проверки «невозможных» ситуаций, то есть вещей, которые вы (в логике вашей программы) предполагать истинны. Утверждения предназначены для того, чтобы сказать вам, не являются ли эти предположения ошибочными по какой-либо причине.
Надеюсь это поможет!
@Chris: Вы заявляете, что утверждения не должны использоваться для проверки параметров. Есть причина для этого? Я склонен генерировать исключения для проверок параметров, особенно в конструкторах, когда я вставляю зависимые объекты. Однако мне говорят использовать утверждения. У меня нет логического объяснения использования исключений, а не утверждений. Вы можете уточнить? Ваше здоровье
Не волнуйтесь, я выяснил почему. По словам Джона Скита, stackoverflow.com/questions/1276308/exception-vs-assertion: «Используйте утверждения для внутренних логических проверок в вашем коде и обычные исключения для ошибок, находящихся вне контроля вашего непосредственного кода».
За годы разработки я заметил, что утверждения часто можно делать только тогда, когда у вас слишком много данных. (например, избыточность хранилища состояний). Что по определению не очень хорошо. Более короткий и лучший код может не дать возможности проверить согласованность. Например, «если мой bool bIsReceiving истинен, то i32 iSourceIP не должен быть нулевым». Это пример бесполезного логического значения, вы сохранили дважды одну и ту же информацию. просто создайте функцию IsReceiving (), возвращающую iSourceIP! = null. мои 2 кт.
Я использую явные проверки, которые вызывают исключения для методов общественный и защищенный и утверждения для частных методов.
Обычно явные проверки в любом случае защищают частные методы от неправильных значений. Итак, на самом деле assert проверяет условие, которое должно быть невозможным. Если утверждение действительно срабатывает, оно сообщает мне, что есть дефект в логике проверки, содержащейся в одной из общедоступных подпрограмм в классе.
Существует аспект связи между утверждениями и выбросами исключений.
Допустим, у нас есть класс User со свойством Name и методом ToString.
Если ToString реализован так:
public string ToString()
{
Debug.Assert(Name != null);
return Name;
}
Он говорит, что Name никогда не должен иметь значение null, и если это так, в классе User есть ошибка.
Если ToString реализован так:
public string ToString()
{
if ( Name == null )
{
throw new InvalidOperationException("Name is null");
}
return Name;
}
В нем говорится, что вызывающий абонент неправильно использует ToString, если Name имеет значение null, и должен проверить это перед вызовом.
Реализация с обоими
public string ToString()
{
Debug.Assert(Name != null);
if ( Name == null )
{
throw new InvalidOperationException("Name is null");
}
return Name;
}
говорит, что если Name имеет значение null, в классе User есть ошибка, но мы все равно хотим ее обработать. (Пользователю не нужно проверять Имя перед вызовом.) Я думаю, что Роббинс рекомендовал именно такую безопасность.
Исключение можно поймать и проглотить, сделав ошибку невидимой для тестирования. Этого не может случиться с Debug.Assert.
Ни у кого не должно быть обработчика catch, который перехватывает все исключения, но люди все равно делают это, и иногда это неизбежно. Если ваш код вызывается из COM, уровень взаимодействия перехватывает все исключения и превращает их в коды ошибок COM, что означает, что вы не увидите необработанные исключения. Утверждения от этого не страдают.
Также, когда исключение не обрабатывается, еще лучше сделать мини-дамп. Одна из областей, в которой VB более мощный, чем C#, заключается в том, что вы можете использовать фильтр исключений для создания мини-дампа, когда исключение находится в полете, и оставить остальную часть обработки исключений без изменений. Сообщение в блоге Грегга Мискелли о внедрении фильтра исключений предоставляет полезный способ сделать это из C#.
Еще одно замечание об активах ... они плохо взаимодействуют с модулем, тестирующим условия ошибки в вашем коде. Стоит иметь оболочку, чтобы отключить assert для ваших модульных тестов.
Вот на 2 цента.
Я считаю, что лучше всего использовать как утверждения, так и исключения. Основные различия между двумя методами, imho, если эти утверждения Assert могут быть легко удалены из текста приложения (определяет, условные атрибуты ...), в то время как выброшенное исключение зависит (типично) от условного кода, который сложнее удалить ( многострочный раздел с условными выражениями препроцессора).
Каждое исключение приложения должно обрабатываться правильно, а утверждения должны выполняться только во время разработки и тестирования алгоритма.
Если вы передаете ссылку на нулевой объект в качестве стандартного параметра и используете это значение, вы получаете исключение нулевого указателя. Действительно: зачем писать утверждение? В данном случае это пустая трата времени. Но как насчет частных членов класса, используемых в классовых подпрограммах? Когда эти значения где-то установлены, лучше проверить с помощью утверждения, установлено ли нулевое значение. Это только потому, что, когда вы используете член, вы получаете исключение нулевого указателя, но вы не знаете, как было установлено значение. Это приводит к перезапуску программы, которая прерывается при использовании всех точек входа для установки закрытого члена.
Исключения более полезны, но ими может быть (imho) очень сложно управлять, и есть возможность использовать слишком много исключений. И они требуют дополнительной проверки, возможно, нежелательной для оптимизации кода. Лично я использую исключения только тогда, когда код требует глубокого управления перехватом (операторы catch находятся очень низко в стеке вызовов) или когда параметры функции жестко не запрограммированы в коде.
Я думал об этом долго и трудно, когда речь идет о предоставлении рекомендации по отладке против утверждают, что касается тестирования проблем.
У вас должна быть возможность протестировать свой класс с ошибочным вводом, неправильным состоянием, недопустимым порядком операций и любыми другими возможными ошибочными условиями, а также с утверждением, если никогда отключится. Каждое утверждение что-то проверяет, должно ли всегда быть истинным, независимо от входных данных или выполненных вычислений.
Я пришел к хорошим практическим правилам:
Утверждения не заменяют надежный код, который правильно работает независимо от конфигурации. Они дополняют друг друга.
Утверждения никогда не должны срабатывать во время выполнения модульного теста, даже если вводятся недопустимые значения или условия ошибки тестирования. Код должен обрабатывать эти условия без выполнения утверждения.
Если assert срабатывает (либо в модульном тесте, либо во время тестирования), в классе обнаружена ошибка.
Для всех других ошибок - обычно связанных с окружающей средой (потеря сетевого соединения) или неправильным использованием (вызывающий передал нулевое значение) - гораздо приятнее и понятнее использовать жесткие проверки и исключения. Если возникает исключение, вызывающий знает, что, скорее всего, это их вина. Если происходит утверждение, вызывающий знает, что это, скорее всего, ошибка в коде, в котором находится утверждение.
По поводу дублирования: согласен. Я не понимаю, зачем вам реплицировать проверку с помощью Debug.Assert И проверки исключений. Это не только добавляет некоторого шума в код и мутит воду в отношении того, кто виноват, но и является формой повторения.
Пример правильного использования 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 следует использовать Только, когда вы знаете, что что-то выходит за пределы желательно, но вы можете быть уверены, что продолжить работу достаточно безопасно. Во всех других обстоятельствах (не стесняйтесь указывать обстоятельства, о которых я не думал) используйте исключения, чтобы быстро и быстро выйти из строя.
Ключевой компромисс для меня заключается в том, хотите ли вы остановить работающую / производственную систему с помощью исключения, чтобы избежать повреждения и упростить устранение неполадок, или вы столкнулись с ситуацией, которая никогда не должна оставаться незамеченной в тестовых / отладочных версиях, но может разрешено продолжить производство (конечно же, с предупреждением).
ср. http://c2.com/cgi/wiki?FailFast скопировано и изменено из вопроса java: Исключение против утверждения
Утверждения могут использоваться для проверки параметров вызовов методов внутренний (вызываемых кодом, принадлежащим тому же компоненту), в отличие от вызовов внешних методов (вызываемых другим компонентом). Например, я мог бы утверждать, что параметр частного метода типа Double не является NaN.