@suyuening
2017-01-22T06:58:56.000000Z
字数 4826
阅读 1720
重构
所谓重构是这样一个过程:「在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构」。重构是一种有纪律的、经过训练的、有条不紊的程序整理方法,可以将整理过程中不小心引入错误的机率降到最低。本质上说,重构就是「在代码写好之后改进它的设计」。
重构(名词):对软件内部结构的一种调整,目的是在不改变「软件可观察行为」前提下,提高其可理解性,降低其修改成本。
重构(动词):使用一系列重构准则(手法〕,在不改变「软件可观察行为」前提 下,调整其结构。
所以,在软件开发过程中,你可能会花上数小时的时间进行重构,其间可能用上数十个不同的重构准则。
Don Roberts给了我一条准则:第一次做某件事时只管去做;第二次做类似的事会产生反感,但无论如何还是做了;第三次再做类似的事,你就应该重构。
事不过三,三则重构(Three strikes and you refactor)
最常见的重构时机就是我想给软件添加新特性的时候。此时,重构的第一个原因往往是为了帮助我理解需要修改的代码。这些代码可能是别人写的,也可能是我自己写的。无论何时只要我想理解代码所做的事,我就会问自己:是否能对这段代码进行重构,使我能更快理解它。 然后我就会重构。
臭味行列中首当其冲的就是Duplicated Code。如果你在一个以上的地点看到相同的程序结构,那么当可肯定:设法将它们合而为一,程序会变得更好。
拥有[短函数」(short methods)的对象会活得比较好、比较长。不熟悉面向对象技术的人,常常觉得对象程序中只有无穷无尽的delegation(委托),根本没有进行任何计算。和此类程序共同生活数年之后,你才会知道,这些小小函数有多大价值。 的匹配项
如果想利用单一class做太多事情,其内往往就会出现太多instance变量。一旦如此,Duplicated Code也就接踵而至了。
刚开始学习编程的时候,老师教我们:把函数所需的所有东西都以参数传递进去。这可以理解,因为除此之外就只能选择全局数据,而全局数据是邪恶的东西。对象 技术改变了这一情况,因为如果你手上没有你所需要的东西,总可以叫另一个对象给你。因此,有了对象,你就不必把函数需要的所有东西都以参数传递给它了,你只需传给它足够的东西、让函数能从中获得自己需要的所有东西就行了。函数需要的东西多半可以在函数的宿主类(host class)中找到。面向对象程序中的函数,其参数列通常比在传统程序中短得多。
我们希望软件能够更容易被修改——毕竟软件再怎么说本来就该是「软」的。一旦需要修改,我们希望能够跳到系统的某一点,只在该处做修改。如果不能做到这点,你就嗅出两种紧密相关的刺鼻味道中的一种了。
如果某个class经常因为不同的原因在不同的方向上发生变化,Divergent Change就出现了。当你看着一个class说:『呃,如果新加入一个数据库,我必须修改这三个函数;如果新出现一种金融工具,我必须修改这四个函数』,那么此时也许将这个对象分成两个会更好,这么一来每个对象就可以只因一种变化而需要修改。当然,往往只有在加入新数据库或新金融工具后,你才能发现这一点。针对某一外界 变化的所有相应修改,都只应该发生在单一class中,而这个新class内的所有内容都应该反应该外界变化。为此,你应该找出因着某特定原因而造成的所有变化,然后运用Extract Class 将它们提炼到另一个class中。
别担心,我们并不是说你不该写注释。从嗅觉上说,Comments不是一种坏味道;事实上它们还是一种香味呢。我们之所以要在这里提到Comments,因为人们常把它当作除臭剂来使用。常常会有这样的情况:你看到一段代码有着长长的注释,然后发现,这些注释之所以存在乃是因为代码很糟糕。这种情况的发生次数之多,实 在令人吃惊。
这里只是列举了代码坏味道的几个方面,更多的内容需要阅读《重构改善既有代码的设计》一书。
确保所有测试都完全自动化,让它们检查自己的测试结果。
一整组(a suite of)测试就是一个强大的「臭虫」侦测器,能够大大缩减查找「臭虫」所耑要的时间。
我用的是JUnit,一个由Erich Gamma 和 Kent Beck [JUnit]开发的开放源码测试框架。这个框架非常简单,却可让你进行测试所需的所有重要事情。本章中我将运用这个测试框架来为一些IO classes幵发测试代码。
你有一段代码可以被组织在一起并独立出来。
将这段代码放进一个独立函数中,并让函数名称解释该函数的用途。
void printOwing(double amount) {
printBanner();
//print details
System.out.println ("name:" + _name);
System.out.println ("amount" + amount);
}
=>
void printOwing(double amount) {
printBanner();
printDetails(amount);
}
void printDetails (double amount) {
System.out.println ("name:" + _name);
System.out.println ("amount" + amount);
}
没有特别说明示例的操作步骤在Eclipse中完成
Eclipse快捷键 : ALT+SHIFT+M
一个函数,其本体(method body)应该与其名称(method name)同样清楚易懂。
在函数调用点插入函数本体,然后移除该函数。
int getRating() {
return (moreThanFiveLateDeliveries()) ? 2 : 1;
}
boolean moreThanFiveLateDeliveries() {
return _numberOfLateDeliveries > 5;
}
=>
int getRating() {
return (_numberOfLateDeliveries > 5) ? 2 : 1;
}
Eclipse快捷键 : ALT+SHIFT+I
你有一个临时变量,只被一个简单表达式赋值一次,而它妨碍了其他重构手法。
将所有对该变量的引用动作,替换为对它赋值的那个表达式本身。
double basePrice = anOrder.basePrice();
return (basePrice > 1000)
=>
return (anOrder.basePrice() > 1000)
你的程序以一个临时变量(temp)保存某一表达式的运算结果。
将这个表达式提炼到一个独立函数(译注:所谓查询式,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;
}
你有一个复杂的表达式。
将该表达式(或其中一部分)的结果放进一个临时变量,以此变量名称来解释表达式用途。
if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
(browser.toUpperCase().indexOf("IE") > -1) &&
wasInitialized() && resize > 0 )
{
// do something
}
=>
final boolean isMacOs = platform.toUpperCase().indexOf("MAC") > -1;
final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") > -1;
final boolean wasResized = resize > 0;
if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
// do something
}
ALT+SHIFT+L,此快捷键用来抽取本地变量,相当于执行菜单Refactor->Extract Local Variable