Поиск на сайте: Расширенный поиск


Новые программы oszone.net Читать ленту новостей RSS
CheckBootSpeed - это диагностический пакет на основе скриптов PowerShell, создающий отчет о скорости загрузки Windows 7 ...
Вы когда-нибудь хотели создать установочный диск Windows, который бы автоматически установил систему, не задавая вопросо...
Если после установки Windows XP у вас перестала загружаться Windows Vista или Windows 7, вам необходимо восстановить заг...
Программа подготовки документов и ведения учетных и отчетных данных по командировкам. Используются формы, утвержденные п...
Red Button – это мощная утилита для оптимизации и очистки всех актуальных клиентских версий операционной системы Windows...
OSzone.net Microsoft Разработка приложений .NET Framework Параметры контрактов кода в Visual Studio 2010 RSS

Параметры контрактов кода в Visual Studio 2010

Текущий рейтинг: 0 (проголосовало 0)
 Посетителей: 1096 | Просмотров: 2530 (сегодня 0)  Шрифт: - +

В прошлый раз я познакомил вас с программными контрактами в том виде, в каком они реализованы в Microsoft .NET Framework 4. Программные контракты, известные как контракты кода (Code Contracts), позволяют выражать формальные условия, необходимые вашему коду для корректной работы. Code Contracts позволяют генерировать исключение, если метод не получил ожидаемые данные или если он вернул данные, не соответствующие постусловиям. Обзор пред- и постусловий прочитайте в моей статье по ссылке (msdn.microsoft.com/magazine/gg983479).

Code Contracts являются частью .NET Framework 4, но используют и некоторые средства Visual Studio 2010, например интеграцию с MSBuild и страницу свойств в окне Project Properties. Важно отметить, что простого написания пред- и постусловий недостаточно. Помимо этого, в каждом проекте, где применяются программные контракты, нужно включить соответствующие средства проверки в исполняющей среде. Это делается на странице Code Contracts свойств проекта в Visual Studio 2010.

В этой статье мы обсудим предназначение различных параметров, которые вы можете задать, и детально рассмотрим одну из самых распространенных операций в Code Contracts: проверку аргументов.

Страница свойств Code Contracts

Следует ли вводить в действие пред- и постусловия контракта кода во всех сборках или только в отладочных? На практике все зависит от вашей концепции программного контракта. Является ли он частью проекта? Или это просто отладочный инструмент?

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

Code Contracts в .NET Framework являются не более чем частью инфраструктуры и не включены ни в один .NET-язык. В результате этого контракты проще конфигурировать индивидуально для каждой сборки в рамках вашего проекта. Реализация программных контрактов в .NET Framework оставляет на ваш выбор, где и когда имеет смысл использовать контракты.

На рис. 1 показана страница свойств в Visual Studio 2010, с помощью которой вы настраиваете то, как программные контракты будут работать в вашем приложении. Заметьте, что эти параметры задаются индивидуально для каждого проекта.

*

Рис. 1. Страница свойств Code Contracts в Visual Studio 2010

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

Проверки в период выполнения

Чтобы включить Code Contracts, вы должны установить флажок Perform Runtime Contract Checking. Без него любые инструкции контракта в исходном коде не дадут никакого эффекта (кроме Contract.Assert и Contract.Assume в любой сборке, где определен символ DEBUG). Данный элемент управления контролирует, будет ли запускаться утилита Rewriter в конце каждого этапа компиляции. Rewriter — это внешний инструмент, который выполняет постобработку программных контрактов и модифицирует MSIL-код, помещая в нужные места проверки предусловий, постусловий и инвариантов.

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

Contract.Requires<TException>(condition)

Сообщение об ошибке приведено на рис. 2.

*

Рис. 2. Данный код требует проверки контракта в период выполнения

Чтобы в деталях увидеть, как работает проверка в период выполнения, рассмотрим следующий код:

public Int32 Sum(Int32 x, Int32 y) {
  // Проверка входных значений
  ValidateOperands(x, y);
  ValidateResult();

  // Выполняем операцию
  if (x == y)
    return 2 * x;
  return x + y;
}

Детали контракта хранятся в методах ValidateXxx с использованием атрибутов ContractAbbreviator, как обсуждалось в прошлой статье. Вот исходный код для методов ValidateXxx:

[ContractAbbreviator]
private void ValidateOperands(Int32 x, Int32 y) {
  Contract.Requires(x >= 0 && y >= 0);
}

[ContractAbbreviator]
private void ValidateResult() {
  Contract.Ensures(Contract.Result<Int32>() >= 0);
}

Если вы используете Contract.Requires вместо Contract.Requires<TException>, то избавите себя от ошибки, показанной на рис. 2, на случай отключения Rewriter в одной из сборок. Сообщение на рис. 2 связано с внутренней реализацией Contract.Requires, который выглядит так:

[Conditional("CONTRACTS_FULL")]
public static void Requires(bool condition, string userMessage)
{
  AssertMustUseRewriter(
    ContractFailureKind.Precondition, "Requires");
}

