65.9K
CodeProject 正在变化。 阅读更多。
Home

我在 C# 中实践契约式设计。

starIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

1.00/5 (5投票s)

2009年12月1日

CPOL

5分钟阅读

viewsIcon

274449

downloadIcon

94

我在 C# 中实践契约式设计。

引言

最近我尝试开发一个验证框架来减少系统中的 bug。我接触到了契约式设计 (DbC)。DbC 的思想确实很棒!但目前还没有可行的实践。所以我自己实现了一个。

更新于 2009-12-02 200400:很多人仍然不理解我的意思。所以我重写了这篇文章。

背景

契约式设计最初由 Bertrand Meyer 提出。您可以访问他的主页:契约式设计,EIFFEL。这位仁兄也为 DbC 开发了 Eiffel。

在 dotnet 中,我们也有一些关于 DbC 的优秀工作,例如:Spec#LinFu。但它们要么试图开发一种新语言 (spec#),要么使用 AOP (LinFu),要么使用特性 (Microsoft...)。结果呢?仍然是一团糟。

为什么契约式设计不能成为 OO、XP 的主流?因为它们都走错了方向。所以……请看看我的工作。

使用代码

在契约式设计中,当我们编写类中的方法时,我们假设用户会满足方法的约束条件,然后方法就会返回承诺的结果。这就是 DbC。就像下面的代码一样

    public class Log
    {
        public void Info(string message)
        {
            //do something here
        }
    }
		

当我调用 Log.Info 时,消息必须非空,否则,函数无法保证返回正确的结果。

以前,我们会尝试在 Info 方法中进行检查。

    public class Log
    {
        public void Info(string message)
        {
         if(string.IsNullorEmpty(message))
           throw new Exception("missing message.");

            //do something here
        }
    }
这被称为:防御性编程。然而,如果情况变得复杂,我们会在业务逻辑中发现成千上万的防御性代码。这真是太糟糕了!!!

更新于 2009-12-02:.net framework 4.0 中发生了什么?看看:微软 .net 4.0 契约式设计

   public class Log
   {
        public void Info(string message)
        {
         CodeContract.Requires(null != message);
 
         //do something here
        }
   }
现在,停下来,仔细想想。以前的代码有什么变化吗?也许微软会说,这是契约式设计,编译代码时我们会收到警告……等等。

请停止自欺欺人。如果它真的如你所想,为什么不随处使用它呢?(因为有时我们已经确定代码是正确的。)那么为什么仍然有很多 bug?(嗯……有些错误是 DbC 无法捕捉的。)好吧……请停止找借口。我告诉你真相。

计算机语言的能力是有限的,但人类的需求是复杂的。微软开发了一个伟大的工具 .net,以及 C# 语言。它可以满足大多数人类需求,并将需求转化为机器代码。但在契约式设计方面,情况就不同了。

如果我们遵循传统的方式,无论使用何种技术,我们都会发现编写“契约”所花费的时间与编写“业务逻辑”所花费的时间一样多。最后,一半的代码是契约。所以这就是我们不接受 DbC 的原因。

判断输入参数非空,这很简单。但是,如果我们传递一个对象呢?如果对象包含另一个对象呢?如果……所以,如果我们仍然想用计算机语言(c#/java/whatever)写下契约,我们只是在浪费时间。我们需要另一种思路。

契约式设计不支持防御性编程。

不幸的是,即使微软仍在研究防御性编程……请看 Spec#……所以我尝试走另一条路。

首先,我为 Log.Info 设置了一个约束。

    public class Log
    {
        [Contract.Constraint("message is not null")]
        public void Info(string message)
        {
            //do something here
        }
    }
在上面的代码中,我实际上什么都没做,只是在方法上设置了一个属性。

然后,我尝试调用方法 Log.Info,如下所示

    public class TestCase
    {
        public void call001()
        {
            string message = "hello world";
            Log log = new Log();
            log.Info(message);
        }
    }
它工作了,对吗?当然。那么它与契约式设计有关吗?请耐心点,我们还没有验证契约。

    public class TestCase
    {
        public void test001()
        {
            MethodInfo method = typeof(TestCase).GetMethod("call001");

            Console.WriteLine("check the call001 can be approved by contract.");

            Console.WriteLine("approval result = :" + Contract.Approval(method));

            foreach (IConstraint cons in Contract.GetOpenConstraints(method))
            {
                Console.WriteLine("the constraint of \"" + cons.ConstraintName + "\" is not committed.");
            }
        }
        public void call001()
        {
            string message = "hello world";

            Log log = new Log();

            log.Info(message);
        }
    }
在 Testcase.test001 中,我使用 'Contract.Approval' 来验证 method call001。并得到结果
check the call001 can be approved by contract.
approval result = :False
the constraint of "message is not null" is not committed.
这意味着,Log.Info 的约束在 call001 中没有被满足,因此 call001 不是一个有效的方法。现在您可以对契约式设计有所感受了。接下来做什么?阅读契约,并遵循契约。例如,我们确定 message 非空,然后继续下一个例子
    public class TestCase
    {
        public void test002()
        {
            MethodInfo method = typeof(TestCase).GetMethod("call002");

            Console.WriteLine("check the call002 can be approved by contract.");

            Console.WriteLine("approval result = :" + Contract.Approval(method));

            foreach (IConstraint cons in Contract.GetOpenConstraints(method))
            {
                Console.WriteLine("the constraint of \"" + cons.ConstraintName + "\" is not committed.");
            }
        }
        [Contract.Commitment("input of log is not null")]
        public void call002()
        {
            string message = "hello world";

            Log log = new Log();

            log.Info(message);
        }
    }
在这种情况下,call002 调用 Log.Info,但我为 call002 设置了 Contract.Commitment。它承诺 log 的输入非空。所以当我运行 test002 时,我得到了
check the call002 can be approved by contract.
approval result = :True
是的!太棒了!通过!这就是我想要的!所以,现在您可以理解我的意思了。

所以……是的,我没有使用机器来验证约束。而是,我将契约式设计应用到编码、应用到“设计”中。这就是我想要的,我相信,这就是契约式设计。

摘要

当问题出现时,找到最简单的解决方案。它是最好的解决方案。契约式设计不是一种新语言,也不是编码,也不是围绕代码的某些东西。契约式设计是一种实践,就像测试驱动开发一样。我相信契约式设计将在 XP 中变得流行,并成为测试驱动开发的伙伴。

更新于 2009-12-02:伙计们,我们通过创造而生活,而不是通过复制/粘贴,也不是通过下载。契约式设计是一种实践。不是一种新语言,或某种属性/或 AOP。

如果有一天,当我们写下一个方法名:call001(),它使用微软 .net framework 12.0 中的方法,该方法是 Log.info

        public void call001()
        {
            string message = "hello world";

            Log log = new Log();

            log.Info(message);
        }
并且在编译代码后,VS2020 给我一个警告,log.Info 的契约是='message is not null',我们需要满足它,那么您最终就会理解我的工作。

嗯,把脑子里的每一个字都打出来很累。希望未来有人能用“跳跃思维”来阅读我的文章。

© . All rights reserved.