一,重构是什么
重构(Refactoring)就是通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。但是这个解释缺少了一个前提,完整的说法应该是:在不改变软件可观察行为的前提下,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。
最近和一个朋友聊天,他说最近在读关于重构的书籍。但是随着阅历的提高,对这块知识比较感兴趣。
二,为什么要重构
关于为什么要重构,查阅相关资料或翻阅相关书籍大多都会提到以下几句话:
1、持续偏纠和改进软件设计 。2、使代码更被其他人所理解。 3、帮助发现隐藏的代码缺陷 。4、从长远来看,有助于提高编程效率,增加项目进度。
三,怎么重构
我们已经对重构的概念有了一定的了解,那么我们通过什么方法进行重构呢?
- 提炼函数
具体做法:①拆分过长的函数。②提炼在多个函数中使用的相同代码
这样做的好处是:①函数被复用的机会大。②如果所提炼的函数命名足够好,那么高层函数的功能更加一目了然
比如以下函数(代码是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()这个函数甚至连注释都不用加大家就都能看懂,而且如果其他地方需要打印分割线或者使用这个算法时候,这两个函数都可以复用了。
-
以查询取代临时变量
比如上一段代码定义了一个为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,暂时先给大家分享到这里。欢迎批评指正。