真的想在C#中喜欢CodeContracts

时间:2010-06-20 01:50:19

标签: c# code-contracts

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

我喜欢合同提供的验证,乍一看它们看起来很棒。通过一些简单的行,我可以在运行代码之前获得一些很好的构建检查。不幸的是,我很难理解代码契约在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);
}

还有更多,但我认为我可能已经足够了,我真的很感激那些比我更聪明的人的洞察力来帮助我“喜欢”代码合同并让这种代码混乱的感觉消失。任何有关如何更好地构建代码,解决方法难以解决的问题的见解都将非常感激。

谢谢!

6 个答案:

答案 0 :(得分:25)

  

这对我来说就像是一块混合物,我希望通过属性或某种形式的内置语言支持,可以更清晰地记录需求。

CC团队已经声明使用属性不够强大,因为你不能在其中包含lambda这样的东西。他们可以包含[NotNull]chosen not to do so之类的内容,因为他们试图尽可能保持CC的一般性。

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

您可以详细了解该团队的推理here

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

  

代码膨胀[...]

您的第二次投诉可能是可以实施的 - 我建议将其发布在the code contracts forum上。 (编辑:似乎有人already has,但还没有答案。)

但是,指定合同不足的情况总是需要更多的假设。如果您在.NET框架中遇到这样的情况,您可以请求在Missing Contracts on Libraries thread中添加合同。

  

此外,采取像[...]

这样的案例

has been addressed。如果您有自动属性,则只需添加非null不变量,并生成前/后条件:

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

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

无论如何,你可能会为你的课程最终得到其他不变量,所以这不是什么大不了的事。

答案 1 :(得分:11)

是的,合同混乱了,但是当PEX读取代码合同并为我生成25个单元测试和100%代码覆盖时,我觉得值得。如果你还没有使用过PEX,那么你就错过了代码合同的最大好处。以下是使用PEX with contracts的初学者指南。

答案 2 :(得分:9)

我真的很想也喜欢它,并且在新项目中实施了很多, 直到我放弃了我带着它的小问题的重量。

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

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

无论如何,没有任何一个问题是破坏性的,而是一种激怒的积累:

  • 静态分析非常慢。您可以将其设置为在后台运行,但之后很容易忽略。
  • 静态分析不是很好,例如:
    • 它只处理简单的控制流程
    • 不会假设只读字段是不变的
    • 集合不由静态检查程序(仅运行时)处理
  • 没有代表合同
  • 除非您以这种或那种方式主动抑制resharper警告,否则不能与Resharper一起使用
    • Resharper很乐观。它会假设一个方法参数不为null,直到你给它一个暗示它可能是 - 如Contract.Assume(thing != null)
  • 可以产生很多警告和建议,有时源于模棱两可的来源。
    • 当任何团队成员让他们的注意力下滑时,警告的数量对每个人来说都会变得无法抗拒
  • 为接口指定抽象合同类的需要很痛苦。
  • 运行时契约使用IL重写,这显然不适合其他重写解决方案,如PostSharp(我认为。
    • 由于IL重写,无法使用编辑并继续
  • 合同与liskov取代原则紧密相关:亚型永远不会放松前提条件。我的意思是,原则上很好,但我认为在处理过度签约的第三方代码时会很痛苦。

答案 3 :(得分:3)

如果您担心代码膨胀,那么我建议您考虑Aspect Oriented Programming作为替代方案。

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

不幸的是,在C#中对AOP的支持并不是很好,但是有一些库可以添加这个功能。

而且我对其他C#实现也有同样的感觉,就像你在Contracts中找到的一样,一开始看起来一切都很好,直到你抬起引擎盖并开始以严肃的方式使用它。

答案 4 :(得分:3)

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

答案 5 :(得分:2)

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

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

我理解你关于代码膨胀的观点,但我想这是代码合约的权衡。额外的检查/安全意味着额外的代码,至少是当前实现的方式。我会尽量在接口上保留尽可能多的合同,这至少可以帮助你减轻代码膨胀的痛苦。