Debug.Assert против исключений - PullRequest
       9

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

17 голосов
/ 03 февраля 2009

Удивительно, но мне удалось найти только один предыдущий вопрос на SO по этому вопросу, и я просто хотел бы получить от сообщества «Голос доверия» (или нет!) На моем подходе.

Я вижу это так:

  • используйте 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 и соберем MyClass в качестве сборки выпуска (тем самым удалив Debug Asserts), пользователь класса не получит полезную информацию о том, что он оставил его в недопустимом состоянии. Но это в некотором роде противоречит другой мантре, которая гласит, что выбросить исключения можно только тогда, когда происходят события, совершенно не зависящие от вас.

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

Редактировать: я заметил этот ответ в связанном вопросе SO ( Проектирование по контракту с использованием утверждений или исключений? ):

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

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

Мысли приветствуются!

Ответы [ 8 ]

5 голосов
/ 03 февраля 2009

Я в основном согласен с выводом вашего собственного вопроса: если A код вшей обнаруживает ошибку A вшей, то это случай для A ssert ( и утверждения должны быть включены в производственный код, если производительность не требует иного). Если код Алисы обнаруживает ошибку в E ve коде, это имеет место для E xceptions, предполагая, что Алиса и Ева находятся на противоположных сторонах вашего программного обеспечения для отслеживания ошибок .

Теперь это общее правило. Утверждения в слегка измененном виде также могут использоваться в качестве механизма "Heads-up, developer!" (и тогда их следует называть не "ASSERT", а "HEADS_UP" или что-то подобное). Что если ваша компания разрабатывает клиент-серверный продукт, а сервер отправляет неверные данные клиенту? Если вы клиент-программист, вам хочется обрабатывать его как внешние данные (это данные Евы, которые являются капутами), и вы хотите выбросить исключение. Но «более мягкое» утверждение, которое приводит к немедленному прекращению работы отладчика Visual Studio, может быть очень хорошим способом для раннего обнаружения этих проблем и сообщения о них команде сервера. В реальной инсталляции вполне возможно, что Мэллори утешает данные между Евой и Алисой, но большую часть времени это действительно ошибка одного из ваших коллег, и вы хотите увидеть это, когда это произойдет - поэтому я и называю их «хедз-ап» утверждения: они не заменяют исключения, но дают вам предупреждение и возможность изучить проблему.

5 голосов
/ 03 февраля 2009

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

Вы должны ВСЕГДА бросать утверждения контракта как исключения (так как они всегда должны быть исключительными). В большинство сред встроены механизмы для отладки отладочных утверждений. Но во время выполнения вы всегда должны выдавать исключение.

Я использую пользовательскую библиотеку, чтобы помочь с этим (в C # / VB.NET). Недавно я поднял его на Codeplex (http://www.contractdriven.com/), если вам интересно, как это работает на практике.

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

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

Возможно, вам больше не придется ничего отлаживать! ; -)

5 голосов
/ 03 февраля 2009

Во-первых, допустимость 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 недопустим. Это отправит ясное сообщение пользователю и, таким образом, решит вашу проблему.

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

Редактировать: Об утверждениях и режиме деблокирования см. Также:

4 голосов
/ 03 февраля 2009

Часто оба: утверждай, потом бросай.

Вы утверждаете, потому что хотите уведомить разработчиков о ошибочном предположении во время разработки.

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

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

2 голосов
/ 03 февраля 2009

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

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

1 голос
/ 06 ноября 2009

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

Иногда вы не знаете, были ли данные получены из внешнего источника или нет. Если безопасность важна и есть вероятность того, что вы имеете дело с внешними данными, которые не были проверены правильно, используйте исключение. Если безопасность не важна, я бы использовал производительность как средство разрешения конфликтов: может ли этот код выполняться в узком цикле? Если это так, рассмотрите возможность использования assert, так как вы получите лучшую производительность в сборке Release. В противном случае используйте исключение.

0 голосов
/ 03 февраля 2009

В .Net методы класса Debug не включаются в вашу программу, когда вы делаете сборку релиза, поэтому вам придется выбросить исключение, если этот код сделает его рабочим.

0 голосов
/ 03 февраля 2009

Это зависит от языка, если вы используете синтаксис Sugar, то вам следует использовать его. однако в Java должны быть включены утверждения, чтобы это работало, поэтому исключение лучше. Однако всегда лучше иметь конкретное исключение, поэтому здесь должно быть IllegalStateException.

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