public static void Requires<TException>(bool condition)
  where TException: Exception {
  AssertMustUseRewriter(
    ContractFailureKind.Precondition, "Requires<TException>");
}

Атрибут Conditional указывает компиляторам, что такой вызов метода следует игнорировать, если только не определен символ CONTRACTS_FULL. Этот символ определяется, только когда вы включаете параметр Perform Runtime Contract Checking. Поскольку Contract.Requires<TException> определяется без условия и у него нет соответствующего атрибута, осуществляется проверка Rewriter с неудачным результатом, если проверка контрактов в период выполнения отключена.

Теперь рассмотрим эффект от применения Rewriter к предыдущему коду. Вы можете легко проверить то, что я говорю, просто установив точки прерывания и нажав Ctrl+F11 для вызова представления Disassembly в Visual Studio 2010. Содержимое этого представления, когда вы пошагово проходите метод Sum, компилируемый без включенной проверки контрактов в период выполнения, показано на рис. 3.

*

Рис. 3. Представление Disassembly при выключенной проверке контрактов в период выполнения

Когда вы включаете проверку в период выполнения, утилита Rewriter изменяет MSIL-код. Если вы будете пошагово проходить тот же код с включенными Code Contracts, то увидите нечто вроде, показанного на рис. 4.

*

Рис. 4. Постусловия, проверяемые после выражения return

Заметная разница в том, что ValidateResult вызывается непосредственно перед выходом из метода Sum и после выражения return. Не нужно быть гуру в MSIL-коде, чтобы увидеть, что происходит в коде на рис. 4. Операнды проверяются до того, как метод начнет выполнять самое первое предусловие. Код, содержащий постусловия, перемещается в самый конец метода, и MSIL-код для заключительного выражения return просто относится к нему. Более интересно первое выражение return — то, которое в методе Sum реализует сокращение: теперь просто происходит переход к адресу, с которого начинается ValidateResult:

...
return 2 * x;
00000054  mov         eax,dword ptr [ebp-8]
00000057  add         eax,eax
00000059  mov         dword ptr [ebp-0Ch],eax
0000005c  nop
0000005d  jmp         0000006B
...
ValidateResult();
0000006b  push        dword ptr ds:[02C32098h]
...

Вернемся к рис. 1 и обратим внимание на раскрывающийся список рядом с флажком Perform Runtime Contract Checking. Этот список позволяет указывать количество программных контрактов, которые вы хотите разрешить: Full, Pre and Post, Preconditions, ReleaseRequires и None.

Full означает, что поддерживаются все типы программных контрактов, а None — что ни один из них не принимается во внимание. Pre and Post исключает инварианты. Уровень Preconditions также исключает выражения Ensures.

ReleaseRequires не поддерживает метод Contract.Requires и позволяет лишь указывать предусловия, используя Requires<TException> или унаследованный формат If-Then-Throw.

На странице свойство можно включать или отключать проверки в период выполнения индивидуально для каждого проекта, но как быть, если нужно отключить такие проверки только в нескольких разделах кода? В этом случае вы можете просто отключать проверки программным способом, используя атрибут ContractRuntimeIgnored. Однако в более свежем выпуске (1.4.40307.0) был добавлен новый параметр Skip Quantifiers, позволяющий не выполнять любые контракты, которые содержат ссылки на Contract.ForAll или Contract.Exists.

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

Assembly Mode

В свойствах Code Contracts также можно настроить Assembly Mode для контрактов. Этот параметр относится к тому, как вы намерены выполнять проверку аргументов. Допустимы два значения: Standard Contract Requires и Contract Reference Assembly. Значения Assembly Mode помогают утилитам вроде Rewriter в тонкой настройке кода и выдаче необходимых предупреждений. Допустим, вы используете Assembly Mode, чтобы объявить, что вы собираетесь задействовать Code Contracts для проверки параметров. Выбирая значение Assembly Mode, вы должны понимать, что они вводят пару простых правил, которые нужно соблюдать, а иначе вы получите ошибку при компиляции.

Assembly Mode следует установить в Standard Contract Requires, если вы используете методы Requires и Requires<T> для проверки аргументов своих методов. А если вы применяете в качестве предусловий любые выражения If-Then-Throw, выберите Custom Parameter Validation. При значении, отличном от Custom Parameter Validation, выражение If-Then-Throw будет интерпретироваться как Requires<T>. Комбинация Custom Parameter Validation и любой формы выражений Requires приведет к ошибке при компиляции.

В чем отличие выражений Requires и If-Then-Throw? Выражение If-Then-Throw всегда генерирует заданное вами исключение, если проверка заканчивается неудачей. В этом отношении оно отличается от Requires, но аналогично Requires<T>. Обычное выражение If-Then-Throw также не обнаруживается утилитами контрактов (Rewriter и Static Checker), если только вы не ставите за ним вызов EndContractBlock. При использовании EndContractBlock должен быть последним методом контракта кода, который вы вызываете в своем методе. Никакие другие вызовы Code Contracts после EndContractBlock недопустимы:

