重构之我见

      所谓重构(Refactoring)就是在不改变软件现有功能的基础上,通过调整程序代码改善软件的质量、性能,使其程序的设计模式和架构更趋合理,提高软件的扩展性和维护性。

      定义很明确,清楚,但是怎么证明重构真的改善了代码的质量,性能?怎么证明重构提高了软件的扩展性合并可维护性呢? 换句话说,怎么证明我们花在重构上的时间真的产生了价值?

      在软件代码的开发中,坏味道是公认的不好的代码,而设计模式,Clean Code是公认的好的代码,因此,如果能够把有坏味道的代码重构为使用模式的代码,或者符合Clean Code标准的代码, 那么就可以认为重构改善了代码的质量,提高了代码的可扩展性和可维护性,这个从不好的代码到好的代码就是重构产生的价值。
   
       从上面的论述中,可以看出,任何重构一定是基于代码坏味道的,因此,重构的基础是对代码坏味道的识别能力,任何不是针对代码坏味道的重构都是耍流氓。

       在做重构时,通常会涉及到3种类型的重构:
一是某个类内部的修改,包括重命名,抽取方法等等,这类重构很简单,有编程经验的程序员们基本上都会;
二是把多个类的相同代码,往上提,从而整合出一个类层次结构,以方便代码复用,提高扩展性等等。
三是把多个类中功能相似的代码提取出来,归总到一个类中。

接下来就通过一个例子来看一下这整个的重构过程:有一个停车场ParkingLot,,这是第一阶段的需求,对于ParkingLot的实现如下:
public class ParkingLot {
    private Map<ParkingTicket, Car> carports = new HashMap<ParkingTicket, Car>();
    private int capacity;
    private int spaces;

    public ParkingLot(int capacity) {
        this.capacity = capacity;
        this.spaces = capacity;
    }

    public ParkingTicket park(Car car) {
        if(spaces == 0){
            return null;
        }
        ParkingTicket ticket = new ParkingTicket();
        carports.put(ticket, car);
        spaces--;
        return ticket;
    }

    public boolean isFull() {
        return spaces == 0;
    }

    public Car unpark(ParkingTicket ticket) {
        return carports.get(ticket);
    }
}
首先,对于park()方法的逻辑有点多,进行第一步重构,在park()方法的整个逻辑应该是获取一个停车位,然后把车停进去。因此可以把park方法按如下修改:
      public ParkingTicket park(Car car) {
        return isFull() ? null : parkCarIntoCarport(car);
    }

    public boolean isFull() {
        return spaces == 0;
    }

    private ParkingTicket parkCarIntoCarport(Car car) {
        ParkingTicket ticket = new ParkingTicket();
        carports.put(ticket, car);
        spaces--;
        return ticket;
    }
,接下来,第二阶段的需求,有一个泊车小弟Parker,管理一批停车场ParkingLot,每次有车来,他都按照顺序一个停车场停满再停下一个停车场。实现这个Parker的代码如下:
public class Parker {
    private List<ParkingLot> parkingLotList = new ArrayList<ParkingLot>();

    public void addParkingLot(ParkingLot parkingLot) {
        parkingLotList.add(parkingLot);
    }

    public ParkingTicket park(Car car) {
        for (ParkingLot parkingLot : parkingLotList) {
            if (!parkingLot.isFull()) {
                return parkingLot.park(car);
            }
        }
        return null;
    }

    public Car unpark(ParkingTicket ticket) {
        for (ParkingLot parkingLot : parkingLotList) {
            if (parkingLot.unpark(ticket) != null) {
                return parkingLot.unpark(ticket);
            }
        }
        return null;
    }
}

第三阶段需求,有另外一个泊车小弟VacancyParker, 它也管理一批停车场,但是他放车的顺序是按照停车场空置率,哪个停车场空置率高就放哪个停车场。于是VacancyParker的代码如下:
public class VacancyParker {
    private List<ParkingLot> parkingLotList = new ArrayList<ParkingLot>();

    public void addParkingLot(ParkingLot parkingLot) {
        parkingLotList.add(parkingLot);
    }

