重复的代码在软件开发过程中是会带来很大隐患的东东,尤其是当重复的代码发生变更时,很多莫名其妙的Bug就随之而来。我做面试官的时候曾经问过应聘者:去除重复代码有哪些种方法?有些人回答很简单,抽取出来一个函数,把重复代码移动进去。事实上真的是这样么?
本文试图阐述几个问题?A)去除重复代码在软件结构中的意义;B)列举代码去重过程中的一些方法,但只是抛砖引玉的作用,并不能完全枚举出来;C)讨论一些特殊的情况下,适当地忍受一些重复逻辑的存在;
首先,什么是重复代码?我是这样定义的:重复代码指的是那些相同、或者相近(完成的业务逻辑相近)的代码。产生的根源根源有如下几种情况:
. Copy操作产生的:我曾经半开玩笑的和同事说过,按Ctrl+C的程序员不是好程序员,仔细分下下来也有
两种情况,一种是Copy过来,直接使用;另外一种是稍作修改后使用。
. 没有设计好造成的:比如张三和李四有一段类似的事情要做,由于沟通、设计等原因,每个人写了一份出来。
去除重复代码在软件结构中的意义:如果一个软件系统没有重复代码,或者几乎没有重复代码,那么软件系统的结构一般情况下不会太差。当然,也有例外的情况,我曾经参与过一个的一个项目,重复的代码确实不多,但是开发者把可能用到的东西都抽象到一个Utility类里面了,结果那个Utility非常复杂,做了很多的业务逻辑(在一些项目里面还发现过Utility类引用了一堆的Spring Bean,做具体的逻辑),几百行之多。很多的Service引用到了该函数,传入了7、8个参数,根据每种Service的业务做了If判断,每次读完了之后回家都做噩梦。所以去除重复代码(以下简称为去重)也并非是一件很简单的问题。
去重的方法,我做了简单归纳,将其分为两类进行阐述(只是我个人的分类方法):A)模式去重;B)设计调整去重 C)短代码去重。模式去重着重于针对GOF设计模式在去重方面的贡献、关键点;短代码去重着重于Utility的设计;
模式去重:实际上很多模式在去重方面都是卓有成效的,只是一些程序员平时不是很关注而已(面试的时候还有程序员奇怪,为什么我问设计模式相关的只是,认为设计模式只是一个理论化的东西,没有实际用处。究其本质,是因为没有真正的理解设计模式本身),下面我们一几个模式作为例子,分析其在去重方面的贡献:
1)工厂方法与模板方法
工厂方法与模板方法的结构实际上是很类似的,甚至有人将工厂方法看做是特殊的模板方法(只是抽象的部分负责创建一个对象而已:
工厂方法:
模板方法:
如果把模板中的primitveOperation1()变为一个创建对象的操作,那么实际上模板方法就是一个工厂方法了。因此下文我们只讨论模板方法。从去重的角度去看,如果不使用模板方法,那么ConcreteClass的TemplateMethod需要重新实现,如果是多个ConcreteClass的情况下,就产生了类似的代码。至于为什么采用模板方法解决这个问题,而不是其他的方式(比如把公共的部分抽象成一个工具类等),不作为本文讨论的内容。
2)Builder模式
Builder在去重方面的作用比较明显,因为如果不使用Builder的话,Client需要自行实现构建过程,那么多个Client的构建的代码必将是重复的。
3)代理(Proxy)模式
代理实际上在Proxy层面对RealSubject进行了访问控制,比如if特定的逻辑下才进行操作RealSubject。为了实现这样的目标,引入了Proxy,比如数据库连接池就是典型的使用代理的例子。如果没有Proxy呢? 则每个使用Subject的地方都需要加入这样的判断if(xxx) realSubject.request(); 这个实际上也是重复的。
以上都是从设计模式角度上看,有哪些可以从去重的角度出发而引入的设计方法。实际上有些比较小粒度的设计手段虽然不构成模式,但是也很好的起到了去重的作用。
设计调整去重:
有待完善。
短代码去重:
有待完善。
参考资料:
《设计模式:可复用面向对象软件设计方法》
《实现模式》
Apache-commons