1

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

假设我们有以下代码:

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)),并对它们运行检查。

所以:

  • 我们应该验证方法使用什么还是只验证整个参数并“分解”参数?
  • 是否有技术原因(无论“它”是什么意思)还是偏好问题?

我无法在手册中找到有关此的指南,也许我错过了什么?

4

1 回答 1

6

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

合同应该是关于它们所使用的上下文的陈述。在你的例子中,你说的是,只有在 ThingsManipulator 使用它们时,Thing 的 Foo 才需要是非空的。

如果您可以说 Thing 的 Foo始终要求为非空,那么对于 Thing 始终是正确的,并且合同属于 Thing。

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

  1. 方法应该有尽可能少的参数(参见https://stackoverflow.com/a/175035/1554471)。
  2. AddThing 作为一个公共方法,它可能应该处理更高级别的抽象,而不是像框架值类型这样的细节。
  3. AddThing 是目前唯一对 Thing 有这些限制的方法。
  4. 所以保留一个 Thing 类型的参数,并检查它的属性。

如果 (3) 不再为真,那么可能值得创建一个装饰或以其他方式派生自 Thing 的新类型,并将合同放在那里,以避免重复自己的问题。但此刻,YAGNI

于 2013-10-16T13:22:07.687 回答