Контракты кода и сбой в частных статических полях только для чтения

В моем классе есть частное статическое поле только для чтения:

public class MyClass
{
    // ISSUE #1 -- requires unproven: path != null
    private static readonly DirectoryInfo MyDirectory =
        new DirectoryInfo(Settings.Default.MyDirectoryPath);

    protected virtual void SomeMethod()
    {
        if (MyDirectory.Exists)
        {
            // ISSUE #2 -- requires unproven: !string.IsNullOrEmpty(path)
            var catalog = new DirectoryCatalog(MyDirectory.FullName);
        }
    }
}

Для проблемы №1 я использовал нулевой оператор объединения, чтобы по умолчанию использовать некоторую магическую строку, и это исправило ее, но мне это решение не очень нравится. Я надеялся, что есть лучшее решение.

Для проблемы № 2 единственное, что я могу придумать, - это использовать Contract.Assumes, потому что, если я пытаюсь использовать Contract.Requires(MyDirectory.Exists || !String.IsNullOrEmpty(MyDirectory.FullName));, он жалуется на видимость (частное поле, используемое в a, требует защищенного метода).


person myermian    schedule 19.06.2012    source источник
comment
Я мечтаю о том дне, когда мы сможем использовать Контракты так, как вы их пытаетесь использовать, но я не думаю, что мы еще на этом закончили ... # 2 предполагает, что DirectoryInfo неправильно помечен контрактами, что оставляет мало жизнеспособных опции.   -  person Roman Starkov    schedule 19.06.2012


Ответы (4)


Проблема №1 является результатом того, что Settings.Default.MyDirectoryPath является кодом, созданным Visual Studio без каких-либо контрактов на свойство. Эта проблема не ограничивается пустыми строками. Многие API теперь имеют контракты, которые требуют, чтобы, скажем, TimeSpan было неотрицательным, но использование настройки непосредственно в API вызовет предупреждение Code Contracts.

Способ решить эту проблему - заключить настройку в метод, имеющий контракт. Например.:

String GetMyDirectoryPath() {
  Contract.Ensures(Contract.Result<String>() != null);
  var myDirectoryPath = Settings.Default.MyDirectoryPath;
  Contract.Assume(myDirectoryPath != null);
  return myDirectoryPath;
}

Обратите внимание, как Contract.Assume действительно выполняет проверку ваших настроек (которые не могут быть проверены Code Contracts, потому что они контролируются внешним файлом конфигурации). Если бы это был TimeSpan, который, как ожидается, будет неотрицательным, вы можете использовать Contract.Assume для проверки, в результате чего будет ContractException, или какой-либо другой метод, использующий вместо этого ваше собственное исключение.

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

Проблема №2, вероятно, связана с тем, что для DirectoryInfo не определены никакие контракты. Самый простой способ - использовать Contract.Assume. Это сделает заявление о том, что, по вашему мнению, является ожидаемым поведением DirectoryInfo, но проверка во время выполнения будет по-прежнему выполняться, чтобы убедиться, что ваше мнение верное (при условии, что вы сохраняете проверки в своем коде).

var path = MyDirectory.FullName;
Contract.Assume(!string.IsNullOrEmpty(path));
var catalog = new DirectoryCatalog(path);
person Martin Liversage    schedule 19.06.2012

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

  1. Вы можете добавить параметр в настройки вашего проекта, чтобы вывести, какие атрибуты следует применять для игнорирования определенных предупреждений. Это делается путем добавления флага «-outputwarnmasks» в «Дополнительные параметры статической проверки» в разделе «Дополнительно» на вкладке «Контракты кода» в настройках файла проекта. Это добавит информацию в окно вывода сборки, в котором будут указаны правильные атрибуты, которые нужно добавить, чтобы игнорировать отдельные случаи. (очень полезно при работе с Entity Framework).
  2. Вы можете переписать свой код, чтобы добавить в него правильные требования и гарантии, чтобы предупреждения не появлялись.

Если вы хотите переписать код: для решения проблемы №1 вам придется обернуть класс Settings и предоставить новый MyDirectoryPath как свойство, которое не генерируется кодом, чтобы вы могли добавить в него проверку и вернуть пустую строку и добавьте Contract.Ensures(Contract.Result<string>() != null) вверху Getter для свойства.

Чтобы решить проблему №2, вам придется заключить доступ к полю класса в частное статическое свойство, которое добавляет соответствующие Ensures и Requires.

Обычно я переписывал код везде, где это было возможно, кроме Entity Framework / LINQ, где вам нужно добавлять атрибуты, особенно со сложными запросами.

** Заявление об ограничении ответственности ** Это всего лишь способы, которые я нашел для решения проблем, поскольку не так много информации о других способах работы с этими типами элементов.

person Adam Gritt    schedule 19.06.2012

Что ж, для вопроса №2, я думаю, вы можете использовать &&, а не ||. Но помимо этого, возможно, для вопроса №1 вы можете поместить эти проверки в статический конструктор? Другой вариант для проблемы №2 - использовать метод, принимающий каталог в качестве параметра:

private static readonly DirectoryInfo MyDirectory;

static MyClass()
{
    Contract.Requires(Settings.Default.MyDirectoryPath != null);
    MyDirectory = new DirectoryInfo(Settings.Default.MyDirectoryPath);
}

protected void SomeMethod()
{
    SomeOtherMethod(MyDirectory);
}

protected virtual void SomeOtherMethod(DirectoryInfo directory)
{
    Contract.Requires(directory.Exists && !String.IsNullOrEmpty(directory.FullName));

    var catalog = new DirectoryCatalog(directory.FullName);
}

У меня нет большого опыта работы с Contract API, так что я могу сбиться с пути со всем этим. :)

person Chris Sinclair    schedule 19.06.2012

Contract.Requires (MyDirectory.Exists ||! String.IsNullOrEmpty (MyDirectory.FullName));

Не делай этого! MyDirectory.Exists может измениться в любое время, и вызывающий абонент не может этого гарантировать. Просто создайте исключение, если каталог не существует - для этого нужны исключения.

person porges    schedule 26.06.2012