代码合同:如何表达这些条件?

时间:2012-11-20 08:43:05

标签: c# code-contracts specifications

我现在正在使用Code Contracts,我不能完全确定Contract类的静态方法是否足够强大,可以与条件的数学符号竞争。

我们假设我们得到了一个简单的因子方法

int Factorial(int n);

我会表达以下条件:

Precondition:
n >= 0

Postconditions:
Factorial(n) = 1, in case n = 0
Factorial(n) = n*(n-1)*...*1, in case n > 0

这些条件以简洁明了的方式清楚地指明了Factorial的行为。我的问题是,是否可以通过代码合同来表达。

前提是微不足道的:

Contract.Requires(n >= 0)

条件发布条件可能使用

表示
if(n==0)
    Contract.Ensures(Contract.Result<int>() == 1);
if(n > 0)
    ...

但我不喜欢我在这里需要“if”语句的方式,因为它使前置和后置条件的简单列表更难阅读。我希望我们会有像

这样的东西
Contract.Ensures(...).InCase(...);

最后但并非最不重要的是,我不知道如何表达这一点,这是关于数学的常用符号:

n*(n-1)*...*1

猜猜我需要某种循环,但这会复制整个实现。是否有任何聪明的方式来表达这样的符号?

提前谢谢。

2 个答案:

答案 0 :(得分:4)

您正在寻找的是单元测试,而不是代码合同。

首先,像if n=0, then f(n) = 1if n=3, then f(n) = 6这样的检查是应该表示为单元测试的测试用例。

在你的情况下,我认为一个合适的帖子条件就像“结果总是&gt; = 1 ”。仅此而已。

假设您的阶乘类看起来像这样:

public class Factorial
{
    public int Compute(int n)
    {
        if (n == 0)
            return 1;

        return n * Compute(n - 1);
    }
}

使用NUnit Framework编写的合适单元测试将是:

[TestFixture]
public class FactorialTests
{
    [TestCase(0, 1)]
    [TestCase(1, 1)]
    [TestCase(2, 2)]
    [TestCase(7, 5040)]
    [TestCase(10, 3628800)]
    public void Compute_ReturnsCorrectResult(int n, int expectedResult)
    {
        var sut = new Factorial();

        Assert.AreEqual(expectedResult, sut.Compute(n));
    }
}

更新(评论后)

  

说明结果&gt; = 1未完全指定算法。

我不认为Code Contract的工作是详细指定算法。算法由方法指定。

如果代码合同是一个复杂的逻辑,就像方法本身一样,那么我想我们需要一个代码合同合同来验证代码合同是否执行了正确的检查。这显然会导致无限递归。

  

我没想到编译器会接受n*(n-1)*...*1。但是一些LINQ风格的通用范围运算符肯定会是一个重要的增加,例如从(n).To(1).Product()或From(n).To(m).Sum()

如果有这种形式的表达因子(并且可能存在),你当然可以在你的代码中使用它,而不是代码契约。

更新2

为了好玩,我找到了一种计算Factorials的LINQ方式:

Enumerable.Range(1, n == 0 ? 1 : n).Aggregate((a, i) => a * i);

答案 1 :(得分:1)

您可以尝试以下方法:

Contract.Ensures(Contract.Result<int>() == AlternativeFactorial(n));

其中AlternativeFactorial是:

[Pure]
public static int AlternativeFactorial(int n)
{
    if(n==0)
        return 1;
    if(n > 0)
    {
        //Alternative implementation.
    }
}

当然,您在合同中使用的任何内容都应该是无副作用的(纯粹的)。

现在,对于因子实现,我无法提出比w0lf更紧凑的“替代”实现。您应该考虑的是将方法的返回值从int更改为BigInteger。因子可以很快变得非常大。另请注意,通过在factorial值上添加后置条件,您的方法返回结果所需的时间将增加一倍。这可以通过仅在调试配置上构建CodeContracts来解决。