重构如何进行?

注:本篇文章是结合自己的体会和《重构》来完成的,如果各位对本文没啥兴趣,强烈建议读一读《重构》,因为本文到处都充斥(此处为褒义)着《重构》的气味。

在日常的代码书写过程中,虽然有项目需求的参照,但是我们的代码还是要有个生产过程,很少有大师级的人物对要求的代码一蹴而就。我们总是要随着需求的演进,不断地改进我们的代码,不断地调整代码之间的调用方式,类的关联等等各个方面。那如何在写代码的过程中,实施重构,有哪些重构方法可以实施呢?从以下几个方面,我们来大体阐述一下,重构的产生,方法及其实施:

1.如何开始项目;

2.重构的前期准备;

3.实施方法;

4.参考资料。

一、如何开始项目:

在接到任务时,要先考虑一下设计思路,但是因为对uml不是很熟悉,尤其是不想通过那些关系图来说明问题,前期的考虑结果也多数情况下会变成一张画满图形关联的草稿纸。于是乎,动手。这没什么特别之处,各位肯定也是这么做的。当年看过一篇文章,作者说自己有了多年的软件开发经验,能够针对既存的代码提出改进建议和意见,但是当要自己开始一个新的项目时,总是不知道该如何下手。那么我前面说的这样的开始,各位同僚可能也会质疑:是不是太简单了?好吧,我们继续深入。我会按照最开始的设计思路,实现一部分代码,当以代码形式展现你的思想时,你一定要把需求跟设计关联在一起,而且要稍微有点前瞻性,不要太过,否则过犹不及,会造成过度设计。好吧,那么我最开始的设计多数是这种情况:对于一个类而言,里面有一到两个方法。对于复杂功能来说,这是很危险的,如果你在纠结中实现了这个需求,你可能会有一种呕吐的感觉,使得你自己都不愿意再回头看你原来的代码。这个时候,所谓的代码“臭味”就会产生,耦合的设计,邋遢的类和冗长的方法。。。。。一般情况下,我会忍住对这种好不容易揉和出来的代码的反感,多看几眼这个方法。然后采取三个措施:抽取方法,抽取方法,抽取方法。。。

我的第一步就是抽取方法。

先别急,Fowler也建议说:一次进行一小步。这个很重要,因为对于我们好不容易“弄”出来的代码,太有可能一下子就超过了50行,超过五十行的代码里面一般情况下会掩盖几个方面的信息:业务耦合,分配不均,职责不明,最关键的是没有针对各种业务添加主要负责人。抽取方法就是要划分业务处理,或者是动作处理。所以,经过第一次处理之后,我们的主要方法可能就被四分五裂了,但是,你的收获是:一个主要而清晰的方法,跟了一帮短小精悍的小方法(其实不一定短小精悍,但是肯定比原来清晰)。因为做java开发,几个ide工具基本都有相应的代码重构处理,不管是eclipse还是intellij都有抽取方法的功能。接下来再观察一下,在某个抽取出来的方法里面,其任务主要是在构造一个对象,这个时候,你又可以动手了:抽取builder对象。

那么第二步,就是抽取代理对象。

如何抽取呢?在第一步的基础上,我们已经得到了处理一个业务的各个子方法,也就是说,我们把原来的业务进行了划分。在这个时候,我们应该再对照一下需求文档,看我们的划分是否“合理”。这里的“合理”验证应该是分析我们的设计是否能够满足需求的进化,也就是稍微前瞻一点的设计(再次提醒:适可而止!),觉得可以满足,需求点里面的哪些变化,这些估计可以跟开发同事一起讨论,也可以qa或者需求管理员讨论。不仅仅可以使得自己对需求有更加深入的理解,也可以使设计思路慢慢清晰起来。好了,现在我们发现抽取出来的方法可以做这样的划分:对象准备、业务处理、验证方法、工具方法、结果处理、异常处理等几个方面(可能还有很多其他方面,各位有的就补充一下,一起分析)。

(一)考虑处理对象准备的方法:

如果此类方法比较复杂,我会考虑将其变成一个类来处理。比如利用设计模式中的工厂、构造器等方式,那么我们就增加了一系列的创建类(重构增加了类的数量和关联复杂度,但使得代码结构更加清晰)。当然,我们不一定要抽取出各种工厂,构造器等类,因为这些类抽取出来后,是为以后的扩展做准备。因为结合需求,我们发现跟当前准备的对象同类的会越来越多,所以这种工厂和构造器就使得以后的扩展变得轻松了。但是,如果我们紧紧是为了抽取出这个准备方法,使它能独立的在原来类的范围内使用,那可以做一个内部类。这些都要依据用户需求才行,不能臆断。当然,如果这个类很想,你就别费这个时间和精力,非要弄出一个工厂类来,那反倒是过度设计了。。。

(二)业务处理方法:

业务处理方法是很容易集聚你的业务领域内容的。个人认为,如果能够跟需求管理员一起就此部分深入讨论,就能够达到领域驱动所要求那些领域定义对象(强调一遍:业务专用对象)。这些业务处理方法,在抽取成类时,命名很重要,要完全体现业务需求的主要内容,而且这个时候,可能要伴随java包的重构,是的业务处理更加具有内聚性。这个时候,我也会考虑一些设计模式,比如facade、state、strategy等等,这个里面基本上可以用上所有结构和行为方面的设计模式。就是说不要因为你抽取出了业务处理对象,并迁移到domain包内,而使得你的业务处理包有很多依赖入口。

