代码之家  ›  专栏  ›  技术社区  ›  Chris Baxter

真的很喜欢C语言中的codecontracts#

  •  58
  • Chris Baxter  · 技术社区  · 14 年前

    我终于开始关注添加到.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);
    

    需要几个假设来验证随时可用的信息。我很感激所有的分析器都知道它是在类型上运行的,因此必须在有限的知识上工作,但是一行代码要求我重写为

    var genericAction = typeof(Action<>);
    
    Contract.Assume(genericAction.IsGenericType);
    Contract.Assume(genericAction.GetGenericArguments().Length == 1);
    
    genericAction.MakeGenericType(typeof(Object));
    

    只是为了保持文档化(是的,我知道我可以使用ContractVerificationAttribute为方法/类等关闭它,或者使用SuppressMessageAttribute为目标特定消息关闭它,但是这似乎会破坏目的,因为您的代码很快就会被抑制等弄得乱七八糟。

    另外,拿一个像

      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必须不为空,并且设置为不能更改的只读字段,但我仍然需要向类中添加“标记”方法,以便证明我的属性要求不返回空值:

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

    还有很多,但我觉得我可能已经说得够多了,我真的很感激比我聪明得多的人的洞察力,帮助我“喜欢”代码契约,让这种代码混乱的感觉消失。对于如何更好地构造代码、解决棘手问题等方面的任何见解都将非常感谢。

    谢谢!

    6 回复  |  直到 9 年前
        1
  •  25
  •   porges    14 年前

    对我来说,这就像是一个很难理解的想法,我希望有一种更清晰的方法来记录需求,无论是通过属性还是某种形式的内置语言支持。

    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 . 如果您有一个auto属性,只需添加一个非空的不变量,就会生成pre-/post条件:

    public class MyClass : IInterface
    {
        private Object Property { get; set; }
    
        [ContractInvariantMethod]
        private void Invariants()
        {
            Contract.Invariant(Property != null);
        }
    }
    

    不管怎样,您可能最终会得到类的其他不变量,所以这没什么大不了的。

        2
  •  11
  •   Matt Dotson    14 年前

    是的,合同杂乱无章,但我觉得当 PEX 读取代码契约并为我生成25个单元测试和100%的代码覆盖率。如果您还没有使用PEX,那么您就错过了代码合同的最大好处。这是使用入门指南 PEX with contracts .

        3
  •  9
  •   Andy    13 年前

    我也很想喜欢它,并且在一个新项目中进行了相当大的实施, 直到我在琐碎的小问题的重压下放弃。

    我很希望微软能恢复规范。我知道代码契约是作为跨语言可用的库编写的,但即使静态分析的速度也需要编译器的支持。

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

    不管怎样,没有一个问题会破坏交易,而是不断累积的刺激:

    • 静态分析非常缓慢。您可以将其设置为在后台运行,但很容易被忽略。
    • 静态分析不是很好,例如:
      • 它只处理简单的控制流
      • 它不会假定只读字段是不变的
      • 静态检查器不处理集合(仅运行时)
    • 无委托合同
    • 除非您以某种方式积极地禁止重新竖排警告,否则重新竖排无法正常工作。
      • Resharper很乐观。它将假定一个方法参数不是空的,直到您给它一个提示,它可能是-like Contract.Assume(thing != null)
    • 可以生成大量的警告和建议,有时来源不明确。
      • 当任何一个团队成员的注意力不集中时,警告的数量就会变得对每个人都是压倒性的。
    • 需要为接口指定抽象契约类是很痛苦的。
    • 运行时契约使用IL重写,这显然与其他重写解决方案(如Postshap)不太匹配(我认为)。
      • 由于IL重写,无法使用“编辑并继续”
    • 契约严格遵循李斯科夫替代原则:子类型永远不能放松先决条件。我的意思是,原则上是好的,但是我想当使用超过合同约定的第三方代码时会很痛苦。
        4
  •  3
  •   eaglestorm    14 年前

    如果您担心代码膨胀,那么我鼓励您查看 Aspect Oriented Programming 作为替代方案。

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

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

    我对其他C实现也有同样的感觉,就像你在合同中发现的那样,一开始一切看起来都很好,直到你掀起引擎盖,开始认真地使用它。

        5
  •  3
  •   Jarek Kardas    14 年前

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

        6
  •  2
  •   koenmetsu    9 年前

    实际上,我发现接口实现非常好。它不会使您的接口或常规代码混乱,您可以将它整齐地存储在解决方案中的某个文件夹中,或者与您的接口存储在同一个类文件中,以您最喜欢的为准。

    你为什么 必修的 要添加对象不变量吗?您只需要添加您认为必要的内容。

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