if (y == 0)
  throw new ArgumentException();
Contract.EndContractBlock();

In addition, Requires statements are automatically inherited. An If-Then-Throw statement is not inherited unless you also use EndContractBlock. In legacy mode, If-Then-Throw Contracts are not inherited. Instead you must manually do the Contract inheritance. The tools will try to warn if they do not detect that the preconditions are repeated in overrides and interface implementations.

Наконец, обратите внимание, что атрибуты ContractAbbreviator не могут содержать выражения If-Then-Throw, но вы можете использовать с этой целью верификаторы контракта (Contract validators). Атрибуты ContractAbbreviator могут включать лишь обычные выражения Contract для проверки аргументов.

Другие параметры

На странице свойств Code Contracts, если вы выберете параметр Perform Runtime Contract Checking, можно будет включить некоторые другие полезные параметры.

Если вы установите параметр Assert on Contract Failure, любые нарушения контракта будут приводить в утверждение (assert), где описывается контекст ошибки. Вы увидите сообщение, аналогичное показанному на рис. 2, и получите возможность выбора нескольких вариантов дальнейших действий. Например, вы можете попытаться снова подключить отладчик, отменить выполнение приложения или просто проигнорировать ошибку и продолжить.

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

Contract.ContractFailed += CentralizedErrorHandler;

Here’s some code that illustrates the handler:

static void CentralizedErrorHandler(
  Object sender, ContractFailedEventArgs e) {
  Console.WriteLine("{0}: {1}; {2}", e.
    FailureKind, e.Condition, e.Message);
  e.SetHandled();
}

Если вы хотите, чтобы в период выполнения генерировалось определенное исключение, тогда следует использовать Requires<TException>. Выражение Requires и централизованный обработчик можно применять, если вы намерены ограничить использование контрактов кода отладочными сборками или если вас не заботит реальный тип исключения. Зачастую достаточно лишь сообщить, что произошла ошибка. Скажем, на верхнем уровне во многих приложениях есть перехватчик исключений любых типов, который сам определяет, как возобновить работу приложения.

Параметр Only Public Surface Contract относится к тому, где бы вы хотели вводить в действие Code Contracts: в каждом методе или только в открытых. Если вы установите флажок этого параметра, тогда Rewriter будет игнорировать выражения Code Contract в закрытых и защищенных членах и обрабатывать только контракты в открытых членах.

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

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

Параметр Call-site Requires Checking обеспечивает еще одну оптимизацию. Допустим, вы пишете библиотеку, которая будет использоваться модулями в других сборках. По соображениям производительности вы отключаете проверку контрактов в период выполнения. Однако, если вы также создаете ссылочную сборку контрактов (Contract reference assembly), вы даете возможность вызывающему коду проверять контракт для каждого вызываемого метода.

Ссылочная сборка контрактов может существовать для каждой сборки, в которой находятся классы, использующие Code Contracts. Она содержит только открытый интерфейс родительской сборки с выражениями контрактов (Contract statements) — никакого другого кода в ней нет. Создание такой сборки можно контролировать со страницы свойств Code Contracts.

Любой код, который будет вызывать вашу библиотеку, может ссылаться на вашу ссылочную сборку контрактов и автоматически импортировать контракты — для этого достаточно включить параметр Call-site Requires Checking. При обработке вызывающего кода утилита Rewriter импортирует контракт для каждого вызываемого метода во внешнюю сборку, которая помещается туда же, куда и ссылочная сборка контрактов. В этом случае контракт проверяется на стороне вызывающего кода, и такой вариант является оптимальным, чтобы включать/отключать проверки для кода, который вы не можете контролировать напрямую.

Заключение

Code Contracts — такая область .NET Framework, которую стоит исследовать гораздо детальнее. В этой статье я лишь поверхностно затронул некоторые конфигурационные параметры и даже не добрался до использования Static Checker. Code Contracts помогают лучше проектировать приложения и писать более ясный код.

Чтобы узнать больше о Code Contracts, прочитайте рубрику «CLR с изнанки» за июнь 2009 г. (msdn.microsoft.com/magazine/ee236408) и посетите сайт DevLabs Code Contracts (msdn.microsoft.com/devlabs/dd491992).

Кроме того, массу интересной информации о разработке с применением Code Contracts можно найти на сайте Microsoft Research Code Contracts (research.microsoft.com/projects/contracts).

Автор: Дино Эспозито  •  Иcточник: MSDN Magazine  •  Опубликована: 07.09.2011
Нашли ошибку в тексте? Сообщите о ней автору: выделите мышкой и нажмите CTRL + ENTER
Теги:   Visual Studio 2010.


Оценить статью:
Вверх
Комментарии посетителей
Комментарии отключены. С вопросами по статьям обращайтесь в форум.