几种常见的代码重构方法

来源:互联网 发布:淘宝客数据采集 编辑:程序博客网 时间:2024/05/22 08:20

Martin Fowler的著作《Refactoring: Improving the Design of Existing Code

1.Extract Method(提取函数)

样例代码:

public void PrintOwing(double amount)
{
    PrintBanner();
    // print details
    Console.WriteLine("name:" + _name);
    Console.WriteLine("amount:" + _amount);
}

重构为:

public void PrintOwing(double amount)
{
    PrintBanner();
    PrintDetails(amount);
}

public void PrintDetails(double amount)
{
    // print details
    Console.WriteLine("name:" + _name);
    Console.WriteLine("amount:" + _amount);
}

思想:让每个函数只做一件事,抽取能够被组织在一起的代码,并单独抽取为一个函数。如果函数的粒度小,被复用的机会就更大。

我常看到初学ASP.NET的同学喜欢把所有的代码全部写在Page_Load事件中。我们暂且不论分层架构,光看页面本身,如果所有的代码都放在一个函数中,那么可重用性就几乎为0

打个比方,你的页面上有个GridView,你希望打开网页的时候给它绑定数据,于是你讲这段代码放在了Page_Load中,当 然,Page_Load还会做除此以外的很多处理。然而,在你相应页面的其他事件的时候(比如点击Button、处理用户输入等),仍然需要重新绑定 GridView的数据。这时候你就非常希望不要重写一遍数据绑定的逻辑(如果重写一个逻辑很多次,那以后要改的话就得改很多处)。于是,我们就应该把数 据绑定逻辑提取到一个单独的函数中。比如叫BindData(),然后分别在Page_Load事件,和其他你需要再次绑定数据的事件中调用 BindData()

PS: 有时候需要重新绑定数据,单独写GridView1.DataBind();是不行的。

Visual Studio中做这样的重构很简单:选中你要提取的代码片段,然后点右键->重构->提取方法,键入一个新方法名称即可。VS会自动处理该片段与已有函数的调用关系。

关于函数的命名,我建议大家用动宾短语,阅读的时候会感觉比较自然。

2.Inline Temp(内联临时变量)

样例代码:

double basePrice = anOrder.BasePrice();
return (basePrice > 1000);

重构为:

return (anOrder.BasePrice() > 1000);

注意:重构前要保证这个临时变量只被赋值一次,所以当遇到for等循环语句时,要注意被内联的变量的值是否会在循环中被改变。有时在执行for之前定义固定的临时变量是必要的。尤其是当临时变量取自某对象的属性,而这个对象将在for循环中被更改。

3.Replace Temp with Query(以查询取代临时变量)

样例代码:

double basePrice = _quantity * _itemPrice;
if(basePrice > 1000)
{
    return basePrice * 0.95;
}
else
{
    return basePrice * 0.98;
}

重构为:

if(BasePrice() > 1000)
{
    return BasePrice() * 0.95;
}
else
{
    return BasePrice() * 0.98;
}
...
double BasePrice()
{
    return _quantity * _itemPrice;
}

其实对于这个重构,我还是有些疑惑的。我们发现,BasePrice()会被计算多次,但书上说不用担心性能问题,不过我对此还是保持怀疑。毕竟有 些计算开销是很大的,并且执行多次可能产生额外的影响,所以我建议大家使用Replace Temp with Query手法的时候要谨慎,如果给一个临时变量复制的操作开销很大,尤其是需要和数据库交互,我不建议采用该手法重构。

4.Replace Nested Conditional with Guard Clauses(以卫语句嵌套条件表达式)

样例代码:

double GetPayAmount()
{
    double result;
    if(_isDead) result = deadAmount();
    else
    {
        if(_isSeparated)
        {
            result = separatedAmount();
        }
        else
        {
            if(_isRetired)
            {
                result = retiredAmount();
            }
            else
            {
                result = normalPayAmount();
            }
        }
    }
    return result;
}

重构为

double GetPayAmount()
{
    if(_isDead)
    {
        return deadAmount();
    }
    if(_isSeparated)
    {
        return separatedAmount();
    }
    if(_isRetired)
    {
        return retiredAmount();
    }
    return normalPayAmount();
}

这个重构手法也是我用的很多的,在初学编程的时候,很多人都会写出很多ifelse嵌套的代码,其实很多时候,else并不是必须的。关于ifelse如何取决,关键看你对各分支的重视程度。

摘录书上的原话:

根据我的经验,条件表达式通常有两种表现形式。第一种形式是:所有分支都属于正常行为。第二种形式则是:条件表达式提供的答案中只有一种是正常行 为,其他都不是常见的情况。这两类表达式有不同的用途,这一点应该通过代码表现出来。如果两条分支都是正常行为,就应该用形如ifelse的条件表达 式;如果某个条件及其罕见,就应该单独检查该条件,并在该条件为真时立刻从函数中返回。这样的单独检查常常被称为卫语句

插一句题外话,我平时写程序,遇到不正常情况需要提示,我通常会在ifthrow一个exception,这些if是并行的,不是嵌套的,最后在catch里捕获消息并弹窗提示,我不知道这种做法是否好,求高手指点: )

5.Introduce Explaining Variable(引入解释性变量)

样例代码:

if((platform.ToUpper().IndexOf("MAC") > -1) && (browser.ToUpper().IndexOf("IE") > -1) && wasInitialized() && resize > 0)
{
    // do something
}

重构为:

bool isMacOs = platform.ToUpper().IndexOf("MAC") > -1;
bool isIE = browser.ToUpper().IndexOf("IE") > -1;
bool wasResized = resize > 0;

if(isMacOs && isIE && wasInitialized() && wasResized)
{
    // do something
}

这种重构手法的目的显而易见,不多叙述了。

6. 我自己常用的代码重构片段

1. 关于ifreturn

// 原始片段
int row = DbHelperSql.ExecuteSql(sql);
if(row) > 1
{
    return true;
}
else
{
    return false;
}

// 这样写可以省略一对括号
int row = DbHelperSql.ExecuteSql(sql);
if(row) > 1
{
    return true;
}
return false;

// 逻辑是这样,直接return一个表达式
int row = DbHelperSql.ExecuteSql(sql);
return (row > 1);

// 应用"Replace Temp with Query"手法
return DbHelperSql.ExecuteSql(sql) > 1;

2. 三目表达式

// 如果碰到非bool类型的返回值可以这样
return Function() == 1 ? "Hehe" : "haha";

Replace Temp with Query中的例子也可以写成三目表达式:

return BasePrice() > 1000 ? BasePrice() * 0.95 : BasePrice() * 0.98;

原创粉丝点击