我的类中有一个私有静态只读字段:
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);
在当前项目中使用代码合约一段时间后,我发现它确实迫使您有时重写代码以纠正问题。 你在这里真的有两个选择。
- 您可以将设置添加到项目设置中,以输出要应用的正确属性,以忽略某些警告。 这是通过将"-outputwarnmasks"标志添加到项目文件设置的"代码协定"选项卡中"高级"部分下的"额外静态检查器选项"来完成的。 这会将信息添加到"生成输出"窗口,为您提供要添加以忽略个别情况的正确属性。 (在处理实体框架时非常有用)。
- 可以重写代码以向代码添加适当的"要求"和"确保",以便不显示警告。
如果要重写代码:若要解决问题 #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
可以随时更改,呼叫者无法保证。如果目录不存在,只需抛出异常 - 这就是异常的用途。