代码协定和私有静态只读字段上的失败



我的类中有一个私有静态只读字段:

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.Assumption,因为如果我尝试使用 Contract.Requires(MyDirectory.Exists || !String.IsNullOrEmpty(MyDirectory.FullName));它会抱怨可见性(在受保护方法的要求中使用的私有字段)。

问题 #1 是 Visual Studio 生成的代码Settings.Default.MyDirectoryPath而没有对属性进行任何协定的结果。此问题不仅限于空字符串。许多 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"标志添加到项目文件设置的"代码协定"选项卡中"高级"部分下的"额外静态检查器选项"来完成的。 这会将信息添加到"生成输出"窗口,为您提供要添加以忽略个别情况的正确属性。 (在处理实体框架时非常有用)。
  2. 可以重写代码以向代码添加适当的"要求"和"确保",以便不显示警告。

如果要重写代码:若要解决问题 #1,必须包装 Settings 类并将新的 MyDirectoryPath 公开为不是代码生成的属性,以便您可以在其中添加检查并返回空字符串,并在属性的 Getter 顶部添加Contract.Ensures(Contract.Result<string>() != null)

要解决问题 #2,您必须将对类字段的访问权限包装在一个私有静态属性中,该属性添加了适当的确保和要求。

我通常会尽可能重写代码,但实体框架/LINQ 除外,您需要添加属性,尤其是复杂查询。

**免责声明**这些只是我发现的解决问题的方法,因为没有大量关于解决这些类型项目的其他方法的信息。

好吧,对于问题#2,我认为您可能希望使用&&而不是||。 但除此之外,也许对于 Issue#1,您可以将这些检查放在静态构造函数中? Issue#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 的经验,所以我可能会对这一切感到失望。 :)

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

别这样! MyDirectory.Exists可以随时更改,呼叫者无法保证。如果目录不存在,只需抛出异常 - 这就是异常的用途。

最新更新