Refactoring practice
Shenyang Liaoning Province P.R.China
前言
很早以前就听说过重构了,但是一直没有理由让我真正的花时间真正理解一些时下正在流行的名词,8月份新的项目终于开始了,是针对去年毕业后工作以来所有代码的一次大动作的修改,主要是修改代码的结构,以便让人能够理解,当然还有性能的调整,上个月有接受过公司的TDD的培训,参加培训之前浏览了一下主要的相关技术,其中就有重构。所以这个时候又重新考虑一下重构这个已经熟悉很久的名词,一个多月以来,学习了《重构》(Martin Flow)的前四章,以及后边的挑着看的一些章节,加上这段时间以来的对代码修改的实战经验,总结一下。
(一) 需要重构的情况
重构的终极目标是代码符合设计模式的要求,我们都知道,一般情况下理想化的目标不能够实现,原因很多,简单的举例比如:项目组新人很多,或者项目组有不喜欢技术的同事,或者上司不喜欢这样对代码胡来。虽然完美的东西不存在,虽然追求完美是人类最不理性的行为之一,但是我们还是可以做一些工作让一些事情让人看起来我们有这个意识,曾经努力过。这对于我们的后来者,好处自然不言而喻,而且个人认为让自己做过的事情看起来优雅也是我们的基本职业素养之一。呵呵,有点夸张了。
基于以上的考虑,我认为最主要的三个主要我们需要考虑的情况是:
方法
方法里我们常见的问题就是方法过大,不同的方法中间有重复的代码,这两种现象最为常见。
方法过大:
方法一般情况下是完成方法名所定义的语义,但是很多时候我们不知不觉就写了很多的代码,于是这种现象就出现了,以至于后来的人想看懂这个方法都非常麻烦。我们这次修改代码的时候,方法过大的情况就是把拼接SQL的代码,检索数据库的代码,把检索出来的数据处理成CSV格式的代码放在一个方法里,导致一个方法最大的时候都能有1K行这么多,看了就头大。
重复代码:
同一个类的不同方法中间出现重复代码的现象很多,比如过滤掉字符串中的特殊字符。
不同的类的方法出现重复代码的可能性就更加大了,很多时候都是彼此沟通不够好,当然即使是同一个人写的东西这种现象也很频繁。 这次我们发现的问题最都的时候就是不同的功能模块之间的数据库操作的时候代码都一模一样。
局部变量:
很多时候我们写代码喜欢定一个局部变量,然后用这个局部变量进行一些操作,但是事实上这个局部变量是毫无作用的,我自己就犯了很多这样的错误。
还有一个关于局部变量的问题,就是不喜欢定义局部变量,每次都从某个地方(比如数据库)取得数据。
类
类的问题比较高级,很多时候都是当初的设计不够合理,或者是开发人员没有遵守好游戏规则。这次我们要修改的代码从类这个级别的高度来看,非常的清晰,类都能很好的负责自己的职能。也不用我们去考虑这些问题,不过话又说回来,这个级别的重构水平比较高,修改起来难度比较大。但是我们平时也会看到别的情况,比如一个类过于臃肿,类的继承关系复杂(主要是继承层次太多)。
其他问题
字符串很多,分散在不同的类里头,这次这个问题也比较麻烦。
这边都是我们短时间里能够处理好的问题,这样处理虽然有不够充分的嫌疑,但是肯定不会把问题左倾。。。。。。 J
(二) 常见的重构方法
重构的方法很多,当然都是针对需要重构的情况提出来的对策。
方法过大
方法过大的情况我们可以抽出来其中的一些代码作为私有方法,达到分解的目的,如果别的类有可能能够用得到,公有的就OK。
方法的重复代码
重复代码应该抽取成一个方法,如果很多类都用到的话,我们应该让这些类都用这个方法。而且一般这种情况我们可以把这个方法作为一个静态方法来使用。
局部变量
对于局部变量没有必要的情况,我们可以Inline这个局部变量。
比如:int a = getInteger(); System.out.println(a);
修改成: System.out.println(getInteger());
如果我们很多时候都要用到getInteger()这个方法取得的值,然后这些值都相同,我们就应该定义一个局部变量来保存这个值,而不是每次都取。
类过大
这种情况很多都是我们的类承担的任务太多,一般情况下我们可以考虑组合模式或者挪走不是这个类应该做的方法。
类的继承关系过于复杂
其实这样的问题肯定是设计得不够合理,修改起来比较麻烦,一般都需要重新考虑设计了。还好我没有碰到过这样的情况。
其他的方法其实还很多,但是我认为这些不仅仅是看看书或者听某个大师讲演一两次就能懂得的,需要的是丰富的经验和仔细的思考。当然,我们不是因此而不做,而且这些是我们努力的方向。
(三) 重构中应该注意的问题
重构是使我们更加高效的利器,但是中国有句老话:过犹不及。所以使用的时候也应该具体情况具体分析。
1. 避免重构引出新的问题
重构就是修改代码,修改肯定会带来一些问题,但是这些问题可不可以避免呢?当然这些都是事在人为,经验丰富的程序员出错的概率肯定会比较少,还有就是使用重构工具,能够很大程度上避免不小心的问题。
2. 避免过度重构
不管什么时候,钻牛角尖肯定是没有必要的,而且往往费力不讨好,我们都是聪明人,见好就收吧。
3. 重构的时机
一般来讲什么时候都可以,但是我认为去动那些运行很好的代码就是自找麻烦,所以就是在编码快要结束和测试这两个阶段比较合适,因为我们对代码还算比较熟,而且这个时候代码动两下也没有关系。
(四) 使用Eclipse的重构工具
好像支持重构的工具很多,但是我只用过Eclipse,其实工具这些东西对于我们程序员来讲,都能很快的学会并且掌握得很好。可以查看一下帮助文档,还有就是之前应该看看《重构》-----Martin Fowler,会比较快的掌握这些工具。
为什么要使用工具,因为人总是会犯错误的,所以依赖工具来避免问题,其实不仅仅是避免问题,使用工具能极大地提高效率,使得重构不再是痛苦,而是一种享受,对于上面提到的问题,都可以很轻松的完成。
比如字符串变量太分散,我们可以Exact Constant抽取成常量,然后Move到我们的常量类,这样不会代码问题,而且很轻松。
(五) 一些特殊场合使用重构
1. 在外包行业中使用重构
虽然我也在从事外包行业,但是这一年多以来,我开始意识到这个行业相对于其他的软件开发有点畸形,其实中国的很多外包公司能够做的就是按照那边提供的说明书编码,然后单元测试(有的单元测试都是走过场),仅此而已。但是这并不是说我们所有的自由都被剥夺,仍然可以做一些重构的工作,然我们的代码看起来更加专业优雅。
程序员角度的建议
a) 让领导知道
b) 自己愿意
c) 时间允许
d) 不要挑战极限
管理人员的建议
a) 对方同意修改
b) 对方愿意付费
c) 重构不会带来大的风险
d) 项目组成员有足够的精力和能力
2. 没有自动化测试的重构
很显然,被重构过的代码有可能会引来Bug,所以重构总离不开测试,其实如果有以前的JUnit之类的自动化测试用例,当然最好,可是没有的时候我们应该怎么办呢?
对于这样地情况,如果只对针对方法进行重构(类内部的重构,不移动任何接口),比如抽出私有方法,删除局部变量等等,当然最重要的是你用的工具支持得足够好(Eclipse就不错),我们使用这些工具进行重构,一般不会带来BUG。
这次我修改代码的时候,大量的使用这样的方法,到目前为止,这个都没有发现给我带来新的BUG,倒是发现了一些古老的BUG,而很可恶的就是我们的项目的Unit Test以前做的很烂,还是一些比较原始的手段,现在要重新测试非常麻烦,但是我用Eclipse进行一些低水平的重构之后,是不是在时间不够充裕的情况下进行单元测试呢?这个问题也一直在考虑。其实通过工具来自动进行的低水平的重构(类内部的重构,不移动任何接口),重构的安全性的应该是足够高的,除非工具不够健壮,很显然,Eclipse已经足够好了。
我已经不打算做了,但是谁能给一个答案呢?就是结合测试来处理问题吧。。。。
(六) 结束语
其实,对于重构,我认为这种思想确实比较能够吸引程序员的思想,至少我是这样的,
因为没有什么事情一定一次就能做好,所以回过头来再考虑一下总是有必要的,当然跟时间等因素关系很大。
重构是一门重要的学科,全部掌握自然很困难,而且需要时间,但是这些都不是问题,我们掌握其中的一部分,能够胜任我们的工作就好!在这个过程中自然离不开很仔细的思考和学习。
其实还有更加重要的一点,就是不重构,因为我们的代码已经足够好了,为什么要呢?但是为什么我们不省下来时间帮妈妈洗洗碗呢?
(七) 参考文献
重构 改善既有代码的设计 -----Martin Fowler
http://www.refactoring.com/
2006-9-10