• 重构手法之简化函数调用【6】


    返回总目录

    本小节目录

    13Replace Error Code with Exception(以异常取代错误码)

    概要

    某个函数返回一个特定的代码,用以表示某种特定的情况。改用异常。

    动机

    异常能清楚地将“普通程序”和“错误处理”分开了,这使得程序更容易理解。

    代码的可理解性应该是我们虔诚追求的目标。

    范例

    class Account
    {
        /// <summary>
        /// 余额
        /// </summary>
        private int _balance;
        /// <summary>
        /// 取款
        /// </summary>
        /// <param name="amount">取款金额</param>
        /// <returns></returns>
        public int Withdraw(int amount)
        {
            if (amount > _balance)
            {
                return -1;
            }
            _balance -= amount;
            return 0;
        }
        public bool CanWithdraw(int amount)
        {
            return amount <= _balance;
        }
    
        public void HandOverdran()
        {
    
        }
    
        public void DoTheUsualThing()
        {
    
        }
    }

    为了让这段代码使用异常,首先决定使用受控异常还是非受控异常。关键在于:调用者是否有责任在取款之前检查存款余额,还是应该由Withdraw()函数负责检查。如果“检查余额”是调用者的责任,那么“取款金额大于存款金额”就是一个编程错误,应该使用非受控异常。如果“检查余额”是Withdraw()函数的责任,就必须在函数中抛出这个异常。

    范例:非受控异常

    使用非受控异常就表示应该由调用者负责检查。首先需要检查调用端的代码,它不应该使用Withdraw()函数的返回值,因为返回值只是用来指出程序员的错误。如果看到这样的代码:

    Account account = new Account();
    if (account.Withdraw(100) == -1)
    {
        account.HandOverdran();
    }
    else
    {
        account.DoTheUsualThing();
    }

    应该将它替换成这样的代码:

    Account account = new Account();
    if (!account.CanWithdraw(100))
    {
        account.HandOverdran();
    }
    else
    {
        account.Withdraw(100);
        account.DoTheUsualThing();
    }

    现在移除错误码,并在程序出错时抛出异常。由于这种行为是异常的、罕见的,所以使用卫语句检查这种情况:

    public void Withdraw(int amount)
    {
        if (amount > _balance)
        {
            throw new ArgumentException("Amount too large.");
        }
        _balance -= amount;
    }

    范例:使用受控异常

    受控异常的处理方式略有不同。首先可以新建一个合适的异常:

    class  BalanceException:Exception
    {
        
    }

    当然了,这里不新建也是可以的。

    然后调整调用端如下:

    Account account = new Account();    
    try
    {
        account.Withdraw(100);
        account.DoTheUsualThing();
    }
    catch (BalanceException ex)
    {
        account.HandOverdran();
    }

    接下来修改Withdraw()函数,让它以异常表示错误状况:

    public void Withdraw(int amount)
    {
        if (amount > _balance)
        {
            throw new BalanceException();
        }
        _balance -= amount;
    }

    小结

    将错误码替换成异常之后,使得代码更容易理解。

    15Replace Exception with Test(以测试取代异常)

     概要

    面对一个调用者可以预先检查的条件,你抛出了一个异常。

    修改调用者,使它在调用函数之前先做检查。

    动机

    异常可以协助我们避免很多复杂的错误处理逻辑。但是,异常也会被滥用。“异常”只应该被用于异常的、罕见的行为,也就是那些产生意料之外的错误的行为,而不应该成为条件检查的替代者。

    范例

    下面的例子中,以一个ResourcePool对象管理一些创建代价高昂而又可以重复使用的资源。这个对象带有两个“池”:一个用以保存可用资源,一个用以保存已分配资源。当用户请求一份资源时,ResourcePool对象从“可用资源池”中取出一份资源交出,并将这份资源转移到“已分配资源池”。当用户释放一份资源时,ResourcePool对象就该将资源从“已分配资源池”放回“可用资源池”。如果“可用资源池”不能满足用户的请求,ResourcePool对象就创建一份新资源。

    class ResourcePool
    {
        private Stack<Resource> _available;
    
        private Stack<Resource> _allocated;
    
        public Resource GetResource()
        {
            Resource result;
            try
            {
                result = _available.Pop();
                _allocated.Push(result);
                return result;
            }
            catch (Exception ex)
            {
                result = new Resource();
                _allocated.Push(result);
                return result;
            }
        }
    }
    
    class Resource
    {
    
    }

    在这里,“可用资源用尽”并不是一件意料外的事件,因此不该使用异常表示这种情况。

    为了去掉这里的异常,首先添加一个适当的提前测试,并在其中处理“可用资源为空”的情况:

    class ResourcePool
    {
        private Stack<Resource> _available;
    
        private Stack<Resource> _allocated;
    
        public Resource GetResource()
        {
            Resource result;
            if (_available.Count == 0)
            {
                result = new Resource();
                _allocated.Push(result);
                return result;
            }
            result = _available.Pop();
            _allocated.Push(result);
            return result;
        }
    }
    
    class Resource
    {
    
    }

    在这里,可以对条件代码加以整理,使用Consolidate Duplicate Conditional Fragments

    class ResourcePool
    {
        private Stack<Resource> _available;
    
        private Stack<Resource> _allocated;
    
        public Resource GetResource()
        {
            Resource result;
            if (_available.Count == 0)
            {
                result = new Resource();
            }
            else
            {
                result = _available.Pop();
            }     
            _allocated.Push(result);
            return result;
        }
    }
    
    class Resource
    {
    
    }

    小结

     阶段性小结

    在对象技术中,最红要的概念莫过于“接口”。容易被理解和被使用的接口,是开发良好面向对象软件的关键。

    最简单也最重要的一件事就是修改函数名称。名称是程序写作者与阅读者交流的关键工具。只要能理解一段程序的功能,就应该大胆地使用Rename Method将所知道的东西传达给他人。

    函数参数在接口中扮演十分重要的角色。Add ParameterRemove Parameter都是很常见的重构手法。刚接触面向对象技术的程序员往往使用很长的参数列。但是,使用对象技术,可以保持参数列的简短。如果来自同一个对象的多个值被当做参数传递,可以运用Preserve Whole Object将它们替换为单一对象,从而缩减参数列。如果此前并不存在这样一个对象,可以运用Introduce Parameter Object将它创建出来。如果函数参数来自该函数可获取的一个对象,则可以使用Replace Parameter with Methods避免传递参数。如果某些参数被用来在条件表达式中做选择依据,可以实施Replace Parameter with Explicit Method。另外,还可以使用Parameterize Method为数个相似函数添加参数,将它们合并到一起。

    明确地将“修改对象状态”的函数和“查询对象状态”的函数分开设计是一个很好的习惯。如果看到这两种函数混在一起,可以使用Separate Query from Modifier将它们分开。

    良好的接口只向用户展现必须展现的东西。如果一个接口暴露了过多细节,可以将不必要暴露的东西隐藏起来,从而改进接口的质量。进行重构时,往往需要暂时暴露某些东西,最后再以Hide MethodRemove Setting Method将它们隐藏起来。

    构造函数往往比较“麻烦”,因为它强迫你必须知道要创建的对象属于哪个类,而你往往并不需要知道这一点。可以使用Replace Constructor with Factory Method避免了这不必要的信息。

    和许多现代编程语言一样,C#也有异常处理机制,这使得错误处理相对容易一些。不习惯使用异常的程序员,往往会以错误码表示程序遇到麻烦。可以使用Replace Error Code with Exception来运用新的异常特性。但有时候异常也并不是最合适的选择,应该实施Replace Exception with Test先测试一番。

    To Be Continued……

  • 相关阅读:
    阶段1 语言基础+高级_1-2 -面向对象和封装_2面向对象思想的举例
    阶段1 语言基础+高级_1-2 -面向对象和封装_1面向对象思想的概述
    2-3 【初识组件】顶部 TabBar
    2-2 工程源码文件结构
    Fragment状态保存
    【51单片机】六种亮灯方式
    Hadoop自学笔记(二)HDFS简单介绍
    lvs 负载均衡环境搭建
    [学习笔记—Objective-C]《Objective-C-基础教程 第2版》第十一章 属性
    说说nio----1
  • 原文地址:https://www.cnblogs.com/liuyoung/p/7955124.html
Copyright © 2020-2023  润新知