软件的发展规律就是这样的,起初十分简单明了,使我们可以轻松地进行合理的设计。接着开始变更,业务变得越来越复杂,程序也随之变得越来越复杂了。正是因为软件开始由简单软件向复杂软件转变,而我们的设计却没有合理地调整,最后导致了我们的系统越维护越困难,成为了不可被扣的遗留系统——IT攻城狮永远的痛。这就是遗留系统产生的根本原因。

因此,解决遗留系统的根本办法,就是在软件由简单软件向复杂软件转变的关键时刻,适时做出调整,使软件重新回到高质量的状态。这里,我们要做出的调整被称为重构,而做出这种调整的最佳方式,就是“小步快跑”啦。说得那么玄乎,到底什么是“小步快跑”呢?说不尽千言万语,倒不如一个简单的示例:

故事是这样的,当用户登录一个网站时,网站往往需要给用户打一个招呼:“hi, XXX! ”。同时,如果此时是上午则显示“Good morning! ”,如果是下午则显示“Good afternoon! ”,除此显示“Good night! ”。对于这样一个需求我们在一个HelloWorld类中写了十来行代码:

/**
 * The Refactoring's hello-world program
 * @author fangang
 */
public class HelloWorld {
    /**
     * Say hello to everyone
     * @param now
     * @param user
     * @return the words what to say
     */
    public String sayHello(Date now, String user){
        //Get current hour of day
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(now);
        int hour = calendar.get(Calendar.HOUR_OF_DAY);

        //Get the right words to say hello
        String words = null;
        if(hour>=6 && hour<12){
            words = "Good morning!";
        }else if(hour>=12 && hour<19){
            words = "Good afternoon!";
        }else{
            words = "Good night!";
        }
        words = "Hi, "+user+". "+words;
        return words;
    }
}

如果需求没有变更,一切都是美好的。但事情总是这样,当软件第一次提交,变更就开始了。系统总是不能直接获得用户名称,而是先获得他的userId,然后通过userId从数据库中获得用户名。后面的问候可能需要更加精细,如中午问候“Good noon! ”、傍晚问候“Good evening! ”、午夜问候“Good midnight! ”。除此之外,用户希望在一些特殊的节日,如新年问候“Happy new year! ”、情人节问候“Happy valentine’s day! ”、三八妇女节问候“Happy women’s day! ”,等等。除了已经列出的节日,他们还希望临时添加一些特殊的日子,因此问候语需要形成一个库,并支持动态添加。不仅如此,这个问候库应当支持多语言,如选择英语则显示“Good morning! ”,而选择中文则显示“上午好!”……总之,各种不同的需求被源源不断地被用户提出来,因此我们的设计师开始头脑发热、充血、开始思维混乱。是的,如果你期望你自己能一步到位搞定所有这些需求,你必然会感到千头万绪、顾此失彼,进而做出错误的设计。但如果你学会了“小步快跑”的开发模式,一切就变得没有那么复杂了。

首先,我们观察原程序,发现它包含三个相对独立的功能代码段,因此我们采用重构中的“抽取方法”,将它们分别抽取到三个函数getHour(), getFirstGreeting(), getSecondGreeting()中,并让原函数对其引用:

/**
 * The Refactoring's hello-world program
 * @author fangang
 */
public class HelloWorld {
    /**
     * Say hello to everyone
     * @param now
     * @param user
     * @return the words what to say
     */
    public String sayHello(Date now, String user){
        //这里将原有的代码通过“抽取方法”抽取到3个函数中
        int hour = getHour(now);
        return getFirstGreeting(user)+getSecondGreeting(hour);
    }

    /**
     * Get current hour of day.
     * @param now
     * @return current hour of day
     */
    private int getHour(Date now){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(now);
        return calendar.get(Calendar.HOUR_OF_DAY);
    }

    /**
     * Get the first greeting.
     * @param user
     * @return the first greeting
     */
    private String getFirstGreeting(String user){
        return "Hi, "+user+". ";
    }

    /**
     * Get the second greeting.
     * @param hour
     * @return the second greeting
     */
    private String getSecondGreeting(int hour){
        if(hour>=6 && hour<12){
            return "Good morning!";
        }else if(hour>=12 && hour<19){
            return "Good afternoon!";
        }else{
            return "Good night!";
        }
    }
}

这次重构虽然使程序结构发生了较大变化,但其中真正执行的代码却没有变化,还是那些代码。随后,我们核对需求发现,用户需求分成了两个不同的分支:对用户问候语的变更,和关于时间的问候语变更。为此,我们再次对HelloWorld的程序进行了分裂,运用重构中的“抽取类”,将对用户问候的程序分裂到GreetingToUser类中,将关于时间的问候程序分裂到GreetingAboutTime类中:

/**
 * The Refactoring's hello-world program
 * @author fangang
 */
