59

我终于赶上了 .NET 3.5/4.0 框架中添加的所有新内容。最近几天我一直在使用 CodeContracts,我真的很努力地喜欢它们。我很好奇其他人对 C# 中 CodeContracts 的实现有何看法?具体来说,人们如何组织诸如接口的契约类、契约不变量的契约方法等?

我喜欢合同提供的验证,乍一看它们看起来很棒。只需几行简单的代码,我什至可以在运行代码之前进行一些不错的构建检查。不幸的是,我很难克服在 C# 中实现代码契约的方式的感觉,它们使我的代码更加混乱,而不是记录契约。为了充分利用合同,我在我的代码中乱扔假设和断言等(我知道有些人会说这是一件好事);但正如我下面的一些示例将显示的那样,它将单个简单的行变成 4 或 5 行,并且在我看来并没有真正增加替代方法(即断言、异常等)的足够价值。

目前,我最大的挫败感是:

接口合约:

[ContractClass(typeof(IInterfaceContract))]
  public interface IInterface
  {
    Object Method(Object arg);
  }

  [ContractClassFor(typeof(IInterface))]
  internal abstract class IInterfaceContract
  {
    private IInterfaceContract() { }

    Object IInterface.Method(Object arg)
    {
      Contract.Requires(arg != null);
      Contract.Ensures(Contract.Result<Object>() != null);
      return default(Object);
    }
  }

这对我来说就像是一个障碍,我希望有一种更简洁的方式来记录需求,无论是通过属性还是某种形式的内置语言支持。我必须实现一个抽象类来实现我的接口,这样我才能指定合同,这一事实充其量似乎很乏味。

代码膨胀:

