说了那么多理论,我们来看看怎样使用抽取方法来重构遗留系统。如前所述,重构的过程首先是阅读程序代码,边阅读边整理程序。将功能相对独立的代码段放在一起,在前面加上注释。调整一些程序的顺序,将相关的代码尽量放在一起,但要保证程序执行的结果不会发生改变。比较典型的,将变量的定义与使用变量的代码放在一起。这个步骤比较实用,因为许多的遗留系统,其代码都有一个坏毛病,就是在程序开始时定义一大堆变量,但要弄清这些变量都用来做什么,却十分困难。边读边调整,将变量的定义逐渐迁移到使用它的代码段中,将大大提高代码可读性,你甚至会发现并简化一些变量。
前面的工作为抽取函数做好了准备,但你不必阅读和整理完所有的代码才开始抽取。许多遗留函数的大函数非常长,你可以整理一部分,就开始着手重构。比如,把刚才分段的代码段抽取出来,形成一个独立的函数。在这里,代码段与注释,是我们决定是否需要抽取成函数的重要标志之一。
在阅读过程中,许多长相相似的代码也是我们需要重视的重要代码。重复代码是许多遗留系统代码质量差的重要原因之一,因此提高代码复用就成为了代码优化一个重要的项目。整合大量重复的代码,将其提取到一个统一的函数中为其它各处所调用,是一个值得推荐的办法。因此,重复代码也是抽取函数的重要标志。
除此之外,一些块操作的语句,如条件语句、循环语句、try语句,都可能成为抽取函数的标志。最典型的就是if语句,包含在if语句中间的常常是一个相对独立的功能,譬如一次重构中,原代码长得像这样:
1 ...... 2 if (cmd != null && cmd.equals("chkCard")){ 3 //此处省略了500行 4 } else if (cmd != null && cmd.equals("chkIc")){ 5 //此处省略了300行 6 } else if (cmd != null && cmd.equals("chkBuffer")){ 7 //此处省略了1000行 8 } 9 ......
整个这段代码有数千行之多,但整体结果就是用这样的一系列if语句组成。随后,我将每个if语句中的代码都提取出来形成了各自的函数。它们被重构成这样:
1 ...... 2 if (cmd != null && cmd.equals("chkCard")){ 3 byte[] ret = chkCard(reader); 4 servletOutput(res, ret); 5 } else if (cmd != null && cmd.equals("chkIc")){ 6 byte[] ret = chkIc(reader); 7 servletOutput(res, ret); 8 } else if (cmd != null && cmd.equals("chkBuffer")){ 9 byte[] ret = chkBuffer(reader); 10 servletOutput(res, ret); 11 } 12 ......
这样,原来if语句中的业务操作代码,就被抽取到chkCard(), chkIc(), chkBuffer()这样的函数中了。起初我们将if语句中的所有代码都抽取出来写入这些函数中,这是十分自然而然想到的办法。但随后发现这样需要将response作为参数传递给这些函数中,这样的设计不太好。因此,将代码还原回来重新重构,将写入response的操作写入到servletOutput()中了。整个过程如图5.1所示:
图5.1分解大函数的示例
完成了此次重构以后,我们原来这个超级大函数由数千行代码,缩减到了百来行代码,这是一个可喜的进步,函数变得结构清晰而易于阅读。但是,被抽取出来的新函数却依然庞大,它们有的会达到一千多行,阅读依然困难。这时我们运用“抽取方法”继续分解。比如这个chkCard(),它执行的是一大堆校验,每个校验其功能都相对独立。因此,我首先调整代码顺序,将每个校验的代码都独立成一段,在前面添加相应的注释。然后使用抽取方法,将校验抽取到一个一个函数中。
整个数千行代码的超级大函数,就这样原子裂变式地逐渐分解,最后分解成数十个函数。每个函数只有数十行代码,并通过注释标注它们的用途与参数、返回值含义。这样,一个起初难于阅读的函数,经过一系列重构,开始变得可以阅读了。是的,我们开始迈出了可喜地一步。但一个对象包含了数十个方法,这些方法被凌乱地堆砌在一起,没有层级、没有主次。最关键是,虽然每个方法都不大,但这个对象却包含数千行代码,依然显得臃肿。因此我们还需要后面的步骤继续重构。
大话重构连载首页:http://www.cnblogs.com/mooodo/p/talkAboutRefactoringHome.html
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!