• 重构手法之简化函数调用【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……

  • 相关阅读:
    PAT B1027 打印沙漏 (20 分)
    PAT B1025 反转链表 (25 分)
    PAT B1022 D进制的A+B (20 分)
    PAT B1018 锤子剪刀布 (20 分)
    PAT B1017 A除以B (20 分)
    PAT B1015 德才论 (25 分)
    PAT B1013 数素数 (20 分)
    PAT B1010 一元多项式求导 (25 分)
    HDU 1405 The Last Practice
    HDU 1165 Eddy's research II
  • 原文地址:https://www.cnblogs.com/liuyoung/p/7955124.html
Copyright © 2020-2023  润新知