真的想在C#中喜欢CodeContracts

Chr*_*ter 59 c# code-contracts

我终于在.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);
    }
  }
Run Code Online (Sandbox Code Playgroud)

这感觉就像这样一个cludge对我来说,我希望有一个更清洁的记录要求的方式,无论是通过属性或某种形式的内置的语言支持.我有这样的事实来实现,实现我的接口一个抽象类,只是让我可以指定合约似乎有些单调乏味的最好的.

代码膨胀:

typeof(Action<>).MakeGenericType(typeof(Object);
Run Code Online (Sandbox Code Playgroud)

需要几个假设才能验证现有的信息.我明白,所有的分析都知道的是,它是在型操作,因此必须在有限的知识工作,但它仍然让我很沮丧的是一个单一的代码行要求我重新写为

var genericAction = typeof(Action<>);

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

genericAction.MakeGenericType(typeof(Object));
Run Code Online (Sandbox Code Playgroud)

只是为了记录事情(是的,我知道我可以使用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;
    }
  }
Run Code Online (Sandbox Code Playgroud)

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

[ContractInvariantMethod]
private void ObjectInvariant()
{
  Contract.Invariant(_obj != null);
}
Run Code Online (Sandbox Code Playgroud)

还有更多,但我认为我可能已经足够大了,我真的很感激那些比我更聪明的人的洞察力来帮助我"喜欢"代码合同并让这种代码杂乱的感觉消失.任何有关如何更好地构建代码,解决方法令人讨厌的问题等的见解将不胜感激.

谢谢!

por*_*ges 26

这感觉就像这样一个cludge对我来说,我希望有一个更清洁的记录要求的方式,无论是通过属性或某种形式的内置的语言支持.

CC团队已经声明使用属性不够强大,因为你不能在其中包含lambdas之类的东西.他们可能会包含类似[NotNull]但却选择不这样做的事情,因为他们试图尽可能保持CC的一般性.

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

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

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

代码膨胀[...]

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

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

此外,采取类似[...]的案例

已得到解决.如果您有自动属性,则只需添加非null不变量,将生成前/后条件:

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

    [ContractInvariantMethod]
    private void Invariants()
    {
        Contract.Invariant(Property != null);
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Mat*_*son 12

是的,合同搞砸了,但是当PEX读取代码合同并为我生成25个单元测试和100%代码覆盖时,我觉得这是值得的.如果你还没有使用过PEX,那么你就错过了代码合同的最大好处.这是使用PEX与合同的入门指南.

  • 去年我在PDC(http://microsoftpdc.com/Sessions/VTL01)上看到了PEX会议,这是一个明确的优势.它补充了测试生成; 因为它负责生物板测试; 但是我确信你很清楚,即使它给你100%的代码覆盖率,我也不会单靠PEX测试(覆盖范围不是一切:D). (4认同)
  • 好的....并且测试将永远是绿色的,无论你的逻辑究竟有多么错误......生成的单元测试只是反映了错误的逻辑.它只对回归很有用,但是很容易让你对错误的代码感觉良好...... (3认同)

And*_*ndy 9

我真的很想也喜欢它,并且在一个新项目的实施中走得很远,直到我放弃了我用它的小问题.

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

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

无论如何,没有一个问题是一个交易破坏者,但积累的烦恼:

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

  • 真正的痛苦是所有数据都来自外部来源.例如,ORM将数据库映射到POCO对象.您必须设置ContractInvariantMethods来定义每个Auto-Property的合同.您基本上最终会在各处做出假设,这与经典的if-block检查没有什么不同,后者在应用程序的整个生命周期中累加时可能执行得更快. (2认同)