typeof(Action<>).MakeGenericType(typeof(Object);

需要几个假设来验证现成的信息。我很感激分析器所知道的只是它在 Type 上运行,因此必须使用有限的知识,但仍然让我感到沮丧的是,一行代码需要我重写为

var genericAction = typeof(Action<>);

Contract.Assume(genericAction.IsGenericType);
Contract.Assume(genericAction.GetGenericArguments().Length == 1);

genericAction.MakeGenericType(typeof(Object));

只是为了将事情记录在案(是的,我知道我可以使用 ContractVerificationAttribute 为方法/类等关闭此功能,或使用 SuppressMessageAttribbute 来针对特定消息,但这似乎违背了目的,因为您的代码很快就会充满抑制等。

另外,举个例子

  public class MyClass
    : IInterface
  {
    private readonly Object _obj;

    public Object Property
    {
      get
      {
        Contract.Ensures(Contract.Result<Object>() != null);
        return _obj;
      }
    }

    public MyClass(Object obj)
    {
      Contract.Requires(obj != null);

      _obj = obj;
    }
  }

obj 必须不为 null,并且设置为无法更改的只读字段,但我仍然需要向我的类添加“标记”方法,以便可以证明我的属性要求不返回 null:

[ContractInvariantMethod]
private void ObjectInvariant()
{
  Contract.Invariant(_obj != null);
}

还有更多,但我想我可能已经咆哮得够多了,我真的很感谢比我聪明得多的人的洞察力来帮助我“喜欢”代码合同并消除这种代码混乱的感觉。任何关于如何更好地构建代码、解决不稳定问题等的见解将不胜感激。

谢谢!

4

6 回答 6

26

这对我来说就像是一个障碍,我希望有一种更简洁的方式来记录需求,无论是通过属性还是某种形式的内置语言支持。

CC 团队表示,使用属性还不够强大,因为你不能在其中包含像 lambdas 这样的东西。他们可以包括类似[NotNull]选择不这样做的内容,因为他们试图使 CC 尽可能通用。

CC 是一个库(而不是扩展 C# 的一部分)的一个原因是它在所有.NET 语言中都受支持。

您可以在此处阅读有关团队推理的更多信息。

就实际使用而言,到目前为止,我一直将我的接口契约与接口保存在同一个文件中,这意味着它们都记录在同一个地方。这是应该改进的东西:)

代码膨胀 [...]

您的第二个投诉可能是可以实施的——我建议将其发布在代码合同论坛上。(编辑:似乎有人已经有了,但还没有答案。)

然而,未充分指定的合同总是需要围绕它们进行更多假设。如果您在 .NET 框架中遇到这样的情况,您可以请求将合同添加到库线程上的缺失合同中

此外,采取像 [...]

已得到解决。如果你有一个自动属性,你只需要添加一个非空不变量,就会生成前置/后置条件:

public class MyClass : IInterface
{
    private Object Property { get; set; }

    [ContractInvariantMethod]
    private void Invariants()
    {
        Contract.Invariant(Property != null);
    }
}

无论如何,您最终可能会为您的类找到其他不变量,所以这没什么大不了的。

于 2010-06-23T04:41:34.050 回答
12

是的,合同使事情变得混乱,但是当PEX读取代码合同并为我生成 25 个单元测试和 100% 的代码覆盖率时,我觉得这是值得的。如果您还没有使用过 PEX,那么您就错过了代码合约的最大好处。这是将PEX 与合约一起使用的入门指南。

于 2010-06-21T04:30:25.810 回答
9

我真的很想喜欢它,并且在一个新项目中的实现方面走了很长一段路,直到我在遇到的小问题的重压下放弃了它。

我希望微软能够复活 Spec#。我知道代码契约是作为一个库编写的,可以跨语言使用,但即使只是静态分析的速度也需要编译器支持。

这家伙提出了一些相同的观点:http ://earthli.com/news/view_article.php?id=2183

无论如何,没有一个问题是破坏交易的,而是累积的愤怒:

  • 静态分析很慢。您可以将其设置为在后台运行,但很容易被忽略。
  • 静态分析不是很好,例如:
    • 它只处理简单的控制流
    • 它不会假设只读字段是不变的
    • 静态检查器不处理集合(仅运行时)
  • 没有委托合同
  • 除非您以一种或另一种方式主动抑制 resharper 警告,否则不适用于 Resharper
    • Resharper 很乐观。它将假定方法参数不为空,直到您提示它可能是 - 像Contract.Assume(thing != null)
  • 可能会产生很多警告和建议,有时源于模棱两可的来源。
    • 一旦任何团队成员的注意力滑落,警告的数量就会让每个人都不堪重负
  • 为接口指定抽象契约类的需要很痛苦。
  • 运行时合约使用 IL 重写,这显然与 PostSharp 等其他重写解决方案(我认为 .
    • 由于IL重写,无法使用edit-and-continue
  • 合约非常严格地遵循 liskov 替换原则:子类型永远不能放松前提条件。我的意思是,原则上很好,但我想在使用过度承包的 3rd 方代码时会很痛苦。
于 2011-02-24T17:03:17.773 回答
3

如果您担心代码膨胀,那么我鼓励您将面向方面的编程作为替代方案。

这将允许您在方面中指定协定,然后在协定中包装任何类或类的方法。如果您的合约将在多个类中使用,那么这将是一种更简洁的实现方式。

不幸的是,C# 中对 AOP 的支持不是很好,但是有几个库添加了这个功能。

而且我对其他 C# 实现也有同样的感觉,就像你在使用 Contracts 时发现的那样,起初一切似乎都很好,直到你揭开面纱并开始认真使用它。

于 2010-06-21T00:26:45.540 回答
3

我为 Visual Studio 2010 创建了一个插件,它可以节省您一些时间为接口和抽象类创建代码协定:http: //visualstudiogallery.msdn.microsoft.com/en-us/d491911d-97f3-4cf6-87b0-6a2882120acf

于 2010-07-15T15:49:39.087 回答
2

实际上,我发现接口实现很棒。它不会弄乱您的界面或常规代码,您可以将其整齐地存储在解决方案中的某个文件夹中,或者与您的界面相同的类文件中,无论您最喜欢哪个。

为什么需要添加对象不变量?您只需要添加您认为必要的量。

我理解您关于代码膨胀的观点,但我想这是与代码合同的权衡。额外的检查/安全意味着额外的代码,至少目前是如何实现的。我会尝试在接口上保留尽可能多的合同,这至少应该可以帮助您减轻代码膨胀的痛苦。

于 2010-06-21T13:59:34.783 回答