减少函数的副作用

目录:

一.什么样的方法被称作是有”副作用的”

二.如何减少方法的副作用

     1.分离修改与查询。

     2.将查询分离到一个value object

     3 .demeter定律,也叫得墨忒耳定律,迪米特法则。

一,什么是副作用

     为了方便下面的讨论,有必要对副作用做一个定义。

    如果一个函数修改了对象的状态,无论这个修改操作是有意还是无意的,都被称作有副作用。

    副作用意味着会出现预料之外的结果,这在调试和维护阶段将非常困难。我很懒,在将来调试和维护阶段,我想悠闲的喝着茶水,完成经理交付给我的任务,然后听着舒心的音乐,度过一天的时光。

    为了减少方法的副作用,很多大师提出了一些优秀的设计原则,其中比较有价值的建议有meyer提出的“任何有返回值的函数,都不应该有看的到的副作用”,后来martin fowler 将其描述为“将修改于查询分离”。后来,Eric Evans提出了一个“Side Effect Free Function”概念。

    和任何一个优秀的原则一样,我并不总是刻意的遵循他们,只有再他们能给我带来好处时,我才会去遵循他们。

接下来你需要了解的是,什么是查询函数与修改函数。

二,查询函数的好处

    如果一个函数没有修改对象状态,仅仅只是返回一些数据,这样的方法被称为查询函数。譬如List<T>的Find函数,该函数只是在集合中查找并返回符合条件的值;再如一个Customer类中有一个GetCustomerID()函数,该函数只是简单的返回customer的ID值。像这样的函数都是查询函数。

    查询函数的好处是你可以任意的调用它,而不用担心它会带来什么副作用,因为他没有修改对象的状态。同时查询函数的好处还在于它的结果是可以预测的,我们通过函数名就可以预测他的行为,而不用去了解的实现。

    同样,你也不用担心查询函数的嵌套问题,一个查询函数可以调用另一个查询函数,你可以形成任意的嵌套深度,而不用担心这种深层的嵌套带来什么副作用。

    查询函数的结果只有两种,要么找到符合结果的值,要么没有找到。他很少会抛出一个异常,遗憾的是很多类库中都抛出了异常。而一个修改函数就不同了,修改函数可能修改成功,也可能修改失败,并且失败的原因常常不只一种。如果想知道哪一种失败原因,你通常不得不去阅读该函数的源代码,在你无法获得源代码的情况下,你就必须去阅读相关的说明和文档。

    正是由于查询函数的结果简单且可预测,因此他具有极好的可测试性。

    一旦你体会到了查询函数带来的种种好处,你就会尝试在你的代码中写更多的查询函数,呵呵,我就是这样。

 

三 ,什么是修改函数

    如果一个函数修改了对象的状态,并且没有返回领域数据,这样的函数被称作修改函数。譬如List<T>的Add(T)函数,该函数值是像集合中添加一个元素,并没有返回任何值。

    请注意我并没有说,修改函数不能有返回值。我只说了修改函数不应该返回“领域数据”。

    举个例子,请你思考一下C#类库中的List<T>的Remove<T>方法是否满足修改函数的条件。该函数的原型如下:

Public bool Remove<T>(T value);

该函数的意图很简单,删除成功就返回true,失败返回false。

怎样,你认为这个函数是否是一个修改函数?答案我就不说了,请你自己思考。

    由于修改函数可以修改对象的状态,所以他常常在我们不知道的情况下,将对象的状态修改成非法状态,这是非常糟糕的。

    修改函数的结果只有两种,一种是修改成功,一种是失败,在失败的情况下可能会抛出一个异常,因此你会在调用的写入try catch语句来捕获异常,毫无疑问,这时代码变得越来越复杂。

    并且,函数失败的原因通常有多种,为了知道是哪一种原因导致函数操作失败,你必须去阅读该函数的源代码,以了解他的实现。在无法获得源代码的情况下,你只能去查看该函数的说明及文档,看看他们都会在什么情况下,抛出什么异常。

    由于函数的失败原因有多种,所以在将来调试和维护的过程中将会带来非常大的麻烦——你不知道是什么原因导致函数的失败。

    在一个深度嵌套的函数中,一个修改函数可能会调用另一个修改函数,这种嵌套可能会持续很久。这样的函数调试和维护更加困难。

    毫无疑问,在系统中使用修改函数的使用是不可避免的,即使他有那么多的缺点。但是如果采取一些好的作法,依然可以使修改函数的副作用降至最低。譬如你可以将修改函数和查询函数放置不同的操作中,一个函数要么是修改函数,要么是查询函数,并且通过将查询函数分离到一个value object对象中,达到更好的效果。你也可以通过适当的遵循demeter定律,来减少方法的副作用。这就是接下来要讨论的内容。