(三)验证方法:

因为验证总是一个重复使用的东西,我们可以通过不断地抽取验证方法,发现我们会有哪些重复动作。一次在写一个关于java容器中的查询功能时,发现泛型可以使得接口变得相当通用(这是泛型的一个很重要的作用,平常没怎么用而已),但是加了泛型之后,我却不了解具体类型了,也就不能特别验证特别的对象了,进而使得我的方法增加了很多验证的静态代码,于是乎,抽之。抽出这个方法之后,我就可以做多方面处理了:1.类型数量不多,我就写死(好代码也是从垃圾代码产生的)。2.随着种类的增多,我就根据strategy或state模式增加相应验证。

一般情况下,验证方法可能仅仅是某个类或者方法内部使用,但是很多情况下,这种验证可以抽取出来共用,就如同工具方法一样。

(四)工具方法:

其实上面的验证方法也可以成为一种工具方法,我们看spring等开源代码时,会看到很多工具类,都是在spring-core的util包里面,如:ClassUtils、FileCopyUtils等。所以,我们在这里抽出来的方法,也可以根据不同的作用慢慢积累起来。这都是最原生的方式,spring肯定也不是一下子就写出这么写工具类。我们的私用api也可以日积月累啦。

(五)异常处理:

异常是java里面很重要的一个组成。只要跟资源相关,就要用到异常处理。所以,在这里我也是把它独立,希望可以在这里特别的对其作出一个说明。以避免不必要的分层级别上,管理异常。在我们重构的过程中,我们要明确到底在哪一层上对异常进行处理。这一点,还是单开一篇文章吧,如果大家等不及,可以看看《Expert one on one j2ee design and development》。

经过以上的处理,个人觉得,我们的项目已具雏形了。

其实,我们在开始一个项目的时候,难免迷茫,万事开头难也体现在这里。即便有很多成熟的框架摆在我们的面前,但不是每个框架都是你的那盘菜。框架准备好了,还不够,如果你想用老三段:M-V-C.那你肯定驾轻就熟,但是仅仅有了这些框架不一定就说明我们一定能写出好项目。从一开始就借助重构不断地改进我们的代码是极其必要的。

二、前期准备

重构的前期准备,主要是测试环境的搭建。一旦决定重构,就要确保一个前提,那就是原来的功能不能被破坏掉。如何确保原来的功能依然可用?测试。而且肯定不能依赖一个简单main函数来测试一下,尽管这可以实现一定的处理,总是不太方便扩展及用例的增加等等。我们只是简单的说明一下单元测试,以junit为例吧。

我接触的项目都不是啥大项目,即便是个大项目,也可以用这样的框架来做:spring-context、core、bean等作为基础,spring mvc作为前端。但是在测试方面,尤其是开发的单元测试也就离不开junit了。

我在测试时,是将spring跟junit结合在一起的,用到了spring-test。所以,可以将spring的context和junit结合在一起。这些东西都有了,前期的准备也就差不多了,junit大家就去找点资料吧,因为它的使用还是比较简单的,关键还是要写出有意义的测试代码。

三、实施方法

(备注:以下内容可能跟《重构》里面有极大的相似之处,各位有理由怀疑,但是咱绝对没那闲工夫抄袭,起码也要算是自己的理解,所以,发现任何问题,望不吝赐教!)
关于重构,都有哪些具体的实施方法呢?首先要说明一点,那就是进行任何重构之前,一定要先准备测试代码,保证原来功能的准确性,或者保证整体功能的准确性。这个相当重要,除非你完全保证,但是一般情况下,别去除非。

我们主要从几个方面来说明吧:

(一)基于函数的处理

1)抽取函数

在《重构》中对这个部分做了很细致的说明。主要原因我想可能是他们没有使用现代点的IDE,上面说了,不管是eclipse还是intellij都有很强大的方法抽取功能。以intellij为例,其重构的功能就有抽取方法,抽取方法类(即:将一段代码转换成一个对象的方法调用),抽取类(即:将一段代码抽取出来作为一个类,并在原来代码中增加对这个类的依赖,把原来的那段代码换成对依赖对象方法的调用),替换重复的代码等等,功能相当强大,鉴于本文不是intellij的说明书,介绍到此结束。言归正传,我们要使用抽取方法时,尤其要考虑变量的问题,也就是我们所关心的代码片段中的变量问题。变量问题分为几种情况:无局部变量,这个时候可以直接将这段代码抽出来,定义成一个函数,然后将原来的代码片段替换为方法引用;有局部变量,这里就比较复杂了,还要进一步划分为无需修改值的情况和修改变量值的情况。对于第一种情况,是说我们的代码片段仅仅是使用变量,于是我们可以把这个变量变成输入参数来用;对于第二种情况,没办法,还要进一步划分为:

posted on 2011-08-04 16:09  eric_chen  阅读(653)  评论(0编辑  收藏  举报