Отлаживать.Assert vs исключения
удивительно, что я смог найти только один предыдущий вопрос по этому вопросу, и я просто хотел бы получить сообщество "вотум доверия" (или нет!) на мой подход.
Я вижу это так:
- использовать
Debug.Assert
утверждать то, что вы ожидаете, было бы правдой. Это будет использоваться, когда мы полностью контролируем нашу среду, например, в методе проверьте некоторые пред-и пост-условия. - использовать исключения в исключительных случаях возникают обстоятельства. Работа с внешними ресурсами, т. е. файлами, базами данных, сетями и т. д.-Это не проблема. Но...
он становится немного мутным в следующем сценарии. Обратите внимание, что это надуманный пример только для иллюстрации!
скажем, у нас есть класс 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 и built MyClass как сборка выпуска (тем самым удаляя утверждения отладки) пользователь класса не получит полезную информацию о том, что он оставил его в недопустимом состоянии. Но это вроде как противоречит другой мантре, которая говорит только исключения, когда вещи полностью выходят из-под вашего контроля.
Я нахожу, что я хожу по кругу с этим-одним из тех программных дебатов, которые, похоже, не имеют окончательного "правильного" ответа. Так давайте поставим это на голосование!
Edit: я заметил этот ответ в связанном вопросе SO (дизайн по контракту, используя утверждения или исключения?):
эмпирическое правило заключается в том, что вы должны использовать утверждения, когда пытаетесь поймать свои собственные ошибки, и исключения, когда пытаетесь поймать ошибки других людей. Другими словами, вы должны использовать исключения для проверки предварительных условий для публичных функций API и при получении любых данных, внешних по отношению к вашей системе. Вы должны использовать утверждения для функций или данных, которые являются внутренними для вашего система.
для меня это имеет смысл и может быть связано с техникой "Assert then throw", описанной ниже.
мысли-добро пожаловать!
8 ответов
Я согласен с большинством людей здесь и следую за дизайном по контракту. Вы должны попытаться очень четко различать требования в развернутом коде (контрактах) и выяснять ожидаемое состояние во время разработки (утверждения отладки).
вы всегда должны бросать утверждения контракта в качестве исключений (поскольку они всегда должны быть исключительными). Существуют механизмы, встроенные в большинство фреймворков для улавливания утверждений отладки. Но во время выполнения, вы всегда должны выбросить исключение.
Я использую пользовательскую библиотеку, чтобы помочь с этим (в C# / VB.NET). Недавно я поставил его на Codeplex (http://www.contractdriven.com/) Если вас интересует, как это работает на практике.
побочным преимуществом этого является то, что, когда вы начинаете использовать DbC более регулярно, вам редко нужно использовать утверждения отладки, поскольку в вашем коде уже есть явные гарантии, поэтому на самом деле трудно войти в недопустимое состояние.
Итак, вопрос в вашем исходном сообщении... -Я хочу сказать, что пользователь MyClass оставил его в недействительном состоянии. Так что же нам делать?"...никогда не должно возникнуть.
возможно, вам больше никогда не понадобится ничего отлаживать! ;-)
во-первых, для класса 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 прошло недопустимо. Это отправит четкое сообщение пользователю и, таким образом, решит вашу проблему.
и, пожалуйста, не говорите мне, что поле mode является общедоступным, и пользователи меняют его без какого-либо контроля.
Edit: о утверждениях и режиме выпуска см. Также:
Я в основном согласен с выводом вашего собственного вопроса: если на код вшей обнаруживает ошибку навши сделаны, это случай для наssert (и утверждения должны быть оставлены в производственном коде, если производительность не диктует иначе). Если код Алисы обнаруживает ошибку в Eкод ve, это случай для Exceptions, предполагая, что Алиса и Ева находятся на противоположные стороны вашего отслеживания ошибок программы.
теперь это общее правило. Утверждения в слегка измененной форме также могут использоваться как "хедз-ап, разработчик!" механизм (и тогда они не должны называться "ASSERT", а "HEADS_UP" или что-то подобное). Что делать, если ваша компания разрабатывает клиент/серверный продукт, и сервер отправляет клиенту недопустимые данные? Если вы клиентский программист, вам нравится рассматривать его как внешние данные (это данные Евы, которые kaputt), и вы хотите выдать исключение. Но" более мягкое " утверждение, которое останавливает отладчик Visual Studio, может быть очень хорошим способом обнаружить эти проблемы очень рано и сообщить об этом команде сервера. В реальной установке это вполне может быть Мэллори, закаляющийся с данными между Евой и Алисой, но большую часть времени это действительно ошибка одного из ваших коллег, и вы хотите увидеть это, когда это произойдет - вот почему я называю их" хедз-ап " утверждениями: они не заменяют исключения, но они дают вам предупреждение и возможность изучить проблему.
часто оба: утверждают, затем бросают.
вы утверждаете, потому что хотите уведомить разработчиков об ошибочном предположении во время разработки.
вы бросаете, потому что, если это происходит в сборке выпуска, вам нужно убедиться, что система не продолжает обработку в плохом состоянии.
желаемые характеристики надежности вашей системы могут повлиять на ваш выбор здесь, но "assert then throw" часто является полезной стратегией, я думаю.
мое использование утверждает следует идеи из оформление по договору. В основном вы утверждаете, что входящие параметры, глобальные переменные и другая информация о состоянии действительны при вводе функции, а возвращаемые значения и состояние также действительны при выходе. Если вы получаете неудачный assert в начале, это ошибка в вызывающем коде, если вы получаете его в конце, ошибка в этом коде. Это предварительные и последующие условия.
метод assert в ваш коммутатор заявление действительно полезно, только если вы проверили свои предварительные условия при входе. Если вы приходите к неприемлемому состоянию в середине вашей функции в этом сценарии, это сбой в вашей функции или вызываемая функция. Лично я бы придерживался утверждения здесь, поскольку это не исключение. Как вы подразумеваете, исключения относятся к ресурсам, а не к ошибкам. Однако вы можете создать пользовательский обработчик утверждений, который существует в сборке выпуска и создает исключение при сбое, чтобы дать вашей программе возможность вернуться в стабильное состояние.
Я бы сказал: используйте исключение, если ошибка находится в чужом коде или в коде в другой подсистеме (независимо от того, написали Вы ее или нет). Также используйте исключение, если есть ошибка в данных, поступивших из внешнего источника, такого как файл.
иногда вы не знаете, пришли ли данные из внешнего источника или нет. Если безопасность важна и есть вероятность, что вы имеете дело с внешними данными, которые не были проверены правильно, использовать исключение. Если безопасность не важна, тогда я бы использовал производительность как тай-брейк: может ли этот код выполняться в узком цикле? Если это так, рассмотрите использование assert, так как вы получите лучшую производительность в сборке выпуска. В противном случае используйте исключение.
Это зависит от языка, это утверждать, если вы синтаксис сахара, то вы должны использовать его. однако в Java утверждает, что для этого нужно включить, поэтому исключение лучше. Однако всегда лучше иметь конкретное исключение, поэтому здесь должно быть IllegalStateException.
в .Net методы класса Debug не включаются в вашу программу, когда вы делаете сборку выпуска, поэтому вам придется создать исключение, если этот код сделает его производственным.