    public ParkingTicket park(Car car) {
        ParkingLot choosedLot = null;
        double vacancyRate = 0;
        for (ParkingLot parkingLot : parkingLotList) {
            if (parkingLot.vacancyRate() > vacancyRate) {
                vacancyRate = parkingLot.vacancyRate();
                choosedLot = parkingLot;
            }
        }

        return choosedLot.park(car);
    }

    public Car unpark(ParkingTicket ticket) {
        for (ParkingLot parkingLot : parkingLotList) {
            if (parkingLot.unpark(ticket) != null) {
                return parkingLot.unpark(ticket);
            }
        }
        return null;
    }
}
,在这个阶段,我们把VacancyParker的park()方法整理一下,如下:
    public ParkingTicket park(Car car) {
        ParkingLot choosedLot = choosePark();
        return choosedLot == null ? null : choosedLot.park(car);
    }

    private ParkingLot choosePark() {
        ParkingLot choosedLot = null;
        double vacancyRate = 0;
        for (ParkingLot parkingLot : parkingLotList) {
            if (parkingLot.vacancyRate() > vacancyRate) {
                vacancyRate = parkingLot.vacancyRate();
                choosedLot = parkingLot;
            }
        }
        return choosedLot;
    }
整理之后,就能发现一个很明显的代码异味,就是Parker和VacancyParker的代码重复特别多,除了在选择ParkingLot的逻辑上之外,其他的代码都是一样的,因此我们可以把代码重构为如下的类层次结构:

模式就出来了。
      第四阶段需求,有一个ParkerManager,它负责管理几个Parker,为了了解他管理的整个停车场的状态,需要获取其管理的所有停车场的状态报告,按如下格式:
parkerManager:
    Parker1:
        ParkingLot1:10/21 (注:共有21个车位,˙其中10个有车)
        ParkingLot2:8/21
    Parker2:
        ParkingLot1:10/21
        ParkingLot2:8/21
    ..........
实现这个需求时,第一阶段的代码如下:
public class ParkerManager {
    private List<Parker> parkers = new ArrayList<Parker>();

    public void addParker(Parker parker) {
        parkers.add(parker);
    }

    public String report() {
        StringBuilder stringBuilder = new StringBuilder().append("manager:\n");
        for (Parker parker : parkers) {
            stringBuilder.append(parker.report());
        }
        return stringBuilder.toString();
    }
}

Parker中的关于report()方法的代码如下:
public String report() {
        StringBuilder stringBuilder = new StringBuilder().append(TWO_SPACE_INDENT).append("parker:\n");
        for (ParkingLot parkingLot : parkingLotList) {
            stringBuilder.append(parkingLot.info());
        }
        return stringBuilder.toString();
    }

ParkingLot中关于report的方法如下:
public String info() {
        StringBuilder stringBuilder = new StringBuilder()
                                            .append(FOUR_SPACE_INDENT)
                                            .append("parkinglot:")
                                            .append(capacity - spaces + "/" + capacity)
                                            .append("\n");
        return stringBuilder.toString();
    }
在完成这个代码之后,有一个不易被发现的Bad Smell,当我们需要修改report的方式的时候,我们需要遍历整个代码结构树上的类,并做出修改。这种情况下,有一个通用的设计模式可以解决这类问题,这就是Visitor模式。即为类层次结构中全部,或部分成员添加一个report方法,该report方法接受一个Report类型的参数(根据需求,可使用不同的实现类),然后,把该成员的report功能委托给Report去做。经过提取参数,pull members up等重构手法对代码拾掇以后,类的层次结构变为:


如果,还有新的需求,我们可以继续通过如上的方法,明确需求-》查找合适的设计模式 -》重构出这个设计模式,保持一个干净的代码环境。

总结:
1、简单设计,以最快的速度实现当前需求
2、TDD保证重构信心
3、模式是重构出来
4、保持对代码异味的高度敏感性

本文中的示例代码地址:https://github.com/xianlinbox/ChessDemo/tree/master/src/main/java/parkinglot

猜你喜欢

转载自ningandjiao.iteye.com/blog/1622580