代码复用应该这样做(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)

特别说明:希望网友们在转载本文时,应当注明作者或出处,以示对作者的尊重,谢谢!

posted @ 2014-01-11 19:17  充满诗意的联盟  阅读(2690)  评论(5编辑  收藏  举报