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

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

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.Requires(MyDirectory.Exists || !String.IsNullOrEmpty(MyDirectory.FullName)); он жалуется на видимость (закрытое поле используется в требовании защищенного метода).

4 ответа

Решение

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

Чтобы решить эту проблему, нужно обернуть параметр в методе, который имеет контракт. Например:

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

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

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

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

var path = MyDirectory.FullName;
Contract.Assume(!string.IsNullOrEmpty(path));
var catalog = new DirectoryCatalog(path);

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

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

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

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

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

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

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

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

Ну, для выпуска № 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, так что я, возможно, сошел с ума от всего этого.:)

Другие вопросы по тегам