论程序员的自我修养——重构(2)

重构与设计模式

        设计模式是一个好东西,它能把复杂的代码变得简单,让系统具有更高的扩展性。但它在某程度上会增加代码结构的复杂性,因此对于规模不大的系统,滥用设计模式,只会让系统变得莫名的臃肿。不过随着系统不断地增加功能,变得日益庞大的时候,设计模式便是磨练出一个良好系统的利器。
        重构便是一个系统从没有设计模式到有设计模式之间的桥梁。在系统还相对简单的时候,可能还不会意识到需要使用设计模式,或者根本不需要使用设计模式。但随着系统变大,复杂的代码和笨重的程序会让系统总有一天崩溃的。这时候,重构代码成了势在必行的了。通过重构,在代码中引入合适的设计模式,能有效延长一个系统的寿命。
        下面通过一个简单的例子看看程序是如何从没有使用设计模式,通过重构使用上设计模式的。对于一个表示员工的类,当系统规模还很小,或者第一遍设计的时候,可能会被设计成如下的样子:
public enum EmployeeType
{
    FullTime,
    PartTime,
    Intern
}

public class Employee
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }
    public EmployeeType Type { get; set; }

    public double Salary
    {
        get
        {
            switch (this.Type)
            {
                case EmployeeType.FullTime: return 10000;
                case EmployeeType.PartTime: return 5000;
                case EmployeeType.Intern: return 1000;
                default: throw new ArgumentException("非法的员工类型");
            }
        }
    }
}
        对于工资计算还比较简单的时候,以上的代码可能还不错,结构简单清晰。但如果由于需求的改变,计算工资的算法变复杂了以后,上述程序就存在很多风险了。由于不同类型的员工工资算法都写在了同一个方法中,当需要修改正式员工工资计算逻辑的时候,也就不得不一同把其他类型员工工资的计算逻辑也看完,这严重影响了调试与修改这段逻辑的效率,而且遇上粗心的程序员,甚至有可能在修改的过程中把其他类型员工的逻辑都以外修改了。
        因此在添加新功能之前重构这段代码成了无法避免的事情了。对于面向对象语言,我们很容易便想到了以下的重构方式:
public abstract class Employee
{
    public string ID { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }

    public abstract double Salary { get; }
}

public class FullTimeEmployee : Employee
{
    public override double Salary
    {
        get { return 10000; }
    }
}

public class PartTimeEmployee : Employee
{
    public override double Salary
    {
        get { return 5000; }
    }
}

public class InternEmployee : Employee
{
    public override double Salary
    {
        get { return 1000; }
    }
}
        感谢面向对象的继承特性,让我们很方便地把需要分离的逻辑分离出来,而且对调用方的改动并不大。除了分离逻辑,这种重构方式也能让增加员工类型变得更方便,不会影响到现有程序。
        这种重构方式在大部分场景都是能运行得很好的,知道遇到了这种场景:实习员工转正了。是的,这种重构方式没有办法在员工对象已经被创建了以后改变员工的类型,而在重构之前是可以的。不能说上述的重构是失败的,因为系统可能本来就没有这种场景,但作为好的程序员,在力所能及的范围内适当提高系统的灵活性还是有必要的,所以上述的代码还能有另一种重构的方法:
public enum EmployeeType
{
    FullTime,
    PartTime,
    Intern
}

public class Employee
{
    private ISalaryCalculator salaryCalculator;

    public string ID { get; set; }
    public string Name { get; set; }
    public string Gender { get; set; }
    public EmployeeType EmployeeType { get; set; }

    public Employee(EmployeeType employeeType)
    {
        this.EmployeeType = employeeType;
    }

    public double Salary
    {
        get
        {
            return SalaryCalculatorFactory.CreateSalaryCalculator(this.EmployeeType).GetSalary();
        }
    }
}

public static class SalaryCalculatorFactory
{
    public static ISalaryCalculator CreateSalaryCalculator(EmployeeType employTime)
    {
        switch (employTime)
        {
            case EmployeeType.FullTime: return new FullTimeSalaryCalculator();
            case EmployeeType.PartTime: return new PartTimeSalaryCalculator();
            case EmployeeType.Intern: return new InternSalaryCalculator();
            default: throw new ArgumentException();
        }
    }
}

public interface ISalaryCalculator
{
    double GetSalary();
}

public class FullTimeSalaryCalculator : ISalaryCalculator
{
    public double GetSalary()
    {
        return 10000;
    }
}

public class PartTimeSalaryCalculator : ISalaryCalculator
{
    public double GetSalary()
    {
        return 5000;
    }
}

public class InternSalaryCalculator : ISalaryCalculator
{
    public double GetSalary()
    {
        return 1000;
    }
}
        熟悉设计模式的人应该一眼就能看出来,上面的代码使用了策略模式。这种重构方式不仅能把该分离的代码分离到不同的类中,也能保证员工对象在新建以后依然能改变员工类型。两者比较,策略模式的重构方式比继承的重构方式灵活性更高,但带来的代价是程序结构变得更复杂了。忽视实际情况比较两者的优劣是没有意义的,程序员是应该学会在适当的时候使用适当的方法,仅此而已。

重构与开发

        既然重构是对已有代码的改进,那么肯定有人觉得重构就像测试一样,是开发完成以后才需要做的事情。但在我看来,与单元测试一样,重构其实是开发的一部分。无论何时,只要发现了代码的臭味,都应该像有强迫症一样立即重构代码,直到臭味被消除了,才继续新功能的开发。那位为什么需要这么迫切地重构代码呢?
        第一,保证所有的臭味都被消除。与画家作画的灵感一样,很多时候重构的想法也是灵光一闪而已。如果当时不立马行动把代码重构,以后就有可能忘记,忘记的后果便是这个臭味会一直存在与系统当中,运气不好的话,系统就会带着这个臭味直到它消亡。
        第二,臭味是会传播的。是的,代码的臭味与事物的臭味一样,如果不把源头清理,臭味会一直蔓延。以上面员工对象为例,如果刚开始不重构,随着系统开发出来的功能越来越多,再想把它重构成继承方式就会越来越困难。因为引用了这个类的模块可能会很多,而且也没办法保证所有模块的引用方式都与设想的引用方式一致。这种阻力有时候会直接扑灭人们重构的火焰的。
        第三,人们通常不会无缘无故review代码。无论一个人多么热爱写程序,我相信会平时有空就review一下代码的人应该不多。我们通常都是在开发的过程中看代码的,因此也就在开发的过程中我们才有机会去重构代码。要么在一个系统完成后还去重构系统的代码,谁有这个动力呢?

重构与程序员

        很多微小的习惯对人的影响对会很大,重构也是这样。当程序员养成重构的习惯,就等于养成了思考的习惯。不断地思考,不断地改进代码,然后不断地进步。IT的世界日新月异,无论你愿不愿意,这个世界都会推着你前进。主动前进总比被动前进好,让这个世界推着你,总有一天这个世界就会放弃你。重构是一个修炼内功很好的方法,如果有志于把编程作为事业,那么就应该养成重构的习惯。

你可能感兴趣的:(程序员感悟,重构,设计模式,程序员)