zoukankan      html  css  js  c++  java
  • 我对于Design by Contract的最佳实践 通往无错代码之路 欢迎砸场(玩笑)

    前言

    在上一篇文章中,我简单介绍了契约设计(Design by Contract)的现状,还有自己的想法。

    http://www.cnblogs.com/zc22/archive/2009/11/30/1614142.html

    这篇文章主要介绍我如何实现了这个设计。

    阅读本文前,想再强调一下,本文介绍的是一种实践,而不是技巧,或者能够保证您代码绝对 0 bug的方法。但是在这种实践下,代码 0 bug 是可以达到的。

    正文

    一个简单的契约设计,就是约束了某个方法调用的要求、以及返回的承诺。比如:

    代码

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

    如果我要调用Log.Info的方法,前提必须满足message非空。否则这个方法不能保证返回需要的结果。

    那么根据经典的计算机原则:在正确的输入下,能够得到正确的输出。我只要保证了我所有调用方法的前提要求,我的代码就是 0 bug。这个就是契约设计。

    以往我们会通过各种判断去检测输入,这种设计模式叫做:防范式设计(请看code complete 2,里面有详细介绍)。防范式设计的前提就是:我们会假设调用这个方法是恶意的,因此要对所有可能出错的输入进行检测。

    在这种设计模式下,就会导致了各种与业务逻辑无关的代码充斥着我们的程序,维护困难,看起来也恶心。因此契约设计是非常反对的。

    可是非常可惜,现在连微软也还走不出这个圈子,一堆的Policy Injection / AOP / attribute / SPec#等等,仍然无法成为主流。

    现在我就尝试去走出这个圈子。

    首先,我会对Log.Info这个方法设置一个Contract.Constraint,如果用中文就是契约里面的约束(合同约束)。

    代码

        public class Log
        {
            [Contract.Constraint(
    "message不能空")]
            
    public void Info(string message)
            {
                
    //do something here
            }
        }

    很简单,我什么都没有做,仅仅好像输入了一个注释,内容是messge不能为空

    然后,我去尝试调用这个方法:

    代码
        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);
            }
        }

    在test001这个方法里面,我对契约进行了验证,因此得到了输出:

    代码

    check the call001 can be approved by contract.
    approval result = :False
    the constraint of "message不能空" is not committed.

    结果是:approval result = FAlse

    意思就是,契约的约束没有被履行,方法call001是个无效的方法。大家这个时候应该清楚什么意思了吧。

    简单说明下,Log.Info有个契约的约束,要使用这个方法必须履行这个约束,可是我在call001这个方法里面没有履行这个约束,所以合同无效,也就不能保证代码是正确的。

    如何保证呢?请看:

    代码
        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.info的契约。所以,我得到的测试结果是:

    代码

    check the call002 can be approved by contract.
    approval result = :True

    结果是:approval result = TRUE,再简单说下,因为我在call002里面使用了承诺,因此在这个承诺范围内,所有契约的约束我都会被满足。

    这下子我的思路就清楚拉吧!!老实说,我真的很兴奋!!!因为我找到了通往 0 bug 的道路了!

    源码下载

     http://www.boxcn.net/shared/304p0ktnke

    核心技术介绍

    其实思路比较简单的,就是查看当前方法体内 所有使用了的方法是否存在了契约的约束,如果存在了但是没有被履行,那么就认为契约无效。

    当然,为了实现这个技巧,我使用了Reflection.Emit,直接操作了IL语言去获取方法体内的信息。

    后记

    当一个问题无法解决的时候怎么办?采用最简单的方法。Design by contract是个绝对的精华,可惜大部分人总是再想怎么去用代码实现,怎么在编译解析、运行中去实现。

    因此我尝试了退一步,海阔天空。就像TestDriven一样,难道我们会在代码里面放各种Assert吗?肯定不会。但是我们在coding的时候需要了。

    契约设计也是一样,他是一种编码过程,而不是结果。只有这个思路清晰了,才能豁然开朗。

    技术支持

    reborn_zhang@hotmail.com

    zc22.cnblogs.com

    http://www.codeproject.com/KB/cs/DesignByContract_indotnet.aspx

  • 相关阅读:
    Eclipse背景颜色设置
    SQL ROW_NUMBER() OVER函数的基本用法用法
    hdu 2844 Coins 多重背包问题
    VC++学习/MFC (1)
    java学习 (1)
    hdu 1506 City Game 二维的多重背包
    java学习(2)
    VC++学习/MFC (2)
    hdu 1506 Largest Rectangle in a Histogram
    hdu 1171 Big Event in HDU
  • 原文地址:https://www.cnblogs.com/zc22/p/1614783.html
Copyright © 2011-2022 走看看