简单的理解<重构>

一,重构是什么

重构(Refactoring)就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。但是这个解释缺少了一个前提,完整的说法应该是:在不改变软件可观察行为的前提下,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。
最近和一个朋友聊天,他说最近在读关于重构的书籍。但是随着阅历的提高,对这块知识比较感兴趣。

二,为什么要重构

关于为什么要重构,查阅相关资料或翻阅相关书籍大多都会提到以下几句话:

1、持续偏纠和改进软件设计 。2、使代码更被其他人所理解。 3、帮助发现隐藏的代码缺陷 。4、从长远来看,有助于提高编程效率,增加项目进度。

三,怎么重构

我们已经对重构的概念有了一定的了解,那么我们通过什么方法进行重构呢?

  1. 提炼函数
    具体做法:①拆分过长的函数。②提炼在多个函数中使用的相同代码
    这样做的好处是:①函数被复用的机会大。②如果所提炼的函数命名足够好,那么高层函数的功能更加一目了然
    比如以下函数(代码是C/C++,但是 C#, Java的同学都可以看得懂):

int nArrays[5] = {1,2,3,2,1};
void PrintResult()
{
int nPrintNum ;

// Print split line;
printf("****************************");
printf("********             *******");
printf("****************************");

// Algorithm;
for (int i = 0;i<5;i++)
{
    nPrintNum += nArrays[i] * 5;
}
// Result:
printf("Result: %d" , nPrintNum);     

}
其实这个函数已经很简单了,我这里举一个简单的例子简单说明一下(代码纯手敲,并没有做过运行)

    首先这个函数名是叫"PrintResult",个人感觉里面最好不要包含算法,所以将算法应该提取出来,当然上面Pring splist line也可以提取出来:

void PrintSplitLine()
{
printf("*******");
printf("******** ");
printf("
");
}

int CalcResult()
{
int nPrintNum = 0;
for (int i = 0;i<5;i++)
{
nPrintNum += nArrays[i] * 5;
}
return nPrintNum;
}

void PrintResult()
{
PrintSplitLine();
int Result = CalcResult();
printf(“Result: %d” , Result );
}
这样的话PrintResult()这个函数甚至连注释都不用加大家就都能看懂,而且如果其他地方需要打印分割线或者使用这个算法时候,这两个函数都可以复用了。

  1. 以查询取代临时变量

    比如上一段代码定义了一个为Result申请了一个临时变量,当然也无可厚非,提倡的做法是直接查询就可以了:
    
void PrintResult()
{
    PrintSplitLine();
    printf("Result: %d" , CalcResult());  
}
3.引入解释性变量
double GetPrice()
{
	//price is base price - quantity discount + shipping
	return m_nQuantity * m_nItemPrice - max(0,m_nQuantity-500) * n_ItemPrice * 0.05 + min(m_nQuantity * m_ItemPrice * 01, 100.0 );
}
    这个表达式太复杂,我们应该将表达式拆分,拆分的结果应该放到临时变量里,以变量名称来解释表达式的用途:
double GetPrice()
{
	double dBasePrice = m_nQuantity * m_nItemPrice;
	double dQuantityDiscount = max(0, m_nQuantity - 500) *  m_nItemPrice * 0.05;
	double dShipping = min( dBasePrice * 0.1, 100.0);
	return dBasePrice - dQuantityDiscount + dShipping;
}
4,分解临时变量

    临时变量通常用于保存临时运算结果,这种临时变量应该只被赋值一次。如果它被赋值超过一次意味着它承担了一个以上的责任,当然这也无可厚非,但是这使得其他人会产生疑惑,这个值到底是干嘛用的?

    比如下面这个函数,本来就已经够复杂的了,但是大家会发现dAcc赋值了两次,而这两次是属于不同的加速度,都用这个变量表示容易让人产生误解。
double GetDistanceTravelled(int nTime)
{
	double nResult = 0;
	// 加速度
	double dAcc = m_dPrimaryForce / m_dMass;
	int nPrimaryTime = min(nTime, m_dDelay);
	nResult = 0.5 * dAcc * nPrimaryTime * nPrimaryTime;
	int nSecondaryTime = nTime - m_dDelay;
	if (nSecondaryTime > 0)
	{
		double nPrimaryVel = dAcc * m_dDelay;
		dAcc = (m_dPrimaryForce + m_dSecondaryForce) / m_dMass;
		nResult += nPrimaryVel * nSecondaryTime + 0.5 * dAcc * nSecondaryTime * nSecondaryTime;
	}
	return nResult;
}
      不同含义的变量应该用两个不同的临时变量:

double GetDistanceTravelled(int nTime)
{
	double nResult = 0;
	// 加速度
	const double dPrimaryAcc = m_dPrimaryForce / m_dMass;
	int nPrimaryTime = min(nTime, m_dDelay);
	nResult = 0.5 * dAcc * nPrimaryTime * nPrimaryTime;
	int nSecondaryTime = nTime - m_dDelay;
	if (nSecondaryTime > 0)
	{
		double nPrimaryVel = dPrimaryAcc * m_dDelay;
		const double dSecondaryAcc = (m_dPrimaryForce + m_dSecondaryForce) / m_dMass;
		nResult += nPrimaryVel * nSecondaryTime + 0.5 * dSecondaryAcc * nSecondaryTime * nSecondaryTime;
	}
	return nResult;
}
5,移除对参数的赋值

    首先观察下面的函数的某个片段:
void Method( Object obj) 
{
	int nResult = obj.GetValue();       // 1
	Object anotherObject = new Object();// 2
	obj = anotherObject;                // 3
	}

  解释一下,首先obj是一个参数,在函数内改变它的值是一个不好的习惯,其次obj是当做一个对象传进来的,也没办法改变它的值。如果在Method函数里面只是想获取某些obj里面的值,下面这种写法是提倡的:
void Method( const Object& obj) 
{
	int nResult = obj.GetValue(); 
	Object anotherObject = new Object();
	// ...
} 
    首先我不改变传入的值所以加了个const,其次传引用只需要传递四个字节。这里需要注意的是Object类中的GetValue()应该也加上const:GetValue() const{ ... },因为const对象不能引用非const成员函数。另外函数加上了const,在函数内就不能改变成员变量了。
  再举个简单的例子:
int Discount (int nInputVal, int nQuantity, int nYearToData) 
{
	if (nInputVal > 50) nInputVal -= 2;
	if (nQuantity > 100) nInputVal -= 1;
	if (nYearToData > 10000) nInputVal -= 4;
	return nInputVal;	
}
    这种直接这种传入值,转了一圈又当做返回值出去了。。。不要修改传入值,否则容易让人混淆。提倡做法:
int Discount (int nInputVal, int nQuantity, int nYearToData) 
{
	int nResult = nInputVal;
	if (nInputVal > 50) nResult -= 2;
	if (nQuantity > 100) nResult -= 1;
	if (nYearToData > 10000) nResult -= 4;
	return nResult;	
}
6.以函数对象取代函数

    如果有一个大型函数,局部变量过多,可以将这个函数放到一个单独的对象中,如此一来,局部变量就变成了对象内的字段。然后可以在同一个对象中将这个大型的函数分解成多个小函数。另外,局部变量过多时,首先考虑“以查询取代临时变量”,有时候你会发现根本无法拆解一个需要拆解的函数,在这种情况下考虑使用函数对象。
class Order
{
	// ...
		double Price()
		{
			double primaryBasePrice;
			double secondaryBasePrice;
			double tertiaryBasePrice;
			//long computation;
			// ...
		}
}

7,替换算法

把某个算法替换为更清晰的算法:
下面这段代码是Java的代码,懂得C++或C#的童鞋应该能直接看懂。两段代码要实现的功能一致,至于你认为哪一种好用,就用哪一种呗(个人比较推崇第二段实现方式)。

String foundPerson( String[] people ){
    for ( int i=0; i < people.length; i++ )  {	
        if ( people[i].equals("Don") ) {
            return "Don";
        }		
        if ( people[i].equals("John") ) {
            return "John";
        }
        if ( people[i].equals("Kent") ) {
             return "Kent";
        }
    }
    return "";
}
String foundPerson( String[] people ){
    List candidates = Arrays.asList(new String[]{"Don","John","Kent"});
    for ( int i=0; i < people.length; i++ )	{
        if ( candidates.contains(people[i) ) {
            return people[i];
        }		
    }
    return "";
}

8.内联函数

  在C++中,以inline修饰的函数叫做内联函数,编译时C++编译器会调用内联函数的地方展开,没有函数压栈开销,内联函数提升程序运行的效率。

OK,暂时先给大家分享到这里。欢迎批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值