避免复制与粘贴

 

《重构之美》之一

logo_black 在开发过程中,当你发现代码可以Copy-paste时,就意味着代码出现了重复。这是一种典型的反模式。William J.Brown等在著作AntiPatterns-Refactoring Software,Architecture, and Projects in Crisis(即《反模式——危机中软件、架构和项目的重构》)中认为这种形式的复用让开发的代码行数量虚假地增加,但是不能像其他形式的复用一样降低成本。Copy-Paste代码的方式违背了DRY(即不要重复你自己)原则,使得多处地方出现了同样或者相似的代码。这是一种征兆,一旦在方法中或方法之间开始Copy-Paste操作,就意味着需要采用Extract Method重构手法。在提取方法之后,还可以根据情况利用Move Method重构手法,将其搬移到一个类中,然后在原来的调用处转为对该类方法的调用。或者利用Replace Method with Method Object,将这些职责封装为专有的类。 

在我的编程生涯中,碰到类似Copy-Paste的情况简直不胜枚举。在一次项目中,我们对开源项目Jasper Report进行了扩展。我们加入了对新报表类型(CJT_REPORT)的支持。在ReportParameterAction类中,我们需要对报表对象ReportUnit判断报表类型。于是,我在ReportParameterAction类中定义了如下的私有方法: 

private void setReportUnitTypeByFileResource(ReportUnit reportUnit) { 

       final String JS_FILE_TYPE = "jrxml"; 

       ResourceReference reference = reportUnit.getMainReport(); 

       if (reference.isLocal()) { 

           FileResource resource = (FileResource)reference.getLocalResource(); 

           String fileType = resource.getFileType(); 

           if (fileType.toLowerCase().equals(JS_FILE_TYPE)){ 

              reportUnit.setReportType(ReportUnit.JS_REPORT); 

           } else { 

              reportUnit.setReportType(ReportUnit.CJT_REPORT);         

            }         

        }     

 }

该方法根据FileResource中的文件类型获得对应的报表类型。这一方法在ReportParameterAction类的createWrappers()方法中被调用。 

    protected Event createWrappers(RequestContext context) { 

       ReportUnit reportUnit = loadReportUnit(context); 

       setReportUnitTypeByFileResource(reportUnit); 

        InitialRequestContext(context, reportUnit);

       int reportType = reportUnit.getReportType(); 

       if (reportType == ReportUnit.CJT_REPORT) {//……} 

       //…… 

     }

后来,我发现在EngineServiceImpl类中同样需要判断报表的类型。然而,setReportUnitTypeByFileResource()方法却被定义为ReportParameterAction的私有方法,无法被EngineServiceImpl对象调用。最简单的做法是采用复制的方式,将这段代码复制到EngineServiceImpl中。好了,如果此时我们不能忍住copy-paste的简便所带来的诱惑,或许就会陷入重复的泥沼了。Copy的动作绝对应该鸣起刺耳的警声。我们需要对这一做法保持足够的警惕。 

通过分析setReportUnitTypeByFileResource()方法,我发现该方法需要操作的对象均与ReportUnit有关,而本身该方法的职责就是要获得ReportUnit的类型,并对其字段reportType进行设置,因此,完全可以将它搬移到ReportUnit中(Move Method重构,不是吗?)。由于ReportUnit已经增加了对reportType字段的getset访问方法,我发现它们与我要重构的方法实有异曲同工之处,因而决定将其搬移到getReportType()方法中: 

    public int getReportType() { 

       if (hasReportTypeBeenSet()) { 

           return reportType; 

       }     

       return  getReportTypeByFileResource();

    }   

     private int getReportTypeByFileResource() { 

       final String CJT_FILE_TYPE = "cjtxml"; 

       int reportType = ReportUnit.JS_REPORT; 

       ResourceReference reference = this.getMainReport(); 

       if (reference != null) { 

           if (reference.isLocal()) { 

              FileResource resource = (FileResource) reference 

                     .getLocalResource(); 

              if (resource != null) { 

                  String fileType = resource.getFileType(); 

                  if (fileType.toLowerCase().equals(CJT_FILE_TYPE)) { 

                     reportType = ReportUnit.CJT_REPORT; 

                  } 

              } 

           } 

       }

        return reportType;

     } 

    private boolean hasReportTypeBeenSet(){ 

       if (reportType != ReportUnit.JS_REPORT 

&& reportType != ReportUnit.CJT_REPORT) { 

           return false; 

       } else { 

           return true; 

       } 

    }

注意,在以上过程中,除了使用Move Method之外,我还运用了Rename Method以及Extract Method等重构手法。 

现在,对报表类型的获取与调用就变得简单了,在ReportParameterActionEngineServiceImpl类中,也规避了重复代码的产生。例如,在createWrappers()方法中的调用被更改为:

    protected Event createWrappers(RequestContext context) { 

       ReportUnit reportUnit = loadReportUnit(context); 

       setReportUnitTypeByFileResource(reportUnit); 

        InitialRequestContext(context, reportUnit);

       int reportType = reportUnit.getReportType(); 

       if (reportType == ReportUnit.CJT_REPORT) {//……} 

       //…… 

     }

posted @ 2010-12-01 18:36  张逸  阅读(3739)  评论(14编辑  收藏  举报