Debug.Assert vs Исключения
Удивительно, но я только смог найти один предыдущий вопрос о SO по этому вопросу, и я просто хотел бы получить сообщество "Голосуй за доверие" (или нет!) по моему подходу.
Таким образом, я вижу:
- используйте
Debug.Assert
, чтобы указать, что будет выглядеть EXPECT. Это будет использоваться, когда мы полностью контролируем нашу среду, например, в методе
проверьте некоторые предварительные и последующие условия.
- Использовать исключения при возникновении особых обстоятельств. Работа с внешними ресурсами, то есть файлами, базами данных, сетями и т.д., Не требует больших усилий. Но...
Он становится немного мутным в следующем сценарии. Обратите внимание, что это только КОНСТРУКТИВНЫЙ ПРИМЕР!
Скажем, у нас есть класс MyClass, который имеет общедоступное свойство MyMode и метод GetSomeValueForCurrentMode()
. Подумайте о MyClass как о том, что нужно было отгрузить (релиз построен) в библиотеке для использования другими разработчиками.
Мы ожидаем, что MyMode будет обновляться внешними пользователями этого класса. Теперь GetSomeValueForCurrentMode()
имеет следующую логику:
switch(MyMode)
{
case Mode.ModeA:
return val1;
case Mode.ModeB:
return val2;
default:
//Uh-uh this should never happen
}
Что я получаю здесь, так это то, что пользователь MyClass оставил его в недопустимом состоянии. Итак, что нам делать?
По умолчанию, должны ли мы Debug.Assert
или throw new InvalidOperationException
(или другие)?
Есть одна мантра, в которой говорится, что мы не должны доверять пользователям наших классов. Если мы выберем Debug.Assert и построим MyClass в качестве сборки релиза (таким образом удалив аргументы Debug), пользователь класса не получит полезную информацию, которую они оставили в недопустимом состоянии. Но это как-то противоречит другой мантре, которая говорит только об исключении, когда происходят вещи, совершенно вне вашего контроля.
Я нахожу, что я обойдусь кругами с этим - один из тех дискуссий по программированию, которые, похоже, не имеют окончательного "правильного" ответа. Так что позвольте поставить это на голосование!
Изменить: я заметил этот ответ в связанном SO-вопросе (Дизайн по контракту с использованием утверждений или исключений?):
Правило большого пальца состоит в том, что вы должны использовать утверждения, когда пытаетесь поймать свои собственные ошибки и исключения, когда пытаетесь поймать ошибки других людей. Другими словами, вы должны использовать исключения, чтобы проверить предварительные условия для общедоступных функций API и всякий раз, когда вы получаете какие-либо данные, которые являются внешними для вашей системы. Вы должны использовать утверждения для функций или данных, которые являются внутренними для вашей системы.
Для меня это имеет смысл и может сочетаться с техникой "Assert then throw", описанной ниже.
Мысли приветствуются!
Ответы
Ответ 1
Я согласен с большинством людей здесь и следую за Design-by-Contract. Вы должны четко и четко различать требования в развернутом коде (Контракты) и вычислять ожидаемое состояние во время проектирования (Отладка утверждений).
Вы должны ВСЕГДА бросать утверждения контракта в качестве исключений (поскольку они всегда должны быть исключительными). Есть механизмы, встроенные в большинство фреймворков для обнаружения утверждений отладки. Но во время выполнения вы всегда должны исключать исключение.
Я использую пользовательскую библиотеку, чтобы помочь с этим (в С#/VB.NET). Недавно я положил его на Codeplex (http://www.contractdriven.com/), если вам интересно, как это работает на практике.
Побочным преимуществом этого является то, что при использовании DbC более регулярно, вам редко приходится использовать утверждения отладки, поскольку в ваш код уже есть явные гарантии, поэтому на самом деле трудно попасть в недопустимое состояние.
Итак, вопрос в вашем оригинальном посте... "Что я получаю здесь, так это то, что пользователь MyClass оставил его в недопустимом состоянии. Так что же нам делать?"... никогда не должно возникать.
Вам больше не понадобится отлаживать что-нибудь еще!; -)
Ответ 2
Во-первых, допустимый MyClass должен быть, конечно, выражен MyClass инвариантом.
Во-вторых, вы говорите: "Мы ожидаем, что MyMode будет обновляться внешними пользователями этого класса" - конечно, у установщика этого режима должна быть стандартная форма дизайна по контракту (как любая публичная функция):
void Setter(mode m)
{
// INVARIANT ASSERT (1)
// PRECONDITION ASSERTS (uses "m") (2)
// BODY (3)
// POSTCONDITION ASSERTS (if any) (4)
// INVARIANT ASSERT (5)
}
В (5) вы потерпите неудачу с кричащим утверждением, что инвариант не выполняется. Но в (2) вы не сработали раньше, потому что переданный режим m недействителен. Это отправит пользователю четкое сообщение и, таким образом, решит вашу проблему.
И, пожалуйста, не говорите мне, что поле режима является общедоступным, и пользователи меняют его без какого-либо контроля.
Изменить: О утверждениях и режиме выпуска см. также:
Ответ 3
Я в основном согласен с заключением вашего собственного вопроса: если A код вшей обнаруживает ошибку A вши, это случай для A ssert (и утверждения должны быть оставлены в производственном коде, если производительность не указала иначе). Если код Алисы обнаруживает ошибку в E ve code, это случай для E xceptions, предполагая, что Алиса и Ева находятся на противоположных сторонах вашего программного обеспечения для отслеживания ошибок.
Теперь, когда общее правило. Утверждения в слегка измененной форме также могут использоваться как механизм "хедз-ап, разработчик! (и тогда их не следует называть" ASSERT ", но" HEADS_UP "или что-то подобное). Что делать, если ваша компания разрабатывает клиент/серверный продукт, а сервер отправляет недействительные данные клиенту? Если вы программист-клиент, вы чувствуете, что рассматриваете его как внешние данные (это данные Eve, которые являются kaputt), и вы хотите выбросить исключение. Но" более мягкое "утверждение, которое отлаживает отладчик Visual Studio прямо там, может быть очень хорошим способом обнаружить эти проблемы на самом раннем этапе и сообщить об этом команде сервера. В реальной установке вполне может быть закат Мэллори с данными между Евой и Алисой, но большую часть времени это действительно ошибка одного из ваших коллег, и вы хотите увидеть его, когда это произойдет - вот почему я называю их" хедз-ап" утверждают: они не заменяют исключения, но они дают вам предупреждение и возможность проверить проблему.
Ответ 4
Часто оба: Assert, а затем throw.
Вы утверждаете, что хотите сообщить разработчикам ошибочное предположение во время разработки.
Вы бросаете, потому что, если это происходит в сборке релизов, вы должны убедиться, что система не продолжает обработку, находясь в плохом состоянии.
Желаемые характеристики надежности вашей системы могут повлиять на ваш выбор здесь, но "утверждать, а затем бросать" часто является полезной стратегией, я думаю.
Ответ 5
Мое использование утверждений следует за идеями дизайн по контракту. В принципе, вы утверждаете, что входящие параметры, глобальные переменные и другая информация состояния действительны при вводе вашей функции и утверждают, что возвращаемые значения и состояние также действительны при выходе. Если вы получаете неудачное утверждение в начале, это ошибка в вызывающем коде, если вы получите его в конце, ошибка в этом коде. Это предварительные условия и условия публикации.
Утверждение в вашей инструкции switch действительно полезно, если вы проверили свои предварительные условия при входе. Если вы достигнете неприемлемого состояния в середине вашей функции в этом сценарии, это будет неудачей в вашей функции или вызванной функции. Лично я бы придерживался утверждения здесь, поскольку это не исключение. Как вы подразумеваете, исключения относятся к ресурсам, а не к ошибкам. Однако вы можете создать настраиваемый обработчик утверждений, который существует в сборке релизов, и выдает исключение при ошибке, чтобы дать вашей программе возможность вернуться в стабильное состояние.
Ответ 6
Я бы сказал: используйте исключение, если ошибка находится в коде другого пользователя или в коде в другой подсистеме (написали ли вы это или нет). Также используйте исключение, если есть ошибка в данных, поступающих от внешнего источника, такого как файл.
Иногда вы не знаете, поступали ли данные из внешнего источника или нет. Если важна безопасность, и есть вероятность, что вы имеете дело с внешними данными, которые не были проверены правильно, используйте исключение. Если безопасность не важна, я бы использовал производительность как тай-брейк: может ли этот код выполняться в узком цикле? Если это так, попробуйте использовать assert, так как вы получите лучшую производительность в сборке Release. В противном случае используйте исключение.
Ответ 7
Это зависит от языка, утверждается, если вы синтаксис сахара, то вы должны его использовать. однако в Java утверждается, что для этого нужно включить, так что исключение лучше. Однако всегда лучше иметь конкретное исключение, поэтому здесь должно быть IllegalStateException.
Ответ 8
В .Net методы класса Debug не включены в вашу программу, когда вы делаете сборку релиза, поэтому вам придется выкинуть исключение, если этот код сделает это.