public class HelloWorld {
    /**
     * Say hello to everyone
     * @param now
     * @param user
     * @return the words what to say
     */
    public String sayHello(Date now, String user){
        GreetingToUser greetingToUser = new GreetingToUser(user);
        GreetingAboutTime greetingAboutTime = new GreetingAboutTime(now);
        return greetingToUser.getGreeting() + greetingAboutTime.getGreeting();
    }
}

/**
 * The greeting to user
 * @author fangang
 */
public class GreetingToUser {
    private String user;
    /**
     * The constructor with user
     * @param user
     */
    public GreetingToUser(String user){
        this.user = user;
    }
    /**
     * @return greeting to user
     */
    public String getGreeting(){
        return "Hi, "+user+". ";
    }
}

/**
 * The greeting about time.
 * @author fangang
 */
public class GreetingAboutTime {
    private Date date;
    public GreetingAboutTime(Date date){
        this.date = date;
    }
    /**
     * @param date
     * @return the hour of day
     */
    private int getHour(Date date){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return calendar.get(Calendar.HOUR_OF_DAY);
    }
    /**
    * @return the greeting about time
    */
    public String getGreeting(){
        int hour = getHour(date);
        if(hour>=6 && hour<12){
            return "Good morning!";
        }else if(hour>=12 && hour<19){
            return "Good afternoon!";
        }else{
            return "Good night!";
        }
    }
}

系统重构到这一步,我们来看看用户关于时间问候语部分的变更需求:问候需要更加精细,如中午问候“Good noon! ”、傍晚问候“Good evening! ”、午夜问候“Good midnight! ”。除此之外,用户希望在一些特殊的节日,如新年问候“Happy new year! ”、情人节问候“Happy valentine’s day! ”、三八妇女节问候“Happy women’s day! ”,等等。此时我们发现,我们对时间问候语的变更不再需要修改HelloWorld或其它什么类,而是仅仅专注于修改GreetingAboutTime就可以了,这就是因重构带来的改善。 同时,我们发现,过去只需getHour()就足够,而现在却需要getMonth()与getDay()。随着程序复杂度的提升,我们适时进行了一次重构,将与时间相关的程序抽取到一个新类DateUtil中,就可以顺利地改写原有的时间问候语程序:

/**
 * The utility of time
 * @author fangang
 */
public class DateUtil {
    private Calendar calendar;
    /**
     * @param date
     */
    public DateUtil(Date date){
        calendar = Calendar.getInstance();
        calendar.setTime(date);
    }
    /**
     * @return the hour of day
     */
    public int getHour(){
        return calendar.get(Calendar.HOUR_OF_DAY);
    }
    /**
     * @return the month of date
     */
    public int getMonth(){
        return calendar.get(Calendar.MONTH)+1;
    }
    /**
     * @return the day of month
    */
    public int getDay(){
        return calendar.get(Calendar.DAY_OF_MONTH);
    }
}

/**
 * The greeting about time.
 * @author fangang
 */
public class GreetingAboutTime {
    private Date date;
    public GreetingAboutTime(Date date){
        this.date = date;
    }
    /**
     * @return the greeting about time
     */
    public String getGreeting(){
        DateUtil dateUtil = new DateUtil(date);
        int month = dateUtil.getMonth();
        int day = dateUtil.getDay();
        int hour = dateUtil.getHour();

        if(month==1 && day==1) return "Happy new year! ";
        if(month==1 && day==14) return "Happy valentine's day! ";
        if(month==3 && day==8) return "Happy women's day! ";
        if(month==5 && day==1) return "Happy Labor day! ";
        ......

        if(hour>=6 && hour<12) return "Good morning!";
        if(hour==12) return "Good noon! ";
        if(hour>=12 && hour<19) return "Good afternoon! ";
        if(hour>=19 && hour<22) return "Good evening! ";
        return "Good night! ";
    }
}

最后,我们建立user表存放用户信息,创建UserDao类为GreetingToUser提供用户信息访问的服务;我们将greetingRule表存放问候语库,创建由GreetingRuleDao接口及其实现类,为GreetingAboutTime提供一个可扩展的、支持多语言的问候语库(如图所示)。所有这一切都是在现有基础上,通过小步快跑的方式一步一步演变的。 图 HelloWorld的设计图

小步快跑是一种逐步进化式的程序设计过程,它要求我们不要一次做太多的设计,不要想着一步到位完成开发。每次完成一个小设计,实现一个小需求,对原有系统进行一个小修改,然后立即运行、测试、验证。它是一个十分新颖的概念,也许你一时半会儿还不能完全领悟,或者欣然接受,因为它太前卫了,与我们传统的思维大相径庭。然而,没有关系,就像一部精彩的小说需要慢慢揭开它神秘的面纱,你会慢慢领悟,终会接受。总之,活在当下,做现在的设计,将来的事情将来再考虑。