代码复用应该这样做(1)
前面我们讨论了小步快跑,是不是开始被雷到了,做了这么多年程序猿,原来程序可以这么开发。是的,小步快跑是一个十分新颖的概念,也许你一时半会儿还不能完全领悟,或者不能欣然接受,因为它太前卫了,与我们传统的思维大相径庭。但是,就像一部精彩的小说,我会慢慢揭开它神秘的面纱,你会慢慢领悟,进而接受。总之,小步快跑就是“活在当下”,做现在的设计,不必过多考虑将来(即使考虑,也是完全可以预见的将来),因为我们有重构。我倡导的是一种快乐的生活方式,编程是一种享受,而不是一个苦差。
然而,我们的问题并没有完全解决,因为我们面对的是遗留系统。遗留系统,就是你对它做任何事情,都要先阅读一大堆晦涩难懂的代码,然后艰难得对其进行修改。IT攻城狮最大的痛不是如何面对客户异想天开的新功能,而是面对遗留系统时情何以堪。代码质量是几乎所有遗留系统共同的问题,你可以骂领导如何只重效益不重质量,你可以骂需求分析如何打酱油而不控制用户需求,你的前任代码写得如何烂。这些多说无益、人人都懂。从自己做起,写好自己的每一段代码,就是对整个项目最大的贡献。
怎样才能写好每一段代码呢?这里我不想讲太多的理论,这些理论到处都有,真不需你在这里费口舌。大家回想一下,我们在以往编写代码的过程中,有多少次在复制粘贴代码?不用说,一定是无数次,据说程序员操作最熟练的快捷键就是“ctrl+C”与“ctrl+V”。毫无疑问,我们编写的代码中有许许多多的功能是相似甚至完全相同的,关键在于你怎样去看待它。复制粘贴是最简单常用的方式,但在一个软件系统中,如果同一功能被反复地复制粘贴了,一旦这段代码需要变更时,那简直就是一种灾难。代码重复的问题普遍存在于许多遗留系统中,但我们很少会重视它,特别是在中国。然而,它就是糟糕代码的元凶,系统维护越来越困难的源头。
因此,今天我们来谈一谈代码复用。毫无疑问,我们首先应当建立起代码复用的意识。但光有意识是远远不够的,真正的考验是一个机会摆在你面前,而你要懂得该怎样做,这才是考验程序员是否优秀的地方。它需要有相当的面向对象分析与设计的理论知识(说实话,这部分是国内软件工程师最应当掌握却普遍缺失的知识,所谓的大牛就是能够用开源框架架构系统的人),与相当的分析设计经验。
不论怎样,我们先看看要解决代码复用的问题应该怎样做,然后再看看添加新代码时应该怎样复用。OK,翠花,上示例!
总体来说,第一步应该是比较代码。将功能相同或相似的代码进行比较(可以使用一些代码比较工具),然后将这些代码段中彼此相同的部分标注出来,运用“抽取方法”将其抽取到另一个函数中。这个函数中的代码,就是我们需要复用的代码。
假如这被比较的两份或者多份代码存在于同一个对象中,则将这段被抽取出来的函数作为它们共同的函数,为其它各份代码所调用。比如,有这样一段代码:
1 /**
2 * 通过DWR获得当前的request、response、session等信息
3 * @author fangang
4 */
5 public class DwrContext {
6 /**
7 * @return 当前的Request
8 */
9 public static HttpServletRequest getRequest(){
10 return WebContextFactory.get().getHttpServletRequest();
11 }
12 /**
13 * @return 当前的Response
14 */
15 public static HttpServletResponse getResponse(){
16 return WebContextFactory.get().getHttpServletResponse();
17 }
18 /**
19 * @return 当前的Session
20 */
21 public static HttpSession getSession(){
22 return WebContextFactory.get().getSession();
23 }
24 }
大家注意到在这段代码的多个函数中都有这段代码WebContextFactory.get(),也就是说这段代码是几个函数相同的部分。我们运用“抽取方法”将其抽取出来,放进getContext()函数中,而让其它的函数不再使用重复的代码,而是替换成对这个函数的调用:
1 /**
2 * 通过DWR获得当前的request、response、session等信息
3 * @author fangang
4 */
5 public class DwrContext {
6 /**
7 * @return DWR的WebContext
8 */
9 private static WebContext getContext(){
10 return WebContextFactory.get();
11 }
12 /**
13 * @return 当前的Request
14 */
15 public static HttpServletRequest getRequest(){
16 return getContext().getHttpServletRequest();
17 }
18 /**
19 * @return 当前的Response
20 */
21 public static HttpServletResponse getResponse(){
22 return getContext().getHttpServletResponse();
23 }
24 /**
25 * @return 当前的Session
26 */
27 public static HttpSession getSession(){
28 return getContext().getSession();
29 }
30 }
这里还有一种情况也相当普遍,就是一个对象中的函数重载,比如这段代码:
1 /**
2 * 初始化工厂。根据路径读取XML文件,将XML文件中的数据装载到工厂中
3 * @param path XML的路径
4 */
5 public void initFactory(String path){
6 // 实现读取path路径下的XML文件,然后装置工厂
7 }
8
9 /**
10 * 初始化工厂。根据路径列表依次读取XML文件,将其中的数据装载到工厂中
11 * @param paths 路径列表
12 */
13 public void initFactory(String[] paths){
14 this.paths = paths;
15 for(int i=0; i<paths.length; i++){
16 // 实现读取paths[i]路径下的XML文件,然后装置工厂
17 }
18 }
这里加粗的部分被我省略了,其实内容很多,并且代码重复。这时我们为什么不设计成让后一个函数读取前一个函数呢?具体说就是这样:
1 /**
2 * 初始化工厂。根据路径读取XML文件,将XML文件中的数据装载到工厂中
3 * @param path XML的路径
4 */
5 public void initFactory(String path){
6 // 实现对一个路径装置工厂
7 }
8
9 /**
10 * 初始化工厂。根据路径列表依次读取XML文件,将XML文件中的数据装载到工厂中
11 * @param paths 路径列表
12 */
13 public void initFactory(String[] paths){
14 this.paths = paths;
15 for(int i=0; i<paths.length; i++){
16 initFactory(paths[i]);
17 }
18 }
(续)
相关文档:
遗留系统:IT攻城狮永远的痛
需求变更是罪恶之源吗?
系统重构是个什么玩意儿
我们应当改变我们的设计习惯
小步快跑是这样玩的(上)
小步快跑是这样玩的(下)
代码复用应该这样做(1)
代码复用应该这样做(2)
代码复用应该这样做(3)
做好代码复用不简单(1)
特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!