四.分离修改与查询

     如果一个函数既是修改函数,又是查询函数,既函数在修改对象状态的同时,又返回了领域数据。这样的方法你应当进行“将修改与查询分离”重构,通过把修改函数与查询函数放置两个不同的操作中,可以实现修改与查询的分离。

具体的重构手法可以参阅MartinFowler的《重构》一书。

 

五,将查询函数分离到一个ValueObject中

    如果采取了上面的建议,把修改与查询放在不同的函数去,那么你就进步一大截了。如果你还知道把查询函数放在一个Value Object中,你的设计就会更进一步。这就是接下来要介绍的内容,最后会给出一段代码示例。

    只有在查询函数非常复杂的情况下,,或者提取出一个value object概念对于我们的设计有好处时,我才建议你这么做。在绝大多数的时候你都不需要。

    将复杂的查询函数提炼到一个Valueobject中,是有很多好处的。

先来看一下代码:

  

 public classOrderAddress
    {
        stringprovince;
        public string Province
        {
            get{ return province; }
        }
        stringcity;
        public string City
        {
            get{ return city; }
        }
 
        stringarea;
        public string Area
        {
            get{ return area; }
        }
 
        stringtown;
        public string Town
        {
            get{ return town; }
        }
 
        stringzipCode;
        public string ZipCode
        {
            get{ return zipCode; }
        }
 
        stringcontacterName;
        public string ContacterName
        {
            get{ return contacterName; }
            set{ contacterName = value; }
        }
 
        stringcontacterNumber;
        public string ContacterNumber
        {
            get{ return contacterNumber; }
            set{ contacterNumber = value; }
        }
 
        publicOrderAddress(string province,string city,string area,string town,stringzipCode,string contacterName,string contacterNumber)
        {
            this.province= province;
            this.city= city;
            this.area= area;
            this.town= town;
            this.zipCode= zipCode;
            this.contacterName= contacterName;
            this.contacterNumber= contacterNumber;
        }
}
 

OrderAddress类有如下的属性,可以访问和修改收货人姓名和电话:

string contacterName;
        public string ContacterName
        {
            get{ return contacterName; }
            set{ contacterName = value; }
        }
 
        stringcontacterNumber;
        public string ContacterNumber
        {
            get{ return contacterNumber; }
            set{ contacterNumber = value; }
        }


你可以一眼就看出get访问器就是我们所说的查询方法,因为他仅仅是返回一个值,没有修改对象的任何状态。

      一旦产生了一个订单,用户只能修改联系人姓名和电话,而省,市等其他地址信息是不能修改的。同时,联系人姓名和电话一般都是同时变化的,修改联系人姓名可能也同时修改联系人电话。

       这里我们将查询方法提炼到一个Value Object 对象中,如下:

    

 public structConsignee
    {
        stringcontacterName;
        public string ContacterName
        {
            get{ return contacterName; }
        }
        stringcontacterNumber;
        public string ContacterNumber
        {
            get{ return contacterNumber; }
        }
       publicConsignee(string contacterName,string contacterNumber)
       {
           this.contacterName= contacterName;
           this.contacterNumber= contacterNumber;
       }
}

这就实现了我么所说的将查询分离到Value Object对象中。

 

四,demeter定律

    网上有很多关于Demeter定律的博客,其中很多都是泛泛而谈,甚至有些博客还有错误。我并不想在这篇博客里介绍demeter定律。如果要详细的介绍他,需要一个单独写一个文章。

    我只是想说,如果你懂得demeter定律,那么适当的采用它,你可以避免方法的副作用。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值