使用代码约定验证参数属性

时间:2013-10-16 12:00:39

标签: c# code-contracts

我与同事讨论过使用代码合同来执行先决条件检查。

假设我们有以下代码:

namespace Project
{
    using System;
    using System.Diagnostics.Contracts;

    public class Thing
    {
        public string Foo { get; set; }

        public int Bar { get; set; }
    }

    public class ThingsManipulator
    {
        public void AddThing(Thing thing)
        {
            Contract.Requires<ArgumentNullException>(thing != null);

            // Do something
        }
    }
}

如果// Do something我正在访问thing.Foothing.Bar做某事,我是否应该通过代码合同对其进行验证?

public void AddThing(Thing thing)
{
    Contract.Requires<ArgumentNullException>(thing != null);
    Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(thing.Foo));
    Contract.Requires<ArgumentException>(thing.Bar > 0);

    // Do something
}

我的同事说只应检查整个参数(即我们应该只放置第一份合约),我认为无论是整个参数还是其中一个属性,方法都应检查它们的用途(即我们应该所有三个合同)。

请注意,我理解并同意,如果参数的属性应始终满足要求,则该要求应放在对象的不变检查中。

我所指的是通常有效但对特定方法无效的值(例如,在上面的示例中thing.Bar可能会很乐意保留负值,但AddThing不喜欢它们)。

我的同事说在这些情况下,方法签名应该显示它使用的所有项目而不是单个对象(例如AddThing(string thingFoo, int thingBar)),并对它们运行检查。

所以:

  • 我们应该验证方法使用的是什么,还是只验证整个参数并“爆炸”参数?
  • 是否有技术上的理由(无论“它”意味着什么)或者是偏好的问题?

我无法在the manual找到相关指南,也许我错过了什么?

1 个答案:

答案 0 :(得分:6)

有助于将合同视为不是验证事物,而是将验证者试图证明的事实陈述。你实质上在说“只要我的输入是正确的( Contract.Requires ),那么以下属性就会成立( Contract.Ensures Contract.Assert Contract.Invariant )“。

合同应该是关于它们所使用的上下文应该始终是真实的陈述。在你的例子中,你说的是Thing的Foo必须是非空的 '正在被一个ThingsManipulator使用。

如果你可以说Thing的Foo 总是需要非空,那么对于Thing来说总是如此,并且契约属于Thing。

在这种情况下你不能,所以我认为这成为关于参数的OO设计问题。大声思考:

  1. 方法应尽可能少参数(参见https://stackoverflow.com/a/175035/1554471)。
  2. AddThing是一个公共方法,它可能应该处理更高级别的抽象,而不是像Framework值类型这样的细节。
  3. AddThing是目前唯一对Thing有这些限制的方法。
  4. 因此,请保留Thing类型的单个参数,并检查其属性。
  5. 如果(3)停止为真,则可能值得创建一个装饰或以其他方式从Thing派生的新类型,并将合同放在那里,以避免repeating yourself的问题。但目前,YAGNI